mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-15 03:51:22 +02:00
Integrate scoped legacy runtime files and retirement recovery
Keep the adapter ownership acceptance tests in the stacked PR. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
18a1e89a08
2 files changed
+56
-4
No files matched your search
@@ -5,6 +5,9 @@ import * as ssh from "./ssh.js";
|
||||
import * as serverUtils from "./server-utils.js";
|
||||
import {
|
||||
cleanupGitHubOperationLaunchers,
|
||||
adapterExecutionTargetRuntimeRoot,
|
||||
prepareAdapterExecutionTargetRuntime,
|
||||
type AdapterComputerExecutionTarget,
|
||||
prepareGitHubOperationLaunchers,
|
||||
adapterExecutionTargetUsesManagedHome,
|
||||
ensureAdapterExecutionTargetRuntimeCommandInstalled,
|
||||
@@ -437,3 +440,36 @@ describe("GitHub launcher lifecycle", () => {
|
||||
expect(runner.execute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("computer legacy runtime ownership", () => {
|
||||
it.each(["claude", "codex"])("isolates %s assets and callback roots for owners sharing a checkout", async adapterKey => {
|
||||
const execute = vi.fn(async () => ({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: 123, startedAt: "2026-10-10T00:00:00Z" }));
|
||||
const target = (ownerId: string, generation = 1) => ({
|
||||
kind: "remote", transport: "computer", providerKey: "boat", environmentId: "environment",
|
||||
leaseId: ownerId, remoteCwd: "/shared/project", listenerPort: 43127,
|
||||
runner: { execute }, processRunner: { execute },
|
||||
resourceAuthority: { kind: "computer-owner", computerId: "computer", ownerId, generation },
|
||||
fileAuthority: { kind: "remote-persistent", placementId: "shared", root: "/shared/project", agentHome: `/homes/${ownerId}` },
|
||||
launch: vi.fn(), inspectProcess: vi.fn(), retainWarm: vi.fn(), retire: vi.fn(),
|
||||
computerTool: { command: "cua-driver", args: ["mcp"] },
|
||||
} satisfies AdapterComputerExecutionTarget);
|
||||
const prepare = (owner: AdapterComputerExecutionTarget) => prepareAdapterExecutionTargetRuntime({
|
||||
target: owner, runId: owner.leaseId!, adapterKey, workspaceLocalDir: "/unused-local-workspace", syncWorkspace: false,
|
||||
});
|
||||
const first = await prepare(target("agent-one"));
|
||||
const second = await prepare(target("agent-two"));
|
||||
expect(first.workspaceRemoteDir).toBe("/shared/project");
|
||||
expect(second.workspaceRemoteDir).toBe(first.workspaceRemoteDir);
|
||||
expect(first.runtimeRootDir).toBe(adapterExecutionTargetRuntimeRoot(target("agent-one"), adapterKey));
|
||||
expect(second.runtimeRootDir).not.toBe(first.runtimeRootDir);
|
||||
expect(adapterExecutionTargetRuntimeRoot(target("agent-one", 2), adapterKey)).not.toBe(first.runtimeRootDir);
|
||||
expect((await prepare(target("agent-one"))).runtimeRootDir).toBe(first.runtimeRootDir);
|
||||
expect(first.runtimeRootDir).toMatch(new RegExp(`^/shared/project/\\.paperclip-runtime/${adapterKey}/owners/[a-f0-9]{64}$`));
|
||||
});
|
||||
|
||||
it("preserves the existing sandbox runtime root", () => {
|
||||
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace" } as const;
|
||||
expect(adapterExecutionTargetRuntimeRoot(target, "claude")).toBe("/workspace/.paperclip-runtime/claude");
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { promisify } from "node:util";
|
||||
import net from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { randomBytes, randomUUID } from "node:crypto";
|
||||
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
||||
import { githubLauncherSource } from "./github-launcher.js";
|
||||
import type { SshRemoteExecutionSpec } from "./ssh.js";
|
||||
import {
|
||||
@@ -1442,6 +1442,20 @@ export function readAdapterExecutionTarget(input: {
|
||||
);
|
||||
}
|
||||
|
||||
/** Keep mutable legacy assets separate for owners sharing the same checkout. */
|
||||
export function adapterExecutionTargetRuntimeRoot(
|
||||
target: Extract<AdapterExecutionTarget, { kind: "remote" }>,
|
||||
adapterKey: string,
|
||||
baseRoot = path.posix.join(target.remoteCwd, ".paperclip-runtime", adapterKey),
|
||||
): string {
|
||||
if (target.transport !== "computer") return baseRoot;
|
||||
const owner = target.resourceAuthority;
|
||||
const scope = createHash("sha256").update(JSON.stringify([
|
||||
owner.computerId, owner.ownerId, owner.generation,
|
||||
])).digest("hex");
|
||||
return path.posix.join(baseRoot, "owners", scope);
|
||||
}
|
||||
|
||||
export async function prepareAdapterExecutionTargetRuntime(input: {
|
||||
runId: string;
|
||||
target: AdapterExecutionTarget | null | undefined;
|
||||
@@ -1535,7 +1549,9 @@ export async function prepareAdapterExecutionTargetRuntime(input: {
|
||||
adapterKey: input.adapterKey,
|
||||
workspaceLocalDir: input.workspaceLocalDir,
|
||||
workspaceRemoteDir: input.workspaceRemoteDir,
|
||||
runtimeRootDir: input.runtimeRootDir,
|
||||
runtimeRootDir: target.transport === "computer"
|
||||
? adapterExecutionTargetRuntimeRoot(target, input.adapterKey, input.runtimeRootDir)
|
||||
: input.runtimeRootDir,
|
||||
syncWorkspace: input.syncWorkspace,
|
||||
workspaceInboundMode: input.workspaceInboundMode,
|
||||
workspaceDurableSeed: input.workspaceDurableSeed,
|
||||
@@ -2033,7 +2049,7 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input: {
|
||||
? Math.trunc(input.timeoutSec * 1000)
|
||||
: target.timeoutMs ?? undefined;
|
||||
const bridgeRuntimeDir = path.posix.join(
|
||||
input.runtimeRootDir?.trim() || path.posix.join(target.remoteCwd, ".paperclip-runtime", input.adapterKey),
|
||||
input.runtimeRootDir?.trim() || adapterExecutionTargetRuntimeRoot(target, input.adapterKey),
|
||||
"process-sessions",
|
||||
);
|
||||
const sessionId = randomUUID();
|
||||
@@ -4402,7 +4418,7 @@ export async function startAdapterExecutionTargetPaperclipBridge(input: {
|
||||
const runtimeRootDir =
|
||||
input.runtimeRootDir?.trim().length
|
||||
? input.runtimeRootDir.trim()
|
||||
: path.posix.join(target.remoteCwd, ".paperclip-runtime", input.adapterKey);
|
||||
: adapterExecutionTargetRuntimeRoot(target, input.adapterKey);
|
||||
const bridgeRuntimeDir = path.posix.join(runtimeRootDir, "paperclip-bridge");
|
||||
const queueDir = path.posix.join(bridgeRuntimeDir, "queue");
|
||||
const assetRemoteDir = path.posix.join(bridgeRuntimeDir, "server");
|
||||
|
||||
Reference in new issue
Block a user