From e6e2407cee75f1137b67d074864cc56952dfee99 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Tue, 24 Mar 2026 08:27:50 -0500 Subject: [PATCH] fix: initialize plugins before killed subagent hooks --- src/agents/subagent-registry.test.ts | 54 ++++++++++++++++++++++++++++ src/agents/subagent-registry.ts | 7 ++++ 2 files changed, 61 insertions(+) diff --git a/src/agents/subagent-registry.test.ts b/src/agents/subagent-registry.test.ts index 3eea8f9697f..1c58f02893e 100644 --- a/src/agents/subagent-registry.test.ts +++ b/src/agents/subagent-registry.test.ts @@ -346,4 +346,58 @@ describe("subagent registry seam flow", () => { }); }); }); + + it("loads runtime plugins before emitting killed subagent ended hooks", async () => { + const endedHookRunner = { + hasHooks: (hookName: string) => hookName === "subagent_ended", + runSubagentEnded: mocks.runSubagentEnded, + }; + mocks.getGlobalHookRunner.mockReturnValue(null); + mocks.ensureRuntimePluginsLoaded.mockImplementation(() => { + mocks.getGlobalHookRunner.mockReturnValue(endedHookRunner as never); + }); + + mod.registerSubagentRun({ + runId: "run-killed-init", + childSessionKey: "agent:main:subagent:killed", + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + requesterOrigin: { channel: "discord", accountId: "acct-1" }, + task: "kill after init", + cleanup: "keep", + workspaceDir: "/tmp/killed-workspace", + }); + + const updated = mod.markSubagentRunTerminated({ + runId: "run-killed-init", + reason: "manual kill", + }); + + expect(updated).toBe(1); + await vi.waitFor(() => { + expect(mocks.ensureRuntimePluginsLoaded).toHaveBeenCalledWith({ + config: { + agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } }, + session: { mainKey: "main", scope: "per-sender" }, + }, + workspaceDir: "/tmp/killed-workspace", + allowGatewaySubagentBinding: true, + }); + }); + expect(mocks.runSubagentEnded).toHaveBeenCalledWith( + expect.objectContaining({ + targetSessionKey: "agent:main:subagent:killed", + reason: "subagent-killed", + accountId: "acct-1", + runId: "run-killed-init", + outcome: "killed", + error: "manual kill", + }), + expect.objectContaining({ + runId: "run-killed-init", + childSessionKey: "agent:main:subagent:killed", + requesterSessionKey: "agent:main:main", + }), + ); + }); }); diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 72beaad8a54..c6a90e43c80 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -1654,10 +1654,17 @@ export function markSubagentRunTerminated(params: { childSessionKey: entry.childSessionKey, }); }); + const cfg = loadConfig(); + ensureRuntimePluginsLoaded({ + config: cfg, + workspaceDir: entry.workspaceDir, + allowGatewaySubagentBinding: true, + }); void emitSubagentEndedHookOnce({ entry, reason: SUBAGENT_ENDED_REASON_KILLED, sendFarewell: true, + accountId: entry.requesterOrigin?.accountId, outcome: SUBAGENT_ENDED_OUTCOME_KILLED, error: reason, inFlightRunIds: endedHookInFlightRunIds,