diff --git a/channels.ts b/channels.ts index 40d179d..1f45ad5 100644 --- a/channels.ts +++ b/channels.ts @@ -13,8 +13,8 @@ export interface ImageAttachment { export interface NotificationParams { sessionId: string; + sessionAlias?: string; context?: string; - feedbackUrl: string; } export interface FeedbackCallback { @@ -23,8 +23,8 @@ export interface FeedbackCallback { export interface FYIParams { sessionId: string; + sessionAlias?: string; context: string; - feedbackUrl: string; } export interface NotificationChannel { @@ -307,7 +307,7 @@ export class TelegramChannel implements NotificationChannel { } const messageParts = this.formatNotificationParts(params); - const keyboard = this.buildKeyboard(params.sessionId, params.feedbackUrl); + const keyboard = this.buildKeyboard(params.sessionId); for (const chatId of this.registeredChatIds) { try { @@ -408,17 +408,19 @@ export class TelegramChannel implements NotificationChannel { private formatNotificationParts(params: NotificationParams): string[] { const TELEGRAM_MAX = 4000; // leave margin under 4096 for safety - const header = `🤖 Agent is waiting for feedback\nSession: ${this.escapeHtml(params.sessionId.slice(0, 20))}â€Ļ\n\n`; - const footer = `\nOpen in browser`; + const sessionLabel = params.sessionAlias && params.sessionAlias !== params.sessionId + ? this.escapeHtml(params.sessionAlias) + : `${this.escapeHtml(params.sessionId.slice(0, 20))}â€Ļ`; + const header = `🤖 Agent is waiting for feedback\nSession: ${sessionLabel}\n\n`; if (!params.context) { - return [header + footer]; + return [header.trimEnd()]; } const contextHtml = this.markdownToTelegramHtml(params.context); // If it all fits in one message, send as one - const singleMessage = header + contextHtml + footer; + const singleMessage = header + contextHtml; if (singleMessage.length <= TELEGRAM_MAX) { return [singleMessage]; } @@ -453,13 +455,7 @@ export class TelegramChannel implements NotificationChannel { current = chunk; } - // Append footer to last chunk if it fits, otherwise make a new part - if (current.length + footer.length <= TELEGRAM_MAX) { - parts.push(current + footer); - } else { - parts.push(current); - parts.push(footer); - } + parts.push(current); return parts; } @@ -467,8 +463,11 @@ export class TelegramChannel implements NotificationChannel { private formatFYIParts(params: FYIParams): string[] { const TELEGRAM_MAX = 4000; - const header = `📋 Agent Status Update\nSession: ${this.escapeHtml(params.sessionId.slice(0, 20))}â€Ļ\n\n`; - const footer = `\n\nâ„šī¸ No response needed — this is an informational update.\nOpen in browser`; + const sessionLabel = params.sessionAlias && params.sessionAlias !== params.sessionId + ? this.escapeHtml(params.sessionAlias) + : `${this.escapeHtml(params.sessionId.slice(0, 20))}â€Ļ`; + const header = `📋 Agent Status Update\nSession: ${sessionLabel}\n\n`; + const footer = `\n\nâ„šī¸ No response needed — this is an informational update.`; const contextHtml = this.markdownToTelegramHtml(params.context); const singleMessage = header + contextHtml + footer; @@ -515,10 +514,7 @@ export class TelegramChannel implements NotificationChannel { return parts; } - private buildKeyboard( - sessionId: string, - _feedbackUrl: string - ): InlineKeyboard { + private buildKeyboard(sessionId: string): InlineKeyboard { return new InlineKeyboard() .text("👍 Approve", `fb:approve:${sessionId.slice(0, 50)}`) .text("👎 Reject", `fb:reject:${sessionId.slice(0, 50)}`) diff --git a/feedback-html-composer-history-script.ts b/feedback-html-composer-history-script.ts index d797aa5..4bf220d 100644 --- a/feedback-html-composer-history-script.ts +++ b/feedback-html-composer-history-script.ts @@ -469,6 +469,36 @@ export const FEEDBACK_HTML_COMPOSER_HISTORY_SCRIPT = ` updateHistoryCollapseUi(); updateHistoryJumpVisibility(); + // ── Agent context panel ── + let agentContextCollapsed = localStorage.getItem(STORAGE_AGENT_CONTEXT_COLLAPSED) === '1'; + showAgentContextEl.checked = localStorage.getItem(STORAGE_SHOW_AGENT_CONTEXT) === '1'; + let lastAgentContext = null; + + function updateAgentContextPanel(context) { + lastAgentContext = context; + const show = showAgentContextEl.checked && context; + agentContextPanelEl.style.display = show ? '' : 'none'; + if (show) { + agentContextContentEl.textContent = context; + agentContextContentEl.classList.toggle('collapsed', agentContextCollapsed); + agentContextToggleEl.textContent = agentContextCollapsed ? 'Expand' : 'Collapse'; + agentContextToggleEl.setAttribute('aria-expanded', String(!agentContextCollapsed)); + } + } + + showAgentContextEl.addEventListener('change', () => { + localStorage.setItem(STORAGE_SHOW_AGENT_CONTEXT, showAgentContextEl.checked ? '1' : '0'); + updateAgentContextPanel(lastAgentContext); + }); + + agentContextToggleEl.addEventListener('click', () => { + agentContextCollapsed = !agentContextCollapsed; + localStorage.setItem(STORAGE_AGENT_CONTEXT_COLLAPSED, agentContextCollapsed ? '1' : '0'); + agentContextContentEl.classList.toggle('collapsed', agentContextCollapsed); + agentContextToggleEl.textContent = agentContextCollapsed ? 'Expand' : 'Collapse'; + agentContextToggleEl.setAttribute('aria-expanded', String(!agentContextCollapsed)); + }); + // ── Audio context management ── function getAudioContext() { if (audioContext) return audioContext; diff --git a/feedback-html.ts b/feedback-html.ts index b4572c4..24961ce 100644 --- a/feedback-html.ts +++ b/feedback-html.ts @@ -161,10 +161,58 @@ export const FEEDBACK_HTML = ` 70% { box-shadow: 0 0 0 8px rgba(63,185,80,0); } 100% { box-shadow: 0 0 0 0 rgba(63,185,80,0); } } - @media (prefers-reduced-motion: reduce) { + @media (prefers-reduced-motion: reduce) { .wait-banner.waiting { animation: none; } * { transition-duration: 0.01ms !important; } } + + /* Agent context panel */ + .agent-context-panel { + margin: 0.5rem 0 0.75rem; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg-surface); + overflow: hidden; + transition: border-color var(--transition-normal); + } + .agent-context-panel:has(.agent-context-content:not(:empty)) { + border-color: rgba(121,192,255,0.35); + } + .agent-context-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.45rem 0.75rem; + background: rgba(121,192,255,0.06); + border-bottom: 1px solid var(--border); + font-size: 0.78rem; + color: var(--fg-muted); + } + .agent-context-title { + font-weight: 500; + color: #79c0ff; + } + :root[data-theme="light"] .agent-context-title { + color: #0969da; + } + :root[data-theme="light"] .agent-context-panel:has(.agent-context-content:not(:empty)) { + border-color: rgba(9,105,218,0.3); + } + :root[data-theme="light"] .agent-context-header { + background: rgba(9,105,218,0.04); + } + .agent-context-content { + padding: 0.6rem 0.75rem; + font-size: 0.82rem; + line-height: 1.55; + max-height: 300px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-word; + color: var(--fg); + } + .agent-context-content:empty { display: none; } + .agent-context-content.collapsed { display: none; } textarea { width: 100%; min-height: 200px; @@ -341,6 +389,13 @@ ${FEEDBACK_HTML_ENHANCED_STYLES}
Type your feedback below. Press Cmd+Enter to submit. Connecting...
ACTIVE_SESSION_INFO
Checking agent wait state...
+
@@ -411,7 +466,8 @@ ${FEEDBACK_HTML_ENHANCED_STYLES}

Settings

- + +