mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Extract Runner provider launch preparation (#16977)
## Thinking Path > - Paperclip manages work for AI agents. > - Runner starts providers in local and remote environments. > - Core selects the provider and task policy. > - The execution environment supplies paths and installed files. > - The transport currently combines these inputs in one large method. > - This PR extracts provider descriptor construction before a separate plugin preparation change. ## Linked Issues or Issue Description **What existing behavior does this improve?** Runner provider launch preparation. **Current behavior** The PRP transport constructs each provider descriptor inside startup. This couples provider configuration to transport lifecycle code. **Proposed behavior** The transport calls one built-in descriptor builder. The builder takes Core provider requirements and execution-environment locations as separate inputs. File staging, provider validation logic, and session rules keep their current behavior. The required Copilot source-identity update changes profile admission metadata. **Reason and benefit** This gives launch preparation a separate unit of code. A later PR can add plugin-owned preparation without adding ownership flags to each provider backend. Refs #15406, #16486. This replaces the earlier installed-home implementation in this PR. #16978 remains a draft. ## What Changed - Extract provider descriptor construction and its existing permission helpers. - Keep helper exports at their previous import path. - Add focused tests for selection, permissions, locations, and managed profile rejection. - Update the Copilot source identity. Bind the extracted file and retain the v49 history. The source attestation requires v50 after this move. This changes the admitted profile identity even though no execution behavior changes are intended. - Document the boundary and its current limits. ## Verification - 338 focused builder, transport, Codex security, and Copilot identity tests passed on the rebased head. - `pnpm -r typecheck` passed. `pnpm build` passed. - The full root `pnpm test:run` on the pre-rebase head is still in progress. It has reported five failures in unchanged server tests. One isolated failure rejects the word `private` from this checkout's `/private/tmp` stack path. The full root suite is not reported as passing. - All 56 CI checks passed on the rebased head. Four optional checks were skipped. The branch is mergeable. Workspace typecheck and build passed on this head. - Standard Greptile and Apex each scored commit `47f631883d8f417f6a818500ebe4e77e22cc70b8` 5/5. The rebase onto master changed only Copilot identity metadata, history handling, and related tests. `git range-diff` confirms that the launch-preparation implementation is unchanged. The existing Apex result remains the review evidence; it did not review the new commit hash. - No live provider calls or Cloud deployment were run. ## Risks - A moved field could change provider startup. Existing transport tests and the new boundary tests check the projection. - Copilot source binding requires a new identity after the move. Existing artifacts and retained sessions must match the new admitted profile. This PR does not claim fresh live qualification. - This PR does not enable plugin-owned preparation. File materialization remains in the existing SDK path. ## Model Used - OpenAI Codex, GPT-6. Reasoning and repository tools. The exact serving model identifier and context size are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [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 either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3bbc2104b6
commit
05f7079ecb
11 files changed
+503
-280
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# Runner launch preparation
|
||||
|
||||
Core selects the provider, model, instructions, tools, and task permissions.
|
||||
The execution environment supplies executable paths and filesystem locations.
|
||||
|
||||
The built-in TypeScript implementation constructs the provider descriptor in
|
||||
`createRunnerProviderLaunch`. The transport sends this descriptor with
|
||||
`run.prepare`. Provider backends keep their existing validation and session rules.
|
||||
|
||||
This separation does not change file preparation. The SDK still prepares local
|
||||
files and stages remote files through the existing execution driver.
|
||||
It does not enable plugin-owned launch preparation.
|
||||
@@ -32,12 +32,12 @@
|
||||
"requiresProviderPolicy": true
|
||||
},
|
||||
"copilot": {
|
||||
"agentProfileVersion": 49,
|
||||
"agentProfileVersion": 50,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702",
|
||||
"commandDigest": "sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4",
|
||||
"requiresProviderPolicy": true
|
||||
},
|
||||
"claude": {
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option<AcpxReleaseProfile> {
|
||||
agent_runtime_package: None,
|
||||
agent_runtime_version: None,
|
||||
command_digest:
|
||||
"sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702",
|
||||
"sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4",
|
||||
requires_provider_policy: true,
|
||||
},
|
||||
"claude" => AcpxReleaseProfile {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { fileURLToPath } from "node:url";
|
||||
import { resolve, dirname } from "node:path";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const fixture = resolve(root, "test/fixtures/copilot-profile-v49-identity.json");
|
||||
const fixture = resolve(root, "test/fixtures/copilot-profile-v50-identity.json");
|
||||
const historical = JSON.parse(readFileSync(resolve(root, "test/fixtures/copilot-profile-v12-identity.json"), "utf8"));
|
||||
const declaration = { ...historical.declaration, agentProfileVersion: 49 };
|
||||
const declaration = { ...historical.declaration, agentProfileVersion: 50 };
|
||||
delete declaration.permissionIdentitySourceSha256;
|
||||
const sources = {
|
||||
displayEventSourceSha256: "src/drivers/acpx/copilot-events.ts",
|
||||
@@ -31,6 +31,7 @@ const sources = {
|
||||
nativeBackendSourceSha256: "src/backends/codex-native-backend.ts",
|
||||
nativeBackendFactorySourceSha256: "src/backends/native-backend-factory.ts",
|
||||
nativeInstructionsSourceSha256: "src/backends/runtime-context.ts",
|
||||
providerLaunchSourceSha256: "src/live/runner-provider-launch.ts",
|
||||
runtimeRequestBridgeSourceSha256: "src/live/runnerd-codex-transport.ts",
|
||||
runtimeRequestFacadeSourceSha256: "src/drivers/codex/codex-session-notifications.ts",
|
||||
runtimeRequestNotificationSourceSha256: "src/drivers/codex/codex-thread-normalization.ts",
|
||||
|
||||
@@ -38,6 +38,7 @@ const SEMANTIC_RECEIPT_SOURCES = [
|
||||
["../../contracts/completion-result.ts", "semanticCompletionContractSourceSha256"],
|
||||
["../../contracts/native-execution.ts", "nativeExecutionSourceSha256"],
|
||||
["installation-integrity.ts", "guardedInstallationSourceSha256"],
|
||||
["../../live/runner-provider-launch.ts", "providerLaunchSourceSha256"],
|
||||
["../../live/runnerd-codex-transport.ts", "runtimeRequestBridgeSourceSha256"],
|
||||
["../codex/codex-session-notifications.ts", "runtimeRequestFacadeSourceSha256"],
|
||||
["../codex/codex-thread-normalization.ts", "runtimeRequestNotificationSourceSha256"],
|
||||
@@ -85,10 +86,20 @@ async function withCopilotTestPlatform<T>(arch: "x64" | "arm64", run: () => Prom
|
||||
vi.mock("./installation-integrity.js", () => ({ verifyNativeAcpxInstallation: vi.fn() }));
|
||||
|
||||
describe("Copilot build-owned installation", () => {
|
||||
it("admits profile49 after bounded completion-recovery compatibility revalidation", () => {
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(49);
|
||||
it("admits the current profile after launch preparation extraction", () => {
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.agentProfileVersion).toBe(50);
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.qualificationStatus).toBeUndefined();
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702");
|
||||
expect(QUALIFIED_ACPX_PROFILES.copilot.commandDigest).toBe("sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4");
|
||||
});
|
||||
|
||||
it("preserves v49 while binding the extracted provider launch builder", () => {
|
||||
const prior = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v49-identity.json", import.meta.url), "utf8"));
|
||||
const current = JSON.parse(readFileSync(new URL("../../../test/fixtures/copilot-profile-v50-identity.json", import.meta.url), "utf8"));
|
||||
expect(prior.commandDigest).toBe("sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702");
|
||||
expect(Object.keys(current.declaration).filter(key => current.declaration[key] !== prior.declaration[key]).sort()).toEqual([
|
||||
"agentProfileVersion", "profileCompatibilitySourceSha256", "providerLaunchSourceSha256", "runtimeRequestBridgeSourceSha256",
|
||||
]);
|
||||
expect(isSupportedAcpxProfileVersion("copilot", 49)).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves qualified v48 while binding completion-proposal recovery in v49", async () => {
|
||||
|
||||
@@ -48,12 +48,12 @@ export const QUALIFIED_ACPX_PROFILE_DATA = {
|
||||
"protocolVersion": 1,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 49,
|
||||
"agentProfileVersion": 50,
|
||||
"agentServerPackage": "@github/copilot",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"agentRuntimePackage": null,
|
||||
"agentRuntimeVersion": null,
|
||||
"commandDigest": "sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702",
|
||||
"commandDigest": "sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4",
|
||||
"permissionPolicy": "interactive"
|
||||
},
|
||||
"claude": {
|
||||
|
||||
@@ -2,13 +2,13 @@ import { QUALIFIED_ACPX_PROFILE_DATA } from "./generated-profiles.js";
|
||||
|
||||
/** Decodable historical profile revisions, not permission to launch them.
|
||||
* Exact current profile/package/digest admission is checked separately. */
|
||||
type HistoricalAcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48;
|
||||
type HistoricalAcpxProfileVersion = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49;
|
||||
type CurrentAcpxProfileVersion = typeof QUALIFIED_ACPX_PROFILE_DATA[keyof typeof QUALIFIED_ACPX_PROFILE_DATA]["agentProfileVersion"];
|
||||
export type AcpxProfileVersion = HistoricalAcpxProfileVersion | CurrentAcpxProfileVersion;
|
||||
|
||||
const historicalVersions: Readonly<Record<string, readonly HistoricalAcpxProfileVersion[]>> = {
|
||||
pi: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], claude: [1, 2, 3, 4, 5], codex: [1, 2, 3, 4, 5],
|
||||
grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48],
|
||||
grok: [1, 2, 3, 4, 5], copilot: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
|
||||
cursor: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createRunnerProviderLaunch, type RunnerProviderLaunchEnvironment, type RunnerProviderLaunchRequirements } from "./runner-provider-launch.js";
|
||||
|
||||
const requirements: RunnerProviderLaunchRequirements = {
|
||||
identity: { runnerInstanceId: "runner", environmentLeaseId: "lease", runId: "run",
|
||||
normalizedSessionId: "session", turnId: "turn", itemId: "item" },
|
||||
provider: "codex", model: "selected-model", approvalPolicy: "on-request",
|
||||
instructions: "Follow the assigned instructions.", runtimeContext: null,
|
||||
readOnly: true, preparedConversation: false, includeCollaborationModeInstructions: false,
|
||||
settings: {},
|
||||
};
|
||||
const environment: RunnerProviderLaunchEnvironment = {
|
||||
options: { codexArgs: ["app-server"] }, root: "/execution/state", cwd: "/execution/workspace",
|
||||
codexHome: "/execution/home/.codex", providerNodeCommand: "/runtime/node",
|
||||
codexCommand: "/runtime/codex", opencodeProxyPath: "/runtime/opencode-proxy.cjs",
|
||||
acpxSidecarPath: "/runtime/acpx-sidecar.cjs",
|
||||
};
|
||||
|
||||
describe("built-in provider launch preparation", () => {
|
||||
it("keeps Core's selection and policy when the execution environment changes", () => {
|
||||
const before = structuredClone(requirements);
|
||||
const local = createRunnerProviderLaunch(requirements, environment);
|
||||
const remote = createRunnerProviderLaunch(requirements, { ...environment,
|
||||
cwd: "/guest/project", codexCommand: "/guest/bin/codex" });
|
||||
expect(local).toMatchObject({ model: "selected-model", approvalPolicy: "on-request",
|
||||
readOnly: true, collaborationMode: "plan", instructions: requirements.instructions });
|
||||
expect(remote).toEqual({ ...local, cwd: "/guest/project", command: "/guest/bin/codex" });
|
||||
expect(requirements).toEqual(before);
|
||||
});
|
||||
|
||||
it("preserves the ACPX profile, run identity, and permission policy", () => {
|
||||
const launch = createRunnerProviderLaunch({ ...requirements, provider: "acpx",
|
||||
model: "claude-selected", settings: { acpxAgent: "claude", acpxPermissionMode: "deny-all" } }, environment);
|
||||
expect(launch).toMatchObject({ kind: "acpx", agent: "claude", model: "claude-selected",
|
||||
sidecarCommand: "/runtime/node", sidecarArgs: ["/runtime/acpx-sidecar.cjs"],
|
||||
runId: "run", normalizedSessionId: "session", permissionMode: "deny-all",
|
||||
permissionModePinned: true, providerPolicy: { readOnly: true },
|
||||
instructions: requirements.instructions });
|
||||
expect(launch.commandDigest).toMatch(/^sha256:[a-f0-9]{64}$/);
|
||||
});
|
||||
|
||||
it("keeps the OpenCode facade shape and prepared conversation mode", () => {
|
||||
expect(createRunnerProviderLaunch({ ...requirements, provider: "opencode", preparedConversation: true }, environment))
|
||||
.toMatchObject({ kind: "opencode", driver: "opencode_server", command: "/runtime/node",
|
||||
args: ["/runtime/opencode-proxy.cjs"], conversationMode: "prepared", collaborationMode: "plan" });
|
||||
expect(createRunnerProviderLaunch({ ...requirements, provider: "opencode" }, environment))
|
||||
.not.toHaveProperty("readOnly");
|
||||
});
|
||||
|
||||
it("uses the existing permission builder when the environment supplies no argument override", () => {
|
||||
const launch = createRunnerProviderLaunch(requirements, { ...environment,
|
||||
options: { environment: { HOME: "/execution/home" } } });
|
||||
expect(launch.args).toEqual(expect.arrayContaining(["app-server"]));
|
||||
expect(JSON.stringify(launch.args)).toContain("paperclip-runner-workspace-read-only");
|
||||
});
|
||||
|
||||
it("rejects missing managed profiles before provider launch", () => {
|
||||
expect(() => createRunnerProviderLaunch({ ...requirements, provider: "claude_managed" }, environment))
|
||||
.toThrow("requires a qualified managed profile");
|
||||
expect(() => createRunnerProviderLaunch({ ...requirements, provider: "aws_agentcore" }, environment))
|
||||
.toThrow("requires a qualified AgentCore profile");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
import { resolve } from "node:path";
|
||||
import { codexExecutableReadOnlyRoots } from "../drivers/codex/codex-security-config.js";
|
||||
import { createIsolatedCodexAppServerArgs } from "../drivers/codex/codex-app-server-driver.js";
|
||||
import { usesPreconfiguredHome } from "../drivers/preconfigured-home.js";
|
||||
import { resolveQualifiedAcpxProfile } from "../drivers/acpx/qualified-profiles.js";
|
||||
import { resolveAcpxProviderMode } from "../drivers/acpx/provider-mode.js";
|
||||
import type { DurableRecoveryIdentity } from "../contracts/durable-recovery.js";
|
||||
import type { NativeRuntimeContextSnapshot } from "../contracts/runtime-context.js";
|
||||
import type { CapabilityRunnerdCodexTransportOptions } from "./runnerd-codex-transport.js";
|
||||
|
||||
/** Core's provider inputs. Executable paths and filesystem layout are supplied separately. */
|
||||
export interface RunnerProviderLaunchRequirements {
|
||||
identity: DurableRecoveryIdentity;
|
||||
provider: NonNullable<CapabilityRunnerdCodexTransportOptions["provider"]>;
|
||||
model: string | null;
|
||||
approvalPolicy: "never" | "on-request" | "untrusted";
|
||||
readOnly: boolean;
|
||||
preparedConversation: boolean;
|
||||
includeCollaborationModeInstructions: boolean;
|
||||
settings: Pick<CapabilityRunnerdCodexTransportOptions,
|
||||
"acpxAgent" | "acpxMode" | "acpxPermissionMode" | "acpxPermissionModePinned" |
|
||||
"piThinkingLevel" | "claudeEffort" | "managedProfile" | "agentCoreProfile">;
|
||||
instructions: string;
|
||||
runtimeContext: NativeRuntimeContextSnapshot | null;
|
||||
}
|
||||
|
||||
/** Locations and provider settings resolved by the built-in execution environment. */
|
||||
export interface RunnerProviderLaunchEnvironment {
|
||||
options: Pick<CapabilityRunnerdCodexTransportOptions,
|
||||
"acpxRuntimeDirectory" | "homeMode" | "environment" | "codexArgs" |
|
||||
"runnerFilesystemRoot" | "disableCodexProjectInstructions" | "externallySandboxed">;
|
||||
root: string;
|
||||
cwd: string;
|
||||
codexHome: string;
|
||||
providerNodeCommand: string;
|
||||
codexCommand?: string;
|
||||
opencodeProxyPath: string;
|
||||
acpxSidecarPath: string;
|
||||
}
|
||||
|
||||
export function createRunnerProviderLaunch(
|
||||
requirements: RunnerProviderLaunchRequirements,
|
||||
input: RunnerProviderLaunchEnvironment,
|
||||
): Record<string, unknown> {
|
||||
const { identity, instructions: baseInstructions, runtimeContext, settings, provider } = requirements;
|
||||
const acpxAgent = provider === "acpx" ? settings.acpxAgent ?? "codex" : null;
|
||||
const requestedModel = requirements.model ?? "";
|
||||
const includeCodexCollaborationInstructions = provider === "codex" &&
|
||||
requirements.includeCollaborationModeInstructions;
|
||||
const { codexHome, providerNodeCommand, codexCommand: effectiveCodexCommand,
|
||||
opencodeProxyPath, acpxSidecarPath } = input;
|
||||
const acpxProfile =
|
||||
provider === "acpx"
|
||||
? resolveQualifiedAcpxProfile(acpxAgent!, requestedModel)
|
||||
: null;
|
||||
const managedProfile = settings.managedProfile;
|
||||
const agentCoreProfile = settings.agentCoreProfile;
|
||||
if (provider === "claude_managed") {
|
||||
if (!managedProfile) {
|
||||
throw new Error(
|
||||
"Claude Managed runner transport requires a qualified managed profile",
|
||||
);
|
||||
}
|
||||
if (requestedModel !== managedProfile.model) {
|
||||
throw new Error(
|
||||
"Claude Managed requested model does not match its qualified profile",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (provider === "aws_agentcore") {
|
||||
if (!agentCoreProfile) {
|
||||
throw new Error(
|
||||
"AWS AgentCore runner transport requires a qualified AgentCore profile",
|
||||
);
|
||||
}
|
||||
if (requestedModel !== agentCoreProfile.model) {
|
||||
throw new Error(
|
||||
"AWS AgentCore requested model does not match its qualified profile",
|
||||
);
|
||||
}
|
||||
}
|
||||
const selectedAcpxMode = provider === "acpx"
|
||||
? resolveAcpxProviderMode(acpxProfile!.agent, settings.acpxMode) : undefined;
|
||||
return provider === "acpx"
|
||||
? {
|
||||
kind: "acpx",
|
||||
provider: "acpx",
|
||||
driver: "acpx_runtime",
|
||||
providerVersion: acpxProfile!.acpxVersion,
|
||||
agent: acpxProfile!.agent,
|
||||
model: requestedModel,
|
||||
acpxVersion: acpxProfile!.acpxVersion,
|
||||
agentServerPackage: acpxProfile!.agentServerPackage,
|
||||
agentServerVersion: acpxProfile!.agentServerVersion,
|
||||
agentRuntimePackage: acpxProfile!.agentRuntimePackage,
|
||||
agentRuntimeVersion: acpxProfile!.agentRuntimeVersion,
|
||||
commandDigest: acpxProfile!.commandDigest,
|
||||
sidecarCommand: providerNodeCommand,
|
||||
sidecarArgs: [acpxSidecarPath],
|
||||
runtimeDirectory:
|
||||
input.options.acpxRuntimeDirectory ??
|
||||
resolve(input.root, "acpx"),
|
||||
normalizedSessionId: identity.normalizedSessionId,
|
||||
runId: identity.runId,
|
||||
cwd: input.cwd,
|
||||
instructions: baseInstructions,
|
||||
providerPolicy: { readOnly: requirements.readOnly },
|
||||
...(selectedAcpxMode === undefined ? {} : { mode: selectedAcpxMode }),
|
||||
...(acpxProfile!.agent === "pi" ? { piThinkingLevel: settings.piThinkingLevel } : {}),
|
||||
...(acpxProfile!.agent === "claude" && settings.claudeEffort ? { claudeEffort: settings.claudeEffort } : {}),
|
||||
permissionMode: resolveRunnerdAcpxPermissionMode(
|
||||
settings.acpxPermissionMode,
|
||||
),
|
||||
permissionModePinned:
|
||||
settings.acpxPermissionModePinned ?? true,
|
||||
runtimeContext,
|
||||
}
|
||||
: provider === "claude_managed"
|
||||
? {
|
||||
kind: "claude_managed",
|
||||
model: managedProfile!.model,
|
||||
profileId: managedProfile!.profileId,
|
||||
anthropicAgentId: managedProfile!.anthropicAgentId,
|
||||
agentVersion: managedProfile!.agentVersion,
|
||||
environmentId: managedProfile!.environmentId,
|
||||
betaVersion: managedProfile!.betaVersion,
|
||||
maxSessionListCostUsd: managedProfile!.maxSessionListCostUsd,
|
||||
instructions: baseInstructions,
|
||||
runtimeContext,
|
||||
}
|
||||
: provider === "aws_agentcore"
|
||||
? {
|
||||
kind: "aws_agentcore",
|
||||
model: agentCoreProfile!.model,
|
||||
profileId: agentCoreProfile!.profileId,
|
||||
region: agentCoreProfile!.region,
|
||||
accountId: agentCoreProfile!.accountId,
|
||||
harnessArn: agentCoreProfile!.harnessArn,
|
||||
harnessVersion: agentCoreProfile!.harnessVersion,
|
||||
endpointArn: agentCoreProfile!.endpointArn,
|
||||
endpointQualifier: agentCoreProfile!.endpointQualifier,
|
||||
agentRuntimeArn: agentCoreProfile!.agentRuntimeArn,
|
||||
memoryArn: agentCoreProfile!.memoryArn,
|
||||
memoryId: agentCoreProfile!.memoryId,
|
||||
invocationRoleArn: agentCoreProfile!.invocationRoleArn,
|
||||
contextBucket: agentCoreProfile!.contextBucket,
|
||||
contextPrefix: agentCoreProfile!.contextPrefix,
|
||||
contextKmsKeyArn: agentCoreProfile!.contextKmsKeyArn,
|
||||
qualificationRevision:
|
||||
agentCoreProfile!.qualificationRevision,
|
||||
eventExpiryDays: agentCoreProfile!.eventExpiryDays,
|
||||
maxEstimatedSessionCostUsd:
|
||||
agentCoreProfile!.maxEstimatedSessionCostUsd,
|
||||
maxIterations: agentCoreProfile!.maxIterations,
|
||||
maxOutputTokens: agentCoreProfile!.maxOutputTokens,
|
||||
timeoutSeconds: agentCoreProfile!.timeoutSeconds,
|
||||
instructions: baseInstructions,
|
||||
runtimeContext,
|
||||
}
|
||||
: {
|
||||
kind: provider,
|
||||
provider,
|
||||
...(input.options.homeMode === "preconfigured" ? { preconfiguredHome: { HOME: input.options.environment!.HOME, CODEX_HOME: codexHome } } : {}),
|
||||
driver:
|
||||
provider === "opencode"
|
||||
? "opencode_server"
|
||||
: "codex_app_server",
|
||||
providerVersion:
|
||||
provider === "opencode" ? "1.18.34" : "codex-app-server-v1",
|
||||
command:
|
||||
provider === "opencode"
|
||||
? providerNodeCommand
|
||||
: effectiveCodexCommand!,
|
||||
args:
|
||||
provider === "opencode"
|
||||
? [opencodeProxyPath]
|
||||
: (input.options.codexArgs ??
|
||||
createRunnerdCodexAppServerArgs({
|
||||
environment: input.options.environment,
|
||||
codexHome,
|
||||
workingDirectory: input.cwd,
|
||||
runtimeStateRoot: input.options.runnerFilesystemRoot ?? input.root,
|
||||
codexCommand: effectiveCodexCommand,
|
||||
instructionWorkingCopyRoot: runtimeContext?.instructions.workingCopy?.rootPath,
|
||||
disableProjectInstructions: input.options.disableCodexProjectInstructions,
|
||||
readOnlyRoots: [
|
||||
...trustedRuntimeReadOnlyRoots(
|
||||
input.options.environment,
|
||||
),
|
||||
...(runtimeContext
|
||||
? [
|
||||
resolve(codexHome, "skills"),
|
||||
runtimeContext.instructions.bundle.rootPath,
|
||||
...runtimeContext.skills.map(
|
||||
(skill) => skill.bundle.rootPath,
|
||||
),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
})),
|
||||
cwd: input.cwd,
|
||||
model: requirements.model,
|
||||
approvalPolicy:
|
||||
requirements.approvalPolicy,
|
||||
externallySandboxed:
|
||||
provider === "codex" &&
|
||||
input.options.externallySandboxed === true,
|
||||
...(provider === "codex" && requirements.readOnly ? { readOnly: true } : {}),
|
||||
instructions:
|
||||
provider === "codex"
|
||||
? withCodexCollaborationRuntimeInstructions(
|
||||
baseInstructions,
|
||||
includeCodexCollaborationInstructions,
|
||||
)
|
||||
: baseInstructions,
|
||||
collaborationMode:
|
||||
requirements.readOnly
|
||||
? "plan"
|
||||
: "default",
|
||||
...(requirements.preparedConversation
|
||||
? { conversationMode: "prepared" }
|
||||
: {}),
|
||||
includeCollaborationModeInstructions:
|
||||
includeCodexCollaborationInstructions,
|
||||
...(provider === "codex"
|
||||
? { includeSkillInstructions: runtimeContext !== null }
|
||||
: {}),
|
||||
runtimeContext,
|
||||
};
|
||||
}
|
||||
|
||||
const CODEX_COLLABORATION_RUNTIME_INSTRUCTIONS = `## Codex-style collaboration
|
||||
|
||||
- Before the first tool call in a turn, send a brief commentary update describing the immediate work you are starting.
|
||||
- During tool-driven work, send concise commentary updates at meaningful transitions so the user can follow progress without opening raw logs.
|
||||
- Reserve \`report_progress\` for meaningful durable milestones on longer work. Do not call it merely to create a completion comment on a short run; Paperclip materializes the final assistant response as the durable completion comment.
|
||||
- Invoke the semantic completion tool exactly once before the final assistant response. After it succeeds, send one self-contained final response with the outcome and verification, then do not call another tool. The completion tool records task disposition; Paperclip keeps receiving your answer until the provider turn ends.`;
|
||||
|
||||
export function withCodexCollaborationRuntimeInstructions(
|
||||
instructions: string,
|
||||
enabled = true,
|
||||
): string {
|
||||
if (!enabled) return instructions;
|
||||
const base = instructions.trimEnd();
|
||||
return `${base}\n\n${CODEX_COLLABORATION_RUNTIME_INSTRUCTIONS}`;
|
||||
}
|
||||
|
||||
export function resolveRunnerdAcpxPermissionMode(
|
||||
configured: CapabilityRunnerdCodexTransportOptions["acpxPermissionMode"],
|
||||
): NonNullable<CapabilityRunnerdCodexTransportOptions["acpxPermissionMode"]> {
|
||||
return configured ?? "approve-all";
|
||||
}
|
||||
|
||||
export function trustedRuntimeReadOnlyRoots(
|
||||
environment: NodeJS.ProcessEnv | undefined,
|
||||
): string[] {
|
||||
const path = environment?.PATH ?? "";
|
||||
const roots = new Set<string>();
|
||||
for (const entry of path.split(process.platform === "win32" ? ";" : ":")) {
|
||||
if (entry === "/opt/homebrew" || entry.startsWith("/opt/homebrew/")) {
|
||||
roots.add("/opt/homebrew");
|
||||
} else if (entry === "/usr/local" || entry.startsWith("/usr/local/")) {
|
||||
roots.add("/usr/local");
|
||||
} else if (entry === "/opt/local" || entry.startsWith("/opt/local/")) {
|
||||
roots.add("/opt/local");
|
||||
} else if (entry === "/nix/store" || entry.startsWith("/nix/store/")) {
|
||||
roots.add("/nix/store");
|
||||
}
|
||||
}
|
||||
return [...roots];
|
||||
}
|
||||
|
||||
export function createRunnerdCodexAppServerArgs(input: {
|
||||
environment: NodeJS.ProcessEnv | undefined;
|
||||
codexHome: string;
|
||||
codexCommand?: string;
|
||||
workingDirectory?: string;
|
||||
runtimeStateRoot?: string;
|
||||
readOnlyRoots?: string[];
|
||||
instructionWorkingCopyRoot?: string;
|
||||
disableProjectInstructions?: boolean;
|
||||
}): string[] {
|
||||
// The filesystem policy denies HOME and CODEX_HOME to keep credentials and
|
||||
// runner state outside provider reach. Always bind those names to the actual
|
||||
// isolated runner home; a stale controller environment must never cause the
|
||||
// execution workspace itself to become an explicit deny root.
|
||||
const args = [
|
||||
...(input.disableProjectInstructions ? ["-c", "project_doc_max_bytes=0"] : []),
|
||||
...createIsolatedCodexAppServerArgs(
|
||||
{
|
||||
...input.environment,
|
||||
HOME: usesPreconfiguredHome(input.environment) ? input.environment?.HOME : input.codexHome,
|
||||
CODEX_HOME: input.codexHome,
|
||||
},
|
||||
[...(input.readOnlyRoots ?? []), ...codexExecutableReadOnlyRoots(input.environment ?? {}, input.codexCommand)],
|
||||
input.instructionWorkingCopyRoot,
|
||||
usesPreconfiguredHome(input.environment) && input.runtimeStateRoot ? [input.runtimeStateRoot] : [],
|
||||
),
|
||||
];
|
||||
if (usesPreconfiguredHome(input.environment) && input.workingDirectory) {
|
||||
args.unshift("-c", `projects={${JSON.stringify(input.workingDirectory)}={trust_level="trusted"}}`);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { createRunnerProviderLaunch, createRunnerdCodexAppServerArgs, trustedRuntimeReadOnlyRoots } from "./runner-provider-launch.js";
|
||||
export { createRunnerdCodexAppServerArgs, resolveRunnerdAcpxPermissionMode, trustedRuntimeReadOnlyRoots, withCodexCollaborationRuntimeInstructions } from "./runner-provider-launch.js";
|
||||
import { applyProviderObservabilityEnvironment, redactObservabilityDiagnostic } from "../drivers/agent-observability.js";
|
||||
import type { RunnerExecutionDriver } from "../control-plane/runner-execution-driver.js";
|
||||
import { resolvePinnedOpenCodeCommand } from "../drivers/opencode/opencode-server-driver.js";
|
||||
@@ -11,7 +13,6 @@ import { preconfiguredHomeEnvironment, preconfiguredCodexHome, usesPreconfigured
|
||||
import { isAcpxCanonicalInputMethod } from "../drivers/acpx/profile-extensions.js";
|
||||
import { RunnerdTraceFrameIndex } from "./runnerd-trace-frame-index.js";
|
||||
import { waitForWarmAttachmentReadiness } from "./warm-attachment-readiness.js";
|
||||
import { codexExecutableReadOnlyRoots } from "../drivers/codex/codex-security-config.js";
|
||||
import { resolveCodexCommand } from "../drivers/codex/codex-command.js";
|
||||
import { isCanonicalProviderEventType } from "../provider-events.js";
|
||||
import { execFileSync } from "node:child_process";
|
||||
@@ -46,7 +47,6 @@ import { createSanitizedCodexEnvironment } from "../drivers/codex/app-server-tra
|
||||
import {
|
||||
CodexAppServerDriver,
|
||||
codexSemanticToolSpecs,
|
||||
createIsolatedCodexAppServerArgs,
|
||||
} from "../drivers/codex/codex-app-server-driver.js";
|
||||
import type {
|
||||
DurableRecoveryCommittedEvent,
|
||||
@@ -72,7 +72,6 @@ import {
|
||||
type DurablePrpControlPlaneOptions,
|
||||
} from "../control-plane/durable-prp-control-plane.js";
|
||||
import {
|
||||
resolveQualifiedAcpxProfile,
|
||||
type QualifiedAcpxAgent,
|
||||
} from "../drivers/acpx/qualified-profiles.js";
|
||||
import { ACPX_CREDENTIAL_BINDING_ENV, createAcpxCredentialBinding, createSanitizedAcpxSpawnInput } from "../drivers/acpx/environment.js";
|
||||
@@ -149,22 +148,6 @@ function readLocalProcessStartedAt(pid: number): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const CODEX_COLLABORATION_RUNTIME_INSTRUCTIONS = `## Codex-style collaboration
|
||||
|
||||
- Before the first tool call in a turn, send a brief commentary update describing the immediate work you are starting.
|
||||
- During tool-driven work, send concise commentary updates at meaningful transitions so the user can follow progress without opening raw logs.
|
||||
- Reserve \`report_progress\` for meaningful durable milestones on longer work. Do not call it merely to create a completion comment on a short run; Paperclip materializes the final assistant response as the durable completion comment.
|
||||
- Invoke the semantic completion tool exactly once before the final assistant response. After it succeeds, send one self-contained final response with the outcome and verification, then do not call another tool. The completion tool records task disposition; Paperclip keeps receiving your answer until the provider turn ends.`;
|
||||
|
||||
export function withCodexCollaborationRuntimeInstructions(
|
||||
instructions: string,
|
||||
enabled = true,
|
||||
): string {
|
||||
if (!enabled) return instructions;
|
||||
const base = instructions.trimEnd();
|
||||
return `${base}\n\n${CODEX_COLLABORATION_RUNTIME_INSTRUCTIONS}`;
|
||||
}
|
||||
|
||||
const CONTROL_PLANE_STATE_MAX_BYTES = 256 * 1024 * 1024;
|
||||
|
||||
function readControlPlaneState(directory: string): Record<string, unknown> {
|
||||
@@ -3408,12 +3391,6 @@ export function createCapabilityRunnerdProviderEnvironment(input: Parameters<typ
|
||||
return applyProviderObservabilityEnvironment(createCapabilityRunnerdProviderEnvironmentBase(input), input.options.observability, input.provider === "acpx");
|
||||
}
|
||||
|
||||
export function resolveRunnerdAcpxPermissionMode(
|
||||
configured: CapabilityRunnerdCodexTransportOptions["acpxPermissionMode"],
|
||||
): NonNullable<CapabilityRunnerdCodexTransportOptions["acpxPermissionMode"]> {
|
||||
return configured ?? "approve-all";
|
||||
}
|
||||
|
||||
const OPEN_CODE_RUNNER_ENVIRONMENT_KEYS = new Set([
|
||||
"PAPERCLIP_AI_PROVIDER_KEY",
|
||||
"PAPERCLIP_AI_PROVIDER_URL",
|
||||
@@ -3475,58 +3452,6 @@ export function resolveSourceCodexHome(
|
||||
return home ? resolve(home, ".codex") : null;
|
||||
}
|
||||
|
||||
export function trustedRuntimeReadOnlyRoots(
|
||||
environment: NodeJS.ProcessEnv | undefined,
|
||||
): string[] {
|
||||
const path = environment?.PATH ?? "";
|
||||
const roots = new Set<string>();
|
||||
for (const entry of path.split(process.platform === "win32" ? ";" : ":")) {
|
||||
if (entry === "/opt/homebrew" || entry.startsWith("/opt/homebrew/")) {
|
||||
roots.add("/opt/homebrew");
|
||||
} else if (entry === "/usr/local" || entry.startsWith("/usr/local/")) {
|
||||
roots.add("/usr/local");
|
||||
} else if (entry === "/opt/local" || entry.startsWith("/opt/local/")) {
|
||||
roots.add("/opt/local");
|
||||
} else if (entry === "/nix/store" || entry.startsWith("/nix/store/")) {
|
||||
roots.add("/nix/store");
|
||||
}
|
||||
}
|
||||
return [...roots];
|
||||
}
|
||||
|
||||
export function createRunnerdCodexAppServerArgs(input: {
|
||||
environment: NodeJS.ProcessEnv | undefined;
|
||||
codexHome: string;
|
||||
codexCommand?: string;
|
||||
workingDirectory?: string;
|
||||
runtimeStateRoot?: string;
|
||||
readOnlyRoots?: string[];
|
||||
instructionWorkingCopyRoot?: string;
|
||||
disableProjectInstructions?: boolean;
|
||||
}): string[] {
|
||||
// The filesystem policy denies HOME and CODEX_HOME to keep credentials and
|
||||
// runner state outside provider reach. Always bind those names to the actual
|
||||
// isolated runner home; a stale controller environment must never cause the
|
||||
// execution workspace itself to become an explicit deny root.
|
||||
const args = [
|
||||
...(input.disableProjectInstructions ? ["-c", "project_doc_max_bytes=0"] : []),
|
||||
...createIsolatedCodexAppServerArgs(
|
||||
{
|
||||
...input.environment,
|
||||
HOME: usesPreconfiguredHome(input.environment) ? input.environment?.HOME : input.codexHome,
|
||||
CODEX_HOME: input.codexHome,
|
||||
},
|
||||
[...(input.readOnlyRoots ?? []), ...codexExecutableReadOnlyRoots(input.environment ?? {}, input.codexCommand)],
|
||||
input.instructionWorkingCopyRoot,
|
||||
usesPreconfiguredHome(input.environment) && input.runtimeStateRoot ? [input.runtimeStateRoot] : [],
|
||||
),
|
||||
];
|
||||
if (usesPreconfiguredHome(input.environment) && input.workingDirectory) {
|
||||
args.unshift("-c", `projects={${JSON.stringify(input.workingDirectory)}={trust_level="trusted"}}`);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
export function unwrapToolResponse(response: Record<string, unknown>, preserveEnvelope = false): {
|
||||
readonly __paperclipSemanticToolOutcome: true;
|
||||
readonly result: unknown;
|
||||
@@ -4967,10 +4892,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
);
|
||||
const acpxAgent =
|
||||
provider === "acpx" ? (this.options.acpxAgent ?? "codex") : null;
|
||||
const requestedModel = typeof params.model === "string" ? params.model : "";
|
||||
const includeCodexCollaborationInstructions =
|
||||
provider === "codex" &&
|
||||
record(params.config).include_collaboration_mode_instructions !== false;
|
||||
const unboundBaseInstructions = String(
|
||||
params.developerInstructions ?? params.baseInstructions ?? "You are a Paperclip agent.",
|
||||
);
|
||||
@@ -4981,197 +4902,25 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
runtimeContext.instructions.bundle.rootPath,
|
||||
)
|
||||
: unboundBaseInstructions;
|
||||
const acpxProfile =
|
||||
provider === "acpx"
|
||||
? resolveQualifiedAcpxProfile(acpxAgent!, requestedModel)
|
||||
: null;
|
||||
const managedProfile = this.options.managedProfile;
|
||||
const agentCoreProfile = this.options.agentCoreProfile;
|
||||
if (provider === "claude_managed") {
|
||||
if (!managedProfile) {
|
||||
throw new Error(
|
||||
"Claude Managed runner transport requires a qualified managed profile",
|
||||
);
|
||||
}
|
||||
if (requestedModel !== managedProfile.model) {
|
||||
throw new Error(
|
||||
"Claude Managed requested model does not match its qualified profile",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (provider === "aws_agentcore") {
|
||||
if (!agentCoreProfile) {
|
||||
throw new Error(
|
||||
"AWS AgentCore runner transport requires a qualified AgentCore profile",
|
||||
);
|
||||
}
|
||||
if (requestedModel !== agentCoreProfile.model) {
|
||||
throw new Error(
|
||||
"AWS AgentCore requested model does not match its qualified profile",
|
||||
);
|
||||
}
|
||||
}
|
||||
const selectedAcpxMode = provider === "acpx"
|
||||
? resolveAcpxProviderMode(acpxProfile!.agent, this.options.acpxMode) : undefined;
|
||||
const completionContract = record(params.completionContract);
|
||||
const runAttachTemplate = {
|
||||
observeCredentials: this.options.observeCredentials !== false,
|
||||
authorizedTools: this.#authorizedTools,
|
||||
...(completionContract.revision &&
|
||||
Array.isArray(completionContract.criterionIds)
|
||||
? { completionContract }
|
||||
: {}),
|
||||
provider:
|
||||
provider === "acpx"
|
||||
? {
|
||||
kind: "acpx",
|
||||
provider: "acpx",
|
||||
driver: "acpx_runtime",
|
||||
providerVersion: acpxProfile!.acpxVersion,
|
||||
agent: acpxProfile!.agent,
|
||||
model: requestedModel,
|
||||
acpxVersion: acpxProfile!.acpxVersion,
|
||||
agentServerPackage: acpxProfile!.agentServerPackage,
|
||||
agentServerVersion: acpxProfile!.agentServerVersion,
|
||||
agentRuntimePackage: acpxProfile!.agentRuntimePackage,
|
||||
agentRuntimeVersion: acpxProfile!.agentRuntimeVersion,
|
||||
commandDigest: acpxProfile!.commandDigest,
|
||||
sidecarCommand: providerNodeCommand,
|
||||
sidecarArgs: [acpxSidecarPath],
|
||||
runtimeDirectory:
|
||||
this.options.acpxRuntimeDirectory ??
|
||||
resolve(this.#root, "acpx"),
|
||||
normalizedSessionId: identity.normalizedSessionId,
|
||||
runId: identity.runId,
|
||||
cwd: String(params.cwd ?? tmpdir()),
|
||||
instructions: baseInstructions,
|
||||
providerPolicy: { readOnly: params.permissions === "paperclip-runner-workspace-read-only" },
|
||||
...(selectedAcpxMode === undefined ? {} : { mode: selectedAcpxMode }),
|
||||
...(acpxProfile!.agent === "pi" ? { piThinkingLevel: this.options.piThinkingLevel } : {}),
|
||||
...(acpxProfile!.agent === "claude" && this.options.claudeEffort ? { claudeEffort: this.options.claudeEffort } : {}),
|
||||
permissionMode: resolveRunnerdAcpxPermissionMode(
|
||||
this.options.acpxPermissionMode,
|
||||
),
|
||||
permissionModePinned:
|
||||
this.options.acpxPermissionModePinned ?? true,
|
||||
runtimeContext,
|
||||
}
|
||||
: provider === "claude_managed"
|
||||
? {
|
||||
kind: "claude_managed",
|
||||
model: managedProfile!.model,
|
||||
profileId: managedProfile!.profileId,
|
||||
anthropicAgentId: managedProfile!.anthropicAgentId,
|
||||
agentVersion: managedProfile!.agentVersion,
|
||||
environmentId: managedProfile!.environmentId,
|
||||
betaVersion: managedProfile!.betaVersion,
|
||||
maxSessionListCostUsd: managedProfile!.maxSessionListCostUsd,
|
||||
instructions: baseInstructions,
|
||||
runtimeContext,
|
||||
}
|
||||
: provider === "aws_agentcore"
|
||||
? {
|
||||
kind: "aws_agentcore",
|
||||
model: agentCoreProfile!.model,
|
||||
profileId: agentCoreProfile!.profileId,
|
||||
region: agentCoreProfile!.region,
|
||||
accountId: agentCoreProfile!.accountId,
|
||||
harnessArn: agentCoreProfile!.harnessArn,
|
||||
harnessVersion: agentCoreProfile!.harnessVersion,
|
||||
endpointArn: agentCoreProfile!.endpointArn,
|
||||
endpointQualifier: agentCoreProfile!.endpointQualifier,
|
||||
agentRuntimeArn: agentCoreProfile!.agentRuntimeArn,
|
||||
memoryArn: agentCoreProfile!.memoryArn,
|
||||
memoryId: agentCoreProfile!.memoryId,
|
||||
invocationRoleArn: agentCoreProfile!.invocationRoleArn,
|
||||
contextBucket: agentCoreProfile!.contextBucket,
|
||||
contextPrefix: agentCoreProfile!.contextPrefix,
|
||||
contextKmsKeyArn: agentCoreProfile!.contextKmsKeyArn,
|
||||
qualificationRevision:
|
||||
agentCoreProfile!.qualificationRevision,
|
||||
eventExpiryDays: agentCoreProfile!.eventExpiryDays,
|
||||
maxEstimatedSessionCostUsd:
|
||||
agentCoreProfile!.maxEstimatedSessionCostUsd,
|
||||
maxIterations: agentCoreProfile!.maxIterations,
|
||||
maxOutputTokens: agentCoreProfile!.maxOutputTokens,
|
||||
timeoutSeconds: agentCoreProfile!.timeoutSeconds,
|
||||
instructions: baseInstructions,
|
||||
runtimeContext,
|
||||
}
|
||||
: {
|
||||
kind: provider,
|
||||
provider,
|
||||
...(this.options.homeMode === "preconfigured" ? { preconfiguredHome: { HOME: this.options.environment!.HOME, CODEX_HOME: codexHome } } : {}),
|
||||
driver:
|
||||
provider === "opencode"
|
||||
? "opencode_server"
|
||||
: "codex_app_server",
|
||||
providerVersion:
|
||||
provider === "opencode" ? "1.18.34" : "codex-app-server-v1",
|
||||
command:
|
||||
provider === "opencode"
|
||||
? providerNodeCommand
|
||||
: effectiveCodexCommand!,
|
||||
args:
|
||||
provider === "opencode"
|
||||
? [opencodeProxyPath]
|
||||
: (this.options.codexArgs ??
|
||||
createRunnerdCodexAppServerArgs({
|
||||
environment: this.options.environment,
|
||||
codexHome,
|
||||
workingDirectory: String(params.cwd ?? tmpdir()),
|
||||
runtimeStateRoot: this.options.runnerFilesystemRoot ?? this.#root,
|
||||
codexCommand: effectiveCodexCommand,
|
||||
instructionWorkingCopyRoot: runtimeContext?.instructions.workingCopy?.rootPath,
|
||||
disableProjectInstructions: this.options.disableCodexProjectInstructions,
|
||||
readOnlyRoots: [
|
||||
...trustedRuntimeReadOnlyRoots(
|
||||
this.options.environment,
|
||||
),
|
||||
...(runtimeContext
|
||||
? [
|
||||
resolve(codexHome, "skills"),
|
||||
runtimeContext.instructions.bundle.rootPath,
|
||||
...runtimeContext.skills.map(
|
||||
(skill) => skill.bundle.rootPath,
|
||||
),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
})),
|
||||
cwd: String(params.cwd ?? tmpdir()),
|
||||
model: typeof params.model === "string" ? params.model : null,
|
||||
approvalPolicy:
|
||||
params.approvalPolicy === "on-request" ||
|
||||
params.approvalPolicy === "untrusted"
|
||||
? params.approvalPolicy
|
||||
: "never",
|
||||
externallySandboxed:
|
||||
provider === "codex" &&
|
||||
this.options.externallySandboxed === true,
|
||||
...(provider === "codex" && params.permissions === "paperclip-runner-workspace-read-only" ? { readOnly: true } : {}),
|
||||
instructions:
|
||||
provider === "codex"
|
||||
? withCodexCollaborationRuntimeInstructions(
|
||||
baseInstructions,
|
||||
includeCodexCollaborationInstructions,
|
||||
)
|
||||
: baseInstructions,
|
||||
collaborationMode:
|
||||
params.permissions ===
|
||||
"paperclip-runner-workspace-read-only"
|
||||
? "plan"
|
||||
: "default",
|
||||
...(params.conversationMode === "prepared"
|
||||
? { conversationMode: "prepared" }
|
||||
: {}),
|
||||
includeCollaborationModeInstructions:
|
||||
includeCodexCollaborationInstructions,
|
||||
...(provider === "codex"
|
||||
? { includeSkillInstructions: runtimeContext !== null }
|
||||
: {}),
|
||||
runtimeContext,
|
||||
},
|
||||
...(completionContract.revision && Array.isArray(completionContract.criterionIds)
|
||||
? { completionContract } : {}),
|
||||
provider: createRunnerProviderLaunch({
|
||||
identity, provider, model: typeof params.model === "string" ? params.model : null,
|
||||
instructions: baseInstructions, runtimeContext, settings: this.options,
|
||||
approvalPolicy: params.approvalPolicy === "on-request" || params.approvalPolicy === "untrusted"
|
||||
? params.approvalPolicy : "never",
|
||||
readOnly: params.permissions === "paperclip-runner-workspace-read-only",
|
||||
preparedConversation: params.conversationMode === "prepared",
|
||||
includeCollaborationModeInstructions: record(params.config).include_collaboration_mode_instructions !== false,
|
||||
}, {
|
||||
options: this.options, root: this.#root, cwd: String(params.cwd ?? tmpdir()),
|
||||
codexHome, providerNodeCommand, codexCommand: effectiveCodexCommand,
|
||||
opencodeProxyPath, acpxSidecarPath,
|
||||
}),
|
||||
};
|
||||
// Preserve the first generation's provider attachment seed independently
|
||||
// of bounded command history. The in-memory copy serves a live warm
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"commandDigest": "sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 50,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "00d6af17216cb0cad902b48633eb19bf0ec6b05837a984cb24661fca593f09c1",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "c0a65e247fc47b263bd369a3d5ab505c4b323d4e6493be8a9ecbeb0e9dbb9107",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1",
|
||||
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1",
|
||||
"innerDistributionSourceSha256": "9bb08347eed01f1ab44e065c8b06ead9be3a25973d6e6329dc4c0a099096279a",
|
||||
"upstreamAppSha256": "7b48282a19b5b0814a0c96ad5e3a125173d792cc55f9525b2effea962f6063c0",
|
||||
"patchedAppSha256": "61f8eebe0c30cc03b311c6818b2acb158a6d16ca58c8bfac5ebd42db095c2679",
|
||||
"messageIdentityContract": "copilot-native-message-id-v1",
|
||||
"ownedDistributionDelivery": "COPILOT_CLI_DIST_DIR:lease-owned-guarded-inner-distribution:v1",
|
||||
"permissionContextContract": "copilot-edit-permission-context-v1",
|
||||
"permissionContextSourceSha256": "de2d38d702b3c574161af0c0c04790fb6f6347916eb3916ce7a131a8c229121b",
|
||||
"permissionAdapterSourceSha256": "ee5929ed2ee647ad0369465dee9d51a00b2dd28e1ff735b43e969ddc9c164668",
|
||||
"permissionLocationsSourceSha256": "f39a81da395b6e23aa2a1a45572a28846e5e81bfd3d764d9a456f74a79c752e9",
|
||||
"permissionRedactionSourceSha256": "ad6770c69269087951790e2e87e22ac088e3d432769adf17ebc865ed512e6c4f",
|
||||
"permissionClassifierSourceSha256": "1161eabd82e26133e3b3db3ab567f65d52808b03f176e93497e02c8330349fb4",
|
||||
"semanticToolReceiptContract": "paperclip.semantic_tool_receipt.v2",
|
||||
"semanticReceiptSourceSha256": "b2795726b2234a663fec1a6ca11f0551ae69882fe9e82aa39686e952d036e791",
|
||||
"semanticBridgeSourceSha256": "89e3dde25ff35bfd4d7f520fd9d14af3cb9027f182a2d51431d298f1015be580",
|
||||
"toolEvidenceSourceSha256": "6eef5039afc75bb851eaf9cd82a6f099e4bd398ca2deb59dd66f5b2a81a7a706",
|
||||
"semanticNormalizedInputContract": "validated-forwarded-input-commit-v1",
|
||||
"semanticSidecarSourceSha256": "cc90b59bdfe6a82f7fa3e819e8671f71f7b14047e68942e8086618ffbaacf59f",
|
||||
"semanticDirectDriverSourceSha256": "ff572638f2cb5d9f8249ff8e2f412311c1e3af209ef0bf7e5ea9f10a21d91103",
|
||||
"semanticValidationSourceSha256": "02fbb92681c78c5de5d2f1aae25aabc25a0e5df6b0ff4baccac652192a8da975",
|
||||
"semanticNormalizationSourceSha256": "8b628b82e8eacc91cdcc6fdca2210ba4cf282f01e9c6f74e95ff271655b5047e",
|
||||
"semanticCompletionContractSourceSha256": "f8b381e1ac2a880558f3e81968f0f8f05de3ded573d044fafc89f198f2ec0433",
|
||||
"semanticValidatorsSourceSha256": "f22d9caf2035b600abc1a6625ae23ca97f1409d7e29ed5b7264bb8f9784c7dc9",
|
||||
"semanticSchemaBundleSourceSha256": "016391a087cc217aafed4ba1f4efafc6e4881a7525952dce824b7f4d24530603",
|
||||
"semanticSidecarProtocolSourceSha256": "03c0c95b900f9dc18f29f6be27b8c79bc0bcab29780190d46a74e95328081236",
|
||||
"semanticSidecarReceiptCommitContract": "correlated-tool-resolve-v1",
|
||||
"displayEventSourceSha256": "d72f9a6035c247bc6a42c067c05753076c54322c4b58984e93922f3325dc6877",
|
||||
"displayProjectionSourceSha256": "75952606b26dd93bbc582c1f0a4424487377272ea745fb3a27ceae031fd5dc7e",
|
||||
"configuredEnvironmentSourceSha256": "5c9008d121979a3a8987e6b8a5752172eafdc4a2b5d725a73f3bbb8fb83be8ff",
|
||||
"configuredEnvironmentRustSourceSha256": "85c87fae62b8a9377e4f0a4c00302c7519fae4397b1c48253f8f2b7ba7faf994",
|
||||
"profileActivitySourceSha256": "4fdb6b0f9125fd8372ba6f27945fa5c8a3d3b2ec891df544092e3dc7a75d5282",
|
||||
"profileExtensionsSourceSha256": "a80ddc95eb4505095bfdbbf85c758199949ab20be9c84b34c77f762eb7180571",
|
||||
"profileInstallationSourceSha256": "e4aacb08015fa9104dce2b0c7d150cc432231c8a3e07d2cec2a325b388f7f382",
|
||||
"attachmentPolicySourceSha256": "4f7638b7cc64e18f6a4ba33fd46de2b3d49225e32aeab41f5c695b268fa4f8e2",
|
||||
"singleReadEvidenceSourceSha256": "4069393405089bde613cecec04f5123c426594811da4c668bd96f0e7e283550f",
|
||||
"nativeSessionRuntimeSourceSha256": "e8a3bee66a1ff1c6f251b5ae57453ffb908336da9c4a6d98f41f94f7ee94a8e8",
|
||||
"nativeRuntimeContextSourceSha256": "28c28c850bceade8c72b3ea246485d2bc77bd6e1dab95b43e3b319e27ca944bf",
|
||||
"nativeSessionContractSourceSha256": "22a8c96ce41ba4f03e976fc37f0cc7c5d05cee99fa789941da31ee1dab440b90",
|
||||
"harnessSessionContractSourceSha256": "03f4cd7f76f34f6d82e009b66aa51f18f6e23dc235b660778060e35b6ea07412",
|
||||
"harnessBackendSourceSha256": "dc827e281ae6731545b166ee4d709a4f67f594a556dcbe63c70fe8f1aca26249",
|
||||
"harnessSessionSourceSha256": "81361733e86b56dd5a66415aefc6f72e69be24acc8d3417370154ebb7fef6e94",
|
||||
"transportContractSourceSha256": "4fa9593226ff3280bc12140bfcbc2c0986e4cc8d4733381c334138dd03b08f46",
|
||||
"nativeBackendSourceSha256": "2406483fd35a564971c22e10fc2bad26bae16ff5a3fb6c65690fcf156522ab6d",
|
||||
"nativeBackendFactorySourceSha256": "7e431c926b76d53c4c08e3a4105353dcd34bd940f6588a1a68f79631f6feeb44",
|
||||
"nativeInstructionsSourceSha256": "ee8df5d85b263d5d2d0dc9ede8602cb7daf6388687108fe05e2371f5f56c3492",
|
||||
"providerLaunchSourceSha256": "2bd265438b88897ab57c73a180009d9f78c382c1576b8d9c16e4bae3dd01c45a",
|
||||
"runtimeRequestBridgeSourceSha256": "ba198ea55c177906d1de215d119466805452b87abefa1db47a1ffabc205d7a73",
|
||||
"runtimeRequestFacadeSourceSha256": "d0ed28e0c5ddad34db8f437e704c8a755eb0b47f27e08e44185f66d1d2205295",
|
||||
"runtimeRequestNotificationSourceSha256": "6196c6175f11c69295dc00fc773201f4fdeca26c500653870253c43b81e65211",
|
||||
"guardedInstallationSourceSha256": "f6d0c33de38259efefa0f4e0f9737cd30cfd3996a8e875e3fbdb8c72dc133dca",
|
||||
"nativeExecutionSourceSha256": "fc7c6568feb0d8d709e9baa51839420c9b90336b4865a9b8405aba06e4d1e9c4",
|
||||
"profileCompatibilitySourceSha256": "afbc27f9dcd824d14fc71552f0244614aaa7260a5b9b2cb9d148974c68a42ce6",
|
||||
"installationSourceSha256": "a1c7c6f103b898216973e77ffbac0ef7572e250aa574d1a4065270e9dfa0dc2d",
|
||||
"assetOwnershipSourceSha256": "1785e3f31e5dad5a13b53e25d8cf9482a9bd89a27b6c127830aed6b81acc605c",
|
||||
"assetInstallerSourceSha256": "d3bd9514e5920d0cc3567aa50487fb534c57a6f66b8da0d3f22c5f6e499c841f",
|
||||
"credentialBindingSourceSha256": "54fd09ad430dac04df98d829061f6701e73b5bf2a0f4de836d2ac36ee511009f",
|
||||
"sidecarErrorSourceSha256": "b09af4577f8bf42eee7036b2ad9842202cfc763e72b53c3287f82c6090cdad80",
|
||||
"profileSourceSha256": "f5538b3dbefa7634a332b87d5ededcefb56aa7e5790cede1c7a0d948a548a456",
|
||||
"metadataProbeSourceSha256": "be4c8416edd27061109bc1f33d0c26071b4facbb3f651beaae6b3c0275cce727",
|
||||
"permissionToolIdentitySourceSha256": "770b47305e571a81f344883057d0e4a482a1cd59ac77d045836593a8e739deb1",
|
||||
"semanticContinuationSourceSha256": "eb877719ef32a935afa4ec93f0f1c43d58d55bc4e1042965febcac4c45ed851c",
|
||||
"runtimeHostSourceSha256": "60e5454f030e87f2afa8b1fb120b26adb126b6b17d0e5e82f49c77f76f466cbb",
|
||||
"runtimeAdapterSourceSha256": "e4c764c2c3c7dd655a965b39472bd1ad8d577d1e7258b25594b171c9429752b8",
|
||||
"runtimeSandboxSourceSha256": "92dfa53f5a3ed0062913ae417eeccec243b430d444495efde2a8e9c4c02e78ea",
|
||||
"cancellationSourceSha256": "a07d1a9e982dec0ae130c1ad1efa5bc75c6a39e013b730ba3ee45256f09ceeca",
|
||||
"agentFilesSourceSha256": "fb32f6badc13dfa22f231479509b631b2f0eba0c99b1ab9f3cd5c0c601aa03ba",
|
||||
"nativeDistributionSourceSha256": "aed327c389e957483a503cb1bb8b247ee7cd7ff4b528fb4567c39bcb4577690b"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user