Render sub-agent activity from log
This commit is contained in:
@@ -244,31 +244,62 @@ function resolveSubAgentFallbackHeader(
|
|||||||
|
|
||||||
interface SubAgentDetailProps {
|
interface SubAgentDetailProps {
|
||||||
log: string;
|
log: string;
|
||||||
actions: Extract<ToolCallDetail, { type: "sub_agent" }>["actions"];
|
|
||||||
childSessionId: string | null | undefined;
|
childSessionId: string | null | undefined;
|
||||||
subAgentType: string | null | undefined;
|
subAgentType: string | null | undefined;
|
||||||
description: string | null | undefined;
|
description: string | null | undefined;
|
||||||
ds: DetailStyles;
|
ds: DetailStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSubAgentActionLog(actions: SubAgentDetailProps["actions"]): string {
|
interface SubAgentActivityRow {
|
||||||
return actions
|
index: number;
|
||||||
.map((action) =>
|
toolName: string;
|
||||||
action.summary ? `[${action.toolName}] ${action.summary}` : `[${action.toolName}]`,
|
summary?: string;
|
||||||
)
|
|
||||||
.join("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripSubAgentActionLog(log: string, actions: SubAgentDetailProps["actions"]): string {
|
interface ParsedSubAgentLog {
|
||||||
const actionLog = buildSubAgentActionLog(actions);
|
actions: SubAgentActivityRow[];
|
||||||
const trimmedLog = log.replace(/^\n+/, "");
|
remainingLog: string;
|
||||||
if (!actionLog || !trimmedLog.startsWith(actionLog)) {
|
}
|
||||||
return trimmedLog;
|
|
||||||
|
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 (
|
return (
|
||||||
<View style={styles.subAgentActionRow}>
|
<View style={styles.subAgentActionRow}>
|
||||||
<Text selectable style={styles.subAgentActionTool}>
|
<Text selectable style={styles.subAgentActionTool}>
|
||||||
@@ -324,13 +355,12 @@ function SubAgentLogText({
|
|||||||
|
|
||||||
function SubAgentDetailSection({
|
function SubAgentDetailSection({
|
||||||
log,
|
log,
|
||||||
actions,
|
|
||||||
childSessionId,
|
childSessionId,
|
||||||
subAgentType,
|
subAgentType,
|
||||||
description,
|
description,
|
||||||
ds,
|
ds,
|
||||||
}: SubAgentDetailProps) {
|
}: SubAgentDetailProps) {
|
||||||
const activityLog = stripSubAgentActionLog(log, actions);
|
const { actions, remainingLog } = useMemo(() => parseSubAgentLog(log), [log]);
|
||||||
const fallbackHeader = resolveSubAgentFallbackHeader(subAgentType, description);
|
const fallbackHeader = resolveSubAgentFallbackHeader(subAgentType, description);
|
||||||
const hasActions = actions.length > 0;
|
const hasActions = actions.length > 0;
|
||||||
return (
|
return (
|
||||||
@@ -363,7 +393,7 @@ function SubAgentDetailSection({
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
<SubAgentLogText
|
<SubAgentLogText
|
||||||
activityLog={activityLog}
|
activityLog={remainingLog}
|
||||||
fallbackHeader={fallbackHeader}
|
fallbackHeader={fallbackHeader}
|
||||||
hasActions={hasActions}
|
hasActions={hasActions}
|
||||||
/>
|
/>
|
||||||
@@ -624,7 +654,6 @@ function buildDetailSections(
|
|||||||
<SubAgentDetailSection
|
<SubAgentDetailSection
|
||||||
key="sub-agent"
|
key="sub-agent"
|
||||||
log={detail.log}
|
log={detail.log}
|
||||||
actions={detail.actions}
|
|
||||||
childSessionId={detail.childSessionId}
|
childSessionId={detail.childSessionId}
|
||||||
subAgentType={detail.subAgentType}
|
subAgentType={detail.subAgentType}
|
||||||
description={detail.description}
|
description={detail.description}
|
||||||
@@ -844,7 +873,6 @@ const styles = StyleSheet.create((theme) => {
|
|||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
subAgentActionTool: {
|
subAgentActionTool: {
|
||||||
minWidth: 56,
|
|
||||||
fontFamily: Fonts.mono,
|
fontFamily: Fonts.mono,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
|
|||||||
@@ -367,18 +367,6 @@ describe("stream reducer canonical tool calls", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md\n[Bash] ls",
|
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"),
|
timestamp: new Date("2025-01-01T10:12:00Z"),
|
||||||
@@ -406,18 +394,6 @@ describe("stream reducer canonical tool calls", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md\n[Bash] ls",
|
log: "[Read] README.md\n[Bash] ls",
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
toolName: "Read",
|
|
||||||
summary: "README.md",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 2,
|
|
||||||
toolName: "Bash",
|
|
||||||
summary: "ls",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const display = buildToolCallDisplayModel({
|
const display = buildToolCallDisplayModel({
|
||||||
|
|||||||
@@ -49,9 +49,7 @@ function hasMeaningfulWorktreeSetupDetail(
|
|||||||
function hasMeaningfulSubAgentDetail(
|
function hasMeaningfulSubAgentDetail(
|
||||||
detail: Extract<ToolCallDetail, { type: "sub_agent" }>,
|
detail: Extract<ToolCallDetail, { type: "sub_agent" }>,
|
||||||
): boolean {
|
): boolean {
|
||||||
return Boolean(
|
return Boolean(detail.subAgentType || detail.description || detail.log);
|
||||||
detail.subAgentType || detail.description || detail.log || detail.actions.length > 0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasMeaningfulPlainTextDetail(
|
function hasMeaningfulPlainTextDetail(
|
||||||
|
|||||||
@@ -48,13 +48,6 @@ describe("tool-call-display", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md",
|
log: "[Read] README.md",
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
toolName: "Read",
|
|
||||||
summary: "README.md",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ describe("tool-call-icon", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository",
|
description: "Inspect repository",
|
||||||
log: "[Read] README.md",
|
log: "[Read] README.md",
|
||||||
actions: [{ index: 1, toolName: "Read", summary: "README.md" }],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(icon).toBe(iconMocks.Bot);
|
expect(icon).toBe(iconMocks.Bot);
|
||||||
|
|||||||
@@ -153,13 +153,6 @@ second line'`,
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Investigate repository",
|
description: "Investigate repository",
|
||||||
log: "[Read] README.md",
|
log: "[Read] README.md",
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
toolName: "Read",
|
|
||||||
summary: "README.md",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
toolCallItem({
|
toolCallItem({
|
||||||
@@ -171,18 +164,6 @@ second line'`,
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Investigate repository",
|
description: "Investigate repository",
|
||||||
log: "[Read] README.md\n[Bash] ls",
|
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.");
|
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_INPUT_CHARS = 400;
|
||||||
const MAX_TOOL_SUMMARY_CHARS = 200;
|
const MAX_TOOL_SUMMARY_CHARS = 200;
|
||||||
|
|
||||||
interface ActivityAction {
|
interface ActivityCuratorOptions {
|
||||||
toolName: string;
|
maxItems?: number;
|
||||||
summary?: string;
|
labelAssistantMessages?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityEntry {
|
interface ActivityEntry {
|
||||||
text: string;
|
text: string;
|
||||||
action: ActivityAction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendText(buffer: string, text: string): string {
|
function appendText(buffer: string, text: string): string {
|
||||||
@@ -28,24 +27,22 @@ function appendText(buffer: string, text: string): string {
|
|||||||
return `${buffer}\n${normalized}`;
|
return `${buffer}\n${normalized}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function activityEntry(text: string, toolName: string, summary?: string): ActivityEntry {
|
function activityEntry(text: string): ActivityEntry {
|
||||||
return {
|
return { text };
|
||||||
text,
|
|
||||||
action: {
|
|
||||||
toolName,
|
|
||||||
...(summary ? { summary } : {}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function flushBuffers(entries: ActivityEntry[], buffers: { message: string; thought: string }) {
|
function flushBuffers(
|
||||||
|
entries: ActivityEntry[],
|
||||||
|
buffers: { message: string; thought: string },
|
||||||
|
options?: ActivityCuratorOptions,
|
||||||
|
) {
|
||||||
if (buffers.message.trim()) {
|
if (buffers.message.trim()) {
|
||||||
const text = 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()) {
|
if (buffers.thought.trim()) {
|
||||||
const text = buffers.thought.trim();
|
const text = buffers.thought.trim();
|
||||||
entries.push(activityEntry(`[Thought] ${text}`, "Thought", text));
|
entries.push(activityEntry(`[Thought] ${text}`));
|
||||||
}
|
}
|
||||||
buffers.message = "";
|
buffers.message = "";
|
||||||
buffers.thought = "";
|
buffers.thought = "";
|
||||||
@@ -100,7 +97,7 @@ function projectForCuration(items: readonly AgentTimelineItem[]): AgentTimelineI
|
|||||||
|
|
||||||
function curateAgentActivityEntries(
|
function curateAgentActivityEntries(
|
||||||
timeline: AgentTimelineItem[],
|
timeline: AgentTimelineItem[],
|
||||||
options?: { maxItems?: number },
|
options?: ActivityCuratorOptions,
|
||||||
): ActivityEntry[] {
|
): ActivityEntry[] {
|
||||||
if (timeline.length === 0) {
|
if (timeline.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
@@ -118,8 +115,8 @@ function curateAgentActivityEntries(
|
|||||||
for (const item of recentItems) {
|
for (const item of recentItems) {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case "user_message":
|
case "user_message":
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
entries.push(activityEntry(`[User] ${item.text.trim()}`, "User", item.text.trim()));
|
entries.push(activityEntry(`[User] ${item.text.trim()}`));
|
||||||
break;
|
break;
|
||||||
case "assistant_message":
|
case "assistant_message":
|
||||||
buffers.message = appendText(buffers.message, item.text);
|
buffers.message = appendText(buffers.message, item.text);
|
||||||
@@ -128,7 +125,7 @@ function curateAgentActivityEntries(
|
|||||||
buffers.thought = appendText(buffers.thought, item.text);
|
buffers.thought = appendText(buffers.thought, item.text);
|
||||||
break;
|
break;
|
||||||
case "tool_call": {
|
case "tool_call": {
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
const inputJson = formatToolInputJson(inputFromUnknownDetail(item.detail));
|
const inputJson = formatToolInputJson(inputFromUnknownDetail(item.detail));
|
||||||
const display = buildToolCallDisplayModel({
|
const display = buildToolCallDisplayModel({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@@ -140,37 +137,37 @@ function curateAgentActivityEntries(
|
|||||||
const displayName = display.displayName;
|
const displayName = display.displayName;
|
||||||
const summary = formatToolSummary(display.summary);
|
const summary = formatToolSummary(display.summary);
|
||||||
if (isLikelyExternalToolName(item.name) && inputJson) {
|
if (isLikelyExternalToolName(item.name) && inputJson) {
|
||||||
entries.push(activityEntry(`[${displayName}] ${inputJson}`, displayName, inputJson));
|
entries.push(activityEntry(`[${displayName}] ${inputJson}`));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (summary) {
|
if (summary) {
|
||||||
entries.push(activityEntry(`[${displayName}] ${summary}`, displayName, summary));
|
entries.push(activityEntry(`[${displayName}] ${summary}`));
|
||||||
} else {
|
} else {
|
||||||
entries.push(activityEntry(`[${displayName}]`, displayName));
|
entries.push(activityEntry(`[${displayName}]`));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "todo":
|
case "todo":
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
entries.push(activityEntry("[Tasks]", "Tasks"));
|
entries.push(activityEntry("[Tasks]"));
|
||||||
for (const entry of item.items) {
|
for (const entry of item.items) {
|
||||||
const checkbox = entry.completed ? "[x]" : "[ ]";
|
const checkbox = entry.completed ? "[x]" : "[ ]";
|
||||||
const text = `- ${checkbox} ${entry.text}`;
|
const text = `- ${checkbox} ${entry.text}`;
|
||||||
entries.push(activityEntry(text, "Assistant", text));
|
entries.push(activityEntry(text));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
entries.push(activityEntry(`[Error] ${item.message}`, "Error", item.message));
|
entries.push(activityEntry(`[Error] ${item.message}`));
|
||||||
break;
|
break;
|
||||||
case "compaction":
|
case "compaction":
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
entries.push(activityEntry("[Compacted]", "Compacted"));
|
entries.push(activityEntry("[Compacted]"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flushBuffers(entries, buffers);
|
flushBuffers(entries, buffers, options);
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
@@ -180,19 +177,10 @@ function curateAgentActivityEntries(
|
|||||||
*/
|
*/
|
||||||
export function curateAgentActivity(
|
export function curateAgentActivity(
|
||||||
timeline: AgentTimelineItem[],
|
timeline: AgentTimelineItem[],
|
||||||
options?: { maxItems?: number },
|
options?: ActivityCuratorOptions,
|
||||||
): string {
|
): string {
|
||||||
const entries = curateAgentActivityEntries(timeline, options);
|
const entries = curateAgentActivityEntries(timeline, options);
|
||||||
return entries.length > 0
|
return entries.length > 0
|
||||||
? entries.map((entry) => entry.text).join("\n")
|
? entries.map((entry) => entry.text).join("\n")
|
||||||
: "No activity to display.";
|
: "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;
|
description?: string;
|
||||||
childSessionId?: string;
|
childSessionId?: string;
|
||||||
log: string;
|
log: string;
|
||||||
actions: Array<{
|
|
||||||
index: number;
|
|
||||||
toolName: string;
|
|
||||||
summary?: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "plain_text";
|
type: "plain_text";
|
||||||
|
|||||||
@@ -280,18 +280,6 @@ describe("ClaudeAgentSession sub-agent sidechain updates", () => {
|
|||||||
|
|
||||||
expect(latest.detail.subAgentType).toBe("Explore");
|
expect(latest.detail.subAgentType).toBe("Explore");
|
||||||
expect(latest.detail.description).toBe("Inspect repository structure");
|
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("[Read] README.md");
|
||||||
expect(latest.detail.log).toContain("[Edit] src/index.ts");
|
expect(latest.detail.log).toContain("[Edit] src/index.ts");
|
||||||
expect(latest.detail.log).not.toContain("VERY_LARGE_OLD_STRING");
|
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");
|
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-1.md");
|
||||||
expect(latest.detail.log).not.toContain("[Read] file-5.md");
|
expect(latest.detail.log).not.toContain("[Read] file-5.md");
|
||||||
expect(latest.detail.log).toContain("[Read] file-6.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}]`,
|
action.summary ? `[${action.toolName}] ${action.summary}` : `[${action.toolName}]`,
|
||||||
)
|
)
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
actions: state.actions.map((action) =>
|
|
||||||
Object.assign(
|
|
||||||
{ index: action.index, toolName: action.toolName },
|
|
||||||
action.summary ? { summary: action.summary } : {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ interface CollaborationModeRecord {
|
|||||||
interface CodexSessionTestAccess {
|
interface CodexSessionTestAccess {
|
||||||
handleToolApprovalRequest(params: unknown): Promise<unknown>;
|
handleToolApprovalRequest(params: unknown): Promise<unknown>;
|
||||||
handleNotification(method: string, params: unknown): void;
|
handleNotification(method: string, params: unknown): void;
|
||||||
|
loadPersistedHistory(): Promise<void>;
|
||||||
refreshResolvedCollaborationMode(): void;
|
refreshResolvedCollaborationMode(): void;
|
||||||
serviceTier: "fast" | null;
|
serviceTier: "fast" | null;
|
||||||
planModeEnabled: boolean;
|
planModeEnabled: boolean;
|
||||||
@@ -705,7 +706,6 @@ describe("Codex app-server provider", () => {
|
|||||||
subAgentType: "Sub-agent",
|
subAgentType: "Sub-agent",
|
||||||
description: "Inspect the stream path.",
|
description: "Inspect the stream path.",
|
||||||
log: "",
|
log: "",
|
||||||
actions: [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -762,12 +762,60 @@ describe("Codex app-server provider", () => {
|
|||||||
type: "sub_agent",
|
type: "sub_agent",
|
||||||
subAgentType: "Sub-agent",
|
subAgentType: "Sub-agent",
|
||||||
description: "Report findings.",
|
description: "Report findings.",
|
||||||
log: "Found the path.",
|
log: "[Assistant] Found the path.",
|
||||||
actions: [{ index: 1, toolName: "Assistant", summary: "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 () => {
|
test("maps question responses from headers back to question ids and completes the tool call", async () => {
|
||||||
const session = createSession();
|
const session = createSession();
|
||||||
const events: AgentStreamEvent[] = [];
|
const events: AgentStreamEvent[] = [];
|
||||||
|
|||||||
@@ -37,9 +37,8 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import readline from "node:readline";
|
import readline from "node:readline";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { loadCodexPersistedTimeline } from "./codex-rollout-timeline.js";
|
|
||||||
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
||||||
import { curateAgentActivity, curateAgentActivityActions } from "../activity-curator.js";
|
import { curateAgentActivity } from "../activity-curator.js";
|
||||||
import {
|
import {
|
||||||
mapCodexRolloutToolCall,
|
mapCodexRolloutToolCall,
|
||||||
mapCodexToolCallFromThreadItem,
|
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> {
|
function toSandboxPolicy(type: string, networkAccess?: boolean): Record<string, unknown> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "read-only":
|
case "read-only":
|
||||||
@@ -2662,8 +2716,8 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
await this.loadSkills();
|
await this.loadSkills();
|
||||||
|
|
||||||
if (this.currentThreadId) {
|
if (this.currentThreadId) {
|
||||||
await this.loadPersistedHistory();
|
|
||||||
await this.ensureThreadLoaded();
|
await this.ensureThreadLoaded();
|
||||||
|
await this.loadPersistedHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
@@ -2867,33 +2921,17 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
|
|
||||||
private async loadPersistedHistory(): Promise<void> {
|
private async loadPersistedHistory(): Promise<void> {
|
||||||
if (!this.client || !this.currentThreadId) return;
|
if (!this.client || !this.currentThreadId) return;
|
||||||
|
const client = this.client;
|
||||||
|
const threadId = this.currentThreadId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let rolloutTimeline: AgentTimelineItem[] = [];
|
const timeline = await loadCodexThreadHistoryTimeline({
|
||||||
try {
|
threadId,
|
||||||
rolloutTimeline = await loadCodexPersistedTimeline(
|
cwd: this.config.cwd ?? null,
|
||||||
this.currentThreadId,
|
requestThread: (threadIdToRead) => {
|
||||||
undefined,
|
return readCodexThread(client, threadIdToRead);
|
||||||
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;
|
|
||||||
|
|
||||||
if (timeline.length > 0) {
|
if (timeline.length > 0) {
|
||||||
this.persistedHistory = timeline;
|
this.persistedHistory = timeline;
|
||||||
this.historyPending = true;
|
this.historyPending = true;
|
||||||
@@ -3625,11 +3663,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
childItems: new Map<string, AgentTimelineItem>(),
|
childItems: new Map<string, AgentTimelineItem>(),
|
||||||
} satisfies CodexSubAgentCallState);
|
} satisfies CodexSubAgentCallState);
|
||||||
|
|
||||||
let actions = timelineItem.detail.actions;
|
|
||||||
if (actions.length === 0 && state.toolCall.detail.type === "sub_agent") {
|
|
||||||
actions = state.toolCall.detail.actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.toolCall = {
|
state.toolCall = {
|
||||||
...timelineItem,
|
...timelineItem,
|
||||||
detail: {
|
detail: {
|
||||||
@@ -3637,7 +3670,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
log:
|
log:
|
||||||
timelineItem.detail.log ||
|
timelineItem.detail.log ||
|
||||||
(state.toolCall.detail.type === "sub_agent" ? state.toolCall.detail.log : ""),
|
(state.toolCall.detail.type === "sub_agent" ? state.toolCall.detail.log : ""),
|
||||||
actions,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.subAgentCallsByCallId.set(timelineItem.callId, state);
|
this.subAgentCallsByCallId.set(timelineItem.callId, state);
|
||||||
@@ -3676,15 +3708,16 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const childTimeline = this.getSubAgentChildTimeline(state);
|
const childTimeline = this.getSubAgentChildTimeline(state);
|
||||||
const log = childTimeline.length > 0 ? curateAgentActivity(childTimeline) : "";
|
const log =
|
||||||
const actions = childTimeline.length > 0 ? curateAgentActivityActions(childTimeline) : [];
|
childTimeline.length > 0
|
||||||
|
? curateAgentActivity(childTimeline, { labelAssistantMessages: true })
|
||||||
|
: "";
|
||||||
const resolvedStatus = status ?? state.toolCall.status;
|
const resolvedStatus = status ?? state.toolCall.status;
|
||||||
const baseToolCall = {
|
const baseToolCall = {
|
||||||
...state.toolCall,
|
...state.toolCall,
|
||||||
detail: {
|
detail: {
|
||||||
...state.toolCall.detail,
|
...state.toolCall.detail,
|
||||||
log,
|
log,
|
||||||
actions,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const nextToolCall: ToolCallTimelineItem =
|
const nextToolCall: ToolCallTimelineItem =
|
||||||
@@ -4284,21 +4317,6 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
return true;
|
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(
|
private warnOnIncompleteEditToolCall(
|
||||||
item: ToolCallTimelineItem,
|
item: ToolCallTimelineItem,
|
||||||
source: string,
|
source: string,
|
||||||
@@ -4549,23 +4567,15 @@ export class CodexAppServerAgentClient implements AgentClient {
|
|||||||
const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd();
|
const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd();
|
||||||
const title = typeof thread.preview === "string" ? thread.preview : null;
|
const title = typeof thread.preview === "string" ? thread.preview : null;
|
||||||
let timeline: AgentTimelineItem[] = [];
|
let timeline: AgentTimelineItem[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rolloutTimeline, read] = await Promise.all([
|
timeline = await loadCodexThreadHistoryTimeline({
|
||||||
loadCodexPersistedTimeline(threadId, undefined, this.logger),
|
threadId,
|
||||||
client.request("thread/read", {
|
cwd,
|
||||||
threadId,
|
requestThread: (threadIdToRead) => {
|
||||||
includeTurns: true,
|
return readCodexThread(client, threadIdToRead);
|
||||||
}) 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;
|
|
||||||
} catch {
|
} catch {
|
||||||
timeline = [];
|
timeline = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ describe("codex tool-call mapper", () => {
|
|||||||
subAgentType: "Sub-agent",
|
subAgentType: "Sub-agent",
|
||||||
description: "Inspect the Codex stream path.",
|
description: "Inspect the Codex stream path.",
|
||||||
log: "",
|
log: "",
|
||||||
actions: [],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -851,7 +851,6 @@ function mapCollabAgentToolCallItem(
|
|||||||
subAgentType: "Sub-agent",
|
subAgentType: "Sub-agent",
|
||||||
...(item.prompt ? { description: item.prompt } : {}),
|
...(item.prompt ? { description: item.prompt } : {}),
|
||||||
log: "",
|
log: "",
|
||||||
actions: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status === "failed") {
|
if (status === "failed") {
|
||||||
|
|||||||
@@ -1667,11 +1667,6 @@ function buildOpenCodeSubAgentTimelineItem(
|
|||||||
...toolCall.detail,
|
...toolCall.detail,
|
||||||
...(childSessionId ? { childSessionId } : {}),
|
...(childSessionId ? { childSessionId } : {}),
|
||||||
log: buildOpenCodeSubAgentLog(toolCall.detail, activity),
|
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 } : {}),
|
...(description ? { description } : {}),
|
||||||
...(childSessionId ? { childSessionId } : {}),
|
...(childSessionId ? { childSessionId } : {}),
|
||||||
log,
|
log,
|
||||||
actions: [],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ describe("opencode tool-call mapper", () => {
|
|||||||
subAgentType: "explore",
|
subAgentType: "explore",
|
||||||
description: "Explore agent-tools codebase",
|
description: "Explore agent-tools codebase",
|
||||||
log: "",
|
log: "",
|
||||||
actions: [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,7 +295,6 @@ describe("opencode tool-call mapper", () => {
|
|||||||
subAgentType: "explore",
|
subAgentType: "explore",
|
||||||
description: "Explore agent-tools codebase",
|
description: "Explore agent-tools codebase",
|
||||||
log: "Found the CLI entrypoint and provider registry.",
|
log: "Found the CLI entrypoint and provider registry.",
|
||||||
actions: [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -320,7 +318,6 @@ describe("opencode tool-call mapper", () => {
|
|||||||
description: "Explore current directory",
|
description: "Explore current directory",
|
||||||
childSessionId: "ses_2268db431ffe299vL1bbot8R7Z",
|
childSessionId: "ses_2268db431ffe299vL1bbot8R7Z",
|
||||||
log: "task_id: ses_2268db431ffe299vL1bbot8R7Z\n\n<task_result>done</task_result>",
|
log: "task_id: ses_2268db431ffe299vL1bbot8R7Z\n\n<task_result>done</task_result>",
|
||||||
actions: [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -346,7 +343,6 @@ describe("opencode tool-call mapper", () => {
|
|||||||
subAgentType: "explore",
|
subAgentType: "explore",
|
||||||
description: "Explore agent-tools codebase",
|
description: "Explore agent-tools codebase",
|
||||||
log: "Tool execution aborted",
|
log: "Tool execution aborted",
|
||||||
actions: [],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -179,13 +179,6 @@ describe("shared messages stream parsing", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md",
|
log: "[Read] README.md",
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
toolName: "Read",
|
|
||||||
summary: "README.md",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -122,18 +122,6 @@ describe("shared messages tool_call schema", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md\n[Bash] ls",
|
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");
|
expect(parsed.detail.type).toBe("sub_agent");
|
||||||
if (parsed.detail.type === "sub_agent") {
|
if (parsed.detail.type === "sub_agent") {
|
||||||
expect(parsed.detail.subAgentType).toBe("Explore");
|
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({
|
const parsed = AgentTimelineItemPayloadSchema.parse({
|
||||||
type: "tool_call",
|
type: "tool_call",
|
||||||
callId: "call_sub_agent_legacy",
|
callId: "call_sub_agent_legacy",
|
||||||
@@ -166,7 +153,7 @@ describe("shared messages tool_call schema", () => {
|
|||||||
if (parsed.type === "tool_call") {
|
if (parsed.type === "tool_call") {
|
||||||
expect(parsed.detail.type).toBe("sub_agent");
|
expect(parsed.detail.type).toBe("sub_agent");
|
||||||
if (parsed.detail.type === "sub_agent") {
|
if (parsed.detail.type === "sub_agent") {
|
||||||
expect(parsed.detail.actions).toEqual([]);
|
expect(parsed.detail.log).toBe("[Read] README.md");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -414,16 +414,6 @@ const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail, z.ZodTypeDef, unkno
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
childSessionId: z.string().optional(),
|
childSessionId: z.string().optional(),
|
||||||
log: z.string(),
|
log: z.string(),
|
||||||
actions: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
index: z.number().int().positive(),
|
|
||||||
toolName: z.string(),
|
|
||||||
summary: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
.default([]),
|
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal("plain_text"),
|
type: z.literal("plain_text"),
|
||||||
|
|||||||
@@ -48,13 +48,6 @@ describe("shared tool-call display mapping", () => {
|
|||||||
subAgentType: "Explore",
|
subAgentType: "Explore",
|
||||||
description: "Inspect repository structure",
|
description: "Inspect repository structure",
|
||||||
log: "[Read] README.md",
|
log: "[Read] README.md",
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
toolName: "Read",
|
|
||||||
summary: "README.md",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user