diff --git a/.changeset/resolve-tool-results.md b/.changeset/resolve-tool-results.md new file mode 100644 index 00000000..33cde60c --- /dev/null +++ b/.changeset/resolve-tool-results.md @@ -0,0 +1,5 @@ +--- +"@perstack/tui-components": patch +--- + +Handle resolveToolResults event type in delegation tree builder diff --git a/packages/tui-components/src/log-viewer/build-run-tree.test.ts b/packages/tui-components/src/log-viewer/build-run-tree.test.ts index 4bdad4a5..eb5525c7 100644 --- a/packages/tui-components/src/log-viewer/build-run-tree.test.ts +++ b/packages/tui-components/src/log-viewer/build-run-tree.test.ts @@ -503,6 +503,43 @@ describe("buildRunTreeFromEvents", () => { expect(gd1Flat.depth).toBe(4) // root > build > te1 > bg1 > gd1 }) + it("handles resolveToolResults events without affecting tree structure", () => { + // resolveToolResults fires after MCP tool execution and after resumeFromStop. + // It should be handled gracefully without creating spurious nodes or breaking merges. + const events = [ + startRun("root", "coordinator"), + makeEvent("callTools", "root", "coordinator", { + newMessage: {}, + toolCalls: [], + usage: baseUsage, + }), + // resolveToolResults after MCP tool execution + makeEvent("resolveToolResults", "root", "coordinator", { + toolResults: [{ id: "tr-1", skillName: "read_file", toolName: "read_file", result: [] }], + }), + stopByDelegate("root", "coordinator"), + startRun("child", "@coordinator/worker", "root"), + completeRun("child", "@coordinator/worker"), + // Resume and immediately resolveToolResults (same timestamp pattern from real data) + resumeFromStop("root-resume", "coordinator"), + makeEvent("resolveToolResults", "root-resume", "coordinator", { + toolResults: [{ id: "tr-2", skillName: "delegate/worker", toolName: "worker", result: [] }], + }), + completeRun("root-resume", "coordinator"), + ] + + const { treeState, runStats } = buildRunTreeFromEvents(events) + + // Resume merged correctly + expect(treeState.nodes.size).toBe(2) // root, child + expect(treeState.nodes.get("root")?.status).toBe("completed") + expect(treeState.nodes.get("child")?.parentRunId).toBe("root") + + // resolveToolResults events counted in stats + const rootStats = runStats.get("root")! + expect(rootStats.eventCount).toBeGreaterThanOrEqual(6) // start, callTools, resolve, stop, resume, resolve, complete + }) + it("shows failed delegates as error nodes (startRun + stopRunByError)", () => { // With the runtime fix, failed delegates now emit startRun + stopRunByError events. // design-roles delegates to 4x find-skill, all fail immediately. diff --git a/packages/tui-components/src/log-viewer/build-run-tree.ts b/packages/tui-components/src/log-viewer/build-run-tree.ts index 0fab8f5d..1f582058 100644 --- a/packages/tui-components/src/log-viewer/build-run-tree.ts +++ b/packages/tui-components/src/log-viewer/build-run-tree.ts @@ -296,6 +296,12 @@ export function buildRunTreeFromEvents(events: RunEvent[]): RunTreeResult { } break } + + case "resolveToolResults": { + // Tool results resolved — no node state change needed. + // Stats are already recorded at the top of the loop. + break + } } }