feat(runner): prepare held Pi 1 production admission

Replay the inactive Pi-only admission patch on the frozen Pi 1.0.0 source, preserving profile 11, its exact digest and all native closure inputs. Cursor and Copilot remain gated. This local preparation requires complete qualification and rebuilt normal-mode acceptance before activation.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-01 15:45:57 -05:00
1 parent 5eba61ece9
commit 89284e2905
27 files changed
+191 -52

No files matched your search

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
PAPERCLIP_RUNNER_DEFAULT_MODELS,
PAPERCLIP_RUNNER_ACPX_PROFILES,
paperclipRunnerTransitionConfig,
isPaperclipRunnerProvider,
resolvePaperclipRunnerModel,
@@ -10,6 +11,13 @@ import {
} from "./paperclip-runner-permissions.js";
describe("Paperclip Runner permission defaults", () => {
it("admits Pi while keeping sibling ACP profiles pending", () => {
expect(PAPERCLIP_RUNNER_ACPX_PROFILES.find(profile => profile.value === "pi"))
.toMatchObject({ qualified: true, credentialEnvironment: ["OPENROUTER_API_KEY"] });
for (const agent of ["cursor", "copilot"]) {
expect(PAPERCLIP_RUNNER_ACPX_PROFILES.find(profile => profile.value === agent)?.qualified).toBe(false);
}
});
it("defaults Codex to the only qualified non-interactive mode", () => {
expect(resolvePaperclipRunnerPermissionMode("codex", undefined)).toBe(
"never",
@@ -245,5 +245,5 @@ export const PAPERCLIP_RUNNER_ACPX_PROFILES = Object.freeze([
{ value: "claude", label: "Claude", qualified: true, credentialEnvironment: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"] },
{ value: "cursor", label: "Cursor", qualified: false, credentialEnvironment: ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"] },
{ value: "copilot", label: "GitHub Copilot", qualified: false, credentialEnvironment: ["COPILOT_GITHUB_TOKEN"] },
{ value: "pi", label: "Pi", qualified: false, credentialEnvironment: ["OPENROUTER_API_KEY"] },
{ value: "pi", label: "Pi", qualified: true, credentialEnvironment: ["OPENROUTER_API_KEY"] },
] as const);
@@ -230,9 +230,16 @@ describe("buildPaperclipRunnerConfig", () => {
expect(() => buildPaperclipRunnerConfig(makeValues({
model: "", adapterSchemaValues: { provider: "acpx", acpxAgent },
}))).toThrow("requires an explicit provider model");
const model = acpxAgent === "pi" ? "openrouter/deepseek/deepseek-v4-flash-0731" : "exact-provider-model";
expect(buildPaperclipRunnerConfig(makeValues({
model: "exact-provider-model", adapterSchemaValues: { provider: "acpx", acpxAgent },
}))).toMatchObject({ provider: "acpx", acpxAgent, model: "exact-provider-model" });
model, adapterSchemaValues: { provider: "acpx", acpxAgent },
}))).toMatchObject({ provider: "acpx", acpxAgent, model });
});
it("rejects a different model when Pi is selected", () => {
expect(() => buildPaperclipRunnerConfig(makeValues({
model: "claude-sonnet-5", adapterSchemaValues: { provider: "acpx", acpxAgent: "pi" },
}))).toThrow("Pi requires exact model");
});
it.each([undefined, "agent", "plan", "ask"])("preserves Cursor session mode %s for server admission", acpxSessionMode => {
@@ -109,6 +109,10 @@ export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record<string
if (provider === "acpx" && ["cursor", "copilot", "pi"].includes(acpxAgent) && !configuredModel && !schemaModel) {
throw new Error(`${acpxAgent} requires an explicit provider model`);
}
if (provider === "acpx" && acpxAgent === "pi"
&& (configuredModel || schemaModel) !== "openrouter/deepseek/deepseek-v4-flash-0731") {
throw new Error("Pi requires exact model openrouter/deepseek/deepseek-v4-flash-0731");
}
const managedProfileId = typeof schemaValues.managedProfileId === "string"
? schemaValues.managedProfileId.trim()
: "";
@@ -18,7 +18,7 @@ import { tmpdir } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { parseProviderPackArguments, materializeCandidateProviderPack } from "./candidate-provider-pack.mjs";
import { parseProviderPackArguments, materializeCandidateProviderPack, providerPackSelections } from "./candidate-provider-pack.mjs";
import { buildNodeStartupTimeout } from "./build-node-startup-timeout.mjs";
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -173,14 +173,14 @@ try {
}
const candidateProviders = {};
for (const provider of candidates) {
for (const { provider, qualification } of providerPackSelections(candidates)) {
const assetPath = `provider-assets/${provider}/${process.platform}-${process.arch}`;
const metadata = await materializeCandidateProviderPack({ provider, outputRoot: join(temporaryRoot, assetPath) });
if (typeof metadata?.version !== "string" || !metadata.version || metadata.version.length > 120
|| !/^sha256:[a-f0-9]{64}$/.test(metadata.profileDigest)
|| !/^sha256:[a-f0-9]{64}$/.test(metadata.closureDigest)) throw new Error("Candidate builder omitted its pinned identity");
candidateProviders[provider] = { version: metadata.version, profileDigest: metadata.profileDigest,
closureDigest: metadata.closureDigest, qualification: "pending", path: assetPath,
closureDigest: metadata.closureDigest, qualification, path: assetPath,
sha256: sha256Tree(join(temporaryRoot, assetPath)) };
}
@@ -334,7 +334,7 @@ try {
codex:
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
},
...(candidates.length ? { candidateProviders } : {}),
candidateProviders,
artifacts: {
grokLauncher: {
path: "dist/providers/grok/launcher.cjs",
@@ -2,6 +2,17 @@ import { materializePiDistribution } from "./materialize-pi-distribution.mjs";
import { materializePinnedCursorDistribution } from "./materialize-cursor-distribution.mjs";
const CANDIDATES = new Set(["cursor", "copilot", "pi"]);
/** Pi ships by default; the diagnostic option remains compatible with old callers. */
export function providerPackSelections(candidates) {
if (candidates.some(provider => !CANDIDATES.has(provider)) || new Set(candidates).size !== candidates.length) {
throw new Error("Unknown or duplicate candidate provider");
}
return [
{ provider: "pi", qualification: "qualified" },
...candidates.filter(provider => provider !== "pi").map(provider => ({ provider, qualification: "pending" })),
];
}
export function parseProviderPackArguments(args) {
let output;
const candidates = [];
@@ -1,10 +1,18 @@
import test from "node:test";
import assert from "node:assert/strict";
import { parseProviderPackArguments, materializeCandidateProviderPack } from "./candidate-provider-pack.mjs";
import { parseProviderPackArguments, materializeCandidateProviderPack, providerPackSelections } from "./candidate-provider-pack.mjs";
test("candidate builds are explicit and leave qualified-only builds unchanged", () => {
test("candidate options stay explicit and Pi is the only default qualified native asset", () => {
assert.deepEqual(parseProviderPackArguments(["--", "/pack"]), { output: "/pack", candidates: [] });
assert.deepEqual(parseProviderPackArguments(["/pack", "--candidate-providers=pi,cursor"]), { output: "/pack", candidates: ["pi", "cursor"] });
assert.deepEqual(providerPackSelections([]), [{ provider: "pi", qualification: "qualified" }]);
assert.deepEqual(providerPackSelections(["pi", "cursor", "copilot"]), [
{ provider: "pi", qualification: "qualified" },
{ provider: "cursor", qualification: "pending" },
{ provider: "copilot", qualification: "pending" },
]);
assert.throws(() => providerPackSelections(["other"]), /Unknown/);
assert.throws(() => providerPackSelections(["pi", "pi"]), /duplicate/);
});
test("candidate builder cannot admit unknown providers, options or duplicate assets", async () => {
for (const args of [["--candidate-providers=cursor,cursor"], ["--candidate-providers=other"], ["--executable=/tmp/x"], ["/one", "/two"]]) {
@@ -546,10 +546,19 @@ describe("native backend factory", () => {
},
);
it.each(["pi", "cursor", "copilot"] as const)("rejects unqualified %s direct execution even with an exact persisted profile", agent => {
it("constructs the qualified Pi backend without a diagnostic opt-in or provider launch", async () => {
const input = acpxExecution();
if (input.provider.kind !== "acpx") throw new Error("invalid fixture");
const model = agent === "pi" ? QUALIFIED_ACPX_PROFILES.pi.qualificationModel : "explicit-fixture-model";
const profile = resolveQualifiedAcpxProfile("pi", QUALIFIED_ACPX_PROFILES.pi.qualificationModel);
Object.assign(input.provider, { agent: "pi", model: profile.qualificationModel, profile });
const backend = createNativeSessionBackend(input, { acpxRuntimeDirectory: "/runtime" });
await expect(backend.descriptor()).resolves.toMatchObject({ name: "acpx_runtime", version: "0.13.1" });
});
it.each(["cursor", "copilot"] as const)("rejects unqualified %s direct execution even with an exact persisted profile", agent => {
const input = acpxExecution();
if (input.provider.kind !== "acpx") throw new Error("invalid fixture");
const model = "explicit-fixture-model";
const profile = resolveQualifiedAcpxProfile(agent, model);
Object.assign(input.provider, { agent, model, profile });
expect(() => createNativeSessionBackend(input, { acpxRuntimeDirectory: "/runtime",
@@ -67,16 +67,23 @@ function agentCoreProfile(overrides: Record<string, unknown> = {}) {
}
describe("eval-session request contract", () => {
it.each(["pi", "cursor", "copilot"] as const)("admits %s only with the matching CLI diagnostic opt-in", (agent) => {
it.each(["cursor", "copilot"] as const)("admits %s only with the matching CLI diagnostic opt-in", (agent) => {
const value = request({ provider: "acpx", acpxAgent: agent, model: "explicit-provider-model" });
expect(() => parseEvalSessionRequest(value)).toThrow("--candidate-profile");
expect(parseEvalSessionRequest(value, { candidateProfile: agent })).toMatchObject({ acpxAgent: agent, model: "explicit-provider-model" });
expect(() => parseEvalSessionRequest(value, { candidateProfile: agent === "pi" ? "cursor" : "pi" })).toThrow("must match");
expect(() => parseEvalSessionRequest(value, { candidateProfile: "pi" })).toThrow("must match");
expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, model: "" }), { candidateProfile: agent })).toThrow("request.model");
expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: "codex", session: { acpxAgent: agent } }))).toThrow("session.acpxAgent must match");
expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, candidateProfile: agent }))).toThrow("--candidate-profile");
});
it("admits Pi without a diagnostic flag and retains matching historical opt-in", () => {
const value = request({ provider: "acpx", acpxAgent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731" });
expect(parseEvalSessionRequest(value)).toMatchObject({ acpxAgent: "pi" });
expect(parseEvalSessionRequest(value, { candidateProfile: "pi" })).toMatchObject({ acpxAgent: "pi" });
expect(() => parseEvalSessionRequest(value, { candidateProfile: "cursor" })).toThrow("must match");
});
it("accepts only known diagnostic flags and rejects ambiguous repeated arguments", () => {
const args = ["--request", "/tmp/request.json", "--output", "/tmp/result.json"];
const expected = resolveQualifiedAcpxProfile("pi", "openrouter/deepseek/deepseek-v4-flash-0731");
@@ -208,11 +215,12 @@ describe("eval-session request contract", () => {
})))).toBe("17");
});
it("requires explicit Pi diagnosis and accepts both qualified remote provider profiles", () => {
expect(() => parseEvalSessionRequest(request({
it("accepts Pi and both qualified remote provider profiles", () => {
expect(parseEvalSessionRequest(request({
provider: "acpx",
acpxAgent: "pi",
}))).toThrow("--candidate-profile");
model: "openrouter/deepseek/deepseek-v4-flash-0731",
}))).toMatchObject({ provider: "acpx", acpxAgent: "pi" });
expect(parseEvalSessionRequest(request({
provider: "aws_agentcore",
driver: "aws_agentcore_harness_api",
@@ -262,7 +262,7 @@ export function parseEvalSessionRequest(
if (options.candidateProfile !== undefined && (provider !== "acpx" || acpxAgent !== options.candidateProfile || !candidate)) {
throw new Error("--candidate-profile must match the request's registered candidate ACPX agent");
}
if (candidate && options.candidateProfile !== acpxAgent) {
if (candidate && acpxAgent !== "pi" && options.candidateProfile !== acpxAgent) {
throw new Error("Candidate ACPX profiles require an explicit matching --candidate-profile diagnostic flag");
}
const managedProfileInput = input.managedProfile === null
@@ -53,7 +53,7 @@ export const ACPX_CAPABILITY_PROFILES: Readonly<Record<QualifiedAcpxAgent, AcpxC
extensionNotifications: ["github.com/copilot/sessionEvent"],
},
pi: {
displayName: "Pi", qualification: "pending", models: "exact-qualified",
displayName: "Pi", qualification: "qualified", models: "exact-qualified",
permissions: "interactive", questions: "form", plans: "semantic-only", tools: "owned-extension",
recovery: "session-load", usage: "reported", steering: "owned-extension-pending", followUp: "owned-extension-pending",
artifacts: "references-pending", extensionRequests: [], extensionNotifications: ["paperclip/pi_notice"],
@@ -93,9 +93,15 @@ describe("ACPX driver profile", () => {
model: "openrouter/deepseek/deepseek-v4-flash-0731",
}),
).toMatchObject({
ok: false,
issues: [{ path: "agent", code: "qualification_pending" }],
ok: true,
config: { agent: "pi", permissionMode: "approve-all" },
});
for (const agent of ["cursor", "copilot"]) {
expect(validateAcpxDriverConfig({ agent, model: "explicit-model" }))
.toMatchObject({ ok: false, issues: [{ path: "agent", code: "qualification_pending" }] });
}
expect(validateAcpxDriverConfig({ agent: "pi", model: "another-model" }))
.toMatchObject({ ok: false, issues: [{ path: "model", code: "invalid_model" }] });
expect(
validateAcpxDriverConfig({
agent: "claude",
@@ -2098,3 +2098,10 @@ export async function probeAcpxClaudeInstallation(model: string): Promise<void>
export async function probeAcpxGrokInstallation(model: string): Promise<void> {
await verifyQualifiedAcpxInstallation(resolveQualifiedAcpxProfile("grok", model));
}
/** Verify the pinned Pi closure and snapshot lease without launching a provider. */
export async function probeAcpxPiInstallation(model: string): Promise<void> {
const installation = await verifyQualifiedAcpxInstallation(resolveQualifiedAcpxProfile("pi", model));
const lease = await installation.openCommand();
await lease.close();
}
@@ -48,7 +48,6 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
acpxVersion: QUALIFIED_ACPX_VERSION,
agent: "pi",
agentProfileVersion: 11,
qualificationStatus: "pending",
agentServerPackage: "pi-acp",
agentServerVersion: "0.0.33",
agentRuntimePackage: "@earendil-works/pi-coding-agent",
+1 -1
View File
@@ -5,4 +5,4 @@ export * from "./durable-live-session-store.js";
export * from "./runnerd-codex-transport.js";
export * from "./turn-stream.js";
export { probeAcpxClaudeInstallation, probeAcpxGrokInstallation } from "../drivers/acpx/installation-integrity.js";
export { probeAcpxClaudeInstallation, probeAcpxGrokInstallation, probeAcpxPiInstallation } from "../drivers/acpx/installation-integrity.js";
@@ -27,17 +27,30 @@ import { persistedCursorUsageNotice } from "../drivers/acpx/usage-accounting.js"
import { captureTurnRejection } from "../../test/capture-turn-rejection.js";
import * as workspaceDiff from "./workspace-diff.js";
it.each(["pi", "cursor", "copilot"] as const)("requires separately bound evaluation opt-in for %s", async (acpxAgent) => {
it.each(["cursor", "copilot"] as const)("requires separately bound evaluation opt-in for %s", async (acpxAgent) => {
const service = new CapabilityLiveSessionService();
await expect(service.create({ provider: "acpx", acpxAgent, requestedModel: "explicit-model" }))
.rejects.toThrow("explicit evaluation opt-in");
const mismatched = new CapabilityLiveSessionService({ transportOptions: {
acpxCandidateProfile: acpxAgent === "pi" ? "cursor" : "pi",
acpxCandidateProfile: "pi",
} });
await expect(mismatched.create({ provider: "acpx", acpxAgent, requestedModel: "explicit-model" }))
.rejects.toThrow("explicit evaluation opt-in");
});
it("admits Pi live sessions without candidate opt-in while preserving the exact profile", async () => {
const service = new CapabilityLiveSessionService({ transportFactory: fakeTransportFactory(providerState()) });
const session = await service.create({ provider: "acpx", acpxAgent: "pi", requestedModel: "openrouter/deepseek/deepseek-v4-flash-0731" });
try {
expect(session.snapshot().config.acpxProfile).toMatchObject({
agent: "pi", agentProfileVersion: 11,
commandDigest: "sha256:5e276f48c8a87b3e6165369faac62d3925282c84b98934575b1b7b97ad50b309",
});
await expect(service.create({ provider: "acpx", acpxAgent: "pi", requestedModel: "another-model" }))
.rejects.toThrow("requires exact model");
} finally { await session.shutdown("test complete"); }
});
class AsyncNotifications implements AsyncIterable<CodexRpcNotification> {
#values: CodexRpcNotification[] = [];
#waiters: Array<(value: IteratorResult<CodexRpcNotification>) => void> = [];
@@ -902,7 +902,7 @@ export class CapabilityLiveSessionService {
async create(input: CreateCapabilityLiveSessionInput = {}): Promise<CapabilityLiveSession> {
if (input.provider === "acpx" && input.acpxAgent !== undefined
&& ["pi", "cursor", "copilot"].includes(input.acpxAgent)
&& ["cursor", "copilot"].includes(input.acpxAgent)
&& this.#transportOptions.acpxCandidateProfile !== input.acpxAgent) {
throw new Error("The candidate ACPX profile requires explicit evaluation opt-in");
}
@@ -2016,6 +2016,21 @@ it.each(["opencode", "acpx"] as const)(
},
);
it("admits Pi runnerd transport without candidate opt-in and keeps siblings gated", async () => {
const root = await mkdtemp(join(tmpdir(), "paperclip-pi-production-admission-"));
const { transport } = createCapabilityRunnerdCodexTransport({ provider: "acpx", acpxAgent: "pi", stateDirectory: root });
try {
await expect(transport.request("collaborationMode/list", {})).resolves.toMatchObject({ data: [{ mode: "plan" }] });
for (const acpxAgent of ["cursor", "copilot"] as const) {
expect(() => createCapabilityRunnerdCodexTransport({ provider: "acpx", acpxAgent, stateDirectory: root }))
.toThrow("explicit evaluation opt-in");
}
} finally {
await transport.close();
await rm(root, { recursive: true, force: true });
}
});
it("allows trusted package-manager runtime roots without exposing HOME paths", () => {
expect(
trustedRuntimeReadOnlyRoots({
@@ -3518,7 +3518,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
throw new Error("native_adopted_runner_state_directory_required");
}
if (options.provider === "acpx" && options.acpxAgent !== undefined
&& ["pi", "cursor", "copilot"].includes(options.acpxAgent)
&& ["cursor", "copilot"].includes(options.acpxAgent)
&& options.acpxCandidateProfile !== options.acpxAgent) {
throw new Error("The candidate ACPX profile requires explicit evaluation opt-in");
}
@@ -1,4 +1,4 @@
import { probeAcpxClaudeInstallation } from "@paperclipai/paperclip-runner/live";
import { probeAcpxClaudeInstallation, probeAcpxPiInstallation } from "@paperclipai/paperclip-runner/live";
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
import { buildSandboxNpmInstallCommand } from "@paperclipai/adapter-utils";
import type { ServerAdapterModule } from "../adapters/index.js";
@@ -20,6 +20,7 @@ import {
vi.mock("@paperclipai/paperclip-runner/live", () => ({
probeAcpxClaudeInstallation: vi.fn(async () => undefined),
probeAcpxGrokInstallation: vi.fn(async () => undefined),
probeAcpxPiInstallation: vi.fn(async () => undefined),
}));
const externalAdapter: ServerAdapterModule = {
@@ -318,7 +319,10 @@ describe("server adapter registry", () => {
});
});
it("keeps the ACPX Pi profile unavailable", async () => {
it.each([true, false])("probes the exact Pi installation without qualification opt-in (ready=%s)", async (ready) => {
const probe = vi.mocked(probeAcpxPiInstallation);
if (ready) probe.mockResolvedValueOnce(undefined);
else probe.mockRejectedValueOnce(new Error("Pi closure unavailable"));
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1",
adapterType: "paperclip_runner",
@@ -330,9 +334,10 @@ describe("server adapter registry", () => {
});
expect(result).toMatchObject({
status: "fail",
checks: [{ code: "paperclip_runner_acpx_agent_unavailable" }],
status: ready ? "pass" : "fail",
checks: [{ code: ready ? "acpx_runtime_ready" : "acpx_runtime_unavailable" }],
});
expect(probe).toHaveBeenLastCalledWith("openrouter/deepseek/deepseek-v4-flash-0731");
});
it("reports qualification-only readiness for an exact host-authorized candidate", async () => {
const key = "PAPERCLIP_RUNNER_ACPX_QUALIFICATION";
+6 -5
View File
@@ -404,7 +404,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
};
}
if (profile.provider === "acpx") {
if (["cursor", "copilot", "pi"].includes(profile.acpxAgent)) {
if (["cursor", "copilot"].includes(profile.acpxAgent)) {
// The profile resolver already validated the isolated host's exact
// qualification pair. Do not report a production readiness pass.
return {
@@ -414,7 +414,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
};
}
try {
if (profile.acpxAgent !== "claude" && profile.acpxAgent !== "grok") throw new Error("Select Codex to use the native Codex runner.");
if (profile.acpxAgent !== "claude" && profile.acpxAgent !== "grok" && profile.acpxAgent !== "pi") throw new Error("Select Codex to use the native Codex runner.");
const target = context.executionTarget;
if (target?.kind === "remote") {
const probe = await runAdapterExecutionTargetShellCommand(
@@ -432,8 +432,9 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
message: "The remote platform is supported. Runtime package integrity and readiness must still be verified by the remote runner before launch." }],
};
}
const { probeAcpxClaudeInstallation, probeAcpxGrokInstallation } = await import("@paperclipai/paperclip-runner/live");
await (profile.acpxAgent === "grok" ? probeAcpxGrokInstallation : probeAcpxClaudeInstallation)(profile.model);
const { probeAcpxClaudeInstallation, probeAcpxGrokInstallation, probeAcpxPiInstallation } = await import("@paperclipai/paperclip-runner/live");
await (profile.acpxAgent === "pi" ? probeAcpxPiInstallation
: profile.acpxAgent === "grok" ? probeAcpxGrokInstallation : probeAcpxClaudeInstallation)(profile.model);
return {
adapterType: "paperclip_runner", status: "pass" as const, testedAt: new Date().toISOString(),
checks: [{ code: "acpx_runtime_ready", level: "info" as const, message: `ACPX ${profile.acpxAgent} runtime is installed and verified. Model access is checked when it runs.` }],
@@ -519,7 +520,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
)
: buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.156.0"),
agentConfigurationDoc:
"# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX Claude/Grok Build through the Rust Paperclip runner and authenticated PRP transport. Cursor, GitHub Copilot, and Pi are awaiting local and Daytona qualification and are not enabled for production runs. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n",
"# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX Claude/Grok Build/Pi through the Rust Paperclip runner and authenticated PRP transport. Pi requires the exact openrouter/deepseek/deepseek-v4-flash-0731 model and a bound OPENROUTER_API_KEY. Cursor and GitHub Copilot are awaiting local and Daytona qualification and are not enabled for production runs. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n",
getConfigSchema: () => ({
fields: [
{
@@ -447,12 +447,12 @@ describe("Paperclip Runner native provider configuration", () => {
).toThrow("provider changed after this run selected its native backend");
});
it("rejects Pi before a native descriptor is persisted", () => {
it("rejects an unqualified Pi model before a native descriptor is persisted", () => {
expect(() =>
resolvePaperclipRunnerNativeProviderInput({
backend: "acpx_runtime",
adapterConfig: { provider: "acpx", acpxAgent: "pi", model: "pi-model" },
}),
).toThrow("Pi is awaiting local and Daytona qualification");
).toThrow("requires exact model openrouter/deepseek/deepseek-v4-flash-0731");
});
});
@@ -8,7 +8,7 @@ const provider = { kind: "acpx", agent: "cursor", model: "exact-model", permissi
const authorize = (value: unknown) => ({ [ACPX_QUALIFICATION_ENV]: JSON.stringify(value) });
describe("host ACPX qualification admission", () => {
afterEach(() => vi.unstubAllEnvs());
it.each(["cursor", "copilot", "pi"])("admits %s through agent validation and native input only for the exact host pair", (agent) => {
it.each(["cursor", "copilot"])("admits %s through agent validation and native input only for the exact host pair", (agent) => {
const config = { provider: "acpx", acpxAgent: agent, model: "exact-model" };
vi.stubEnv(ACPX_QUALIFICATION_ENV, undefined);
expect(() => resolvePaperclipRunnerProviderProfile(config)).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_agent_unavailable" }));
@@ -19,6 +19,21 @@ describe("host ACPX qualification admission", () => {
expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model: "other-model" })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_qualification_invalid" }));
expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model: "" })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_required" }));
});
it("admits only the exact Pi model without host authorization and preserves permission modes", () => {
const config = { provider: "acpx", acpxAgent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731" };
for (const authorization of [undefined, "malformed unrelated candidate authorization"]) {
vi.stubEnv(ACPX_QUALIFICATION_ENV, authorization);
expect(resolvePaperclipRunnerProviderProfile(config)).toMatchObject(config);
for (const acpxPermissionMode of [undefined, "approve-all", "approve-paperclip", "approve-reads", "deny-all"]) {
expect(resolvePaperclipRunnerNativeProviderInput({ backend: "acpx_runtime", adapterConfig: { ...config, acpxPermissionMode } }))
.toMatchObject({ ...config, acpxPermissionMode: acpxPermissionMode ?? "approve-all" });
}
for (const model of [undefined, "", "different-model"]) {
expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model }))
.toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_unqualified" }));
}
}
});
it("keeps normal candidate execution closed and admits only the exact operator pair", () => {
expect(resolveAcpxQualification(provider, {})).toBeUndefined();
expect(resolveAcpxQualification(provider, authorize([{ agent: "cursor", model: "exact-model" }]))).toBe("cursor");
@@ -1163,22 +1163,32 @@ describe("remote provider pack manifest", () => {
const candidatePath = "provider-assets/pi/linux-x64";
await mkdir(join(root, candidatePath), { recursive: true });
await writeFile(join(root, candidatePath, "runtime"), "pinned runtime");
const candidates = { pi: { version: "0.0.33", profileDigest: digest("profile"),
closureDigest: digest("closure"), qualification: "pending", path: candidatePath,
const candidates = { pi: { version: "1.0.0", profileDigest: digest("profile"),
closureDigest: digest("closure"), qualification: "qualified", path: candidatePath,
sha256: sha256DirectoryTree(join(root, candidatePath)) } };
Object.assign(payload, { candidateProviders: candidates });
await writeManifest();
expect(readRemoteProviderPackManifest(root).payload.candidateProviders?.pi?.qualification).toBe("pending");
expect(readRemoteProviderPackManifest(root).payload.candidateProviders?.pi?.qualification).toBe("qualified");
await writeFile(join(root, candidatePath, "runtime"), "substitute runtime");
expect(() => readRemoteProviderPackManifest(root)).toThrow("candidate asset tree digest mismatch");
await writeFile(join(root, candidatePath, "runtime"), "pinned runtime");
for (const invalid of [{ path: "../outside" }, { qualification: "qualified" }]) {
candidates.pi.qualification = "pending";
await writeManifest();
expect(readRemoteProviderPackManifest(root).payload.candidateProviders?.pi?.qualification).toBe("pending");
candidates.pi.qualification = "qualified";
for (const invalid of [{ path: "../outside" }, { qualification: "unknown" }]) {
const original = { ...candidates.pi };
Object.assign(candidates.pi, invalid);
await writeManifest();
expect(() => readRemoteProviderPackManifest(root)).toThrow("invalid candidate identity");
candidates.pi = original;
}
for (const provider of ["cursor", "copilot"]) {
Object.assign(candidates, { [provider]: { ...candidates.pi, path: `provider-assets/${provider}/linux-x64` } });
await writeManifest();
expect(() => readRemoteProviderPackManifest(root)).toThrow("invalid candidate identity");
delete (candidates as Record<string, unknown>)[provider];
}
await writeManifest();
for (const [artifactName, substituteName] of [
["nodeCommand", "productionLock"],
@@ -8769,6 +8779,11 @@ describe("native process ownership", () => {
},
"acpx_runtime",
],
[
"Pi ACPX without candidate authorization",
{ kind: "acpx", agent: "pi", model: "openrouter/deepseek/deepseek-v4-flash-0731", permissionMode: "approve-reads" },
"acpx_runtime",
],
])(
"admits the qualified %s provider",
async (_name, provider, driverKind) => {
@@ -8807,7 +8822,7 @@ describe("native process ownership", () => {
},
);
it.each(["pi", "cursor", "copilot"])("rejects ACPX candidate %s without host authorization before constructing a backend", async (agent) => {
it.each(["cursor", "copilot"])("rejects ACPX candidate %s without host authorization before constructing a backend", async (agent) => {
const piExecution = {
...execution,
binding: { ...execution.binding, runId: "run-acpx-pi-rejected" },
@@ -7562,7 +7562,7 @@ async function executePaperclipNativeSessionWithinScope(
}
if (
input.execution.provider.kind === "acpx" &&
["pi", "cursor", "copilot"].includes(input.execution.provider.agent) &&
["cursor", "copilot"].includes(input.execution.provider.agent) &&
!resolveAcpxQualification(input.execution.provider, process.env)
) {
throw new Error(
@@ -9579,7 +9579,7 @@ type RemoteProviderPackManifest = {
bridgeDigest: string;
acpxProfileDigests: typeof REMOTE_PROVIDER_PACK_PROFILE_DIGESTS;
candidateProviders?: Partial<Record<"cursor" | "copilot" | "pi", {
version: string; profileDigest: string; closureDigest: string; qualification: "pending";
version: string; profileDigest: string; closureDigest: string; qualification: "pending" | "qualified";
path: string; sha256: string;
}>>;
artifacts: {
@@ -9755,7 +9755,10 @@ export function readRemoteProviderPackManifest(
const expectedPath = `provider-assets/${provider}/${payload.target.platform}-${payload.target.architecture}`;
if (!["cursor", "copilot", "pi"].includes(provider) || !candidate
|| Object.keys(candidate).some(key => !["version", "profileDigest", "closureDigest", "qualification", "path", "sha256"].includes(key))
|| candidate.qualification !== "pending" || candidate.path !== expectedPath
// Inventory metadata never grants admission; local profile declarations do.
// Preserve shared packs built before Pi promotion for other qualified providers.
|| (candidate.qualification !== "pending" && !(provider === "pi" && candidate.qualification === "qualified"))
|| candidate.path !== expectedPath
|| typeof candidate.version !== "string" || !candidate.version || candidate.version.length > 120
|| !/^sha256:[a-f0-9]{64}$/.test(candidate.profileDigest)
|| !/^sha256:[a-f0-9]{64}$/.test(candidate.closureDigest)
@@ -12633,7 +12636,9 @@ async function createRunnerdBackendWithinSessionClaim(
? {
acpxAgent: input.execution.provider.agent,
// Read only the server operator environment, never agent/runtime env.
acpxCandidateProfile: resolveAcpxQualification(input.execution.provider, process.env),
acpxCandidateProfile: input.execution.provider.agent === "pi"
? undefined
: resolveAcpxQualification(input.execution.provider, process.env),
acpxPermissionMode: input.execution.provider.permissionMode,
acpxCursorMode: input.execution.provider.cursorMode,
acpxPermissionModePinned:
@@ -25,6 +25,7 @@ export const QUALIFIED_ACPX_RUNNER_MODELS = {
grok: "grok-4.7",
claude: "claude-sonnet-5",
codex: "gpt-5.6-sol",
pi: "openrouter/deepseek/deepseek-v4-flash-0731",
} as const;
export type QualifiedPaperclipRunnerAcpxAgent =
@@ -462,14 +463,14 @@ export function resolvePaperclipRunnerProviderProfile(
}
throw new PaperclipRunnerProviderProfileError("paperclip_runner_acpx_agent_unavailable", `${pendingAcpxProfile.label} is awaiting local and Daytona qualification. Its profile is not enabled for production runs.`);
}
if (acpxAgent !== "claude" && acpxAgent !== "codex" && acpxAgent !== "grok") {
if (acpxAgent !== "claude" && acpxAgent !== "codex" && acpxAgent !== "grok" && acpxAgent !== "pi") {
throw new PaperclipRunnerProviderProfileError(
"paperclip_runner_acpx_agent_unavailable",
"Paperclip Runner ACPX requires a qualified agent profile.",
);
}
const qualifiedModel = QUALIFIED_ACPX_RUNNER_MODELS[acpxAgent];
if (acpxAgent === "codex" && model !== qualifiedModel) {
if ((acpxAgent === "codex" || acpxAgent === "pi") && model !== qualifiedModel) {
throw new PaperclipRunnerProviderProfileError(
"paperclip_runner_acpx_model_unqualified",
`Paperclip Runner ACPX ${acpxAgent} requires exact model ${qualifiedModel}.`,
@@ -34,6 +34,7 @@ const instructionsFileHint =
"Absolute path to a markdown file (e.g. AGENTS.md) that defines this agent's behavior. Injected into the system prompt at runtime. Note: Codex may still auto-apply repo-scoped AGENTS.md files from the workspace.";
const defaultOpenCodeRunnerModel = "openrouter/deepseek/deepseek-v4-flash-0731";
const defaultAcpxClaudeModel = "claude-sonnet-5";
const defaultAcpxPiModel = "openrouter/deepseek/deepseek-v4-flash-0731";
const defaultClaudeManagedModel = "claude-sonnet-5";
const defaultAwsAgentCoreModel = "global.anthropic.claude-sonnet-4-6";
@@ -242,16 +243,17 @@ export function CodexLocalConfigFields({
</Field>
)}
{runnerManaged && runnerProvider === "acpx" && runnerSchemaValue("acpxAgent", "claude") !== "grok" && (
<Field configSection="adapter" label="ACP agent" hint="Cursor, GitHub Copilot, and Pi are awaiting local and Daytona qualification.">
<Field configSection="adapter" label="ACP agent" hint="Pi uses DeepSeek V4 Flash 0731 through OpenRouter. Cursor and GitHub Copilot are awaiting local and Daytona qualification.">
<select className={inputClass}
value={String(isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude"))}
onChange={(event) => {
const profile = PAPERCLIP_RUNNER_ACPX_PROFILES.find(entry => entry.value === event.target.value);
const acpxSessionMode = profile?.value === "cursor" ? "agent" : undefined;
if (!profile?.qualified) return;
if (isCreate) set!({ model: profile.value === "claude" ? defaultAcpxClaudeModel : "",
const model = profile.value === "pi" ? defaultAcpxPiModel : profile.value === "claude" ? defaultAcpxClaudeModel : "";
if (isCreate) set!({ model,
adapterSchemaValues: { ...values!.adapterSchemaValues, acpxAgent: profile.value, acpxSessionMode } });
else { mark("adapterConfig", "acpxAgent", profile.value); mark("adapterConfig", "acpxSessionMode", acpxSessionMode); mark("adapterConfig", "model", profile.value === "claude" ? defaultAcpxClaudeModel : ""); }
else { mark("adapterConfig", "acpxAgent", profile.value); mark("adapterConfig", "acpxSessionMode", acpxSessionMode); mark("adapterConfig", "model", model); }
}}>
{PAPERCLIP_RUNNER_ACPX_PROFILES.map(profile => <option key={profile.value} value={profile.value} disabled={!profile.qualified}>
{profile.label}{profile.qualified ? "" : " — qualification pending"}