From 7634c65d227fb5f738c9c55a78ad3927b31c680f Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 11 Oct 2026 07:39:31 -0500 Subject: [PATCH] Complete computer lifecycle in warm identity fixtures Give the shared target identity regressions a typed computer target and validate retention and retirement when integrated with Boat lifecycle handling. Co-Authored-By: Paperclip --- .../native-session-executor.test.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index b8f7ad47577..644b42a2482 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -1,4 +1,5 @@ import type { CommandManagedRuntimeRunner } from "@paperclipai/adapter-utils/command-managed-runtime"; +import type { AdapterComputerExecutionTarget } from "@paperclipai/adapter-utils/execution-target"; import { createBoatTransportAdmission } from "../../modules/computers/adapters/boat.js"; import { closeWarmNativeSession, compareAndSealRetiredComputerCheckpoint, retiredComputerCheckpointIsSettled, retiredComputerCheckpointUnsettledReason, retireUnlaunchedComputerAttempt, nativeSessionBootstrapTimeoutMs } from "./native-session-executor.js"; import { prepareHeartbeatGitHubLaunchers } from "../heartbeat-github-launchers.js"; @@ -8034,13 +8035,25 @@ describe("native warm session supervision", () => { session: { ...execution.session, normalizedSessionId: name, lifecyclePolicy: { mode: "warm", idleTimeoutMs: 60_000 } } } as NativeExecutionInputV1; const close = vi.fn(async () => undefined); const collectStopped = vi.fn(async () => undefined); - const target = { kind: "remote", transport: "computer", environmentId: name, remoteCwd: `/tmp/${name}`, - resourceAuthority: { kind: "computer-owner", computerId: name, ownerId: name, generation: 1 } }; + const retainWarm = vi.fn(async () => undefined); + const retire = vi.fn(async options => { + await options?.beforeStop?.(); + return true; + }); + const unexpectedProcessOperation = async (): Promise => { throw new Error("Identity fixture must not operate on a process"); }; + const target: AdapterComputerExecutionTarget = { kind: "remote", transport: "computer", environmentId: name, + remoteCwd: `/tmp/${name}`, listenerPort: 45001, + resourceAuthority: { kind: "computer-owner", computerId: name, ownerId: name, generation: 1 }, + fileAuthority: { kind: "remote-persistent", placementId: name, root: `/tmp/${name}`, agentHome: `/tmp/${name}/home` }, + processRunner: { execute: unexpectedProcessOperation }, launch: unexpectedProcessOperation, + inspectProcess: unexpectedProcessOperation, retainWarm, retire, + computerTool: { command: "cua-driver", args: ["mcp"] } }; state.execute.mockReset().mockImplementationOnce(async options => { await options.onSession?.({ close }); return result; }); await executePaperclipNativeSession({ db: leaseDb(current), execution: current, runnerInstanceId: name, - runnerExecutionTarget: target as never, + runnerExecutionTarget: target, instructionWorkingCopy: { runId: current.binding.runId, root: `/tmp/${name}/home`, collectStopped, checkpointWarm: async () => true, hasChanges: async () => false } }); + expect(retainWarm).toHaveBeenCalledWith(60_000); const canReuse = vi.fn(async () => true); const nextAuthority = { ...target.resourceAuthority, ...(change === "generation" ? { generation: 2 } : {}), @@ -8049,16 +8062,19 @@ describe("native warm session supervision", () => { }; const reservation = await reserveWarmNativeInstructionDirectory({ companyId: current.binding.companyId, agentId: current.binding.agentId, previousRunId: current.binding.runId, runId: `${name}-two`, canReuse, - target: { ...target, resourceAuthority: nextAuthority } as never }); + target: { ...target, resourceAuthority: nextAuthority } }); if (change === "same" || change === "generation") { expect(reservation?.reuseRunId).toBe(current.binding.runId); expect(canReuse).toHaveBeenCalledOnce(); expect(close).not.toHaveBeenCalled(); + expect(retire).not.toHaveBeenCalled(); await reservation!.release(); } else { expect(reservation).toBeNull(); expect(canReuse).not.toHaveBeenCalled(); + expect(retire).toHaveBeenCalledOnce(); expect(close).toHaveBeenCalledOnce(); + expect(retire.mock.invocationCallOrder[0]).toBeLessThan(close.mock.invocationCallOrder[0]!); expect(collectStopped).toHaveBeenCalledOnce(); } });