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:
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.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user