diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts index b117f06d600..6da0253f6af 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts @@ -11,11 +11,10 @@ import { } from "./subagent-announce.js"; import { __testing as subagentRegistryTesting } from "./subagent-registry.js"; import { __testing as subagentSpawnTesting } from "./subagent-spawn.js"; +import type { SubagentLifecycleHookRunner } from "./subagent-spawn.js"; type SessionsSpawnTestConfig = ReturnType<(typeof import("../config/config.js"))["loadConfig"]>; -type SessionsSpawnHookRunner = ReturnType< - (typeof import("../plugins/hook-runner-global.js"))["getGlobalHookRunner"] ->; +type SessionsSpawnHookRunner = SubagentLifecycleHookRunner | null; type CreateSessionsSpawnTool = (typeof import("./tools/sessions-spawn-tool.js"))["createSessionsSpawnTool"]; export type CreateOpenClawToolsOpts = Parameters[0]; @@ -87,7 +86,7 @@ const hoisted = vi.hoisted(() => { set configOverride(next: SessionsSpawnTestConfig) { configOverride = next; }, - hookRunnerOverride: undefined as SessionsSpawnHookRunner, + hookRunnerOverride: null as SessionsSpawnHookRunner, defaultRunSubagentAnnounceFlow, runSubagentAnnounceFlowOverride: defaultRunSubagentAnnounceFlow, }; @@ -123,7 +122,7 @@ export function resetSessionsSpawnAnnounceFlowOverride(): void { } export function resetSessionsSpawnHookRunnerOverride(): void { - hoisted.state.hookRunnerOverride = undefined; + hoisted.state.hookRunnerOverride = null; } export function setSessionsSpawnHookRunnerOverride(next: SessionsSpawnHookRunner): void { diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagent-spawn.ts index ba49d860d13..191e26a9690 100644 --- a/src/agents/subagent-spawn.ts +++ b/src/agents/subagent-spawn.ts @@ -52,11 +52,16 @@ export { decodeStrictBase64 }; type SubagentSpawnDeps = { callGateway: typeof callGateway; - getGlobalHookRunner: typeof getGlobalHookRunner; + getGlobalHookRunner: () => SubagentLifecycleHookRunner | null; loadConfig: typeof loadConfig; updateSessionStore: typeof updateSessionStore; }; +export type SubagentLifecycleHookRunner = Pick< + NonNullable>, + "hasHooks" | "runSubagentSpawning" | "runSubagentSpawned" | "runSubagentEnded" +>; + const defaultSubagentSpawnDeps: SubagentSpawnDeps = { callGateway, getGlobalHookRunner, @@ -150,6 +155,14 @@ async function callSubagentGateway( return await subagentSpawnDeps.callGateway(params); } +function readGatewayRunId(response: Awaited>): string | undefined { + if (!response || typeof response !== "object") { + return undefined; + } + const { runId } = response as { runId?: unknown }; + return typeof runId === "string" && runId ? runId : undefined; +} + function loadSubagentConfig() { return subagentSpawnDeps.loadConfig(); } @@ -264,7 +277,7 @@ function summarizeError(err: unknown): string { } async function ensureThreadBindingForSubagentSpawn(params: { - hookRunner: ReturnType; + hookRunner: SubagentLifecycleHookRunner | null; childSessionKey: string; agentId: string; label?: string; @@ -687,7 +700,7 @@ export async function spawnSubagentDirect( workspaceDir: _workspaceDir, ...publicSpawnedMetadata } = spawnedMetadata; - const response = await callSubagentGateway<{ runId: string }>({ + const response = await callSubagentGateway({ method: "agent", params: { message: childTaskMessage, @@ -707,8 +720,9 @@ export async function spawnSubagentDirect( }, timeoutMs: 10_000, }); - if (typeof response?.runId === "string" && response.runId) { - childRunId = response.runId; + const runId = readGatewayRunId(response); + if (runId) { + childRunId = runId; } } catch (err) { if (attachmentAbsDir) {