From 30bc9e40ce014ba55f367a3ce81bbbab7442c7fc Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 13 Oct 2026 10:59:57 -0500 Subject: [PATCH] Align native setup isolation and finish shared runtime pickers Use the server-resolved sandbox transport for native setup, retaining local and SSH isolation and stripping injected flags. Preserve the complete local and managed E2E roster and existing graders. Replace the remaining scoped native dropdowns with the shared picker without changing saved runner or account choices. Co-Authored-By: Paperclip --- doc/plans/2026-10-06-default-agent-runner.md | 12 +++ maps/agent-access-map/onboarding.md | 6 ++ maps/feature-map/onboarding.md | 6 ++ maps/storybook-map/INDEX.md | 20 ++-- maps/storybook-map/REVIEW.md | 17 ++++ maps/storybook-map/catalog.json | 46 +++++----- .../src/live/runnerd-codex-transport.test.ts | 22 +++-- .../src/live/runnerd-codex-transport.ts | 2 +- .../native-runtime/setup-readiness.test.ts | 11 ++- .../native-runtime/setup-readiness.ts | 3 +- tests/runner-e2e/completion-quality.test.ts | 8 +- tests/runner-e2e/confirmation-replies.test.ts | 12 ++- tests/runner-e2e/pi-controls-evidence.test.ts | 2 +- .../codex-local/config-fields.test.tsx | 92 +++++++++++++++++++ ui/src/adapters/codex-local/config-fields.tsx | 51 +++++----- .../AgentConfigForm.render.test.tsx | 78 +++++++++++----- ui/src/components/AgentConfigForm.tsx | 23 +++-- 17 files changed, 299 insertions(+), 112 deletions(-) diff --git a/doc/plans/2026-10-06-default-agent-runner.md b/doc/plans/2026-10-06-default-agent-runner.md index 5356284579c..1a1030a4f0b 100644 --- a/doc/plans/2026-10-06-default-agent-runner.md +++ b/doc/plans/2026-10-06-default-agent-runner.md @@ -1666,6 +1666,18 @@ On the integrated checkout, two focused Codex runs pass 261 tests each across on Fresh-source remote package qualification, ordinary Apple Silicon onboarding and immutable managed staging onboarding remain pending. The previous canary60bc and stagingd67 receipts stay attributed to those sources. The new budget record reserves resources60/provider30/cleanup10 dollars and retains all prior unknown holds. The supported private Cloud skill and CLI are read from exact private master `4f3a63ad6f3f84c87e0e129da35909d8ccc7ffb0`; a deliberate pre-merge Core preview is separate from actual private Cloud distribution. Freeze the final reviewed product source before building; deploy only the owned pinned QA stack, with `set_default=false`. +Published intermediate448db859 passes the focused source controls and starts fresh hosted checks. A bounded managed-image review then identifies a concrete setup mismatch: real sandbox tasks select provider outer isolation, while `probeNativeRunnerEnvironment` overrides the verified transport choice with false. Correct this internal forwarding and derive its value from the server-resolved target; preserve local/SSH Codex isolation and removal of injected environment flags. The obsolete remote package qualification38423256993 is cancelled to limit costs and is excluded from final proof. Native/Docker work remains remote. Current source and new-head qualification must close the corrected boundary before acceptance or merge. + +The maintained Daytona execution image is distinct from a standard Core app image. A saved immutable OCI digest without snapshot/template fields uses normal environment probing, measured OS/architecture/libc, actual image binding and confirmed lease cleanup. The existing full-stack E2E workflow owns its content-addressed execution image and one explicit managed-onboarding cell; its image build and live cell are not yet dispatched. A Core app image cannot be assumed to have compatible Daytona boot behavior merely because it contains the daemon. No provider credential is read to work around this boundary. + +The isolation correction changes three production lines, with no API/configuration field or permission change. Inert launch baseline1/8 fails; matching treatment8/8 and23 inert/source neighbors pass. Selected-target server baseline12/12 fails; the full treatment suite51/51 passes, including contradictory configuration/environment flags. Direct server no-emit and independent source review pass. Read-only and approval policy, owner-bound credentials, deadlines and cleanup remain intact. A separately retained overbroad local-listener scaffold failure is not counted as native execution proof. + +Intermediate448 hosted CI completes successfully. Fresh review4/5 correctly identifies the new managed case entering a local-only confirmation fixture. Baseline reproduces two failures; scope the existing local fixture correctly, retain all twelve local cases and the thirteenth explicit managed case, and reuse the separate managed-fixture checks. The full existing E2E support run passes2694 controls and exposes two more stale roster assumptions. Update those assertions while retaining the completion judge for accepted managed work and every Pi admission/negative control. No parallel suite or replacement grader is added. Final source still needs fresh published checks/review and live qualification. + +Embedded-browser inspection of the built448 stories confirms the dark shared Runner popup and saved Legacy choice. It also exposes remaining OS-native environment/lifecycle/ACP dropdowns on the relevant runtime page. Their narrow shared-picker conversion preserves all values and callbacks and leaves unrelated provider-specific controls alone. These UI changes require a new JavaScript-only Storybook build and rendering checks; the old build remains literal448 evidence. No screenshot baseline is created or changed. + +The corrected source passes the full existing E2E support suite:125 files/2697 tests, plus all128 existing Node source/qualification contract controls and direct E2E no-emit. Relevant dropdown baseline145/145 and treatment152/152 pass, including exact save requests for selecting/clearing an environment while retaining Legacy/native execution and accounts. UI no-emit and token gates pass. Independent review of the managed-isolation and confirmation-fixture corrections is clear. These are local source checks; exact-head hosted CI/review, fresh remote packages, rendered stories and real Mac/managed onboarding still remain required. Next action is a single corrected candidate publication, then affected qualification using the existing workflows. + Current published Codex head1a0ca662 is draft with green historical checks. Actual master833769ab is newer than merged prerequisite d67 and release repair60bc. Prepared private onboarding/Storybook work will be reconciled by source, without claiming its private/historical checks as final qualification. Native Connect correction is already in the Codex slice. Final managed defaults require the saved immutable OCI environment probe owned by ordinary Connect. Existing suites/fixtures/assertions/reporting are reused; no duplicate grader or silent Legacy fallback is added. All heavy builds stay remote, Codex payloads remain external official npm dependencies, and saved agents retain runners. Next action: preserve/commit current evidence, integrate actual master plus reviewed prepared corrections, run targeted checks, freeze/push candidate, require fresh fullCI/review, then qualify ordinary creation/onboarding on Mac and the one staging target before protected squash merge. diff --git a/maps/agent-access-map/onboarding.md b/maps/agent-access-map/onboarding.md index ef413c8084d..5530589d3f6 100644 --- a/maps/agent-access-map/onboarding.md +++ b/maps/agent-access-map/onboarding.md @@ -97,6 +97,12 @@ identity or unconfirmed cleanup remains visible. No probe is added to creation or discovery, no agent/company grant is broadened, and provider authentication errors never cause an automatic runner switch. +Native setup derives outer sandbox isolation from the server-resolved execution +target, as task execution does. This is an internal transport choice, not a +request/configuration permission. Local and SSH setup keep Codex isolation; +caller-supplied environment flags do not authorize an external sandbox. Existing +company, account, probe and registration-cleanup gates still apply. + Scoped source review: 2026-10-13, exact baseline `1a0ca66242121e39d88455aa2b52a07acc4a07d9` plus the privately composed onboarding correction. [Wizard controls](../../ui/src/components/OnboardingWizard.test.tsx) diff --git a/maps/feature-map/onboarding.md b/maps/feature-map/onboarding.md index 774bcb95f24..6de1b9b52ff 100644 --- a/maps/feature-map/onboarding.md +++ b/maps/feature-map/onboarding.md @@ -70,6 +70,12 @@ themselves allocate no target. A valid measured unsupported target follows the server's Legacy default. Named Cloud snapshots remain a separate unqualified boot-identity and fresh-signup gap. +Selected native setup must use the same isolation as task execution. A resolved +sandbox transport uses the provider's outer isolation; local and SSH transports +retain Codex isolation. An injected environment flag cannot establish that +transport identity. Verify these boundaries with the existing native setup +controls and a real managed task/follow-up; source checks alone are insufficient. + The explicit manual Product E2E cell `confirmation-replies.runner-codex.daytona.task-card-accept` covers this fresh managed wizard path without fixture agent creation or a fixture preprobe. Its diff --git a/maps/storybook-map/INDEX.md b/maps/storybook-map/INDEX.md index d9ec42885ff..18c7581fbaa 100644 --- a/maps/storybook-map/INDEX.md +++ b/maps/storybook-map/INDEX.md @@ -18,9 +18,9 @@ Instance setup, company wizard, first agent and task. | Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note | | --- | --- | --- | --- | --- | -| [Getting started and access/Onboarding and first work/Onboarding/Agent arc](../../ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx) | `onboarding/adapter`, `onboarding/first-task` | unreviewed / production / fixtures | 31 / 11 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Four managed Codex cases use scoped immutable-image discovery and a stateful saved-probe fixture: native default, measured Legacy, permission refusal, and unconfirmed cleanup. | -| [Getting started and access/Onboarding and first work/Onboarding/Character](../../ui/storybook/stories/onboarding/onboarding-character.stories.tsx) | `onboarding/company` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Getting started and access/Onboarding and first work/Onboarding/Connect a model](../../ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx) | `onboarding/adapter`, `onboarding/first-task` | historical / prototype / fixtures | 5 / 0 | Older tile/mode preview; onboarding-agent-arc mounts the production wizard. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Getting started and access/Onboarding and first work/Onboarding/Agent arc](../../ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx) | `onboarding/adapter`, `onboarding/first-task` | unreviewed / production / fixtures | 31 / 11 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Four managed Codex cases use scoped immutable-image discovery and a stateful saved-probe fixture: native default, measured Legacy, permission refusal, and unconfirmed cleanup. Native isolation recipe added; these fixture setup responses do not establish server-resolved transport isolation, real sandbox cleanup, or local/SSH enforcement. | +| [Getting started and access/Onboarding and first work/Onboarding/Character](../../ui/storybook/stories/onboarding/onboarding-character.stories.tsx) | `onboarding/company` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The added native isolation recipe is outside this character preview’s demonstrated scope. | +| [Getting started and access/Onboarding and first work/Onboarding/Connect a model](../../ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx) | `onboarding/adapter`, `onboarding/first-task` | historical / prototype / fixtures | 5 / 0 | Older tile/mode preview; onboarding-agent-arc mounts the production wizard. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The expanded native isolation recipe is not exercised by this retained historical tile preview. | ### Login, invitations, and access @@ -198,15 +198,15 @@ Agent identity, instructions, reporting lines, lifecycle, and built-ins. | --- | --- | --- | --- | --- | | [Agents and reusable capabilities/Hiring, configuration, and organization/Pages/Agent Detail](../../ui/storybook/stories/agents/agent-detail.stories.tsx) | `agents/lifecycle`, `agents/instructions` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Persistent files](../../ui/storybook/stories/agents/agent-files.stories.tsx) | `agents/instructions` | unreviewed / production / fixtures | 9 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Hiring, configuration, and organization/Product/Agent Management](../../ui/storybook/stories/agents/agent-management.stories.tsx) | `agents/lifecycle`, `agents/instructions`, `execution-environments/assignment` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. ForcedKubernetesExecution and ForcedKubernetesMissingEnvironment mount AgentConfigForm with a forced execution target or missing-target warning. Read-only assignment presentation; no saved target or remote execution proof. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Agents and reusable capabilities/Hiring, configuration, and organization/Product/Agent Management](../../ui/storybook/stories/agents/agent-management.stories.tsx) | `agents/lifecycle`, `agents/instructions`, `execution-environments/assignment` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. ForcedKubernetesExecution and ForcedKubernetesMissingEnvironment mount AgentConfigForm with a forced execution target or missing-target warning. Read-only assignment presentation; no saved target or remote execution proof. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Source review of the Environment override and Codex lifecycle/ACP selectors confirms reuse of the harness SelectPopover with the existing option and save semantics; render/play and live execution of these updated controls remain unverified. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Personas/Full pages](../../ui/storybook/stories/agents/agent-persona-pages.stories.tsx) | `agents/organization`, `navigation-preferences/profile` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Master reconciliation inspected assignment-history recipient suggestions and manual-selection retention; existing stories do not establish the new assignment-history behavior. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Personas](../../ui/storybook/stories/agents/agent-personas.stories.tsx) | `agents/organization`, `navigation-preferences/profile` | unreviewed / production / fixtures | 19 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Configuration refresh](../../ui/storybook/stories/agents/agent-settings-redesign.stories.tsx) | `agents/lifecycle`, `agents/instructions` | mixed / mixed / fixtures | 20 / 1 | Production configuration panels in a locally composed settings shell; full AgentDetail route parity remains unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Existing Legacy/native Codex editing, light/mobile and Advanced states retained unchanged. | +| [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Configuration refresh](../../ui/storybook/stories/agents/agent-settings-redesign.stories.tsx) | `agents/lifecycle`, `agents/instructions` | mixed / mixed / fixtures | 20 / 1 | Production configuration panels in a locally composed settings shell; full AgentDetail route parity remains unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Existing Legacy/native Codex editing, light/mobile and Advanced states retained unchanged. Source review of the Environment override and Codex lifecycle/ACP selectors confirms reuse of the harness SelectPopover with the existing option and save semantics; render/play and live execution of these updated controls remain unverified. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Product/Built-in Agents](../../ui/storybook/stories/agents/built-in-agents.stories.tsx) | `agents/built-in` | unreviewed / production / fixtures | 4 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Codex runner/Creation](../../ui/storybook/stories/agents/codex-runner/creation.stories.tsx) | `agents/hire`, `runs-adapters/validation` | current / production / fixtures | 9 / 7 | Production NewAgent and AgentBasicsDialog with isolated API fixtures. Automatic and explicit Legacy choices, qualified/unqualified discovery, old native links, setup failure and returned hire identity are asserted in source. Fixture runtime selection follows the requested runner and simulated target qualification; render/play, production persistence and provider execution are not verified by this source pass. | +| [Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Codex runner/Creation](../../ui/storybook/stories/agents/codex-runner/creation.stories.tsx) | `agents/hire`, `runs-adapters/validation` | current / production / fixtures | 9 / 7 | Production NewAgent and AgentBasicsDialog with isolated API fixtures. Automatic and explicit Legacy choices, qualified/unqualified discovery, old native links, setup failure and returned hire identity are asserted in source. Fixture runtime selection follows the requested runner and simulated target qualification; render/play, production persistence and provider execution are not verified by this source pass. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/External agent invitation/Components](../../ui/storybook/stories/agents/external-agent-invite/components.stories.tsx) | `agents/hire`, `access/invitations` | unreviewed / production / fixtures | 8 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/External agent invitation/Journeys](../../ui/storybook/stories/agents/external-agent-invite/journeys.stories.tsx) | `agents/hire`, `access/invitations` | unreviewed / production / fixtures | 20 / 4 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/New agent](../../ui/storybook/stories/agents/new-agent-onboarding.stories.tsx) | `agents/hire` | historical / prototype / fixtures | 37 / 1 | Retained local wizard predates production ui/src/components/new-agent flow; shared fields do not make the wizard current. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/New agent](../../ui/storybook/stories/agents/new-agent-onboarding.stories.tsx) | `agents/hire` | historical / prototype / fixtures | 37 / 1 | Retained local wizard predates production ui/src/components/new-agent flow; shared fields do not make the wizard current. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/00 Review guide](../../ui/storybook/stories/agents/primary-agent/00-review-guide.stories.tsx) | `agents/primary-agent`, `tasks/create`, `agent-chat/discovery` | unreviewed / guide / static | 1 / 0 | Review guide retained; explanatory content does not establish runtime or production coverage. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Primary choice, replacement confirmation, unavailable states and task/chat fallback presentation use local presentation-provider callbacks. Production preference persistence and default-resolution precedence remain unverified by this suite. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/01 Components](../../ui/storybook/stories/agents/primary-agent/01-components.stories.tsx) | `agents/primary-agent`, `tasks/create`, `agent-chat/discovery` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Primary choice, replacement confirmation, unavailable states and task/chat fallback presentation use local presentation-provider callbacks. Production preference persistence and default-resolution precedence remain unverified by this suite. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/02 Sidebar](../../ui/storybook/stories/agents/primary-agent/02-sidebar.stories.tsx) | `agents/primary-agent`, `tasks/create`, `agent-chat/discovery` | unreviewed / production / fixtures | 12 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Expanded upstream verification recipes were inspected; the existing fixture scenarios do not prove the added backend handoff, workspace, recovery or authorization contracts. Primary choice, replacement confirmation, unavailable states and task/chat fallback presentation use local presentation-provider callbacks. Production preference persistence and default-resolution precedence remain unverified by this suite. Creation-visibility verification recipes were updated; this story scenario is unchanged and does not prove that journey. The filter uses one lowercase label without helper text. All chat-channel tasks count as bot-created; the focused visibility stories cover human-initiated channel work. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | @@ -233,12 +233,12 @@ Adapter/model setup, account validation, transcripts, and run history. | [Agents and reusable capabilities/Runs, harnesses, and model accounts/Assistant connections/Dot Runner](../../ui/storybook/stories/runs-adapters/dot-runner-connection.stories.tsx) | `runs-adapters/validation`, `gateways-profiles/client-connect` | unreviewed / production / fixtures | 4 / 2 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/07 Subscription sign-in](../../ui/storybook/stories/runs-adapters/local-ai-subscription.stories.tsx) | `runs-adapters/validation` | unreviewed / production / fixtures | 4 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/00 Overview](../../ui/storybook/stories/runs-adapters/provider-routing-00-overview.stories.tsx) | `runs-adapters/validation` | unreviewed / guide / static | 1 / 0 | Review guide retained; explanatory content does not establish runtime or production coverage. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/01 Onboarding](../../ui/storybook/stories/runs-adapters/provider-routing-01-onboarding.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 6 / 3 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/01 Onboarding](../../ui/storybook/stories/runs-adapters/provider-routing-01-onboarding.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 6 / 3 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/02 Connect](../../ui/storybook/stories/runs-adapters/provider-routing-02-connect.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 14 / 3 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/03 Agent](../../ui/storybook/stories/runs-adapters/provider-routing-03-agent.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 34 / 7 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/03 Agent](../../ui/storybook/stories/runs-adapters/provider-routing-03-agent.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 34 / 7 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/04 Manage](../../ui/storybook/stories/runs-adapters/provider-routing-04-manage.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 11 / 5 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/05 Recovery](../../ui/storybook/stories/runs-adapters/provider-routing-05-recovery.stories.tsx) | `runs-adapters/validation` | mixed / mixed / fixtures | 7 / 2 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | -| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/06 Production components](../../ui/storybook/stories/runs-adapters/provider-routing-06-production.stories.tsx) | `runs-adapters/validation` | unreviewed / production / fixtures | 5 / 5 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | +| [Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/06 Production components](../../ui/storybook/stories/runs-adapters/provider-routing-06-production.stories.tsx) | `runs-adapters/validation` | unreviewed / production / fixtures | 5 / 5 | Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof. | | [Agents and reusable capabilities/Runs, harnesses, and model accounts/Composer/Session goals](../../ui/storybook/stories/runs-adapters/session-goals.stories.tsx) | `runs-adapters/session` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. | ### Skills and Skill Studio diff --git a/maps/storybook-map/REVIEW.md b/maps/storybook-map/REVIEW.md index 954448ee6d8..2a9b12635da 100644 --- a/maps/storybook-map/REVIEW.md +++ b/maps/storybook-map/REVIEW.md @@ -637,3 +637,20 @@ The incoming recovery entry metadata is retained. No classifications or visual baselines were promoted. These results prove source inventory, references and structural contracts; no new rendering, `play`, provider, permission, cleanup or production-continuity acceptance is claimed for the merged source. + +## Codex configuration dropdown source review — 2026-10-13 + +Inspected the frozen AgentConfigForm environment override and Codex runtime +lifecycle/ACP dropdown replacements. They now use the same SelectPopover as the +harness picker, retaining their options, normalization and create/edit callbacks. +Refreshed seven existing suite fingerprints after tracing their rendered subjects; +added the Codex config-fields dependency to the agent-management and saved-settings +suites that actually mount those fields. The other five suites import picker fields +or substitute local runtime content, so their notes explicitly exclude the changed +controls. Historical and mixed classifications, all prior review histories and +Legacy/native states remain intact. + +Canonical catalog generation and the existing structural tests establish source +inventory and references only. The prior Storybook build at `448db859e3` predates +these dropdown edits; it does not prove their rendering or interactions. No new +render/play, visual-baseline, provider or production-persistence proof is claimed. diff --git a/maps/storybook-map/catalog.json b/maps/storybook-map/catalog.json index 2c692e1be1c..aac2ba40263 100644 --- a/maps/storybook-map/catalog.json +++ b/maps/storybook-map/catalog.json @@ -473,7 +473,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. ForcedKubernetesExecution and ForcedKubernetesMissingEnvironment mount AgentConfigForm with a forced execution target or missing-target warning. Read-only assignment presentation; no saved target or remote execution proof. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. ForcedKubernetesExecution and ForcedKubernetesMissingEnvironment mount AgentConfigForm with a forced execution target or missing-target warning. Read-only assignment presentation; no saved target or remote execution proof. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Source review of the Environment override and Codex lifecycle/ACP selectors confirms reuse of the harness SelectPopover with the existing option and save semantics; render/play and live execution of these updated controls remain unverified.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/agents.md", @@ -489,9 +489,10 @@ "maps/feature-map/execution-environments.md", "ui/src/adapters/adapter-display-registry.ts", "ui/src/components/CodexRunnerSelect.tsx", - "ui/src/components/ui/select.tsx" + "ui/src/components/ui/select.tsx", + "ui/src/adapters/codex-local/config-fields.tsx" ], - "fingerprint": "e4132d95c94d9fc741bdff39f6ca1ebddebdadd39719826b9b1358ab701e0b1e", + "fingerprint": "4c6bfad3978afe78514282e6dcc58fcadaf5cfb89adccc5661c964c523b700e3", "stableId": "product-agent-management", "sidebarTitle": "Agents and reusable capabilities/Hiring, configuration, and organization/Product/Agent Management" }, @@ -560,7 +561,7 @@ "status": "mixed", "implementation": "mixed", "boundary": "fixtures", - "notes": "Production configuration panels in a locally composed settings shell; full AgentDetail route parity remains unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Existing Legacy/native Codex editing, light/mobile and Advanced states retained unchanged.", + "notes": "Production configuration panels in a locally composed settings shell; full AgentDetail route parity remains unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Existing Legacy/native Codex editing, light/mobile and Advanced states retained unchanged. Source review of the Environment override and Codex lifecycle/ACP selectors confirms reuse of the harness SelectPopover with the existing option and save semantics; render/play and live execution of these updated controls remain unverified.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/agents.md", @@ -583,9 +584,10 @@ "ui/storybook/stories/agents/agent-settings-redesign.stories.tsx", "ui/src/adapters/adapter-display-registry.ts", "ui/src/components/CodexRunnerSelect.tsx", - "ui/src/components/ui/select.tsx" + "ui/src/components/ui/select.tsx", + "ui/src/adapters/codex-local/config-fields.tsx" ], - "fingerprint": "2fc42facb1475c3b8180c625656055823525b4f62b48edc5df6f4650b4b360e8", + "fingerprint": "fe0647d007c3221a2700ad6dd8d62d9db989377c79e87be35e03cf6e53253a00", "stableId": "agents-configuration-refresh", "sidebarTitle": "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Configuration refresh" }, @@ -3184,7 +3186,7 @@ "status": "historical", "implementation": "prototype", "boundary": "fixtures", - "notes": "Retained local wizard predates production ui/src/components/new-agent flow; shared fields do not make the wizard current. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Retained local wizard predates production ui/src/components/new-agent flow; shared fields do not make the wizard current. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/agents.md", @@ -3210,7 +3212,7 @@ "ui/src/components/ui/select.tsx" ], "replacement": "ui/storybook/stories/agents/agent-persona-pages.stories.tsx", - "fingerprint": "34f949b23e845d7ea08c274315cfd8e5c221fe3616124edd78e1385051b69008", + "fingerprint": "369a9343261cad73ee699c82c42e9cd5c5f2b8f6654e25cce04e1af626943499", "stableId": "onboarding-new-agent", "sidebarTitle": "Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/New agent" }, @@ -3299,7 +3301,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Four managed Codex cases use scoped immutable-image discovery and a stateful saved-probe fixture: native default, measured Legacy, permission refusal, and unconfirmed cleanup.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. Four managed Codex cases use scoped immutable-image discovery and a stateful saved-probe fixture: native default, measured Legacy, permission refusal, and unconfirmed cleanup. Native isolation recipe added; these fixture setup responses do not establish server-resolved transport isolation, real sandbox cleanup, or local/SSH enforcement.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/onboarding.md", @@ -3313,7 +3315,7 @@ "ui/src/components/ui/select.tsx", "ui/storybook/fixtures/paperclipData.ts" ], - "fingerprint": "5abbd0a737b931fadc44cd57d618336defede2cf8f92f0dbfed2edaaef88272e", + "fingerprint": "9b4ecac7c692291fe4be571ced273b9c56f390050e1efba5f3601137bec3f517", "stableId": "onboarding-agent-arc", "sidebarTitle": "Getting started and access/Onboarding and first work/Onboarding/Agent arc" }, @@ -3326,7 +3328,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The added native isolation recipe is outside this character preview’s demonstrated scope.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/onboarding.md", @@ -3334,7 +3336,7 @@ "ui/src/components/ui/button.tsx", "ui/storybook/stories/onboarding/onboarding-character.stories.tsx" ], - "fingerprint": "f6907cd663ffa4d68b5a096c9cd2b331bb8efacdf54736c4035e81bc5eb822bd", + "fingerprint": "0b67eb03cbd6567a64afddead9c02970fd2d7fb002fd11fe6128b4916bd4c7ac", "stableId": "onboarding-character", "sidebarTitle": "Getting started and access/Onboarding and first work/Onboarding/Character" }, @@ -3348,7 +3350,7 @@ "status": "historical", "implementation": "prototype", "boundary": "fixtures", - "notes": "Older tile/mode preview; onboarding-agent-arc mounts the production wizard. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Older tile/mode preview; onboarding-agent-arc mounts the production wizard. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. The expanded native isolation recipe is not exercised by this retained historical tile preview.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/onboarding.md", @@ -3356,7 +3358,7 @@ "ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx" ], "replacement": "ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx", - "fingerprint": "f835bc68ceaba5ee2338f7564c01d9d54d41df78c186d6962d15215f7c8bf7de", + "fingerprint": "7ee1f3c943f9ec71ec4e4ceda46d3ba20ccca5ee2c8c7c7b5ba889f93edd9507", "stableId": "onboarding-connect-a-model", "sidebarTitle": "Getting started and access/Onboarding and first work/Onboarding/Connect a model" }, @@ -4136,7 +4138,7 @@ "status": "mixed", "implementation": "mixed", "boundary": "fixtures", - "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/runs-adapters.md", @@ -4176,7 +4178,7 @@ "ui/src/components/CodexRunnerSelect.tsx", "ui/src/components/ui/select.tsx" ], - "fingerprint": "07200aabed585ad5def71a98917158311b247024636a2b3356afdeb1cb0e6347", + "fingerprint": "412a440c2ac7a72cde3384c387a64aeec24a4ca22307f2fd13bbca827c5890e0", "stableId": "ai-connections-provider-routing-01-onboarding", "sidebarTitle": "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/01 Onboarding" }, @@ -4220,7 +4222,7 @@ "status": "mixed", "implementation": "mixed", "boundary": "fixtures", - "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/runs-adapters.md", @@ -4260,7 +4262,7 @@ "ui/src/components/CodexRunnerSelect.tsx", "ui/src/components/ui/select.tsx" ], - "fingerprint": "ca81928905321887730941e99c1992dbe622dc37d4bdced6a042cde68281508c", + "fingerprint": "64bf50840c715749b66280c72901a8eecac5500831f00df96ff568579d64c856", "stableId": "ai-connections-provider-routing-03-agent", "sidebarTitle": "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/03 Agent" }, @@ -4331,7 +4333,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof.", + "notes": "Design-review route uses simulated account/provider state; audit the rendered subject before treating it as the live connection flow. Codex selection/branding and upstream recipe source changes inspected; prior parity limitations remain, with no new rendered or live proof. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/runs-adapters.md", @@ -4351,7 +4353,7 @@ "ui/src/components/CodexRunnerSelect.tsx", "ui/src/components/ui/select.tsx" ], - "fingerprint": "d24f11571c1b8bec0080b062b7fc3fcc66171bed6ba68a77dbaef5982f6ab407", + "fingerprint": "0adf7fa67da78ff0b655c8054b637aa6ae7ffd376299da905b2d426c5b2500cc", "stableId": "ai-connections-provider-routing-06-production-components", "sidebarTitle": "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/06 Production components" }, @@ -5769,7 +5771,7 @@ "status": "current", "implementation": "production", "boundary": "fixtures", - "notes": "Production NewAgent and AgentBasicsDialog with isolated API fixtures. Automatic and explicit Legacy choices, qualified/unqualified discovery, old native links, setup failure and returned hire identity are asserted in source. Fixture runtime selection follows the requested runner and simulated target qualification; render/play, production persistence and provider execution are not verified by this source pass.", + "notes": "Production NewAgent and AgentBasicsDialog with isolated API fixtures. Automatic and explicit Legacy choices, qualified/unqualified discovery, old native links, setup failure and returned hire identity are asserted in source. Fixture runtime selection follows the requested runner and simulated target qualification; render/play, production persistence and provider execution are not verified by this source pass. AgentConfigForm shared-module dropdown changes inspected; this suite demonstrates imported picker fields or substitutes local runtime content, not the updated environment and Codex lifecycle/ACP controls. No new render/play or live proof.", "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/agents.md", @@ -5785,7 +5787,7 @@ "ui/storybook/fixtures/onboardingEnvironment.ts", "ui/storybook/stories/agents/codex-runner/creation.stories.tsx" ], - "fingerprint": "c9adeb976f4b6d488680cc2829026ac3edf706f4af3774d72ebabdb0d0d4e243", + "fingerprint": "5055ac064b22cda06d70b9f41049f0c8fb22cb94f35398cb0690bc13980b22d9", "stableId": "agents-codex-runner-creation", "sidebarTitle": "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Codex runner/Creation" } diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index b44f6b7c36a..f58e018a809 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -2830,12 +2830,14 @@ const fakeCodex = resolve( "../../runner/target/debug/fake-codex-app-server", ); -it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooperative-release", "launch-failure"] as const)( +it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooperative-release", "launch-failure", "launch-failure-sandbox", "launch-failure-unsandboxed"] as const)( "bounds setup registration teardown and retains unconfirmed state (%s)", async mode => { const root = await mkdtemp(join(tmpdir(), "runnerd-setup-registration-")); const runtimeDirectory = join(root, "native-state"); await mkdir(runtimeDirectory, { mode: 0o700 }); + const launchFailure = mode.startsWith("launch-failure"); + let template: Record | null = null; let registrationStarted!: () => void; const started = new Promise(resolve => { registrationStarted = resolve; }); let returnRegistration!: (registration: { connection: { mode: "listen"; listenAddress: string; listenPort: number; listenPath: string }; release: () => Promise }) => void; @@ -2853,17 +2855,22 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper }; let registrationSignal: AbortSignal | undefined; const cleanup = vi.fn(async () => { await rm(runtimeDirectory, { recursive: true, force: true }); }); - const launch = vi.fn(() => { throw new Error("owned launcher failed before execution"); }); + const launch = vi.fn((spec: durableControlPlane.RunnerProcessLaunchSpec) => { + expect(spec.environment.PAPERCLIP_RUNNER_EXTERNAL_SANDBOX).toBeUndefined(); + throw new Error("owned launcher failed before execution"); + }); let settled = false; vi.useFakeTimers(); const result = probeNativeRunnerEnvironment({ runtimeDirectory, provider: "codex", model: "gpt-test", timeoutMs: 1_000, - environment: { PATH: "", OPENAI_API_KEY: "inert-test-key" }, + environment: { PATH: "", OPENAI_API_KEY: "inert-test-key", PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "true" }, transportOptions: { + ...(mode === "launch-failure-sandbox" ? { externallySandboxed: true } : mode === "launch-failure-unsandboxed" ? { externallySandboxed: false } : {}), runnerBinary: resolve(import.meta.dirname, "../../package.json"), codexCommand: resolve(import.meta.dirname, "../../package.json"), sourceCodexHome: "", runnerProcessLauncher: launch, controlPlaneRegistration: async (_authority, _identity, signal) => { + template = structuredClone(_authority.store.state.runAttachTemplate!); registrationSignal = signal; if (mode === "cooperative-release") signal?.addEventListener("abort", () => returnRegistration(registered), { once: true }); registrationStarted(); @@ -2874,16 +2881,17 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper }).then(value => { settled = true; return value; }, (error: unknown) => { settled = true; return error; }); try { await started; - if (mode === "launch-failure") returnRegistration(registered); + if (launchFailure) returnRegistration(registered); await vi.advanceTimersByTimeAsync(1_000); if (mode === "late-release" || mode === "late-release-error" || mode === "stuck-release") returnRegistration(registered); await vi.advanceTimersByTimeAsync(10_000); - if (mode === "late-release" || mode === "cooperative-release" || mode === "launch-failure") await result; + if (mode === "late-release" || mode === "cooperative-release" || launchFailure) await result; expect(settled).toBe(true); expect(registrationSignal?.aborted).toBe(true); const failure = await result; + expect(template).toMatchObject({ provider: { externallySandboxed: mode === "launch-failure-sandbox", readOnly: true, approvalPolicy: "never", collaborationMode: "plan" } }); expect(failure).toBeInstanceOf(Error); - expect((failure as Error).message).toContain(mode === "launch-failure" ? "owned launcher failed before execution" : "Native provider hello probe timed out."); + expect((failure as Error).message).toContain(launchFailure ? "owned launcher failed before execution" : "Native provider hello probe timed out."); if (mode === "stuck" || mode === "stuck-release" || mode === "late-release-error") { expect(failure).toBeInstanceOf(AggregateError); expect((failure as AggregateError).cause).toMatchObject({ message: "Native provider hello probe timed out." }); @@ -2900,7 +2908,7 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper expect(cleanup).toHaveBeenCalledOnce(); await expect(stat(runtimeDirectory)).rejects.toThrow(); } - expect(launch).toHaveBeenCalledTimes(mode === "launch-failure" ? 1 : 0); + expect(launch).toHaveBeenCalledTimes(launchFailure ? 1 : 0); expect(vi.getTimerCount()).toBe(0); } finally { returnRegistration(registered); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 75a7543ff7e..32d623cef3b 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -7210,7 +7210,7 @@ export async function probeNativeRunnerEnvironment(options: { sourceCodexHome: sourceCodexHome ?? "", stateDirectory: options.runtimeDirectory, environment, - externallySandboxed: false, + externallySandboxed: options.transportOptions?.externallySandboxed === true, closeGraceMs: 10_000, lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, }); diff --git a/server/src/services/native-runtime/setup-readiness.test.ts b/server/src/services/native-runtime/setup-readiness.test.ts index dc67b294511..e4738ca6bcf 100644 --- a/server/src/services/native-runtime/setup-readiness.test.ts +++ b/server/src/services/native-runtime/setup-readiness.test.ts @@ -13,7 +13,7 @@ vi.mock("../../vendor/paperclip-runner/index.js", async original => ({ ...await vi.mock("./native-session-executor.js", async original => ({ ...await original(), createRemoteNativeArtifactPreparation: nativeArtifacts, createRemoteRunnerProcessLauncher: remoteLauncher })); import { testNativeRunnerAuthentication } from "./setup-readiness.js"; const context = { - companyId: "company", adapterType: "paperclip_runner", config: {}, + companyId: "company", adapterType: "paperclip_runner", config: { externallySandboxed: false, env: { PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "false" } }, executionTarget: { kind: "remote" as const, transport: "sandbox" as const, providerKey: "test", remoteCwd: "/workspace", runner: { execute: vi.fn() } }, }; describe("Codex selected native account verification", () => { @@ -43,11 +43,12 @@ describe("Codex selected native account verification", () => { vi.stubEnv(key, "ambient-account"); vi.stubEnv("ANTHROPIC_API_KEY", "ambient-anthropic"); nativeProbe.mockImplementation(async input => { await input.onCleanupConfirmed(); return receipt(provider, model); }); - const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: { env: { [key]: "bound-account" }, modelReasoningEffort: "low" } }, provider, model); + const result = await testNativeRunnerAuthentication({ companyId: "company", adapterType: "paperclip_runner", config: { externallySandboxed: true, env: { [key]: "bound-account", PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "true" }, modelReasoningEffort: "low" } }, provider, model); expect(result.status).toBe("pass"); const input = nativeProbe.mock.calls[0][0]; expect(input).toMatchObject({ provider, model, timeoutMs: 45_000, environment: { [key]: "bound-account" } }); expect(input.transportOptions.runnerBinary).toBe("/fixture/bin/paperclip-runnerd"); + expect(input.transportOptions.externallySandboxed).toBe(false); expect(selectedRunner).toHaveBeenCalledOnce(); expect(input.environment.ANTHROPIC_API_KEY).toBeUndefined(); if (provider === "codex") expect(input.reasoningEffort).toBe("low"); @@ -110,10 +111,10 @@ describe("Codex selected native account verification", () => { await input.onCleanupConfirmed(); return receipt("codex", "gpt-6.1-sol"); }); - expect((await testNativeRunnerAuthentication({ ...sshContext, config: { env: { OPENAI_API_KEY: "company-account" }, modelReasoningEffort: "low" } }, "codex", "gpt-6.1-sol")).status).toBe("pass"); + expect((await testNativeRunnerAuthentication({ ...sshContext, config: { externallySandboxed: true, env: { OPENAI_API_KEY: "company-account", PAPERCLIP_RUNNER_EXTERNAL_SANDBOX: "true" }, modelReasoningEffort: "low" } }, "codex", "gpt-6.1-sol")).status).toBe("pass"); const input = nativeProbe.mock.calls[0][0]; expect(input).toMatchObject({ reasoningEffort: "low", environment: { OPENAI_API_KEY: "company-account" }, workingDirectory: expect.stringMatching(/^\/qa-workspace\/.paperclip-runtime\/paperclip-native-setup-.*\/filesystem\/workspace$/) }); - expect(input.transportOptions).toMatchObject({ runnerBinary: selectedBinary, codexCommand: expect.stringMatching(/\/bin\/codex$/), runnerFilesystemRoot: expect.stringMatching(/\/filesystem$/) }); + expect(input.transportOptions).toMatchObject({ runnerBinary: selectedBinary, codexCommand: expect.stringMatching(/\/bin\/codex$/), runnerFilesystemRoot: expect.stringMatching(/\/filesystem$/), externallySandboxed: false }); expect(nativeArtifacts).toHaveBeenCalledWith(expect.objectContaining({ controllerRunnerBinary: selectedBinary, model: "gpt-6.1-sol", targetPlatformArtifactVerified: true })); expect(selectedRemoteRunner).toHaveBeenCalledExactlyOnceWith({ runner: { execute: sshExecute }, remoteCwd: "/qa-workspace", configuredPath: null }); expect(selectedRunner).not.toHaveBeenCalled(); @@ -257,6 +258,7 @@ describe("Codex selected native account verification", () => { expect(selectedRunner).not.toHaveBeenCalled(); expect(nativeArtifacts).toHaveBeenCalledWith(expect.objectContaining({ target, runner: target.runner, controllerRunnerBinary: selectedBinary, targetPlatformArtifactVerified: true })); expect(nativeProbe.mock.calls[0][0].transportOptions.runnerBinary).toBe(selectedBinary); + expect(nativeProbe.mock.calls[0][0].transportOptions.externallySandboxed).toBe(true); expect(nativeArtifacts.mock.results[0].value.prepare).toHaveBeenCalledWith("dial_wss"); expect(execute).not.toHaveBeenCalled(); }); @@ -288,6 +290,7 @@ describe("Codex selected native account verification", () => { const target = { ...context.executionTarget, providerKey: "daytona", runner: { execute: sshExecute }, leaseId: "lease", effectiveCapabilities: { reusableLeases: false, nativeSyncIn: false, nativeSyncOut: false, persistentProcessSessions: false, independentControlCommands: true, incrementalSessionOutput: false, concurrentSyncOperations: false, duplexCommandStream: false, runnerWebSocketIngress: true }, getRunnerIngressEndpoint }; expect((await testNativeRunnerAuthentication({ ...context, executionTarget: target }, "codex", "gpt-6.1-sol")).status).toBe("pass"); expect(order).toEqual(["artifacts", "ingress", "connect"]); + expect(nativeProbe.mock.calls[0][0].transportOptions.externallySandboxed).toBe(true); expect(connectionClose).toHaveBeenCalledOnce(); expect(registerPrp).not.toHaveBeenCalled(); }); }); diff --git a/server/src/services/native-runtime/setup-readiness.ts b/server/src/services/native-runtime/setup-readiness.ts index 7729e595a6c..654cd391c50 100644 --- a/server/src/services/native-runtime/setup-readiness.ts +++ b/server/src/services/native-runtime/setup-readiness.ts @@ -73,6 +73,7 @@ async function probeRemoteNativeCodex(options: { return probeNativeRunnerEnvironment({ runtimeDirectory: runtimeDirectory, provider: "codex", model: options.model, reasoningEffort: options.reasoningEffort, workingDirectory: remoteWorkspace, environment: options.environment, timeoutMs: options.timeoutMs, transportOptions: { + externallySandboxed: target.transport === "sandbox", runnerBinary: controllerRunnerBinary, codexCommand: remoteCodexBinary, sourceCodexHome: options.sourceCodexHome, runnerFilesystemRoot: remoteFilesystemRoot, runnerStateDirectory: remoteStateDirectory, prpIdentity: { runnerInstanceId, runId: id, environmentLeaseId: `setup-${id}`, normalizedSessionId: `setup-${id}`, turnId: `turn-${id}`, itemId: `item-${id}` }, @@ -178,7 +179,7 @@ export async function testNativeRunnerAuthentication(context: AdapterEnvironment receipt = await probeNativeRunnerEnvironment({ runtimeDirectory, provider, model, environment, timeoutMs, ...(provider === "codex" && typeof effort === "string" && effort ? { reasoningEffort: effort } : {}), onCleanupConfirmed: async () => { await rm(runtimeDirectory!, { recursive: true, force: true }); }, - transportOptions: { runnerBinary: resolvePaperclipRunnerBinary(), sourceCodexHome: sourceCodexHome ?? "" }, + transportOptions: { runnerBinary: resolvePaperclipRunnerBinary(), sourceCodexHome: sourceCodexHome ?? "", externallySandboxed: false }, ...(credentialRefreshAuthPath ? { onCodexCredentialRefresh: async (filename: string) => { if (filename !== join(runtimeDirectory!, "codex-home", "auth.json")) throw new Error("The native Codex credential refresh handoff is invalid."); await copyBackCodexAuth({ hostAuthPath: credentialRefreshAuthPath!, log: () => {}, readSandboxAuth: async () => Buffer.from(await readLocalAiCredentialFile(filename)) }); diff --git a/tests/runner-e2e/completion-quality.test.ts b/tests/runner-e2e/completion-quality.test.ts index 991225f1098..6e2f91d18ee 100644 --- a/tests/runner-e2e/completion-quality.test.ts +++ b/tests/runner-e2e/completion-quality.test.ts @@ -1,17 +1,23 @@ import { describe, expect, it } from "vitest"; import { runsCompletionUpdateProbe, COMPLETION_QUALITY_CONFIG, completionQualityControls, completionQualityStatus, completionQualityRequest, judgeCompletionQuality, reserveCompletionQuality, validateCompletionQuality } from "./completion-quality.js"; import { runnerMatrix } from "./catalog.js"; +import { MANAGED_FIRST_TASK_CELL } from "./first-task-fixtures.js"; const observation = { sourceId: "chat", marker: "REF", worker: { id: "task", title: "Welcome", status: "done", completedAt: "2026-09-01T00:00:00Z" }, documents: [{ id: "doc", issueId: "task", key: "welcome", body: "Welcome to the garden. Meet at 10:30." }], comments: [{ id: "reply", issueId: "chat", authorAgentId: "agent", createdAt: "2026-09-01T00:01:00Z", body: "The note is ready at /issues/task", createdByRunId: "run" }], runs: [] }; const criteria = Object.keys(COMPLETION_QUALITY_CONFIG.rubric).map(id => ({ id, passed: true, rationale: "Supported by the saved note and reply", evidenceIds: ["reply", "doc"] })); const reports = [{ replyId: "reply", rationale: "Reports the completed task", completedTaskIdsReferenced: ["task"], resultAccessTaskIds: ["task"], correctsReplyIds: [] as string[] }]; describe("completion semantic qualification", () => { it.each(["runner-codex", "runner-acpx-claude"])("requires a completion judge only for accepted work on %s", profile => { - const cells = runnerMatrix.filter(e => e.suite.id === "confirmation-replies" && e.profile.id === profile); + const cells = runnerMatrix.filter(e => e.suite.id === "confirmation-replies" && e.profile.id === profile && e.environment.id === "local"); expect(cells).toHaveLength(6); expect(cells.filter(runsCompletionUpdateProbe).map(e => e.task.id).sort()).toEqual([ "interview-plan-accept", "task-card-accept", "task-reply-accept", ]); }); + it("requires the existing completion judge for accepted managed onboarding work", () => { + const managed = runnerMatrix.find(e => e.id === MANAGED_FIRST_TASK_CELL); + expect(managed).toBeDefined(); + expect(runsCompletionUpdateProbe(managed!)).toBe(true); + }); it("retains the completion suite's gate and excludes ordinary chat journeys", () => { const completion = runnerMatrix.filter(e => e.suite.id === "completion-updates"); expect(completion).toHaveLength(10); diff --git a/tests/runner-e2e/confirmation-replies.test.ts b/tests/runner-e2e/confirmation-replies.test.ts index d69fb0a67f4..7b329395312 100644 --- a/tests/runner-e2e/confirmation-replies.test.ts +++ b/tests/runner-e2e/confirmation-replies.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { gradeConfirmationReply, assertAmbiguousReplyUnresolved, ambiguousConfirmationFixtures } from "./confirmation-replies.js"; import { firstTaskScenario } from "./first-task-cases.js"; import type { FirstTaskEvidence } from "./first-task-scoring.js"; -import { provisionFirstTaskFixtures } from "./first-task-fixtures.js"; +import { MANAGED_FIRST_TASK_CELL, provisionFirstTaskFixtures } from "./first-task-fixtures.js"; import { runnerMatrix } from "./catalog.js"; import { parseRunnerSelectors, selectRunnerExecutions } from "./selectors.js"; @@ -111,7 +111,7 @@ describe("confirmation-reply independent oracle", () => { payload: { questions: [{ prompt, options: [{ label: "Welcome note" }, { label: "Poster" }] }] }, }] })).toThrow(); }); - it.each(runnerMatrix.filter(e => e.suite.id === "confirmation-replies" && e.task.flow === "first_task"))("provisions the real onboarding fixture contract for $id", async execution => { + it.each(runnerMatrix.filter(e => e.suite.id === "confirmation-replies" && e.task.flow === "first_task" && e.environment.id === "local"))("provisions the real local onboarding fixture contract for $id", async execution => { const get = vi.fn().mockResolvedValue([{ id: "local", driver: "local" }]); const patch = vi.fn().mockResolvedValue({ id: "company" }); const postSensitive = vi.fn().mockResolvedValue({ id: "secret" }); @@ -137,12 +137,14 @@ describe("confirmation-reply independent oracle", () => { originalIds: ["a", "b"], tasks: [], reply: "", agentId: "agent", answerId: "reply" })).toThrow(); }); - it("selects exactly twelve explicit-only cases using production native profiles", () => { + it("selects twelve local and one managed explicit-only cases using production native profiles", () => { const cells = runnerMatrix.filter(e => e.suite.id === "confirmation-replies"); - expect(cells).toHaveLength(12); + expect(cells).toHaveLength(13); + expect(cells.filter(c => c.environment.id === "local")).toHaveLength(12); + expect(cells.filter(c => c.environment.id === "daytona").map(c => c.id)).toEqual([MANAGED_FIRST_TASK_CELL]); expect(new Set(cells.map(c => c.profile.id))).toEqual(new Set(["runner-codex", "runner-acpx-claude"])); expect(new Set(cells.map(c => c.task.id)).size).toBe(6); - expect(selectRunnerExecutions(parseRunnerSelectors(["--suite", "confirmation-replies"]))).toHaveLength(12); + expect(selectRunnerExecutions(parseRunnerSelectors(["--suite", "confirmation-replies"]))).toHaveLength(13); expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(c => c.suite.id === "confirmation-replies")).toBe(false); }); }); diff --git a/tests/runner-e2e/pi-controls-evidence.test.ts b/tests/runner-e2e/pi-controls-evidence.test.ts index 23f45210d8a..9cc6ea1c2ae 100644 --- a/tests/runner-e2e/pi-controls-evidence.test.ts +++ b/tests/runner-e2e/pi-controls-evidence.test.ts @@ -180,7 +180,7 @@ describe("Pi controls catalog admission", () => { expect(cells).toHaveLength(4); expect(cells.every(c => c.profile.qualificationCandidate === "pi" && c.task.expectedRunCount === 1)).toBe(true); expect(cells.map(c => `${c.environment.id}/${c.task.id}`).sort()).toEqual(["daytona/pending-permission-stop", "daytona/same-turn-steering", "local/pending-permission-stop", "local/same-turn-steering"]); expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(c => c.suite.id === "pi-controls")).toBe(false); - expect(validateRunnerCatalog()).toHaveLength(729); + expect(validateRunnerCatalog()).toHaveLength(730); for (const cell of cells) expect(buildRunnerE2EProcessEnvironment({}, [cell]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined(); expect(() => assertRemoteNativeEvidencePrerequisites(cells, {})).toThrow(); }); diff --git a/ui/src/adapters/codex-local/config-fields.test.tsx b/ui/src/adapters/codex-local/config-fields.test.tsx index c63d3f725a4..c7e5264cac3 100644 --- a/ui/src/adapters/codex-local/config-fields.test.tsx +++ b/ui/src/adapters/codex-local/config-fields.test.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { act, useState } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; +import type { CreateConfigValues } from "@paperclipai/adapter-utils"; import { beforeAll, describe, expect, it } from "vitest"; @@ -54,6 +55,71 @@ async function renderRunner(config: Record, expand?: string, in ); } +async function selectField(container: HTMLElement, label: string, value: string) { + const picker = container.querySelector(`[aria-label="${label}"]`)!; + await act(async () => { + picker.focus(); + await new Promise(resolve => window.setTimeout(resolve, 0)); + }); + await act(async () => picker.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }))); + const option = document.body.querySelector(`[role="listbox"][aria-label="${label}"] [data-value="${value}"]`)!; + expect(option).not.toBeNull(); + await act(async () => { + option.click(); + await new Promise(resolve => window.setTimeout(resolve, 0)); + }); +} + +async function editFields( + mode: "create" | "edit", + adapterType: "codex_local" | "paperclip_runner", + inspect: (container: HTMLElement, saved: () => Record) => Promise, +) { + const container = document.createElement("div"); + document.body.appendChild(container); + const root = createRoot(container); + let currentConfig: Record = { + ...(adapterType === "paperclip_runner" ? { provider: "codex", lifecycleMode: "warm", idleTimeoutMs: 45_000 } : { engine: "acp", mode: "persistent", nonInteractivePermissions: "deny" }), + env: { CODEX_HOME: { type: "secret_ref", secretId: "codex-account" } }, + }; + let currentValues: CreateConfigValues = { + ...defaultCreateValues, + adapterType, + runner: adapterType === "paperclip_runner" ? "paperclip" : "legacy", + adapterSchemaValues: { provider: "codex" }, + envBindings: { CODEX_HOME: { type: "secret_ref", secretId: "codex-account" } }, + paperclipRunnerLifecycleMode: "warm", + paperclipRunnerIdleTimeoutMs: 45_000, + codexEngine: "acp", + codexAcpMode: "persistent", + codexAcpNonInteractivePermissions: "deny", + }; + function Fixture() { + const [config, setConfig] = useState(currentConfig); + const [values, setValues] = useState(currentValues); + currentConfig = config; + currentValues = values; + return setValues(previous => ({ ...previous, ...patch }))} + config={config} eff={(_group, _field, original) => original} + mark={(_group, field, value) => setConfig(previous => ({ ...previous, [field]: value }))} + models={[]} hideInstructionsFile hideRunnerHarness />; + } + try { + await act(async () => root.render()); + await inspect(container, () => mode === "create" + ? adapterType === "paperclip_runner" ? buildPaperclipRunnerConfig(currentValues) : buildCodexRunnerConfig(currentValues) + : currentConfig); + if (mode === "create") { + expect(currentValues.adapterType).toBe(adapterType); + expect(currentValues.runner).toBe(adapterType === "paperclip_runner" ? "paperclip" : "legacy"); + } + } finally { + await act(async () => root.unmount()); + container.remove(); + } +} + describe("Paperclip Runner Codex configuration", () => { it.each(["", "max"])("preserves or overrides saved native Codex reasoning effort (%s)", thinkingEffort => { const values = { ...defaultCreateValues, thinkingEffort, adapterSchemaValues: { provider: "codex", modelReasoningEffort: "high" } }; @@ -122,6 +188,32 @@ describe("Paperclip Runner Codex configuration", () => { await act(async () => root.unmount()); container.remove(); }); + it.each(["create", "edit"] as const)("changes native lifecycle while retaining timeout and account in %s mode", async mode => { + await editFields(mode, "paperclip_runner", async (container, saved) => { + expect(container.querySelector('[aria-label="Runner lifecycle"]')?.textContent).toContain("Warm session"); + expect(container.textContent).toContain("Warm idle timeout (ms)"); + const account = saved().env; + await selectField(container, "Runner lifecycle", "per_turn"); + expect(container.textContent).not.toContain("Warm idle timeout (ms)"); + expect(saved()).toMatchObject({ provider: "codex", lifecycleMode: "per_turn", env: account }); + await selectField(container, "Runner lifecycle", "warm"); + expect(container.textContent).toContain("Warm idle timeout (ms)"); + expect(saved()).toMatchObject({ provider: "codex", lifecycleMode: "warm", idleTimeoutMs: 45_000, env: account }); + }); + }); + it.each(["create", "edit"] as const)("changes Legacy ACP session and permissions without replacing the account in %s mode", async mode => { + await editFields(mode, "codex_local", async (container, saved) => { + expect(container.querySelector('[aria-label="ACP session mode"]')?.textContent).toContain("Persistent"); + expect(container.querySelector('[aria-label="ACP non-interactive permissions"]')?.textContent).toContain("Deny"); + const account = saved().env; + await selectField(container, "ACP session mode", "oneshot"); + await selectField(container, "ACP non-interactive permissions", "fail"); + expect(saved()).toMatchObject({ engine: "acp", mode: "oneshot", nonInteractivePermissions: "fail", env: account }); + await selectField(container, "ACP session mode", "persistent"); + await selectField(container, "ACP non-interactive permissions", "deny"); + expect(saved()).toMatchObject({ engine: "acp", mode: "persistent", nonInteractivePermissions: "deny", env: account }); + }); + }); it("preserves an unknown native provider instead of presenting it as Codex", async () => { const html = await renderRunner({ provider: "future-provider" }); expect(html).toContain("future-provider"); diff --git a/ui/src/adapters/codex-local/config-fields.tsx b/ui/src/adapters/codex-local/config-fields.tsx index ae81a604ef8..85b2e12203a 100644 --- a/ui/src/adapters/codex-local/config-fields.tsx +++ b/ui/src/adapters/codex-local/config-fields.tsx @@ -544,19 +544,20 @@ export function CodexLocalConfigFields({ label="Runner lifecycle" hint="Turn by turn suspends after each run. Warm keeps the same provider process available between governed runs." > - + options={[ + { value: "per_turn", label: "Turn by turn" }, + { value: "warm", label: "Warm session" }, + ]} + /> )} {runnerManaged && runnerProvider !== "openai_dot" && runnerLifecycleMode === "warm" && ( @@ -630,8 +631,8 @@ export function CodexLocalConfigFields({ label="ACP session mode" hint="Persistent keeps ACP session state between runs. One-shot starts fresh each run." > - + options={[ + { value: "persistent", label: "Persistent" }, + { value: "oneshot", label: "One-shot" }, + ]} + /> - + options={[ + { value: "deny", label: "Deny" }, + { value: "fail", label: "Fail" }, + ]} + /> {!managedSandboxOnly && ( { - if (select) { - const setter = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value")?.set; - setter?.call(select, environmentId); - select.dispatchEvent(new Event("change", { bubbles: true })); - } - }); - await flushReact(); + const options = await environmentOptions(container); + const option = options.querySelector(`[role="option"][data-value="${environmentId}"]`); + expect(option).not.toBeNull(); + await clickElement(option); } async function clickByText(container: HTMLElement, label: string) { @@ -674,6 +678,40 @@ describe("AgentConfigForm environment selector", () => { expect(result.onSave).toHaveBeenCalledWith({ name: "Renamed Cody" }); }); + it.each(["codex_local", "paperclip_runner"])("changes and clears the environment without changing a saved %s runner or account", async adapterType => { + const adapterConfig = { + model: "gpt-5.6-sol", + ...(adapterType === "paperclip_runner" ? { provider: "codex", reasoningEffort: "high" } : { modelReasoningEffort: "high" }), + env: { CODEX_HOME: { type: "secret_ref", secretId: "codex-account" } }, + }; + const result = await renderForm([ + ...daytonaEnvironments(), + makeEnvironment({ id: "sandbox-2", name: "Daytona Two", driver: "sandbox", config: { provider: "daytona" } }), + ], { adapterType, adapterConfig, defaultEnvironmentId: "sandbox-1" }, { showAdapterTypeField: true }); + roots.push(result.root); + await clickByText(result.container, "Advanced"); + const runnerLabel = adapterType === "paperclip_runner" ? "Paperclip Runner" : "Legacy runner"; + + await selectEnvironment(result.container, "sandbox-2"); + expect(result.container.querySelector('[aria-label="Environment override"]')?.textContent).toContain("Daytona Two"); + expect(result.container.querySelector('[aria-label="Runner"]')?.textContent).toContain(runnerLabel); + await clickByText(result.container, "Save"); + expect(result.onSave).toHaveBeenLastCalledWith({ defaultEnvironmentId: "sandbox-2" }); + + await selectEnvironment(result.container, ""); + expect(result.container.querySelector('[aria-label="Environment override"]')?.textContent).toContain("Default: Local"); + expect(result.container.querySelector('[aria-label="Runner"]')?.textContent).toContain(runnerLabel); + await clickByText(result.container, "Save"); + expect(result.onSave).toHaveBeenLastCalledWith({ defaultEnvironmentId: null }); + }); + + it("clears a new agent's environment override to the empty draft value", async () => { + const result = await renderCreateForm(daytonaEnvironments(), { defaultEnvironmentId: "sandbox-1", runner: "legacy" }); + roots.push(result.root); + await selectEnvironment(result.container, ""); + expect(result.onChange).toHaveBeenLastCalledWith({ defaultEnvironmentId: "" }); + }); + it("submits only an explicit runner change and retains saved configuration for server translation", async () => { const result = await renderForm([], { adapterType: "paperclip_runner", adapterConfig: { provider: "codex", model: "gpt-5.6-sol", reasoningEffort: "high", env: { CODEX_HOME: { type: "secret_ref", secretId: "codex-account" } } } }); roots.push(result.root); @@ -910,7 +948,7 @@ describe("AgentConfigForm environment selector", () => { roots.push(result.root); expect(result.container.textContent).not.toContain("Environment override"); - expect(result.container.querySelector("select")).toBeNull(); + expect(result.container.querySelector('[aria-label="Environment override"]')).toBeNull(); }); it("renders GPT-6 Astra and its model-specific reasoning efforts", async () => { @@ -1029,7 +1067,7 @@ describe("AgentConfigForm environment selector", () => { roots.push(result.root); const text = result.container.textContent ?? ""; - const selector = result.container.querySelector("select"); + const selector = await environmentOptions(result.container); expect(text).toContain("Environment"); expect(text).toContain("Environment override"); @@ -1048,7 +1086,7 @@ describe("AgentConfigForm environment selector", () => { roots.push(result.root); const text = result.container.textContent ?? ""; - const selector = result.container.querySelector("select"); + const selector = await environmentOptions(result.container); expect(text).toContain("Environment override"); expect(selector?.textContent).toContain("E2B · sandbox"); @@ -1062,7 +1100,7 @@ describe("AgentConfigForm environment selector", () => { roots.push(result.root); const text = result.container.textContent ?? ""; - const selector = result.container.querySelector("select"); + const selector = await environmentOptions(result.container); expect(text).toContain("Environment override"); expect(selector?.textContent).toContain("E2B · sandbox"); @@ -1084,7 +1122,7 @@ describe("AgentConfigForm environment selector", () => { roots.push(result.root); const text = result.container.textContent ?? ""; - const selector = result.container.querySelector("select"); + const selector = await environmentOptions(result.container); expect(text).toContain("Environment override"); expect(selector?.textContent).toContain("Default: Local"); @@ -1104,7 +1142,7 @@ describe("AgentConfigForm environment selector", () => { ]); roots.push(result.root); - const selector = result.container.querySelector("select"); + const selector = await environmentOptions(result.container); expect(selector?.textContent).toContain("Default: Paperclip Computer"); expect(selector?.textContent).toContain("Paperclip Computer"); @@ -2488,15 +2526,7 @@ describe("AgentConfigForm environment selector", () => { await runTest(result.container); expect(findButton(result.container, "Sign in")).toBeTruthy(); - const select = result.container.querySelector("select"); - await act(async () => { - if (select) { - const setter = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value")?.set; - setter?.call(select, ""); - select.dispatchEvent(new Event("change", { bubbles: true })); - } - }); - await flushReact(); + await selectEnvironment(result.container, ""); expect(findButton(result.container, "Sign in")).toBeFalsy(); }); diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 09f6c144a02..7b6ef27182a 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1579,25 +1579,24 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
- + options={[ + { value: "", label: `Default: ${inheritedEnvironmentLabel}` }, + ...environmentOptions.map((environment) => ({ + value: environment.id, + label: environmentDisplayLabel(environment), + })), + ]} + />