mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Preserve Pi thinking and recovery alongside current provider modes
This commit is contained in:
1 parent
945330e8c9
commit
5506014e1f
22 files changed
+79
-30
No files matched your search
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user