From d0c756e8ab97bacf8f5c87c0e2be272450482dcf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 12:26:57 +0100 Subject: [PATCH] perf(test): slim subagent lifecycle imports --- ...agents.sessions-spawn-depth-limits.test.ts | 20 +++++--- ...subagents.sessions-spawn.lifecycle.test.ts | 27 ++++------ ...s.subagents.sessions-spawn.test-harness.ts | 49 +++++++++++++++++++ src/agents/spawn-requester-origin.ts | 2 +- src/agents/subagent-announce-queue.ts | 7 +-- src/agents/subagent-control.test.ts | 3 ++ src/agents/subagent-registry-announce-read.ts | 3 +- src/agents/subagent-registry-lifecycle.ts | 39 ++++++++++----- src/agents/subagent-registry-run-manager.ts | 3 +- src/agents/subagent-registry.store.ts | 2 +- src/agents/subagent-registry.ts | 46 +++++++++++++---- src/agents/tools/sessions-spawn-tool.ts | 2 +- 12 files changed, 147 insertions(+), 56 deletions(-) diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-depth-limits.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-depth-limits.test.ts index a5a09a57a43..61c52a6df78 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-depth-limits.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-depth-limits.test.ts @@ -10,6 +10,16 @@ vi.mock("../gateway/call.js", () => ({ callGateway: (opts: unknown) => callGatewayMock(opts), })); +vi.mock("../tasks/task-executor.js", () => ({ + completeTaskRunByRunId: vi.fn(), + createQueuedTaskRun: vi.fn(), + createRunningTaskRun: vi.fn(), + failTaskRunByRunId: vi.fn(), + recordTaskRunProgressByRunId: vi.fn(), + setDetachedTaskDeliveryStatusByRunId: vi.fn(), + startTaskRunByRunId: vi.fn(), +})); + let storeTemplatePath = ""; let configOverride: Record = { session: createPerSenderSessionConfig(), @@ -20,13 +30,9 @@ let subagentRegistryTesting: typeof import("./subagent-registry.js").__testing; let setSubagentSpawnDepsForTest: typeof import("./subagent-spawn.js").__testing.setDepsForTest; let createSessionsSpawnTool: typeof import("./tools/sessions-spawn-tool.js").createSessionsSpawnTool; -vi.mock("../config/config.js", async () => { - const actual = await vi.importActual("../config/config.js"); - return { - ...actual, - loadConfig: () => configOverride, - }; -}); +vi.mock("../config/config.js", () => ({ + loadConfig: () => configOverride, +})); function writeStore(agentId: string, store: Record) { const storePath = storeTemplatePath.replaceAll("{agentId}", agentId); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts index ebb83de0a77..948f69fc957 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts @@ -11,6 +11,7 @@ import { setSessionsSpawnHookRunnerOverride, setupSessionsSpawnGatewayMock, setSessionsSpawnConfigOverride, + waitForSessionsSpawnEvent, } from "./openclaw-tools.subagents.sessions-spawn.test-harness.js"; import { getLatestSubagentRunByChildSessionKey, @@ -61,15 +62,6 @@ function buildDiscordCleanupHooks(onDelete: (key: string | undefined) => void) { }; } -const waitFor = async (label: string, predicate: () => boolean, timeoutMs = 30_000) => { - await vi.waitFor( - () => { - expect(predicate(), label).toBe(true); - }, - { timeout: timeoutMs, interval: 1 }, - ); -}; - async function getDiscordGroupSpawnTool() { return await getSessionsSpawnTool({ agentSessionKey: "discord:group:req", @@ -152,7 +144,7 @@ async function emitLifecycleEndAndFlush(params: { } async function waitForRunCleanup(childSessionKey: string) { - await waitFor("run cleanup bookkeeping", () => { + await waitForSessionsSpawnEvent("run cleanup bookkeeping", () => { const run = getLatestSubagentRunByChildSessionKey(childSessionKey); return run?.cleanupCompletedAt != null; }); @@ -232,7 +224,7 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { if (!child.runId) { throw new Error("missing child runId"); } - await waitFor( + await waitForSessionsSpawnEvent( "subagent wait, label patch, and main agent trigger", () => ctx.waitCalls.some((call) => call.runId === child.runId) && @@ -295,7 +287,7 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { endedAt: 2345, }); - await waitFor( + await waitForSessionsSpawnEvent( "lifecycle cleanup", () => ctx.calls.filter((call) => call.method === "agent").length >= 2 && Boolean(deletedKey), ); @@ -358,14 +350,14 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { if (!child.runId) { throw new Error("missing child runId"); } - await waitFor("agent.wait called for child run", () => + await waitForSessionsSpawnEvent("agent.wait called for child run", () => ctx.waitCalls.some((call) => call.runId === child.runId), ); - await waitFor( + await waitForSessionsSpawnEvent( "main agent cleanup trigger", () => ctx.calls.filter((call) => call.method === "agent").length >= 2, ); - await waitFor("delete cleanup", () => Boolean(deletedKey)); + await waitForSessionsSpawnEvent("delete cleanup", () => Boolean(deletedKey)); const childWait = ctx.waitCalls.find((call) => call.runId === child.runId); expect(childWait?.timeoutMs).toBe(1000); @@ -416,12 +408,11 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { } const childSessionKey = child.sessionKey; - await waitFor( + await waitForSessionsSpawnEvent( "timeout outcome", () => ctx.waitCalls.some((call) => call.runId === child.runId) && getLatestSubagentRunByChildSessionKey(childSessionKey)?.outcome?.status === "timeout", - 20_000, ); await waitForRunCleanup(childSessionKey); @@ -488,7 +479,7 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { endedAt: 2000, }); - await waitFor( + await waitForSessionsSpawnEvent( "account-aware lifecycle announce", () => ctx.calls.filter((call) => call.method === "agent").length >= 2, ); 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 054b9780934..b9f68afcbc8 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.test-harness.ts @@ -23,6 +23,13 @@ type SessionsSpawnGatewayMockOptions = { onSessionsDelete?: (params: unknown) => void; agentWaitResult?: { status: "ok" | "timeout"; startedAt: number; endedAt: number }; }; +type EventWaiter = { + label: string; + predicate: () => boolean; + resolve: () => void; + reject: (error: Error) => void; + timer: ReturnType; +}; const hoisted = vi.hoisted(() => { const callGatewayMock = vi.fn(); @@ -90,9 +97,23 @@ const hoisted = vi.hoisted(() => { defaultRunSubagentAnnounceFlow, runSubagentAnnounceFlowOverride: defaultRunSubagentAnnounceFlow, }; + const eventWaiters: EventWaiter[] = []; + const notifyEventWaiters = () => { + for (let index = eventWaiters.length - 1; index >= 0; index -= 1) { + const waiter = eventWaiters[index]; + if (!waiter?.predicate()) { + continue; + } + clearTimeout(waiter.timer); + eventWaiters.splice(index, 1); + waiter.resolve(); + } + }; return { callGatewayMock, defaultConfigOverride, + eventWaiters, + notifyEventWaiters, nextRunId: () => { nextRunId += 1; return `run-${nextRunId}`; @@ -122,6 +143,26 @@ export function findGatewayRequest(method: string): GatewayRequest | undefined { return getGatewayRequests().find((request) => request.method === method); } +export async function waitForSessionsSpawnEvent( + label: string, + predicate: () => boolean, + timeoutMs = 5_000, +): Promise { + if (predicate()) { + return; + } + await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + const index = hoisted.eventWaiters.findIndex((waiter) => waiter.timer === timer); + if (index >= 0) { + hoisted.eventWaiters.splice(index, 1); + } + reject(new Error(`Timed out waiting for ${label}`)); + }, timeoutMs); + hoisted.eventWaiters.push({ label, predicate, resolve, reject, timer }); + }); +} + export function resetSessionsSpawnConfigOverride(): void { hoisted.state.configOverride = hoisted.defaultConfigOverride; } @@ -165,6 +206,10 @@ export async function getSessionsSpawnTool(opts: CreateOpenClawToolsOpts) { cleanupBrowserSessionsForLifecycleEnd: async () => {}, ensureContextEnginesInitialized: () => {}, ensureRuntimePluginsLoaded: () => {}, + persistSubagentRunsToDisk: () => { + hoisted.notifyEventWaiters(); + }, + restoreSubagentRunsFromDisk: () => 0, resolveContextEngine: async () => ({ info: { id: "test", name: "Test" }, assemble: async ({ messages }) => ({ messages, estimatedTokens: 0 }), @@ -195,6 +240,7 @@ export function setupSessionsSpawnGatewayMock(setupOpts: SessionsSpawnGatewayMoc getCallGatewayMock().mockImplementation(async (optsUnknown: unknown) => { const request = optsUnknown as GatewayRequest; calls.push(request); + hoisted.notifyEventWaiters(); if (request.method === "sessions.list" && setupOpts.includeSessionsList) { return { @@ -233,6 +279,7 @@ export function setupSessionsSpawnGatewayMock(setupOpts: SessionsSpawnGatewayMoc if (request.method === "agent.wait") { const params = request.params as AgentWaitCall | undefined; waitCalls.push(params ?? {}); + hoisted.notifyEventWaiters(); const waitResult = setupOpts.agentWaitResult ?? { status: "ok", startedAt: 1000, @@ -246,11 +293,13 @@ export function setupSessionsSpawnGatewayMock(setupOpts: SessionsSpawnGatewayMoc if (request.method === "sessions.patch") { setupOpts.onSessionsPatch?.(request.params); + hoisted.notifyEventWaiters(); return { ok: true }; } if (request.method === "sessions.delete") { setupOpts.onSessionsDelete?.(request.params); + hoisted.notifyEventWaiters(); return { ok: true }; } diff --git a/src/agents/spawn-requester-origin.ts b/src/agents/spawn-requester-origin.ts index a90a2588cb0..1fe3dc92824 100644 --- a/src/agents/spawn-requester-origin.ts +++ b/src/agents/spawn-requester-origin.ts @@ -1,7 +1,7 @@ import type { ChatType } from "../channels/chat-type.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveFirstBoundAccountId } from "../routing/bound-account-read.js"; -import { normalizeDeliveryContext } from "../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; // Delivery targets often carry a transport wrapper (e.g. Matrix `room:` or // LINE `line:group:`), while route bindings commonly store raw peer ids on diff --git a/src/agents/subagent-announce-queue.ts b/src/agents/subagent-announce-queue.ts index e4e9eccf0ec..725136ee759 100644 --- a/src/agents/subagent-announce-queue.ts +++ b/src/agents/subagent-announce-queue.ts @@ -1,10 +1,7 @@ import { type QueueDropPolicy, type QueueMode } from "../auto-reply/reply/queue.js"; import { defaultRuntime } from "../runtime.js"; -import { - type DeliveryContext, - deliveryContextKey, - normalizeDeliveryContext, -} from "../utils/delivery-context.js"; +import { deliveryContextKey, normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +import type { DeliveryContext } from "../utils/delivery-context.types.js"; import { applyQueueRuntimeSettings, applyQueueDropPolicy, diff --git a/src/agents/subagent-control.test.ts b/src/agents/subagent-control.test.ts index 42667f47f7a..4ed37737569 100644 --- a/src/agents/subagent-control.test.ts +++ b/src/agents/subagent-control.test.ts @@ -159,6 +159,9 @@ beforeEach(() => { cleanupBrowserSessionsForLifecycleEnd: async () => {}, ensureContextEnginesInitialized: () => {}, ensureRuntimePluginsLoaded: () => {}, + getSubagentRunsSnapshotForRead: (runs) => new Map(runs), + persistSubagentRunsToDisk: () => {}, + restoreSubagentRunsFromDisk: () => 0, resolveContextEngine: async () => ({ info: { id: "test", name: "Test" }, assemble: async ({ messages }) => ({ messages, estimatedTokens: 0 }), diff --git a/src/agents/subagent-registry-announce-read.ts b/src/agents/subagent-registry-announce-read.ts index 147cb4113cb..a4a109b9d55 100644 --- a/src/agents/subagent-registry-announce-read.ts +++ b/src/agents/subagent-registry-announce-read.ts @@ -1,4 +1,5 @@ -import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +import type { DeliveryContext } from "../utils/delivery-context.types.js"; import { subagentRuns } from "./subagent-registry-memory.js"; import { countPendingDescendantRunsExcludingRunFromRuns, diff --git a/src/agents/subagent-registry-lifecycle.ts b/src/agents/subagent-registry-lifecycle.ts index 6d89a3265ca..8452c9cc491 100644 --- a/src/agents/subagent-registry-lifecycle.ts +++ b/src/agents/subagent-registry-lifecycle.ts @@ -1,5 +1,5 @@ import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; -import { cleanupBrowserSessionsForLifecycleEnd } from "../browser-lifecycle-cleanup.js"; +import type { cleanupBrowserSessionsForLifecycleEnd } from "../browser-lifecycle-cleanup.js"; import { formatErrorMessage, readErrorName } from "../infra/errors.js"; import { defaultRuntime } from "../runtime.js"; import { emitSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; @@ -8,13 +8,8 @@ import { failTaskRunByRunId, setDetachedTaskDeliveryStatusByRunId, } from "../tasks/detached-task-runtime.js"; -import { normalizeDeliveryContext } from "../utils/delivery-context.js"; -import { withSubagentOutcomeTiming } from "./subagent-announce-output.js"; -import { - captureSubagentCompletionReply, - runSubagentAnnounceFlow, - type SubagentRunOutcome, -} from "./subagent-announce.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +import { type SubagentRunOutcome, withSubagentOutcomeTiming } from "./subagent-announce-output.js"; import { SUBAGENT_ENDED_REASON_COMPLETE, type SubagentLifecycleEndedReason, @@ -37,6 +32,23 @@ import { } from "./subagent-registry-helpers.js"; import type { SubagentRunRecord } from "./subagent-registry.types.js"; +type CaptureSubagentCompletionReply = + (typeof import("./subagent-announce.js"))["captureSubagentCompletionReply"]; +type RunSubagentAnnounceFlow = (typeof import("./subagent-announce.js"))["runSubagentAnnounceFlow"]; +type BrowserCleanupModule = Pick< + typeof import("../browser-lifecycle-cleanup.js"), + "cleanupBrowserSessionsForLifecycleEnd" +>; + +let browserCleanupPromise: Promise | null = null; + +async function loadCleanupBrowserSessionsForLifecycleEnd(): Promise< + BrowserCleanupModule["cleanupBrowserSessionsForLifecycleEnd"] +> { + browserCleanupPromise ??= import("../browser-lifecycle-cleanup.js"); + return (await browserCleanupPromise).cleanupBrowserSessionsForLifecycleEnd; +} + export function createSubagentRegistryLifecycleController(params: { runs: Map; resumedRuns: Set; @@ -61,9 +73,9 @@ export function createSubagentRegistryLifecycleController(params: { workspaceDir?: string; }): Promise; resumeSubagentRun(runId: string): void; - captureSubagentCompletionReply: typeof captureSubagentCompletionReply; + captureSubagentCompletionReply: CaptureSubagentCompletionReply; cleanupBrowserSessionsForLifecycleEnd?: typeof cleanupBrowserSessionsForLifecycleEnd; - runSubagentAnnounceFlow: typeof runSubagentAnnounceFlow; + runSubagentAnnounceFlow: RunSubagentAnnounceFlow; warn(message: string, meta?: Record): void; }) { const scheduledResumeTimers = new Set>(); @@ -223,7 +235,7 @@ export function createSubagentRegistryLifecycleController(params: { let captured: string | undefined; try { - captured = await captureSubagentCompletionReply(sessionKey); + captured = await params.captureSubagentCompletionReply(sessionKey); } catch { return false; } @@ -658,7 +670,10 @@ export function createSubagentRegistryLifecycleController(params: { return; } - await (params.cleanupBrowserSessionsForLifecycleEnd ?? cleanupBrowserSessionsForLifecycleEnd)({ + const cleanupBrowserSessions = + params.cleanupBrowserSessionsForLifecycleEnd ?? + (await loadCleanupBrowserSessionsForLifecycleEnd()); + await cleanupBrowserSessions({ sessionKeys: [entry.childSessionKey], onWarn: (msg) => params.warn(msg, { runId: entry.runId }), }); diff --git a/src/agents/subagent-registry-run-manager.ts b/src/agents/subagent-registry-run-manager.ts index a5e2d605cb2..b93a20f2910 100644 --- a/src/agents/subagent-registry-run-manager.ts +++ b/src/agents/subagent-registry-run-manager.ts @@ -4,7 +4,8 @@ import { callGateway } from "../gateway/call.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { getGlobalHookRunner } from "../plugins/hook-runner-global.js"; import { createRunningTaskRun } from "../tasks/detached-task-runtime.js"; -import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +import type { DeliveryContext } from "../utils/delivery-context.types.js"; import { waitForAgentRun } from "./run-wait.js"; import type { ensureRuntimePluginsLoaded as ensureRuntimePluginsLoadedFn } from "./runtime-plugins.js"; import { type SubagentRunOutcome, withSubagentOutcomeTiming } from "./subagent-announce-output.js"; diff --git a/src/agents/subagent-registry.store.ts b/src/agents/subagent-registry.store.ts index bf7a5c693a5..4b3a21ac2b9 100644 --- a/src/agents/subagent-registry.store.ts +++ b/src/agents/subagent-registry.store.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { resolveStateDir } from "../config/paths.js"; import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { readStringValue } from "../shared/string-coerce.js"; -import { normalizeDeliveryContext } from "../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; import type { SubagentRunRecord } from "./subagent-registry.types.js"; export type PersistedSubagentRegistryVersion = 1 | 2; diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 4a5264de4fc..af1d9707293 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -1,4 +1,4 @@ -import { cleanupBrowserSessionsForLifecycleEnd } from "../browser-lifecycle-cleanup.js"; +import type { cleanupBrowserSessionsForLifecycleEnd } from "../browser-lifecycle-cleanup.js"; import { loadConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { ContextEngine, SubagentEndReason } from "../context-engine/types.js"; @@ -6,11 +6,11 @@ import { callGateway } from "../gateway/call.js"; import { onAgentEvent } from "../infra/agent-events.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { importRuntimeModule } from "../shared/runtime-import.js"; -import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../utils/delivery-context.shared.js"; +import type { DeliveryContext } from "../utils/delivery-context.types.js"; import type { ensureRuntimePluginsLoaded as ensureRuntimePluginsLoadedFn } from "./runtime-plugins.js"; import type { SubagentRunOutcome } from "./subagent-announce-output.js"; import { resetAnnounceQueuesForTests } from "./subagent-announce-queue.js"; -import * as subagentAnnounceModule from "./subagent-announce.js"; import { SUBAGENT_ENDED_REASON_COMPLETE, SUBAGENT_ENDED_REASON_ERROR, @@ -67,9 +67,18 @@ export { } from "./subagent-registry-helpers.js"; const log = createSubsystemLogger("agents/subagent-registry"); +type SubagentAnnounceModule = Pick< + typeof import("./subagent-announce.js"), + "captureSubagentCompletionReply" | "runSubagentAnnounceFlow" +>; +type BrowserCleanupModule = Pick< + typeof import("../browser-lifecycle-cleanup.js"), + "cleanupBrowserSessionsForLifecycleEnd" +>; + type SubagentRegistryDeps = { callGateway: typeof callGateway; - captureSubagentCompletionReply: typeof subagentAnnounceModule.captureSubagentCompletionReply; + captureSubagentCompletionReply: SubagentAnnounceModule["captureSubagentCompletionReply"]; cleanupBrowserSessionsForLifecycleEnd: typeof cleanupBrowserSessionsForLifecycleEnd; getSubagentRunsSnapshotForRead: typeof getSubagentRunsSnapshotForRead; loadConfig: typeof loadConfig; @@ -77,24 +86,41 @@ type SubagentRegistryDeps = { persistSubagentRunsToDisk: typeof persistSubagentRunsToDisk; resolveAgentTimeoutMs: typeof resolveAgentTimeoutMs; restoreSubagentRunsFromDisk: typeof restoreSubagentRunsFromDisk; - runSubagentAnnounceFlow: typeof subagentAnnounceModule.runSubagentAnnounceFlow; + runSubagentAnnounceFlow: SubagentAnnounceModule["runSubagentAnnounceFlow"]; ensureContextEnginesInitialized?: () => void; ensureRuntimePluginsLoaded?: typeof ensureRuntimePluginsLoadedFn; resolveContextEngine?: (cfg: OpenClawConfig) => Promise; }; +let subagentAnnouncePromise: Promise | null = null; +let browserCleanupPromise: Promise | null = null; + +async function loadSubagentAnnounceModule(): Promise { + subagentAnnouncePromise ??= import("./subagent-announce.js"); + return await subagentAnnouncePromise; +} + +async function loadCleanupBrowserSessionsForLifecycleEnd(): Promise< + BrowserCleanupModule["cleanupBrowserSessionsForLifecycleEnd"] +> { + browserCleanupPromise ??= import("../browser-lifecycle-cleanup.js"); + return (await browserCleanupPromise).cleanupBrowserSessionsForLifecycleEnd; +} + const defaultSubagentRegistryDeps: SubagentRegistryDeps = { callGateway, - captureSubagentCompletionReply: (sessionKey) => - subagentAnnounceModule.captureSubagentCompletionReply(sessionKey), - cleanupBrowserSessionsForLifecycleEnd, + captureSubagentCompletionReply: async (sessionKey) => + (await loadSubagentAnnounceModule()).captureSubagentCompletionReply(sessionKey), + cleanupBrowserSessionsForLifecycleEnd: async (params) => + (await loadCleanupBrowserSessionsForLifecycleEnd())(params), getSubagentRunsSnapshotForRead, loadConfig, onAgentEvent, persistSubagentRunsToDisk, resolveAgentTimeoutMs, restoreSubagentRunsFromDisk, - runSubagentAnnounceFlow: (params) => subagentAnnounceModule.runSubagentAnnounceFlow(params), + runSubagentAnnounceFlow: async (params) => + (await loadSubagentAnnounceModule()).runSubagentAnnounceFlow(params), }; let subagentRegistryDeps: SubagentRegistryDeps = defaultSubagentRegistryDeps; @@ -744,6 +770,8 @@ export function resetSubagentRegistryForTests(opts?: { persist?: boolean }) { contextEngineInitPromise = null; contextEngineRegistryPromise = null; runtimePluginsPromise = null; + subagentAnnouncePromise = null; + browserCleanupPromise = null; resetAnnounceQueuesForTests(); stopSweeper(); sweepInProgress = false; diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index 35b13c8710a..a9e64fc20e2 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -1,7 +1,7 @@ import { Type } from "@sinclair/typebox"; import { loadConfig } from "../../config/config.js"; import { callGateway } from "../../gateway/call.js"; -import { normalizeDeliveryContext } from "../../utils/delivery-context.js"; +import { normalizeDeliveryContext } from "../../utils/delivery-context.shared.js"; import type { GatewayMessageChannel } from "../../utils/message-channel.js"; import { optionalStringEnum } from "../schema/typebox.js"; import type { SpawnedToolContext } from "../spawned-context.js";