Download src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 4.74 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts
-
curl -L -o subagent-spawn-cleanup.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/subagents/spawn/subagent-spawn-cleanup.test.ts
4.74 kB
| import { describe, expect, it, vi } from "vitest"; | |
| import { withPluginRuntimeGatewayRequestScope } from "../../../plugins/runtime/gateway-request-scope.js"; | |
| import { | |
| cleanupProvisionalSession, | |
| terminateAcceptedCollectorRun, | |
| } from "./subagent-spawn-cleanup.js"; | |
| function sessionChangedError(): Error { | |
| return Object.assign(new Error("session changed"), { | |
| name: "GatewayClientRequestError", | |
| gatewayCode: "INVALID_REQUEST", | |
| details: { reason: "session-changed" }, | |
| }); | |
| } | |
| describe("subagent spawn cleanup identity", () => { | |
| it("requires both frozen session identities before deletion", async () => { | |
| const callGateway = vi.fn(); | |
| await expect( | |
| cleanupProvisionalSession("agent:main:subagent:child", { | |
| expectedSessionId: "session-id", | |
| callGateway, | |
| }), | |
| ).resolves.toBe(false); | |
| expect(callGateway).not.toHaveBeenCalled(); | |
| }); | |
| it("accepts chat.abort only when it confirms the exact run", async () => { | |
| const callGateway = vi | |
| .fn() | |
| .mockResolvedValueOnce({ ok: true, aborted: false, runIds: [] }) | |
| .mockResolvedValueOnce({ deleted: true }); | |
| await terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| expectedSessionId: "session-id", | |
| expectedLifecycleRevision: "session-revision", | |
| callGateway, | |
| }); | |
| expect(callGateway).toHaveBeenNthCalledWith(2, { | |
| method: "sessions.delete", | |
| params: { | |
| key: "agent:main:subagent:child", | |
| emitLifecycleHooks: false, | |
| deleteTranscript: true, | |
| expectedSessionId: "session-id", | |
| expectedLifecycleRevision: "session-revision", | |
| }, | |
| timeoutMs: 60_000, | |
| }); | |
| }); | |
| it("does not delete after chat.abort confirms the matching run", async () => { | |
| const callGateway = vi.fn(async () => ({ | |
| ok: true, | |
| aborted: true, | |
| runIds: ["gateway-run"], | |
| })); | |
| await terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| expectedSessionId: "session-id", | |
| expectedLifecycleRevision: "session-revision", | |
| callGateway, | |
| }); | |
| expect(callGateway).toHaveBeenCalledOnce(); | |
| }); | |
| it("stops without deleting a durable session when the accepted run already ended", async () => { | |
| const callGateway = vi.fn(async () => ({ | |
| ok: true, | |
| aborted: false, | |
| runIds: [], | |
| })); | |
| await terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| sessionCleanup: "preserve", | |
| callGateway, | |
| }); | |
| expect(callGateway).toHaveBeenCalledOnce(); | |
| }); | |
| it("retries abort without deleting a durable session after a gateway error", async () => { | |
| const callGateway = vi | |
| .fn() | |
| .mockRejectedValueOnce(new Error("gateway unavailable")) | |
| .mockResolvedValueOnce({ ok: true, aborted: false, runIds: [] }); | |
| await terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| sessionCleanup: "preserve", | |
| callGateway, | |
| }); | |
| expect(callGateway).toHaveBeenCalledTimes(2); | |
| expect(callGateway).toHaveBeenNthCalledWith(2, { | |
| method: "chat.abort", | |
| params: { | |
| sessionKey: "agent:main:subagent:child", | |
| runId: "gateway-run", | |
| }, | |
| timeoutMs: 60_000, | |
| }); | |
| }); | |
| it("stops accepted-run cleanup when its Gateway request owner is retired", async () => { | |
| const callGateway = vi | |
| .fn() | |
| .mockRejectedValueOnce(new Error("Gateway request owner is retired")) | |
| .mockResolvedValue({ aborted: false, runIds: [] }); | |
| await withPluginRuntimeGatewayRequestScope( | |
| { resolveGatewayContext: () => undefined, isWebchatConnect: () => false }, | |
| () => | |
| terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| sessionCleanup: "preserve", | |
| callGateway, | |
| }), | |
| ); | |
| expect(callGateway).toHaveBeenCalledOnce(); | |
| }); | |
| it("stops cleanup when guarded deletion observes a successor lifecycle", async () => { | |
| const callGateway = vi | |
| .fn() | |
| .mockResolvedValueOnce({ ok: true, aborted: true, runIds: ["different-run"] }) | |
| .mockRejectedValueOnce(sessionChangedError()); | |
| await expect( | |
| terminateAcceptedCollectorRun({ | |
| childSessionKey: "agent:main:subagent:child", | |
| gatewayRunId: "gateway-run", | |
| expectedSessionId: "session-id", | |
| expectedLifecycleRevision: "session-revision", | |
| callGateway, | |
| }), | |
| ).resolves.toBeUndefined(); | |
| expect(callGateway).toHaveBeenCalledTimes(2); | |
| }); | |
| }); | |