diff --git a/packages/app/src/components/tool-call-details.tsx b/packages/app/src/components/tool-call-details.tsx index d763de01c..338571804 100644 --- a/packages/app/src/components/tool-call-details.tsx +++ b/packages/app/src/components/tool-call-details.tsx @@ -244,31 +244,62 @@ function resolveSubAgentFallbackHeader( interface SubAgentDetailProps { log: string; - actions: Extract["actions"]; childSessionId: string | null | undefined; subAgentType: string | null | undefined; description: string | null | undefined; ds: DetailStyles; } -function buildSubAgentActionLog(actions: SubAgentDetailProps["actions"]): string { - return actions - .map((action) => - action.summary ? `[${action.toolName}] ${action.summary}` : `[${action.toolName}]`, - ) - .join("\n"); +interface SubAgentActivityRow { + index: number; + toolName: string; + summary?: string; } -function stripSubAgentActionLog(log: string, actions: SubAgentDetailProps["actions"]): string { - const actionLog = buildSubAgentActionLog(actions); - const trimmedLog = log.replace(/^\n+/, ""); - if (!actionLog || !trimmedLog.startsWith(actionLog)) { - return trimmedLog; +interface ParsedSubAgentLog { + actions: SubAgentActivityRow[]; + remainingLog: string; +} + +function parseBracketedSubAgentLine(line: string, index: number): SubAgentActivityRow | null { + const match = line.match(/^\[([^\]]+)\](?:\s+(.*))?$/); + if (!match) { + return null; } - return trimmedLog.slice(actionLog.length).replace(/^\n+/, ""); + const toolName = match[1]?.trim(); + if (!toolName) { + return null; + } + const summary = match[2]?.trim(); + return { + index, + toolName, + ...(summary ? { summary } : {}), + }; } -function SubAgentActionRow({ action }: { action: SubAgentDetailProps["actions"][number] }) { +function parseSubAgentLog(log: string): ParsedSubAgentLog { + const actions: SubAgentActivityRow[] = []; + const remainingLines: string[] = []; + for (const line of log.replace(/^\n+/, "").split("\n")) { + const normalizedLine = line.trim(); + if (!normalizedLine) { + continue; + } + const parsedAction = parseBracketedSubAgentLine(normalizedLine, actions.length + 1); + if (parsedAction) { + actions.push(parsedAction); + } else { + remainingLines.push(line); + } + } + return { + actions, + remainingLog: remainingLines.join("\n").replace(/^\n+/, ""), + }; +} + +function SubAgentActionRow({ action }: { action: SubAgentActivityRow }) { return ( @@ -324,13 +355,12 @@ function SubAgentLogText({ function SubAgentDetailSection({ log, - actions, childSessionId, subAgentType, description, ds, }: SubAgentDetailProps) { - const activityLog = stripSubAgentActionLog(log, actions); + const { actions, remainingLog } = useMemo(() => parseSubAgentLog(log), [log]); const fallbackHeader = resolveSubAgentFallbackHeader(subAgentType, description); const hasActions = actions.length > 0; return ( @@ -363,7 +393,7 @@ function SubAgentDetailSection({ ) : null} @@ -624,7 +654,6 @@ function buildDetailSections( { gap: theme.spacing[2], }, subAgentActionTool: { - minWidth: 56, fontFamily: Fonts.mono, fontSize: theme.fontSize.xs, color: theme.colors.foregroundMuted, diff --git a/packages/app/src/types/stream.test.ts b/packages/app/src/types/stream.test.ts index 5695a6454..48d12b28e 100644 --- a/packages/app/src/types/stream.test.ts +++ b/packages/app/src/types/stream.test.ts @@ -367,18 +367,6 @@ describe("stream reducer canonical tool calls", () => { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md\n[Bash] ls", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - { - index: 2, - toolName: "Bash", - summary: "ls", - }, - ], }, }), timestamp: new Date("2025-01-01T10:12:00Z"), @@ -406,18 +394,6 @@ describe("stream reducer canonical tool calls", () => { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md\n[Bash] ls", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - { - index: 2, - toolName: "Bash", - summary: "ls", - }, - ], }); const display = buildToolCallDisplayModel({ diff --git a/packages/app/src/utils/tool-call-detail-state.ts b/packages/app/src/utils/tool-call-detail-state.ts index 18d9500ac..5be0ee82d 100644 --- a/packages/app/src/utils/tool-call-detail-state.ts +++ b/packages/app/src/utils/tool-call-detail-state.ts @@ -49,9 +49,7 @@ function hasMeaningfulWorktreeSetupDetail( function hasMeaningfulSubAgentDetail( detail: Extract, ): boolean { - return Boolean( - detail.subAgentType || detail.description || detail.log || detail.actions.length > 0, - ); + return Boolean(detail.subAgentType || detail.description || detail.log); } function hasMeaningfulPlainTextDetail( diff --git a/packages/app/src/utils/tool-call-display.test.ts b/packages/app/src/utils/tool-call-display.test.ts index 5c14ca365..4347da799 100644 --- a/packages/app/src/utils/tool-call-display.test.ts +++ b/packages/app/src/utils/tool-call-display.test.ts @@ -48,13 +48,6 @@ describe("tool-call-display", () => { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - ], }, }); diff --git a/packages/app/src/utils/tool-call-icon.test.ts b/packages/app/src/utils/tool-call-icon.test.ts index 4a484be53..cc6588f84 100644 --- a/packages/app/src/utils/tool-call-icon.test.ts +++ b/packages/app/src/utils/tool-call-icon.test.ts @@ -23,7 +23,6 @@ describe("tool-call-icon", () => { subAgentType: "Explore", description: "Inspect repository", log: "[Read] README.md", - actions: [{ index: 1, toolName: "Read", summary: "README.md" }], }); expect(icon).toBe(iconMocks.Bot); diff --git a/packages/server/src/server/agent/activity-curator.test.ts b/packages/server/src/server/agent/activity-curator.test.ts index d4b5990ad..ceea22612 100644 --- a/packages/server/src/server/agent/activity-curator.test.ts +++ b/packages/server/src/server/agent/activity-curator.test.ts @@ -153,13 +153,6 @@ second line'`, subAgentType: "Explore", description: "Investigate repository", log: "[Read] README.md", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - ], }, }), toolCallItem({ @@ -171,18 +164,6 @@ second line'`, subAgentType: "Explore", description: "Investigate repository", log: "[Read] README.md\n[Bash] ls", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - { - index: 2, - toolName: "Bash", - summary: "ls", - }, - ], }, }), ]; @@ -237,35 +218,3 @@ second line'`, expect(curateAgentActivity([])).toBe("No activity to display."); }); }); - -describe("curateAgentActivityActions", () => { - it("converts curated activity lines into sub-agent action entries", async () => { - const { curateAgentActivityActions } = await import("./activity-curator.js"); - const timeline: AgentTimelineItem[] = [ - { type: "assistant_message", text: "Inspecting the repo" }, - toolCallItem({ - callId: "shell-1", - name: "shell", - detail: { - type: "shell", - command: "npm test", - output: "ok", - exitCode: 0, - }, - }), - ]; - - expect(curateAgentActivityActions(timeline)).toEqual([ - { - index: 1, - toolName: "Assistant", - summary: "Inspecting the repo", - }, - { - index: 2, - toolName: "Shell", - summary: "npm test", - }, - ]); - }); -}); diff --git a/packages/server/src/server/agent/activity-curator.ts b/packages/server/src/server/agent/activity-curator.ts index 2172c2e33..014e05062 100644 --- a/packages/server/src/server/agent/activity-curator.ts +++ b/packages/server/src/server/agent/activity-curator.ts @@ -7,14 +7,13 @@ const DEFAULT_MAX_ITEMS = 0; const MAX_TOOL_INPUT_CHARS = 400; const MAX_TOOL_SUMMARY_CHARS = 200; -interface ActivityAction { - toolName: string; - summary?: string; +interface ActivityCuratorOptions { + maxItems?: number; + labelAssistantMessages?: boolean; } interface ActivityEntry { text: string; - action: ActivityAction; } function appendText(buffer: string, text: string): string { @@ -28,24 +27,22 @@ function appendText(buffer: string, text: string): string { return `${buffer}\n${normalized}`; } -function activityEntry(text: string, toolName: string, summary?: string): ActivityEntry { - return { - text, - action: { - toolName, - ...(summary ? { summary } : {}), - }, - }; +function activityEntry(text: string): ActivityEntry { + return { text }; } -function flushBuffers(entries: ActivityEntry[], buffers: { message: string; thought: string }) { +function flushBuffers( + entries: ActivityEntry[], + buffers: { message: string; thought: string }, + options?: ActivityCuratorOptions, +) { if (buffers.message.trim()) { const text = buffers.message.trim(); - entries.push(activityEntry(text, "Assistant", text)); + entries.push(activityEntry(options?.labelAssistantMessages ? `[Assistant] ${text}` : text)); } if (buffers.thought.trim()) { const text = buffers.thought.trim(); - entries.push(activityEntry(`[Thought] ${text}`, "Thought", text)); + entries.push(activityEntry(`[Thought] ${text}`)); } buffers.message = ""; buffers.thought = ""; @@ -100,7 +97,7 @@ function projectForCuration(items: readonly AgentTimelineItem[]): AgentTimelineI function curateAgentActivityEntries( timeline: AgentTimelineItem[], - options?: { maxItems?: number }, + options?: ActivityCuratorOptions, ): ActivityEntry[] { if (timeline.length === 0) { return []; @@ -118,8 +115,8 @@ function curateAgentActivityEntries( for (const item of recentItems) { switch (item.type) { case "user_message": - flushBuffers(entries, buffers); - entries.push(activityEntry(`[User] ${item.text.trim()}`, "User", item.text.trim())); + flushBuffers(entries, buffers, options); + entries.push(activityEntry(`[User] ${item.text.trim()}`)); break; case "assistant_message": buffers.message = appendText(buffers.message, item.text); @@ -128,7 +125,7 @@ function curateAgentActivityEntries( buffers.thought = appendText(buffers.thought, item.text); break; case "tool_call": { - flushBuffers(entries, buffers); + flushBuffers(entries, buffers, options); const inputJson = formatToolInputJson(inputFromUnknownDetail(item.detail)); const display = buildToolCallDisplayModel({ name: item.name, @@ -140,37 +137,37 @@ function curateAgentActivityEntries( const displayName = display.displayName; const summary = formatToolSummary(display.summary); if (isLikelyExternalToolName(item.name) && inputJson) { - entries.push(activityEntry(`[${displayName}] ${inputJson}`, displayName, inputJson)); + entries.push(activityEntry(`[${displayName}] ${inputJson}`)); break; } if (summary) { - entries.push(activityEntry(`[${displayName}] ${summary}`, displayName, summary)); + entries.push(activityEntry(`[${displayName}] ${summary}`)); } else { - entries.push(activityEntry(`[${displayName}]`, displayName)); + entries.push(activityEntry(`[${displayName}]`)); } break; } case "todo": - flushBuffers(entries, buffers); - entries.push(activityEntry("[Tasks]", "Tasks")); + flushBuffers(entries, buffers, options); + entries.push(activityEntry("[Tasks]")); for (const entry of item.items) { const checkbox = entry.completed ? "[x]" : "[ ]"; const text = `- ${checkbox} ${entry.text}`; - entries.push(activityEntry(text, "Assistant", text)); + entries.push(activityEntry(text)); } break; case "error": - flushBuffers(entries, buffers); - entries.push(activityEntry(`[Error] ${item.message}`, "Error", item.message)); + flushBuffers(entries, buffers, options); + entries.push(activityEntry(`[Error] ${item.message}`)); break; case "compaction": - flushBuffers(entries, buffers); - entries.push(activityEntry("[Compacted]", "Compacted")); + flushBuffers(entries, buffers, options); + entries.push(activityEntry("[Compacted]")); break; } } - flushBuffers(entries, buffers); + flushBuffers(entries, buffers, options); return entries; } @@ -180,19 +177,10 @@ function curateAgentActivityEntries( */ export function curateAgentActivity( timeline: AgentTimelineItem[], - options?: { maxItems?: number }, + options?: ActivityCuratorOptions, ): string { const entries = curateAgentActivityEntries(timeline, options); return entries.length > 0 ? entries.map((entry) => entry.text).join("\n") : "No activity to display."; } - -export function curateAgentActivityActions( - timeline: AgentTimelineItem[], - options?: { maxItems?: number }, -): Array<{ index: number; toolName: string; summary?: string }> { - return curateAgentActivityEntries(timeline, options).map((entry, index) => - Object.assign({ index: index + 1 }, entry.action), - ); -} diff --git a/packages/server/src/server/agent/agent-sdk-types.ts b/packages/server/src/server/agent/agent-sdk-types.ts index 90844327e..b2d7f94aa 100644 --- a/packages/server/src/server/agent/agent-sdk-types.ts +++ b/packages/server/src/server/agent/agent-sdk-types.ts @@ -237,11 +237,6 @@ export type ToolCallDetail = description?: string; childSessionId?: string; log: string; - actions: Array<{ - index: number; - toolName: string; - summary?: string; - }>; } | { type: "plain_text"; diff --git a/packages/server/src/server/agent/providers/claude-agent.sub-agent-sidechain.test.ts b/packages/server/src/server/agent/providers/claude-agent.sub-agent-sidechain.test.ts index d95654040..9b7744a22 100644 --- a/packages/server/src/server/agent/providers/claude-agent.sub-agent-sidechain.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.sub-agent-sidechain.test.ts @@ -280,18 +280,6 @@ describe("ClaudeAgentSession sub-agent sidechain updates", () => { expect(latest.detail.subAgentType).toBe("Explore"); expect(latest.detail.description).toBe("Inspect repository structure"); - expect(latest.detail.actions).toEqual([ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - { - index: 2, - toolName: "Edit", - summary: "src/index.ts", - }, - ]); expect(latest.detail.log).toContain("[Read] README.md"); expect(latest.detail.log).toContain("[Edit] src/index.ts"); expect(latest.detail.log).not.toContain("VERY_LARGE_OLD_STRING"); @@ -334,18 +322,6 @@ describe("ClaudeAgentSession sub-agent sidechain updates", () => { throw new Error("expected sub_agent detail"); } - expect(latest.detail.actions).toHaveLength(200); - expect(latest.detail.actions[0]).toEqual({ - index: 6, - toolName: "Read", - summary: "file-6.md", - }); - expect(latest.detail.actions[199]).toEqual({ - index: 205, - toolName: "Read", - summary: "file-205.md", - }); - expect(latest.detail.log).not.toContain("[Read] file-1.md"); expect(latest.detail.log).not.toContain("[Read] file-5.md"); expect(latest.detail.log).toContain("[Read] file-6.md"); diff --git a/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts b/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts index d836e5625..59f138f3f 100644 --- a/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts +++ b/packages/server/src/server/agent/providers/claude/sidechain-tracker.ts @@ -99,12 +99,6 @@ export class ClaudeSidechainTracker { action.summary ? `[${action.toolName}] ${action.summary}` : `[${action.toolName}]`, ) .join("\n"), - actions: state.actions.map((action) => - Object.assign( - { index: action.index, toolName: action.toolName }, - action.summary ? { summary: action.summary } : {}, - ), - ), }; return [ diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index 587a61a46..46a141865 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -30,6 +30,7 @@ interface CollaborationModeRecord { interface CodexSessionTestAccess { handleToolApprovalRequest(params: unknown): Promise; handleNotification(method: string, params: unknown): void; + loadPersistedHistory(): Promise; refreshResolvedCollaborationMode(): void; serviceTier: "fast" | null; planModeEnabled: boolean; @@ -705,7 +706,6 @@ describe("Codex app-server provider", () => { subAgentType: "Sub-agent", description: "Inspect the stream path.", log: "", - actions: [], }, }, }, @@ -762,12 +762,60 @@ describe("Codex app-server provider", () => { type: "sub_agent", subAgentType: "Sub-agent", description: "Report findings.", - log: "Found the path.", - actions: [{ index: 1, toolName: "Assistant", summary: "Found the path." }], + log: "[Assistant] Found the path.", }, }); }); + test("loads Codex persisted history from the app-server thread", async () => { + const session = createSession(); + const requests: Array<{ method: string; params: unknown }> = []; + session.client = { + request: vi.fn(async (method: string, params: unknown) => { + requests.push({ method, params }); + if (method !== "thread/read") { + return {}; + } + return { + thread: { + turns: [ + { + items: [ + { + type: "agentMessage", + id: "message-history", + text: "History loaded.", + }, + ], + }, + ], + }, + }; + }), + }; + + await asInternals(session).loadPersistedHistory(); + + const history: AgentStreamEvent[] = []; + for await (const event of session.streamHistory()) { + history.push(event); + } + + expect(requests.map((request) => [request.method, request.params])).toEqual([ + ["thread/read", { threadId: "test-thread", includeTurns: true }], + ]); + expect(history).toEqual([ + { + type: "timeline", + provider: "codex", + item: { + type: "assistant_message", + text: "History loaded.", + }, + }, + ]); + }); + test("maps question responses from headers back to question ids and completes the tool call", async () => { const session = createSession(); const events: AgentStreamEvent[] = []; diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index c44256bdd..d309ae244 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -37,9 +37,8 @@ import os from "node:os"; import path from "node:path"; import readline from "node:readline"; import { z } from "zod"; -import { loadCodexPersistedTimeline } from "./codex-rollout-timeline.js"; import { renderPromptAttachmentAsText } from "../prompt-attachments.js"; -import { curateAgentActivity, curateAgentActivityActions } from "../activity-curator.js"; +import { curateAgentActivity } from "../activity-curator.js"; import { mapCodexRolloutToolCall, mapCodexToolCallFromThreadItem, @@ -1557,6 +1556,61 @@ function threadItemToTimeline( } } +const CodexThreadReadResponseSchema = z + .object({ + thread: z + .object({ + turns: z + .array( + z + .object({ + items: z.array(z.unknown()).default([]), + }) + .passthrough(), + ) + .default([]), + }) + .passthrough() + .default({ turns: [] }), + }) + .passthrough(); + +type CodexThreadReadResponse = z.infer; +type CodexThreadReadRequest = (threadId: string) => Promise; + +async function requestCodexThreadHistory( + requestThread: CodexThreadReadRequest, + threadId: string, +): Promise { + const response = await requestThread(threadId); + return CodexThreadReadResponseSchema.parse(response); +} + +async function loadCodexThreadHistoryTimeline(params: { + threadId: string; + cwd: string | null; + requestThread: CodexThreadReadRequest; +}): Promise { + const response = await requestCodexThreadHistory(params.requestThread, params.threadId); + const timeline: AgentTimelineItem[] = []; + for (const turn of response.thread.turns) { + for (const item of turn.items) { + const timelineItem = threadItemToTimeline(item, { cwd: params.cwd }); + if (timelineItem) { + timeline.push(timelineItem); + } + } + } + return timeline; +} + +function readCodexThread(client: CodexAppServerClient, threadId: string): Promise { + return client.request("thread/read", { + threadId, + includeTurns: true, + }); +} + function toSandboxPolicy(type: string, networkAccess?: boolean): Record { switch (type) { case "read-only": @@ -2662,8 +2716,8 @@ class CodexAppServerAgentSession implements AgentSession { await this.loadSkills(); if (this.currentThreadId) { - await this.loadPersistedHistory(); await this.ensureThreadLoaded(); + await this.loadPersistedHistory(); } this.connected = true; @@ -2867,33 +2921,17 @@ class CodexAppServerAgentSession implements AgentSession { private async loadPersistedHistory(): Promise { if (!this.client || !this.currentThreadId) return; + const client = this.client; + const threadId = this.currentThreadId; + try { - let rolloutTimeline: AgentTimelineItem[] = []; - try { - rolloutTimeline = await loadCodexPersistedTimeline( - this.currentThreadId, - undefined, - this.logger, - ); - } catch { - rolloutTimeline = []; - } - - const response = (await this.client.request("thread/read", { - threadId: this.currentThreadId, - includeTurns: true, - })) as { thread?: { turns?: Array<{ items?: unknown[] }> } }; - const thread = response?.thread; - const threadTimeline: AgentTimelineItem[] = []; - if (thread && Array.isArray(thread.turns)) { - for (const turn of thread.turns) { - const items = Array.isArray(turn.items) ? turn.items : []; - this.collectThreadTurnTimelineItems(items, threadTimeline); - } - } - - const timeline = rolloutTimeline.length > 0 ? rolloutTimeline : threadTimeline; - + const timeline = await loadCodexThreadHistoryTimeline({ + threadId, + cwd: this.config.cwd ?? null, + requestThread: (threadIdToRead) => { + return readCodexThread(client, threadIdToRead); + }, + }); if (timeline.length > 0) { this.persistedHistory = timeline; this.historyPending = true; @@ -3625,11 +3663,6 @@ class CodexAppServerAgentSession implements AgentSession { childItems: new Map(), } satisfies CodexSubAgentCallState); - let actions = timelineItem.detail.actions; - if (actions.length === 0 && state.toolCall.detail.type === "sub_agent") { - actions = state.toolCall.detail.actions; - } - state.toolCall = { ...timelineItem, detail: { @@ -3637,7 +3670,6 @@ class CodexAppServerAgentSession implements AgentSession { log: timelineItem.detail.log || (state.toolCall.detail.type === "sub_agent" ? state.toolCall.detail.log : ""), - actions, }, }; this.subAgentCallsByCallId.set(timelineItem.callId, state); @@ -3676,15 +3708,16 @@ class CodexAppServerAgentSession implements AgentSession { return; } const childTimeline = this.getSubAgentChildTimeline(state); - const log = childTimeline.length > 0 ? curateAgentActivity(childTimeline) : ""; - const actions = childTimeline.length > 0 ? curateAgentActivityActions(childTimeline) : []; + const log = + childTimeline.length > 0 + ? curateAgentActivity(childTimeline, { labelAssistantMessages: true }) + : ""; const resolvedStatus = status ?? state.toolCall.status; const baseToolCall = { ...state.toolCall, detail: { ...state.toolCall.detail, log, - actions, }, }; const nextToolCall: ToolCallTimelineItem = @@ -4284,21 +4317,6 @@ class CodexAppServerAgentSession implements AgentSession { return true; } - private collectThreadTurnTimelineItems(items: unknown[], target: AgentTimelineItem[]): void { - for (const item of items) { - const timelineItem = threadItemToTimeline(item, { - cwd: this.config.cwd ?? null, - }); - if (!timelineItem) { - continue; - } - if (timelineItem.type === "tool_call") { - this.warnOnIncompleteEditToolCall(timelineItem, "thread_read", item); - } - target.push(timelineItem); - } - } - private warnOnIncompleteEditToolCall( item: ToolCallTimelineItem, source: string, @@ -4549,23 +4567,15 @@ export class CodexAppServerAgentClient implements AgentClient { const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd(); const title = typeof thread.preview === "string" ? thread.preview : null; let timeline: AgentTimelineItem[] = []; + try { - const [rolloutTimeline, read] = await Promise.all([ - loadCodexPersistedTimeline(threadId, undefined, this.logger), - client.request("thread/read", { - threadId, - includeTurns: true, - }) as Promise<{ thread?: { turns?: Array<{ items?: unknown[] }> } }>, - ]); - const turns = read.thread?.turns ?? []; - const itemsFromThreadRead: AgentTimelineItem[] = []; - for (const turn of turns) { - for (const item of turn.items ?? []) { - const timelineItem = threadItemToTimeline(item, { cwd }); - if (timelineItem) itemsFromThreadRead.push(timelineItem); - } - } - timeline = rolloutTimeline.length > 0 ? rolloutTimeline : itemsFromThreadRead; + timeline = await loadCodexThreadHistoryTimeline({ + threadId, + cwd, + requestThread: (threadIdToRead) => { + return readCodexThread(client, threadIdToRead); + }, + }); } catch { timeline = []; } diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts index 6871ec04c..a3b9f98fc 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts @@ -174,7 +174,6 @@ describe("codex tool-call mapper", () => { subAgentType: "Sub-agent", description: "Inspect the Codex stream path.", log: "", - actions: [], }, }); }); diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts index 2ac389ba9..80ec64c94 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts @@ -851,7 +851,6 @@ function mapCollabAgentToolCallItem( subAgentType: "Sub-agent", ...(item.prompt ? { description: item.prompt } : {}), log: "", - actions: [], }; if (status === "failed") { diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index 6c84936b1..d45e36194 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -1667,11 +1667,6 @@ function buildOpenCodeSubAgentTimelineItem( ...toolCall.detail, ...(childSessionId ? { childSessionId } : {}), log: buildOpenCodeSubAgentLog(toolCall.detail, activity), - actions: activity.actions.map((action) => ({ - index: action.index, - toolName: action.toolName, - ...(action.summary ? { summary: action.summary } : {}), - })), }, }; } diff --git a/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts b/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts index 9c5a5bbcc..517afc381 100644 --- a/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts +++ b/packages/server/src/server/agent/providers/opencode/tool-call-detail-parser.ts @@ -111,7 +111,6 @@ function deriveOpencodeTaskDetail( ...(description ? { description } : {}), ...(childSessionId ? { childSessionId } : {}), log, - actions: [], }; } diff --git a/packages/server/src/server/agent/providers/opencode/tool-call-mapper.test.ts b/packages/server/src/server/agent/providers/opencode/tool-call-mapper.test.ts index a0db0272d..08aa9638b 100644 --- a/packages/server/src/server/agent/providers/opencode/tool-call-mapper.test.ts +++ b/packages/server/src/server/agent/providers/opencode/tool-call-mapper.test.ts @@ -271,7 +271,6 @@ describe("opencode tool-call mapper", () => { subAgentType: "explore", description: "Explore agent-tools codebase", log: "", - actions: [], }); }); @@ -296,7 +295,6 @@ describe("opencode tool-call mapper", () => { subAgentType: "explore", description: "Explore agent-tools codebase", log: "Found the CLI entrypoint and provider registry.", - actions: [], }); }); @@ -320,7 +318,6 @@ describe("opencode tool-call mapper", () => { description: "Explore current directory", childSessionId: "ses_2268db431ffe299vL1bbot8R7Z", log: "task_id: ses_2268db431ffe299vL1bbot8R7Z\n\ndone", - actions: [], }); }); @@ -346,7 +343,6 @@ describe("opencode tool-call mapper", () => { subAgentType: "explore", description: "Explore agent-tools codebase", log: "Tool execution aborted", - actions: [], }); }); diff --git a/packages/server/src/shared/messages.stream-parsing.test.ts b/packages/server/src/shared/messages.stream-parsing.test.ts index cff9acb0e..e5f1d97f9 100644 --- a/packages/server/src/shared/messages.stream-parsing.test.ts +++ b/packages/server/src/shared/messages.stream-parsing.test.ts @@ -179,13 +179,6 @@ describe("shared messages stream parsing", () => { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - ], }, error: null, }, diff --git a/packages/server/src/shared/messages.tool-call-schema.test.ts b/packages/server/src/shared/messages.tool-call-schema.test.ts index b31804eb3..983ee8dd4 100644 --- a/packages/server/src/shared/messages.tool-call-schema.test.ts +++ b/packages/server/src/shared/messages.tool-call-schema.test.ts @@ -122,18 +122,6 @@ describe("shared messages tool_call schema", () => { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md\n[Bash] ls", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - { - index: 2, - toolName: "Bash", - summary: "ls", - }, - ], }, }); @@ -142,12 +130,11 @@ describe("shared messages tool_call schema", () => { expect(parsed.detail.type).toBe("sub_agent"); if (parsed.detail.type === "sub_agent") { expect(parsed.detail.subAgentType).toBe("Explore"); - expect(parsed.detail.actions).toHaveLength(2); } } }); - it("defaults missing sub_agent actions for older payloads", () => { + it("parses sub_agent detail without structured actions", () => { const parsed = AgentTimelineItemPayloadSchema.parse({ type: "tool_call", callId: "call_sub_agent_legacy", @@ -166,7 +153,7 @@ describe("shared messages tool_call schema", () => { if (parsed.type === "tool_call") { expect(parsed.detail.type).toBe("sub_agent"); if (parsed.detail.type === "sub_agent") { - expect(parsed.detail.actions).toEqual([]); + expect(parsed.detail.log).toBe("[Read] README.md"); } } }); diff --git a/packages/server/src/shared/messages.ts b/packages/server/src/shared/messages.ts index 33db0d39f..7d610d9b4 100644 --- a/packages/server/src/shared/messages.ts +++ b/packages/server/src/shared/messages.ts @@ -414,16 +414,6 @@ const ToolCallDetailPayloadSchema: z.ZodType { subAgentType: "Explore", description: "Inspect repository structure", log: "[Read] README.md", - actions: [ - { - index: 1, - toolName: "Read", - summary: "README.md", - }, - ], }, });