diff --git a/doc/runner-launch-preparation.md b/doc/runner-launch-preparation.md new file mode 100644 index 00000000000..b45f49b03ba --- /dev/null +++ b/doc/runner-launch-preparation.md @@ -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. diff --git a/packages/paperclip-runner/acpx-profiles.json b/packages/paperclip-runner/acpx-profiles.json index 38442d25355..21955036b37 100644 --- a/packages/paperclip-runner/acpx-profiles.json +++ b/packages/paperclip-runner/acpx-profiles.json @@ -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": { diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs index 5c0a1555349..39f9206b27a 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_profiles.rs @@ -47,7 +47,7 @@ pub(crate) fn acpx_release_profile(agent: &str) -> Option { agent_runtime_package: None, agent_runtime_version: None, command_digest: - "sha256:37a8f6a540a7417bb12becbf283158df8cca925c5136ecc0db14597441fff702", + "sha256:d8f89c8e75883a793122ed908016b7730ededa6c3df741f23298c2445d7e6fc4", requires_provider_policy: true, }, "claude" => AcpxReleaseProfile { diff --git a/packages/paperclip-runner/scripts/copilot-profile-identity.mjs b/packages/paperclip-runner/scripts/copilot-profile-identity.mjs index 31a5c8da9fd..11618410c90 100644 --- a/packages/paperclip-runner/scripts/copilot-profile-identity.mjs +++ b/packages/paperclip-runner/scripts/copilot-profile-identity.mjs @@ -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", diff --git a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts index a67e2d6b936..f736301a8ad 100644 --- a/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/copilot-installation.test.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(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 () => { diff --git a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts index 22bca29a0fc..0f88f9551d4 100644 --- a/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts +++ b/packages/paperclip-runner/src/drivers/acpx/generated-profiles.ts @@ -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": { diff --git a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts index 03f9c4dd56f..d0d6837ab48 100644 --- a/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts +++ b/packages/paperclip-runner/src/drivers/acpx/profile-compatibility.ts @@ -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> = { 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], }; diff --git a/packages/paperclip-runner/src/live/runner-provider-launch.test.ts b/packages/paperclip-runner/src/live/runner-provider-launch.test.ts new file mode 100644 index 00000000000..d0e27594308 --- /dev/null +++ b/packages/paperclip-runner/src/live/runner-provider-launch.test.ts @@ -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"); + }); +}); diff --git a/packages/paperclip-runner/src/live/runner-provider-launch.ts b/packages/paperclip-runner/src/live/runner-provider-launch.ts new file mode 100644 index 00000000000..fa59eee43d0 --- /dev/null +++ b/packages/paperclip-runner/src/live/runner-provider-launch.ts @@ -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; + model: string | null; + approvalPolicy: "never" | "on-request" | "untrusted"; + readOnly: boolean; + preparedConversation: boolean; + includeCollaborationModeInstructions: boolean; + settings: Pick; + instructions: string; + runtimeContext: NativeRuntimeContextSnapshot | null; +} + +/** Locations and provider settings resolved by the built-in execution environment. */ +export interface RunnerProviderLaunchEnvironment { + options: Pick; + root: string; + cwd: string; + codexHome: string; + providerNodeCommand: string; + codexCommand?: string; + opencodeProxyPath: string; + acpxSidecarPath: string; +} + +export function createRunnerProviderLaunch( + requirements: RunnerProviderLaunchRequirements, + input: RunnerProviderLaunchEnvironment, +): Record { + 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 { + return configured ?? "approve-all"; +} + +export function trustedRuntimeReadOnlyRoots( + environment: NodeJS.ProcessEnv | undefined, +): string[] { + const path = environment?.PATH ?? ""; + const roots = new Set(); + 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; +} diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 0fa9d5aeebf..391c6b0f397 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -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 { @@ -3408,12 +3391,6 @@ export function createCapabilityRunnerdProviderEnvironment(input: Parameters { - 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(); - 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, 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 diff --git a/packages/paperclip-runner/test/fixtures/copilot-profile-v50-identity.json b/packages/paperclip-runner/test/fixtures/copilot-profile-v50-identity.json new file mode 100644 index 00000000000..808fadba4e4 --- /dev/null +++ b/packages/paperclip-runner/test/fixtures/copilot-profile-v50-identity.json @@ -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" + } +}