diff --git a/doc/plans/2026-10-03-cursor-production-readiness.md b/doc/plans/2026-10-03-cursor-production-readiness.md index 48fb8860968..cc3f37afb98 100644 --- a/doc/plans/2026-10-03-cursor-production-readiness.md +++ b/doc/plans/2026-10-03-cursor-production-readiness.md @@ -33,6 +33,8 @@ TypeScript profile registry, provider-pack builder, server pack verifier, and Cursor installation code. `packages/paperclip-runner/acpx-profiles.json` now owns the release/profile declarations; generated TypeScript and named Rust structs share them. Model admission is explicit and separate from qualification examples. +Server admission calls the same resolver. Existing Claude/Grok product defaults +are named separately; Cursor and Codex ACP still require explicit selection. Cursor closure pins are generated from the existing distribution manifest. Build and typecheck reject declaration drift, including mismatches with the Cursor release attestation and installed dependencies. diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index 89ff04c8451..10443933060 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -138,7 +138,7 @@ import { httpAdapter } from "./http/index.js"; import { DEFAULT_OPENCODE_RUNNER_MODEL, PaperclipRunnerProviderProfileError, - QUALIFIED_ACPX_RUNNER_MODELS, + DEFAULT_ACPX_RUNNER_MODELS, QUALIFIED_OPENCODE_RUNNER_VERSION, resolvePaperclipRunnerProviderProfile, } from "../services/native-runtime/provider-profile.js"; @@ -490,19 +490,19 @@ const paperclipRunnerAdapter: ServerAdapterModule = { models: [ ...codexModels, { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, - { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], listModels: async () => [ ...await listCodexModels(), { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, - { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], refreshModels: async () => [ ...await refreshCodexModels(), { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, - { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: DEFAULT_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], supportsLocalAgentJwt: false, diff --git a/server/src/services/native-runtime/acpx-qualification.test.ts b/server/src/services/native-runtime/acpx-qualification.test.ts index 051e1033898..eeacd60fff6 100644 --- a/server/src/services/native-runtime/acpx-qualification.test.ts +++ b/server/src/services/native-runtime/acpx-qualification.test.ts @@ -43,6 +43,24 @@ describe("host ACPX qualification admission", () => { it("does not alter existing qualified providers", () => { expect(resolveAcpxQualification({ ...provider, agent: "codex" } as typeof provider, authorize([]))).toBeUndefined(); }); + it.each(["claude", "grok", "cursor"])("keeps %s model selection open to native verification", (acpxAgent) => { + expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent, model: "explicit-new-model" })) + .toMatchObject({ acpxAgent, model: "explicit-new-model" }); + }); + it("preserves exact Codex ACP admission and explicit selection", () => { + expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent: "codex", model: "gpt-5.6-sol" })) + .toMatchObject({ acpxAgent: "codex", model: "gpt-5.6-sol" }); + for (const model of [undefined, "", "different-model"]) { + expect(() => resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent: "codex", model })) + .toThrow(expect.objectContaining({ code: "paperclip_runner_acpx_model_unqualified" })); + } + }); + it("preserves product defaults separately from qualification examples", () => { + for (const [acpxAgent, model] of [["claude", "claude-sonnet-5"], ["grok", "grok-4.7"]]) { + expect(resolvePaperclipRunnerProviderProfile({ provider: "acpx", acpxAgent })) + .toMatchObject({ acpxAgent, model }); + } + }); it("binds the executor to host process environment rather than agent configuration", () => { const source = readFileSync(new URL("./native-session-executor.ts", import.meta.url), "utf8"); expect(source).toContain("resolveAcpxQualification(input.execution.provider, process.env)"); diff --git a/server/src/services/native-runtime/provider-profile.ts b/server/src/services/native-runtime/provider-profile.ts index c743dab93e5..0174fa26958 100644 --- a/server/src/services/native-runtime/provider-profile.ts +++ b/server/src/services/native-runtime/provider-profile.ts @@ -1,3 +1,4 @@ +import { resolveQualifiedAcpxProfile } from "../../vendor/paperclip-runner/index.js"; import { isPaperclipRunnerProvider, PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES, @@ -21,16 +22,16 @@ export const DEFAULT_OPENCODE_RUNNER_MODEL = "openrouter/deepseek/deepseek-v4-flash-0731" as const; export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const; -export const QUALIFIED_ACPX_RUNNER_MODELS = { +export const DEFAULT_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: "", + // These profiles require explicit configuration. Admission belongs to the runner. + codex: null, + cursor: null, } as const; export type QualifiedPaperclipRunnerAcpxAgent = - keyof typeof QUALIFIED_ACPX_RUNNER_MODELS; + keyof typeof DEFAULT_ACPX_RUNNER_MODELS; type AdmittedPaperclipRunnerAcpxAgent = QualifiedPaperclipRunnerAcpxAgent | AcpxQualificationCandidate; export type PaperclipRunnerProviderProfile = @@ -473,17 +474,19 @@ export function resolvePaperclipRunnerProviderProfile( "Paperclip Runner ACPX requires a qualified agent profile.", ); } - const qualifiedModel = QUALIFIED_ACPX_RUNNER_MODELS[acpxAgent]; - if (acpxAgent === "codex" && model !== qualifiedModel) { + const selectedModel = model || DEFAULT_ACPX_RUNNER_MODELS[acpxAgent] || ""; + try { + resolveQualifiedAcpxProfile(acpxAgent, selectedModel); + } catch (error) { throw new PaperclipRunnerProviderProfileError( "paperclip_runner_acpx_model_unqualified", - `Paperclip Runner ACPX ${acpxAgent} requires exact model ${qualifiedModel}.`, + error instanceof Error ? error.message : "Paperclip Runner ACPX requires an admitted model.", ); } return { provider: "acpx", backend: "acpx_runtime", - model: model || qualifiedModel, + model: selectedModel, acpxAgent, }; }