mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 07:10:34 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Runner gives each provider durable execution, tool, and completion contracts. > - Cursor Cloud currently has a direct adapter outside that native contract. > - Cloud work needs exact ownership across HTTP, SSE, Stop, and recovery. > - This PR adds a qualified Cloud API driver and verified proxy to Runner. > - Product setup and hosted placement remain separate dependent changes. ## Linked Issues or Issue Description Refs #5664, #11803, #17299, and #17471. Product integration is #18069. Managed-only API placement is #18310. The related direct-adapter model/transcript work is not replaced. No duplicate native Cursor Cloud integration was found. ## What Changed - Add native `cursor_cloud` / `cursor_cloud_api` with explicit model/repository validation, bounded HTTP/SSE, durable per-turn checkpoints and a qualified Node proxy launched through the Rust facade. - Commit terminal custody before publication/release; coalesce consumers, preserve ambiguous create/Stop/recovery fences, reject late tools, and distinguish definitely-unsent oversized requests from ambiguous responses. - Support scoped cold attachment, current authority, reconnect and verified artifact staging. Keep unsupported warm/external and Windows paths closed. - Preserve early source cursors and verify immutable legacy prefixes before suffix replay, including supported compaction/masking. Optional proof publication failure falls back to complete cold verification. - Preserve canonical records at the durable native producer with JSON-safe privacy and the existing 8MiB bound; drain admitted successor flushes. Reconcile upstream semantic/pack/schema/lifecycle changes and retain S3 and legacy CLI checks. ## Verification Core head `37c442ec8dd4`: all 66 check contexts are terminal successful/neutral/skipped, including required `ci / verify` and `ci / e2e`; fresh exact-head Greptile is 5/5 with zero unresolved threads. Dependent Product `81e828d20a77` and Top `dd2959689627` include the later client handoff correction and are running their own fresh gates. Core: 47/47 Rust, 4/4 compiled-native retirement, 19/19 settlement and normal Runner build passed. Runtime Top `9af1a5de42a2`: build and recursive typecheck passed. Earlier source-scoped evidence includes 398 focused tests, five relay tests and token/type gates. The 251-assertion, 7/8-suite run failed physical PostgreSQL teardown; the unchanged stable WSP suite passed 128 through the documented wrapper. The downstream fixture corrections passed 7/7 browser and 121/121 startup tests without timeout/oracle relaxation; a broken-unsubscribe control fails. Hosted runtime `9af1a5de42a2` has a passing normal build and recursive typecheck. Latest dependent heads additionally contain a pure client fix for stale live/history overlap, with 48 focused tests, UI typecheck and token gates passing. Its new client is not deployed in the frozen hosted image; server/native/API/provider source is unchanged by these four downstream files. Fresh hosted Linux admission on `9af1a5de42a2` retired its owned runner/proxy/socket with zero prompts. The saved configuration survived reopening, and Test authenticated the bound key/catalog/selected model without a prompt. Artifact with `claude-sonnet-4-5` completed, downloaded the exact 27 bytes, matched 121 canonical identities against complete logs and independently reached FINISHED. The active Stop retry was clicked within its immutable boundary, produced one canonical interrupted turn and acknowledged run-only cancellation, retained a valid 35.076s quiet window (maximum gap 4.584s), and independently remained CANCELLED/artifact-absent beyond the original 180-second command deadline. All provider-observer credential callbacks, DB clients and loaders closed; accounting is settled and explicitly unpriced. The first fresh Stop retains its failed 10.373s sampling gap and every sample. On the upgraded9af server, the historical F226 artifact was reopened and downloaded with its exact original hash after its provider agent had been deleted/GET404 earlier today. Historical F226 full Artifact/Stop cleanup, Question, corrected Plan and reconnect retain their original identities. Deleting the three newly created disposable provider agents and the new-case post-delete grade remain blocked on explicit user approval after automatic review rejected expanding the two original approved IDs. No new-case cleanup or complete fresh matrix pass is claimed; this is separate from the directly observed task/Stop behavior and retained exact cleanup proof. Retained failures: stale fixture 132/133; E8 48/135 with 87 reset-hook failures; Mac 17 SQL failures/skips; earlier startup-title, title-save, custody/Stop and deletion failures with unknown causes and bounded recoveries. `dea023` Artifact passed 17 checks but Stop failed its false warning and 50 duplicate log identities. Pre-repair ACK failure and original Core CI failure remain recorded; retry alone establishes no cause or full local-suite pass. Provider dollar cost stays UNKNOWN. ## Risks Experimental: Cursor independently acknowledges neither the effective model nor instruction digest before the first prompt. The caller’s explicit values are submitted and behavior checked live. Unpriced provider dollar cost stays UNKNOWN. Only qualified per-turn controllers are admitted. Uncertain work and incomplete cleanup stay fenced. Existing damaged/duplicate archives remain failed evidence. Prefix proof adds bounded object-store I/O; the 8MiB producer bound does not guarantee future providers’ output fits. ## Model Used OpenAI Codex, GPT-6 family, assisted with reasoning, repository tools, code execution, browser verification, and delegated code review. The serving model’s exact API ID and context window are not exposed. No value is inferred from the model catalog. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used and the limits of available model metadata - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have linked public issues or described the issue in this PR - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch describes the change and contains no internal ticket ID - [x] I have run the scoped local tests reported above; pending gates are stated separately - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation - [x] I have considered and documented the risks - [x] All required CI gates pass on the current head - [x] Fresh Greptile is 5/5 with no unresolved findings on the current head - [x] I will address all reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
1065 lines
54 KiB
TypeScript
1065 lines
54 KiB
TypeScript
import { parseNativeCursorCloudConfig, type NativeCursorCloudConfig } from "./cursor-cloud.js";
|
|
import { resolveGrokDisableWebSearch } from "../drivers/acpx/grok-web-search.js";
|
|
import { resolveGrokEffort } from "../drivers/acpx/grok-effort.js";
|
|
import { resolveClaudeEffort } from "../drivers/acpx/claude-effort.js";
|
|
import type { DotBindingSnapshot, MuseBindingSnapshot, ExternalBotProviderKind } from "./external-provider.js";
|
|
import { QUALIFIED_ACPX_VERSION, QUALIFIED_ACPX_PROFILE_DATA } from "../drivers/acpx/generated-profiles.js";
|
|
import { isSupportedAcpxProfileVersion, type AcpxProfileVersion } from "../drivers/acpx/profile-compatibility.js";
|
|
import { isProviderMode } from "./provider-mode.js";
|
|
import { createHash } from "node:crypto";
|
|
import type { PrpStructuredRunResult, PrpTerminalState } from "../protocol/replay-contract.js";
|
|
import { explicitTaskSkillNames, parseNativeRuntimeContext, type NativeRuntimeContextSnapshot } from "./runtime-context.js";
|
|
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V1 = "paperclip.native-execution-input.v1" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V2 = "paperclip.native-execution-input.v2" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V3 = "paperclip.native-execution-input.v3" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V10 = "paperclip.native-execution-input.v10" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V9 = "paperclip.native-execution-input.v9" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V8 = "paperclip.native-execution-input.v8" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V6 = "paperclip.native-execution-input.v6" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V4 = "paperclip.native-execution-input.v4" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA = "paperclip.native-execution-input.v5" as const;
|
|
export const NATIVE_EXECUTION_INPUT_SCHEMA_V7 = "paperclip.native-execution-input.v7" as const;
|
|
export type NativeHomeMode = "isolated" | "preconfigured";
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA_V1 = "paperclip.native-model-envelope.v1" as const;
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA_V2 = "paperclip.native-model-envelope.v2" as const;
|
|
export const NATIVE_MODEL_ENVELOPE_SCHEMA = "paperclip.native-model-envelope.v3" as const;
|
|
export const NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS = 86_400_000;
|
|
|
|
export type NativeExecutionMode = "default" | "plan";
|
|
|
|
export interface NativePlanningContext {
|
|
documentId: string | null;
|
|
baseRevisionId: string | null;
|
|
baseRevisionNumber: number;
|
|
markdown: string;
|
|
sha256: string;
|
|
reviewContext: Record<string, unknown>;
|
|
}
|
|
|
|
export interface StrictCompletionContractInput {
|
|
revision: string;
|
|
objective: string;
|
|
criteria: Array<{ id: string; requirement: string }>;
|
|
}
|
|
|
|
export interface NativeInteractionResponseEnvelope {
|
|
interactionId: string;
|
|
kind:
|
|
| "connection_intent"
|
|
| "suggest_tasks"
|
|
| "ask_user_questions"
|
|
| "request_confirmation"
|
|
| "request_checkbox_confirmation"
|
|
| "request_item_verdicts";
|
|
response: Record<string, unknown>;
|
|
}
|
|
|
|
export interface NativeCredentialBindingRef {
|
|
bindingId: string;
|
|
service: string;
|
|
destination: string;
|
|
expiresAt: string | null;
|
|
displayName: string | null;
|
|
}
|
|
|
|
export type NativeSessionLifecyclePolicy =
|
|
| { mode: "per_turn"; idleTimeoutMs: null }
|
|
| { mode: "warm"; idleTimeoutMs: number };
|
|
|
|
export interface NativeManagedAgentProfileSnapshot {
|
|
profileId: string;
|
|
anthropicAgentId: string;
|
|
agentVersion: string;
|
|
environmentId: string;
|
|
betaVersion: "managed-agents-2026-04-01";
|
|
}
|
|
|
|
export interface NativeAwsAgentCoreProfileSnapshot {
|
|
profileId: string;
|
|
region: string;
|
|
accountId: string;
|
|
harnessArn: string;
|
|
harnessVersion: string;
|
|
endpointArn: string;
|
|
endpointQualifier: string;
|
|
agentRuntimeArn: string;
|
|
memoryArn: string;
|
|
memoryId: string;
|
|
invocationRoleArn: string;
|
|
contextBucket: string;
|
|
contextPrefix: string;
|
|
contextKmsKeyArn: string;
|
|
qualificationRevision: string;
|
|
eventExpiryDays: 90;
|
|
}
|
|
|
|
export type NativeAcpxAgent = keyof typeof QUALIFIED_ACPX_PROFILE_DATA;
|
|
|
|
function isNativeAcpxAgent(value: unknown): value is NativeAcpxAgent {
|
|
return typeof value === "string" && Object.hasOwn(QUALIFIED_ACPX_PROFILE_DATA, value);
|
|
}
|
|
export type NativeCodexApprovalPolicy = "never" | "on-request" | "untrusted";
|
|
export type NativeOpenCodePermissionMode = "allow" | "ask" | "deny";
|
|
export type NativeAcpxPermissionMode = "approve-all" | "approve-paperclip" | "approve-reads" | "deny-all";
|
|
export type NativeCursorMode = "agent" | "plan" | "ask";
|
|
|
|
/** Decodable history; current immutable admission comes from the shared manifest. */
|
|
export type NativeAcpxProfileVersion = AcpxProfileVersion;
|
|
|
|
export interface NativeAcpxProfileSnapshot {
|
|
driverKind: "acpx_runtime";
|
|
protocolVersion: 1;
|
|
acpxVersion: typeof QUALIFIED_ACPX_VERSION;
|
|
agent: NativeAcpxAgent;
|
|
agentProfileVersion: AcpxProfileVersion;
|
|
agentServerPackage: string;
|
|
agentServerVersion: string;
|
|
agentRuntimePackage: string | null;
|
|
agentRuntimeVersion: string | null;
|
|
commandDigest: string;
|
|
}
|
|
|
|
export type NativeProviderConfig =
|
|
| { kind: "cursor_cloud"; model: string; cursorCloud: NativeCursorCloudConfig }
|
|
| { kind: "codex"; model: string | null; approvalPolicy?: NativeCodexApprovalPolicy }
|
|
| { kind: "opencode"; model: string; permissionMode?: NativeOpenCodePermissionMode }
|
|
| {
|
|
kind: "claude_managed";
|
|
model: string;
|
|
managedProfile: NativeManagedAgentProfileSnapshot;
|
|
maxSessionListCostUsd: number;
|
|
}
|
|
| {
|
|
kind: "aws_agentcore";
|
|
model: string;
|
|
agentCoreProfile: NativeAwsAgentCoreProfileSnapshot;
|
|
maxEstimatedSessionCostUsd: number;
|
|
invocationLimits: {
|
|
maxIterations: number;
|
|
maxOutputTokens: number;
|
|
timeoutSeconds: number;
|
|
};
|
|
}
|
|
| {
|
|
kind: "acpx";
|
|
agent: NativeAcpxAgent;
|
|
model: string;
|
|
permissionMode?: NativeAcpxPermissionMode;
|
|
mode?: string;
|
|
piThinkingLevel?: "off" | "low" | "high" | "max";
|
|
/** Present only in persisted v1-v3 inputs. */
|
|
permissionPolicy?: "interactive";
|
|
profile: NativeAcpxProfileSnapshot;
|
|
};
|
|
|
|
export type NativeProviderConfigV4 =
|
|
| { kind: "codex"; model: string | null; approvalPolicy: NativeCodexApprovalPolicy }
|
|
| { kind: "opencode"; model: string; permissionMode: NativeOpenCodePermissionMode }
|
|
| Extract<NativeProviderConfig, { kind: "claude_managed" | "aws_agentcore" | "cursor_cloud" }>
|
|
| {
|
|
kind: "acpx";
|
|
agent: NativeAcpxAgent;
|
|
model: string;
|
|
permissionMode: NativeAcpxPermissionMode;
|
|
mode?: string;
|
|
piThinkingLevel?: "off" | "low" | "high" | "max";
|
|
profile: NativeAcpxProfileSnapshot;
|
|
};
|
|
|
|
export type NativeProviderConfigV5 =
|
|
| Exclude<NativeProviderConfigV4, { kind: "codex" }>
|
|
| (Extract<NativeProviderConfigV4, { kind: "codex" }> & { reasoningEffort?: string; webSearch?: "disabled" });
|
|
|
|
export interface NativeExecutionInputV1 {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V1;
|
|
binding: {
|
|
companyId: string;
|
|
runId: string;
|
|
issueId: string;
|
|
agentId: string;
|
|
agentKeyId?: string;
|
|
executionWorkspaceId: string;
|
|
};
|
|
task: {
|
|
identifier: string;
|
|
title: string;
|
|
description: string | null;
|
|
/** Redacted, server-authored task markdown including the current wake comment. */
|
|
prompt: string;
|
|
workMode: "standard";
|
|
};
|
|
workspace: {
|
|
cwd: string;
|
|
repoUrl: string | null;
|
|
repoRef: string | null;
|
|
branchName: string | null;
|
|
};
|
|
session: {
|
|
normalizedSessionId: string | null;
|
|
driverKind: "cursor_cloud_api" | "codex_app_server" | "opencode_server" | "claude_managed_agents_api" | "aws_agentcore_harness_api" | "acpx_runtime";
|
|
protocolVersion: 1;
|
|
lifecyclePolicy: NativeSessionLifecyclePolicy;
|
|
};
|
|
provider: NativeProviderConfig;
|
|
completionContract: {
|
|
id: string;
|
|
sha256: string;
|
|
schemaVersion: string;
|
|
contract: StrictCompletionContractInput;
|
|
};
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
credentialBindings: NativeCredentialBindingRef[];
|
|
}
|
|
|
|
export interface NativeExecutionInputV2 extends Omit<NativeExecutionInputV1, "schema" | "task"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V2;
|
|
executionMode: NativeExecutionMode;
|
|
task: Omit<NativeExecutionInputV1["task"], "workMode"> & {
|
|
workMode: "standard" | "planning" | "ask" | "skill_test";
|
|
};
|
|
planningContext: NativePlanningContext | null;
|
|
}
|
|
|
|
export interface NativeExecutionInputV3 extends Omit<NativeExecutionInputV2, "schema"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V3;
|
|
runtimeContext: NativeRuntimeContextSnapshot;
|
|
}
|
|
|
|
export interface NativeExecutionInputV4 extends Omit<NativeExecutionInputV3, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V4;
|
|
provider: NativeProviderConfigV4;
|
|
/** Used only after the runtime proves provider-session recovery succeeded. */
|
|
continuationPrompt?: string | null;
|
|
/** Restored only when starting a fresh provider session, never on a resume. */
|
|
initialCommunicationGuidance?: string | null;
|
|
}
|
|
|
|
/** Identity and content revision supplied by the builder that owns the source. */
|
|
export interface NativeCompletionSource {
|
|
kind: "description" | "comment";
|
|
id: string;
|
|
revision: string;
|
|
}
|
|
|
|
export interface NativeCompletionSources {
|
|
/** References are valid only for this exact rendered prompt and contract revision. */
|
|
promptSha256: string;
|
|
contractRevision: string;
|
|
criteria: Array<{ id: string; source: NativeCompletionSource }>;
|
|
}
|
|
|
|
export interface NativeExecutionInputV5 extends Omit<NativeExecutionInputV4, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA;
|
|
provider: NativeProviderConfigV5;
|
|
completionSources?: NativeCompletionSources;
|
|
}
|
|
|
|
export interface NativeExecutionInputV6 extends Omit<NativeExecutionInputV5, "schema" | "provider" | "workspace" | "session"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V6;
|
|
provider: { kind: ExternalBotProviderKind; model: null; binding: DotBindingSnapshot };
|
|
workspace: { access: "none"; cwd: null; repoUrl: null; repoRef: null; branchName: null };
|
|
session: Omit<NativeExecutionInputV5["session"], "driverKind"> & { driverKind: "openai_dot_mcp" | "grok_bot_mcp" };
|
|
}
|
|
|
|
export interface NativeExecutionInputV7 extends Omit<NativeExecutionInputV5, "schema"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V7;
|
|
homeMode: NativeHomeMode;
|
|
}
|
|
|
|
export type NativeProviderConfigV9 = Exclude<NativeProviderConfigV5, { kind: "acpx" }>
|
|
| (Extract<NativeProviderConfigV5, { kind: "acpx" }> & { claudeEffort?: "low" | "medium" | "high" | "xhigh" | "max" });
|
|
export interface NativeExecutionInputV9 extends Omit<NativeExecutionInputV7, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V9;
|
|
provider: NativeProviderConfigV9;
|
|
}
|
|
|
|
export type NativeProviderConfigV10 = Exclude<NativeProviderConfigV9, { kind: "acpx" }>
|
|
| (Extract<NativeProviderConfigV9, { kind: "acpx" }> & { grokEffort?: "minimal" | "low" | "medium" | "high" | "xhigh"; grokDisableWebSearch?: boolean });
|
|
export interface NativeExecutionInputV10 extends Omit<NativeExecutionInputV9, "schema" | "provider"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V10;
|
|
provider: NativeProviderConfigV10;
|
|
}
|
|
|
|
export interface NativeExecutionInputV8 extends Omit<NativeExecutionInputV6, "schema" | "provider" | "session"> {
|
|
schema: typeof NATIVE_EXECUTION_INPUT_SCHEMA_V8;
|
|
provider: { kind: "muse"; model: null; binding: MuseBindingSnapshot; bridgeRevision: "muse-v1" };
|
|
session: Omit<NativeExecutionInputV6["session"], "driverKind"> & { driverKind: "muse_external" };
|
|
}
|
|
|
|
export type NativeExecutionInput = NativeExecutionInputV1 | NativeExecutionInputV2 | NativeExecutionInputV3 | NativeExecutionInputV4 | NativeExecutionInputV5 | NativeExecutionInputV6 | NativeExecutionInputV7 | NativeExecutionInputV8 | NativeExecutionInputV9 | NativeExecutionInputV10;
|
|
|
|
/** The only task data that may enter provider-visible model input. */
|
|
export interface NativeModelEnvelopeV1 {
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA_V1;
|
|
task: NativeExecutionInputV1["task"];
|
|
/** Remote providers have no Paperclip workspace mounted in their service. */
|
|
workspace: Pick<NativeExecutionInputV1["workspace"], "cwd"> | null;
|
|
completionContract: StrictCompletionContractInput;
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
}
|
|
|
|
export interface NativeModelEnvelopeV2 {
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA_V2;
|
|
task: NativeExecutionInputV2["task"];
|
|
executionMode: NativeExecutionMode;
|
|
planningContext: NativePlanningContext | null;
|
|
workspace: Pick<NativeExecutionInputV2["workspace"], "cwd"> | null;
|
|
completionContract: StrictCompletionContractInput;
|
|
interactionResponses: NativeInteractionResponseEnvelope[];
|
|
}
|
|
|
|
|
|
/** Explicit model projection; execution-only fields never enter the model task. */
|
|
export interface NativeModelEnvelopeV3 extends Omit<NativeModelEnvelopeV2, "schema" | "task" | "completionContract"> {
|
|
requestedSkills: string[];
|
|
constraints?: string[];
|
|
schema: typeof NATIVE_MODEL_ENVELOPE_SCHEMA;
|
|
task: Pick<NativeExecutionInputV2["task"], "identifier" | "title" | "prompt" | "workMode">;
|
|
completionContract: Omit<StrictCompletionContractInput, "criteria"> & {
|
|
criteria: Array<{ id: string; requirement: string } | { id: string; source: NativeCompletionSource & { location: "task.prompt" } }>;
|
|
};
|
|
}
|
|
|
|
export interface NativeSessionExecutionResult {
|
|
result: PrpStructuredRunResult;
|
|
terminal: PrpTerminalState;
|
|
turnId: string | null;
|
|
normalizedSessionId: string;
|
|
providerSessionId: string | null;
|
|
driverKind: string;
|
|
driverVersion: string;
|
|
nativeEventCount: number;
|
|
highestContiguousSourceSeq: number;
|
|
usage: Record<string, unknown> | null;
|
|
/** The active durable goal reached a safe turn boundary for run rollover. */
|
|
goalRolloverRequired?: boolean;
|
|
}
|
|
|
|
export class NativeExecutionInputError extends Error {
|
|
readonly code = "native_execution_input_invalid" as const;
|
|
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = "NativeExecutionInputError";
|
|
}
|
|
}
|
|
|
|
function record(value: unknown, path: string): Record<string, unknown> {
|
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
throw new NativeExecutionInputError(`${path} must be an object`);
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function exactKeys(value: Record<string, unknown>, keys: readonly string[], path: string): void {
|
|
const allowed = new Set(keys);
|
|
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
if (unknown.length > 0) {
|
|
throw new NativeExecutionInputError(`${path} contains unknown field ${unknown[0]}`);
|
|
}
|
|
}
|
|
|
|
function text(value: unknown, path: string): string {
|
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
throw new NativeExecutionInputError(`${path} must be a non-empty string`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function nullableText(value: unknown, path: string): string | null {
|
|
return value === null ? null : text(value, path);
|
|
}
|
|
|
|
/**
|
|
* Strictly validates the closed Paperclip-to-runner launch contract. This is
|
|
* intentionally not an extensible metadata bag: new fields require a contract
|
|
* revision and an explicit security review.
|
|
*/
|
|
function parseExternalNativeExecutionInput(input: Record<string, unknown>): NativeExecutionInputV6 {
|
|
const provider = record(input.provider, "input.provider");
|
|
exactKeys(provider, ["kind", "model", "binding"], "input.provider");
|
|
const b = record(provider.binding, "input.provider.binding");
|
|
exactKeys(b, ["bindingId", "bindingGeneration", "companyId", "agentId", "acceptByUnixMs", "expiresAtUnixMs"], "input.provider.binding");
|
|
const binding = record(input.binding, "input.binding");
|
|
const workspace = record(input.workspace, "input.workspace");
|
|
exactKeys(workspace, ["access", "cwd", "repoUrl", "repoRef", "branchName"], "input.workspace");
|
|
const session = record(input.session, "input.session");
|
|
const driverKind = provider.kind === "grok_bot" ? "grok_bot_mcp" : "openai_dot_mcp";
|
|
if (!["openai_dot", "grok_bot"].includes(String(provider.kind)) || provider.model !== null || workspace.access !== "none"
|
|
|| [workspace.cwd, workspace.repoUrl, workspace.repoRef, workspace.branchName].some(v => v !== null)
|
|
|| session.driverKind !== driverKind || record(session.lifecyclePolicy, "input.session.lifecyclePolicy").mode !== "per_turn"
|
|
|| binding.companyId !== b.companyId || binding.agentId !== b.agentId
|
|
|| !Number.isSafeInteger(b.bindingGeneration) || Number(b.bindingGeneration) < 1
|
|
|| !Number.isSafeInteger(b.acceptByUnixMs) || !Number.isSafeInteger(b.expiresAtUnixMs)
|
|
|| Number(b.acceptByUnixMs) < 1 || Number(b.acceptByUnixMs) >= Number(b.expiresAtUnixMs)
|
|
|| Number(b.expiresAtUnixMs) - Number(b.acceptByUnixMs) > 86_400_000
|
|
|| !Array.isArray(input.credentialBindings) || input.credentialBindings.length !== 0) {
|
|
throw new NativeExecutionInputError("External v6 requires a bound per-turn remote service with no filesystem or credential access");
|
|
}
|
|
// Reuse the preceding closed contract's task/context validation. The local
|
|
// validation value never leaves this function or becomes an execution path.
|
|
const common = parseNativeExecutionInput({ ...input, schema: NATIVE_EXECUTION_INPUT_SCHEMA,
|
|
provider: { kind: "codex", model: null, approvalPolicy: "never" },
|
|
session: { ...session, driverKind: "codex_app_server" },
|
|
workspace: { cwd: "/", repoUrl: null, repoRef: null, branchName: null } }) as NativeExecutionInputV5;
|
|
return { ...common, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V6,
|
|
provider: { kind: provider.kind as ExternalBotProviderKind, model: null, binding: {
|
|
bindingId: text(b.bindingId, "input.provider.binding.bindingId"), bindingGeneration: Number(b.bindingGeneration),
|
|
companyId: text(b.companyId, "input.provider.binding.companyId"), agentId: text(b.agentId, "input.provider.binding.agentId"),
|
|
acceptByUnixMs: Number(b.acceptByUnixMs), expiresAtUnixMs: Number(b.expiresAtUnixMs),
|
|
} }, workspace: { access: "none", cwd: null, repoUrl: null, repoRef: null, branchName: null },
|
|
session: { ...common.session, driverKind } };
|
|
}
|
|
|
|
function parseMuseNativeExecutionInput(input: Record<string, unknown>): NativeExecutionInputV8 {
|
|
const provider = record(input.provider, "input.provider");
|
|
exactKeys(provider, ["kind", "model", "binding", "bridgeRevision"], "input.provider");
|
|
const session = record(input.session, "input.session");
|
|
if (provider.kind !== "muse" || provider.bridgeRevision !== "muse-v1" || session.driverKind !== "muse_external") {
|
|
throw new NativeExecutionInputError("Muse v8 requires the muse-v1 bridge and muse_external driver");
|
|
}
|
|
const common = parseExternalNativeExecutionInput({ ...input, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V6,
|
|
provider: { kind: "openai_dot", model: provider.model, binding: provider.binding },
|
|
session: { ...session, driverKind: "openai_dot_mcp" } });
|
|
return { ...common, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V8,
|
|
provider: { kind: "muse", model: null, binding: common.provider.binding, bridgeRevision: "muse-v1" },
|
|
session: { ...common.session, driverKind: "muse_external" } };
|
|
}
|
|
|
|
export function parseNativeExecutionInput(value: unknown): NativeExecutionInput {
|
|
const input = record(value, "input");
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8) return parseMuseNativeExecutionInput(input);
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6) return parseExternalNativeExecutionInput(input);
|
|
const isV10 = input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V10;
|
|
const isV9 = isV10 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V9;
|
|
const isV7 = isV9 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V7;
|
|
const isV5 = isV7 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA;
|
|
const isV4 = isV5 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V4;
|
|
const isV3 = isV4 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V3;
|
|
const isV2 = isV3 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V2;
|
|
exactKeys(input, [
|
|
"schema",
|
|
"binding",
|
|
"task",
|
|
"workspace",
|
|
"session",
|
|
"provider",
|
|
"completionContract",
|
|
"interactionResponses",
|
|
"credentialBindings",
|
|
...(isV2 ? ["executionMode", "planningContext"] : []),
|
|
...(isV3 ? ["runtimeContext"] : []),
|
|
...(isV4 ? ["continuationPrompt", "initialCommunicationGuidance"] : []),
|
|
...(isV5 ? ["completionSources"] : []),
|
|
...(isV7 ? ["homeMode"] : []),
|
|
], "input");
|
|
if (!isV2 && input.schema !== NATIVE_EXECUTION_INPUT_SCHEMA_V1) {
|
|
throw new NativeExecutionInputError(
|
|
`input.schema must be ${NATIVE_EXECUTION_INPUT_SCHEMA_V1}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V2}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V3}, ${NATIVE_EXECUTION_INPUT_SCHEMA_V4}, or ${NATIVE_EXECUTION_INPUT_SCHEMA}`,
|
|
);
|
|
}
|
|
|
|
if (isV7) {
|
|
if (input.homeMode !== "isolated" && input.homeMode !== "preconfigured") {
|
|
throw new NativeExecutionInputError("input.homeMode must be isolated or preconfigured");
|
|
}
|
|
const selected = record(input.provider, "input.provider");
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && input.executionMode === "plan") {
|
|
throw new NativeExecutionInputError("preconfigured ACPX read-only execution is not supported; use isolated mode or native Codex");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && selected.permissionMode !== "approve-all") {
|
|
throw new NativeExecutionInputError("preconfigured home currently requires ACPX approve-all; local permission rules still apply");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind === "acpx" && selected.agent === "codex") {
|
|
throw new NativeExecutionInputError("preconfigured ACPX Codex cannot protect Runner state; use native Codex or isolated mode");
|
|
}
|
|
if (input.homeMode === "preconfigured" && selected.kind !== "codex"
|
|
&& !(selected.kind === "acpx" && selected.agent === "claude")) {
|
|
throw new NativeExecutionInputError("preconfigured home supports native Codex and ACPX Claude only");
|
|
}
|
|
}
|
|
const binding = record(input.binding, "input.binding");
|
|
exactKeys(binding, ["companyId", "runId", "issueId", "agentId", "agentKeyId", "executionWorkspaceId"], "input.binding");
|
|
const task = record(input.task, "input.task");
|
|
exactKeys(task, ["identifier", "title", "description", "prompt", "workMode"], "input.task");
|
|
const workspace = record(input.workspace, "input.workspace");
|
|
exactKeys(workspace, ["cwd", "repoUrl", "repoRef", "branchName"], "input.workspace");
|
|
const session = record(input.session, "input.session");
|
|
exactKeys(session, ["normalizedSessionId", "driverKind", "protocolVersion", "lifecyclePolicy"], "input.session");
|
|
const completionContract = record(input.completionContract, "input.completionContract");
|
|
exactKeys(completionContract, ["id", "sha256", "schemaVersion", "contract"], "input.completionContract");
|
|
const contract = record(completionContract.contract, "input.completionContract.contract");
|
|
exactKeys(contract, ["revision", "objective", "criteria"], "input.completionContract.contract");
|
|
|
|
if (task.workMode !== "standard" && (!isV2 || task.workMode !== "planning" && task.workMode !== "ask" && task.workMode !== "skill_test")) {
|
|
throw new NativeExecutionInputError("input.task.workMode must be standard, planning, ask, or skill_test");
|
|
}
|
|
const executionMode: NativeExecutionMode = isV2 && input.executionMode === "plan" ? "plan" : "default";
|
|
if (isV2 && input.executionMode !== "default" && input.executionMode !== "plan") {
|
|
throw new NativeExecutionInputError("input.executionMode must be default or plan");
|
|
}
|
|
if (isV2 && task.workMode !== "planning" && executionMode === "plan") {
|
|
throw new NativeExecutionInputError("plan execution mode requires planning work mode");
|
|
}
|
|
let planningContext: NativePlanningContext | null = null;
|
|
if (isV2 && input.planningContext !== null) {
|
|
const context = record(input.planningContext, "input.planningContext");
|
|
exactKeys(context, [
|
|
"documentId", "baseRevisionId", "baseRevisionNumber", "markdown", "sha256", "reviewContext",
|
|
], "input.planningContext");
|
|
if (!Number.isSafeInteger(context.baseRevisionNumber) || Number(context.baseRevisionNumber) < 0) {
|
|
throw new NativeExecutionInputError("input.planningContext.baseRevisionNumber must be a non-negative integer");
|
|
}
|
|
planningContext = {
|
|
documentId: context.documentId === null ? null : text(context.documentId, "input.planningContext.documentId"),
|
|
baseRevisionId: context.baseRevisionId === null
|
|
? null
|
|
: text(context.baseRevisionId, "input.planningContext.baseRevisionId"),
|
|
baseRevisionNumber: Number(context.baseRevisionNumber),
|
|
markdown: typeof context.markdown === "string"
|
|
? context.markdown
|
|
: (() => { throw new NativeExecutionInputError("input.planningContext.markdown must be a string"); })(),
|
|
sha256: text(context.sha256, "input.planningContext.sha256"),
|
|
reviewContext: structuredClone(record(context.reviewContext, "input.planningContext.reviewContext")),
|
|
};
|
|
}
|
|
if (isV2 && executionMode === "plan" && planningContext === null) {
|
|
throw new NativeExecutionInputError("input.planningContext is required in plan execution mode");
|
|
}
|
|
if (isV2 && executionMode === "default" && input.planningContext !== null) {
|
|
throw new NativeExecutionInputError("input.planningContext is only valid in plan execution mode");
|
|
}
|
|
if (
|
|
(
|
|
session.driverKind !== "cursor_cloud_api"
|
|
&& session.driverKind !== "codex_app_server"
|
|
&& session.driverKind !== "opencode_server"
|
|
&& session.driverKind !== "claude_managed_agents_api"
|
|
&& session.driverKind !== "aws_agentcore_harness_api"
|
|
&& session.driverKind !== "acpx_runtime"
|
|
)
|
|
|| session.protocolVersion !== 1
|
|
) {
|
|
throw new NativeExecutionInputError("input.session must select a supported protocol version 1 driver");
|
|
}
|
|
const driverKind = session.driverKind as NativeExecutionInputV1["session"]["driverKind"];
|
|
const lifecyclePolicyValue = session.lifecyclePolicy === undefined
|
|
? { mode: "per_turn", idleTimeoutMs: null }
|
|
: record(session.lifecyclePolicy, "input.session.lifecyclePolicy");
|
|
exactKeys(lifecyclePolicyValue, ["mode", "idleTimeoutMs"], "input.session.lifecyclePolicy");
|
|
let lifecyclePolicy: NativeSessionLifecyclePolicy;
|
|
if (lifecyclePolicyValue.mode === "per_turn") {
|
|
if (lifecyclePolicyValue.idleTimeoutMs !== null) {
|
|
throw new NativeExecutionInputError("input.session.lifecyclePolicy.idleTimeoutMs must be null for per_turn");
|
|
}
|
|
lifecyclePolicy = { mode: "per_turn", idleTimeoutMs: null };
|
|
} else if (lifecyclePolicyValue.mode === "warm") {
|
|
if (
|
|
!Number.isSafeInteger(lifecyclePolicyValue.idleTimeoutMs)
|
|
|| Number(lifecyclePolicyValue.idleTimeoutMs) <= 0
|
|
|| Number(lifecyclePolicyValue.idleTimeoutMs) > NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS
|
|
) {
|
|
throw new NativeExecutionInputError(
|
|
`input.session.lifecyclePolicy.idleTimeoutMs must be a positive integer no greater than ${NATIVE_SESSION_IDLE_TIMEOUT_MAX_MS} for warm`,
|
|
);
|
|
}
|
|
lifecyclePolicy = { mode: "warm", idleTimeoutMs: Number(lifecyclePolicyValue.idleTimeoutMs) };
|
|
} else {
|
|
throw new NativeExecutionInputError("input.session.lifecyclePolicy.mode must be per_turn or warm");
|
|
}
|
|
const provider = input.provider === undefined
|
|
? { kind: "codex", model: null }
|
|
: record(input.provider, "input.provider");
|
|
if (
|
|
provider.kind !== "cursor_cloud"
|
|
&& provider.kind !== "codex"
|
|
&& provider.kind !== "opencode"
|
|
&& provider.kind !== "claude_managed"
|
|
&& provider.kind !== "aws_agentcore"
|
|
&& provider.kind !== "acpx"
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.kind must be cursor_cloud, codex, opencode, claude_managed, aws_agentcore, or acpx");
|
|
}
|
|
exactKeys(
|
|
provider,
|
|
provider.kind === "cursor_cloud"
|
|
? ["kind", "model", "cursorCloud"]
|
|
: provider.kind === "claude_managed"
|
|
? ["kind", "model", "managedProfile", "maxSessionListCostUsd"]
|
|
: provider.kind === "aws_agentcore"
|
|
? ["kind", "model", "agentCoreProfile", "maxEstimatedSessionCostUsd", "invocationLimits"]
|
|
: provider.kind === "acpx"
|
|
? ["kind", "agent", "model", isV4 ? "permissionMode" : "permissionPolicy", "profile", "piThinkingLevel", ...(isV4 ? ["mode"] : []), ...(isV9 ? ["claudeEffort"] : []), ...(isV10 ? ["grokEffort", "grokDisableWebSearch"] : [])]
|
|
: provider.kind === "codex" && isV4
|
|
? ["kind", "model", "approvalPolicy", ...(isV5 ? ["reasoningEffort", "webSearch"] : [])]
|
|
: provider.kind === "opencode" && isV4
|
|
? ["kind", "model", "permissionMode"]
|
|
: ["kind", "model"],
|
|
"input.provider",
|
|
);
|
|
if (
|
|
(provider.kind === "cursor_cloud" && session.driverKind !== "cursor_cloud_api")
|
|
|| (provider.kind === "codex" && session.driverKind !== "codex_app_server")
|
|
|| (provider.kind === "opencode" && session.driverKind !== "opencode_server")
|
|
|| (provider.kind === "claude_managed" && session.driverKind !== "claude_managed_agents_api")
|
|
|| (provider.kind === "aws_agentcore" && session.driverKind !== "aws_agentcore_harness_api")
|
|
|| (provider.kind === "acpx" && session.driverKind !== "acpx_runtime")
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.kind does not match input.session.driverKind");
|
|
}
|
|
const providerModel = provider.model === null || provider.model === undefined
|
|
? null
|
|
: text(provider.model, "input.provider.model");
|
|
if (provider.kind === "opencode" && (providerModel === null || !providerModel.includes("/"))) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for opencode in provider/model form");
|
|
}
|
|
let parsedProvider: NativeProviderConfigV9 | NativeProviderConfig | NativeProviderConfigV5;
|
|
if (provider.kind === "cursor_cloud") {
|
|
if (!isV5 || !providerModel || providerModel.length > 240 || /[\x00-\x1f\x7f]/.test(providerModel) || lifecyclePolicy.mode !== "per_turn") {
|
|
throw new NativeExecutionInputError("Cursor Cloud requires a model and per-turn lifecycle in a v5 or later input");
|
|
}
|
|
parsedProvider = { kind: "cursor_cloud", model: providerModel, cursorCloud: parseNativeCursorCloudConfig(provider.cursorCloud) };
|
|
} else if (provider.kind === "claude_managed") {
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for claude_managed");
|
|
}
|
|
const managedProfile = record(provider.managedProfile, "input.provider.managedProfile");
|
|
exactKeys(
|
|
managedProfile,
|
|
["profileId", "anthropicAgentId", "agentVersion", "environmentId", "betaVersion"],
|
|
"input.provider.managedProfile",
|
|
);
|
|
if (managedProfile.betaVersion !== "managed-agents-2026-04-01") {
|
|
throw new NativeExecutionInputError(
|
|
"input.provider.managedProfile.betaVersion must be managed-agents-2026-04-01",
|
|
);
|
|
}
|
|
if (
|
|
typeof provider.maxSessionListCostUsd !== "number"
|
|
|| !Number.isFinite(provider.maxSessionListCostUsd)
|
|
|| provider.maxSessionListCostUsd <= 0
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.maxSessionListCostUsd must be a positive finite number");
|
|
}
|
|
parsedProvider = {
|
|
kind: "claude_managed",
|
|
model: providerModel,
|
|
managedProfile: {
|
|
profileId: text(managedProfile.profileId, "input.provider.managedProfile.profileId"),
|
|
anthropicAgentId: text(
|
|
managedProfile.anthropicAgentId,
|
|
"input.provider.managedProfile.anthropicAgentId",
|
|
),
|
|
agentVersion: text(managedProfile.agentVersion, "input.provider.managedProfile.agentVersion"),
|
|
environmentId: text(managedProfile.environmentId, "input.provider.managedProfile.environmentId"),
|
|
betaVersion: "managed-agents-2026-04-01",
|
|
},
|
|
maxSessionListCostUsd: provider.maxSessionListCostUsd,
|
|
};
|
|
} else if (provider.kind === "aws_agentcore") {
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for aws_agentcore");
|
|
}
|
|
const profile = record(provider.agentCoreProfile, "input.provider.agentCoreProfile");
|
|
exactKeys(profile, [
|
|
"profileId", "region", "accountId", "harnessArn", "harnessVersion", "endpointArn",
|
|
"endpointQualifier", "agentRuntimeArn", "memoryArn", "memoryId", "invocationRoleArn",
|
|
"contextBucket", "contextPrefix", "contextKmsKeyArn", "qualificationRevision", "eventExpiryDays",
|
|
], "input.provider.agentCoreProfile");
|
|
if (profile.eventExpiryDays !== 90) {
|
|
throw new NativeExecutionInputError("input.provider.agentCoreProfile.eventExpiryDays must be 90");
|
|
}
|
|
if (
|
|
typeof provider.maxEstimatedSessionCostUsd !== "number"
|
|
|| !Number.isFinite(provider.maxEstimatedSessionCostUsd)
|
|
|| provider.maxEstimatedSessionCostUsd <= 0
|
|
) {
|
|
throw new NativeExecutionInputError("input.provider.maxEstimatedSessionCostUsd must be a positive finite number");
|
|
}
|
|
const limits = record(provider.invocationLimits, "input.provider.invocationLimits");
|
|
exactKeys(limits, ["maxIterations", "maxOutputTokens", "timeoutSeconds"], "input.provider.invocationLimits");
|
|
const positiveInteger = (value: unknown, path: string): number => {
|
|
if (!Number.isSafeInteger(value) || Number(value) <= 0) {
|
|
throw new NativeExecutionInputError(`${path} must be a positive integer`);
|
|
}
|
|
return Number(value);
|
|
};
|
|
const maxIterations = positiveInteger(limits.maxIterations, "input.provider.invocationLimits.maxIterations");
|
|
const maxOutputTokens = positiveInteger(limits.maxOutputTokens, "input.provider.invocationLimits.maxOutputTokens");
|
|
const timeoutSeconds = positiveInteger(limits.timeoutSeconds, "input.provider.invocationLimits.timeoutSeconds");
|
|
if (maxIterations > 8) throw new NativeExecutionInputError("input.provider.invocationLimits.maxIterations exceeds 8");
|
|
if (maxOutputTokens > 4096) throw new NativeExecutionInputError("input.provider.invocationLimits.maxOutputTokens exceeds 4096");
|
|
if (timeoutSeconds > 300) throw new NativeExecutionInputError("input.provider.invocationLimits.timeoutSeconds exceeds 300");
|
|
parsedProvider = {
|
|
kind: "aws_agentcore",
|
|
model: providerModel,
|
|
agentCoreProfile: {
|
|
profileId: text(profile.profileId, "input.provider.agentCoreProfile.profileId"),
|
|
region: text(profile.region, "input.provider.agentCoreProfile.region"),
|
|
accountId: text(profile.accountId, "input.provider.agentCoreProfile.accountId"),
|
|
harnessArn: text(profile.harnessArn, "input.provider.agentCoreProfile.harnessArn"),
|
|
harnessVersion: text(profile.harnessVersion, "input.provider.agentCoreProfile.harnessVersion"),
|
|
endpointArn: text(profile.endpointArn, "input.provider.agentCoreProfile.endpointArn"),
|
|
endpointQualifier: text(profile.endpointQualifier, "input.provider.agentCoreProfile.endpointQualifier"),
|
|
agentRuntimeArn: text(profile.agentRuntimeArn, "input.provider.agentCoreProfile.agentRuntimeArn"),
|
|
memoryArn: text(profile.memoryArn, "input.provider.agentCoreProfile.memoryArn"),
|
|
memoryId: text(profile.memoryId, "input.provider.agentCoreProfile.memoryId"),
|
|
invocationRoleArn: text(profile.invocationRoleArn, "input.provider.agentCoreProfile.invocationRoleArn"),
|
|
contextBucket: text(profile.contextBucket, "input.provider.agentCoreProfile.contextBucket"),
|
|
contextPrefix: text(profile.contextPrefix, "input.provider.agentCoreProfile.contextPrefix"),
|
|
contextKmsKeyArn: text(profile.contextKmsKeyArn, "input.provider.agentCoreProfile.contextKmsKeyArn"),
|
|
qualificationRevision: text(profile.qualificationRevision, "input.provider.agentCoreProfile.qualificationRevision"),
|
|
eventExpiryDays: 90,
|
|
},
|
|
maxEstimatedSessionCostUsd: provider.maxEstimatedSessionCostUsd,
|
|
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.mode !== undefined && !isProviderMode(provider.mode)) {
|
|
throw new NativeExecutionInputError("input.provider.mode must be a bounded nonempty provider mode identifier");
|
|
}
|
|
if (providerModel === null) {
|
|
throw new NativeExecutionInputError("input.provider.model is required for acpx");
|
|
}
|
|
if (!isNativeAcpxAgent(provider.agent)) {
|
|
throw new NativeExecutionInputError("input.provider.agent must name a registered ACPX profile");
|
|
}
|
|
if (isV4) {
|
|
if (provider.permissionMode !== "approve-all" && provider.permissionMode !== "approve-paperclip" && provider.permissionMode !== "approve-reads" && provider.permissionMode !== "deny-all") {
|
|
throw new NativeExecutionInputError("input.provider.permissionMode must be approve-all, approve-paperclip, approve-reads, or deny-all");
|
|
}
|
|
} else if (provider.permissionPolicy !== "interactive") {
|
|
throw new NativeExecutionInputError("input.provider.permissionPolicy must be interactive");
|
|
}
|
|
const claudeEffort = resolveClaudeEffort(provider.agent, provider.claudeEffort);
|
|
const grokEffort = resolveGrokEffort(provider.agent, provider.grokEffort);
|
|
const grokDisableWebSearch = resolveGrokDisableWebSearch(provider.agent, provider.grokDisableWebSearch);
|
|
const profile = record(provider.profile, "input.provider.profile");
|
|
exactKeys(profile, [
|
|
"driverKind",
|
|
"protocolVersion",
|
|
"acpxVersion",
|
|
"agent",
|
|
"agentProfileVersion",
|
|
"agentServerPackage",
|
|
"agentServerVersion",
|
|
"agentRuntimePackage",
|
|
"agentRuntimeVersion",
|
|
"commandDigest",
|
|
], "input.provider.profile");
|
|
if (
|
|
profile.driverKind !== "acpx_runtime"
|
|
|| profile.protocolVersion !== 1
|
|
|| profile.acpxVersion !== QUALIFIED_ACPX_VERSION
|
|
|| profile.agent !== provider.agent
|
|
|| !isSupportedAcpxProfileVersion(provider.agent, profile.agentProfileVersion)
|
|
) {
|
|
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)) {
|
|
throw new NativeExecutionInputError("input.provider.profile agent runtime package and version must both be present or null");
|
|
}
|
|
parsedProvider = {
|
|
kind: "acpx",
|
|
agent: provider.agent,
|
|
model: providerModel,
|
|
...(isV4
|
|
? { permissionMode: provider.permissionMode as NativeAcpxPermissionMode }
|
|
: { permissionPolicy: "interactive" as const }),
|
|
...(provider.mode === undefined ? {} : { mode: provider.mode as string }),
|
|
...(claudeEffort === undefined ? {} : { claudeEffort }),
|
|
...(grokEffort === undefined ? {} : { grokEffort }),
|
|
...(grokDisableWebSearch === undefined ? {} : { grokDisableWebSearch }),
|
|
...(provider.piThinkingLevel === undefined ? {} : { piThinkingLevel: provider.piThinkingLevel as "off" | "low" | "high" | "max" }),
|
|
profile: {
|
|
driverKind: "acpx_runtime",
|
|
protocolVersion: 1,
|
|
acpxVersion: QUALIFIED_ACPX_VERSION,
|
|
agent: provider.agent,
|
|
agentProfileVersion: profile.agentProfileVersion,
|
|
agentServerPackage: text(profile.agentServerPackage, "input.provider.profile.agentServerPackage"),
|
|
agentServerVersion: text(profile.agentServerVersion, "input.provider.profile.agentServerVersion"),
|
|
agentRuntimePackage: runtimePackage,
|
|
agentRuntimeVersion: runtimeVersion,
|
|
commandDigest: text(profile.commandDigest, "input.provider.profile.commandDigest"),
|
|
},
|
|
};
|
|
} else if (provider.kind === "opencode") {
|
|
if (isV4 && provider.permissionMode !== "allow" && provider.permissionMode !== "ask" && provider.permissionMode !== "deny") {
|
|
throw new NativeExecutionInputError("input.provider.permissionMode must be allow, ask, or deny");
|
|
}
|
|
parsedProvider = {
|
|
kind: "opencode",
|
|
model: providerModel!,
|
|
...(isV4
|
|
? { permissionMode: provider.permissionMode as NativeOpenCodePermissionMode }
|
|
: {}),
|
|
};
|
|
} else {
|
|
if (isV4 && provider.approvalPolicy !== "never" && provider.approvalPolicy !== "on-request" && provider.approvalPolicy !== "untrusted") {
|
|
throw new NativeExecutionInputError("input.provider.approvalPolicy must be never, on-request, or untrusted");
|
|
}
|
|
if (isV5 && provider.reasoningEffort !== undefined && !["minimal", "low", "medium", "high", "xhigh", "max", "ultra"].includes(String(provider.reasoningEffort))) {
|
|
throw new NativeExecutionInputError("input.provider.reasoningEffort is unsupported");
|
|
}
|
|
if (isV5 && provider.webSearch !== undefined && provider.webSearch !== "disabled") {
|
|
throw new NativeExecutionInputError("input.provider.webSearch must be disabled when specified");
|
|
}
|
|
parsedProvider = {
|
|
kind: "codex",
|
|
model: providerModel,
|
|
...(isV4
|
|
? { approvalPolicy: provider.approvalPolicy as NativeCodexApprovalPolicy }
|
|
: {}),
|
|
...(isV5 && provider.reasoningEffort !== undefined
|
|
? { reasoningEffort: text(provider.reasoningEffort, "input.provider.reasoningEffort") }
|
|
: {}),
|
|
...(isV5 && provider.webSearch === "disabled" ? { webSearch: "disabled" as const } : {}),
|
|
};
|
|
}
|
|
if (!Array.isArray(contract.criteria) || contract.criteria.length === 0) {
|
|
throw new NativeExecutionInputError("input.completionContract.contract.criteria must not be empty");
|
|
}
|
|
const criteria = contract.criteria.map((entry, index) => {
|
|
const criterion = record(entry, `input.completionContract.contract.criteria[${index}]`);
|
|
exactKeys(criterion, ["id", "requirement"], `input.completionContract.contract.criteria[${index}]`);
|
|
return { id: text(criterion.id, `criteria[${index}].id`), requirement: text(criterion.requirement, `criteria[${index}].requirement`) };
|
|
});
|
|
|
|
if (!Array.isArray(input.interactionResponses) || !Array.isArray(input.credentialBindings)) {
|
|
throw new NativeExecutionInputError("interactionResponses and credentialBindings must be arrays");
|
|
}
|
|
const interactionResponses = input.interactionResponses.map((entry, index) => {
|
|
const response = record(entry, `input.interactionResponses[${index}]`);
|
|
exactKeys(response, ["interactionId", "kind", "response"], `input.interactionResponses[${index}]`);
|
|
if (![
|
|
"connection_intent",
|
|
"suggest_tasks",
|
|
"ask_user_questions",
|
|
"request_confirmation",
|
|
"request_checkbox_confirmation",
|
|
"request_item_verdicts",
|
|
].includes(String(response.kind))) {
|
|
throw new NativeExecutionInputError(`input.interactionResponses[${index}].kind is unsupported`);
|
|
}
|
|
const kind = response.kind as NativeInteractionResponseEnvelope["kind"];
|
|
return {
|
|
interactionId: text(response.interactionId, `input.interactionResponses[${index}].interactionId`),
|
|
kind,
|
|
response: structuredClone(record(response.response, `input.interactionResponses[${index}].response`)),
|
|
};
|
|
});
|
|
const credentialBindings = input.credentialBindings.map((entry, index) => {
|
|
const bindingRef = record(entry, `input.credentialBindings[${index}]`);
|
|
exactKeys(bindingRef, ["bindingId", "service", "destination", "expiresAt", "displayName"], `input.credentialBindings[${index}]`);
|
|
return {
|
|
bindingId: text(bindingRef.bindingId, `credentialBindings[${index}].bindingId`),
|
|
service: text(bindingRef.service, `credentialBindings[${index}].service`),
|
|
destination: text(bindingRef.destination, `credentialBindings[${index}].destination`),
|
|
expiresAt: nullableText(bindingRef.expiresAt, `credentialBindings[${index}].expiresAt`),
|
|
displayName: nullableText(bindingRef.displayName, `credentialBindings[${index}].displayName`),
|
|
};
|
|
});
|
|
|
|
const common = {
|
|
binding: {
|
|
companyId: text(binding.companyId, "input.binding.companyId"),
|
|
runId: text(binding.runId, "input.binding.runId"),
|
|
issueId: text(binding.issueId, "input.binding.issueId"),
|
|
agentId: text(binding.agentId, "input.binding.agentId"),
|
|
...(binding.agentKeyId === undefined ? {} : { agentKeyId: text(binding.agentKeyId, "input.binding.agentKeyId") }),
|
|
executionWorkspaceId: text(binding.executionWorkspaceId, "input.binding.executionWorkspaceId"),
|
|
},
|
|
task: {
|
|
identifier: text(task.identifier, "input.task.identifier"),
|
|
title: text(task.title, "input.task.title"),
|
|
description: nullableText(task.description, "input.task.description"),
|
|
// `prompt` was added after native run inputs were already persisted.
|
|
// Recovery must retain those runs, so derive the same bounded model
|
|
// prompt from their task fields instead of making them unrecoverable.
|
|
prompt: text(task.prompt ?? task.description ?? task.title, "input.task.prompt"),
|
|
workMode: task.workMode as "standard" | "planning" | "ask" | "skill_test",
|
|
},
|
|
workspace: {
|
|
cwd: text(workspace.cwd, "input.workspace.cwd"),
|
|
repoUrl: nullableText(workspace.repoUrl, "input.workspace.repoUrl"),
|
|
repoRef: nullableText(workspace.repoRef, "input.workspace.repoRef"),
|
|
branchName: nullableText(workspace.branchName, "input.workspace.branchName"),
|
|
},
|
|
session: {
|
|
normalizedSessionId: nullableText(session.normalizedSessionId, "input.session.normalizedSessionId"),
|
|
driverKind,
|
|
protocolVersion: 1 as const,
|
|
lifecyclePolicy,
|
|
},
|
|
provider: parsedProvider,
|
|
completionContract: {
|
|
id: text(completionContract.id, "input.completionContract.id"),
|
|
sha256: text(completionContract.sha256, "input.completionContract.sha256"),
|
|
schemaVersion: text(completionContract.schemaVersion, "input.completionContract.schemaVersion"),
|
|
contract: {
|
|
revision: text(contract.revision, "input.completionContract.contract.revision"),
|
|
objective: text(contract.objective, "input.completionContract.contract.objective"),
|
|
criteria,
|
|
},
|
|
},
|
|
interactionResponses,
|
|
credentialBindings,
|
|
};
|
|
if (!isV2) {
|
|
return {
|
|
...common,
|
|
schema: NATIVE_EXECUTION_INPUT_SCHEMA_V1,
|
|
task: { ...common.task, workMode: "standard" },
|
|
};
|
|
}
|
|
const current = {
|
|
...common,
|
|
executionMode,
|
|
planningContext,
|
|
};
|
|
if (!isV3) return { ...current, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V2 };
|
|
const withRuntimeContext = { ...current, runtimeContext: parseNativeRuntimeContext(input.runtimeContext) };
|
|
if (!isV4) return { ...withRuntimeContext, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V3 };
|
|
const withPermissions = {
|
|
...withRuntimeContext,
|
|
...(input.continuationPrompt !== undefined ? { continuationPrompt: nullableText(input.continuationPrompt, "input.continuationPrompt") } : {}),
|
|
...(input.initialCommunicationGuidance !== undefined ? { initialCommunicationGuidance: nullableText(input.initialCommunicationGuidance, "input.initialCommunicationGuidance") } : {}),
|
|
provider: parsedProvider as NativeProviderConfigV4,
|
|
};
|
|
if (!isV5) return { ...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V4 };
|
|
if (isV10) return {
|
|
...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V10, homeMode: input.homeMode as NativeHomeMode,
|
|
provider: parsedProvider as NativeProviderConfigV10,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
if (isV9) return {
|
|
...withPermissions, schema: NATIVE_EXECUTION_INPUT_SCHEMA_V9, homeMode: input.homeMode as NativeHomeMode,
|
|
provider: parsedProvider as NativeProviderConfigV9,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
return {
|
|
...withPermissions,
|
|
...(isV7 ? { schema: NATIVE_EXECUTION_INPUT_SCHEMA_V7, homeMode: input.homeMode as NativeHomeMode } : { schema: NATIVE_EXECUTION_INPUT_SCHEMA }),
|
|
provider: parsedProvider as NativeProviderConfigV5,
|
|
...(input.completionSources !== undefined ? { completionSources: parseCompletionSources(input.completionSources) } : {}),
|
|
};
|
|
}
|
|
|
|
function parseCompletionSources(value: unknown): NativeCompletionSources {
|
|
const sources = record(value, "input.completionSources");
|
|
exactKeys(sources, ["promptSha256", "contractRevision", "criteria"], "input.completionSources");
|
|
const digest = (value: unknown, path: string): string => {
|
|
const result = text(value, path);
|
|
if (!/^[a-f0-9]{64}$/.test(result)) throw new NativeExecutionInputError(`${path} must be a sha256 digest`);
|
|
return result;
|
|
};
|
|
if (!Array.isArray(sources.criteria)) throw new NativeExecutionInputError("input.completionSources.criteria must be an array");
|
|
const ids = new Set<string>();
|
|
return {
|
|
promptSha256: digest(sources.promptSha256, "input.completionSources.promptSha256"),
|
|
contractRevision: text(sources.contractRevision, "input.completionSources.contractRevision"),
|
|
criteria: sources.criteria.map((value) => {
|
|
const entry = record(value, "completion source criterion");
|
|
exactKeys(entry, ["id", "source"], "completion source criterion");
|
|
const id = text(entry.id, "completion source criterion.id");
|
|
if (ids.has(id)) throw new NativeExecutionInputError("duplicate completion source criterion");
|
|
ids.add(id);
|
|
const source = record(entry.source, "completion source");
|
|
exactKeys(source, ["kind", "id", "revision"], "completion source");
|
|
if (source.kind !== "description" && source.kind !== "comment") throw new NativeExecutionInputError("invalid completion source kind");
|
|
return { id, source: { kind: source.kind, id: text(source.id, "completion source.id"), revision: digest(source.revision, "completion source.revision") } };
|
|
}),
|
|
};
|
|
}
|
|
|
|
export interface NativeContinuationEnvelope {
|
|
schema: "paperclip.native-continuation.v1";
|
|
events: string;
|
|
completion: { revision: string; criterionIds: string[]; instruction: string };
|
|
}
|
|
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput, options: { resumedSession: true }): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3 | NativeContinuationEnvelope;
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3;
|
|
export function buildNativeModelEnvelope(input: NativeExecutionInput, options?: { resumedSession: boolean }): NativeModelEnvelopeV1 | NativeModelEnvelopeV2 | NativeModelEnvelopeV3 | NativeContinuationEnvelope {
|
|
if (options?.resumedSession && "continuationPrompt" in input && input.continuationPrompt) {
|
|
return {
|
|
schema: "paperclip.native-continuation.v1",
|
|
events: input.continuationPrompt,
|
|
completion: {
|
|
revision: input.completionContract.contract.revision,
|
|
criterionIds: input.completionContract.contract.criteria.map((criterion) => criterion.id),
|
|
instruction: "Before ending this turn, obtain one accepted paperclip_finish or paperclip_block result. Earlier reports belong to earlier turns; a final message alone does not complete this turn.",
|
|
},
|
|
};
|
|
}
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V1) {
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA_V1,
|
|
task: structuredClone(input.task),
|
|
workspace: input.provider.kind === "cursor_cloud" || input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore"
|
|
? null
|
|
: { cwd: input.workspace.cwd },
|
|
completionContract: structuredClone(input.completionContract.contract),
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|
|
if (input.schema === NATIVE_EXECUTION_INPUT_SCHEMA || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V7 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V9 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V10) {
|
|
const sourceBinding = input.completionSources;
|
|
const references = sourceBinding?.contractRevision === input.completionContract.contract.revision
|
|
&& sourceBinding.promptSha256 === createHash("sha256").update(input.task.prompt).digest("hex")
|
|
? new Map(sourceBinding.criteria.map((criterion) => [criterion.id, criterion.source]))
|
|
: new Map<string, NativeCompletionSource>();
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA,
|
|
requestedSkills: explicitTaskSkillNames(input.task.description, input.runtimeContext.skills.map((skill) => skill.runtimeName)),
|
|
task: {
|
|
identifier: input.task.identifier,
|
|
title: input.task.title,
|
|
prompt: !options?.resumedSession && input.initialCommunicationGuidance
|
|
? `${input.initialCommunicationGuidance}\n\n${input.task.prompt}`
|
|
: input.task.prompt,
|
|
workMode: input.task.workMode,
|
|
},
|
|
executionMode: input.executionMode,
|
|
planningContext: structuredClone(input.planningContext),
|
|
workspace: input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V6 || input.schema === NATIVE_EXECUTION_INPUT_SCHEMA_V8 || input.provider.kind === "cursor_cloud" || input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore" ? null : { cwd: input.workspace.cwd },
|
|
completionContract: {
|
|
...structuredClone(input.completionContract.contract),
|
|
criteria: input.completionContract.contract.criteria.map((criterion) => {
|
|
const source = references.get(criterion.id);
|
|
return source && source.revision === createHash("sha256").update(criterion.requirement.trim()).digest("hex")
|
|
&& (source.kind !== "description" || source.id === input.binding.issueId)
|
|
? { id: criterion.id, source: { ...source, location: "task.prompt" as const } } : { ...criterion };
|
|
}),
|
|
},
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|
|
return {
|
|
schema: NATIVE_MODEL_ENVELOPE_SCHEMA_V2,
|
|
task: {
|
|
...structuredClone(input.task),
|
|
prompt: !options?.resumedSession && "initialCommunicationGuidance" in input && input.initialCommunicationGuidance
|
|
? `${input.initialCommunicationGuidance}\n\n${input.task.prompt}`
|
|
: input.task.prompt,
|
|
},
|
|
executionMode: input.executionMode,
|
|
planningContext: structuredClone(input.planningContext),
|
|
workspace: input.provider.kind === "cursor_cloud" || input.provider.kind === "claude_managed" || input.provider.kind === "aws_agentcore"
|
|
? null
|
|
: { cwd: input.workspace.cwd },
|
|
completionContract: structuredClone(input.completionContract.contract),
|
|
interactionResponses: structuredClone(input.interactionResponses),
|
|
};
|
|
}
|