mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 20:46:28 +01:00
refactor(runner): generate shared ACPX release declarations
Consolidate runtime pins and explicit model admission policy across Rust, TypeScript, and packaging while preserving recovery identity. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e75fde6098
commit
fb25904052
20 files changed
+665
-320
No files matched your search
@@ -25,6 +25,29 @@ spend or enforceable per-run dollar accounting. Native AskQuestion is implemente
|
||||
defensively but is not advertised or certified. See the
|
||||
[Cursor capability contract](../architecture/runner-cursor-capabilities.md).
|
||||
|
||||
## Bounded declaration cleanup — 2026-10-06
|
||||
|
||||
The PR audit found duplicated release tuples in Rust descriptor validation,
|
||||
duplicated model rules in Rust session validation, and independent pins in the
|
||||
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.
|
||||
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.
|
||||
|
||||
All provider versions, digests, model acceptance rules, and recovery identity
|
||||
fields retain their previous meaning. A regression check pins the pre-cleanup
|
||||
Cursor recovery digest. Historical rejection fixtures and evidence keep their
|
||||
original identities. Cursor launch flags, credential names, and native extension
|
||||
translation remain provider-adapter responsibilities; generic mode, lifecycle,
|
||||
permission, and transport contracts retain the earlier cleanup.
|
||||
|
||||
This is a source cleanup after the live qualification recorded below. Its checks
|
||||
do not relabel the earlier package/image as a build of this source, and no further
|
||||
paid qualification or release is implied.
|
||||
|
||||
## Final installed-release qualification — 2026-10-06
|
||||
|
||||
This qualification follows the generic mode and provider lifecycle cleanup. Its
|
||||
|
||||
@@ -41,6 +41,24 @@ Legacy fresh attempts receive the same bounded handoff, including resume-failure
|
||||
fallbacks. Provider
|
||||
authentication repairs retain their existing fresh-session recovery behavior.
|
||||
|
||||
## ACPX release declarations
|
||||
|
||||
`acpx-profiles.json` owns the package versions, command/profile digests, model
|
||||
admission policies, and required execution policies used by TypeScript and Rust.
|
||||
After changing it, run `pnpm --filter @paperclipai/paperclip-runner generate:acpx-profiles`.
|
||||
Normal build and typecheck reject stale generated declarations. Generation also
|
||||
checks installed dependency pins and agreement with Cursor's distribution manifest
|
||||
and immutable release attestation (`cursor-contract.json`). Cursor's per-platform
|
||||
closure pins are generated from `cursor-distributions.json`.
|
||||
|
||||
Model admission is separate from qualification examples. `provider-verified`
|
||||
requires an explicit caller-selected model and native verification; `exact`
|
||||
retains the existing restriction for the Codex ACP and Pi profiles. Qualification
|
||||
examples are neither a model catalog nor defaults. The legacy snapshot field
|
||||
`qualificationModel` contains the selected model after resolution; its serialized
|
||||
name is retained to preserve recovery identities. Historical profile fixtures
|
||||
remain immutable evidence, not release declarations.
|
||||
|
||||
## Public package surfaces
|
||||
|
||||
- `@paperclipai/paperclip-runner` — production contracts, clients/backends,
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"schema": "paperclip.acpx-profiles.v1",
|
||||
"acpxVersion": "0.13.1",
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"profiles": {
|
||||
"grok": {
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "builtin:grok-acp",
|
||||
"agentServerVersion": "1",
|
||||
"agentRuntimePackage": "native:grok",
|
||||
"agentRuntimeVersion": "1.0.13",
|
||||
"commandDigest": "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
"modelAdmission": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"requiresProviderPolicy": false,
|
||||
"qualification": {
|
||||
"model": "grok-4.7"
|
||||
}
|
||||
},
|
||||
"pi": {
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "pi-acp",
|
||||
"agentServerVersion": "0.0.33",
|
||||
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
|
||||
"agentRuntimeVersion": "0.84.2",
|
||||
"commandDigest": "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
"modelAdmission": {
|
||||
"kind": "exact",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
"requiresProviderPolicy": true,
|
||||
"qualification": {
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
}
|
||||
},
|
||||
"cursor": {
|
||||
"agentProfileVersion": 11,
|
||||
"agentServerPackage": "cursor-agent",
|
||||
"agentServerVersion": "2026.09.26-dd393fe",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
"modelAdmission": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"requiresProviderPolicy": true,
|
||||
"qualification": {
|
||||
"model": ""
|
||||
}
|
||||
},
|
||||
"copilot": {
|
||||
"agentProfileVersion": 2,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
"modelAdmission": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"requiresProviderPolicy": true,
|
||||
"qualification": {
|
||||
"model": "",
|
||||
"status": "pending"
|
||||
}
|
||||
},
|
||||
"claude": {
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "@agentclientprotocol/claude-agent-acp",
|
||||
"agentServerVersion": "0.73.0",
|
||||
"agentRuntimePackage": "@anthropic-ai/claude-agent-sdk",
|
||||
"agentRuntimeVersion": "0.3.286",
|
||||
"commandDigest": "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
"modelAdmission": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"requiresProviderPolicy": false,
|
||||
"qualification": {
|
||||
"model": "claude-sonnet-5"
|
||||
}
|
||||
},
|
||||
"codex": {
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "@agentclientprotocol/codex-acp",
|
||||
"agentServerVersion": "1.6.2",
|
||||
"agentRuntimePackage": "@openai/codex",
|
||||
"agentRuntimeVersion": "0.160.0",
|
||||
"commandDigest": "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
"modelAdmission": {
|
||||
"kind": "exact",
|
||||
"model": "gpt-5.6-sol"
|
||||
},
|
||||
"requiresProviderPolicy": false,
|
||||
"qualification": {
|
||||
"model": "gpt-5.6-sol"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "pnpm run check:protocol-manifest && pnpm run build:typescript && pnpm run check:capability-contract && pnpm run check:capability-inventory && pnpm run check:protocol-coverage && pnpm run check:semantic-contracts && pnpm run check:runner-workflow-traceability && pnpm run build:binary && node scripts/generate-replay-goldens.mjs --check && node scripts/generate-semantic-action-catalog.mjs --check",
|
||||
"build:typescript": "pnpm run ensure:eval-build-deps && pnpm run check:protocol-types && node ./node_modules/typescript/bin/tsc --version && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node ./node_modules/typescript/bin/tsc -p tsconfig.surfaces.json && node scripts/build-verified-provider-entrypoints.mjs",
|
||||
"build:typescript": "pnpm run check:acpx-profiles && pnpm run ensure:eval-build-deps && pnpm run check:protocol-types && node ./node_modules/typescript/bin/tsc --version && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node ./node_modules/typescript/bin/tsc -p tsconfig.surfaces.json && node scripts/build-verified-provider-entrypoints.mjs",
|
||||
"build:rust": "cargo build --manifest-path runner/Cargo.toml --locked --workspace --bins",
|
||||
"build:binary": "cargo build --release --manifest-path runner/Cargo.toml --locked -p paperclip-runner-core --bin paperclip-runnerd && node scripts/stage-runner-binary.mjs",
|
||||
"stage:release-binaries": "node scripts/stage-release-runner-binaries.mjs",
|
||||
@@ -70,8 +70,8 @@
|
||||
"build:scenarios": "vite build --config vite.scenarios.config.ts && node scripts/check-capability-csp-bundle.mjs",
|
||||
"build:issue-thread": "vite build --config vite.issue-thread.config.ts",
|
||||
"typecheck": "pnpm run typecheck:typescript && pnpm run typecheck:rust",
|
||||
"typecheck:typescript": "pnpm run ensure:eval-build-deps && node --check scripts/protocol-contract.mjs && node --check scripts/generate-protocol-manifest.mjs && node --check scripts/generate-protocol-schema-module.mjs && node --check scripts/generate-acpx-sidecar-contract.mjs && node --check scripts/generate-replay-goldens.mjs && node --check scripts/generate-semantic-action-catalog.mjs && pnpm run check:protocol-types && node ./node_modules/typescript/bin/tsc --version && node ./node_modules/typescript/bin/tsc -p tsconfig.json --noEmit && node ./node_modules/typescript/bin/tsc -p tsconfig.surfaces.json --noEmit",
|
||||
"typecheck:rust": "cargo fmt --manifest-path runner/Cargo.toml --all -- --check && cargo check --manifest-path runner/Cargo.toml --locked --workspace",
|
||||
"typecheck:typescript": "pnpm run check:acpx-profiles && pnpm run ensure:eval-build-deps && node --check scripts/protocol-contract.mjs && node --check scripts/generate-protocol-manifest.mjs && node --check scripts/generate-protocol-schema-module.mjs && node --check scripts/generate-acpx-sidecar-contract.mjs && node --check scripts/generate-replay-goldens.mjs && node --check scripts/generate-semantic-action-catalog.mjs && pnpm run check:protocol-types && node ./node_modules/typescript/bin/tsc --version && node ./node_modules/typescript/bin/tsc -p tsconfig.json --noEmit && node ./node_modules/typescript/bin/tsc -p tsconfig.surfaces.json --noEmit",
|
||||
"typecheck:rust": "pnpm run check:acpx-profiles && cargo fmt --manifest-path runner/Cargo.toml --all -- --check && cargo check --manifest-path runner/Cargo.toml --locked --workspace",
|
||||
"typecheck:browser": "tsc -p tsconfig.browser.json --noEmit",
|
||||
"test": "pnpm run test:typescript && pnpm run test:rust",
|
||||
"test:typescript": "pnpm run test:typescript:prep && vitest run",
|
||||
@@ -161,7 +161,9 @@
|
||||
"browser:preview": "vite preview --config vite.config.ts",
|
||||
"verify": "pnpm run build && pnpm run typecheck && pnpm run check:replay-goldens && pnpm run test && pnpm run test:sdk && pnpm run test:scenarios && pnpm run check:browser-tokens && pnpm run test:browser && pnpm run test:browser:sdk && pnpm run test:browser:scenarios && pnpm run check:forbidden-imports && pnpm run check:tracked-imports && pnpm run check:numbered-milestones && pnpm run check:package-boundaries && pnpm run check:clean-consumers && pnpm run docs:validate && pnpm run check:conformance-parity && pnpm run check:replay-parity && pnpm run trace:conformance && pnpm run replay:fixture && pnpm run trace:local-runner -- --quiet",
|
||||
"verify:rootless": "bash scripts/verify-rootless-linux.sh",
|
||||
"test:opencode:qualification": "PAPERCLIP_OPENCODE_QUALIFY=1 node --test scripts/qualify-opencode-runtime.test.mjs"
|
||||
"test:opencode:qualification": "PAPERCLIP_OPENCODE_QUALIFY=1 node --test scripts/qualify-opencode-runtime.test.mjs",
|
||||
"generate:acpx-profiles": "node scripts/generate-acpx-profiles.mjs",
|
||||
"check:acpx-profiles": "node scripts/generate-acpx-profiles.mjs --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/claude-agent-acp": "0.73.0",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use crate::generated_acpx_profiles::{
|
||||
acpx_release_profile, ACPX_DRIVER_KIND, QUALIFIED_ACPX_VERSION,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::fs::{self, DirBuilder, File};
|
||||
use std::io::{Read, Write};
|
||||
@@ -185,67 +188,15 @@ impl AcpxProviderDescriptor {
|
||||
&self,
|
||||
context: &AcpxEventProjectionContext,
|
||||
) -> Result<(), DurableRunnerError> {
|
||||
let expected = match self.agent.as_str() {
|
||||
"claude" => (
|
||||
"claude-sonnet-5",
|
||||
"@agentclientprotocol/claude-agent-acp",
|
||||
"0.73.0",
|
||||
Some("@anthropic-ai/claude-agent-sdk"),
|
||||
Some("0.3.286"),
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
),
|
||||
"codex" => (
|
||||
"gpt-5.6-sol",
|
||||
"@agentclientprotocol/codex-acp",
|
||||
"1.6.2",
|
||||
Some("@openai/codex"),
|
||||
Some("0.160.0"),
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
),
|
||||
"pi" => (
|
||||
"openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
"pi-acp",
|
||||
"0.0.33",
|
||||
Some("@earendil-works/pi-coding-agent"),
|
||||
Some("0.84.2"),
|
||||
"sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
),
|
||||
"cursor" => (
|
||||
self.model.as_str(),
|
||||
"cursor-agent",
|
||||
"2026.09.26-dd393fe",
|
||||
None,
|
||||
None,
|
||||
"sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
),
|
||||
"copilot" => (
|
||||
self.model.as_str(),
|
||||
"@github/copilot",
|
||||
"1.0.88",
|
||||
None,
|
||||
None,
|
||||
"sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
),
|
||||
"grok" => (
|
||||
"grok-4.7",
|
||||
"builtin:grok-acp",
|
||||
"1",
|
||||
Some("native:grok"),
|
||||
Some("1.0.13"),
|
||||
"sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
),
|
||||
_ => {
|
||||
return Err(DurableRunnerError::invalid(
|
||||
"ACPX agent must name a known immutable profile",
|
||||
))
|
||||
}
|
||||
};
|
||||
let expected = acpx_release_profile(&self.agent).ok_or_else(|| {
|
||||
DurableRunnerError::invalid("ACPX agent must name a known immutable profile")
|
||||
})?;
|
||||
if self.kind != "acpx"
|
||||
|| self.provider != "acpx"
|
||||
|| self.driver != "acpx_runtime"
|
||||
|| self.provider_version != "0.13.1"
|
||||
|| self.acpx_version != "0.13.1"
|
||||
|| (self.agent != "claude" && self.agent != "grok" && self.model != expected.0)
|
||||
|| self.driver != ACPX_DRIVER_KIND
|
||||
|| self.provider_version != QUALIFIED_ACPX_VERSION
|
||||
|| self.acpx_version != QUALIFIED_ACPX_VERSION
|
||||
|| !expected.model_admission.accepts(&self.model)
|
||||
|| self.model.trim().is_empty()
|
||||
|| self.model.len() > 240
|
||||
|| self.model.contains('\0')
|
||||
@@ -254,13 +205,12 @@ impl AcpxProviderDescriptor {
|
||||
|| mode.chars().count() > 240
|
||||
|| mode.chars().any(char::is_control)
|
||||
})
|
||||
|| (matches!(self.agent.as_str(), "pi" | "cursor" | "copilot")
|
||||
&& self.provider_policy.is_none())
|
||||
|| self.agent_server_package != expected.1
|
||||
|| self.agent_server_version != expected.2
|
||||
|| self.agent_runtime_package.as_deref() != expected.3
|
||||
|| self.agent_runtime_version.as_deref() != expected.4
|
||||
|| self.command_digest != expected.5
|
||||
|| (expected.requires_provider_policy && self.provider_policy.is_none())
|
||||
|| self.agent_server_package != expected.agent_server_package
|
||||
|| self.agent_server_version != expected.agent_server_version
|
||||
|| self.agent_runtime_package.as_deref() != expected.agent_runtime_package
|
||||
|| self.agent_runtime_version.as_deref() != expected.agent_runtime_version
|
||||
|| self.command_digest != expected.command_digest
|
||||
{
|
||||
return Err(DurableRunnerError::invalid(
|
||||
"ACPX provider descriptor does not match a qualified immutable profile",
|
||||
@@ -2328,48 +2278,28 @@ mod tests {
|
||||
}
|
||||
|
||||
fn descriptor(agent: &str) -> Value {
|
||||
let (model, package, version, runtime_package, runtime_version, digest) =
|
||||
if agent == "claude" {
|
||||
(
|
||||
"claude-sonnet-5",
|
||||
"@agentclientprotocol/claude-agent-acp",
|
||||
"0.73.0",
|
||||
json!("@anthropic-ai/claude-agent-sdk"),
|
||||
json!("0.3.286"),
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
)
|
||||
} else if agent == "cursor" {
|
||||
(
|
||||
"gpt-5.6-luna[context=272k,reasoning=medium,fast=false]",
|
||||
"cursor-agent",
|
||||
"2026.09.26-dd393fe",
|
||||
Value::Null,
|
||||
Value::Null,
|
||||
"sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"gpt-5.6-sol",
|
||||
"@agentclientprotocol/codex-acp",
|
||||
"1.6.2",
|
||||
json!("@openai/codex"),
|
||||
json!("0.160.0"),
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
)
|
||||
};
|
||||
let profile = acpx_release_profile(agent).unwrap();
|
||||
let model = match agent {
|
||||
"claude" => "claude-sonnet-5",
|
||||
"cursor" => "gpt-5.6-luna[context=272k,reasoning=medium,fast=false]",
|
||||
"pi" => "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
"grok" => "grok-4.7",
|
||||
"copilot" => "explicit-test-model",
|
||||
_ => "gpt-5.6-sol",
|
||||
};
|
||||
let mut value = json!({
|
||||
"kind": "acpx",
|
||||
"provider": "acpx",
|
||||
"driver": "acpx_runtime",
|
||||
"providerVersion": "0.13.1",
|
||||
"providerVersion": QUALIFIED_ACPX_VERSION,
|
||||
"agent": agent,
|
||||
"model": model,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerPackage": package,
|
||||
"agentServerVersion": version,
|
||||
"agentRuntimePackage": runtime_package,
|
||||
"agentRuntimeVersion": runtime_version,
|
||||
"commandDigest": digest,
|
||||
"acpxVersion": QUALIFIED_ACPX_VERSION,
|
||||
"agentServerPackage": profile.agent_server_package,
|
||||
"agentServerVersion": profile.agent_server_version,
|
||||
"agentRuntimePackage": profile.agent_runtime_package,
|
||||
"agentRuntimeVersion": profile.agent_runtime_version,
|
||||
"commandDigest": profile.command_digest,
|
||||
"sidecarCommand": "/qualified/node",
|
||||
"sidecarArgs": ["/qualified/acpx-sidecar.js"],
|
||||
"runtimeDirectory": "/runtime/acpx",
|
||||
@@ -2381,9 +2311,11 @@ mod tests {
|
||||
"permissionModePinned": true,
|
||||
"runtimeContext": null,
|
||||
});
|
||||
if profile.requires_provider_policy {
|
||||
value["providerPolicy"] = json!({"readOnly": true});
|
||||
}
|
||||
if agent == "cursor" {
|
||||
value["mode"] = json!("agent");
|
||||
value["providerPolicy"] = json!({"readOnly": true});
|
||||
}
|
||||
value
|
||||
}
|
||||
@@ -2822,50 +2754,50 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admits_candidate_descriptors_only_with_explicit_policy_and_exact_distribution() {
|
||||
for (agent, package, version, digest, runtime_package, runtime_version, model) in [
|
||||
(
|
||||
"cursor",
|
||||
"cursor-agent",
|
||||
"2026.09.26-dd393fe",
|
||||
"sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
None,
|
||||
None,
|
||||
"explicit-model",
|
||||
),
|
||||
(
|
||||
"copilot",
|
||||
"@github/copilot",
|
||||
"1.0.88",
|
||||
"sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
None,
|
||||
None,
|
||||
"explicit-model",
|
||||
),
|
||||
(
|
||||
"pi",
|
||||
"pi-acp",
|
||||
"0.0.33",
|
||||
"sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
Some("@earendil-works/pi-coding-agent"),
|
||||
Some("0.84.2"),
|
||||
"openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
),
|
||||
] {
|
||||
let mut value = descriptor("codex");
|
||||
value["agent"] = json!(agent);
|
||||
value["model"] = json!(model);
|
||||
value["agentServerPackage"] = json!(package);
|
||||
value["agentServerVersion"] = json!(version);
|
||||
value["agentRuntimePackage"] = json!(runtime_package);
|
||||
value["agentRuntimeVersion"] = json!(runtime_version);
|
||||
value["commandDigest"] = json!(digest);
|
||||
let missing: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap();
|
||||
assert!(missing.validate(&context()).is_err());
|
||||
value["providerPolicy"] = json!({"readOnly":true});
|
||||
if agent == "cursor" {
|
||||
value["mode"] = json!("agent");
|
||||
fn model_admission_is_independent_of_qualification_examples() {
|
||||
for agent in ["claude", "grok", "cursor", "copilot", "codex", "pi"] {
|
||||
let original: AcpxProviderDescriptor =
|
||||
serde_json::from_value(descriptor(agent)).unwrap();
|
||||
original.validate_session(&context()).unwrap();
|
||||
let mut alternative = descriptor(agent);
|
||||
alternative["model"] = json!("explicit-model-not-in-a-catalog");
|
||||
let alternative: AcpxProviderDescriptor = serde_json::from_value(alternative).unwrap();
|
||||
assert_eq!(
|
||||
alternative.validate_session(&context()).is_ok(),
|
||||
!matches!(agent, "codex" | "pi")
|
||||
);
|
||||
for field in [
|
||||
"agentServerPackage",
|
||||
"agentServerVersion",
|
||||
"agentRuntimePackage",
|
||||
"agentRuntimeVersion",
|
||||
"commandDigest",
|
||||
"acpxVersion",
|
||||
"providerVersion",
|
||||
] {
|
||||
let mut tampered = descriptor(agent);
|
||||
tampered[field] = json!("untrusted");
|
||||
let tampered: AcpxProviderDescriptor = serde_json::from_value(tampered).unwrap();
|
||||
assert!(
|
||||
tampered.validate_session(&context()).is_err(),
|
||||
"{agent}: {field}"
|
||||
);
|
||||
}
|
||||
let mut empty = descriptor(agent);
|
||||
empty["model"] = json!(" ");
|
||||
let empty: AcpxProviderDescriptor = serde_json::from_value(empty).unwrap();
|
||||
assert!(empty.validate_session(&context()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admits_candidate_descriptors_only_with_explicit_policy_and_exact_distribution() {
|
||||
for agent in ["cursor", "copilot", "pi"] {
|
||||
let mut value = descriptor(agent);
|
||||
let mut missing = value.clone();
|
||||
missing.as_object_mut().unwrap().remove("providerPolicy");
|
||||
let missing: AcpxProviderDescriptor = serde_json::from_value(missing).unwrap();
|
||||
assert!(missing.validate(&context()).is_err());
|
||||
let valid: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap();
|
||||
valid.validate(&context()).unwrap();
|
||||
if matches!(agent, "cursor" | "copilot") {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::generated_acpx_profiles::{acpx_release_profile, ModelAdmission};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -83,31 +84,22 @@ pub struct AcpxProviderSessionConfig {
|
||||
impl AcpxProviderSessionConfig {
|
||||
pub fn validate(&self) -> Result<(), LocalRunnerError> {
|
||||
self.transport.validate()?;
|
||||
let qualified_model = match self.agent.as_str() {
|
||||
"claude" => "claude-sonnet-5",
|
||||
"grok" => "grok-4.7",
|
||||
"codex" => "gpt-5.6-sol",
|
||||
"pi" => "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
"cursor" | "copilot" => self.model.as_str(),
|
||||
_ => {
|
||||
return Err(LocalRunnerError::invalid(
|
||||
"ACPX agent must name a known immutable profile",
|
||||
))
|
||||
let profile = acpx_release_profile(&self.agent).ok_or_else(|| {
|
||||
LocalRunnerError::invalid("ACPX agent must name a known immutable profile")
|
||||
})?;
|
||||
if let ModelAdmission::Exact(qualified_model) = profile.model_admission {
|
||||
if self.model != qualified_model {
|
||||
return Err(LocalRunnerError::invalid(format!(
|
||||
"ACPX {} profile requires exact model {qualified_model}",
|
||||
self.agent
|
||||
)));
|
||||
}
|
||||
};
|
||||
if self.agent != "claude" && self.agent != "grok" && self.model != qualified_model {
|
||||
return Err(LocalRunnerError::invalid(format!(
|
||||
"ACPX {} profile requires exact model {qualified_model}",
|
||||
self.agent
|
||||
)));
|
||||
}
|
||||
validate_text(&self.model, MAX_MODEL_CHARS, "ACPX model")?;
|
||||
if let Some(mode) = self.mode.as_deref() {
|
||||
validate_text(mode, MAX_ID_CHARS, "ACPX provider mode")?;
|
||||
}
|
||||
if matches!(self.agent.as_str(), "pi" | "cursor" | "copilot")
|
||||
&& self.provider_policy.is_none()
|
||||
{
|
||||
if profile.requires_provider_policy && self.provider_policy.is_none() {
|
||||
return Err(LocalRunnerError::invalid(
|
||||
"ACPX candidate requires explicit provider read-only policy",
|
||||
));
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Generated from acpx-profiles.json and cursor-distributions.json. Do not edit.
|
||||
pub(crate) const QUALIFIED_ACPX_VERSION: &str = "0.13.1";
|
||||
pub(crate) const ACPX_DRIVER_KIND: &str = "acpx_runtime";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ModelAdmission {
|
||||
Exact(&'static str),
|
||||
ProviderVerified,
|
||||
}
|
||||
|
||||
impl ModelAdmission {
|
||||
pub(crate) fn accepts(&self, model: &str) -> bool {
|
||||
match self {
|
||||
Self::Exact(expected) => model == *expected,
|
||||
Self::ProviderVerified => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AcpxReleaseProfile {
|
||||
pub agent_server_package: &'static str,
|
||||
pub agent_server_version: &'static str,
|
||||
pub agent_runtime_package: Option<&'static str>,
|
||||
pub agent_runtime_version: Option<&'static str>,
|
||||
pub command_digest: &'static str,
|
||||
pub model_admission: ModelAdmission,
|
||||
pub requires_provider_policy: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
Some(match agent {
|
||||
"grok" => AcpxReleaseProfile {
|
||||
agent_server_package: "builtin:grok-acp",
|
||||
agent_server_version: "1",
|
||||
agent_runtime_package: Some("native:grok"),
|
||||
agent_runtime_version: Some("1.0.13"),
|
||||
command_digest:
|
||||
"sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
model_admission: ModelAdmission::ProviderVerified,
|
||||
requires_provider_policy: false,
|
||||
},
|
||||
"pi" => AcpxReleaseProfile {
|
||||
agent_server_package: "pi-acp",
|
||||
agent_server_version: "0.0.33",
|
||||
agent_runtime_package: Some("@earendil-works/pi-coding-agent"),
|
||||
agent_runtime_version: Some("0.84.2"),
|
||||
command_digest:
|
||||
"sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
model_admission: ModelAdmission::Exact("openrouter/deepseek/deepseek-v4-flash-0731"),
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"cursor" => AcpxReleaseProfile {
|
||||
agent_server_package: "cursor-agent",
|
||||
agent_server_version: "2026.09.26-dd393fe",
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
model_admission: ModelAdmission::ProviderVerified,
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"copilot" => AcpxReleaseProfile {
|
||||
agent_server_package: "@github/copilot",
|
||||
agent_server_version: "1.0.88",
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
model_admission: ModelAdmission::ProviderVerified,
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"claude" => AcpxReleaseProfile {
|
||||
agent_server_package: "@agentclientprotocol/claude-agent-acp",
|
||||
agent_server_version: "0.73.0",
|
||||
agent_runtime_package: Some("@anthropic-ai/claude-agent-sdk"),
|
||||
agent_runtime_version: Some("0.3.286"),
|
||||
command_digest:
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
model_admission: ModelAdmission::ProviderVerified,
|
||||
requires_provider_policy: false,
|
||||
},
|
||||
"codex" => AcpxReleaseProfile {
|
||||
agent_server_package: "@agentclientprotocol/codex-acp",
|
||||
agent_server_version: "1.6.2",
|
||||
agent_runtime_package: Some("@openai/codex"),
|
||||
agent_runtime_version: Some("0.160.0"),
|
||||
command_digest:
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
model_admission: ModelAdmission::Exact("gpt-5.6-sol"),
|
||||
requires_provider_policy: false,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,7 @@ pub mod codex_provider;
|
||||
mod codex_startup_trust;
|
||||
pub mod durable;
|
||||
pub mod fake_harness;
|
||||
mod generated_acpx_profiles;
|
||||
pub mod generated_acpx_sidecar_contract;
|
||||
pub mod local_runner;
|
||||
pub mod managed_provider;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import profiles from "../acpx-profiles.json" with { type: "json" };
|
||||
import { createHash } from "node:crypto";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import {
|
||||
@@ -310,12 +311,12 @@ try {
|
||||
const payload = {
|
||||
pins: {
|
||||
nodeMinimum: minimumNodeVersion.join("."),
|
||||
codex: "0.160.0",
|
||||
codex: profiles.profiles.codex.agentRuntimeVersion,
|
||||
opencode: "1.18.34",
|
||||
acpx: "0.13.1",
|
||||
grok: "1.0.13",
|
||||
claudeAcp: "0.73.0",
|
||||
codexAcp: "1.6.2",
|
||||
acpx: profiles.acpxVersion,
|
||||
grok: profiles.profiles.grok.agentRuntimeVersion,
|
||||
claudeAcp: profiles.profiles.claude.agentServerVersion,
|
||||
codexAcp: profiles.profiles.codex.agentServerVersion,
|
||||
},
|
||||
target: { platform: process.platform, architecture: process.arch },
|
||||
runnerSourceRevision: `${revision}${dirty ? "-dirty" : ""}`,
|
||||
@@ -328,11 +329,11 @@ try {
|
||||
.update(distDigest)
|
||||
.digest("hex")}`,
|
||||
acpxProfileDigests: {
|
||||
grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
grok: profiles.profiles.grok.commandDigest,
|
||||
claude:
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
profiles.profiles.claude.commandDigest,
|
||||
codex:
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
profiles.profiles.codex.commandDigest,
|
||||
},
|
||||
...providerPackManifestFields(candidateProviders, candidates),
|
||||
artifacts: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import profiles from "../acpx-profiles.json" with { type: "json" };
|
||||
import { materializePinnedCursorDistribution } from "./materialize-cursor-distribution.mjs";
|
||||
const CANDIDATES = new Set(["cursor", "copilot", "pi"]);
|
||||
|
||||
@@ -35,7 +36,7 @@ export async function materializeCandidateProviderPack({ provider, outputRoot })
|
||||
if (provider === "cursor") {
|
||||
const result = await materializePinnedCursorDistribution({ destination: outputRoot });
|
||||
return { version: result.version,
|
||||
profileDigest: "sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
profileDigest: profiles.profiles.cursor.commandDigest,
|
||||
closureDigest: `sha256:${result.closureSha256}` };
|
||||
}
|
||||
throw new Error(`The ${provider} candidate distribution builder is not included in this source revision`);
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = new URL("../", import.meta.url);
|
||||
const readJson = async path => JSON.parse(await readFile(new URL(path, root), "utf8"));
|
||||
const manifest = await readJson("acpx-profiles.json");
|
||||
const distributions = await readJson("cursor-distributions.json");
|
||||
const contract = await readJson("cursor-contract.json");
|
||||
const pkg = await readJson("package.json");
|
||||
assert.equal(manifest.schema, "paperclip.acpx-profiles.v1");
|
||||
assert.equal(manifest.acpxVersion, pkg.dependencies.acpx);
|
||||
const entries = Object.entries(manifest.profiles);
|
||||
for (const [agent, profile] of entries) {
|
||||
assert.match(agent, /^[a-z]+$/);
|
||||
assert.ok(Number.isSafeInteger(profile.agentProfileVersion) && profile.agentProfileVersion > 0);
|
||||
for (const key of ["agentServerPackage", "agentServerVersion"]) assert.ok(typeof profile[key] === "string" && profile[key].length > 0);
|
||||
assert.equal(profile.agentRuntimePackage === null, profile.agentRuntimeVersion === null);
|
||||
assert.match(profile.commandDigest, /^sha256:[a-f0-9]{64}$/);
|
||||
assert.equal(typeof profile.requiresProviderPolicy, "boolean");
|
||||
assert.equal(typeof profile.qualification.model, "string");
|
||||
assert.ok([undefined, "pending"].includes(profile.qualification.status));
|
||||
assert.ok(["exact", "provider-verified"].includes(profile.modelAdmission.kind));
|
||||
if (profile.modelAdmission.kind === "exact") assert.ok(profile.modelAdmission.model?.trim());
|
||||
else assert.equal(profile.modelAdmission.model, undefined);
|
||||
for (const [name, version] of [[profile.agentServerPackage, profile.agentServerVersion], [profile.agentRuntimePackage, profile.agentRuntimeVersion]]) {
|
||||
if (name && Object.hasOwn(pkg.dependencies, name)) assert.equal(version, pkg.dependencies[name], `${agent}: installed dependency drift`);
|
||||
}
|
||||
}
|
||||
// The Cursor contract is an immutable release attestation, not another runtime
|
||||
// lookup table. Refuse generation if a release declaration disagrees with it.
|
||||
assert.equal(manifest.profiles.cursor.agentServerVersion, distributions.version);
|
||||
assert.equal(manifest.profiles.cursor.agentServerVersion, contract.vendor);
|
||||
assert.equal(manifest.profiles.cursor.agentProfileVersion, contract.profileVersion);
|
||||
assert.equal(manifest.profiles.cursor.commandDigest, contract.commandDigest);
|
||||
assert.equal(distributions.patchVersion, contract.nativePatch);
|
||||
for (const distribution of Object.values(distributions.platforms)) assert.match(distribution.closureSha256, /^[a-f0-9]{64}$/);
|
||||
|
||||
const quote = JSON.stringify;
|
||||
const profiles = Object.fromEntries(entries.map(([agent, declaration]) => {
|
||||
const { modelAdmission, requiresProviderPolicy, qualification, ...runtime } = declaration;
|
||||
return [agent, {
|
||||
driverKind: manifest.driverKind, protocolVersion: manifest.protocolVersion,
|
||||
acpxVersion: manifest.acpxVersion, agent, ...runtime,
|
||||
...(qualification.status ? { qualificationStatus: qualification.status } : {}),
|
||||
qualificationModel: qualification.model, reportedModelId: qualification.model,
|
||||
permissionPolicy: "interactive",
|
||||
}];
|
||||
}));
|
||||
const policies = Object.fromEntries(entries.map(([agent, profile]) => [agent, profile.modelAdmission]));
|
||||
const header = "Generated from acpx-profiles.json and cursor-distributions.json. Do not edit.";
|
||||
const typescript = `// ${header}
|
||||
export const QUALIFIED_ACPX_VERSION = ${quote(manifest.acpxVersion)} as const;
|
||||
export const ACPX_DRIVER_KIND = ${quote(manifest.driverKind)} as const;
|
||||
export const ACPX_DRIVER_PROTOCOL_VERSION = ${manifest.protocolVersion} as const;
|
||||
|
||||
export const QUALIFIED_ACPX_PROFILE_DATA = ${JSON.stringify(profiles, null, 2)} as const;
|
||||
|
||||
export const ACPX_MODEL_ADMISSION = ${JSON.stringify(policies, null, 2)} as const;
|
||||
|
||||
export const CURSOR_DISTRIBUTION_PINS = ${JSON.stringify(Object.fromEntries(Object.entries(distributions.platforms).map(([platform, { closureSha256, executable, entrypoint }]) => [platform, { closureSha256, executable, entrypoint }])), null, 2)} as const;
|
||||
`;
|
||||
const option = value => value === null ? "None" : `Some(${quote(value)})`;
|
||||
const rust = `// ${header}
|
||||
pub(crate) const QUALIFIED_ACPX_VERSION: &str = ${quote(manifest.acpxVersion)};
|
||||
pub(crate) const ACPX_DRIVER_KIND: &str = ${quote(manifest.driverKind)};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ModelAdmission {
|
||||
Exact(&'static str),
|
||||
ProviderVerified,
|
||||
}
|
||||
|
||||
impl ModelAdmission {
|
||||
pub(crate) fn accepts(&self, model: &str) -> bool {
|
||||
match self {
|
||||
Self::Exact(expected) => model == *expected,
|
||||
Self::ProviderVerified => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AcpxReleaseProfile {
|
||||
pub agent_server_package: &'static str,
|
||||
pub agent_server_version: &'static str,
|
||||
pub agent_runtime_package: Option<&'static str>,
|
||||
pub agent_runtime_version: Option<&'static str>,
|
||||
pub command_digest: &'static str,
|
||||
pub model_admission: ModelAdmission,
|
||||
pub requires_provider_policy: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
Some(match agent {
|
||||
${entries.map(([agent, p]) => ` ${quote(agent)} => AcpxReleaseProfile {
|
||||
agent_server_package: ${quote(p.agentServerPackage)},
|
||||
agent_server_version: ${quote(p.agentServerVersion)},
|
||||
agent_runtime_package: ${option(p.agentRuntimePackage)},
|
||||
agent_runtime_version: ${option(p.agentRuntimeVersion)},
|
||||
command_digest:
|
||||
${quote(p.commandDigest)},
|
||||
model_admission: ${p.modelAdmission.kind === "exact" ? `ModelAdmission::Exact(${quote(p.modelAdmission.model)})` : "ModelAdmission::ProviderVerified"},
|
||||
requires_provider_policy: ${p.requiresProviderPolicy},
|
||||
},`).join("\n")}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
`;
|
||||
|
||||
for (const [path, content] of [
|
||||
["src/drivers/acpx/generated-profiles.ts", typescript],
|
||||
["runner/crates/runner-core/src/generated_acpx_profiles.rs", rust],
|
||||
]) {
|
||||
const url = new URL(path, root);
|
||||
if (process.argv.includes("--check")) {
|
||||
assert.equal(await readFile(url, "utf8"), content, `Stale ACPX profiles: ${fileURLToPath(url)}; run generate:acpx-profiles`);
|
||||
} else await writeFile(url, content);
|
||||
}
|
||||
console.log("TypeScript/Rust ACPX profiles match the release manifest.");
|
||||
@@ -1,3 +1,4 @@
|
||||
import { QUALIFIED_ACPX_VERSION } from "../drivers/acpx/generated-profiles.js";
|
||||
import { isSupportedAcpxProfileVersion, type AcpxProfileVersion } from "../drivers/acpx/profile-compatibility.js";
|
||||
import { isProviderMode } from "./provider-mode.js";
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -90,7 +91,7 @@ export type NativeAcpxPermissionMode = "approve-all" | "approve-paperclip" | "ap
|
||||
export interface NativeAcpxProfileSnapshot {
|
||||
driverKind: "acpx_runtime";
|
||||
protocolVersion: 1;
|
||||
acpxVersion: "0.13.1";
|
||||
acpxVersion: typeof QUALIFIED_ACPX_VERSION;
|
||||
agent: NativeAcpxAgent;
|
||||
agentProfileVersion: AcpxProfileVersion;
|
||||
agentServerPackage: string;
|
||||
@@ -606,7 +607,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
|
||||
if (
|
||||
profile.driverKind !== "acpx_runtime"
|
||||
|| profile.protocolVersion !== 1
|
||||
|| profile.acpxVersion !== "0.13.1"
|
||||
|| profile.acpxVersion !== QUALIFIED_ACPX_VERSION
|
||||
|| profile.agent !== provider.agent
|
||||
|| !isSupportedAcpxProfileVersion(provider.agent, profile.agentProfileVersion)
|
||||
) {
|
||||
@@ -628,7 +629,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
|
||||
profile: {
|
||||
driverKind: "acpx_runtime",
|
||||
protocolVersion: 1,
|
||||
acpxVersion: "0.13.1",
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION,
|
||||
agent: provider.agent,
|
||||
agentProfileVersion: profile.agentProfileVersion,
|
||||
agentServerPackage: text(profile.agentServerPackage, "input.provider.profile.agentServerPackage"),
|
||||
|
||||
@@ -2,16 +2,12 @@ import { join } from "node:path";
|
||||
|
||||
import { verifyNativeAcpxInstallation, type VerifiedAcpxInstallation } from "./installation-integrity.js";
|
||||
import type { NativeAcpxDistributionInput } from "./native-distribution-integrity.js";
|
||||
import { CURSOR_FIXED_ARGUMENTS } from "./cursor-launch-policy.js";
|
||||
import { CURSOR_PINNED_VERSION, CURSOR_FIXED_ARGUMENTS } from "./cursor-launch-policy.js";
|
||||
import { QUALIFIED_ACPX_PROFILES, type QualifiedAcpxProfile } from "./qualified-profiles.js";
|
||||
import { resolveRunnerProviderAssetsRoot } from "./provider-assets-root.js";
|
||||
|
||||
export const CURSOR_PINNED_VERSION = "2026.09.26-dd393fe";
|
||||
const CLOSURE_PINS = Object.freeze({
|
||||
"darwin-arm64": "257424bd48e35412091c6adfc61e4648e836757ec1d240d890bba81a24918c30",
|
||||
"darwin-x64": "6f28c799c5afdc64fbdff8a2157f565f17ae7615efe014ac389d63bf70cf2be2",
|
||||
"linux-x64": "eadb8bb8ffb0450455b15b88c9b230307a9e149958a0c452d16dd157b4633d74",
|
||||
});
|
||||
import { CURSOR_DISTRIBUTION_PINS } from "./generated-profiles.js";
|
||||
export { CURSOR_PINNED_VERSION } from "./cursor-launch-policy.js";
|
||||
|
||||
/** Trusted package assets only: no workspace, PATH, executable override or env root. */
|
||||
export function cursorNativeDistributionSpec(
|
||||
@@ -19,14 +15,15 @@ export function cursorNativeDistributionSpec(
|
||||
architecture: string = process.arch,
|
||||
): NativeAcpxDistributionInput {
|
||||
const key = `${platform}-${architecture}`;
|
||||
if (!Object.hasOwn(CLOSURE_PINS, key)) throw new Error(`Cursor ${CURSOR_PINNED_VERSION} has no pinned distribution for ${key}`);
|
||||
if (!Object.hasOwn(CURSOR_DISTRIBUTION_PINS, key)) throw new Error(`Cursor ${CURSOR_PINNED_VERSION} has no pinned distribution for ${key}`);
|
||||
const distribution = CURSOR_DISTRIBUTION_PINS[key as keyof typeof CURSOR_DISTRIBUTION_PINS];
|
||||
const distributionRoot = join(resolveRunnerProviderAssetsRoot(import.meta.url, "cursor"), key);
|
||||
return {
|
||||
distributionRoot,
|
||||
manifestPath: join(distributionRoot, ".paperclip-cursor-closure.json"),
|
||||
expectedClosureSha256: CLOSURE_PINS[key as keyof typeof CLOSURE_PINS],
|
||||
executable: "node",
|
||||
entrypoint: "index.js",
|
||||
expectedClosureSha256: distribution.closureSha256,
|
||||
executable: distribution.executable,
|
||||
entrypoint: distribution.entrypoint,
|
||||
fixedArguments: [...CURSOR_FIXED_ARGUMENTS],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { lstat, readFile, realpath } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
export const CURSOR_PINNED_VERSION = "2026.09.26-dd393fe";
|
||||
import { QUALIFIED_ACPX_PROFILES } from "./qualified-profiles.js";
|
||||
|
||||
export const CURSOR_PINNED_VERSION = QUALIFIED_ACPX_PROFILES.cursor.agentServerVersion;
|
||||
export const CURSOR_FIXED_ARGUMENTS = ["--disable-project-configs", "--disable-auto-update", "acp"] as const;
|
||||
export const CURSOR_CREDENTIAL_ENVIRONMENT_NAMES = ["CURSOR_API_KEY", "CURSOR_AUTH_TOKEN"] as const;
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Generated from acpx-profiles.json and cursor-distributions.json. Do not edit.
|
||||
export const QUALIFIED_ACPX_VERSION = "0.13.1" as const;
|
||||
export const ACPX_DRIVER_KIND = "acpx_runtime" as const;
|
||||
export const ACPX_DRIVER_PROTOCOL_VERSION = 1 as const;
|
||||
|
||||
export const QUALIFIED_ACPX_PROFILE_DATA = {
|
||||
"grok": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "grok",
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "builtin:grok-acp",
|
||||
"agentServerVersion": "1",
|
||||
"agentRuntimePackage": "native:grok",
|
||||
"agentRuntimeVersion": "1.0.13",
|
||||
"commandDigest": "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
"qualificationModel": "grok-4.7",
|
||||
"reportedModelId": "grok-4.7",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"pi": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "pi",
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "pi-acp",
|
||||
"agentServerVersion": "0.0.33",
|
||||
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
|
||||
"agentRuntimeVersion": "0.84.2",
|
||||
"commandDigest": "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
"qualificationModel": "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
"reportedModelId": "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"cursor": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "cursor",
|
||||
"agentProfileVersion": 11,
|
||||
"agentServerPackage": "cursor-agent",
|
||||
"agentServerVersion": "2026.09.26-dd393fe",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
"qualificationModel": "",
|
||||
"reportedModelId": "",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"copilot": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 2,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
"qualificationStatus": "pending",
|
||||
"qualificationModel": "",
|
||||
"reportedModelId": "",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"claude": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "claude",
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "@agentclientprotocol/claude-agent-acp",
|
||||
"agentServerVersion": "0.73.0",
|
||||
"agentRuntimePackage": "@anthropic-ai/claude-agent-sdk",
|
||||
"agentRuntimeVersion": "0.3.286",
|
||||
"commandDigest": "sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
"qualificationModel": "claude-sonnet-5",
|
||||
"reportedModelId": "claude-sonnet-5",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"codex": {
|
||||
"driverKind": "acpx_runtime",
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "codex",
|
||||
"agentProfileVersion": 1,
|
||||
"agentServerPackage": "@agentclientprotocol/codex-acp",
|
||||
"agentServerVersion": "1.6.2",
|
||||
"agentRuntimePackage": "@openai/codex",
|
||||
"agentRuntimeVersion": "0.160.0",
|
||||
"commandDigest": "sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
"qualificationModel": "gpt-5.6-sol",
|
||||
"reportedModelId": "gpt-5.6-sol",
|
||||
"permissionPolicy": "interactive"
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ACPX_MODEL_ADMISSION = {
|
||||
"grok": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"pi": {
|
||||
"kind": "exact",
|
||||
"model": "openrouter/deepseek/deepseek-v4-flash-0731"
|
||||
},
|
||||
"cursor": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"copilot": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"claude": {
|
||||
"kind": "provider-verified"
|
||||
},
|
||||
"codex": {
|
||||
"kind": "exact",
|
||||
"model": "gpt-5.6-sol"
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const CURSOR_DISTRIBUTION_PINS = {
|
||||
"darwin-arm64": {
|
||||
"closureSha256": "257424bd48e35412091c6adfc61e4648e836757ec1d240d890bba81a24918c30",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js"
|
||||
},
|
||||
"darwin-x64": {
|
||||
"closureSha256": "6f28c799c5afdc64fbdff8a2157f565f17ae7615efe014ac389d63bf70cf2be2",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js"
|
||||
},
|
||||
"linux-x64": {
|
||||
"closureSha256": "eadb8bb8ffb0450455b15b88c9b230307a9e149958a0c452d16dd157b4633d74",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js"
|
||||
}
|
||||
} as const;
|
||||
@@ -18,19 +18,24 @@ describe("qualified ACPX profiles", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["claude-opus-5-5", "claude-fable-5-1", "custom-model-not-in-catalog"])("accepts the exact Claude model %s", (model) => {
|
||||
expect(resolveQualifiedAcpxProfile("claude", model)).toMatchObject({
|
||||
it.each(["claude", "grok", "cursor", "copilot"] as const)("lets %s verify an explicit model absent from the catalog", (agent) => {
|
||||
const model = "custom-model-not-in-catalog";
|
||||
expect(resolveQualifiedAcpxProfile(agent, model)).toMatchObject({
|
||||
qualificationModel: model, reportedModelId: model,
|
||||
commandDigest: QUALIFIED_ACPX_PROFILES.claude.commandDigest,
|
||||
commandDigest: QUALIFIED_ACPX_PROFILES[agent].commandDigest,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unqualified model substitutions", () => {
|
||||
it.each(["codex", "pi"] as const)("preserves the existing exact-model restriction for %s", (agent) => {
|
||||
expect(() =>
|
||||
resolveQualifiedAcpxProfile("codex", "some-other-model"),
|
||||
resolveQualifiedAcpxProfile(agent, "some-other-model"),
|
||||
).toThrow("requires exact model");
|
||||
});
|
||||
|
||||
it.each(["claude", "codex", "grok", "cursor", "copilot", "pi"] as const)("never selects a qualification model by default for %s", (agent) => {
|
||||
expect(() => resolveQualifiedAcpxProfile(agent, " ")).toThrow("must not be empty");
|
||||
});
|
||||
|
||||
it("binds Codex ACP to the CLI runtime it launches", () => {
|
||||
expect(QUALIFIED_ACPX_PROFILES.codex).toMatchObject({
|
||||
agentRuntimePackage: "@openai/codex",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const QUALIFIED_ACPX_VERSION = "0.13.1" as const;
|
||||
export const ACPX_DRIVER_KIND = "acpx_runtime" as const;
|
||||
export const ACPX_DRIVER_PROTOCOL_VERSION = 1 as const;
|
||||
import { ACPX_DRIVER_KIND, ACPX_DRIVER_PROTOCOL_VERSION, QUALIFIED_ACPX_VERSION, QUALIFIED_ACPX_PROFILE_DATA, ACPX_MODEL_ADMISSION } from "./generated-profiles.js";
|
||||
export { ACPX_DRIVER_KIND, ACPX_DRIVER_PROTOCOL_VERSION, QUALIFIED_ACPX_VERSION } from "./generated-profiles.js";
|
||||
|
||||
import type { NativeAcpxAgent, NativeAcpxProfileSnapshot } from "../../contracts/native-execution.js";
|
||||
|
||||
@@ -13,13 +12,15 @@ export interface QualifiedAcpxProfile {
|
||||
readonly agent: QualifiedAcpxAgent;
|
||||
readonly agentProfileVersion: NativeAcpxProfileSnapshot["agentProfileVersion"];
|
||||
readonly qualificationStatus?: "pending";
|
||||
readonly modelPolicy?: "explicit-provider-verified";
|
||||
/** Wire identity: an npm package name or a runner-owned builtin: identifier. */
|
||||
readonly agentServerPackage: string;
|
||||
readonly agentServerVersion: string;
|
||||
readonly agentRuntimePackage: string | null;
|
||||
readonly agentRuntimeVersion: string | null;
|
||||
readonly commandDigest: string;
|
||||
/** Legacy snapshot field: after resolution this is the caller's selected model.
|
||||
* Keep its serialized name for recovery identity compatibility. The unresolved
|
||||
* value is qualification metadata, never a product default or admission rule. */
|
||||
readonly qualificationModel: string;
|
||||
/** Exact model ID sent to ACP; catalogs are suggestions, not an allowlist. */
|
||||
readonly reportedModelId: string;
|
||||
@@ -33,86 +34,7 @@ export interface QualifiedAcpxProfile {
|
||||
*/
|
||||
export const QUALIFIED_ACPX_PROFILES: Readonly<
|
||||
Record<QualifiedAcpxAgent, QualifiedAcpxProfile>
|
||||
> = deepFreeze({
|
||||
grok: {
|
||||
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "grok", agentProfileVersion: 1,
|
||||
agentServerPackage: "builtin:grok-acp", agentServerVersion: "1",
|
||||
agentRuntimePackage: "native:grok", agentRuntimeVersion: "1.0.13",
|
||||
commandDigest: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
qualificationModel: "grok-4.7", reportedModelId: "grok-4.7", permissionPolicy: "interactive",
|
||||
},
|
||||
pi: {
|
||||
driverKind: ACPX_DRIVER_KIND,
|
||||
protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION,
|
||||
agent: "pi",
|
||||
agentProfileVersion: 1,
|
||||
agentServerPackage: "pi-acp",
|
||||
agentServerVersion: "0.0.33",
|
||||
agentRuntimePackage: "@earendil-works/pi-coding-agent",
|
||||
agentRuntimeVersion: "0.84.2",
|
||||
commandDigest:
|
||||
"sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
|
||||
qualificationModel: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
reportedModelId: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
permissionPolicy: "interactive",
|
||||
},
|
||||
cursor: {
|
||||
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "cursor", agentProfileVersion: 11,
|
||||
agentServerPackage: "cursor-agent", agentServerVersion: "2026.09.26-dd393fe",
|
||||
agentRuntimePackage: null, agentRuntimeVersion: null,
|
||||
commandDigest: "sha256:2feb50c7b0a317dff454c00115a5bbe4d5c757189691586577be9c80234d477e",
|
||||
// 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",
|
||||
},
|
||||
copilot: {
|
||||
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 2,
|
||||
agentServerPackage: "@github/copilot", agentServerVersion: "1.0.88",
|
||||
agentRuntimePackage: null, agentRuntimeVersion: null,
|
||||
commandDigest: "sha256:b18c01603dd0169d233140709cfaa8bf5304a03cf5de78ca4f625f30013e8457",
|
||||
// Authenticated discovery has not established a qualification model. Never
|
||||
// turn this empty declaration into a default; callers must select an ID.
|
||||
qualificationModel: "", reportedModelId: "", permissionPolicy: "interactive",
|
||||
modelPolicy: "explicit-provider-verified", qualificationStatus: "pending",
|
||||
},
|
||||
claude: {
|
||||
driverKind: ACPX_DRIVER_KIND,
|
||||
protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION,
|
||||
agent: "claude",
|
||||
agentProfileVersion: 1,
|
||||
agentServerPackage: "@agentclientprotocol/claude-agent-acp",
|
||||
agentServerVersion: "0.73.0",
|
||||
agentRuntimePackage: "@anthropic-ai/claude-agent-sdk",
|
||||
agentRuntimeVersion: "0.3.286",
|
||||
commandDigest:
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
qualificationModel: "claude-sonnet-5",
|
||||
reportedModelId: "claude-sonnet-5",
|
||||
permissionPolicy: "interactive",
|
||||
},
|
||||
codex: {
|
||||
driverKind: ACPX_DRIVER_KIND,
|
||||
protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION,
|
||||
agent: "codex",
|
||||
agentProfileVersion: 1,
|
||||
agentServerPackage: "@agentclientprotocol/codex-acp",
|
||||
agentServerVersion: "1.6.2",
|
||||
agentRuntimePackage: "@openai/codex",
|
||||
agentRuntimeVersion: "0.160.0",
|
||||
commandDigest:
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
qualificationModel: "gpt-5.6-sol",
|
||||
reportedModelId: "gpt-5.6-sol",
|
||||
permissionPolicy: "interactive",
|
||||
},
|
||||
});
|
||||
> = deepFreeze(QUALIFIED_ACPX_PROFILE_DATA);
|
||||
|
||||
export function resolveQualifiedAcpxProfile(
|
||||
agent: QualifiedAcpxAgent,
|
||||
@@ -120,9 +42,10 @@ export function resolveQualifiedAcpxProfile(
|
||||
): QualifiedAcpxProfile {
|
||||
const profile = QUALIFIED_ACPX_PROFILES[agent];
|
||||
if (!requestedModel.trim()) throw new Error("ACPX model must not be empty");
|
||||
if (agent !== "claude" && agent !== "grok" && profile.modelPolicy !== "explicit-provider-verified" && requestedModel !== profile.qualificationModel) {
|
||||
const admission = ACPX_MODEL_ADMISSION[agent];
|
||||
if (admission.kind === "exact" && requestedModel !== admission.model) {
|
||||
throw new Error(
|
||||
`ACPX ${agent} profile requires exact model ${profile.qualificationModel}; received ${requestedModel}`,
|
||||
`ACPX ${agent} profile requires exact model ${admission.model}; received ${requestedModel}`,
|
||||
);
|
||||
}
|
||||
return { ...structuredClone(profile), qualificationModel: requestedModel, reportedModelId: requestedModel };
|
||||
|
||||
@@ -26,6 +26,19 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe("ACPX recovery identity", () => {
|
||||
it("preserves the pre-manifest Cursor recovery profile identity", async () => {
|
||||
const fixture = await recoveryFixture();
|
||||
const requestedModel = "explicit-test-model";
|
||||
const binding = await createAcpxRecoveryBinding({
|
||||
...fixture.input,
|
||||
requestedModel,
|
||||
profile: resolveQualifiedAcpxProfile("cursor", requestedModel),
|
||||
});
|
||||
// Captured from e75fde6098b0ddd8cec765bfb6ecaeecb88a26a6 before
|
||||
// consolidating release declarations; this is historical evidence.
|
||||
expect(binding.profileDigest).toBe("sha256:801bf9f450a2776ec879f2d85c05d8950172565a87f8f79177e0fc1211ae0bab");
|
||||
});
|
||||
|
||||
it("derives one stable, filesystem-safe runtime directory name", () => {
|
||||
expect(acpxRuntimeSessionDirectoryName("session/1")).toMatch(
|
||||
/^session_1-[0-9a-f]{16}$/,
|
||||
|
||||
@@ -33,17 +33,8 @@ const claudePatch = await readFile(
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
const qualifiedProfiles = await readFile(
|
||||
new URL("../src/drivers/acpx/qualified-profiles.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const runnerdAcpxBackend = await readFile(
|
||||
new URL(
|
||||
"../runner/crates/runner-core/src/acpx_provider_backend.rs",
|
||||
import.meta.url,
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
const profiles = JSON.parse(await readFile(new URL("../acpx-profiles.json", import.meta.url), "utf8"));
|
||||
const runnerdProfiles = await readFile(new URL("../runner/crates/runner-core/src/generated_acpx_profiles.rs", import.meta.url), "utf8");
|
||||
const providerPackBuilder = await readFile(
|
||||
new URL("../scripts/build-provider-pack.mjs", import.meta.url),
|
||||
"utf8",
|
||||
@@ -75,29 +66,16 @@ test("the runner pins every qualified ACPX production dependency", () => {
|
||||
});
|
||||
|
||||
test("the patched Codex ACP executable digest stays aligned across launch boundaries", async () => {
|
||||
const profileMatch =
|
||||
/agent: "codex"[\s\S]*?commandDigest:\s*"(sha256:[a-f0-9]{64})"/.exec(
|
||||
qualifiedProfiles,
|
||||
);
|
||||
assert.ok(profileMatch, "qualified Codex ACPX profile digest");
|
||||
const digest = profileMatch[1];
|
||||
const digest = profiles.profiles.codex.commandDigest;
|
||||
const packagePath = createRequire(import.meta.url).resolve("@agentclientprotocol/codex-acp/package.json");
|
||||
const installed = JSON.parse(await readFile(packagePath, "utf8"));
|
||||
const executable = await readFile(resolve(dirname(packagePath), installed.bin["codex-acp"]));
|
||||
assert.equal(digest, `sha256:${createHash("sha256").update(executable).digest("hex")}`,
|
||||
"the identity binds installed executable bytes, not the patch file");
|
||||
|
||||
assert.match(runnerdAcpxBackend, new RegExp(`"codex"[\\s\\S]*?${digest}`));
|
||||
assert.match(
|
||||
providerPackBuilder,
|
||||
new RegExp(`acpxProfileDigests:[\\s\\S]*?codex:[\\s\\S]*?${digest}`),
|
||||
);
|
||||
assert.match(
|
||||
nativeSessionExecutor,
|
||||
new RegExp(
|
||||
`REMOTE_PROVIDER_PACK_PROFILE_DIGESTS[\\s\\S]*?codex:[\\s\\S]*?${digest}`,
|
||||
),
|
||||
);
|
||||
assert.match(runnerdProfiles, new RegExp(`"codex"[\\s\\S]*?${digest}`));
|
||||
assert.match(providerPackBuilder, /codex:\s*profiles\.profiles\.codex\.commandDigest/);
|
||||
assert.match(nativeSessionExecutor, /codex:\s*QUALIFIED_ACPX_PROFILES\.codex\.commandDigest/);
|
||||
});
|
||||
|
||||
test("the package exposes only the reviewed runner CLI binaries", () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { QUALIFIED_ACPX_PROFILES, QUALIFIED_ACPX_VERSION } from "../../vendor/paperclip-runner/index.js";
|
||||
import { isProviderMode } from "../../vendor/paperclip-runner/index.js";
|
||||
import { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "../../vendor/paperclip-runner/index.js";
|
||||
import { nativeRetryCancellationEligible, rethrowNativeCancellationLockConflict, assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
|
||||
@@ -9483,19 +9484,19 @@ const RUNNERD_BINARY_CONTRACT_VERSION = 2;
|
||||
const REMOTE_PROVIDER_PACK_SCHEMA = "paperclip-runner/remote-provider-pack/v1";
|
||||
const REMOTE_PROVIDER_PACK_PINS = {
|
||||
nodeMinimum: "24.11.0",
|
||||
codex: "0.160.0",
|
||||
codex: QUALIFIED_ACPX_PROFILES.codex.agentRuntimeVersion,
|
||||
opencode: "1.18.34",
|
||||
acpx: "0.13.1",
|
||||
claudeAcp: "0.73.0",
|
||||
codexAcp: "1.6.2",
|
||||
grok: "1.0.13",
|
||||
acpx: QUALIFIED_ACPX_VERSION,
|
||||
claudeAcp: QUALIFIED_ACPX_PROFILES.claude.agentServerVersion,
|
||||
codexAcp: QUALIFIED_ACPX_PROFILES.codex.agentServerVersion,
|
||||
grok: QUALIFIED_ACPX_PROFILES.grok.agentRuntimeVersion,
|
||||
} as const;
|
||||
const REMOTE_PROVIDER_PACK_PROFILE_DIGESTS = {
|
||||
grok: "sha256:f0b698395a3704ed2ffaf84ea19bdb20c36c8a0a70b7c629c7b6ffe144e59e55",
|
||||
grok: QUALIFIED_ACPX_PROFILES.grok.commandDigest,
|
||||
claude:
|
||||
"sha256:9d73d1f0f121fb96cc8badb28c22d5bff02d8582eb2e40360a81c189e1b9422a",
|
||||
QUALIFIED_ACPX_PROFILES.claude.commandDigest,
|
||||
codex:
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3",
|
||||
QUALIFIED_ACPX_PROFILES.codex.commandDigest,
|
||||
} as const;
|
||||
const REMOTE_PROVIDER_PACK_ARTIFACT_PATHS = {
|
||||
grokLauncher: "dist/providers/grok/launcher.cjs",
|
||||
|
||||
Reference in new issue
Block a user