From 6106e5f65f2a8becf71c0dbfab6a7aa31493fd51 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 21 Oct 2025 19:49:44 +0200 Subject: [PATCH] fix: simplify activity curator to chronological text format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove grouping and clever formatting. Just convert activity feed to chronological text. Changes: - Process updates in order (chronological) - Buffer message/thought chunks and flush when needed - Output tool calls and plans as they appear - No grouping, no reorganizing, no sections - Simple text format: messages, [Tool] status, [Plan] entries 🤖 Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --- .../src/server/acp/activity-curator.ts | 172 +++++++----------- 1 file changed, 62 insertions(+), 110 deletions(-) diff --git a/packages/voice-assistant/src/server/acp/activity-curator.ts b/packages/voice-assistant/src/server/acp/activity-curator.ts index 4cc0b17d0..4253783bc 100644 --- a/packages/voice-assistant/src/server/acp/activity-curator.ts +++ b/packages/voice-assistant/src/server/acp/activity-curator.ts @@ -1,17 +1,12 @@ import type { AgentUpdate } from "./types.js"; /** - * Curate agent activity updates into a human-readable, token-efficient format - * Consolidates message chunks, formats tool calls, and structures output cleanly + * Convert agent activity updates into chronological text format */ export function curateAgentActivity(updates: AgentUpdate[]): string { - const sections: string[] = []; - - // Group updates by type and consolidate - let currentMessage = ""; - let currentThought = ""; - const toolCalls: Array<{ timestamp: Date; data: any }> = []; - const plans: Array<{ timestamp: Date; data: any }> = []; + const lines: string[] = []; + let messageBuffer = ""; + let thoughtBuffer = ""; for (const update of updates) { const updateType = update.notification.update.sessionUpdate; @@ -20,121 +15,78 @@ export function curateAgentActivity(updates: AgentUpdate[]): string { switch (updateType) { case "agent_message_chunk": if (content?.type === "text" && content?.text) { - currentMessage += content.text; + messageBuffer += content.text; } break; case "agent_thought_chunk": if (content?.type === "text" && content?.text) { - currentThought += content.text; + thoughtBuffer += content.text; } break; case "tool_call": - case "tool_call_update": - toolCalls.push({ - timestamp: update.timestamp, - data: update.notification.update, - }); - break; + case "tool_call_update": { + // Flush any buffered message before tool call + if (messageBuffer.trim()) { + lines.push(messageBuffer.trim()); + messageBuffer = ""; + } + if (thoughtBuffer.trim()) { + lines.push(`[Thought: ${thoughtBuffer.trim()}]`); + thoughtBuffer = ""; + } - case "plan": - plans.push({ - timestamp: update.timestamp, - data: (update.notification.update as any).entries, - }); + const data = update.notification.update as any; + const title = data.title || data.kind || "Tool"; + const status = data.status || "unknown"; + + lines.push(`\n[${title}] ${status}`); + + if (data.rawInput && Object.keys(data.rawInput).length > 0) { + lines.push(`Input: ${JSON.stringify(data.rawInput)}`); + } + + if (data.rawOutput && Object.keys(data.rawOutput).length > 0) { + lines.push(`Output: ${JSON.stringify(data.rawOutput)}`); + } + break; + } + + case "plan": { + // Flush any buffered content before plan + if (messageBuffer.trim()) { + lines.push(messageBuffer.trim()); + messageBuffer = ""; + } + if (thoughtBuffer.trim()) { + lines.push(`[Thought: ${thoughtBuffer.trim()}]`); + thoughtBuffer = ""; + } + + const entries = (update.notification.update as any).entries; + lines.push("\n[Plan]"); + for (const entry of entries) { + lines.push(`- [${entry.status}] ${entry.content}`); + } + break; + } + + case "user_message_chunk": + if (content?.type === "text" && content?.text) { + lines.push(`User: ${content.text}`); + } break; } } - // Format consolidated message - if (currentMessage.trim()) { - sections.push("## Agent Response\n\n" + currentMessage.trim()); + // Flush any remaining buffered content + if (messageBuffer.trim()) { + lines.push(messageBuffer.trim()); + } + if (thoughtBuffer.trim()) { + lines.push(`[Thought: ${thoughtBuffer.trim()}]`); } - // Format consolidated thoughts - if (currentThought.trim()) { - sections.push( - "## Agent Thoughts\n\n```\n" + currentThought.trim() + "\n```" - ); - } - - // Format tool calls - if (toolCalls.length > 0) { - const toolSection = ["## Tool Calls\n"]; - - // Group tool calls by toolCallId to show lifecycle - const toolGroups = new Map(); - for (const call of toolCalls) { - const toolCallId = call.data.toolCallId || "unknown"; - if (!toolGroups.has(toolCallId)) { - toolGroups.set(toolCallId, []); - } - toolGroups.get(toolCallId)!.push(call); - } - - for (const [toolCallId, calls] of toolGroups) { - const latestCall = calls[calls.length - 1]; - const data = latestCall.data; - - toolSection.push(`\n### ${data.title || data.kind || "Tool"}`); - toolSection.push(`- **Status**: ${data.status || "unknown"}`); - toolSection.push(`- **Call ID**: ${toolCallId.slice(0, 8)}...`); - - if (data.rawInput && Object.keys(data.rawInput).length > 0) { - toolSection.push(`- **Input**: ${JSON.stringify(data.rawInput, null, 2)}`); - } - - if (data.rawOutput && Object.keys(data.rawOutput).length > 0) { - toolSection.push(`- **Output**: ${JSON.stringify(data.rawOutput, null, 2)}`); - } - - if (data.content && data.content.length > 0) { - toolSection.push(`- **Content**: ${formatToolContent(data.content)}`); - } - } - - sections.push(toolSection.join("\n")); - } - - // Format plans - if (plans.length > 0) { - const latestPlan = plans[plans.length - 1]; - const planSection = ["## Plan\n"]; - - for (const entry of latestPlan.data) { - const icon = - entry.status === "completed" ? "✅" : - entry.status === "in_progress" ? "🔄" : - "⏳"; - - const priority = - entry.priority === "high" ? "🔴" : - entry.priority === "medium" ? "🟡" : - "🟢"; - - planSection.push(`${icon} ${priority} ${entry.content}`); - } - - sections.push(planSection.join("\n")); - } - - return sections.join("\n\n---\n\n") || "No activity to display."; -} - -/** - * Format tool call content into readable text - */ -function formatToolContent(content: any[]): string { - return content - .map((item) => { - if (item.type === "text") { - return item.text; - } - if (item.type === "image") { - return `[Image: ${item.source || "embedded"}]`; - } - return JSON.stringify(item); - }) - .join("\n"); + return lines.join("\n\n") || "No activity to display."; }