mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-13 23:39:21 +02:00
Isolate persistent computer runtime assets by agent session
Root runner binaries, harness installs, provider packs, and session state under the trusted persistent agent home and normalized session. Keep the shared project working directory unchanged and preserve existing sandbox paths. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
4d2113e188
commit
5a16ef77c0
2 files changed
+44
-16
No files matched your search
@@ -373,6 +373,7 @@ import {
|
||||
syncRemoteRunnerDirectoryOut,
|
||||
verifyNativeHarnessBackup,
|
||||
shouldRestoreNativeHarnessBackupIntoSandbox,
|
||||
remoteRunnerStorageRoots,
|
||||
} from "./native-session-executor.js";
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -710,6 +711,28 @@ describe("remote runner launch fingerprint compatibility", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("persistent computer runtime storage", () => {
|
||||
it("isolates agents and sessions sharing one project while keeping warm roots stable", () => {
|
||||
const target = (agent: string) => ({ kind: "remote", transport: "computer", remoteCwd: "/shared/project",
|
||||
fileAuthority: { kind: "remote-persistent", root: "/shared/project", placementId: "shared", agentHome: `/home/user/paperclip/company/agents/${agent}` },
|
||||
} as never);
|
||||
const a = remoteRunnerStorageRoots(target("a"), "session-1");
|
||||
const b = remoteRunnerStorageRoots(target("b"), "session-1");
|
||||
expect(a.runtimeRoot).not.toBe(b.runtimeRoot);
|
||||
expect(a.runtimeRoot).toContain("/agents/a/.paperclip-runtime/");
|
||||
expect(a.sessionRoot).toBe(a.runtimeRoot);
|
||||
expect(remoteRunnerStorageRoots(target("a"), "session-1")).toEqual(a);
|
||||
expect(remoteRunnerStorageRoots(target("a"), "session-2").runtimeRoot).not.toBe(a.runtimeRoot);
|
||||
expect((target("a") as { remoteCwd: string }).remoteCwd).toBe("/shared/project");
|
||||
expect(() => remoteRunnerStorageRoots({ kind: "remote", transport: "computer", remoteCwd: "/shared/project" } as never, "session")).toThrow("runner_remote_storage_home_missing");
|
||||
});
|
||||
it("preserves existing sandbox runtime and session paths", () => {
|
||||
const roots = remoteRunnerStorageRoots({ kind: "remote", transport: "sandbox", remoteCwd: "/workspace" } as never, "session-1");
|
||||
expect(roots.runtimeRoot).toBe("/workspace/.paperclip-runtime/paperclip-runner");
|
||||
expect(roots.sessionRoot).toBe(`${roots.runtimeRoot}/sessions/${createHash("sha256").update("session-1").digest("hex")}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("remote runner process supervision", () => {
|
||||
it("admits a valid computer identity when the supervised RPC takes longer than two seconds", async () => {
|
||||
vi.useFakeTimers();
|
||||
@@ -13439,6 +13462,7 @@ describe("runnerd provider runtime wiring", () => {
|
||||
runnerIngressAuthorized: true, runnerRemoteCodexNpmSpec: override,
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "computer", remoteCwd: "/workspace",
|
||||
fileAuthority: { kind: "remote-persistent", root: "/workspace", placementId: "workspace", agentHome: "/home/user/agent" },
|
||||
environmentId: "environment", leaseId: "lease", providerKey: "boat",
|
||||
effectiveCapabilities: { runnerWebSocketIngress: true },
|
||||
runner: { execute: remoteExecute }, processRunner: { execute: remoteExecute },
|
||||
@@ -13452,7 +13476,7 @@ describe("runnerd provider runtime wiring", () => {
|
||||
await expect(transport.controlPlaneRegistration({})).rejects.toThrow("reached-private-codex-verification");
|
||||
expect(remoteExecute).toHaveBeenCalledWith(expect.objectContaining({
|
||||
command: "npm",
|
||||
args: ["install", "--prefix", "/workspace/.paperclip-runtime/paperclip-runner/harnesses/codex", "--no-audit", "--no-fund", expected],
|
||||
args: ["install", "--prefix", `/home/user/agent/.paperclip-runtime/paperclip-runner/sessions/${createHash("sha256").update(execution.session.normalizedSessionId ?? `session-${execution.binding.runId}`).digest("hex")}/harnesses/codex`, "--no-audit", "--no-fund", expected],
|
||||
}));
|
||||
expect(remoteExecute.mock.calls.filter(([command]) => command.command === "npm")).toHaveLength(1);
|
||||
expect(remoteExecute.mock.calls.some(([command]) => command.args?.includes("--global"))).toBe(false);
|
||||
|
||||
@@ -1697,6 +1697,18 @@ function nativeSessionKey(execution: NativeExecutionInput): string {
|
||||
);
|
||||
}
|
||||
|
||||
/** Persistent computer artifacts belong to an agent session, independent of a shared repo cwd. */
|
||||
export function remoteRunnerStorageRoots(
|
||||
target: Extract<AdapterExecutionTarget, { kind: "remote" }>,
|
||||
sessionKey: string,
|
||||
) {
|
||||
const home = target.transport === "computer" ? target.fileAuthority?.agentHome : target.remoteCwd;
|
||||
if (!home || !posix.isAbsolute(home)) throw new Error("runner_remote_storage_home_missing");
|
||||
const base = posix.join(home, ".paperclip-runtime", "paperclip-runner");
|
||||
const sessionRoot = posix.join(base, "sessions", createHash("sha256").update(sessionKey).digest("hex"));
|
||||
return { runtimeRoot: target.transport === "computer" ? sessionRoot : base, sessionRoot };
|
||||
}
|
||||
|
||||
export function nativeSessionWorkspaceScope(execution: { binding: Pick<NativeExecutionInput["binding"], "runId" | "executionWorkspaceId">; workspace: NativeExecutionInput["workspace"] }) {
|
||||
if ("access" in execution.workspace) return { kind: "none" as const };
|
||||
// Projectless local runs use the heartbeat run id as a durable placeholder
|
||||
@@ -11417,10 +11429,10 @@ async function prepareRemoteDotRunner({ input, target, runner, root, identity }:
|
||||
identity: NonNullable<NonNullable<Parameters<typeof createNativeSessionBackend>[1]>["dotRunnerOptions"]>["identity"];
|
||||
}) {
|
||||
const runnerBinary = input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary();
|
||||
const remoteRoot = posix.join(target.remoteCwd, ".paperclip-runtime", "paperclip-runner");
|
||||
const storage = remoteRunnerStorageRoots(target, identity.normalizedSessionId);
|
||||
const remoteRoot = storage.runtimeRoot;
|
||||
const remoteBinary = posix.join(remoteRoot, "bin", "paperclip-runnerd");
|
||||
const runnerStateDirectory = posix.join(remoteRoot, "sessions",
|
||||
createHash("sha256").update(identity.normalizedSessionId).digest("hex"), "runner");
|
||||
const runnerStateDirectory = posix.join(storage.sessionRoot, "runner");
|
||||
const requiredMode = resolveRemoteRunnerTransportMode({ target, runnerIngressAuthorized: input.runnerIngressAuthorized === true });
|
||||
const artifact = readRunnerdArtifactBinding(runnerBinary);
|
||||
const prepare = async () => {
|
||||
@@ -11687,13 +11699,10 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
return { ...backend, descriptor: () => backend.descriptor(), openSession: input => backend.openSession(input),
|
||||
recoverSession: (snapshot, options) => backend.recoverSession!(snapshot, options), bindManagedSession: session => session };
|
||||
}
|
||||
const remoteRuntimeRoot = remoteTarget
|
||||
? posix.join(
|
||||
remoteTarget.remoteCwd,
|
||||
".paperclip-runtime",
|
||||
"paperclip-runner",
|
||||
)
|
||||
const remoteStorage = remoteTarget
|
||||
? remoteRunnerStorageRoots(remoteTarget, nativeSessionKey(input.execution))
|
||||
: null;
|
||||
const remoteRuntimeRoot = remoteStorage?.runtimeRoot ?? null;
|
||||
const requiresRemoteProviderPack =
|
||||
remoteTarget !== null &&
|
||||
(input.execution.provider.kind === "opencode" ||
|
||||
@@ -11764,12 +11773,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
)
|
||||
: posix.join(remoteRuntimeRoot, "bin", "codex")
|
||||
: null;
|
||||
const remoteSessionDigest = createHash("sha256")
|
||||
.update(nativeSessionKey(input.execution))
|
||||
.digest("hex");
|
||||
const remoteSessionRoot = remoteRuntimeRoot
|
||||
? posix.join(remoteRuntimeRoot, "sessions", remoteSessionDigest)
|
||||
: null;
|
||||
const remoteSessionRoot = remoteStorage?.sessionRoot ?? null;
|
||||
const remoteStateDirectory = remoteSessionRoot
|
||||
? posix.join(remoteSessionRoot, "runner")
|
||||
: undefined;
|
||||
|
||||
Reference in new issue
Block a user