fix: simplify activity curator to chronological text format

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 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
Mohamed Boudra
2025-10-21 19:49:44 +02:00
co-authored by Claude Happy
parent 13f8e31295
commit 6106e5f65f
@@ -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<string, typeof toolCalls>();
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.";
}