diff --git a/src/cli/program/register.agent.test.ts b/src/cli/program/register.agent.test.ts index 1220a64cfb7..9929d3acf95 100644 --- a/src/cli/program/register.agent.test.ts +++ b/src/cli/program/register.agent.test.ts @@ -12,7 +12,6 @@ const mocks = vi.hoisted(() => ({ agentsSetIdentityCommandMock: vi.fn(), agentsUnbindCommandMock: vi.fn(), setVerboseMock: vi.fn(), - createDefaultDepsMock: vi.fn(() => ({ deps: true })), runtime: { log: vi.fn(), error: vi.fn(), @@ -29,7 +28,6 @@ const agentsListCommandMock = mocks.agentsListCommandMock; const agentsSetIdentityCommandMock = mocks.agentsSetIdentityCommandMock; const agentsUnbindCommandMock = mocks.agentsUnbindCommandMock; const setVerboseMock = mocks.setVerboseMock; -const createDefaultDepsMock = mocks.createDefaultDepsMock; const runtime = mocks.runtime; vi.mock("../../commands/agent-via-gateway.js", () => ({ @@ -62,10 +60,6 @@ vi.mock("../../global-state.js", () => ({ setVerbose: mocks.setVerboseMock, })); -vi.mock("../deps.js", () => ({ - createDefaultDeps: mocks.createDefaultDepsMock, -})); - vi.mock("../../runtime.js", () => ({ defaultRuntime: mocks.runtime, })); @@ -88,7 +82,6 @@ describe("registerAgentCommands", () => { agentsListCommandMock.mockResolvedValue(undefined); agentsSetIdentityCommandMock.mockResolvedValue(undefined); agentsUnbindCommandMock.mockResolvedValue(undefined); - createDefaultDepsMock.mockReturnValue({ deps: true }); }); function commandCall(mock: { mock: { calls: unknown[][] } }, index = 0): unknown[] { @@ -99,17 +92,16 @@ describe("registerAgentCommands", () => { return call; } - it("runs agent command with deps and verbose enabled for --verbose on", async () => { + it("runs agent command with verbose enabled for --verbose on", async () => { await runCli(["agent", "--message", "hi", "--verbose", "ON", "--json"]); expect(setVerboseMock).toHaveBeenCalledWith(true); - expect(createDefaultDepsMock).toHaveBeenCalledTimes(1); const [options, callRuntime, deps] = commandCall(agentCliCommandMock); expect((options as { message?: string }).message).toBe("hi"); expect((options as { verbose?: string }).verbose).toBe("ON"); expect((options as { json?: boolean }).json).toBe(true); expect(callRuntime).toBe(runtime); - expect(deps).toEqual({ deps: true }); + expect(deps).toBeUndefined(); }); it("runs agent command with verbose disabled for --verbose off", async () => { @@ -120,7 +112,7 @@ describe("registerAgentCommands", () => { expect((options as { message?: string }).message).toBe("hi"); expect((options as { verbose?: string }).verbose).toBe("off"); expect(callRuntime).toBe(runtime); - expect(deps).toEqual({ deps: true }); + expect(deps).toBeUndefined(); }); it("accepts a model override for one-shot agent runs", async () => { @@ -131,7 +123,7 @@ describe("registerAgentCommands", () => { expect((options as { agent?: string }).agent).toBe("ops"); expect((options as { model?: string }).model).toBe("openai/gpt-5.4"); expect(callRuntime).toBe(runtime); - expect(deps).toEqual({ deps: true }); + expect(deps).toBeUndefined(); }); it("forwards an explicit session key to the agent command", async () => { @@ -141,7 +133,7 @@ describe("registerAgentCommands", () => { expect((options as { message?: string }).message).toBe("hi"); expect((options as { sessionKey?: string }).sessionKey).toBe("agent:ops:incident-42"); expect(callRuntime).toBe(runtime); - expect(deps).toEqual({ deps: true }); + expect(deps).toBeUndefined(); }); it("runs agents add and computes hasFlags based on explicit options", async () => { diff --git a/src/cli/program/register.agent.ts b/src/cli/program/register.agent.ts index 46eb96a198f..bcac30699ec 100644 --- a/src/cli/program/register.agent.ts +++ b/src/cli/program/register.agent.ts @@ -14,7 +14,6 @@ type AgentsBindModule = typeof import("../../commands/agents.commands.bind.js"); type AgentsDeleteModule = typeof import("../../commands/agents.commands.delete.js"); type AgentsIdentityModule = typeof import("../../commands/agents.commands.identity.js"); type AgentsListModule = typeof import("../../commands/agents.commands.list.js"); -type CliDepsModule = typeof import("../deps.js"); type GlobalStateModule = typeof import("../../global-state.js"); async function loadAgentCliCommand(): Promise { @@ -51,10 +50,6 @@ async function loadAgentsListCommand(): Promise { - return (await import("../deps.js")).createDefaultDeps; -} - async function loadSetVerbose(): Promise { return (await import("../../global-state.js")).setVerbose; } @@ -130,11 +125,8 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.openclaw.ai/cli/age await runCommandWithRuntime(defaultRuntime, async () => { const setVerbose = await loadSetVerbose(); setVerbose(verboseLevel === "on"); - // Build default deps (keeps parity with other commands; future-proofing). - const createDefaultDeps = await loadCreateDefaultDeps(); - const deps = createDefaultDeps(); const agentCliCommand = await loadAgentCliCommand(); - await agentCliCommand(opts, defaultRuntime, deps); + await agentCliCommand(opts, defaultRuntime); }); }); diff --git a/src/commands/agent-via-gateway.test.ts b/src/commands/agent-via-gateway.test.ts index b54af2f27ae..8173c40176f 100644 --- a/src/commands/agent-via-gateway.test.ts +++ b/src/commands/agent-via-gateway.test.ts @@ -20,6 +20,7 @@ const isGatewayTransportError = vi.hoisted(() => }), ); const agentCommand = vi.hoisted(() => vi.fn()); +const agentModuleLoadCount = vi.hoisted(() => vi.fn()); const runtime: RuntimeEnv = { log: vi.fn(), @@ -135,6 +136,17 @@ function createSignalProcess() { }; } +async function waitForAgentCommandCall(expectedCalls = 1) { + for ( + let attempt = 0; + attempt < 50 && agentCommand.mock.calls.length < expectedCalls; + attempt += 1 + ) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + expect(agentCommand).toHaveBeenCalledTimes(expectedCalls); +} + function mockMessages(mock: unknown): string[] { const calls = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls ?? []; return calls.map(([message]) => String(message)); @@ -188,7 +200,10 @@ vi.mock("../gateway/call.js", () => ({ isGatewayTransportError, randomIdempotencyKey: () => "idem-1", })); -vi.mock("./agent.js", () => ({ agentCommand })); +vi.mock("./agent.js", () => { + agentModuleLoadCount(); + return { agentCommand }; +}); let originalForceConsoleToStderr = false; @@ -237,6 +252,7 @@ describe("agentCliCommand", () => { expect(request).not.toHaveProperty("scopes"); expect(request.params).not.toHaveProperty("cleanupBundleMcpOnRunEnd"); expect(agentCommand).not.toHaveBeenCalled(); + expect(agentModuleLoadCount).not.toHaveBeenCalled(); expect(runtime.log).toHaveBeenCalledWith("hello"); }); }); @@ -958,12 +974,11 @@ describe("agentCliCommand", () => { const run = agentCliCommand({ message: "hi", to: "+1555", local: true }, runtime, { process: signals.processLike, }); - await Promise.resolve(); + await waitForAgentCommandCall(); signals.emit("SIGTERM"); await run; expect(callGateway).not.toHaveBeenCalled(); - expect(agentCommand).toHaveBeenCalledTimes(1); expect(runtime.exit).toHaveBeenCalledWith(143); expect(signals.listenerCount("SIGTERM")).toBe(0); expect(signals.listenerCount("SIGINT")).toBe(0); @@ -991,12 +1006,11 @@ describe("agentCliCommand", () => { const run = agentCliCommand({ message: "hi", to: "+1555", local: true }, runtime, { process: signals.processLike, }); - await Promise.resolve(); + await waitForAgentCommandCall(); signals.emit("SIGTERM"); await expect(run).resolves.toBeUndefined(); expect(callGateway).not.toHaveBeenCalled(); - expect(agentCommand).toHaveBeenCalledTimes(1); expect(runtime.exit).toHaveBeenCalledWith(143); }); }); @@ -1015,10 +1029,7 @@ describe("agentCliCommand", () => { const run = agentCliCommand({ message: "hi", to: "+1555" }, runtime, { process: signals.processLike, }); - for (let attempt = 0; attempt < 10 && agentCommand.mock.calls.length === 0; attempt += 1) { - await Promise.resolve(); - } - expect(agentCommand).toHaveBeenCalledTimes(1); + await waitForAgentCommandCall(); signals.emit("SIGTERM"); resolveFallback?.({ payloads: [], diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index 1a5ed32b0fb..238d2159855 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -29,7 +29,6 @@ import { import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; import { normalizeMessageChannel } from "../utils/message-channel.js"; -import { agentCommand } from "./agent.js"; import { buildExplicitSessionIdSessionKey, resolveSessionKeyForRequest } from "./agent/session.js"; type AgentGatewayResult = { @@ -93,6 +92,7 @@ type AgentGatewayCallIdentity = Pick< Parameters[0], "clientName" | "mode" | "scopes" >; +type EmbeddedAgentCommandModule = typeof import("./agent.js"); const AGENT_CLI_SIGNALS: readonly AgentCliSignal[] = ["SIGINT", "SIGTERM"]; const GATEWAY_ABORT_RETRY_DELAYS_MS = [50, 150, 300, 600] as const; @@ -102,6 +102,13 @@ const AGENT_CLI_SIGNAL_EXIT_CODES: Record = { SIGTERM: 143, }; +let embeddedAgentCommandPromise: Promise | undefined; + +function loadEmbeddedAgentCommand(): Promise { + embeddedAgentCommandPromise ??= import("./agent.js").then((module) => module.agentCommand); + return embeddedAgentCommandPromise; +} + function protectJsonStdout(opts: Pick): void { if (opts.json === true) { routeLogsToStderr(); @@ -720,6 +727,7 @@ export async function agentCliCommand( }; try { if (dispatchOpts.local === true) { + const agentCommand = await loadEmbeddedAgentCommand(); const result = await agentCommand(localOpts, runtime, deps); return returnAfterSignalExit(result, signalBridge.getReceivedSignal(), runtime); } @@ -747,6 +755,7 @@ export async function agentCliCommand( runtime.error?.( `EMBEDDED FALLBACK: Gateway agent timed out; running embedded agent with fresh session ${fallbackSession.sessionId}: ${String(err)}`, ); + const agentCommand = await loadEmbeddedAgentCommand(); const result = await agentCommand( { ...localOpts, @@ -773,6 +782,7 @@ export async function agentCliCommand( runtime.error?.( `EMBEDDED FALLBACK: Gateway agent failed; running embedded agent: ${String(err)}`, ); + const agentCommand = await loadEmbeddedAgentCommand(); const result = await agentCommand( { ...localOpts,