mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Bind corrected ACPX profiles and deduplicate steering acknowledgements
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
36e7121046
commit
342287678f
22 files changed
+304
-94
No files matched your search
@@ -9,7 +9,10 @@ qualification. The existing draft stack must be reviewed in dependency order.
|
||||
|
||||
- Pi: `@earendil-works/pi-coding-agent@1.0.0`.
|
||||
- Wrapper: `pi-acp@0.0.33`; ACPX: `0.13.1`; Node: `24.21.0`.
|
||||
- Pi profile: 13. Model: `openrouter/deepseek/deepseek-v4-flash-0731`.
|
||||
- Pi profile: 14. Model: `openrouter/deepseek/deepseek-v4-flash-0731`.
|
||||
- Profile 14 binds the corrected outbound ACPX client patch. Profile 13 remains
|
||||
historical evidence. Cursor 11 and Copilot 15 also bind that shared patch;
|
||||
both remain pending and receive no new paid qualification in this work.
|
||||
- Reasoning: native-confirmed `low`. No silent model or thinking fallback.
|
||||
- Exact source, suite definition, installed package integrity, runner digest,
|
||||
environment and cost evidence must accompany each attempt.
|
||||
@@ -22,7 +25,7 @@ qualification. The existing draft stack must be reviewed in dependency order.
|
||||
| Live lifecycle | Pending native question survives controller restart in the original process; one creation and resolution; stale answer after provider death rejected; Stop retires owned processes; three warm turns keep process identity | Installed local restart and three-turn warm continuity pass at `f5f57e380`. Pending-permission Stop and stale response rejection pass at `03dd6ef93`. Provider-death evidence remains required. |
|
||||
| Product workflows | All 26 explicit Pi Product E2E cells (13 local, 13 Daytona); screenshots, public state, independent artifacts, terminal and cleanup evidence | Historical partial passes exist. Full final-source campaign remains required. |
|
||||
| Runner protocol | Pi roster passes through the native packaged runner and authenticated mock control plane; lifecycle/denial/control cases remain distinct from Product tests | Installed get-task-context passes at `f5f57e380`; context-before-action times out. Five cells remain unexecuted. |
|
||||
| Installed distribution | Public CLI/server tars on ARM Mac, Intel Mac and Linux; normal Pi setup; exact Linux companion and immutable Daytona image imported without binary override | Fresh ARM public installation and normal packaged no-key probe pass. The Linux fleet job is queued. Final-source Intel/Linux/Daytona receipts remain required. |
|
||||
| Installed distribution | Public CLI/server tars on ARM Mac, Intel Mac and Linux; normal Pi setup; exact Linux companion and immutable Daytona image imported without binary override | Historical ARM and Intel public installations pass. Linux CI retains `session_handshake_timeout`; queued fleet jobs were cancelled. Final-source platform receipts, Linux companion and Daytona image remain required. |
|
||||
| Governance and spend | Company isolation, human-only permission, duplicate/stale answers, Stop and budget hard stop; pricing estimates never become claimed bills | 58 focused governance/cost/session invariants pass at `f5f57e380`. Live pending-permission Stop passes at `03dd6ef93`. The qualification key cap is not proof of Paperclip budget enforcement. |
|
||||
| Integration and rollout | Review each prerequisite; final-head typecheck, tests, build and CI pass; no unresolved review; exact artifacts; rollback recorded | Draft #14956 is stacked on #14924. Prerequisite PRs #14921–#14924 remain open. No merge or release. |
|
||||
|
||||
@@ -42,6 +45,24 @@ prices are estimates. Unpriced usage must stay unpriced in the ledger and UI.
|
||||
|
||||
## Evidence so far
|
||||
|
||||
- At `36e712104`, installed steering returns HTTP 200 and the native command
|
||||
journal records accepted delivery. The public API retains the correct
|
||||
correlation-bound acknowledgement at source sequence 65 and also a
|
||||
rehydrated transport echo at 66. The exact-one acknowledgement gate rejects
|
||||
that duplicate before permission denial. A regression using the actual
|
||||
rehydration function reproduces both items; suppressing the transport echo
|
||||
retains the one authoritative item. The interrupted attempt remains failed.
|
||||
- The shared ACPX patch also requires portable hunk metadata and new byte-bound
|
||||
identities. Pi advances to 14, Cursor to pending 11, and Copilot to pending
|
||||
15. Historical fixtures remain immutable, and old installed identities fail
|
||||
closed. The corrected patch passes all 16 packaging checks; identity and
|
||||
steering regressions pass 244 tests. No other provider qualification expands.
|
||||
- The credential-free Intel public installation at `36e712104` passes closed
|
||||
admission in 44 seconds. Its Greptile review is 5/5, but CI is held by the
|
||||
stale patch-bound profiles, portable patch metadata and Linux admission.
|
||||
The paid attempt and task-owned Linux build were stopped when that identity
|
||||
mismatch was found. Their evidence is retained; the owned server and database
|
||||
processes are retired. Qualification must use newly installed profile 14.
|
||||
- At `4237bc369`, the native turn-binding correction advances the steering
|
||||
journey past `steering_stale_turn`, but the provider boundary still rejects
|
||||
delivery. The real patched ACPX client lacks `requestExtension`: its types
|
||||
|
||||
@@ -209,7 +209,7 @@ impl AcpxProviderDescriptor {
|
||||
"0.0.33",
|
||||
Some("@earendil-works/pi-coding-agent"),
|
||||
Some("1.0.0"),
|
||||
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
|
||||
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
|
||||
),
|
||||
"cursor" => (
|
||||
self.model.as_str(),
|
||||
@@ -217,7 +217,7 @@ impl AcpxProviderDescriptor {
|
||||
"2026.09.26-dd393fe",
|
||||
None,
|
||||
None,
|
||||
"sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
|
||||
"sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
|
||||
),
|
||||
"copilot" => (
|
||||
self.model.as_str(),
|
||||
@@ -225,7 +225,7 @@ impl AcpxProviderDescriptor {
|
||||
"1.0.88",
|
||||
None,
|
||||
None,
|
||||
"sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
|
||||
"sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
|
||||
),
|
||||
"grok" => (
|
||||
"grok-4.7",
|
||||
@@ -3014,7 +3014,7 @@ mod tests {
|
||||
"cursor",
|
||||
"cursor-agent",
|
||||
"2026.09.26-dd393fe",
|
||||
"sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
|
||||
"sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
|
||||
None,
|
||||
None,
|
||||
"explicit-model",
|
||||
@@ -3023,7 +3023,7 @@ mod tests {
|
||||
"copilot",
|
||||
"@github/copilot",
|
||||
"1.0.88",
|
||||
"sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
|
||||
"sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
|
||||
None,
|
||||
None,
|
||||
"explicit-model",
|
||||
@@ -3032,7 +3032,7 @@ mod tests {
|
||||
"pi",
|
||||
"pi-acp",
|
||||
"0.0.33",
|
||||
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
|
||||
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
|
||||
Some("@earendil-works/pi-coding-agent"),
|
||||
Some("1.0.0"),
|
||||
"openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ use sha2::{Digest, Sha256};
|
||||
const CODEX_ACPX_DIGEST: &str =
|
||||
"sha256:c4538599d1ab767db5dff50934f13bb5ba313a59d9c4a83e993fac4617ea63d3";
|
||||
const PI_ACPX_DIGEST: &str =
|
||||
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177";
|
||||
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4";
|
||||
|
||||
fn temporary_directory(label: &str) -> PathBuf {
|
||||
let nonce = SystemTime::now()
|
||||
|
||||
@@ -7,7 +7,7 @@ import { COPILOT_VERSION, materializePinnedCopilotBinary, resolveCopilotDistribu
|
||||
|
||||
const MAX_ARCHIVE_BYTES = 128 * 1024 * 1024;
|
||||
const MAX_EXPANDED_BYTES = 384 * 1024 * 1024;
|
||||
const PROFILE_DIGEST = "sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b";
|
||||
const PROFILE_DIGEST = "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04";
|
||||
|
||||
/** Build-time only; outputRoot is the exact runner-owned platform asset directory. */
|
||||
export async function buildPinnedCopilotDistribution({ outputRoot, platform = process.platform, architecture = process.arch }, { fetchImpl = fetch } = {}) {
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function materializeCandidateProviderPack({ provider, outputRoot })
|
||||
if (provider === "cursor") {
|
||||
const result = await materializePinnedCursorDistribution({ destination: outputRoot });
|
||||
return { version: result.version,
|
||||
profileDigest: "sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
|
||||
profileDigest: "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
|
||||
closureDigest: `sha256:${result.closureSha256}` };
|
||||
}
|
||||
throw new Error(`The ${provider} candidate distribution builder is not included in this source revision`);
|
||||
|
||||
@@ -568,6 +568,9 @@ describe("native backend factory", () => {
|
||||
|
||||
it.each([
|
||||
["cursor", "../../test/fixtures/cursor-acp/profile-v7-identity.json"],
|
||||
["cursor", "../../test/fixtures/cursor-acp/profile-v10-identity.json"],
|
||||
["copilot", "../../test/fixtures/copilot-profile-v14-identity.json"],
|
||||
["pi", "../../test-fixtures/pi-acp/profile-v13-identity.json"],
|
||||
["cursor", "../../test/fixtures/cursor-acp/profile-v9-identity.json"],
|
||||
["copilot", "../../test/fixtures/copilot-profile-v7-identity.json"],
|
||||
["copilot", "../../test/fixtures/copilot-profile-v9-identity.json"],
|
||||
|
||||
@@ -344,7 +344,7 @@ describe("NativeExecutionInputV1", () => {
|
||||
profile: provider.profile,
|
||||
});
|
||||
expect(parseNativeExecutionInput(parsed)).toEqual(parsed);
|
||||
for (const unsupportedVersion of [0, 15, 1.5, "14", null]) {
|
||||
for (const unsupportedVersion of [0, 16, 1.5, "14", null]) {
|
||||
expect(() => parseNativeExecutionInput({
|
||||
...input,
|
||||
session: { ...input.session, driverKind: "acpx_runtime" },
|
||||
@@ -521,7 +521,7 @@ describe("native task context ownership", () => {
|
||||
});
|
||||
}
|
||||
|
||||
it.each(["off", "low", "high", "max"])("requires exact Pi13 thinking level %s in native input", piThinkingLevel => {
|
||||
it.each(["off", "low", "high", "max"])("requires exact current Pi thinking level %s in native input", piThinkingLevel => {
|
||||
const { qualificationModel, reportedModelId: _reported, permissionPolicy: _permission, modelPolicy: _policy, qualificationStatus: _status, ...profile } = QUALIFIED_ACPX_PROFILES.pi;
|
||||
const value = { ...currentInput(), session: { ...currentInput().session, driverKind: "acpx_runtime" }, provider: { kind: "acpx", agent: "pi", model: qualificationModel, permissionMode: "approve-all", piThinkingLevel, profile } };
|
||||
expect(parseNativeExecutionInput(value).provider).toEqual(value.provider);
|
||||
|
||||
@@ -91,7 +91,7 @@ export interface NativeAcpxProfileSnapshot {
|
||||
protocolVersion: 1;
|
||||
acpxVersion: "0.13.1";
|
||||
agent: NativeAcpxAgent;
|
||||
agentProfileVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14;
|
||||
agentProfileVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;
|
||||
agentServerPackage: string;
|
||||
agentServerVersion: string;
|
||||
agentRuntimePackage: string | null;
|
||||
@@ -578,9 +578,6 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
|
||||
if (provider.piThinkingLevel !== undefined && (provider.agent !== "pi" || (typeof provider.piThinkingLevel !== "string" || !["off", "low", "high", "max"].includes(provider.piThinkingLevel)))) {
|
||||
throw new NativeExecutionInputError("input.provider.piThinkingLevel must be off, low, high, or max and is supported only for Pi");
|
||||
}
|
||||
if (provider.agent === "pi" && record(provider.profile, "input.provider.profile").agentProfileVersion === 13 && provider.piThinkingLevel === undefined) {
|
||||
throw new NativeExecutionInputError("input.provider.piThinkingLevel is required for Pi profile 13");
|
||||
}
|
||||
if (provider.cursorMode !== undefined && (provider.agent !== "cursor"
|
||||
|| (provider.cursorMode !== "agent" && provider.cursorMode !== "plan" && provider.cursorMode !== "ask"))) {
|
||||
throw new NativeExecutionInputError("input.provider.cursorMode must be agent, plan, or ask and is supported only for Cursor");
|
||||
@@ -616,10 +613,13 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
|
||||
|| profile.protocolVersion !== 1
|
||||
|| profile.acpxVersion !== "0.13.1"
|
||||
|| profile.agent !== provider.agent
|
||||
|| (profile.agentProfileVersion !== 1 && profile.agentProfileVersion !== 2 && profile.agentProfileVersion !== 3 && profile.agentProfileVersion !== 4 && profile.agentProfileVersion !== 5 && profile.agentProfileVersion !== 6 && profile.agentProfileVersion !== 7 && profile.agentProfileVersion !== 8 && profile.agentProfileVersion !== 9 && profile.agentProfileVersion !== 10 && profile.agentProfileVersion !== 11 && profile.agentProfileVersion !== 12 && profile.agentProfileVersion !== 13 && profile.agentProfileVersion !== 14)
|
||||
|| (profile.agentProfileVersion !== 1 && profile.agentProfileVersion !== 2 && profile.agentProfileVersion !== 3 && profile.agentProfileVersion !== 4 && profile.agentProfileVersion !== 5 && profile.agentProfileVersion !== 6 && profile.agentProfileVersion !== 7 && profile.agentProfileVersion !== 8 && profile.agentProfileVersion !== 9 && profile.agentProfileVersion !== 10 && profile.agentProfileVersion !== 11 && profile.agentProfileVersion !== 12 && profile.agentProfileVersion !== 13 && profile.agentProfileVersion !== 14 && profile.agentProfileVersion !== 15)
|
||||
) {
|
||||
throw new NativeExecutionInputError("input.provider.profile does not match the qualified ACPX v1 profile");
|
||||
}
|
||||
if (provider.agent === "pi" && profile.agentProfileVersion >= 13 && provider.piThinkingLevel === undefined) {
|
||||
throw new NativeExecutionInputError("input.provider.piThinkingLevel is required for Pi profile 13 or later");
|
||||
}
|
||||
const runtimePackage = nullableText(profile.agentRuntimePackage, "input.provider.profile.agentRuntimePackage");
|
||||
const runtimeVersion = nullableText(profile.agentRuntimeVersion, "input.provider.profile.agentRuntimeVersion");
|
||||
if ((runtimePackage === null) !== (runtimeVersion === null)) {
|
||||
|
||||
@@ -80,8 +80,15 @@ describe("Copilot build-owned installation", () => {
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending");
|
||||
});
|
||||
|
||||
it("admits the current v14 declaration and binds its source policy, receipts and patch hashes", () => {
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
|
||||
it("preserves v14 history while binding the corrected outbound ACPX delivery", () => {
|
||||
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
|
||||
const current = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
|
||||
expect(Object.keys(current.declaration).filter(key => current.declaration[key] !== prior.declaration[key]).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBe("pending");
|
||||
});
|
||||
|
||||
it("admits the current v15 declaration and binds its source policy, receipts and patch hashes", () => {
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
|
||||
expect(identity.declaration.systemInstructionDelivery).toBe(COPILOT_SYSTEM_INSTRUCTION_DELIVERY);
|
||||
expect(identity.declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
|
||||
expect(identity.declaration.permissionContextContract).toBe(COPILOT_PERMISSION_CONTEXT_CONTRACT);
|
||||
@@ -112,7 +119,7 @@ describe("Copilot build-owned installation", () => {
|
||||
expect([...closure].filter(path => !bound.has(path))).toEqual(["negative-control:unbound-policy"]);
|
||||
});
|
||||
it.each([...PERMISSION_POLICY_SOURCES, ...SEMANTIC_RECEIPT_SOURCES])("changing %s changes the candidate identity", (_path, field) => {
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v14-identity.json", import.meta.url), "utf8"));
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v15-identity.json", import.meta.url), "utf8"));
|
||||
identity.declaration[field] = "0".repeat(64);
|
||||
const sorted = Object.fromEntries(Object.entries(identity.declaration).sort(([a], [b]) => a.localeCompare(b)));
|
||||
expect(`sha256:${createHash("sha256").update(JSON.stringify(sorted)).digest("hex")}`).not.toBe(identity.commandDigest);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { QUALIFIED_ACPX_PROFILES, type QualifiedAcpxProfile } from "./qualified-
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => { vi.unstubAllEnvs(); await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
|
||||
const candidate = (): QualifiedAcpxProfile => ({ ...QUALIFIED_ACPX_PROFILES.pi, agentProfileVersion: 13 });
|
||||
const candidate = (): QualifiedAcpxProfile => ({ ...QUALIFIED_ACPX_PROFILES.pi, agentProfileVersion: 14 });
|
||||
|
||||
async function fixture() {
|
||||
const root = await mkdtemp(join(tmpdir(), "pi-installation-test-")); roots.push(root);
|
||||
@@ -26,11 +26,17 @@ async function fixture() {
|
||||
}
|
||||
|
||||
describe("Pi installation factory", () => {
|
||||
it("preserves profile 13 and changes only the version and corrected ACPX patch binding", async () => {
|
||||
const prior = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v13-identity.json", import.meta.url), "utf8"));
|
||||
const current = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v14-identity.json", import.meta.url), "utf8"));
|
||||
expect(Object.keys(current.declaration).filter(key => JSON.stringify(current.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
|
||||
});
|
||||
|
||||
it("binds the profile declaration to the reviewed patch and platform closure pins", async () => {
|
||||
const hash = (bytes: string | Uint8Array) => createHash("sha256").update(bytes).digest("hex");
|
||||
const canonical = (value: any): string => Array.isArray(value) ? `[${value.map(canonical).join(",")}]`
|
||||
: value && typeof value === "object" ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value);
|
||||
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v13-identity.json", import.meta.url), "utf8"));
|
||||
const identity = JSON.parse(await readFile(new URL("../../../test-fixtures/pi-acp/profile-v14-identity.json", import.meta.url), "utf8"));
|
||||
const declaration = identity.declaration;
|
||||
expect(declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
|
||||
expect(identity.commandDigest).toBe(`sha256:${hash(canonical(declaration))}`);
|
||||
@@ -57,18 +63,19 @@ describe("Pi installation factory", () => {
|
||||
|
||||
it("rejects legacy profiles and caller-selected identities", () => {
|
||||
expect(() => assertPiInstallationProfile(candidate())).not.toThrow();
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 13");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 1 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 2 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 3 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 4 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 5 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 6 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 7 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 8 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 9 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 10 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 11 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 12 })).toThrow("version 14");
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), agentProfileVersion: 13 })).toThrow("version 14");
|
||||
for (const changed of [{ agentServerVersion: "latest" }, { commandDigest: `sha256:${"0".repeat(64)}` }, { reportedModelId: "different" }, { agentRuntimePackage: "ambient-pi" }]) {
|
||||
expect(() => assertPiInstallationProfile({ ...candidate(), ...changed })).toThrow("trusted declaration");
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ function record(value: unknown): Record<string, unknown> {
|
||||
/** Candidate identity is build-owned; model selection cannot substitute a CLI. */
|
||||
export function assertPiInstallationProfile(profile: QualifiedAcpxProfile): void {
|
||||
const trusted = QUALIFIED_ACPX_PROFILES.pi;
|
||||
if (profile.agentProfileVersion !== 13) throw new Error("Pi rich ACP requires profile version 13; reopen the previous session");
|
||||
if (profile.agentProfileVersion !== 14) throw new Error("Pi rich ACP requires profile version 14; reopen the previous session");
|
||||
if (profile.agent !== "pi" || profile.driverKind !== trusted.driverKind || profile.protocolVersion !== trusted.protocolVersion || profile.acpxVersion !== trusted.acpxVersion || profile.agentServerPackage !== "pi-acp" || profile.agentServerVersion !== "0.0.33" || profile.agentRuntimePackage !== "@earendil-works/pi-coding-agent" || profile.agentRuntimeVersion !== "1.0.0" || profile.commandDigest !== trusted.commandDigest || profile.permissionPolicy !== "interactive" || profile.qualificationModel !== PI_MODEL || profile.reportedModelId !== PI_MODEL) throw new Error("Pi distribution profile differs from its trusted declaration");
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,11 @@ describe("qualified ACPX profiles", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("binds Cursor v10 to native instructions, tool identity, closures and the exact ACPX guard patch", () => {
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v10-identity.json", import.meta.url), "utf8"));
|
||||
it("binds Cursor v11 to native instructions, tool identity, closures and the exact ACPX guard patch", () => {
|
||||
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v10-identity.json", import.meta.url), "utf8"));
|
||||
const identity = JSON.parse(readFileSync(new URL("../../../test/fixtures/cursor-acp/profile-v11-identity.json", import.meta.url), "utf8"));
|
||||
expect(Object.keys(identity.declaration).filter(key => JSON.stringify(identity.declaration[key]) !== JSON.stringify(prior.declaration[key])).sort()).toEqual(["acpxPatchSha256", "agentProfileVersion"]);
|
||||
expect(QUALIFIED_ACPX_PROFILES.cursor.qualificationStatus).toBe("pending");
|
||||
const distribution = JSON.parse(readFileSync(new URL("../../../cursor-distributions.json", import.meta.url), "utf8"));
|
||||
expect(identity.declaration.distribution).toEqual(distribution);
|
||||
expect(identity.declaration.sharedRuntimeContract).toBe("paperclip.acpx-runtime-contract.v1");
|
||||
|
||||
@@ -47,23 +47,23 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
|
||||
protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION,
|
||||
agent: "pi",
|
||||
agentProfileVersion: 13,
|
||||
agentProfileVersion: 14,
|
||||
agentServerPackage: "pi-acp",
|
||||
agentServerVersion: "0.0.33",
|
||||
agentRuntimePackage: "@earendil-works/pi-coding-agent",
|
||||
agentRuntimeVersion: "1.0.0",
|
||||
commandDigest:
|
||||
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
|
||||
"sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
|
||||
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: 10,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "cursor", agentProfileVersion: 11,
|
||||
agentServerPackage: "cursor-agent", agentServerVersion: "2026.09.26-dd393fe",
|
||||
agentRuntimePackage: null, agentRuntimeVersion: null,
|
||||
commandDigest: "sha256:1df2a15b93bc3a14fa47fa3315344ba023fe2412048047cdc6f32096a6336564",
|
||||
commandDigest: "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
|
||||
// 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",
|
||||
@@ -71,10 +71,10 @@ export const QUALIFIED_ACPX_PROFILES: Readonly<
|
||||
},
|
||||
copilot: {
|
||||
driverKind: ACPX_DRIVER_KIND, protocolVersion: ACPX_DRIVER_PROTOCOL_VERSION,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 14,
|
||||
acpxVersion: QUALIFIED_ACPX_VERSION, agent: "copilot", agentProfileVersion: 15,
|
||||
agentServerPackage: "@github/copilot", agentServerVersion: "1.0.88",
|
||||
agentRuntimePackage: null, agentRuntimeVersion: null,
|
||||
commandDigest: "sha256:5e5955c57917a7e7c25703b3ed9e420cd72105eab033b611508dc37fdba3858b",
|
||||
commandDigest: "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
|
||||
// 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",
|
||||
|
||||
@@ -27,6 +27,9 @@ afterEach(async () => {
|
||||
|
||||
describe("ACPX recovery identity", () => {
|
||||
it.each([
|
||||
["cursor", "../../../test/fixtures/cursor-acp/profile-v10-identity.json"],
|
||||
["copilot", "../../../test/fixtures/copilot-profile-v14-identity.json"],
|
||||
["pi", "../../../test-fixtures/pi-acp/profile-v13-identity.json"],
|
||||
["cursor", "../../../test/fixtures/cursor-acp/profile-v7-identity.json"],
|
||||
["cursor", "../../../test/fixtures/cursor-acp/profile-v8-identity.json"],
|
||||
["cursor", "../../../test/fixtures/cursor-acp/profile-v9-identity.json"],
|
||||
|
||||
+22
@@ -42,8 +42,30 @@ import {
|
||||
type PrpEvent,
|
||||
type PrpStructuredRunResult,
|
||||
} from "./codex-app-server-driver.test-support.js";
|
||||
import { rehydrateRunnerdItemNotification } from "../../live/runnerd-codex-transport.js";
|
||||
|
||||
describe("Codex app-server Codex driver", () => {
|
||||
it("retains one correlation-bound steering acknowledgement after the rehydrated runnerd echo", async () => {
|
||||
const transport = new FakeCodexTransport("thread-root");
|
||||
const session = await makeDriver([transport]).openSession({
|
||||
runId: "run-steering-echo", normalizedSessionId: "normalized-steering-echo", workingDirectory: WORKSPACE,
|
||||
});
|
||||
const { turnId } = await session.startTurn({ message: { role: "user", text: "Start." } });
|
||||
await session.steer?.({ turnId, message: { role: "user", text: "Stay concise." }, correlationId: "queued-comment-1" });
|
||||
transport.push("item/completed", rehydrateRunnerdItemNotification({
|
||||
provider: "acpx", providerTurnId: turnId, itemId: `acpx-control-${"a".repeat(64)}`,
|
||||
kind: "steering_acknowledgement", mode: "steer", status: "acknowledged",
|
||||
text: "Steering acknowledged for the active turn.",
|
||||
}, "thread-root", turnId));
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
await session.close({ reason: "fixture complete" });
|
||||
const events: PrpEvent[] = [];
|
||||
for await (const event of session.events()) events.push(event);
|
||||
expect(events.filter(event => event.eventType === "item.completed" && event.payload.kind === "steering_acknowledgement"))
|
||||
.toEqual([expect.objectContaining({ turnId, itemId: `${turnId}:steer:queued-comment-1`,
|
||||
payload: expect.objectContaining({ kind: "steering_acknowledgement", status: "acknowledged" }) })]);
|
||||
});
|
||||
|
||||
it("captures an exact skillless model/environment snapshot with credentials absent", async () => {
|
||||
const transport = new FakeCodexTransport();
|
||||
const session = await makeDriver([transport]).openSession({
|
||||
|
||||
@@ -331,12 +331,12 @@ async function mapNotificationBody(state: CodexSessionState, notification: Codex
|
||||
if (
|
||||
notification.method === "item/completed"
|
||||
&& text(params.kind) === "steering_acknowledgement"
|
||||
&& Object.keys(item).length === 0
|
||||
) {
|
||||
// runnerd persists its own command acknowledgement as a canonical PRP
|
||||
// item. The request() call is already the authoritative acknowledgement
|
||||
// and steer() emits the user-visible item with the active turn binding.
|
||||
// Do not reinterpret this transport-level echo as an unbound Codex item.
|
||||
// Rehydration adds an item object to this transport echo; it still must
|
||||
// not become a second acknowledgement beside the correlation-bound item.
|
||||
return;
|
||||
}
|
||||
if (notification.method === "paperclip/runResult") {
|
||||
|
||||
@@ -43,8 +43,8 @@ it("admits Pi live sessions without candidate opt-in while preserving the exact
|
||||
const session = await service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "openrouter/deepseek/deepseek-v4-flash-0731" });
|
||||
try {
|
||||
expect(session.snapshot().config.acpxProfile).toMatchObject({
|
||||
agent: "pi", agentProfileVersion: 13,
|
||||
commandDigest: "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
|
||||
agent: "pi", agentProfileVersion: 14,
|
||||
commandDigest: "sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
|
||||
});
|
||||
await expect(service.create({ provider: "acpx", acpxAgent: "pi", piThinkingLevel: "low", requestedModel: "another-model" }))
|
||||
.rejects.toThrow("requires exact model");
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"commandDigest": "sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "pi",
|
||||
"agentProfileVersion": 14,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "0.0.33",
|
||||
"agentRuntimeVersion": "1.0.0",
|
||||
"nodeVersion": "24.21.0",
|
||||
"closure": {
|
||||
"darwin-arm64": "f6538a35e08f1c1816e738277a1843acfa1ce4522bbaa3340dbddd859dc7dd04",
|
||||
"darwin-x64": "4728e5a4e7fc1ba602c3e219824fb84884b05a06cdd19dd3e521ee312e1b373a",
|
||||
"linux-x64": "2957c0ec20ca1ace64d1a2b10c4a99f47f59e0c5c33a89161c1d5b48341c2b25"
|
||||
},
|
||||
"wrapperSha256": "8ef777e9e6a3cef37775bab2b800e4931330c23a0b151ce07fdc5b943f28435d",
|
||||
"helperSha256": "2191b1e5f281d24508ec7eaff39011ee863a8a70bf54f4aa0490ad0172b323da",
|
||||
"extensionSha256": "8f9b538dbe924a314099f9597ee3876f7ccd88211c4f4b2e52f46b6c7ffa76ba",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"nativeQuestions": "select-confirm-input-editor-v2",
|
||||
"pathPolicy": "sdk-normalization-v1",
|
||||
"mcpToolNames": "provider-safe-exact-source-v1",
|
||||
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
|
||||
"nativeAssistantMessages": "pi1-rpc-deltas-v3-bound-message-and-tool-receipts",
|
||||
"nativeNotices": "session-extension-v2-bounded-runtime-failure",
|
||||
"messageProjectionSha256": "17d1e91b948dfa6a56e1905645add52b502041ad843c8d50a13b07956f22aadd",
|
||||
"noticeProjectionSha256": "014485adc690998e395d334ea3ae67f72be6060a2fc78a1185df7b8d1da28fbf",
|
||||
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
|
||||
"nativeInputDisposition": "queued-only-v1",
|
||||
"cacheWarming": "disabled-owned-decision-v1",
|
||||
"thinkingLevel": {
|
||||
"supported": [
|
||||
"off",
|
||||
"low",
|
||||
"high",
|
||||
"max"
|
||||
],
|
||||
"newConfigurationDefault": "low",
|
||||
"admission": "explicit-native-effective-before-prompt-v1",
|
||||
"recovery": "mode-bound-v1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"commandDigest": "sha256:8faf47520f156c62c79ea6ca7d1d90a85ddc9a6621f798bd14e87bc6c643bd04",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 15,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1",
|
||||
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
|
||||
"innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a",
|
||||
"upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0",
|
||||
"patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679",
|
||||
"messageIdentityContract": "copilot-native-message-id-v1",
|
||||
"ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1",
|
||||
"permissionContextContract": "copilot-edit-permission-context-v1",
|
||||
"permissionContextSourceSha256": "fe1bb611bea289d81b1c7c973fbb01b93de133a7caa8fe78b7ff4ee1f485b224",
|
||||
"permissionAdapterSourceSha256": "49421a779c76e286ce65d75052026eab9630b1995af9ccf045a2d43d284633ce",
|
||||
"permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9",
|
||||
"permissionRedactionSourceSha256": "687270d7bbe3dccce6c2fed8664e76cda724b43e545cb153b9f54b018de52927",
|
||||
"permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4",
|
||||
"permissionIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
|
||||
"semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2",
|
||||
"semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791",
|
||||
"semanticBridgeSourceSha256": "8a43ebafb7a85dd1714ed6945670472666dcb63e8af32b4a74644163fa0e370d",
|
||||
"toolEvidenceSourceSha256": "4b517eed447615b3cc820af348ccc360978175149caa0bbc838d1f78332abe75",
|
||||
"semanticNormalizedInputContract": "validated-forwarded-input-commit-v1",
|
||||
"semanticSidecarSourceSha256": "b83898b2453d0f425275d3bc58140675fc8f14505703103079094093f16be43f",
|
||||
"semanticDirectDriverSourceSha256": "2db465e56ba41f06b406e039e566d9e3aea0a014fc6a47a6a54d5bc682dabcab",
|
||||
"semanticValidationSourceSha256": "039fca52266fe21ba74ca64660d21228314d8ed737b19ba67941bfb25086e4d5",
|
||||
"semanticNormalizationSourceSha256": "b1ce59251bf9c671f66e0e5173646190b9a2d1bfc5bdb7fb123824213986b92b",
|
||||
"semanticCompletionContractSourceSha256": "00a684f9aea87f68eabf0974681c52461bc308631a1252a89c4ed087d7158b28",
|
||||
"semanticValidatorsSourceSha256": "950b49b233d8dd62ff858398becfc55f0a181160cb8a434d9221b5c17816867c",
|
||||
"semanticSchemaBundleSourceSha256": "2974c75590f816fbdbc7806f531698fdc8d62a99601a17625b81de0f074d6383",
|
||||
"semanticSidecarProtocolSourceSha256": "72d9800603a652d6580084bb3b5dac4d4dd6d644324b0d15d293d7c198fd6b55",
|
||||
"semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"commandDigest": "sha256:13207d7b6afcfe681d4fe9098655df056c2cf87e03f5141e961cd01c32c5bdd2",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "cursor",
|
||||
"agentProfileVersion": 11,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "2026.09.26-dd393fe",
|
||||
"patchVersion": "paperclip-cursor-usage-v4",
|
||||
"distribution": {
|
||||
"schema": "paperclip.cursor_distribution.v1",
|
||||
"version": "2026.09.26-dd393fe",
|
||||
"platforms": {
|
||||
"darwin-arm64": {
|
||||
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/darwin/arm64/agent-cli-package.tar.gz",
|
||||
"archiveSha256": "538827d96a779bab854a865c8e42859e8e87db34b5f69770261b90fc8cfff191",
|
||||
"closureSha256": "257424bd48e35412091c6adfc61e4648e836757ec1d240d890bba81a24918c30",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js",
|
||||
"vendorClosureSha256": "77394184a89b0e7384971181da19c3c82d83a399b04e7944b3f94fe3d7e62b25"
|
||||
},
|
||||
"darwin-x64": {
|
||||
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/darwin/x64/agent-cli-package.tar.gz",
|
||||
"archiveSha256": "ed1771c44cbf0f8059c67cac0d939e6a60eb730bd066d7dc4cbb14de314cfb6e",
|
||||
"closureSha256": "6f28c799c5afdc64fbdff8a2157f565f17ae7615efe014ac389d63bf70cf2be2",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js",
|
||||
"vendorClosureSha256": "7c8775160af74e5fb8d25a30e8412c1a6476b0661529078a686e5118fd1c5e5f"
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://downloads.cursor.com/lab/2026.09.26-dd393fe/linux/x64/agent-cli-package.tar.gz",
|
||||
"archiveSha256": "8085fd120f5c71f4eae7fea26a043718e5644e3071e4fab3220a0e58c51f9593",
|
||||
"closureSha256": "eadb8bb8ffb0450455b15b88c9b230307a9e149958a0c452d16dd157b4633d74",
|
||||
"executable": "node",
|
||||
"entrypoint": "index.js",
|
||||
"vendorClosureSha256": "bf04ef8ea6a63191067a6b3e15139aa2c793d8419daab246aa3f0a012e1bbcd9"
|
||||
}
|
||||
},
|
||||
"patchVersion": "paperclip-cursor-usage-v4"
|
||||
},
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"instructionBinding": "native-global-rules-v1",
|
||||
"instructionAdmission": "synchronous-protocol-guard-v1",
|
||||
"acpxPatchSha256": "c0139d0b3af085ec5272a7812c9f89aff0e8618d1d44d9c9e9a59525d1972436",
|
||||
"sessionModeAdmission": "native-config-ack-recovery-bound-v1",
|
||||
"sessionModes": [
|
||||
"agent",
|
||||
"plan",
|
||||
"ask"
|
||||
],
|
||||
"defaultSessionMode": "agent",
|
||||
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
|
||||
"nativePlanToolIdentity": "request-item-id-bound-lifecycle-v1",
|
||||
"nativePermissionToolIdentity": "opaque-native-tool-id-sha256-v1",
|
||||
"toolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
|
||||
"permissionAdapterSourceSha256": "49421a779c76e286ce65d75052026eab9630b1995af9ccf045a2d43d284633ce",
|
||||
"toolEvidenceSourceSha256": "8310308ed9605415cb28b7c350d83defef68e90cd30bc2f0491d3d09825e8cd9"
|
||||
}
|
||||
}
|
||||
+42
-43
@@ -20,10 +20,9 @@ index 5e2113ac0..45aff03c3 100644
|
||||
private setSessionModelThroughConfig;
|
||||
private setSessionModelThroughLegacyMethod;
|
||||
diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
|
||||
index d454fd7c5..6eba74345 100644
|
||||
--- a/dist/live-checkpoint-BSIrfgVo.js
|
||||
+++ b/dist/live-checkpoint-BSIrfgVo.js
|
||||
@@ -1068,12 +1068,14 @@ function serializeSessionRecordForDisk(record) {
|
||||
@@ -1068,12 +1068,14 @@
|
||||
last_agent_disconnect_reason: canonical.lastAgentDisconnectReason,
|
||||
protocol_version: canonical.protocolVersion,
|
||||
agent_capabilities: canonical.agentCapabilities,
|
||||
@@ -38,7 +37,7 @@ index d454fd7c5..6eba74345 100644
|
||||
acpx: canonical.acpx,
|
||||
imported_from: canonical.importedFrom ? {
|
||||
record_id: canonical.importedFrom.recordId,
|
||||
@@ -1239,7 +1241,8 @@ function parseRequestTokenUsage(raw) {
|
||||
@@ -1239,7 +1241,8 @@
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
const parsed = parseTokenUsage(value);
|
||||
if (parsed == null) return null;
|
||||
@@ -48,7 +47,7 @@ index d454fd7c5..6eba74345 100644
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
@@ -1325,6 +1328,93 @@ function isAgentMessage$1(raw) {
|
||||
@@ -1325,6 +1328,93 @@
|
||||
function isConversationMessage(raw) {
|
||||
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
|
||||
}
|
||||
@@ -142,7 +141,7 @@ index d454fd7c5..6eba74345 100644
|
||||
function parseConversationRecord(record) {
|
||||
if (!hasValidConversationCore(record)) return;
|
||||
const title = parseConversationTitle(record.title);
|
||||
@@ -1339,7 +1429,8 @@ function parseConversationRecord(record) {
|
||||
@@ -1339,7 +1429,8 @@
|
||||
updated_at: record.updated_at,
|
||||
cumulative_token_usage: cumulativeTokenUsage ?? {},
|
||||
cumulative_cost: cumulativeCost,
|
||||
@@ -152,7 +151,7 @@ index d454fd7c5..6eba74345 100644
|
||||
};
|
||||
}
|
||||
const INVALID_VALUE = Symbol("invalid");
|
||||
@@ -1541,12 +1632,14 @@ function parseSessionRecord(raw) {
|
||||
@@ -1541,12 +1632,14 @@
|
||||
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
|
||||
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
|
||||
agentCapabilities: asRecord$4(record.agent_capabilities),
|
||||
@@ -167,7 +166,7 @@ index d454fd7c5..6eba74345 100644
|
||||
acpx: parseAcpxState(record.acpx),
|
||||
importedFrom: metadata.importedFrom
|
||||
};
|
||||
@@ -1661,8 +1754,9 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
|
||||
@@ -1661,8 +1754,9 @@
|
||||
"RedactedThinking",
|
||||
"ToolUse"
|
||||
]);
|
||||
@@ -178,7 +177,7 @@ index d454fd7c5..6eba74345 100644
|
||||
"agent_capabilities",
|
||||
"messages.Agent.content.ToolUse.input",
|
||||
"acpx.desired_config_options",
|
||||
@@ -3104,10 +3198,10 @@ function authEnvKey(methodId) {
|
||||
@@ -3104,10 +3198,10 @@
|
||||
authEnvKeyCache.set(methodId, key);
|
||||
return key;
|
||||
}
|
||||
@@ -191,7 +190,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (typeof value === "string" && value.trim().length > 0) return value;
|
||||
}
|
||||
function protectedEnvKey(key) {
|
||||
@@ -3135,8 +3229,21 @@ function promotePrefixedAuthEnvironment(env) {
|
||||
@@ -3135,8 +3229,21 @@
|
||||
}
|
||||
return protectedKeys;
|
||||
}
|
||||
@@ -215,7 +214,7 @@ index d454fd7c5..6eba74345 100644
|
||||
const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env);
|
||||
if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) {
|
||||
addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential);
|
||||
@@ -3178,10 +3285,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
||||
@@ -3178,10 +3285,10 @@
|
||||
const configCredentials = authCredentials ?? {};
|
||||
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
|
||||
}
|
||||
@@ -228,7 +227,7 @@ index d454fd7c5..6eba74345 100644
|
||||
stdio: [
|
||||
"pipe",
|
||||
"pipe",
|
||||
@@ -3961,10 +4068,13 @@ function resolveClientCapabilities(params) {
|
||||
@@ -3961,10 +4068,13 @@
|
||||
...params.elicitationModes.includes("url") ? { url: {} } : {}
|
||||
} } : {}
|
||||
};
|
||||
@@ -244,7 +243,7 @@ index d454fd7c5..6eba74345 100644
|
||||
};
|
||||
}
|
||||
function hasResponseField(response, field) {
|
||||
@@ -4067,6 +4177,27 @@ function createNdJsonMessageStream(agentCommand, output, input) {
|
||||
@@ -4067,6 +4177,27 @@
|
||||
} })
|
||||
};
|
||||
}
|
||||
@@ -272,7 +271,7 @@ index d454fd7c5..6eba74345 100644
|
||||
var AcpClient = class {
|
||||
options;
|
||||
connection;
|
||||
@@ -4105,7 +4236,9 @@ var AcpClient = class {
|
||||
@@ -4105,7 +4236,9 @@
|
||||
cwd: asAbsoluteCwd(options.cwd),
|
||||
authPolicy: options.authPolicy ?? "skip",
|
||||
permissionPolicy: snapshotPermissionPolicy(options.permissionPolicy),
|
||||
@@ -283,7 +282,7 @@ index d454fd7c5..6eba74345 100644
|
||||
};
|
||||
this.eventHandlers = {
|
||||
onAcpMessage: this.options.onAcpMessage,
|
||||
@@ -4216,9 +4349,21 @@ var AcpClient = class {
|
||||
@@ -4216,9 +4349,21 @@
|
||||
this.lastAgentExit = void 0;
|
||||
this.lastKnownPid = child.pid ?? void 0;
|
||||
this.attachAgentLifecycleObservers(child);
|
||||
@@ -305,7 +304,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (!this.options.verbose) return;
|
||||
process.stderr.write(chunk);
|
||||
});
|
||||
@@ -4226,6 +4371,7 @@ var AcpClient = class {
|
||||
@@ -4226,6 +4371,7 @@
|
||||
const output = Readable.toWeb(child.stdout);
|
||||
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
|
||||
const connection = this.createConnection(stream, launch);
|
||||
@@ -313,7 +312,7 @@ index d454fd7c5..6eba74345 100644
|
||||
connection.signal.addEventListener("abort", () => {
|
||||
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
|
||||
}, { once: true });
|
||||
@@ -4253,7 +4399,12 @@ var AcpClient = class {
|
||||
@@ -4253,7 +4399,12 @@
|
||||
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
|
||||
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
|
||||
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
|
||||
@@ -327,7 +326,7 @@ index d454fd7c5..6eba74345 100644
|
||||
};
|
||||
}
|
||||
logAgentLaunch(plan) {
|
||||
@@ -4279,10 +4430,17 @@ var AcpClient = class {
|
||||
@@ -4279,10 +4430,17 @@
|
||||
}
|
||||
async spawnAgentProcess(plan) {
|
||||
const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
|
||||
@@ -347,7 +346,7 @@ index d454fd7c5..6eba74345 100644
|
||||
try {
|
||||
await waitForSpawn$1(spawnedChild);
|
||||
} catch (error) {
|
||||
@@ -4293,10 +4451,10 @@ var AcpClient = class {
|
||||
@@ -4293,10 +4451,10 @@
|
||||
createConnection(stream, launch) {
|
||||
const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => {
|
||||
await this.handleSessionUpdate(params);
|
||||
@@ -361,7 +360,7 @@ index d454fd7c5..6eba74345 100644
|
||||
}).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
|
||||
return await this.handleReadTextFile(params);
|
||||
}).onRequest(methods.client.fs.writeTextFile, async ({ params }) => {
|
||||
@@ -4312,6 +4470,11 @@ var AcpClient = class {
|
||||
@@ -4312,6 +4470,11 @@
|
||||
}).onRequest(methods.client.terminal.release, async ({ params }) => {
|
||||
return await this.handleReleaseTerminal(params);
|
||||
});
|
||||
@@ -373,7 +372,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (launch.devinAcp) app.onRequest("_cognition.ai/request_diagnostics", (params) => {
|
||||
return params && typeof params === "object" && !Array.isArray(params) ? params : {};
|
||||
}, async () => ({}));
|
||||
@@ -4333,12 +4496,12 @@ var AcpClient = class {
|
||||
@@ -4333,12 +4496,12 @@
|
||||
async initializeProtocolConnection(connection, launch) {
|
||||
const initializePromise = connection.initialize({
|
||||
protocolVersion: PROTOCOL_VERSION,
|
||||
@@ -388,7 +387,7 @@ index d454fd7c5..6eba74345 100644
|
||||
clientInfo: resolveClientInfo(launch.devinAcp)
|
||||
});
|
||||
const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
|
||||
@@ -4358,6 +4521,49 @@ var AcpClient = class {
|
||||
@@ -4358,6 +4521,49 @@
|
||||
throw normalizedError;
|
||||
}
|
||||
createTappedStream(base) {
|
||||
@@ -438,7 +437,7 @@ index d454fd7c5..6eba74345 100644
|
||||
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
|
||||
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
|
||||
const elicitationRequestIds = /* @__PURE__ */ new Set();
|
||||
@@ -4367,8 +4573,12 @@ var AcpClient = class {
|
||||
@@ -4367,8 +4573,12 @@
|
||||
const shouldSuppressInboundReplaySessionUpdate = (message) => {
|
||||
return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message);
|
||||
};
|
||||
@@ -452,7 +451,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (requestId !== void 0) {
|
||||
elicitationRequestIds.add(requestId);
|
||||
return;
|
||||
@@ -4379,6 +4589,7 @@ var AcpClient = class {
|
||||
@@ -4379,6 +4589,7 @@
|
||||
}
|
||||
};
|
||||
return {
|
||||
@@ -460,7 +459,7 @@ index d454fd7c5..6eba74345 100644
|
||||
readable: new ReadableStream({ async start(controller) {
|
||||
const reader = base.readable.getReader();
|
||||
try {
|
||||
@@ -4386,16 +4597,27 @@ var AcpClient = class {
|
||||
@@ -4386,16 +4597,27 @@
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
if (!value) continue;
|
||||
@@ -490,7 +489,7 @@ index d454fd7c5..6eba74345 100644
|
||||
const promptOwner = promptRequestOwner(message);
|
||||
if (promptOwner) bindPromptOwner(promptOwner);
|
||||
const id = responseId(message);
|
||||
@@ -4403,10 +4625,20 @@ var AcpClient = class {
|
||||
@@ -4403,10 +4625,20 @@
|
||||
onAcpOutputMessage()?.("outbound", message);
|
||||
onAcpMessage()?.("outbound", message);
|
||||
}
|
||||
@@ -511,7 +510,7 @@ index d454fd7c5..6eba74345 100644
|
||||
writer.releaseLock();
|
||||
}
|
||||
} })
|
||||
@@ -4422,7 +4654,10 @@ var AcpClient = class {
|
||||
@@ -4422,7 +4654,10 @@
|
||||
const createPromise = this.runConnectionRequest(() => connection.newSession({
|
||||
cwd: sessionCwd,
|
||||
mcpServers: this.options.mcpServers ?? [],
|
||||
@@ -523,21 +522,21 @@ index d454fd7c5..6eba74345 100644
|
||||
}));
|
||||
result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
|
||||
} catch (error) {
|
||||
@@ -4596,6 +4831,13 @@ var AcpClient = class {
|
||||
@@ -4589,6 +4824,13 @@
|
||||
} catch (error) {
|
||||
throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`);
|
||||
}
|
||||
}
|
||||
+ }
|
||||
+ async requestExtension(method, params) {
|
||||
+ closedExtensionMethods([method]);
|
||||
+ const payload = boundedExtensionRecord(params);
|
||||
+ const connection = this.getConnection();
|
||||
+ const result = await this.runConnectionRequest(() => connection.extMethod(method, payload));
|
||||
+ return boundedExtensionRecord(result);
|
||||
+ }
|
||||
}
|
||||
async setSessionModel(sessionId, modelId, controlOverride) {
|
||||
const control = this.resolveModelControl(sessionId, controlOverride);
|
||||
if (!control) throw new RequestedModelUnsupportedError(`Cannot set model "${modelId}": the ACP session did not advertise a model config option or legacy session/set_model support.`, "missing-capability");
|
||||
const resolvedModelId = resolveRequestedModelId({
|
||||
@@ -4864,7 +5106,7 @@ var AcpClient = class {
|
||||
@@ -4864,7 +5106,7 @@
|
||||
}
|
||||
selectAuthMethod(methods) {
|
||||
for (const method of methods) {
|
||||
@@ -546,7 +545,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (envCredential) return {
|
||||
methodId: method.id,
|
||||
credential: envCredential,
|
||||
@@ -4890,7 +5125,7 @@ var AcpClient = class {
|
||||
@@ -4890,7 +5132,7 @@
|
||||
}
|
||||
readAgentSpecificEnvCredential(methodId) {
|
||||
if (!this.isGrokBuildAcpCommand() || methodId !== "xai.api_key") return;
|
||||
@@ -555,7 +554,7 @@ index d454fd7c5..6eba74345 100644
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
||||
}
|
||||
selectAgentManagedAuthMethod(methodId) {
|
||||
@@ -4915,9 +5150,9 @@ var AcpClient = class {
|
||||
@@ -4915,9 +5157,9 @@
|
||||
await connection.authenticate({ methodId: selected.methodId });
|
||||
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
|
||||
}
|
||||
@@ -567,7 +566,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (hostResponse) return hostResponse;
|
||||
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
|
||||
if (!recorded) {
|
||||
@@ -4926,14 +5161,81 @@ var AcpClient = class {
|
||||
@@ -4926,14 +5168,81 @@
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -651,7 +650,7 @@ index d454fd7c5..6eba74345 100644
|
||||
})).then((response) => ({
|
||||
kind: "response",
|
||||
response
|
||||
@@ -4970,7 +5272,7 @@ var AcpClient = class {
|
||||
@@ -4970,7 +5279,7 @@
|
||||
isElicitationSessionCancelling(sessionId) {
|
||||
return this.closing || this.cancellingSessionIds.has(sessionId);
|
||||
}
|
||||
@@ -660,7 +659,7 @@ index d454fd7c5..6eba74345 100644
|
||||
if (!this.options.onPermissionRequest) return;
|
||||
const signal = this.cancellationSignalForSession(params.sessionId);
|
||||
try {
|
||||
@@ -4978,7 +5280,7 @@ var AcpClient = class {
|
||||
@@ -4978,7 +5287,7 @@
|
||||
sessionId: params.sessionId,
|
||||
raw: params,
|
||||
inferredKind: inferToolKind(params)
|
||||
@@ -669,7 +668,7 @@ index d454fd7c5..6eba74345 100644
|
||||
return this.hostPermissionDecisionResponse(params, signal, decision);
|
||||
} catch (error) {
|
||||
return this.hostPermissionErrorResponse(params, signal, error);
|
||||
@@ -5028,6 +5330,12 @@ var AcpClient = class {
|
||||
@@ -5028,6 +5337,12 @@
|
||||
attachAgentLifecycleObservers(child) {
|
||||
child.once("exit", (exitCode, signal) => {
|
||||
this.recordAgentExit("process_exit", exitCode, signal);
|
||||
@@ -682,7 +681,7 @@ index d454fd7c5..6eba74345 100644
|
||||
});
|
||||
child.once("close", (exitCode, signal) => {
|
||||
this.recordAgentExit("process_close", exitCode, signal);
|
||||
@@ -5239,6 +5547,7 @@ function applyConversation(record, conversation) {
|
||||
@@ -5239,6 +5554,7 @@
|
||||
record.cumulative_token_usage = conversation.cumulative_token_usage;
|
||||
record.cumulative_cost = conversation.cumulative_cost;
|
||||
record.request_token_usage = conversation.request_token_usage;
|
||||
@@ -690,7 +689,7 @@ index d454fd7c5..6eba74345 100644
|
||||
}
|
||||
//#endregion
|
||||
//#region src/runtime/engine/session-options.ts
|
||||
@@ -5701,7 +6010,8 @@ function cloneSessionConversation(conversation) {
|
||||
@@ -5701,7 +6017,8 @@
|
||||
updated_at: conversation.updated_at,
|
||||
cumulative_token_usage: deepClone(conversation.cumulative_token_usage ?? {}),
|
||||
cumulative_cost: cloneUsageCost(conversation.cumulative_cost),
|
||||
@@ -700,7 +699,7 @@ index d454fd7c5..6eba74345 100644
|
||||
};
|
||||
}
|
||||
function cloneUsageCost(cost) {
|
||||
@@ -5771,10 +6081,20 @@ function recordSessionUpdate(conversation, state, notification, timestamp = isoN
|
||||
@@ -5771,10 +6088,20 @@
|
||||
trimConversationForRuntime(conversation);
|
||||
return acpx;
|
||||
}
|
||||
@@ -723,7 +722,7 @@ index d454fd7c5..6eba74345 100644
|
||||
updateConversationTimestamp(conversation, timestamp);
|
||||
trimConversationForRuntime(conversation);
|
||||
return true;
|
||||
@@ -6435,6 +6755,27 @@ async function withConnectedSession(options) {
|
||||
@@ -6435,6 +6762,27 @@
|
||||
//#region src/runtime/engine/prompt-turn.ts
|
||||
const SESSION_REPLY_IDLE_MS = 1e3;
|
||||
const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3;
|
||||
@@ -751,7 +750,7 @@ index d454fd7c5..6eba74345 100644
|
||||
async function runPromptTurn(params) {
|
||||
try {
|
||||
const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation);
|
||||
@@ -6444,7 +6785,23 @@ async function runPromptTurn(params) {
|
||||
@@ -6444,7 +6792,23 @@
|
||||
idleMs: SESSION_REPLY_IDLE_MS,
|
||||
timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
|
||||
}).catch(() => {});
|
||||
|
||||
@@ -20,8 +20,8 @@ assert.equal(JSON.parse(await readFile(join(server, 'package.json'), 'utf8')).na
|
||||
const { resolvePaperclipRunnerBinary } = await import(pathToFileURL(join(server, 'dist/services/native-runtime/native-codex-runner.js')));
|
||||
const { createCapabilityRunnerdCodexTransport } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/live/runnerd-codex-transport.js')));
|
||||
const { QUALIFIED_ACPX_PROFILES } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js')));
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 13);
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177');
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 14);
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:f35145437eeb355ed37bc5a4fa93d7ede561d9c45daf311979b46890b808ddd4');
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.qualificationModel, 'openrouter/deepseek/deepseek-v4-flash-0731');
|
||||
const daemon = resolvePaperclipRunnerBinary();
|
||||
assert.equal(await realpath(daemon), join(server, 'dist/vendor/paperclip-runner/bin/paperclip-runnerd'));
|
||||
@@ -63,7 +63,7 @@ try {
|
||||
assert.equal(state.activeTurnId, null);
|
||||
assert.equal(state.identity, null);
|
||||
assert.equal(state.providerExitUnconfirmed, false);
|
||||
console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 13, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true }));
|
||||
console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 14, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true }));
|
||||
} finally {
|
||||
try { await bundle?.transport.close(); }
|
||||
finally { clearTimeout(watchdog); await rm(root, { recursive: true, force: true }); }
|
||||
|
||||
Reference in new issue
Block a user