diff --git a/maps/agent-access-map/agents.md b/maps/agent-access-map/agents.md index f579d431b41..1d053c34f6b 100644 --- a/maps/agent-access-map/agents.md +++ b/maps/agent-access-map/agents.md @@ -9,6 +9,12 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to creation/configuration, +approval activation, built-in provisioning and import preservation described +below. The original baseline commit above still governs unrelated entries; this +update does not claim operation-complete review or live agent verification. + ## `hire` Review: `baseline`. @@ -30,17 +36,45 @@ bypass hiring authorization. Source: [runner resolver](../../server/src/services Policy: Agent creation/configuration uses action grants, resource policies and change consent; some controls require a board actor. +The creation boundary resolves the request-level runner before adapter validation +and credential normalization. Omitted/automatic new Codex selection uses the +qualified target's default; explicit Legacy is retained. Disabled adapters and +external overrides affect new selection, while missing credentials/runtime or +incompatible settings remain errors. Existing agents and reviewed pending hires +retain their recorded execution. Native `hire_agent` explicitly inherits its +caller's runtime and remains subject to permission and credential restrictions; +it is not an arbitrary host-configuration or login tool. REST/CLI creation and +hire runner options use the [shared request contract](../../packages/shared/src/validators/agent.ts). + Evidence: `source-only` — [implementation](../../server/src/routes/agents.ts#L1395) at the reviewed commit above. +Scoped Evidence: `source-only` — [creation policy](../../server/src/services/agent-hiring.ts), +[runner resolution](../../server/src/services/agent-runner-selection.ts), +[native hire binding](../../server/src/services/native-runtime/paperclip-runner-tool-authority.ts), +[frozen approval activation](../../server/src/modules/agent-lifecycle/adapters/approvals.ts) +and [same-harness import preservation](../../server/src/services/company-portability.ts) +at the scoped commit. No permitted-agent journey was executed for this map update. + Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the remaining operations before qualifying the whole capability. +For this scope, compare automatic and explicit Legacy creation, assert foreign +company/environment and missing-grant denials, then compare pending approval +configuration across restart and activation. An unrelated edit/import update must +not change existing execution; a new package without a runner choice receives +creation defaults. Do not count a board call or component test as agent evidence. + Gaps: Identity/persona hiring works. Operator account login and sensitive adapter/environment configuration are separate guarded actions. +The runner request does not grant new account, environment-probe or native API +fallback authority; see [validation access](./runs-adapters.md#validation) and +[environment administration](./execution-environments.md#probe). Full access +qualification remains not run. + ## `instructions` Review: `baseline`. @@ -144,3 +178,10 @@ remaining operations before qualifying the whole capability. Gaps: Provisioning checks agents:create. Enabling/running its built-in routine is not the same permission path. + +Scoped source review: [built-in provisioning](../../server/src/services/built-in-agents.ts) +recognizes the underlying Codex harness of a native company agent. New configured +provisioning resolves the runner, while unrelated reconciliation and same-harness +edits retain existing execution. Verify explicit runner changes preserve compatible +saved settings; the general creation/access policy above still applies. This is +source-only evidence at the scoped commit, not a live provisioning result. diff --git a/maps/agent-access-map/execution-environments.md b/maps/agent-access-map/execution-environments.md index 74860af970f..5c2b2583167 100644 --- a/maps/agent-access-map/execution-environments.md +++ b/maps/agent-access-map/execution-environments.md @@ -10,6 +10,12 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped immutable-platform source review: 2026-10-12 at +`d16eccf109b524778ceb896f79ccb3881bc61a9b`, limited to saved sandbox probing, +server-owned qualification metadata and read/invalidation behavior. The original +baseline commit and review depth remain for unrelated operations; no live agent +or provider qualification is claimed by this update. + ## `configuration` Review: `baseline`. @@ -34,6 +40,18 @@ own policy and access review. Gaps: API exists; an agent cannot use instance environment management as an ordinary company actor. +Scoped source review: [environment routes](../../server/src/routes/environments.ts) +require board organization access for reads and local-implicit board or instance +administrator for management. Create/PATCH reject client-supplied platform-proof +metadata; [service updates](../../server/src/services/environments.ts) discard +supplied proof, preserve current server proof atomically for metadata-only edits, +and invalidate it when the target configuration or driver changes. Public row +responses hide the private record. This is source-only at the scoped commit. +Verify forged metadata rejection, concurrent probe/update preservation, target +invalidation and restricted-reader redaction; actual agent-denial proof remains +not run. Company/team packages do not import or export environment rows, so those +packages do not transfer platform qualification. + ## `capabilities` Review: `baseline`. @@ -73,6 +91,15 @@ Agent access: `restricted` — Board/admin probes / CLI environment. Policy: Environment management is instance administrator/board; permitted assignment uses agent/project policy. +Both probe endpoints require a board actor that is local-implicit or an instance +administrator; ordinary agent identity and company membership alone are denied. +The saved sandbox probe also resolves company/environment and secret access +before allocating a fresh lease. Its measurement is company-bound to an immutable +image digest, effective provider configuration and active custom-image identity; +only confirmed cleanup permits recording it. A draft probe does not record saved +qualification. Reading stored qualification does not lease/provision a target and +does not grant an agent permission to probe or manage that target. + Evidence: `source-only` — [implementation](../../server/src/routes/environments.ts#L353) at the reviewed commit above. @@ -82,6 +109,24 @@ own policy and access review. Gaps: Reachability probes are not granted by an agent's right to execute assigned work. +Scoped Evidence: `source-only` — [probe authorization](../../server/src/routes/environments.ts), +[immutable binding, measurement and read](../../server/src/services/environment-probe.ts), +[private metadata writes](../../server/src/services/environments.ts) and +[custom-image binding](../../server/src/services/environment-custom-image-runtime.ts) +at the scoped commit. A connection-only success or a fixture cannot certify +native compatibility, account authentication or agent access. + +Scoped Verification: `not-run` — on one authorized owned target, compare pinned +image proof with mutable/snapshot and changed-config controls; require independent +cleanup evidence before recording proof. Assert agent/non-admin board denial, +secret-context denial where the actor lacks access and forged-proof rejection. +Verify API reads hide the private record and metadata-only updates cannot overwrite it. Then use +[runtime validation](./runs-adapters.md#validation) for account/model/native startup. + +There is no new delegated agent probe binding or client proof-write API. Any +human-action request/result handoff requires its own admitted path and verification; +an accepted interaction does not turn the caller into a board administrator. + ## `assignment` Review: `baseline`. diff --git a/maps/agent-access-map/onboarding.md b/maps/agent-access-map/onboarding.md index f4eb025f50c..c3c4f3429df 100644 --- a/maps/agent-access-map/onboarding.md +++ b/maps/agent-access-map/onboarding.md @@ -9,6 +9,11 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to first-agent selection, +selected-runtime validation and draft restoration. Unrelated baseline entries +retain the original source commit; no live agent verification is added. + ## `instance` Review: `baseline`. @@ -74,10 +79,25 @@ provider-account/environment setup. Policy: Instance setup is operator work; company creation is board; agent creation/task creation retain their individual permissions. +The wizard selects the Codex harness and lets the server resolve an automatic +new-agent runner; explicit Legacy remains available. Supported-platform and +immutable sandbox evidence follow [environment probe policy](./execution-environments.md#probe). +Creation/discovery do not provision a target to determine this default. Adapter +setup validation requires `agents:create`, plus saved-agent update access when +testing an existing agent; interactive account login additionally requires a +board user and owner-bound session. An agent's right to hire is not login or +instance-environment administration authority. + Evidence: `source-only` — [implementation](../../ui/src/components/OnboardingWizard.tsx#L279) at the reviewed commit above. +Scoped Evidence: `source-only` — [wizard/default selection](../../ui/src/components/OnboardingWizard.tsx), +[creation resolver](../../server/src/services/agent-runner-selection.ts) and +[setup/login authorization](../../server/src/routes/agents.ts) at the scoped +commit. Existing agent/approval execution preservation follows +[hiring access](./agents.md#hire); no live onboarding proof is claimed here. + Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the @@ -86,6 +106,11 @@ remaining operations before qualifying the whole capability. Gaps: Account authorization, environment administration and model selection need separate checks. +Verify automatic onboarding without a post-hire runner change, explicit Legacy, +saved configuration after reload and the actual selected runtime's useful task +plus follow-up. Assert denied agent login/probe and foreign-company account or +environment access separately. These agent-context checks remain `not-run`. + ## `first-task` Review: `baseline`. @@ -134,3 +159,8 @@ evidence. Gaps: No server onboarding-draft API for this browser-local state; classify explicitly or intentionally make it a cross-client product feature. + +Scoped source review of [draft restoration](../../ui/src/components/OnboardingWizard.tsx) +preserves harness/runner intent and invalidates obsolete setup-test results after +a selection change. Browser draft continuity is not API or live-agent evidence, +and opening a saved Legacy agent does not apply new-agent defaults. diff --git a/maps/agent-access-map/runs-adapters.md b/maps/agent-access-map/runs-adapters.md index de444886a96..5f069d79265 100644 --- a/maps/agent-access-map/runs-adapters.md +++ b/maps/agent-access-map/runs-adapters.md @@ -9,6 +9,11 @@ Each capability needs operation-level review before its entire scope can be qualified. No live agent execution is claimed. Follow the [map contract](../README.md) for format, permission checks, evidence and maintenance. +Scoped runner-default source review: 2026-10-12 at +`eee0534154eaf5d0d3f2bbc97c1e24d86f34a5a5`, limited to runner discovery, +selected-runtime testing and saved execution/session preservation. The original +baseline source and review depth remain unchanged for unrelated operations. + ## `availability` Review: `baseline`. @@ -40,6 +45,15 @@ Source: [adapter discovery](../../server/src/adapters/registry.ts). Gaps: Raw instance adapter listing requires board, while company model discovery exists for eligible agents. +Scoped source-only evidence: [adapter discovery](../../server/src/routes/adapters.ts) +reports supported runners/effective default for the selected company/environment; +[selection policy](../../server/src/services/agent-runner-selection.ts) reads +server-owned immutable sandbox facts without provisioning. New Codex defaults +and explicit overrides follow the [shared runner contract](../../packages/shared/src/agent-runner.ts); +other harness defaults remain unchanged. The board inventory does not grant a +native agent an equivalent discovery binding. See +[probe restrictions](./execution-environments.md#probe) for acquiring qualification. + ## `validation` Review: `baseline`. @@ -65,6 +79,22 @@ remaining operations before qualifying the whole capability. Gaps: Environment test path checks creation/configuration policy; missing provider auth must use its owner setup flow. +Scoped source review: [test authorization and resolution](../../server/src/routes/agents.ts) +requires same-company `agents:create`, saved-agent update access when supplied, +allowed environment selection, and normal secret/responsible-user checks. An +unchanged saved runner avoids new-creation policy but still receives selected +runtime validation. [Native readiness](../../server/src/services/native-runtime/setup-readiness.ts) +tests native Codex startup/model/account and bounded teardown; an authentication +or Legacy CLI check cannot replace that runtime evidence. Missing dependencies, +credentials, invalid models and incompatible settings fail without silently +changing runners. Account login remains board/owner-bound. + +Verification for this scope remains `not-run`: use an admitted agent REST/CLI +validation path, assert missing grants and cross-company targets/accounts are +denied, then prove the useful task/follow-up separately. `hire_agent` inherits its +caller's runtime; do not assume its binding accepts arbitrary runner or account +settings. See [creation access](./agents.md#hire). + ## `runs` Review: `baseline`. @@ -114,3 +144,10 @@ own policy and access review. Gaps: Execution context reset is not a permitted generic native API mutation or an ordinary agent management path. + +Scoped source-only evidence: [configuration changes](../../server/src/services/agent-configuration.ts) +and [native execution/recovery](../../server/src/services/native-runtime/native-session-executor.ts) +retain existing execution on unrelated edits and use recorded run configuration +for active/recovered runs. Explicit runner changes use revisions and session +invalidation. Verify saved configuration, current versus historical run identity, +restart and follow-up without granting session-reset authority to an agent. diff --git a/maps/feature-map/agents.md b/maps/feature-map/agents.md index 4f4e05529d7..ec1eb49ab7d 100644 --- a/maps/feature-map/agents.md +++ b/maps/feature-map/agents.md @@ -8,7 +8,7 @@ Agent access: [companion](../agent-access-map/agents.md). ## Sub-features -- `hire`: choose identity, adapter, model, and required account settings; edit +- `hire`: choose identity, harness, model, and required account settings; edit reporting relationships through the agent configuration afterward. - `instructions`: edit agent configuration and instruction files with durable revisions. - `organization`: inspect and change reporting relationships through supported controls. @@ -39,6 +39,25 @@ Automated: [new agent page](../../ui/src/pages/NewAgent.test.tsx) and [configura Manual: Hire an agent with a distinct name and role. Complete any required approval, reload the roster, and open its configuration. Confirm manager, adapter, and model persisted; run a small task to prove execution readiness. +For a new Codex hire, choose the Codex harness. Automatic selection uses Paperclip +Runner on a qualified selected target; Advanced offers an explicit Legacy runner +override. Record the persisted runner and +underlying harness, rather than treating the displayed Codex name as execution +proof. See [runtime selection and validation](./runs-adapters.md#adapter-setup) +and [target qualification](./execution-environments.md#environment-settings). + +Repeat with an explicit Legacy Codex hire. If approval is required, compare its +reviewed execution configuration before and after restart and activation; approval +must retain the reviewed runner. Test an existing Legacy and native agent through +an unrelated edit, reload and restart: neither is automatically upgraded. + +Creation defaults also apply to API/CLI hires, built-ins, plugins and new team +imports. Exports preserve resolved execution choices; a package without a runner +choice uses the new-agent default, while updating an existing same-harness agent +preserves its execution. For a native Codex import, edit a model or account binding +and verify unchanged runtime settings survive; an explicit clear must stay cleared. +Use the [portability recipe](./companies.md) for package preparation and boundaries. + For Claude Code on Apple Silicon, submit without a runner override and inspect saved configuration: `paperclip_runner`, `provider: acpx`, `acpxAgent: claude`, and ordinary model `claude-opus-5-5`. The Advanced runner selector can explicitly @@ -58,6 +77,12 @@ Automated: [instruction service](../../server/src/__tests__/agent-instructions-s Manual: Save a harmless instruction edit, reload, and verify its revision and effect in the next eligible run. Pause and resume the disposable agent; confirm pause prevents new work and does not erase previous runs. +An explicit runner change is a configuration change, with normal revisions and +session invalidation. Incompatible settings must produce a field-specific error; +selecting another runner must not silently discard them or change an active run's +recorded execution. Keep runner choice in Advanced and verify the next eligible +run separately from historical transcripts. + ### `organization` Automated: [org chart](../../ui/src/pages/OrgChart.test.tsx) is component evidence. Built-in setup also has [configuration modal tests](../../ui/src/components/ConfigureBuiltInAgentModal.test.tsx). diff --git a/maps/feature-map/execution-environments.md b/maps/feature-map/execution-environments.md index bb1fa10d78f..dda710a1d89 100644 --- a/maps/feature-map/execution-environments.md +++ b/maps/feature-map/execution-environments.md @@ -38,6 +38,33 @@ Automated: [environment UI](../../ui/src/pages/CompanyEnvironments.test.tsx), [d Manual: Save a disposable target, reload it, and run its available probe. Verify both success and missing-credential/reachability errors identify the target. Confirm unavailable providers are not offered as working run targets. +For an immutable sandbox image, pin the OCI image with `@sha256` and explicitly +probe the saved environment in the intended company. The existing connection +probe allocates a fresh lease and can measure its OS, architecture and libc. +Qualification is recorded only when the selected image/configuration matches +the live lease and cleanup is confirmed. Inspect `platformQualificationRecorded` +and its diagnostic in the probe result: connection success alone does not mean +the image has native runtime qualification. + +The measured record is server-owned, company-bound and tied to the effective +configuration and active custom-image identity. Mutable tags, provider defaults +and name-only snapshots do not qualify. Changing the target or image invalidates +the record; normal metadata edits cannot forge or overwrite it, and API reads +omit the private metadata. Reading this evidence for discovery or creation does +not allocate a sandbox. The draft `probe-config` checks are not a saved-image +qualification operation. See the [runtime contract](../../packages/paperclip-runner/README.md) +and [adapter validation recipe](./runs-adapters.md#adapter-setup) for native +binary/provider readiness after platform qualification. + +Verification recipe: probe a pinned owned image, record its company/config/image +identity and cleanup result, then change the image or target and verify its old +qualification is not reused. A mutable-image connection can succeed while +qualification remains absent. Assert ordinary agent and non-admin board probe +requests are denied, and environment create/PATCH payloads cannot supply +qualification metadata. +This recipe and its [probe controls](../../server/src/__tests__/environment-probe.test.ts) +are not claims that a live provider or agent-access journey ran for this map update. + The local row has no UI Test button. Its API/CLI probe reports host/cwd availability only; it does not validate an adapter binary, workspace, or task execution. Remote-driver probes and a real bounded run need their own evidence. @@ -51,5 +78,7 @@ Manual: Run a bounded command in the intended target, inspect its output and wor ## Gotchas - A probe is narrower evidence than a complete task with tools, files, and resumption. +- Platform qualification does not authenticate a model account or qualify every + harness; the selected native runtime still needs its own setup and useful-task proof. - The local option may appear only when editing an existing local environment; availability follows current UI/provider capabilities. - Workspace service reachability and environment reachability are separate checks. diff --git a/maps/feature-map/onboarding.md b/maps/feature-map/onboarding.md index ecfaeefaaef..5a411e3ebfa 100644 --- a/maps/feature-map/onboarding.md +++ b/maps/feature-map/onboarding.md @@ -50,6 +50,22 @@ company's onboarding route, and finish; drafts save automatically. Follow the created first task, verify the selected agent and company, and inspect its output or explicit authentication gate. Reload both company and task. +For Codex, choose the harness and leave runner selection automatic to prove the +first-agent default. On a qualified target it saves Paperclip Runner; Advanced +also permits Legacy. Do not switch runners after onboarding when claiming proof +of that default. Test the selected runtime and account, then complete a bounded +useful task and follow-up with an independently checked file artifact. Saved +execution configuration and recorded run identity must match after reload. +Use [runner validation](./runs-adapters.md#adapter-setup) and +[environment qualification](./execution-environments.md#environment-settings) +for the prerequisites; a successful login alone is insufficient. + +Leave and resume during harness/runner setup. Confirm the draft retains the +explicit choice and compatible account bindings, old native Codex setup links +recover the harness and runner, and a changed harness or runner invalidates stale +test results. Existing Legacy agents remain Legacy when their onboarding/setup +configuration is reopened; this journey does not migrate them. + ### `cli-setup` Automated: [CLI onboarding](../../cli/src/__tests__/onboard.test.ts) and [isolated test drive](../../cli/src/__tests__/test-drive.test.ts) cover their setup contracts; they do not establish that every model account is usable. diff --git a/maps/feature-map/runs-adapters.md b/maps/feature-map/runs-adapters.md index 738e7d4eb6b..b2d761d2944 100644 --- a/maps/feature-map/runs-adapters.md +++ b/maps/feature-map/runs-adapters.md @@ -37,12 +37,44 @@ models/authentication and unsupported Legacy-only options must fail visibly. Manual: Select the intended adapter and account, refresh model choices, and run its environment test. Save, reload, then execute a small task. Repeat with a deliberately unavailable account and verify the failure remains visible. +Choose Codex as the harness; the UI displays its name/icon for both runners. +Advanced identifies the automatic default and offers Paperclip Runner or Legacy. +Only new Codex agents default to the native runner on supported macOS or GNU/Linux +targets; the [shared selection contract](../../packages/shared/src/agent-runner.ts) +and [runtime prerequisites](../../packages/paperclip-runner/README.md) define the +platform/libc boundary. Windows and Alpine/musl remain Legacy; other harness +defaults are unchanged. Disabled adapters, external overrides, and selected-target +qualification also affect availability. + +For sandbox defaults, follow the [environment probe](./execution-environments.md#environment-settings) +recipe first. Discovery/creation read company-bound immutable-image evidence +without allocating a target. A mutable image or snapshot name does not establish +native compatibility. Then test the actual selected runtime with its selected +account/model: a legacy CLI or authentication probe alone cannot establish native +readiness. Missing dependencies, failed authentication, invalid models and +incompatible custom settings must remain actionable errors without silent runner +switching. Saved unchanged execution can still be tested without applying new +selection policy, but runtime/account validation must still run. + +Verify an automatic Codex hire and an explicit Legacy control through a useful +file artifact, follow-up and reload. Preserve Codex reasoning effort and account +isolation; retain explicit Legacy permission choices, including false. Reopen an +existing agent, make an unrelated edit, and confirm both its persisted runner and +next run stay unchanged. Use [hiring/import preservation](./agents.md#hire-agent) +for approval and portability controls. + ### `run-history` Automated: [live run routes](../../server/src/__tests__/agent-live-run-routes.test.ts) and [run event sequencing](../../server/src/__tests__/heartbeat-run-event-sequencing.test.ts) cover run data contracts. Manual: Start a disposable task, open its running transcript, and follow it to a terminal outcome. Reload and compare task/run IDs, status, output, and cost. Verify a failed run remains distinguishable from a completed run with useful output. +An explicit runner change starts a configuration revision and invalidates the +eligible session. Active runs, restart recovery and historical transcript parsing +retain their recorded execution configuration; do not infer their runner from a +later edit to the agent. Account/harness branding and execution identity are +different observations, both required in the evidence. + ## Gotchas - Model accounts are managed in Apps and inline agent account controls. diff --git a/packages/paperclip-runner/scripts/release-provider-pack.test.mjs b/packages/paperclip-runner/scripts/release-provider-pack.test.mjs index 5dded2a32fd..171edd46f06 100644 --- a/packages/paperclip-runner/scripts/release-provider-pack.test.mjs +++ b/packages/paperclip-runner/scripts/release-provider-pack.test.mjs @@ -52,7 +52,7 @@ test("release assembly rejects rehashed stale or missing packs before staging an const source = dirname(dirname(fileURLToPath(import.meta.url))); try { // Run the actual assembler in an isolated layout so it cannot overwrite built artifacts. - for (const file of ["scripts/stage-release-runner-binaries.mjs", "scripts/release-provider-pack.mjs", "src/live/runner-binary.ts", "acpx-profiles.json", "cursor-distributions.json"]) { + for (const file of ["scripts/stage-release-runner-binaries.mjs", "scripts/release-provider-pack.mjs", "src/live/runner-binary.ts", "acpx-profiles.json", "cursor-distributions.json", "release-runner-targets.json"]) { mkdirSync(dirname(join(root, file)), { recursive: true }); cpSync(join(source, file), join(root, file)); } const platforms = {}; diff --git a/server/src/services/agent-runner-selection.test.ts b/server/src/services/agent-runner-selection.test.ts index 9fd4d748ce1..a8d6bc60c27 100644 --- a/server/src/services/agent-runner-selection.test.ts +++ b/server/src/services/agent-runner-selection.test.ts @@ -9,7 +9,8 @@ vi.mock("./environment-probe.js", () => ({ readSandboxPlatformQualification: sta vi.mock("./environment-config.js", () => ({ resolveEnvironmentDriverConfigForRuntime: state.resolveEnvironment })); vi.mock("@paperclipai/adapter-utils/ssh", () => ({ runSshCommand: state.ssh })); import { AGENT_RUNNER_SSH_PLATFORM_COMMAND, agentRunnerAvailability, resolveAgentRunnerTargetForCompany, resolveNewAgentRunner, resolveNewAgentRunnerForCompany } from "./agent-runner-selection.js"; -import { buildCodexAcpConfig } from "@paperclipai/adapter-codex-local/server"; +import { buildCodexAcpConfig, resolveCodexExecutionEngine } from "@paperclipai/adapter-codex-local/server"; +import { secretService } from "./secrets.js"; import { localAgentRunnerTarget } from "../lib/agent-runner-platform.js"; const target = { driver: "local", platform: "linux", architecture: "x64" }; const qualifiedTargets = [target, { driver: "local", platform: "linux", architecture: "arm64" }, { driver: "local", platform: "darwin", architecture: "arm64" }, { driver: "local", platform: "darwin", architecture: "x64" }]; @@ -183,6 +184,71 @@ describe("server-owned Codex runner selection", () => { .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["acpPermissionMode"] } })); }); + it.each([undefined, "acp"])("rejects effective Legacy ACP network denial for engine=%s before native selection", async engine => { + const adapterConfig = { ...(engine ? { engine } : {}), env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(resolveCodexExecutionEngine(adapterConfig).engine).toBe("acp"); + expect(buildCodexAcpConfig(adapterConfig)).toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }); + // Native permissions use a separately trusted server variable. This would + // enable networking despite the saved Legacy flag without admission rejection. + const { codexNetworkAccess } = await import(new URL( + "../../../packages/paperclip-runner/src/drivers/codex/codex-security-config.ts", import.meta.url, + ).href); + expect(codexNetworkAccess({ PAPERCLIP_RUNNER_NETWORK_ACCESS: "enabled", PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" })).toBe(true); + for (const runner of [undefined, "auto", "paperclip"] as const) { + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"] } })); + } + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner: "legacy" })) + .toEqual({ adapterType: "codex_local", adapterConfig }); + expect(adapterConfig.env).toEqual({ PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" }); + }); + + it.each([ + { type: "plain", value: "false" }, + { type: "secret_ref", secretId: "fixture-policy", version: "latest" }, + { type: "user_secret_ref", key: "fixture-policy", version: "latest" }, + ])("does not discard a bound ACP network restriction or guess its value (%j)", async binding => { + const adapterConfig = { engine: "acp", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: binding } }; + if (binding.type === "plain") { + const resolved = await secretService({} as never).resolveEnvBindings("fixture-company", adapterConfig.env); + expect(buildCodexAcpConfig({ ...adapterConfig, env: resolved.env })) + .toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }); + } + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: ["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"] } })); + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target, runner: "legacy" })) + .toEqual({ adapterType: "codex_local", adapterConfig }); + }); + + it.each([ + { extraArgs: ["-c", "sandbox_workspace_write.network_access=true"] }, + { extraArgs: [], args: ["-c", "sandbox_workspace_write.network_access=true"] }, + ])("retains actionable argument errors when Legacy arguments override a network flag (%j)", args => { + const adapterConfig = { engine: "acp", ...args, env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(buildCodexAcpConfig(adapterConfig)).toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" } }); + expect(() => resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target })) + .toThrow(expect.objectContaining({ status: 422, details: { code: "agent_runner_config_incompatible", fields: [args.extraArgs.length ? "extraArgs" : "args"] } })); + }); + + it.each(["true", "False", " false ", { type: "plain", value: "true" }])("keeps compatible ACP environment settings (%j) and native idempotence", value => { + const adapterConfig = { engine: "acp", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: value, OPENAI_API_KEY: { type: "secret_ref", secretId: "fixture-api-key" } } }; + const literal = typeof value === "string" ? value : value.value; + expect(buildCodexAcpConfig({ ...adapterConfig, env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: literal } })) + .toMatchObject({ env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "true" } }); + const resolved = resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target }); + expect(resolved.adapterType).toBe("paperclip_runner"); + expect(resolved.adapterConfig.env).toEqual(adapterConfig.env); + expect(resolveNewAgentRunner(resolved)).toEqual(resolved); + }); + + it("keeps a saved native profile and an explicitly selected Legacy CLI's unused ACP flag unchanged", () => { + const adapterConfig = { engine: "cli", env: { PAPERCLIP_CODEX_ACP_NETWORK_ACCESS: "false" } }; + expect(resolveCodexExecutionEngine(adapterConfig).engine).toBe("cli"); + expect(resolveNewAgentRunner({ adapterType: "codex_local", adapterConfig, target }).adapterType).toBe("paperclip_runner"); + const saved = { adapterType: "paperclip_runner", adapterConfig: { provider: "codex", env: adapterConfig.env } }; + expect(resolveNewAgentRunner(saved)).toEqual(saved); + }); + it("preserves explicit historical native profiles, including managed configurations", () => { for (const config of [{ provider: "acpx", acpxAgent: "claude", model: "claude-sonnet-5" }, { provider: "claude_managed", managedProfileId: "profile", managedAgentsRetentionAcknowledged: true }, { provider: "aws_agentcore", agentCoreProfileId: "profile" }]) expect(resolveNewAgentRunner({ adapterType: "paperclip_runner", adapterConfig: config })).toEqual({ adapterType: "paperclip_runner", adapterConfig: config }); }); diff --git a/server/src/services/agent-runner-selection.ts b/server/src/services/agent-runner-selection.ts index 6842cf4c164..3d5ad289de2 100644 --- a/server/src/services/agent-runner-selection.ts +++ b/server/src/services/agent-runner-selection.ts @@ -155,6 +155,18 @@ export function resolveNewAgentRunner(input: AgentRunnerSelection): { adapterTyp if (config.dangerouslyBypassApprovalsAndSandbox === false) unsupported.push("dangerouslyBypassApprovalsAndSandbox"); if (config.dangerouslyBypassSandbox === false) unsupported.push("dangerouslyBypassSandbox"); if (unsupported.length) incompatible(unsupported.map(field => aliasSources.get(field) ?? field)); + const env = config.env && typeof config.env === "object" && !Array.isArray(config.env) + ? config.env as Record : {}; + const network = env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS; + const binding = network && typeof network === "object" && !Array.isArray(network) + ? network as Record : null; + // Native networking is server-owned. Never translate a Legacy restriction + // into that trusted variable, or guess the value of a deferred policy binding. + if (config.engine !== "cli" && (network === "false" + || (binding?.type === "plain" && binding.value === "false") + || binding?.type === "secret_ref" || binding?.type === "user_secret_ref")) { + incompatible(["env.PAPERCLIP_CODEX_ACP_NETWORK_ACCESS"]); + } const effort = config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort; for (const key of ["engine", "command", "extraArgs", "args", "agentCommand", "mode", "nonInteractivePermissions", "stateDir", "warmHandleIdleMs", "dangerouslyBypassApprovalsAndSandbox", "dangerouslyBypassSandbox", "search", "fastMode", "reasoningEffort", "effort"]) delete config[key]; if (config.model !== undefined && typeof config.model !== "string") incompatible(["model"]);