Verify agent harnesses through bounded computer probe owners

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-10 08:31:36 -05:00
1 parent ebb60670c1
commit d29e2a8bfe
8 files changed
+70 -12

No files matched your search

@@ -12,6 +12,7 @@ import {
ensurePathInEnv,
} from "@paperclipai/adapter-utils/server-utils";
import {
adapterExecutionTargetIsCommandBacked,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetDirectory,
runAdapterExecutionTargetProcess,
@@ -90,7 +91,7 @@ export async function testEnvironment(
const command = asString(config.command, "claude");
const target = ctx.executionTarget ?? null;
const targetIsRemote = target?.kind === "remote";
const targetIsSandbox = target?.kind === "remote" && target.transport === "sandbox";
const targetIsSandbox = adapterExecutionTargetIsCommandBacked(target);
const cwd = resolveAdapterExecutionTargetCwd(target, asString(config.cwd, ""), process.cwd());
const runId = `claude-envtest-${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -9,6 +9,7 @@ import {
ensurePathInEnv,
} from "@paperclipai/adapter-utils/server-utils";
import {
adapterExecutionTargetIsCommandBacked,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetDirectory,
maybeRunSandboxInstallCommand,
@@ -277,7 +278,7 @@ export async function testEnvironment(
const command = asString(config.command, "codex");
const target = ctx.executionTarget ?? null;
const targetIsRemote = target?.kind === "remote";
const targetIsSandbox = target?.kind === "remote" && target.transport === "sandbox";
const targetIsSandbox = adapterExecutionTargetIsCommandBacked(target);
const cwd = resolveAdapterExecutionTargetCwd(target, asString(config.cwd, ""), process.cwd());
const targetLabel = targetIsRemote
? ctx.environmentName ?? describeAdapterExecutionTarget(target)
@@ -313,6 +313,25 @@ describe("server adapter registry", () => {
});
});
it("verifies the Claude harness on a computer after checking its platform", async () => {
const calls: string[] = [];
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1", adapterType: "paperclip_runner",
config: { provider: "acpx", acpxAgent: "claude", model: "claude-sonnet-4-6", env: { ANTHROPIC_API_KEY: "test-key" } },
executionTarget: { kind: "remote", transport: "computer", remoteCwd: "/workspace", resourceAuthority: { computerId: "computer" },
runner: { execute: async (input: { command: string; args?: string[] }) => {
calls.push(input.command);
const stdout = input.command === "claude" ? JSON.stringify({ type: "result", result: "hello", usage: { input_tokens: 1, output_tokens: 1 } })
: input.args?.some(arg => arg.includes("uname -s")) ? "Linux\nx86_64\n" : "";
return { exitCode: 0, timedOut: false, stdout, stderr: "", signal: null, pid: null, startedAt: new Date().toISOString() };
} },
} as never,
});
expect(calls).toContain("claude");
expect(result.checks).toContainEqual(expect.objectContaining({ code: "claude_hello_probe_passed", level: "info" }));
expect(result.checks.some(check => check.level === "error")).toBe(false);
});
it.each([true, false])("checks actual local ACPX installation readiness (%s)", async (ready) => {
const probe = vi.mocked(probeAcpxClaudeInstallation);
if (ready) probe.mockResolvedValueOnce(undefined);
@@ -298,7 +298,7 @@ describe("claude_local environment diagnostics", () => {
expect(result.checks.some((check) => check.code === "claude_cwd_invalid")).toBe(false);
});
it("uses full permission bypass for sandbox hello probes", async () => {
it.each(["sandbox", "computer"] as const)("uses full permission bypass for %s hello probes", async (transport) => {
const executeCalls: Array<{ command: string; args?: string[]; env?: Record<string, string> }> = [];
const result = await testEnvironment({
@@ -310,11 +310,12 @@ describe("claude_local environment diagnostics", () => {
},
executionTarget: {
kind: "remote",
transport: "sandbox",
transport,
resourceAuthority: { computerId: "probe-computer" },
providerKey: "cloudflare",
remoteCwd: "/workspace/paperclip",
runner: {
execute: async (input) => {
execute: async (input: { command: string; args?: string[]; env?: Record<string, string> }) => {
executeCalls.push({ command: input.command, args: input.args, env: input.env });
if (input.command === "claude") {
return {
@@ -345,7 +346,7 @@ describe("claude_local environment diagnostics", () => {
};
},
},
},
} as never,
environmentName: "QA Cloudflare",
});
+6
View File
@@ -432,6 +432,12 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
if (!((os === "Linux" && arch === "x86_64") || (os === "Darwin" && (arch === "arm64" || (profile.acpxAgent !== "grok" && arch === "x86_64"))))) {
throw new Error(`ACPX ${profile.acpxAgent} requires a qualified Linux x64 or macOS architecture.`);
}
if (target.transport === "computer" && profile.acpxAgent === "claude") {
const hello = await claudeTestEnvironment({ ...context, adapterType: "claude_local",
config: { ...context.config, engine: "cli", model: profile.model,
dangerouslySkipPermissions: context.config.acpxPermissionMode === "approve-all" } });
return { ...hello, adapterType: "paperclip_runner" };
}
return {
adapterType: "paperclip_runner", status: "warn" as const, testedAt: new Date().toISOString(),
checks: [{ code: "acpx_remote_runtime_unverified", level: "warn" as const,
@@ -361,7 +361,11 @@ export function agentEnvironmentTestService(db: Db, pluginWorkerManager?: Plugin
executionTarget: target,
environmentName: environment.name,
fallbackChecks: [],
sandboxIdentityCheck: buildSandboxIdentityCheck({
sandboxIdentityCheck: environment.driver === "computer" ? {
code: "computer_test_identity", level: "info",
message: `Environment test ran on "${environment.name}" with a bounded computer owner.`,
detail: `paperclipLeaseId=${leaseRecord.lease.id}; ownerId=${leaseRecord.lease.providerLeaseId}`,
} : buildSandboxIdentityCheck({
environmentName: environment.name,
lease: leaseRecord.lease,
}),
@@ -433,7 +437,7 @@ export function agentEnvironmentTestService(db: Db, pluginWorkerManager?: Plugin
// The environment drivers the adapter Test route accepts. A local, SSH, or
// sandbox environment can host a probe; a plugin environment cannot.
const ADAPTER_TEST_ALLOWED_ENVIRONMENT_DRIVERS = ["local", "ssh", "sandbox"];
const ADAPTER_TEST_ALLOWED_ENVIRONMENT_DRIVERS = ["local", "ssh", "sandbox", "computer"];
// The fail-closed tenant-binding guard for the adapter Test route. A caller
// may name any instance environment by id, so the route must reject an
@@ -0,0 +1,24 @@
import { describe, expect, it, vi } from "vitest";
import { createComputerEnvironmentDriver } from "./computer-environment-driver.js";
const state = vi.hoisted(() => {
const binding = { owner: { computerId: "computer", ownerId: "owner", generation: 1 },
listenerPort: 43127, remoteCwd: "/remote/workspace", placementId: "placement", agentHome: "/remote/home" };
return { admit: vi.fn(async () => binding), admitProbe: vi.fn(async () => binding),
retire: vi.fn(async () => ({ retired: true })), acquireLease: vi.fn(async (input: unknown) => input) };
});
vi.mock("../modules/computers/index.js", () => ({ computerService: () => state }));
vi.mock("./environments.js", () => ({ environmentService: () => state }));
vi.mock("./instance-settings.js", () => ({ instanceSettingsService: () => ({ getExperimental: async () => ({ enableBoatEnvironments: true }) }) }));
describe("computer environment probe ownership", () => {
it("admits a bounded probe without fabricating a heartbeat run", async () => {
const db = { update: () => ({ set: () => ({ where: async () => undefined }) }) };
const driver = createComputerEnvironmentDriver(db as never);
await driver.acquireRunLease({ companyId: "company", agentId: "agent", heartbeatRunId: null,
environment: { id: "environment", config: {} }, adapterType: "paperclip_runner" } as never);
expect(state.admit).not.toHaveBeenCalled();
expect(state.admitProbe).toHaveBeenCalledWith(expect.objectContaining({ companyId: "company", environmentId: "environment", agentId: "agent", probeId: expect.any(String) }));
expect(state.acquireLease).toHaveBeenCalledWith(expect.objectContaining({ heartbeatRunId: null, providerLeaseId: "owner", metadata: expect.objectContaining({ driver: "computer", computerOwner: { computerId: "computer", ownerId: "owner", generation: 1 } }) }));
});
});
@@ -3,7 +3,7 @@ import { environmentLeases } from "@paperclipai/db";
import { remoteTerminationReceipt } from "./remote-execution-termination.js";
import { instanceSettingsService } from "./instance-settings.js";
import { resolvePaperclipRunnerIdleTimeoutMs } from "@paperclipai/adapter-utils";
import { createHash } from "node:crypto";
import { createHash, randomUUID } from "node:crypto";
import type { Db } from "@paperclipai/db";
import type { EnvironmentLease } from "@paperclipai/shared";
import type { AdapterComputerExecutionTarget, EffectiveExecutionCapabilities } from "@paperclipai/adapter-utils/execution-target";
@@ -45,11 +45,13 @@ export function createComputerEnvironmentDriver(db: Db): EnvironmentRuntimeDrive
driver: "computer",
async acquireRunLease(input) {
if (!(await instanceSettingsService(db).getExperimental()).enableBoatEnvironments) throw new Error("boat_environments_disabled");
if (!input.agentId || !input.heartbeatRunId) throw new Error("computer_agent_run_required");
if (!input.agentId) throw new Error("computer_agent_required");
const idleTimeoutMs = resolvePaperclipRunnerIdleTimeoutMs(input.environment.config.runnerIdleTimeoutMs);
const binding = await computers.admit({ companyId: input.companyId, environmentId: input.environment.id,
const binding = input.heartbeatRunId ? await computers.admit({ companyId: input.companyId, environmentId: input.environment.id,
agentId: input.agentId, runId: input.heartbeatRunId, idleTimeoutMs,
sessionKey: createHash("sha256").update(JSON.stringify([input.agentId, input.executionWorkspaceId ?? input.issueId, input.adapterType])).digest("hex") });
sessionKey: createHash("sha256").update(JSON.stringify([input.agentId, input.executionWorkspaceId ?? input.issueId, input.adapterType])).digest("hex") })
: await computers.admitProbe({ companyId: input.companyId, environmentId: input.environment.id,
agentId: input.agentId, probeId: randomUUID(), idleTimeoutMs });
try {
// The durable owner advanced generation; prior retained bookkeeping no
// longer owns the process and must not be reaped as an allocation.