Render sub-agent activity from log

This commit is contained in:
Mohamed Boudra
2026-05-02 17:20:22 +07:00
parent 771f164e26
commit 49e3011a28
21 changed files with 208 additions and 303 deletions
@@ -244,31 +244,62 @@ function resolveSubAgentFallbackHeader(
interface SubAgentDetailProps {
log: string;
actions: Extract<ToolCallDetail, { type: "sub_agent" }>["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 (
<View style={styles.subAgentActionRow}>
<Text selectable style={styles.subAgentActionTool}>
@@ -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({
</View>
) : null}
<SubAgentLogText
activityLog={activityLog}
activityLog={remainingLog}
fallbackHeader={fallbackHeader}
hasActions={hasActions}
/>
@@ -624,7 +654,6 @@ function buildDetailSections(
<SubAgentDetailSection
key="sub-agent"
log={detail.log}
actions={detail.actions}
childSessionId={detail.childSessionId}
subAgentType={detail.subAgentType}
description={detail.description}
@@ -844,7 +873,6 @@ const styles = StyleSheet.create((theme) => {
gap: theme.spacing[2],
},
subAgentActionTool: {
minWidth: 56,
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
-24
View File
@@ -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({
@@ -49,9 +49,7 @@ function hasMeaningfulWorktreeSetupDetail(
function hasMeaningfulSubAgentDetail(
detail: Extract<ToolCallDetail, { type: "sub_agent" }>,
): boolean {
return Boolean(
detail.subAgentType || detail.description || detail.log || detail.actions.length > 0,
);
return Boolean(detail.subAgentType || detail.description || detail.log);
}
function hasMeaningfulPlainTextDetail(
@@ -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",
},
],
},
});
@@ -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);
@@ -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",
},
]);
});
});
@@ -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),
);
}
@@ -237,11 +237,6 @@ export type ToolCallDetail =
description?: string;
childSessionId?: string;
log: string;
actions: Array<{
index: number;
toolName: string;
summary?: string;
}>;
}
| {
type: "plain_text";
@@ -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");
@@ -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 [
@@ -30,6 +30,7 @@ interface CollaborationModeRecord {
interface CodexSessionTestAccess {
handleToolApprovalRequest(params: unknown): Promise<unknown>;
handleNotification(method: string, params: unknown): void;
loadPersistedHistory(): Promise<void>;
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[] = [];
@@ -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<typeof CodexThreadReadResponseSchema>;
type CodexThreadReadRequest = (threadId: string) => Promise<unknown>;
async function requestCodexThreadHistory(
requestThread: CodexThreadReadRequest,
threadId: string,
): Promise<CodexThreadReadResponse> {
const response = await requestThread(threadId);
return CodexThreadReadResponseSchema.parse(response);
}
async function loadCodexThreadHistoryTimeline(params: {
threadId: string;
cwd: string | null;
requestThread: CodexThreadReadRequest;
}): Promise<AgentTimelineItem[]> {
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<unknown> {
return client.request("thread/read", {
threadId,
includeTurns: true,
});
}
function toSandboxPolicy(type: string, networkAccess?: boolean): Record<string, unknown> {
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<void> {
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<string, AgentTimelineItem>(),
} 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 = [];
}
@@ -174,7 +174,6 @@ describe("codex tool-call mapper", () => {
subAgentType: "Sub-agent",
description: "Inspect the Codex stream path.",
log: "",
actions: [],
},
});
});
@@ -851,7 +851,6 @@ function mapCollabAgentToolCallItem(
subAgentType: "Sub-agent",
...(item.prompt ? { description: item.prompt } : {}),
log: "",
actions: [],
};
if (status === "failed") {
@@ -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 } : {}),
})),
},
};
}
@@ -111,7 +111,6 @@ function deriveOpencodeTaskDetail(
...(description ? { description } : {}),
...(childSessionId ? { childSessionId } : {}),
log,
actions: [],
};
}
@@ -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\n<task_result>done</task_result>",
actions: [],
});
});
@@ -346,7 +343,6 @@ describe("opencode tool-call mapper", () => {
subAgentType: "explore",
description: "Explore agent-tools codebase",
log: "Tool execution aborted",
actions: [],
});
});
@@ -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,
},
@@ -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");
}
}
});
-10
View File
@@ -414,16 +414,6 @@ const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail, z.ZodTypeDef, unkno
description: z.string().optional(),
childSessionId: z.string().optional(),
log: z.string(),
actions: z
.array(
z.object({
index: z.number().int().positive(),
toolName: z.string(),
summary: z.string().optional(),
}),
)
.optional()
.default([]),
}),
z.object({
type: z.literal("plain_text"),
@@ -48,13 +48,6 @@ describe("shared tool-call display mapping", () => {
subAgentType: "Explore",
description: "Inspect repository structure",
log: "[Read] README.md",
actions: [
{
index: 1,
toolName: "Read",
summary: "README.md",
},
],
},
});