From 8aace2b4482855b7ce1c35f16676523cd6d5c80f Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:00:07 -0500 Subject: [PATCH] fix(regression): hydrate node tool event metadata --- src/gateway/server-chat.agent-events.test.ts | 63 ++++++++++++++++++++ src/gateway/server-chat.ts | 6 +- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/gateway/server-chat.agent-events.test.ts b/src/gateway/server-chat.agent-events.test.ts index c49a7b306a5..e8c1c8b2cd1 100644 --- a/src/gateway/server-chat.agent-events.test.ts +++ b/src/gateway/server-chat.agent-events.test.ts @@ -781,6 +781,69 @@ describe("agent event handler", () => { resetAgentRunContextForTest(); }); + it("hydrates node session tool events with session ownership metadata", () => { + const { nodeSendToSession, handler } = createHarness({ + resolveSessionKeyForRun: () => "session-1", + }); + + vi.mocked(loadGatewaySessionRow).mockReturnValue({ + key: "session-1", + kind: "direct", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + lastThreadId: 42, + fastMode: true, + verboseLevel: "on", + updatedAt: 1_200, + }); + + registerAgentRunContext("run-tool-node", { sessionKey: "session-1", verboseLevel: "on" }); + + handler({ + runId: "run-tool-node", + seq: 1, + stream: "tool", + ts: 1_234, + data: { + phase: "start", + name: "exec", + toolCallId: "tool-node-1", + args: { command: "echo hi" }, + }, + }); + + expect(nodeSendToSession).toHaveBeenCalledWith( + "session-1", + "agent", + expect.objectContaining({ + runId: "run-tool-node", + sessionKey: "session-1", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + lastThreadId: 42, + fastMode: true, + verboseLevel: "on", + stream: "tool", + ts: 1_234, + data: expect.objectContaining({ + phase: "start", + name: "exec", + toolCallId: "tool-node-1", + args: { command: "echo hi" }, + }), + }), + ); + resetAgentRunContextForTest(); + }); + it("broadcasts terminal session status to session subscribers on lifecycle end", () => { const { broadcastToConnIds, sessionEventSubscribers, handler } = createHarness({ resolveSessionKeyForRun: () => "session-finished", diff --git a/src/gateway/server-chat.ts b/src/gateway/server-chat.ts index 831d4a31dd2..a1e9382561d 100644 --- a/src/gateway/server-chat.ts +++ b/src/gateway/server-chat.ts @@ -803,7 +803,11 @@ export function createAgentEventHandler({ // Send tool events to node/channel subscribers only when verbose is enabled; // WS clients already received the event above via broadcastToConnIds. if (!isToolEvent || toolVerbose !== "off") { - nodeSendToSession(sessionKey, "agent", isToolEvent ? toolPayload : agentPayload); + nodeSendToSession( + sessionKey, + "agent", + isToolEvent ? { ...toolPayload, ...buildSessionEventSnapshot(sessionKey) } : agentPayload, + ); } if (!isAborted && evt.stream === "assistant" && typeof evt.data?.text === "string") { emitChatDelta(sessionKey, clientRunId, evt.runId, evt.seq, evt.data.text, evt.data.delta);