mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Normalize chat display execution state
This commit is contained in:
parent
d9e1a79b7a
commit
be7f504871
12 changed files with 67 additions and 92 deletions
|
|
@ -27,7 +27,7 @@ export function agentDisplayItem(item: CollabAgentToolCallItem, turnId?: string)
|
|||
model: item.model,
|
||||
reasoningEffort: item.reasoningEffort,
|
||||
agents,
|
||||
state: collabAgentExecutionState(item.tool, item.status, item.receiverThreadIds, agents),
|
||||
executionState: collabAgentExecutionState(item.tool, item.status, item.receiverThreadIds, agents),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ export function planProgressDisplayItem(turnId: string, explanation: string | nu
|
|||
explanation: trimmedExplanation !== undefined && trimmedExplanation.length > 0 ? trimmedExplanation : null,
|
||||
steps: plan.map((step) => ({ step: step.step, status: step.status })),
|
||||
status,
|
||||
state: taskProgressExecutionState(status),
|
||||
executionState: taskProgressExecutionState(status),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function createReviewResultItem(id: string, text: string): DisplayItem {
|
|||
role: "tool",
|
||||
text: parsed.summary,
|
||||
markdown: false,
|
||||
state: autoReviewExecutionState(parsed.status),
|
||||
executionState: autoReviewExecutionState(parsed.status),
|
||||
details: [{ title: "Review", rows: parsed.rows }],
|
||||
};
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ export function createAutoReviewResultItem(params: AutoReviewNotification): Disp
|
|||
text,
|
||||
turnId: params.turnId,
|
||||
markdown: false,
|
||||
state: completed ? autoReviewExecutionState(status) : "running",
|
||||
executionState: completed ? autoReviewExecutionState(status) : "running",
|
||||
details: [{ title: "Review", rows }],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,7 @@ const AGENT_FAILED = ["interrupted", "errored", "notFound"] as const satisfies r
|
|||
const AGENT_FAILED_COMPAT = ["failed"] as const;
|
||||
|
||||
export function executionState(item: DisplayItem): ExecutionState {
|
||||
if (item.state) return item.state;
|
||||
if (item.kind === "command") return commandExecutionState(item.status, item.exitCode);
|
||||
if (item.kind === "fileChange") return patchApplyExecutionState(item.status);
|
||||
if (item.kind === "taskProgress") return taskProgressExecutionState(item.status);
|
||||
if (item.kind === "agent") return collabAgentToolCallExecutionState(item.status);
|
||||
if (item.kind === "tool" || item.kind === "hook" || item.kind === "reasoning") return toolStatusExecutionState(item.status);
|
||||
return null;
|
||||
return item.executionState ?? null;
|
||||
}
|
||||
|
||||
export function commandExecutionState(status: string, exitCode?: number): ExecutionState {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export function completeReasoningItems(items: readonly DisplayItem[], turnId: st
|
|||
? {
|
||||
...item,
|
||||
status: "completed",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
|
@ -191,11 +191,13 @@ export function appendItemOutput(
|
|||
? {
|
||||
status: "inProgress",
|
||||
changes: [],
|
||||
executionState: "running",
|
||||
}
|
||||
: {
|
||||
command: fallbackText,
|
||||
cwd: "(unknown)",
|
||||
status: "running",
|
||||
executionState: "running",
|
||||
}),
|
||||
},
|
||||
] as DisplayItem[];
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ function mcpToolCallDisplayItem(item: McpToolCallItem, turnId?: string): Display
|
|||
["Error JSON", item.error],
|
||||
]),
|
||||
output: "",
|
||||
state: mcpToolCallExecutionState(item.status),
|
||||
executionState: mcpToolCallExecutionState(item.status),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ function dynamicToolCallDisplayItem(item: DynamicToolCallItem, turnId?: string):
|
|||
["Result JSON", item.contentItems],
|
||||
]),
|
||||
output: "",
|
||||
state: dynamicToolCallExecutionState(item.status, item.success),
|
||||
executionState: dynamicToolCallExecutionState(item.status, item.success),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ function imageGenerationDisplayItem(item: ImageGenerationItem, turnId?: string):
|
|||
...bodyDetail("Result", item.result),
|
||||
],
|
||||
output: "",
|
||||
state: imageGenerationExecutionState(item.status),
|
||||
executionState: imageGenerationExecutionState(item.status),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +465,7 @@ export function commandDisplayItem(item: CommandExecutionItem, turnId?: string):
|
|||
...definedProp("exitCode", exitCode),
|
||||
...definedProp("durationMs", durationMs),
|
||||
output: item.aggregatedOutput ?? "",
|
||||
state: commandExecutionState(item.status, exitCode),
|
||||
executionState: commandExecutionState(item.status, exitCode),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +481,7 @@ export function fileChangeDisplayItem(item: FileChangeItem, turnId?: string): Di
|
|||
itemId: item.id,
|
||||
status: item.status,
|
||||
changes,
|
||||
state: patchApplyExecutionState(item.status),
|
||||
executionState: patchApplyExecutionState(item.status),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export interface DisplayBase {
|
|||
text: string;
|
||||
turnId?: string;
|
||||
itemId?: string;
|
||||
state?: ExecutionState;
|
||||
executionState?: ExecutionState;
|
||||
}
|
||||
|
||||
export interface DisplayDetailMetaRow {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export function createApprovalResultItem(approval: PendingApproval, action: Appr
|
|||
text: approvalResultText(approval, action),
|
||||
...definedProp("turnId", approvalTurnId(approval)),
|
||||
markdown: false,
|
||||
state: kind === "accept" || kind === "accept-session" ? "completed" : "failed",
|
||||
executionState: kind === "accept" || kind === "accept-session" ? "completed" : "failed",
|
||||
details: [
|
||||
{
|
||||
title: "Approval",
|
||||
|
|
@ -94,7 +94,7 @@ export function createUserInputResultItem(
|
|||
text: status === "submitted" ? `Input submitted for ${label}.` : `Input request cancelled for ${label}.`,
|
||||
...definedProp("turnId", input.params.turnId),
|
||||
markdown: false,
|
||||
state: status === "submitted" ? "completed" : "failed",
|
||||
executionState: status === "submitted" ? "completed" : "failed",
|
||||
details,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ describe("ChatController", () => {
|
|||
|
||||
expect(state.displayItems).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: "r1", kind: "reasoning", status: "completed", state: "completed" }),
|
||||
expect.objectContaining({ id: "r1", kind: "reasoning", status: "completed", executionState: "completed" }),
|
||||
expect.objectContaining({ id: "a1", kind: "message", text: "answer" }),
|
||||
]),
|
||||
);
|
||||
|
|
@ -732,7 +732,7 @@ describe("ChatController", () => {
|
|||
role: "tool",
|
||||
text: "Allowed for this session: Need access",
|
||||
turnId: "turn",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
details: [
|
||||
{
|
||||
title: "Approval",
|
||||
|
|
@ -1304,7 +1304,7 @@ describe("ChatController", () => {
|
|||
id: "review-review-1",
|
||||
kind: "reviewResult",
|
||||
text: "Auto-review approved: npm test",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
const reviewItem = expectPresent(state.displayItems[0]);
|
||||
expect("details" in reviewItem ? reviewItem.details[0] : null).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { normalizeProposedPlanMarkdown, planProgressDisplayItem } from "../../../../src/features/chat/display/plan";
|
||||
import { pathRelativeToRoot } from "../../../../src/features/chat/display/paths";
|
||||
import { createAutoReviewResultItem, createReviewResultItem } from "../../../../src/features/chat/display/review";
|
||||
import { executionState } from "../../../../src/features/chat/display/state";
|
||||
import { commandExecutionState, executionState } from "../../../../src/features/chat/display/state";
|
||||
import { displayItemFromThreadItem, displayItemsFromTurns } from "../../../../src/features/chat/display/thread-items";
|
||||
import { referencedThreadPrompt } from "../../../../src/domain/threads/reference";
|
||||
import type { DisplayItem } from "../../../../src/features/chat/display/types";
|
||||
|
|
@ -27,7 +27,17 @@ function expectPresent<T>(value: T | null | undefined): T {
|
|||
}
|
||||
|
||||
function commandItem(id: string, text: string, turnId: string): DisplayItem {
|
||||
return { id, kind: "command", role: "tool", text, turnId, command: text, cwd: "/vault", status: "completed" };
|
||||
return {
|
||||
id,
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text,
|
||||
turnId,
|
||||
command: text,
|
||||
cwd: "/vault",
|
||||
status: "completed",
|
||||
executionState: "completed",
|
||||
};
|
||||
}
|
||||
|
||||
function fileChangeItem(id: string, turnId: string, path = "src/main.ts"): DisplayItem {
|
||||
|
|
@ -38,6 +48,7 @@ function fileChangeItem(id: string, turnId: string, path = "src/main.ts"): Displ
|
|||
text: "File change completed",
|
||||
turnId,
|
||||
status: "completed",
|
||||
executionState: "completed",
|
||||
changes: [{ kind: "update", path, diff: "" }],
|
||||
};
|
||||
}
|
||||
|
|
@ -225,7 +236,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
model: "gpt-5.5",
|
||||
reasoningEffort: "high",
|
||||
agents: [{ threadId: "child-thread", status: "completed", message: "Done" }],
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -246,7 +257,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
expect(displayItemFromThreadItem(item, "t1")).toMatchObject({
|
||||
kind: "agent",
|
||||
status: "completed",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -269,7 +280,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
text: "npm run check (exit 1)",
|
||||
output: "stderr with many details",
|
||||
state: "failed",
|
||||
executionState: "failed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -292,7 +303,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "read",
|
||||
text: "src/main.ts",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -317,7 +328,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
text: "src/main.ts",
|
||||
command: "nl -ba src/main.ts | sed -n '1,20p'",
|
||||
cwd: "/vault",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -340,7 +351,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "read",
|
||||
text: "/vault/src/main.ts",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -365,7 +376,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
text: "npm run check",
|
||||
command: "/bin/zsh -lc 'npm run check'",
|
||||
cwd: "/vault",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -390,7 +401,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
text: '"command target" in src/display',
|
||||
command: "rg 'command target' src/display",
|
||||
cwd: "/vault",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -413,7 +424,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "search",
|
||||
text: "target in src/display",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -436,7 +447,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "search",
|
||||
text: "target in src/display",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -459,7 +470,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "list files",
|
||||
text: "src/display",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -485,7 +496,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "search",
|
||||
text: "target in src",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -508,7 +519,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "command",
|
||||
actionLabel: "list files",
|
||||
text: "workspace",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -530,7 +541,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
expect(displayItemFromThreadItem(item, "t1")).toMatchObject({
|
||||
kind: "command",
|
||||
text: "rg error src",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -569,16 +580,16 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
|
||||
expect(displayItemFromThreadItem(command, "t1")).toMatchObject({
|
||||
text: "npm run check",
|
||||
state: "running",
|
||||
executionState: "running",
|
||||
});
|
||||
expect(displayItemFromThreadItem(fileChange, "t1")).toMatchObject({
|
||||
text: "src/main.ts",
|
||||
state: "running",
|
||||
executionState: "running",
|
||||
});
|
||||
expect(displayItemFromThreadItem(tool, "t1")).toMatchObject({
|
||||
text: "123",
|
||||
toolLabel: "github.pull_request_read",
|
||||
state: "running",
|
||||
executionState: "running",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -604,7 +615,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
{ title: "Arguments JSON", body: expect.stringContaining('"id": 123') },
|
||||
{ title: "Error JSON", body: expect.stringContaining("Not found") },
|
||||
],
|
||||
state: "failed",
|
||||
executionState: "failed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -629,7 +640,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
{ title: "Arguments JSON", body: expect.stringContaining("https://example.com") },
|
||||
{ title: "Result JSON", body: expect.stringContaining("ok") },
|
||||
],
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -669,7 +680,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
{ title: "Revised prompt", body: "A precise UI mockup." },
|
||||
{ title: "Result", body: "image result" },
|
||||
],
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -711,7 +722,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
kind: "tool",
|
||||
text: "details",
|
||||
toolLabel: "multi_tool_use.parallel",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -778,7 +789,7 @@ describe("thread item conversion preserves app-server semantics", () => {
|
|||
).toMatchObject({
|
||||
kind: "reviewResult",
|
||||
text: "Auto-review approved: Auto-review returned a low-risk allow decision.",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
details: [
|
||||
{
|
||||
title: "Review",
|
||||
|
|
@ -898,7 +909,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
role: "tool",
|
||||
text: "Auto-review approved: npm test",
|
||||
turnId: "t1",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
{
|
||||
id: "review-2",
|
||||
|
|
@ -906,7 +917,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
role: "tool",
|
||||
text: "Auto-review approved: npm test",
|
||||
turnId: "t1",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "done", turnId: "t1" },
|
||||
];
|
||||
|
|
@ -926,7 +937,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
it("hides empty completed reasoning items", () => {
|
||||
const items: DisplayItem[] = [
|
||||
{ id: "u1", kind: "message", role: "user", text: "do it", turnId: "t1" },
|
||||
{ id: "r1", kind: "reasoning", role: "tool", text: "", turnId: "t1", status: "completed" },
|
||||
{ id: "r1", kind: "reasoning", role: "tool", text: "", turnId: "t1", status: "completed", executionState: "completed" },
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "done", turnId: "t1" },
|
||||
];
|
||||
|
||||
|
|
@ -1177,7 +1188,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
role: "tool",
|
||||
text: "Auto-review approved: npm test",
|
||||
turnId: "t1",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
{
|
||||
id: "review-2",
|
||||
|
|
@ -1185,7 +1196,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
role: "tool",
|
||||
text: "Auto-review approved: npm test",
|
||||
turnId: "t1",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "done", turnId: "t1" },
|
||||
];
|
||||
|
|
@ -1205,7 +1216,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
role: "tool",
|
||||
text: "Auto-review approved: npm test",
|
||||
turnId: "t1",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
{ id: "a1", kind: "message", role: "assistant", text: "still working", turnId: "t1" },
|
||||
];
|
||||
|
|
@ -1239,47 +1250,15 @@ describe("workspace path summaries stay readable without hiding audit paths", ()
|
|||
|
||||
describe("execution state uses typed status adapters before rendered text", () => {
|
||||
it("detects failed command state", () => {
|
||||
expect(
|
||||
executionState({
|
||||
id: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "Command",
|
||||
command: "npm test",
|
||||
cwd: "/vault",
|
||||
status: "failed",
|
||||
exitCode: 1,
|
||||
}),
|
||||
).toBe("failed");
|
||||
expect(commandExecutionState("failed", 1)).toBe("failed");
|
||||
});
|
||||
|
||||
it("does not infer command failure from the command text", () => {
|
||||
expect(
|
||||
executionState({
|
||||
id: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "rg error src",
|
||||
command: "rg error src",
|
||||
cwd: "/vault",
|
||||
status: "completed",
|
||||
exitCode: 0,
|
||||
}),
|
||||
).toBe("completed");
|
||||
expect(commandExecutionState("completed", 0)).toBe("completed");
|
||||
});
|
||||
|
||||
it("uses typed command status before command text", () => {
|
||||
expect(
|
||||
executionState({
|
||||
id: "c1",
|
||||
kind: "command",
|
||||
role: "tool",
|
||||
text: "rg error src",
|
||||
command: "rg error src",
|
||||
cwd: "/vault",
|
||||
status: "inProgress",
|
||||
}),
|
||||
).toBe("running");
|
||||
expect(commandExecutionState("inProgress")).toBe("running");
|
||||
});
|
||||
|
||||
it("does not infer unknown status strings with broad matching", () => {
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ describe("message stream block identity and message actions", () => {
|
|||
text: "Auto-review approved: npm test",
|
||||
turnId: "turn",
|
||||
markdown: false,
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
details: [
|
||||
{
|
||||
title: "Review",
|
||||
|
|
@ -276,7 +276,7 @@ describe("message stream block identity and message actions", () => {
|
|||
cwd: "/vault",
|
||||
status: "completed",
|
||||
output: "ok",
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
},
|
||||
],
|
||||
openDetails: new Set(),
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ describe("pending request renderer decisions", () => {
|
|||
text: "Input submitted for 1 question.",
|
||||
turnId: "turn",
|
||||
markdown: false,
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
details: [{ title: "Question: Scope", rows: [{ key: "Answer", value: "Narrow" }] }],
|
||||
},
|
||||
],
|
||||
|
|
@ -394,7 +394,7 @@ describe("pending request renderer decisions", () => {
|
|||
text: "Allowed for this session: Need access",
|
||||
turnId: "turn",
|
||||
markdown: false,
|
||||
state: "completed",
|
||||
executionState: "completed",
|
||||
details: [
|
||||
{
|
||||
title: "Approval",
|
||||
|
|
|
|||
Loading…
Reference in a new issue