Enable qualified Cursor and package normal Daytona assets

Keep explicit models and unknown accounting. Bind ordinary remote startup to the packaged Linux daemon and matching image manifest after the local and Daytona qualification matrix.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-04 20:54:17 -05:00
1 parent afe305b505
commit 630ed86131
33 files changed
+252 -64

No files matched your search

@@ -243,7 +243,7 @@ export function normalizeLegacyRunnerProvider(
export const PAPERCLIP_RUNNER_ACPX_PROFILES = Object.freeze([
{ value: "grok", label: "Grok Build", qualified: true, credentialEnvironment: ["XAI_API_KEY"] },
{ 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: "cursor", label: "Cursor", qualified: true, 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"] },
] as const);
@@ -98,7 +98,7 @@ export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record<string
? providerCandidate
: "codex";
const selectedAcpxProfile = PAPERCLIP_RUNNER_ACPX_PROFILES.find(profile => profile.value === schemaValues.acpxAgent);
if (provider === "acpx" && selectedAcpxProfile && !selectedAcpxProfile.qualified && selectedAcpxProfile.value !== "cursor") {
if (provider === "acpx" && selectedAcpxProfile && !selectedAcpxProfile.qualified) {
throw new Error(`${selectedAcpxProfile.label} is not enabled for production`);
}
const acpxAgent = selectedAcpxProfile?.value ?? "claude";
@@ -180,7 +180,7 @@ try {
|| !/^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: provider === "cursor" ? "qualified" : "pending", path: assetPath,
sha256: sha256Tree(join(temporaryRoot, assetPath)) };
}
@@ -19,6 +19,16 @@ const verified = required.map(target => {
if (sha256 !== artifact.sha256 || runnerBinaryTarget(bytes) !== target) throw Error(`Release daemon ${target} digest or architecture mismatch`);
return { target, source: artifact.path, sha256 };
});
let remoteProviderPack = null;
if (manifest.remoteProviderPack) {
const entry = manifest.remoteProviderPack;
if (!isAbsolute(entry.path) || !/^sha256:[a-f0-9]{64}$/.test(entry.sha256)) throw Error("Invalid remote provider-pack release artifact");
const bytes = readFileSync(entry.path);
if (bytes.length > 512 * 1024 || "sha256:" + createHash("sha256").update(bytes).digest("hex") !== entry.sha256) throw Error("Remote provider-pack release manifest digest mismatch");
const identity = JSON.parse(bytes);
if (identity.schema !== "paperclip-runner/remote-provider-pack/v1" || identity.payload?.target?.platform !== "linux" || identity.payload?.target?.architecture !== "x64" || !/^sha256:[a-f0-9]{64}$/.test(identity.digest ?? "")) throw Error("Remote provider-pack release must bind a Linux x64 image");
remoteProviderPack = { bytes, identity };
}
const platforms = {};
for (const artifact of verified) {
const relative = `${artifact.target}/paperclip-runnerd`, destination = join(root, "dist", "bin", relative);
@@ -31,5 +41,11 @@ for (const artifact of verified) {
const sha256 = "sha256:" + createHash("sha256").update(readFileSync(destination)).digest("hex");
platforms[artifact.target] = { path: relative, sha256, sourceSha256: artifact.sha256 };
}
writeFileSync(join(root, "dist", "bin", "release-manifest.json"), JSON.stringify({schema:"paperclip.runner.release-binaries.v1",sourceRevision:manifest.sourceRevision,platforms},null,2)+"\n");
if (remoteProviderPack) {
const destination = join(root, "dist", "remote-provider-packs", "linux-x64", "provider-pack.json");
mkdirSync(dirname(destination), { recursive: true });
writeFileSync(destination, remoteProviderPack.bytes, { mode: 0o444 });
}
writeFileSync(join(root, "dist", "bin", "release-manifest.json"), JSON.stringify({schema:"paperclip.runner.release-binaries.v1",sourceRevision:manifest.sourceRevision,platforms,
...(remoteProviderPack ? { remoteProviderPack: { target: "linux-x64", digest: remoteProviderPack.identity.digest, sourceRevision: remoteProviderPack.identity.payload.runnerSourceRevision } } : {})},null,2)+"\n");
console.log("Staged verified release daemons for " + required.join(", "));
@@ -529,7 +529,7 @@ describe("native backend factory", () => {
},
);
it.each(["pi", "cursor", "copilot"] as const)("rejects unqualified %s direct execution even with an exact persisted profile", agent => {
it.each(["pi", "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 = agent === "pi" ? QUALIFIED_ACPX_PROFILES.pi.qualificationModel : "explicit-fixture-model";
@@ -540,6 +540,15 @@ describe("native backend factory", () => {
})).toThrow("ACPX candidate direct execution requires completed qualification");
});
it("constructs the qualified Cursor backend without candidate admission", async () => {
const input = acpxExecution();
if (input.provider.kind !== "acpx") throw new Error("invalid fixture");
const model = "explicit-cursor-model";
Object.assign(input.provider, { agent: "cursor", model, cursorMode: "agent", profile: resolveQualifiedAcpxProfile("cursor", model) });
const backend = createNativeSessionBackend(input, { acpxRuntimeDirectory: "/runtime", acpxEnvironment: { CURSOR_API_KEY: "explicit-fixture" } });
await expect(backend.descriptor()).resolves.toMatchObject({ name: "acpx_runtime", version: "0.13.1" });
});
it("rejects a Codex ACPX snapshot that drifts from its qualified profile", () => {
const input = acpxExecution();
if (input.provider.kind !== "acpx") throw new Error("invalid fixture");
@@ -558,7 +558,7 @@ describe("qualified ACPX runtime sidecar", () => {
["pi", "openrouter/deepseek/deepseek-v4-flash-0731"],
["cursor", "explicit-cursor-model"],
["copilot", "explicit-copilot-model"],
] as const)("initializes the declared %s candidate without promoting its profile", async (agent, model) => {
] as const)("initializes the declared %s profile with its current admission status", async (agent, model) => {
const sidecar = startSidecar();
sidecar.write(initializeRequest(1, agent, model));
const frame = await sidecar.next((value) => value.id === 1);
@@ -566,7 +566,7 @@ describe("qualified ACPX runtime sidecar", () => {
const result = frame.result as Record<string, unknown>;
expect(result.profile).toEqual(resolveQualifiedAcpxProfile(agent, model));
expect(result.profile).toMatchObject({ reportedModelId: model });
expect(ACPX_CAPABILITY_PROFILES[agent].qualification).toBe("pending");
expect(ACPX_CAPABILITY_PROFILES[agent].qualification).toBe(agent === "cursor" ? "qualified" : "pending");
});
it("fails closed after an unsupported provider bootstrap", async () => {
@@ -67,7 +67,7 @@ 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(["pi", "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" });
@@ -77,6 +77,11 @@ describe("eval-session request contract", () => {
expect(() => parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: agent, candidateProfile: agent }))).toThrow("--candidate-profile");
});
it("admits qualified Cursor without a diagnostic flag and preserves its explicit model", () => {
expect(parseEvalSessionRequest(request({ provider: "acpx", acpxAgent: "cursor", model: "exact-cursor-model" })))
.toMatchObject({ acpxAgent: "cursor", model: "exact-cursor-model" });
});
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");
@@ -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 !== "cursor" && options.candidateProfile !== acpxAgent) {
throw new Error("Candidate ACPX profiles require an explicit matching --candidate-profile diagnostic flag");
}
const managedProfileInput = input.managedProfile === null
@@ -42,7 +42,7 @@ export const ACPX_CAPABILITY_PROFILES: Readonly<Record<QualifiedAcpxAgent, AcpxC
artifacts: "policy_disabled", extensionRequests: [], extensionNotifications: [],
},
cursor: {
displayName: "Cursor", qualification: "pending", models: "explicit-provider-verified",
displayName: "Cursor", qualification: "qualified", models: "explicit-provider-verified",
permissions: "interactive", questions: "semantic-only", plans: "cursor-decision", tools: "authenticated-mcp",
recovery: "session-load", usage: "unverified", steering: "unsupported", followUp: "controller-queue",
artifacts: "references-pending",
@@ -1,6 +1,6 @@
import { verifyCursorInstallation } from "./cursor-installation.js";
import { assertCursorWorkspacePolicy } from "./cursor-launch-policy.js";
import type { QualifiedAcpxAgent, QualifiedAcpxProfile } from "./qualified-profiles.js";
import { resolveQualifiedAcpxProfile, type QualifiedAcpxAgent, type QualifiedAcpxProfile } from "./qualified-profiles.js";
import { verifyQualifiedAcpxInstallation, type VerifiedAcpxInstallation } from "./installation-integrity.js";
/** Closed build-owned registry. Provider branches add their pinned installations here. */
@@ -48,3 +48,9 @@ export function classifyAcpxProfileError(agent: QualifiedAcpxAgent, error: unkno
} else return null;
return Object.assign(new Error(detail), { code, retryable: false });
}
/** Probe trusted installed bytes without credentials or a billable prompt. */
export async function probeAcpxCursorInstallation(model: string): Promise<void> {
const installation = await verifyAcpxProfileInstallation(resolveQualifiedAcpxProfile("cursor", model));
await (await installation.openCommand()).close();
}
@@ -64,10 +64,10 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
agentServerPackage: "cursor-agent", agentServerVersion: "2026.09.26-dd393fe",
agentRuntimePackage: null, agentRuntimeVersion: null,
commandDigest: "sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
// Authenticated discovery has not established a qualification model. Never
// turn this empty declaration into a default; callers must select an ID.
// Every caller selects a model explicitly; the native provider must verify
// that exact ID. Qualification never turns Luna into a default model.
qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive",
modelPolicy: "explicit-provider-verified", qualificationStatus: "pending",
modelPolicy: "explicit-provider-verified",
},
copilot: {
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
+2
View File
@@ -78,3 +78,5 @@ export * from "./generated/capability-contract.js";
export * from "./semantic-tools/index.js";
export * as acceptedCapabilitySemanticTools from "./semantic-tools/index.js";
export * from "./compatibility.js";
export { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "./live/bundled-remote-provider-pack.js";
@@ -0,0 +1,39 @@
import { createHash } from "node:crypto";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, expect, it } from "vitest";
import { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "./bundled-remote-provider-pack.js";
const roots: string[] = [];
afterEach(() => roots.splice(0).forEach(root => rmSync(root, { recursive: true, force: true })));
function release() {
const root = mkdtempSync(join(tmpdir(), "paperclip-remote-release-")); roots.push(root);
const output = join(root, "dist/vendor/paperclip-runner"), binary = join(output, "bin/linux-x64/paperclip-runnerd");
mkdirSync(join(output, "bin/linux-x64"), { recursive: true });
const bytes = Buffer.alloc(64); Buffer.from([0x7f, 0x45, 0x4c, 0x46, 2, 1]).copy(bytes); bytes.writeUInt16LE(62, 18);
writeFileSync(binary, bytes);
const manifest = { schema: "paperclip.runner.release-binaries.v1", sourceRevision: "a".repeat(40), platforms: { "linux-x64": { path: "linux-x64/paperclip-runnerd", sha256: "sha256:" + createHash("sha256").update(bytes).digest("hex") } } };
const save = () => writeFileSync(join(output, "bin/release-manifest.json"), JSON.stringify(manifest)); save();
return { output, binary, bytes, manifest, save, url: pathToFileURL(join(output, "live/bundled-remote-provider-pack.js")).href };
}
it("selects the packaged Linux daemon even when the controller is macOS", () => {
const r = release();
expect(bundledRemoteRunnerBinary(r.url)).toBe(r.binary);
expect(bundledRemoteProviderPackManifestPath(r.url)).toBe(join(r.output, "remote-provider-packs/linux-x64/provider-pack.json"));
});
it.each(["bytes", "architecture", "digest", "path"])("rejects a mismatched packaged remote daemon: %s", reason => {
const r = release();
if (reason === "bytes") writeFileSync(r.binary, Buffer.alloc(64));
if (reason === "architecture") { r.bytes.writeUInt16LE(183, 18); writeFileSync(r.binary, r.bytes); }
if (reason === "digest") r.manifest.platforms["linux-x64"].sha256 = "sha256:" + "b".repeat(64);
if (reason === "path") r.manifest.platforms["linux-x64"].path = "../../foreign-daemon";
r.save(); expect(() => bundledRemoteRunnerBinary(r.url)).toThrow(/runner_remote_artifact_(incompatible|unavailable)/);
});
it("resolves source development through built package assets without a runtime override", () => {
expect(bundledRemoteProviderPackManifestPath("file:///repo/packages/paperclip-runner/src/live/bundled-remote-provider-pack.ts"))
.toBe("/repo/packages/paperclip-runner/dist/remote-provider-packs/linux-x64/provider-pack.json");
for (const url of ["https://example.com/dist/live/bundled-remote-provider-pack.js", "file:///tmp/foreign.js", "file:///repo/dist/live/bundled-remote-provider-pack.js?override=1"]) {
expect(() => bundledRemoteProviderPackManifestPath(url)).toThrow("package file layout");
}
});
@@ -0,0 +1,36 @@
import { fileURLToPath } from "node:url";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { runnerBinaryTarget } from "./runner-binary.js";
/** Release-owned identity for the pack already installed in the Daytona image. */
export function bundledRemoteProviderPackManifestPath(moduleUrl = import.meta.url): string {
const url = new URL(moduleUrl);
if (url.protocol !== "file:" || url.search || url.hash) throw new Error("Bundled remote provider pack requires a package file layout");
if (/\/src\/live\/bundled-remote-provider-pack\.ts$/.test(url.pathname)) {
return fileURLToPath(new URL("../../dist/remote-provider-packs/linux-x64/provider-pack.json", url));
}
if (/\/dist\/(?:vendor\/paperclip-runner\/)?live\/bundled-remote-provider-pack\.js$/.test(url.pathname)) {
return fileURLToPath(new URL("../remote-provider-packs/linux-x64/provider-pack.json", url));
}
throw new Error("Bundled remote provider pack requires a package file layout");
}
/** A macOS controller must authorize Linux bytes for its Daytona daemon. */
export function bundledRemoteRunnerBinary(moduleUrl = import.meta.url): string {
const manifestPath = bundledRemoteProviderPackManifestPath(moduleUrl);
const outputRoot = resolve(dirname(manifestPath), "../..");
const release = JSON.parse(readFileSync(join(outputRoot, "bin/release-manifest.json"), "utf8"));
const artifact = release?.platforms?.["linux-x64"];
if (release?.schema !== "paperclip.runner.release-binaries.v1" || !/^[a-f0-9]{40}$/.test(release.sourceRevision ?? "") ||
artifact?.path !== "linux-x64/paperclip-runnerd" || !/^sha256:[a-f0-9]{64}$/.test(artifact.sha256 ?? "")) {
throw new Error("runner_remote_artifact_unavailable: install the complete Paperclip release with its Linux daemon");
}
const binary = join(outputRoot, "bin", artifact.path);
const bytes = readFileSync(binary);
if (runnerBinaryTarget(bytes) !== "linux-x64" || "sha256:" + createHash("sha256").update(bytes).digest("hex") !== artifact.sha256) {
throw new Error("runner_remote_artifact_incompatible: packaged Linux daemon identity mismatch");
}
return binary;
}
@@ -6,3 +6,7 @@ export * from "./runnerd-codex-transport.js";
export * from "./turn-stream.js";
export { probeAcpxClaudeInstallation, probeAcpxGrokInstallation } from "../drivers/acpx/installation-integrity.js";
export { probeAcpxCursorInstallation } from "../drivers/acpx/profile-installation.js";
export { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "./bundled-remote-provider-pack.js";
@@ -26,7 +26,7 @@ import { defaultCapabilityRunnerdBinary } from "./runnerd-codex-transport.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(["pi", "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");
@@ -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)
&& ["pi", "copilot"].includes(input.acpxAgent)
&& this.#transportOptions.acpxCandidateProfile !== input.acpxAgent) {
throw new Error("The candidate ACPX profile requires explicit evaluation opt-in");
}
@@ -1829,7 +1829,7 @@ export class CapabilityLiveSession {
const candidate = this.#config.acpxAgent;
if (missingTokens && this.#config.provider === "acpx"
&& (candidate === "pi" || candidate === "cursor" || candidate === "copilot")
&& this.#transportOptions.acpxCandidateProfile === candidate) {
&& (candidate === "cursor" || this.#transportOptions.acpxCandidateProfile === candidate)) {
// Native candidate wrappers can complete a turn without a usage receipt,
// including entitlement-denied turns. Retain the actual result for the
// oracle without inventing tokens, charges, or a successful model call.
@@ -3519,7 +3519,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)
&& ["pi", "copilot"].includes(options.acpxAgent)
&& options.acpxCandidateProfile !== options.acpxAgent) {
throw new Error("The candidate ACPX profile requires explicit evaluation opt-in");
}
+9 -3
View File
@@ -20,10 +20,11 @@ const releaseBinaries = JSON.parse(readFileSync(join(repo, 'server/dist/vendor/p
assert.equal(releaseBinaries.schema, 'paperclip.runner.release-binaries.v1');
assert.match(releaseBinaries.sourceRevision, /^[a-f0-9]{40}$/);
run('git', ['merge-base', '--is-ancestor', releaseBinaries.sourceRevision, sourceRevision], repo);
// Controller/fixture repairs may follow a frozen runtime. Its exact binary
// provenance stays unchanged; require the Runner source to remain identical.
run('git', ['diff', '--quiet', releaseBinaries.sourceRevision, sourceRevision, '--', 'packages/paperclip-runner'], repo);
// Controller and TypeScript admission may follow a frozen daemon. Keep its
// exact provenance and require the Rust sources and protocol to remain identical.
run('git', ['diff', '--quiet', releaseBinaries.sourceRevision, sourceRevision, '--', 'packages/paperclip-runner/runner', 'packages/paperclip-runner/protocol'], repo);
assert.deepEqual(Object.keys(releaseBinaries.platforms).sort(), ['darwin-arm64', 'darwin-x64', 'linux-x64']);
assert.equal(releaseBinaries.remoteProviderPack?.target, 'linux-x64', 'Release assembly must include the expected ordinary Daytona image pack');
const releaseVersion = `0.0.0-cursor-verify.${sourceRevision.slice(0, 12)}`;
const listing = run(process.execPath, [join(repo, 'scripts/release-package-map.mjs'), 'list'], repo).toString().trim().split('\n').map(line => line.split('\t'));
const packages = new Map(listing.map(([dir, name]) => [name, {dir, manifest: JSON.parse(readFileSync(join(repo, dir, 'package.json')))}]));
@@ -86,6 +87,7 @@ import { execFileSync } from 'node:child_process';
import { defaultCapabilityRunnerdBinary } from '/consumer/node_modules/@paperclipai/server/dist/vendor/paperclip-runner/live/runnerd-codex-transport.js';
import { resolvePaperclipRunnerBinary } from '/consumer/node_modules/@paperclipai/server/dist/services/native-runtime/native-codex-runner.js';
import { runnerBinaryTarget } from '/consumer/node_modules/@paperclipai/server/dist/vendor/paperclip-runner/live/runner-binary.js';
import { bundledRemoteRunnerBinary, bundledRemoteProviderPackManifestPath } from '/consumer/node_modules/@paperclipai/server/dist/vendor/paperclip-runner/live/bundled-remote-provider-pack.js';
import { verifyAcpxProfileInstallation } from '/consumer/node_modules/@paperclipai/server/dist/vendor/paperclip-runner/drivers/acpx/profile-installation.js';
import { resolveQualifiedAcpxProfile } from '/consumer/node_modules/@paperclipai/server/dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js';
const profile = resolveQualifiedAcpxProfile('cursor', 'gpt-5.6-luna[context=272k,reasoning=medium,fast=false]');
@@ -107,6 +109,10 @@ for (const [target, artifact] of Object.entries(manifest.platforms)) {
const binary = defaultCapabilityRunnerdBinary();
assert.equal(binary, binaryRoot + '/linux-x64/paperclip-runnerd');
assert.equal(resolvePaperclipRunnerBinary(), binary, 'The installed server must use the same verified platform daemon');
assert.equal(bundledRemoteRunnerBinary(), binary);
const remotePack = JSON.parse(readFileSync(bundledRemoteProviderPackManifestPath(), 'utf8'));
assert.equal(remotePack.digest, manifest.remoteProviderPack.digest);
assert.equal(remotePack.payload.providers.cursor.qualification, 'qualified');
const metadata = JSON.parse(execFileSync(binary, ['--build-metadata'], { encoding: 'utf8' }));
assert.equal(metadata.schema, 'paperclip-runner/runnerd-build-metadata/v1');
assert.equal(metadata.binaryContractVersion, 2);
+20 -3
View File
@@ -1,4 +1,4 @@
import { probeAcpxClaudeInstallation } from "@paperclipai/paperclip-runner/live";
import { probeAcpxClaudeInstallation, probeAcpxCursorInstallation } 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),
probeAcpxCursorInstallation: vi.fn(async () => undefined),
}));
it("advertises tool-refresh recovery for the selected legacy harness", () => {
@@ -326,6 +327,22 @@ describe("server adapter registry", () => {
});
});
it.each([true, false])("checks ordinary Cursor runtime readiness (%s)", async (ready) => {
const probe = vi.mocked(probeAcpxCursorInstallation);
if (ready) probe.mockResolvedValueOnce(undefined);
else probe.mockRejectedValueOnce(new Error("Run paperclipai runtime setup cursor"));
const model = "gpt-5.6-luna[context=272k,reasoning=medium,fast=false]";
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1", adapterType: "paperclip_runner",
config: { provider: "acpx", acpxAgent: "cursor", model },
});
expect(probe).toHaveBeenLastCalledWith(model);
expect(result).toMatchObject({
status: ready ? "pass" : "fail",
checks: [expect.objectContaining({ code: ready ? "acpx_runtime_ready" : "acpx_runtime_unavailable" })],
});
});
it("keeps the ACPX Pi profile unavailable", async () => {
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1",
@@ -345,11 +362,11 @@ describe("server adapter registry", () => {
it("reports qualification-only readiness for an exact host-authorized candidate", async () => {
const key = "PAPERCLIP_RUNNER_ACPX_QUALIFICATION";
const previous = process.env[key];
process.env[key] = JSON.stringify([{ agent: "cursor", model: "exact-model" }]);
process.env[key] = JSON.stringify([{ agent: "copilot", model: "exact-model" }]);
try {
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
companyId: "company-1", adapterType: "paperclip_runner",
config: { provider: "acpx", acpxAgent: "cursor", model: "exact-model" },
config: { provider: "acpx", acpxAgent: "copilot", model: "exact-model" },
});
expect(result).toMatchObject({ status: "warn", checks: [{ code: "acpx_candidate_qualification_only" }] });
} finally {
+5 -5
View File
@@ -406,7 +406,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = {
};
}
if (profile.provider === "acpx") {
if (["cursor", "copilot", "pi"].includes(profile.acpxAgent)) {
if (["copilot", "pi"].includes(profile.acpxAgent)) {
// The profile resolver already validated the isolated host's exact
// qualification pair. Do not report a production readiness pass.
return {
@@ -416,7 +416,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 !== "cursor") throw new Error("Select Codex to use the native Codex runner.");
const target = context.executionTarget;
if (target?.kind === "remote") {
const probe = await runAdapterExecutionTargetShellCommand(
@@ -434,8 +434,8 @@ 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, probeAcpxCursorInstallation } = await import("@paperclipai/paperclip-runner/live");
await (profile.acpxAgent === "grok" ? probeAcpxGrokInstallation : profile.acpxAgent === "cursor" ? probeAcpxCursorInstallation : 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.` }],
@@ -521,7 +521,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/Cursor through the Rust Paperclip runner and authenticated PRP transport. 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",
getConfigSchema: () => ({
fields: [
{
@@ -4,11 +4,11 @@ import { ACPX_QUALIFICATION_ENV, resolveAcpxQualification } from "./acpx-qualifi
import type { NativeExecutionInput } from "../../vendor/paperclip-runner/index.js";
import { resolvePaperclipRunnerProviderProfile, resolvePaperclipRunnerNativeProviderInput } from "./provider-profile.js";
const provider = { kind: "acpx", agent: "cursor", model: "exact-model", permissionMode: "approve-all" } as NativeExecutionInput["provider"];
const provider = { kind: "acpx", agent: "copilot", model: "exact-model", permissionMode: "approve-all" } as NativeExecutionInput["provider"];
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(["copilot", "pi"])("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" }));
@@ -21,8 +21,8 @@ describe("host ACPX qualification admission", () => {
});
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");
for (const entries of [[{ agent: "copilot", model: "exact-model" }], [{ agent: "cursor", model: "other-model" }]]) {
expect(resolveAcpxQualification(provider, authorize([{ agent: "copilot", model: "exact-model" }]))).toBe("copilot");
for (const entries of [[{ agent: "pi", model: "exact-model" }], [{ agent: "copilot", model: "other-model" }]]) {
expect(() => resolveAcpxQualification(provider, authorize(entries))).toThrow("exact model");
}
});
@@ -32,6 +32,14 @@ describe("host ACPX qualification admission", () => {
])("rejects malformed or broad authorization %j", entries => {
expect(() => resolveAcpxQualification(provider, authorize(entries))).toThrow("Invalid ACPX");
});
it("admits Cursor through ordinary configuration and ignores unrelated qualification authority", () => {
const config = { provider: "acpx", acpxAgent: "cursor", model: "exact-cursor-model" };
vi.stubEnv(ACPX_QUALIFICATION_ENV, JSON.stringify([{ agent: "pi", model: "pi-model" }]));
expect(resolvePaperclipRunnerProviderProfile(config)).toMatchObject({ acpxAgent: "cursor", model: config.model });
expect(resolvePaperclipRunnerNativeProviderInput({ backend: "acpx_runtime", adapterConfig: config })).toMatchObject({ acpxAgent: "cursor", model: config.model });
expect(resolveAcpxQualification({ ...provider, agent: "cursor" } as typeof provider, authorize([]))).toBeUndefined();
expect(() => resolvePaperclipRunnerProviderProfile({ ...config, model: "" })).toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_required" }));
});
it("does not alter existing qualified providers", () => {
expect(resolveAcpxQualification({ ...provider, agent: "codex" } as typeof provider, authorize([]))).toBeUndefined();
});
@@ -8,7 +8,7 @@ export function resolveAcpxQualification(
provider: NativeExecutionInput["provider"] | { kind: "acpx"; agent: string; model: string },
hostEnvironment: NodeJS.ProcessEnv,
): AcpxQualificationCandidate | undefined {
if (provider.kind !== "acpx" || !["cursor", "copilot", "pi"].includes(provider.agent)) return undefined;
if (provider.kind !== "acpx" || !["copilot", "pi"].includes(provider.agent)) return undefined;
const encoded = hostEnvironment[ACPX_QUALIFICATION_ENV];
if (!encoded) return undefined;
let entries: unknown;
@@ -310,6 +310,7 @@ import {
REMOTE_RUNNER_CHILD_LAUNCH_SCRIPT,
verifyRemoteRunnerReattachment,
readRemoteProviderPackManifest,
readBundledRemoteProviderPackManifest,
providerSessionIdentityFromDurableProviderState,
durableProviderCheckpointFailureReason,
providerSessionIdentityTransitionIsAllowed,
@@ -1156,10 +1157,22 @@ describe("remote provider pack manifest", () => {
await mkdir(join(root, cursorPath), { recursive: true });
await writeFile(join(root, cursorPath, "runtime"), "pinned Cursor runtime");
Object.assign(payload, { providers: { cursor: { version: "2026.09.26-dd393fe", profileDigest: digest("cursor-profile"),
closureDigest: digest("cursor-closure"), qualification: "pending", path: cursorPath,
closureDigest: digest("cursor-closure"), qualification: "qualified", path: cursorPath,
sha256: sha256DirectoryTree(join(root, cursorPath)) } } });
await writeManifest();
expect(readRemoteProviderPackManifest(root).payload.providers?.cursor?.version).toBe("2026.09.26-dd393fe");
const releaseMetadata = await mkdtemp(join(tmpdir(), "paperclip-image-identity-"));
const manifestPath = join(releaseMetadata, "provider-pack.json");
await cp(join(root, "provider-pack.json"), manifestPath);
expect(readBundledRemoteProviderPackManifest(manifestPath).digest).toBe(readRemoteProviderPackManifest(root).digest);
// A metadata-only release identity is never accepted as a host asset tree.
expect(() => readRemoteProviderPackManifest(releaseMetadata)).toThrow();
const originalTarget = payload.target;
payload.target = { platform: "darwin", architecture: "arm64" };
await writeManifest(); await cp(join(root, "provider-pack.json"), manifestPath);
expect(() => readBundledRemoteProviderPackManifest(manifestPath)).toThrow();
payload.target = originalTarget; await writeManifest();
await rm(releaseMetadata, { recursive: true, force: true });
await writeFile(join(root, cursorPath, "runtime"), "substitute Cursor runtime");
expect(() => readRemoteProviderPackManifest(root)).toThrow("asset tree digest mismatch");
await writeFile(join(root, cursorPath, "runtime"), "pinned Cursor runtime");
@@ -8234,7 +8247,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(["pi", "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" },
@@ -1,3 +1,4 @@
import { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "../../vendor/paperclip-runner/index.js";
import { nativeRetryCancellationEligible, rethrowNativeCancellationLockConflict, assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
import { readNativeCursorPlanWait } from "./native-cursor-plan-wait.js";
import { NativeCursorPermissionDeclinedError, readCompletedCursorPermissionDecline } from "./native-cursor-permission-decline.js";
@@ -7502,7 +7503,7 @@ async function executePaperclipNativeSessionWithinScope(
}
if (
input.execution.provider.kind === "acpx" &&
["pi", "cursor", "copilot"].includes(input.execution.provider.agent) &&
["pi", "copilot"].includes(input.execution.provider.agent) &&
!resolveAcpxQualification(input.execution.provider, process.env)
) {
throw new Error(
@@ -9511,11 +9512,11 @@ type RemoteProviderPackManifest = {
bridgeDigest: string;
acpxProfileDigests: typeof REMOTE_PROVIDER_PACK_PROFILE_DIGESTS;
providers?: Partial<Record<"cursor", {
version: string; profileDigest: string; closureDigest: string; qualification: "pending";
version: string; profileDigest: string; closureDigest: string; qualification: "qualified" | "pending";
path: string; sha256: string;
}>>;
candidateProviders?: Partial<Record<"cursor" | "copilot" | "pi", {
version: string; profileDigest: string; closureDigest: string; qualification: "pending";
version: string; profileDigest: string; closureDigest: string; qualification: "qualified" | "pending";
path: string; sha256: string;
}>>;
artifacts: {
@@ -9579,9 +9580,19 @@ function providerPackRelativePath(value: unknown, field: string): string {
return value;
}
export function readRemoteProviderPackManifest(
packRoot: string,
): RemoteProviderPackManifest {
export function readRemoteProviderPackManifest(packRoot: string): RemoteProviderPackManifest {
return readRemoteProviderPackIdentity(packRoot, true);
}
export function readBundledRemoteProviderPackManifest(manifestPath = bundledRemoteProviderPackManifestPath()): RemoteProviderPackManifest {
const manifest = readRemoteProviderPackIdentity(dirname(manifestPath), false);
if (manifest.payload.target.platform !== "linux" || manifest.payload.target.architecture !== "x64") {
throw new Error("runner_remote_provider_artifact_incompatible: bundled image pack must target Linux x64");
}
return manifest;
}
function readRemoteProviderPackIdentity(packRoot: string, verifyControllerFiles: boolean): RemoteProviderPackManifest {
let manifest: RemoteProviderPackManifest;
try {
manifest = JSON.parse(
@@ -9669,15 +9680,15 @@ export function readRemoteProviderPackManifest(
);
}
if (
typeof artifact?.sha256 !== "string" ||
sha256File(resolve(packRoot, artifactPath)) !== artifact.sha256
!/^sha256:[a-f0-9]{64}$/.test(artifact?.sha256 ?? "") ||
(verifyControllerFiles && sha256File(resolve(packRoot, artifactPath)) !== artifact.sha256)
) {
throw new Error(
`runner_remote_provider_artifact_incompatible: ${label} digest mismatch`,
);
}
}
if (sha256DirectoryTree(resolve(packRoot, "dist")) !== payload.distDigest) {
if (verifyControllerFiles && sha256DirectoryTree(resolve(packRoot, "dist")) !== payload.distDigest) {
throw new Error(
"runner_remote_provider_artifact_incompatible: provider dist tree digest mismatch",
);
@@ -9691,7 +9702,7 @@ export function readRemoteProviderPackManifest(
const expectedPath = `provider-assets/${provider}/${payload.target.platform}-${payload.target.architecture}`;
if (!(inventory === "providers" ? ["cursor"] : ["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
|| candidate.qualification !== (provider === "cursor" ? "qualified" : "pending") || 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)
@@ -9699,7 +9710,7 @@ export function readRemoteProviderPackManifest(
throw new Error("runner_remote_provider_artifact_incompatible: invalid candidate identity");
}
const candidatePath = providerPackRelativePath(candidate.path, "candidate assets");
if (sha256DirectoryTree(resolve(packRoot, candidatePath)) !== candidate.sha256) {
if (verifyControllerFiles && sha256DirectoryTree(resolve(packRoot, candidatePath)) !== candidate.sha256) {
throw new Error("runner_remote_provider_artifact_incompatible: candidate asset tree digest mismatch");
}
}
@@ -10915,7 +10926,11 @@ async function createRunnerdBackendWithinSessionClaim(
const configuredProviderPackRoot =
input.runnerRemoteProviderPackPath?.trim() || null;
let expectedProviderPackManifest: RemoteProviderPackManifest | null = null;
if (requiresRemoteProviderPack) {
const useBundledCursorImageAssets = requiresRemoteProviderPack && !configuredProviderPackRoot &&
input.execution.provider.kind === "acpx" && input.execution.provider.agent === "cursor";
if (useBundledCursorImageAssets) {
expectedProviderPackManifest = readBundledRemoteProviderPackManifest();
} else if (requiresRemoteProviderPack) {
if (
!configuredProviderPackRoot ||
!existsSync(configuredProviderPackRoot) ||
@@ -10942,7 +10957,7 @@ async function createRunnerdBackendWithinSessionClaim(
// When an explicit remote artifact is configured, prepareRemoteRunner stages
// these exact bytes at remoteBinary before launch.
const controllerRunnerBinary = remoteTarget
? input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary()
? input.runnerRemoteBinaryPath?.trim() || (useBundledCursorImageAssets ? bundledRemoteRunnerBinary() : resolvePaperclipRunnerBinary())
: resolvePaperclipRunnerBinary();
const explicitRemoteCodex = input.runnerRemoteCodexPath?.trim() || null;
const remoteCodexNpmSpec = input.runnerRemoteCodexNpmSpec?.trim() || null;
@@ -11470,7 +11485,7 @@ async function createRunnerdBackendWithinSessionClaim(
}
if (
requiresRemoteProviderPack &&
configuredProviderPackRoot &&
expectedProviderPackManifest &&
stagedRemoteProviderPackRoot
) {
const packSource = await prepareVerifiedRemoteProviderPack({
@@ -11526,6 +11541,9 @@ async function createRunnerdBackendWithinSessionClaim(
return preinstalledProviderPack !== null;
},
stageAndVerify: async () => {
if (!configuredProviderPackRoot) {
throw new Error("runner_remote_provider_artifact_incompatible: install the matching Paperclip package and Daytona image; the image provider pack did not match the bundled release identity");
}
if (!remoteCommandRunner.syncIn) {
throw new Error(
"runner_remote_provider_artifact_incompatible: this remote transport cannot stage a provider pack; preinstall the exact manifest-matched pack",
@@ -25,6 +25,8 @@ export const QUALIFIED_ACPX_RUNNER_MODELS = {
grok: "grok-4.7",
claude: "claude-sonnet-5",
codex: "gpt-5.6-sol",
// Cursor models are explicit and provider-verified; there is no default.
cursor: "",
} as const;
export type QualifiedPaperclipRunnerAcpxAgent =
@@ -462,7 +464,10 @@ 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 === "cursor" && !model) {
throw new PaperclipRunnerProviderProfileError("paperclip_runner_acpx_model_required", "Cursor requires an explicit model ID; there is no default model.");
}
if (acpxAgent !== "claude" && acpxAgent !== "codex" && acpxAgent !== "grok" && acpxAgent !== "cursor") {
throw new PaperclipRunnerProviderProfileError(
"paperclip_runner_acpx_agent_unavailable",
"Paperclip Runner ACPX requires a qualified agent profile.",
+3
View File
@@ -131,3 +131,6 @@ export const NativeProviderTerminalFailure = runner.NativeProviderTerminalFailur
export const completeTerminatedRemoteNativeSessionCleanup = runner.completeTerminatedRemoteNativeSessionCleanup;
export const completeTerminatedLocalNativeSessionCleanup = runner.completeTerminatedLocalNativeSessionCleanup;
export const bundledRemoteProviderPackManifestPath = runner.bundledRemoteProviderPackManifestPath;
export const bundledRemoteRunnerBinary = runner.bundledRemoteRunnerBinary;
+3 -3
View File
@@ -325,13 +325,13 @@ export const runnerProfiles: readonly RunnerProfileFixture[] = [
] as const;
// Explicit qualification choices from authenticated model discovery, not shipped
// defaults or claims that these candidates have passed inference qualification.
// defaults. Cursor has local/Daytona proof; Copilot and Pi remain pending.
export const extendedHarnessProfiles: readonly RunnerProfileFixture[] = [
nativeProfile({
id: "runner-acpx-cursor", label: "Runner Cursor (candidate)", provider: "acpx", acpxAgent: "cursor",
id: "runner-acpx-cursor", label: "Runner Cursor", provider: "acpx", acpxAgent: "cursor",
qualificationCandidate: "cursor", credential: "CURSOR_AUTH_TOKEN",
model: "gpt-5.6-luna[context=272k,reasoning=medium,fast=false]",
modelQualification: { source: "candidate_runner_profile", qualificationId: "cursor:2026.09.26-dd393fe:discovery-2026-09-28" },
modelQualification: { source: "qualified_runner_profile", qualificationId: "cursor:2026.09.26-dd393fe:v11:luna:local-daytona-2026-10-04" },
}),
nativeProfile({
id: "runner-acpx-copilot", label: "Runner Copilot (candidate)", provider: "acpx", acpxAgent: "copilot",
+5 -4
View File
@@ -16,12 +16,12 @@ describe("extended ACP harness qualification", () => {
expect(all.some(cell => cell.suite.id === "extended-harnesses")).toBe(false);
expect(runnerSuites.find(suite => suite.id === "extended-harnesses")?.manualOnly).toBe(true);
});
it("uses exact discovered models, encrypted credential references and pending qualification metadata", () => {
it("uses exact discovered models, encrypted credential references and current qualification metadata", () => {
expect(extendedHarnessProfiles.map(profile => profile.model)).toEqual([
"gpt-5.6-luna[context=272k,reasoning=medium,fast=false]", "gpt-5.6-luna", "openrouter/deepseek/deepseek-v4-flash-0731",
]);
for (const profile of extendedHarnessProfiles) {
expect(profile.modelQualification.source).toBe("candidate_runner_profile");
expect(profile.modelQualification.source).toBe(profile.qualificationCandidate === "cursor" ? "qualified_runner_profile" : "candidate_runner_profile");
const secretRef = { type: "secret_ref" as const, secretId: "11111111-1111-4111-8111-111111111111", version: "latest" as const };
const payload = profile.buildAgent({ executionId: "fixture", environmentId: "local", environmentFixtureId: "local", workspacePath: "/tmp/workspace", secretRefs: { [profile.credential]: secretRef } });
expect(payload.adapterConfig).toMatchObject({ provider: "acpx", model: profile.model, timeoutSec: 120, acpxAgent: profile.qualificationCandidate, env: { [profile.credential]: secretRef } });
@@ -31,11 +31,12 @@ describe("extended ACP harness qualification", () => {
const cell = selected[0]!;
const ambient = { PAPERCLIP_RUNNER_ACPX_QUALIFICATION: "ambient", CURSOR_AUTH_TOKEN: "cursor", CURSOR_API_KEY: "cursor-key", COPILOT_GITHUB_TOKEN: "copilot", GH_TOKEN: "github", GITHUB_TOKEN: "github" };
const env = buildRunnerE2EProcessEnvironment(ambient, [cell]);
expect(JSON.parse(env.PAPERCLIP_RUNNER_ACPX_QUALIFICATION!)).toEqual([{ agent: "cursor", model: cell.profile.model }]);
expect(env.PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined();
const server = buildPaperclipServerEnvironment(env);
for (const name of Object.keys(ambient).filter(name => name !== "PAPERCLIP_RUNNER_ACPX_QUALIFICATION")) expect(server[name]).toBeUndefined();
expect(buildRunnerE2EProcessEnvironment(ambient, []).PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined();
expect(() => buildRunnerE2EProcessEnvironment({}, [{ ...cell, suite: { ...cell.suite, manualOnly: false } }])).toThrow("explicit");
const pending = selected.find(value => value.profile.qualificationCandidate === "copilot")!;
expect(() => buildRunnerE2EProcessEnvironment({}, [{ ...pending, suite: { ...pending.suite, manualOnly: false } }])).toThrow("explicit");
});
it("grades the actual file independently of the model's validation claim", () => {
const cell = selected.find(cell => cell.task.id === "file-edit-validate")!;
+1 -1
View File
@@ -114,7 +114,7 @@ export function buildRunnerE2EProcessEnvironment(
const candidates = new Map<string, string>();
for (const execution of executions) {
const agent = execution.profile.qualificationCandidate;
if (!agent) continue;
if (!agent || agent === "cursor") continue;
const admittedSuite = execution.suite.id === "extended-harnesses"
|| execution.suite.id === "rich-acp-warm-continuity"
|| (execution.suite.id === "cursor-native" && agent === "cursor")
@@ -5,10 +5,10 @@ import type { ActiveStopPending } from "./native-active-stop-evidence.js";
import { buildRunnerE2EProcessEnvironment } from "./harness-env.js";
import { runnerMatrix } from "./catalog.js";
it("admits only explicit Cursor loss cells through the qualification harness", () => {
it("runs qualified Cursor loss cells without qualification overrides", () => {
const execution = runnerMatrix.find(row => row.suite.id === "native-provider-loss" && row.environment.id === "local")!;
expect(JSON.parse(buildRunnerE2EProcessEnvironment({}, [execution]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION!))
.toEqual([{ agent: "cursor", model: execution.profile.model }]);
expect(buildRunnerE2EProcessEnvironment({}, [execution]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION)
.toBeUndefined();
expect(() => buildRunnerE2EProcessEnvironment({}, [{ ...execution, profile: { ...execution.profile, qualificationCandidate: "copilot" } }])).toThrow("explicit provider qualification suite");
});
@@ -76,7 +76,7 @@ describe("Paperclip Runner Codex configuration", () => {
expect(html).toContain('<option value="acpx" selected="">ACP agents</option>');
expect(html).toContain("ACP agent");
expect(html).toContain('<option value="claude" selected="">Claude</option>');
expect(html).toContain('<option value="cursor" disabled="">Cursor — qualification pending</option>');
expect(html).toContain('<option value="cursor">Cursor</option>');
expect(html).toContain('<option value="copilot" disabled="">GitHub Copilot — qualification pending</option>');
expect(html).toContain('<option value="pi" disabled="">Pi — qualification pending</option>');
expect(html).not.toContain("Codex via ACPX");
@@ -242,7 +242,7 @@ 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="Cursor uses Paperclip questions; per-run cost is unavailable. GitHub Copilot and Pi await qualification.">
<select className={inputClass}
value={String(isCreate ? values!.adapterSchemaValues?.acpxAgent ?? "claude" : eff("adapterConfig", "acpxAgent", config.acpxAgent ?? "claude"))}
onChange={(event) => {