Preserve Pi thinking and recovery alongside current provider modes

This commit is contained in:
Dotta committed 2026-10-08 14:38:10 -05:00
1 parent 945330e8c9
commit 5506014e1f
22 files changed
+79 -30

No files matched your search

+3 -3
View File
@@ -14,12 +14,12 @@
"requiresProviderPolicy": false
},
"pi": {
"agentProfileVersion": 1,
"agentProfileVersion": 13,
"agentServerPackage": "pi-acp",
"agentServerVersion": "0.0.33",
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
"agentRuntimeVersion": "0.84.2",
"commandDigest": "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
"agentRuntimeVersion": "1.0.0",
"commandDigest": "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
"requiresProviderPolicy": true
},
"cursor": {
@@ -80,7 +80,7 @@
{
"path": "schemas/provider-descriptor.schema.json",
"id": "https://paperclip.dev/schemas/prp/v1/provider-descriptor.schema.json",
"sha256": "b3d42a51be5a632c0404194f036c20e94b03097537f3fe031a908e61ccd29085"
"sha256": "c6618fe65517e01b191c970775a1677ddbd22c80afcffad627920e568dc6ff3e"
},
{
"path": "schemas/provider-event.schema.json",
@@ -27,9 +27,9 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<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"),
agent_runtime_version: Some("1.0.0"),
command_digest:
"sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
"sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
requires_provider_policy: true,
},
"cursor" => AcpxReleaseProfile {
@@ -15,7 +15,7 @@ import {
export interface CodexAcpxNativeSessionBackendOptions extends Omit<
CodexAcpxDriverOptions,
"model" | "permissionMode" | "mode" | "systemInstructions" | "providerPolicy" | "runtimeContext"
"model" | "permissionMode" | "mode" | "piThinkingLevel" | "systemInstructions" | "providerPolicy" | "runtimeContext"
> {}
export type AcpxNativeSessionBackendOptions =
@@ -75,6 +75,7 @@ export function createAcpxNativeSessionBackend(
model: input.provider.model,
permissionMode: input.provider.permissionMode ?? "approve-reads",
mode: input.provider.mode,
piThinkingLevel: input.provider.piThinkingLevel,
systemInstructions,
runtimeContext: "runtimeContext" in input ? input.runtimeContext : null,
providerPolicy: { readOnly: "executionMode" in input && input.executionMode === "plan" },
@@ -1,4 +1,5 @@
#!/usr/bin/env node
import { resolvePiThinkingLevel } from "../drivers/acpx/pi-thinking.js";
import { parseProviderMode } from "../contracts/provider-mode.js";
import { acpxProfileActivity, type AcpxActivityAdapter, type AcpxToolEvidence } from "../drivers/acpx/profile-activity.js";
import { createHash } from "node:crypto";
@@ -294,6 +295,7 @@ async function dispatch(
model: params.model,
permissionMode: params.permissionMode,
mode: params.mode,
piThinkingLevel: params.piThinkingLevel,
providerPolicy: params.providerPolicy,
systemInstructions: params.systemInstructions,
runtimeContext: params.runtimeContext,
@@ -1200,7 +1202,7 @@ function safeOutput(value: unknown): Record<string, unknown> {
function parseOpenParams(
value: Record<string, unknown>,
): AcpxSidecarOpenParams {
const fields = new Set(["runtimeDirectory", "normalizedSessionId", "workingDirectory", "agent", "model", "permissionMode", "mode", "permissionModePinned", "providerPolicy", "systemInstructions", "runtimeContext", "tools", "providerSessionKey", "expectedIdentity"]);
const fields = new Set(["runtimeDirectory", "normalizedSessionId", "workingDirectory", "agent", "model", "permissionMode", "mode", "piThinkingLevel", "permissionModePinned", "providerPolicy", "systemInstructions", "runtimeContext", "tools", "providerSessionKey", "expectedIdentity"]);
if (Object.keys(value).some(key => !fields.has(key))) throw new Error("ACPX open parameters include an unsupported field");
const agent = requireQualifiedAgent(value.agent);
const model = requiredText(value.model, "model");
@@ -1226,6 +1228,7 @@ function parseOpenParams(
model,
permissionMode: requiredPermissionMode(value.permissionMode),
...(value.mode === undefined ? {} : { mode: parseProviderMode(value.mode) }),
...(piThinkingLevel ? { piThinkingLevel } : {}),
permissionModePinned: value.permissionModePinned === true,
...(value.providerPolicy == null ? {} : { providerPolicy: parseProviderPolicy(value.providerPolicy) }),
systemInstructions: boundedText(
@@ -1296,6 +1299,7 @@ function parseExpectedIdentity(value: unknown): AcpxExpectedSessionIdentity {
? {}
: { permissionMode: requiredPermissionMode(input.permissionMode) }),
...(input.mode === undefined ? {} : { mode: parseProviderMode(input.mode) }),
...(input.piThinkingLevel === undefined ? {} : { piThinkingLevel: resolvePiThinkingLevel("pi", input.piThinkingLevel) }),
providerLifetimeFenceCandidates: requiredFenceCandidates(
input.providerLifetimeFenceCandidates,
),
@@ -456,6 +456,7 @@ export interface AcpxSessionIdentity {
permissionMode?: "approve-all" | "approve-paperclip" | "approve-reads" | "deny-all";
/** Effective provider mode identifier, bound to the session identity. */
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
providerLifetimeFenceCandidates: readonly [number, number, number];
}
@@ -535,25 +535,25 @@ describe("native task context ownership", () => {
}
it.each(["off", "low", "high", "max"])("requires exact Pi13 thinking level %s in native input", piThinkingLevel => {
const { qualificationModel, reportedModelId: _reported, permissionPolicy: _permission, modelPolicy: _policy, qualificationStatus: _status, ...profile } = QUALIFIED_ACPX_PROFILES.pi;
const { permissionPolicy: _permission, qualificationStatus: _status, ...profile } = QUALIFIED_ACPX_PROFILES.pi;
const qualificationModel = "openrouter/test-provider-model";
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);
for (const mode of [undefined, null, "medium", "minimal", "xhigh", "", { toString: () => "low" }]) expect(() => parseNativeExecutionInput({ ...value, provider: { ...value.provider, piThinkingLevel: mode } })).toThrow(/piThinkingLevel/);
expect(() => parseNativeExecutionInput({ ...value, provider: { ...value.provider, agent: "codex" } })).toThrow(/piThinkingLevel/);
});
it.each(["agent", "plan", "ask"])("round-trips Cursor mode %s through the closed execution contract", cursorMode => {
it.each(["agent", "plan", "ask"])("round-trips Cursor mode %s through the closed execution contract", mode => {
const { qualificationModel: _model, reportedModelId: _reported, permissionPolicy: _permission,
modelPolicy: _policy, qualificationStatus: _status, ...profile } = QUALIFIED_ACPX_PROFILES.cursor;
const value = { ...currentInput(), session: { ...currentInput().session, driverKind: "acpx_runtime" },
provider: { kind: "acpx", agent: "cursor", model: "explicit-model", permissionMode: "deny-all", cursorMode, profile } };
provider: { kind: "acpx", agent: "cursor", model: "explicit-model", permissionMode: "deny-all", mode, profile } };
const result = parseNativeExecutionInput(value);
expect(result.provider).toEqual(value.provider);
expect(parseNativeExecutionInput(result)).toEqual(result);
for (const mode of [null, "", "PLAN", "auto", true, { toString: () => "plan" }]) {
expect(() => parseNativeExecutionInput({ ...value, provider: { ...value.provider, cursorMode: mode } })).toThrow("cursorMode");
for (const mode of [null, "", true, { toString: () => "plan" }]) {
expect(() => parseNativeExecutionInput({ ...value, provider: { ...value.provider, mode: mode } })).toThrow("mode");
}
expect(() => parseNativeExecutionInput({ ...value, provider: { ...value.provider, agent: "copilot" } })).toThrow("cursorMode");
});
it.each([
@@ -130,6 +130,7 @@ export type NativeProviderConfig =
model: string;
permissionMode?: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
/** Present only in persisted v1-v3 inputs. */
permissionPolicy?: "interactive";
profile: NativeAcpxProfileSnapshot;
@@ -145,6 +146,7 @@ export type NativeProviderConfigV4 =
model: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
profile: NativeAcpxProfileSnapshot;
};
@@ -502,7 +504,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
: provider.kind === "aws_agentcore"
? ["kind", "model", "agentCoreProfile", "maxEstimatedSessionCostUsd", "invocationLimits"]
: provider.kind === "acpx"
? ["kind", "agent", "model", isV4 ? "permissionMode" : "permissionPolicy", "profile", ...(isV4 ? ["mode"] : [])]
? ["kind", "agent", "model", isV4 ? "permissionMode" : "permissionPolicy", "profile", "piThinkingLevel", ...(isV4 ? ["mode"] : [])]
: provider.kind === "codex" && isV4
? ["kind", "model", "approvalPolicy", ...(isV5 ? ["reasoningEffort"] : [])]
: provider.kind === "opencode" && isV4
@@ -622,6 +624,12 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
invocationLimits: { maxIterations, maxOutputTokens, timeoutSeconds },
};
} else if (provider.kind === "acpx") {
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.mode !== undefined && !isProviderMode(provider.mode)) {
throw new NativeExecutionInputError("input.provider.mode must be a bounded nonempty provider mode identifier");
}
@@ -673,6 +681,7 @@ export function parseNativeExecutionInput(value: unknown): NativeExecutionInput
? { permissionMode: provider.permissionMode as NativeAcpxPermissionMode }
: { permissionPolicy: "interactive" as const }),
...(provider.mode === undefined ? {} : { mode: provider.mode as string }),
...(provider.piThinkingLevel === undefined ? {} : { piThinkingLevel: provider.piThinkingLevel as "off" | "low" | "high" | "max" }),
profile: {
driverKind: "acpx_runtime",
protocolVersion: 1,
@@ -146,6 +146,7 @@ export interface CodexAcpxDriverOptions {
model: string;
permissionMode?: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
providerPolicy?: { readOnly: boolean };
runtimeContext?: OpenAcpxRuntimeHostOptions["runtimeContext"];
systemInstructions?: string;
@@ -460,6 +461,7 @@ export class CodexAcpxDriver implements HarnessDriver {
model: this.#options.model,
permissionMode: this.#options.permissionMode ?? "approve-all",
mode: this.#options.mode,
piThinkingLevel: this.#options.piThinkingLevel,
providerPolicy: this.#options.providerPolicy,
runtimeContext: this.#options.runtimeContext,
systemInstructions: this.#options.systemInstructions,
@@ -1285,6 +1287,7 @@ class CodexAcpxSession implements HarnessSession {
effectiveModel: identity.effectiveModel,
permissionMode: identity.permissionMode,
...(identity.mode === undefined ? {} : { mode: identity.mode }),
...(identity.piThinkingLevel === undefined ? {} : { piThinkingLevel: identity.piThinkingLevel }),
providerLifetimeFenceCandidates:
identity.providerLifetimeFenceCandidates,
},
@@ -2148,6 +2151,7 @@ function validateRecoverySnapshot(snapshot: PersistedHarnessSession): void {
identity.permissionMode,
)) ||
(identity.mode !== undefined && !isProviderMode(identity.mode)) ||
(identity.piThinkingLevel !== undefined && !["off", "low", "high", "max"].includes(identity.piThinkingLevel)) ||
!validProviderLifetimeFenceCandidates(
identity.providerLifetimeFenceCandidates,
)
@@ -302,6 +302,10 @@ export async function openQualifiedAcpxRuntime(
);
};
const commandLaunches = { count: 0, refreshConsumedCommand: options.refreshConsumedCommand };
const selectedPiThinkingLevel = resolvePiThinkingLevel(options.profile.agent, options.piThinkingLevel);
const piThinking = selectedPiThinkingLevel
? createPiThinkingAdmission(selectedPiThinkingLevel, { restoring: options.restoringSession })
: null;
const modeBinding = createAcpxModeBinding(options.profile.agent, options.mode);
const cursorInstructions = options.profile.agent === "cursor"
? createCursorInstructionAdmission(options.systemInstructions)
@@ -594,7 +598,7 @@ export async function openQualifiedAcpxRuntime(
return runtimePort(
runtime,
handle,
{ ...requireIdentity(handle), ...(modeBinding ? { mode: modeBinding.selectedMode } : {}) },
{ ...requireIdentity(handle), ...(modeBinding ? { mode: modeBinding.selectedMode } : {}), ...(selectedPiThinkingLevel ? { piThinkingLevel: selectedPiThinkingLevel } : {}) },
baseStore,
children,
runtimeCloseTimeoutMs,
@@ -22,12 +22,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = {
"protocolVersion": 1,
"acpxVersion": "0.13.1",
"agent": "pi",
"agentProfileVersion": 1,
"agentProfileVersion": 13,
"agentServerPackage": "pi-acp",
"agentServerVersion": "0.0.33",
"agentRuntimePackage": "@earendil-works/pi-coding-agent",
"agentRuntimeVersion": "0.84.2",
"commandDigest": "sha256:8c696f38296d53d0061fa11534570c5ddd951b63532aed30e0f1fcc676dc169f",
"agentRuntimeVersion": "1.0.0",
"commandDigest": "sha256:fe1e6da01b2a9e4c691ca27cf689d2d6de846a93be6b23fc1e103c9addd7b177",
"permissionPolicy": "interactive"
},
"cursor": {
@@ -2,7 +2,7 @@
* Exact current profile/package/digest admission is checked separately. */
export type AcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14;
const historicalVersions: Readonly<Record<string, readonly AcpxProfileVersion[]>> = {
pi: [1, 2, 3, 4, 5], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5],
pi: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5],
grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5],
cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
};
@@ -1,3 +1,4 @@
import { resolvePiThinkingLevel } from "./pi-thinking.js";
import { parseProviderMode } from "../../contracts/provider-mode.js";
import { createHash } from "node:crypto";
import { realpath, stat } from "node:fs/promises";
@@ -21,6 +22,7 @@ export interface AcpxRecoveryBinding {
effectiveModel: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
profileSessionKey: string;
}
@@ -36,6 +38,7 @@ export interface AcpxIdentityRecord {
effectiveModel: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
providerLifetimeFenceCandidates: readonly [number, number, number];
}
@@ -47,10 +50,12 @@ export async function createAcpxRecoveryBinding(input: {
requestedModel: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
providerPolicy?: { readOnly: boolean; readRoots?: readonly string[]; protectedPaths?: readonly string[] };
}): Promise<AcpxRecoveryBinding> {
validateIdentity(input.normalizedSessionId, "normalized session");
const mode = parseProviderMode(input.mode);
const piThinkingLevel = resolvePiThinkingLevel(input.profile.agent, input.piThinkingLevel);
if (input.providerPolicy !== undefined && typeof input.providerPolicy.readOnly !== "boolean") throw new Error("ACPX recovery requires a valid task execution policy");
if (input.requestedModel !== input.profile.qualificationModel) {
throw new Error("ACPX recovery requested model does not match its admitted profile");
@@ -95,6 +100,7 @@ export async function createAcpxRecoveryBinding(input: {
profileDigest,
permissionMode: input.permissionMode,
...(mode ? { mode } : {}),
...(piThinkingLevel ? { piThinkingLevel } : {}),
}),
).replace("sha256:", "paperclip-");
return {
@@ -108,6 +114,7 @@ export async function createAcpxRecoveryBinding(input: {
effectiveModel: input.requestedModel,
permissionMode: input.permissionMode,
...(mode ? { mode } : {}),
...(piThinkingLevel ? { piThinkingLevel } : {}),
profileSessionKey,
};
}
@@ -129,6 +136,7 @@ export function createAcpxIdentityRecord(
effectiveModel: binding.effectiveModel,
permissionMode: binding.permissionMode,
...(binding.mode ? { mode: binding.mode } : {}),
...(binding.piThinkingLevel ? { piThinkingLevel: binding.piThinkingLevel } : {}),
providerLifetimeFenceCandidates: Object.freeze([
...expected.providerLifetimeFenceCandidates,
]) as readonly [number, number, number],
@@ -155,6 +163,7 @@ export function acpxProviderSessionIdentity(
effectiveModel: record.effectiveModel,
permissionMode: record.permissionMode,
...(record.mode ? { mode: record.mode } : {}),
...(record.piThinkingLevel ? { piThinkingLevel: record.piThinkingLevel } : {}),
providerLifetimeFenceCandidates: record.providerLifetimeFenceCandidates,
};
verifyExpectedAcpxIdentity(identity, binding, record);
@@ -180,7 +189,8 @@ export function verifyExpectedAcpxIdentity(
expected.requestedModel !== binding.requestedModel ||
expected.effectiveModel !== binding.effectiveModel ||
expected.permissionMode !== binding.permissionMode ||
expected.mode !== binding.mode
expected.mode !== binding.mode ||
expected.piThinkingLevel !== binding.piThinkingLevel
) {
throw new Error(
"ACPX recovery identity conflicts with the immutable session configuration",
@@ -200,6 +210,7 @@ export function verifyExpectedAcpxIdentity(
record.effectiveModel !== binding.effectiveModel ||
record.permissionMode !== binding.permissionMode ||
record.mode !== binding.mode ||
record.piThinkingLevel !== binding.piThinkingLevel ||
!sameFenceCandidates(
record.providerLifetimeFenceCandidates,
expected.providerLifetimeFenceCandidates,
@@ -225,6 +236,7 @@ function parsePersistedRecord(value: unknown): AcpxIdentityRecord {
"effectiveModel",
"permissionMode",
"mode",
"piThinkingLevel",
"providerLifetimeFenceCandidates",
]);
return validatedRecord(record);
@@ -252,6 +264,7 @@ function validatedRecord(value: Record<string, unknown>): AcpxIdentityRecord {
throw new Error("ACPX identity permission mode is invalid");
}
if (value.mode !== undefined) parseProviderMode(value.mode);
if (value.piThinkingLevel !== undefined) resolvePiThinkingLevel("pi", value.piThinkingLevel);
validateFenceCandidates(value.providerLifetimeFenceCandidates);
return value as unknown as AcpxIdentityRecord;
}
@@ -281,6 +294,7 @@ function validateExpected(expected: AcpxExpectedSessionIdentity): void {
throw new Error("Expected ACPX permission mode is invalid");
}
if (expected.mode !== undefined) parseProviderMode(expected.mode);
if (expected.piThinkingLevel !== undefined) resolvePiThinkingLevel("pi", expected.piThinkingLevel);
validateFenceCandidates(expected.providerLifetimeFenceCandidates);
}
@@ -81,6 +81,7 @@ const ACPX_ADMISSION_CLEANUP_RESCHEDULE_MS = 1_000;
export interface AcpxRuntimePortIdentity {
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
acpxRecordId: string;
backendSessionId: string;
agentSessionId: string;
@@ -160,6 +161,7 @@ export interface AcpxRuntimePortOpenOptions {
providerSessionKey: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
permissionPolicy: ReturnType<typeof acpxRuntimePermissionPolicy>;
launchEnvironment: Readonly<NodeJS.ProcessEnv>;
/** Kernel credential-home quorum inherited by the provider sentinel. */
@@ -234,6 +236,7 @@ export interface OpenAcpxRuntimeHostOptions {
model: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
systemInstructions?: string;
runtimeContext?: NativeRuntimeContextSnapshot | null;
environment?: NodeJS.ProcessEnv;
@@ -344,6 +347,7 @@ export class AcpxRuntimeHost {
requestedModel: options.model,
permissionMode: options.permissionMode,
mode: resolveAcpxProviderMode(options.agent, options.mode),
piThinkingLevel: resolvePiThinkingLevel(options.agent, options.piThinkingLevel),
...(["cursor", "copilot", "pi"].includes(options.agent) && options.providerPolicy !== undefined
? { providerPolicy: options.providerPolicy } : {}),
}),
@@ -565,6 +569,7 @@ export class AcpxRuntimeHost {
restoringSession: options.expectedIdentity !== undefined,
permissionMode: binding.permissionMode,
mode: binding.mode,
piThinkingLevel: binding.piThinkingLevel,
permissionPolicy: acpxRuntimePermissionPolicy(
binding.permissionMode,
),
@@ -622,6 +627,7 @@ export class AcpxRuntimeHost {
),
dependencies.retainAdmissionCleanup,
);
if (runtimeIdentity.piThinkingLevel !== binding.piThinkingLevel) throw new Error("ACPX runtime Pi thinking level conflicts with session binding");
if (runtimeIdentity.mode !== binding.mode) {
throw new Error("ACPX runtime Provider mode does not match the admitted session configuration");
}
@@ -46,6 +46,7 @@ export interface AcpxSidecarOpenParams {
model: string;
permissionMode: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
permissionModePinned: boolean;
providerPolicy?: { readOnly: boolean };
systemInstructions: string;
@@ -67,6 +68,7 @@ export interface AcpxExpectedSessionIdentity {
effectiveModel: string;
permissionMode?: NativeAcpxPermissionMode;
mode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
providerLifetimeFenceCandidates: readonly [number, number, number];
}
@@ -1,3 +1,4 @@
import { resolvePiThinkingLevel } from "../acpx/pi-thinking.js";
import { isProviderMode } from "../../contracts/provider-mode.js";
import type { PersistedHarnessProviderIdentity } from "../../contracts/harness-driver.js";
import type { NativeUserMessage } from "../../contracts/types.js";
@@ -79,6 +80,7 @@ export function parseProviderIdentity(
"ACPX provider identity contains an invalid permission mode",
);
}
const piThinkingLevel = identity.piThinkingLevel === undefined ? undefined : resolvePiThinkingLevel("pi", identity.piThinkingLevel);
const mode = identity.mode;
if (mode !== undefined && !isProviderMode(mode)) {
throw new Error("ACPX provider identity contains an invalid provider mode");
@@ -109,6 +111,7 @@ export function parseProviderIdentity(
effectiveModel: identity.effectiveModel as string,
...(permissionMode === undefined ? {} : { permissionMode }),
...(mode === undefined ? {} : { mode }),
...(piThinkingLevel === undefined ? {} : { piThinkingLevel }),
providerLifetimeFenceCandidates: fenceCandidates as [
number,
number,
@@ -1,5 +1,6 @@
import { normalizeProviderNotice } from "../drivers/provider-notices.js";
import { liveRunResultFeedback } from "./run-result-feedback.js";
import { resolvePiThinkingLevel } from "../drivers/acpx/pi-thinking.js";
import { createHash, randomUUID } from "node:crypto";
import { resolve } from "node:path";
@@ -1,3 +1,4 @@
import { admittedPiThinkingLevel, resolvePiThinkingLevel } from "../drivers/acpx/pi-thinking.js";
import { configuredEnvironment } from "../configured-environment.js";
import { resolveAcpxProviderMode } from "../drivers/acpx/provider-mode.js";
import { isAcpxCanonicalInputMethod } from "../drivers/acpx/profile-extensions.js";
@@ -1162,6 +1163,7 @@ export interface CapabilityRunnerdCodexTransportOptions {
acpxCandidateProfile?: "pi" | "cursor" | "copilot";
acpxPermissionMode?: NativeAcpxPermissionMode;
acpxMode?: string;
piThinkingLevel?: "off" | "low" | "high" | "max";
acpxPermissionModePinned?: boolean;
acpxSidecarPath?: string;
/** SHA-256 verified by the provider-pack authority before runner startup. */
@@ -3526,6 +3528,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
readonly #bridgedRuntimeInputs = new Map<string, { durableTurnId: string; permission?: boolean }>();
constructor(readonly options: CapabilityRunnerdCodexTransportOptions) {
resolvePiThinkingLevel(options.provider === "acpx" ? options.acpxAgent ?? "codex" : "", options.piThinkingLevel);
if (options.acpxMode !== undefined && options.provider !== "acpx") {
throw new Error("acpxMode requires the ACPX provider");
}
@@ -4752,6 +4755,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
instructions: baseInstructions,
providerPolicy: { readOnly: params.permissions === "paperclip-runner-workspace-read-only" },
...(selectedAcpxMode === undefined ? {} : { mode: selectedAcpxMode }),
...(acpxProfile!.agent === "pi" ? { piThinkingLevel: this.options.piThinkingLevel } : {}),
permissionMode: resolveRunnerdAcpxPermissionMode(
this.options.acpxPermissionMode,
),
@@ -1168,14 +1168,6 @@ export const providerDescriptorSchema = {
],
"maxLength": 240
},
"piThinkingLevel": {
"enum": [
"off",
"low",
"high",
"max"
]
},
"turnControls": {
"$ref": "#/$defs/turnControls"
}
@@ -602,7 +602,7 @@ describe("Cursor mode native execution projection", () => {
}
it.each([undefined, "agent", "plan", "ask"] as const)("preserves mode %s independently of permissions and task planning", mode => {
const result = buildNativeExecutionInput(fixture(mode));
expect(result.provider).toMatchObject({ kind: "acpx", agent: "cursor", cursorMode: mode ?? "agent", permissionMode: "deny-all" });
expect(result.provider).toMatchObject({ kind: "acpx", agent: "cursor", mode: mode ?? "agent", permissionMode: "deny-all" });
expect(result.executionMode).toBe("default");
expect(result.task.workMode).toBe("standard");
});
@@ -114,6 +114,7 @@ export function buildNativeExecutionInput(input: BuildNativeExecutionInput): Nat
throw new Error("native_execution_input_invalid: issue work mode must be standard, planning, or ask");
}
const mode = resolvePaperclipRunnerCursorMode(input.provider, input.acpxAgent, input.acpxSessionMode);
const piThinkingLevel = resolvePaperclipRunnerPiThinkingLevel(input.provider, input.acpxAgent, input.piThinkingLevel);
const executionMode = input.executionMode
?? (input.issue.workMode === "planning" ? "plan" : "default");
const acpxProfile = input.provider === "acpx"
@@ -277,6 +278,7 @@ export function buildNativeExecutionInput(input: BuildNativeExecutionInput): Nat
model: input.model,
permissionMode: input.acpxPermissionMode ?? "approve-all",
...(mode === undefined ? {} : { mode }),
...(piThinkingLevel === undefined ? {} : { piThinkingLevel }),
profile: {
driverKind: acpxProfile!.driverKind,
protocolVersion: acpxProfile!.protocolVersion,
@@ -5624,6 +5624,7 @@ export function providerSessionIdentityFromDurableProviderState(input: {
identity.effectiveModel !== expectedModel ||
identity.permissionMode !== input.execution.provider.permissionMode ||
!acpxRecoveryModeMatches(input.execution.provider, descriptor.mode, identity.mode) ||
!acpxRecoveryPiThinkingMatches(input.execution.provider, descriptor.piThinkingLevel, identity.piThinkingLevel) ||
!["approve-all", "approve-paperclip", "approve-reads", "deny-all"].includes(
String(identity.permissionMode),
) ||
@@ -13035,6 +13036,7 @@ async function createRunnerdBackendWithinSessionClaim(
: resolveAcpxQualification(input.execution.provider, process.env),
acpxPermissionMode: input.execution.provider.permissionMode,
acpxMode: input.execution.provider.mode,
piThinkingLevel: input.execution.provider.piThinkingLevel,
acpxPermissionModePinned:
input.execution.schema === "paperclip.native-execution-input.v4" ||
input.execution.schema === "paperclip.native-execution-input.v5",