diff --git a/vscode-extension/src/acpAgentActor.ts b/vscode-extension/src/acpAgentActor.ts index 4d02f7f6..9b71fbfd 100644 --- a/vscode-extension/src/acpAgentActor.ts +++ b/vscode-extension/src/acpAgentActor.ts @@ -39,6 +39,7 @@ export interface SlashCommandInfo { */ export interface AcpAgentCallbacks { onAgentText: (agentSessionId: string, text: string) => void; + onUserText: (agentSessionId: string, text: string) => void; onAgentComplete: (agentSessionId: string) => void; onRequestPermission?: ( params: acp.RequestPermissionRequest, @@ -171,6 +172,17 @@ class SymposiumClient implements acp.Client { } break; } + case "user_message_chunk": { + if (update.content.type === "text") { + const text = update.content.text; + logger.debug("user", "Text chunk", { + length: text.length, + text: text.length > 50 ? text.slice(0, 50) + "..." : text, + }); + this.callbacks.onUserText(params.sessionId, update.content.text); + } + break; + } } } diff --git a/vscode-extension/src/chatViewProvider.ts b/vscode-extension/src/chatViewProvider.ts index c6069cda..b907ac6c 100644 --- a/vscode-extension/src/chatViewProvider.ts +++ b/vscode-extension/src/chatViewProvider.ts @@ -126,6 +126,32 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { }); } }, + onUserText: (agentSessionId, text) => { + const receiveTime = Date.now(); + const tabId = this.#agentSessionToTab.get(agentSessionId); + if (tabId) { + // Capture for testing if enabled + if (this.#testResponseCapture.has(tabId)) { + this.#testResponseCapture.get(tabId)!.push(text); + } + + logger.debug("perf", "Received chunk from user", { + receiveTime, + textLength: text.length, + }); + this.#sendToWebview({ + type: "user-text", + tabId, + text, + timestamp: receiveTime, + }); + const sendTime = Date.now(); + logger.debug("perf", "Sent chunk to webview", { + sendTime, + delay: sendTime - receiveTime, + }); + } + }, onAgentComplete: (agentSessionId) => { const tabId = this.#agentSessionToTab.get(agentSessionId); if (tabId) { diff --git a/vscode-extension/src/symposium-webview.ts b/vscode-extension/src/symposium-webview.ts index 449bc1be..eda9bf56 100644 --- a/vscode-extension/src/symposium-webview.ts +++ b/vscode-extension/src/symposium-webview.ts @@ -687,6 +687,27 @@ window.addEventListener("message", (event: MessageEvent) => { console.log( `[PERF] Append: ${appendEnd - appendStart}ms, UI update: ${uiUpdateEnd - uiUpdateStart}ms, total: ${uiUpdateEnd - receiveTime}ms`, ); + } else if (message.type === "user-text") { + const extensionDelay = message.timestamp + ? receiveTime - message.timestamp + : "unknown"; + console.log( + `[PERF] Webview received chunk at ${receiveTime}, extension->webview delay=${extensionDelay}ms, length=${message.text.length}`, + ); + + // We don't expect a stream here - so just add an item + const newMessageId = uuidv4(); + mynahUI.addChatItem(message.tabId, { + type: ChatItemType.SYSTEM_PROMPT, + body: message.text, + }); + + // Assume that all messages here count as *prompts* + mynahUI.updateStore(message.tabId, { + loadingChat: true, + }); + + saveState(); } else if (message.type === "agent-complete") { // Hide loading/thinking indicator mynahUI.updateStore(message.tabId, {