chore: sync fork with upstream (ColeMurray/background-agents) - #23
Merged
Merged
Conversation
## Summary - add `anthropic/claude-fable-5-1` to the shared model catalog with low through max reasoning and a high default - expose Fable 5.1 through model selectors and Linear's `model:fable-5-1` override - update model documentation, changelog, and the frozen OpenCode reasoning catalog metadata and checksums ## Validation - `npm run build -w @open-inspect/shared` - `npm test -w @open-inspect/shared -- --run` (867 tests) - `npm test -w @open-inspect/linear-bot -- --run` (235 tests) - `npm test -w @open-inspect/web -- --run src/hooks/use-enabled-models.test.tsx src/components/model-reasoning-selector.test.tsx src/components/settings/models-settings.test.tsx` (23 tests) - `npm test -w @open-inspect/slack-bot -- --run src/app-home/models.test.ts` (2 tests) - `npm run typecheck` - `npm run lint` - `uv run --extra dev ruff check tests/test_opencode_reasoning_contract.py` - `uv run --extra dev ruff format --check tests/test_opencode_reasoning_contract.py` - `uv run --extra dev pytest tests/test_opencode_reasoning_contract.py tests/test_reasoning_config.py -v` (1 passed, 3 opt-in wire tests skipped) ## Verification note The opt-in wire suite was also attempted with OpenCode 1.18.29. The binary reported the expected version, but its isolated health endpoint timed out before test execution in this environment, so live wire serialization remains unverified here. The non-wire reasoning configuration test passes, and the fixture metadata matches the current public OpenCode catalog. --- *Created with [Open-Inspect](https://open-inspect-prod.vercel.app/session/b63f573451504f0f4620f3f34b3520eb)* <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added Claude Fable 5.1 (`anthropic/claude-fable-5-1`) to the available model catalog and model picker. - Added adaptive reasoning controls with effort levels from low to max, defaulting to high. - Added support for selecting Claude Fable 5.1 using the `model:fable-5-1` override label. - **Bug Fixes** - Improved model label handling so versioned and provider-qualified Claude model labels resolve correctly. - **Documentation** - Updated model support tables and available-model documentation to include Claude Fable 5.1. - Updated integration guidance with the new model override label. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cole Murray <2492022+ColeMurray@users.noreply.github.com> Co-authored-by: waclaude <colemurray.cs+ghwaclaude@gmail.com>
…slot skill test (ColeMurray#1848) ## Problem `managed-skills.test.ts > validates more assigned environments than the engine has parameter slots` intermittently fails with `Test timed out in 5000ms`. ## Root cause The test seeded 101 environments with one `EnvironmentStore.create()` call per environment. Each `create()` is its own `db.batch()`, so the test issued ~115 sequential D1 round-trips before its two `SkillStore.create()` assertions even started. In isolation that is cheap. Under the full parallel integration run — one workerd instance per test file across all cores — every round-trip waits on a starved pool, and the accumulated latency crosses vitest's 5s default `testTimeout`. The `validateAssignments` chunking from ColeMurray#1556 is not implicated. This test was simply the only one in the suite doing a three-digit sequential seed. Measured on a 16-core machine, with the full integration suite running concurrently for load: | Seeding method | 101 environments | | --- | --- | | Sequential `create()` loop | 28–32 ms | | Single `env.DB.batch()` | 5 ms | ## Fix Seed the rows through one `env.DB.batch()` of `bindEnvironmentInsert` statements, matching the pattern the file's existing `seedGlobalCatalog` helper already uses. `bindEnvironmentInsert` is the same statement builder `EnvironmentStore.create()` composes, so the rows are identical; the loop only added transaction boundaries. The test still asserts the same behavior: 101 assignments persist, and a create with one missing environment among many is still rejected with `environments do not exist`. ## Verification - The target test passes at 22ms inside a full 103-file / 1228-test integration run, down from a 5000ms timeout. - `tsc --noEmit -p tsconfig.test.json`, eslint, and prettier all clean. ## Note for reviewers The integration config leaves vitest's 5s default `testTimeout` in place while running one workerd instance per file across all cores, so any test's first case can stall past it on a loaded machine. A separate run here hit exactly that in `events-messages-list.test.ts` (passes in isolation, all cases under 40ms, no seeding loop). If these load-only timeouts recur across unrelated files, raising `testTimeout` globally is the follow-up — deliberately out of scope here, since it is not what caused this failure. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Tests** - Streamlined test setup for scenarios involving a large number of assigned environments. - Preserved the existing test data, validation behavior, and assertions while improving setup efficiency. - No changes to exported or public functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Moves OpenCode behind an `AgentHarness` seam in the sandbox runtime so a
second agent can be added without touching the bridge's session, event
and prompt-queue logic. No behaviour change for OpenCode sessions.
- `harness/base.py` defines the two halves of the seam: `AgentHarness`
(bridge half: open, close, create-or-resume a session, run a prompt,
abort) and `HarnessProcessOwner` (supervisor half: start and stop the
vendor process).
- `harness/opencode.py` implements both for OpenCode.
`opencode_client.py` and `prompt_stream.py` move under `harness/` as
`opencode_client.py` and `opencode_stream.py`; `test_prompt_stream.py`
keeps its name.
- `bridge.py` drops its OpenCode-specific code (about 260 lines) and
drives the harness through the Protocol. `entrypoint.py` and
`supervisor.py` pick the process owner from the session config's
`harness` field, which only accepts `opencode` today.
- The second commit declares `session_id` on the Protocol, renames a
local that shadowed the turn outcome, and widens `OpenCodeServer.start`
to `Sequence`, which keeps `mypy src/` at the same five pre-existing
errors `main` has.
- The third commit is the review round: harness startup sits inside the
same try/finally as the run loop and a failing `close()` can no longer
replace a `HarnessStartError`; the harness owns `session_id` through
`resume_session` and `create_session` (startup only resumes, the first
prompt creates, as before the seam); `TurnOutcome` rejects contradictory
states; only deployable harness ids are listed, so
`parse_harness_id("claude")` is rejected until that harness lands; the
`ready` and `snapshot_ready` events emit exactly what the shared schema
on `main` declares; registry imports are at module scope.
`test_bridge_harness_lifecycle.py` covers the start-error,
cleanup-failure, invalid-resume and contract cases.
No runtime manifest bump: the image ships the same Python package.
Part 1 of the Claude Agent harness stack. Next: the harness
discriminator on sessions and automations.
## Testing
- `packages/sandbox-runtime`: `uv run python -m pytest` (950 passed, 3
skipped), `ruff check`, `ruff format --check`, `mypy src/` unchanged
from `main`, `node --test tests/*.test.mjs` (21 passed).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added configurable agent selection for sessions, defaulting to
OpenCode.
* Added agent-aware session creation, resumption, prompting, stopping,
and status reporting.
* Added clearer reporting for unrecoverable startup failures.
* **Bug Fixes**
* Improved restoration when saved sessions are unavailable.
* Prevented cleanup errors from obscuring the original startup failure.
* Improved handling of agent-process crashes and deterministic failures.
* Ensured failed startup cleanup completes without masking the
underlying error.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…rray#1851) ## Summary Adds `harness` to sessions and automations end to end, so a session records which agent it runs on. Stacked on ColeMurray#1850. - `@open-inspect/shared/harnesses`: the harness catalog, display labels, and the one compatibility rule applied wherever a model or provider-auth selection enters a session. The catalog lists exactly the harnesses the runtime on the branch can boot, so here it is `opencode` alone; ColeMurray#1853 adds `claude` together with the runtime that boots it. A provider a harness has no auth row for resolves to the API key rather than binding an installation default the harness cannot use. The rule's auth half runs wherever modes are known: session create, automation create and update (explicit selections, or the stored pins when harness or model changes), and child spawn (the inherited modes against the child's model). - D1 migration `0075` adds a nullable `sessions.harness` column; automations gain the same column. Both read as `opencode` when null. - The session Durable Object migration renames `opencode_session_id` to `agent_session_id` with a dual read of the legacy key, so existing sessions resume unchanged. - Sandbox spawn passes `harness` to the runtime and `web_api.py` forwards it into the sandbox environment. Child sessions inherit the parent's harness; a child cannot switch. - The `ready` bridge event reports which harness booted, and the session Durable Object logs a `sandbox.harness_mismatch` warning when it differs from the session's harness (image or config drift, never reconciled silently). - A harness that never suggests a title would leave the session untitled, so once the first turn settles the session offers the prompt's first line; the only-if-unset write decides whether it lands, so a vendor suggestion that arrived mid-turn wins. **Inbox join-order pin.** Adding a column to `sessions` flipped SQLite's join order on the inbox list query so it read every session row before filtering. The `CROSS JOIN` in `session-inbox-store.ts` pins the order the query was written for. A regression test that measures the read bound belongs to inbox work not yet on `main` and will arrive with it. The fixture commit adds `harness: "opencode"` to the unit and integration fixtures that build sessions and automations; reviewers can skip it. ## Testing - `npm run typecheck` (every package, including `test/integration`), `npm run lint`, `npm run lint:sql-portability`. - control-plane: unit 4121 passed; integration 1227 passed, 1 skipped. - shared 877 passed; web 1581 passed; modal-infra 238 passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added harness support for sessions and automations, including child sessions, scheduled runs, sandbox execution, and restores. - Added compatibility checks for selected models and provider authentication, with clear errors for unsupported combinations. - Sessions now receive an automatic fallback title when none is provided. - Session state now reports the agent session identifier and harness. - **Bug Fixes** - Improved session inbox query performance. - Added handling for snapshot-ready events and harness mismatch warnings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…oleMurray#1852) ## Summary The in-process tool server the Claude Agent harness will use, plus the SDK it depends on. Nothing wires the module yet; the next PR does. Stacked on ColeMurray#1851. - `harness/claude_tools.py` implements the seven Open Inspect tools as one in-process MCP server named `oi`: `spawn-child`, `send-child-prompt`, `cancel-child`, `get-child-status`, `create-pull-request`, `slack-notify`, `upload-media`. They are the same tools OpenCode gets as `.opencode/tool/*.js` plugins, over the same control-plane side channels, and they run inside the bridge process so no credential has to reach the child for them to work. Tools are gated the way the OpenCode plugins are (Slack only when the session has a Slack thread). - `claude-agent-sdk==0.2.152` pinned exactly in `sandbox-runtime/pyproject.toml`: the wheel bundles the `claude` CLI (2.1.259) whose message shapes the harness translates. The three lock files (`sandbox-runtime/uv.lock`, `modal-infra/uv.lock` through its path dependency, `sandbox-images/locks/runtime.txt`) resolve the pin; that is where the line count comes from. No runtime manifest bump: the dependency is inert until the harness lands. **Contract review.** Seven places where the port had drifted from the JavaScript tools and the control-plane routes were corrected: the child-detail formatter reads the `session` envelope; trajectory paging uses `cursor`; a single-repository session reads its branch from the checkout, not the bridge's cwd; absent pull-request optionals are omitted rather than sent as `null`; pull-request creation outlasts the server's push budget; `viewport`/`dimensions` are `{width, height}` objects serialized to the JSON strings the media endpoint parses; video uploads require `caption`. `test_claude_tools.py` now pins those wire shapes. ## Testing - sandbox-runtime: pytest 965 passed, 3 skipped (`test_claude_tools.py` exercises `build_tool_server` and the wire contracts), ruff, mypy unchanged, `uv lock --check`. - modal-infra: pytest 238 passed, `uv lock --check`. - sandbox-images: `cli.py lock --check`, pytest 56 passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Claude harness tools for managing child sessions, including spawning and checking status. * Added pull request creation with repository and current-branch detection. * Added Slack notifications with actionable status and error feedback. * Added media upload support with file-type validation and structured image/video metadata. * Tools are automatically limited based on repository availability and Slack configuration. * **Improvements** * Child session details now include sandbox information, recent events, and trajectory continuation. * Pull request requests now enforce a dedicated timeout and omit unavailable optional fields. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary The Claude Agent SDK harness behind the seam from ColeMurray#1850, on the tool server from ColeMurray#1852. - `harness/claude.py`: the `AgentHarness` for the SDK client. Creates or resumes a session by UUID, streams SDK messages into bridge events (text, tool calls, results, cost), maps reasoning effort onto the SDK's thinking options, and aborts a running turn. The `oi` tool server from ColeMurray#1852 is registered as an MCP server and allow-listed as `mcp__oi__*`. - `claude_stager.py`: the `HarnessProcessOwner` for Claude. There is no resident process (the SDK spawns the child inside the bridge), so `start()` only prepares what the child will see: a per-sandbox `CLAUDE_CONFIG_DIR` outside every repository where no credential is ever written, bundled and managed skills, the standalone bin scripts, and a small JSON handoff. - `harness/claude_env.py` and `sandbox_bin.py`: the child runs with the sandbox environment OpenCode sees, minus the Anthropic credential family of the other auth mode, so it holds exactly one credential and never both. The sentinel test in `test_claude_env.py` is the one to read. - `credentials/provider_credential_client.py`: fetches a connected subscription's stored provider secret (the `stored_provider_secret` credential kind, as opposed to a brokered access token) from the control plane's sandbox-only runtime-credential endpoint on every bridge start (the route lands in ColeMurray#1854), so restarts and snapshot restores each produce an issuance record; the token lives in process memory only. Without a connected account the harness uses `ANTHROPIC_API_KEY`. - `@open-inspect/shared/harnesses`: `claude` enters the catalog here, with the runtime that boots it: Anthropic models only, API key or a connected Anthropic account (that account mode gets its control-plane route in ColeMurray#1854). Providers the harness has no auth row for resolve to the API-key fallback. - Runtime manifest: generation 64, `v64-claude-harness`, rebuild generation 64, global compatibility floor unchanged at 62, and a per-harness floor `harnessMinimumGeneration: { claude: 64 }`. Image selection applies the session harness's floor, so a Claude session never boots a prebuilt image from before its runtime existed, and no OpenCode image or snapshot is retired by this change. **Turn contract.** Prompts are stamped `origin: human` and the harness consumes the SDK message stream turn-aware: a turn the session injected is skipped from its opening user message through its result (only those two carry an origin), so none of its output reaches the timeline and its result never ends this prompt; the child also runs with `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`, so an `Agent` call returns when its sub-agent finishes (several launched in one message still run concurrently, as OpenCode's `task` tool does), and Claude's `Agent` tool is reported as the runtime-neutral `task` tool the timeline groups under. One prompt budget covers connect, submit, reads and emits, each read keeps the inactivity budget, and both cleanup after a timeout and `abort()` are bounded by the cleanup budget, dropping the child when the interrupt hangs. A conversation reset rotates the vendor session id, which the harness adopts and the bridge persists after the turn. A turn without a running total leaves the cost baseline unknown so the next total charges nothing rather than the missing turn's cost; failed connects spend the reconnect budget. **Handoff and staging.** The supervisor's handoff to the bridge carries decisions only (workdir, config dir, repository presence), written owner-only and atomically; MCP configuration reaches the bridge through its own `SESSION_CONFIG`. A `CLAUDE_CONFIG_DIR` inside the workspace or a checkout falls back to the default, decided once in the composition root so managed skills (materialized before the stager runs) and the stager use the same directory; local MCP packages are preinstalled at staging as at OpenCode boot. The credential client treats 408/425/429, a retryable 409 and an undecodable success body as transient. One follow-up from the ColeMurray#1852 review rides at the bottom of this PR since that one merged first: the upload tool refuses `hasAudio: true` locally and its schema says so, instead of sending the file and relaying the endpoint's 400. Three bugs found running real sessions are kept as separate commits so they read apart from the harness itself: resume looked for transcripts under the bridge's config dir instead of the child's; the handoff writer choked on the frozen session config; the child's allow-listed environment stripped the session context the sandbox helpers need. ## Testing - sandbox-runtime: pytest 1033 passed, 3 skipped; ruff; mypy unchanged from `main`; node tests 21 passed. - control-plane unit 4124 passed; shared 882 passed; typecheck and lint across every package. - modal-infra: pytest 238 passed. sandbox-images: lock check, pytest 56 passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Claude Agent as a supported harness for Anthropic models. * Added isolated Claude configuration, managed skills, MCP setup, and secure credential handling. * Added automatic session recovery and persistence when conversations reconnect or rotate session IDs. * Added harness-specific runtime compatibility checks for prebuilt sandbox images. * **Bug Fixes** * Improved provider account fallback when harness-specific authentication is unavailable. * Video uploads now reject files containing audio tracks. * **Tests** * Expanded coverage for Claude execution, authentication, reconnects, tools, staging, and runtime compatibility. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
… delivery (ColeMurray#1854) ## Summary An Anthropic provider account: connect a Claude subscription by pasting the output of `claude setup-token`, and deliver that stored provider secret to a sandbox at boot without writing it to disk. Builds on the runtime from ColeMurray#1853; the browser consent flow follows in ColeMurray#1855 and the settings dialog in ColeMurray#1857. ### Account model - `auth/model-provider-account-anthropic-adapter.ts`: the third provider adapter. A setup token is a static credential: it does not refresh, it cannot be verified against the provider (`supportsVerification: false`, enforced by `ModelProviderAccountService.verify()` with a 409 before anything is read or claimed), and a pasted account carries no external identity, so two pasted tokens are never de-duplicated. A pasted value the adapter cannot read is a 400 on create and reconnect. The adapter declares `runtimeCredentialKind: "stored_provider_secret"`, the counterpart of the brokered access tokens OpenAI and xAI mint per request. - `auth/anthropic.ts` and the authorization-code capability on the adapter: the PKCE start/exchange helpers and the capability interface ColeMurray#1855 wires into routes. They sit here so the adapter is complete as a unit; no route calls them in this PR. - `@open-inspect/shared/types/provider-accounts`: Anthropic joins the subscription providers, with the connect/reconnect request schemas (`setupToken`), the authorization-code request/response schemas for ColeMurray#1855, `STATIC_CREDENTIAL_PROVIDER_IDS`, and the per-provider connection method. ### Credential delivery - `POST /sessions/:id/provider-auth/anthropic/runtime-credential` (sandbox-authenticated, `no-store`): the sandbox's bridge calls it on every start. The route reads the session's binding, requires an active unarchived account, decrypts only after every check, and returns `{ kind, secret, credentialVersion, expiresAt }`. A credential within an hour of its recorded expiry fences the account to `reconnect_required` instead; that fence is a compare-and-set on the credential version the request inspected, and a lost race (a reconnect rotated it in between) returns a 409 marked `retryable: true`, which the runtime client from ColeMurray#1853 retries. Every other 409 describes the account and is final. - The existing access-token route refuses providers whose runtime credential kind is not a brokered token, so the setup token only leaves through the route above. - The sandbox principal now carries the authenticated sandbox id: the session runtime's token verification returns it, route admission stores it on the principal, and the runtime-credential route rejects a caller whose `X-Sandbox-ID` names a different sandbox. ### Session wiring - `provider-account-resolution.ts`: when nothing is selected and no default exists, OpenAI and xAI still fall back to the legacy scoped-OAuth path their plugins understand; Anthropic never had one, so it falls back to the API key (`api_key_fallback`) rather than a placeholder. - `user-env-resolver.ts`: a pre-spawn check. A session bound to a connected account that has since been disabled, archived or fenced fails the prompt in the queue with reconnect guidance, before a sandbox is spawned into a credential denial. The resolver now returns the bound account id per provider for that check. - `managed-provider-env.ts`: the Anthropic entry of the per-provider env fold. In account mode the sandbox gets `ANTHROPIC_OAUTH_MANAGED=1` and any user-supplied `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL` or `CLAUDE_CODE_OAUTH_*` values are stripped so a stale user key or gateway setting cannot shadow the managed credential. The Anthropic platform key arrives out of band on Modal, so an api_key session is not validated from the assembled env the way OpenAI and xAI are. - The harness auth rule from ColeMurray#1851 now has a provider it can bite on: automation create and update reject a pinned Anthropic account for an OpenCode harness, and a child on an Anthropic model cannot inherit one from an OpenCode parent. ### What disabling, archiving or reconnecting does It stops new hand-outs immediately: the next bridge start on a bound session is refused, and the next prompt fails the pre-spawn check with reconnect guidance. A sandbox that already holds the token keeps it in memory until it exits. Open-Inspect does not chase running sandboxes: disconnecting never revokes the token at Anthropic, so stopping our own sandboxes would only shorten a window it cannot close. Revocation is done at Anthropic, and the docs in ColeMurray#1858 say so. (An earlier revision of this PR carried an issuance ledger, a cleanup outbox, triggers and a scheduled job for that chase; they were removed in 35aec41, see the PR comment.) ### Migration `0076` - Rebuilds `model_provider_account_authorizations` to widen the provider check to Anthropic and add `authorization_kind` (`device` | `authorization_code`, default `device`); existing rows are copied over as `device`. Nothing in this PR reads or writes the column; ColeMurray#1855 does. - Backfills an `anthropic` / `api_key` / `legacy_migration` row into `session_model_provider_auth` for every existing session, which is what those sessions were already using for Anthropic models. - Re-creates the session seed trigger from 0064 with that same third row, so sessions the previous worker inserts during the deploy window come out complete. `sql-portability-baseline.json` records the trigger. - **Deploy note:** the new worker requires exactly one `session_model_provider_auth` row per subscription provider and the old worker writes two, so session create and resume on subscription providers and the Provider Accounts page are unavailable between the migration apply and the worker deploy (a few minutes on the standard pipeline). Rollback is fix-forward. ### Web The settings page renders every provider in the shared catalog, so this PR adds the Anthropic icon and lets the page list a provider it cannot connect yet (its Add entry is disabled, Verify is hidden for static-credential providers). The connection dialog arrives with ColeMurray#1857. ## Testing At the PR head: - `npm run typecheck`, `npm run lint`, `npm run lint:sql-portability`. - shared 882 passed; control-plane unit 4137 passed; control-plane integration 1235 passed, 1 skipped (route catalog and admission snapshots updated for the one new route); web 1581 passed; sandbox-runtime pytest 1033 passed, 3 skipped. - Integration coverage for the delivery route: token delivered to the bound sandbox, wrong sandbox id refused, unbound session refused, brokered provider refused on this route, stored-secret provider refused on the access-token route, expiry fence, fence lost to a rotated credential, disabled account refused from then on.
…ount hardening (ColeMurray#1855) ## Summary The browser path for connecting a Claude account: a PKCE authorization-code flow against Anthropic's consent page, with the code pasted back. Stacked on ColeMurray#1854. - `model-provider-accounts/authorization-code-service.ts`: start (mint the PKCE verifier and state, return the authorization URL), status, complete (exchange the pasted `code#state`, persist the account, never persist the refresh token) and cancel. - `authorization-transaction.ts` holds the lifecycle both completion kinds share: reservation (reconnect target checks, attempt limit), ownership, durable expiry, stale-claim detection, cancellation and the connected response. Ownership is by user, provider **and** `authorization_kind`, so the device and authorization-code route families cannot read, claim, settle or cancel each other's rows. The processing lease is 90s, pinned by a test to stay above every provider call made under a claim. - Exchange outcomes cross the capability boundary as `ProviderAuthorizationCodeExchangeError` with a provider-neutral classification: `rejected` (the provider's verdict; terminal), `retry_safe` (429; the row returns to pending and the same code can be pasted again, three times), `ambiguous` (timeout, transit failure, 5xx; the one-use code may be spent, so the transaction fails and the user starts a fresh authorization). The Anthropic adapter maps its reasons onto these. - Routes: `POST`, `GET` and `DELETE` on `/model-provider-accounts/anthropic/authorization-codes[/:id]` and `POST .../:id/complete`, all `private, no-store`. - Identity: the exchange names the granting Claude account, so browser slots carry it (duplicate creates converge, a reconnect from another account is refused). A pasted slot has no identity; it adopts one the first time a browser reconnect names it, unless that account already has a slot. A slot with an identity can be reconnected only through the browser, so a pasted token can never replace the credential behind a verified identity. The atomic writer fences the reconnect on the identity the slot held when the transaction started and compares with `IS` so null matches. - Three fixes from running the real exchange, kept as separate commits: the token endpoint rejects requests without a User-Agent (Cloudflare error 1010, and a Worker's outbound fetch sends none); the OAuth hosts are `claude.com/cai/oauth/authorize` and `platform.claude.com/v1/oauth/token` with the one-year `expires_in`; the exchange names the granting account. The integration config mocks the token endpoint with the real response shape. Token ids in fixtures are synthetic. ## Testing - `npm run typecheck`, `npm run lint`, `git diff --check`. - control-plane: unit 4218 passed; integration 1253 passed, 1 skipped, including `provider-account-authorization-codes.test.ts` (wrong-kind routes under the same provider, ambiguous and throttled exchanges, identity adoption and its refusal, pasted reconnect refused on a browser-named slot). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added an authorization-code OAuth flow for Anthropic accounts, including authorization, status, completion, and cancellation. - Anthropic connections now retain additional account and organization details from authorization. - Identity-less accounts can adopt an account identity during reconnect when appropriate. - **Bug Fixes** - Improved handling of invalid, expired, ambiguous, rate-limited, and temporarily unavailable authorization attempts. - Added safe retry behavior for transient failures while preventing reuse of potentially consumed authorization codes. - Improved validation and conflict detection during account reconnects. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…urray#1856) ## Summary The web side of choosing a harness. Stacked on ColeMurray#1855; it needs only ColeMurray#1851 to compile and ColeMurray#1853 for a session to run. - New sessions: the model control becomes one agent-and-model control. The trigger reads as one phrase (vendor mark, `Claude Agent:`, model, effort); the menu has Agent, Model and Effort rows, as submenus on desktop and drill-in views on mobile. Picking an agent whose model list excludes the current model swaps to a compatible one. - One harness-aware model selection (`resolveHarnessModelSelection`) feeds the home composer, the session composer and the automation form. The picker, the submitted model and the submit gate all come from it, so a harness with no compatible enabled model blocks submission with a message instead of resolving to a hidden fallback. - Switching harness drops provider-account pins the new harness cannot use (`reconcileProviderSelectionsForHarness` in the shared catalog, the same policy the control plane validates with), and the auth controls offer no account where the harness cannot select one. - Session page: the same trigger with the harness fixed and no Agent row. The header gets no badge. - Automations: the form gains a harness select and the list shows it. - Timeline: tool formatters for Claude's tool names (`Read`, `Edit`, `Bash`, `MultiEdit`, …) and external MCP tools (`mcp__<server>__<tool>`), so Claude Agent turns render like OpenCode turns. The runtime strips its own MCP qualification from first-party tools (`create-pull-request`, `slack-notify`, child status) at the event boundary, the same place it already maps its sub-agent tool to `task`, so those reach the existing renderers unchanged. The formatters have their own test file. - `HarnessIcon` and `HarnessName` render the vendor marks; `OpenCodeIcon` is the pixel "O" from opencode.ai's favicon redrawn in `currentColor`. - The session snapshot server message and reader carry `harness`; the shared schema defaults it, so readers see a required field. The product name is "Claude Agent" everywhere user-facing, and a test asserts it. ## Testing - `npm run typecheck`, `npm run lint`. - web 1611 passed; shared 885 passed; sandbox-runtime 1034 passed; control-plane unit suite green. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added agent/harness selection across sessions and automations, with provider-specific labels and icons. * Harness choices are saved and restored, with compatible models and reasoning options shown automatically. * Automation forms now support selecting, editing, and submitting an agent harness. * Session and automation details display the selected agent. * Improved formatting and grouping for Claude Agent, MCP, and additional tool activity. * **Bug Fixes** * Incompatible model selections now fall back to supported alternatives. * Provider authentication options now reflect harness compatibility. * Clear feedback is shown when no enabled model supports the selected harness. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…oleMurray#1857) ## Summary Settings > Provider Accounts learns to connect a Claude account. Stacked on ColeMurray#1856; it depends on ColeMurray#1855 for the routes. - `provider-authorization-code-dialog.tsx`: two entry points, one mounted at a time. Browser: open the authorization URL, paste the `code#state` value the consent page shows (the `#state` half is required), and the dialog completes the transaction. Setup token: paste the output of `claude setup-token`. Switching to the setup token unmounts the browser flow and cancels its transaction; a save in flight locks the inputs, Back, Cancel and dismissal, so two credential writes never overlap. - `use-provider-authorization-code.ts`: the transaction lifecycle (start, complete, reconcile, cancel) as one phase machine, validated with the shared schemas. A completion is abortable; when its outcome is unknown (another request owns the claim, a lost response, or the deadline aborted it) the hook polls the durable status for a bounded window before it decides. Only 404/410 mean the transaction is gone. Settled states use the control plane's vocabulary, so a provider rejection (`denied`) offers a fresh transaction. - The settings page's connection strategies are a complete record again: Anthropic maps to the authorization-code dialog and Verify is hidden for static-credential providers. The reconnect dialog explains that sessions which already received the credential keep it until their sandbox exits. A form closes as soon as its write succeeds and the list refreshes best-effort, so a refresh failure cannot resubmit an identity-less setup-token account. - The authorization-code BFF routes accept only providers that connect by that method. ## Testing - `npm run typecheck`, `npm run lint`. - web 1648 passed. The hook tests cover start, completion, 409 reconciliation, a never-settling completion aborted at the deadline, unmount abort, expiry and reconnect; the dialog tests cover both entry points, cancel with a pasted code, Start over after denial, and the save lock; the settings tests cover a connect whose refresh fails. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added Anthropic account connection and reconnection through an authorization-code flow. - Added support for connecting with a Claude setup token where applicable. - Added progress, countdown, cancellation, retry, and failure states during authorization. - Added clearer handling for denied or expired authorization attempts. - **Bug Fixes** - Improved account refresh and error feedback after connection actions. - Added credential-rotation warnings and restricted unsupported account actions for Anthropic connections. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Documentation for the Claude Agent harness. Stacked on ColeMurray#1857. - `docs/CLAUDE_AGENT.md`: choosing a harness, connecting a Claude account (browser and setup token), how the credential reaches the sandbox, the lifecycle of disable, archive and reconnect, a runbook for revoking a setup token, and the migration window to plan for when deploying. - CHANGELOG release note, including the deploy-window note for migrations 0075 and 0076 and the runtime manifest bump. - README, HOW_IT_WORKS and AVAILABLE_MODELS mention the second harness. ## Testing - `prettier --check` on the five files. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added support for connecting Anthropic/Claude accounts through browser authorization or setup tokens. - Added connection and reconnection flows with expiration tracking, retry handling, cancellation, and credential-rotation warnings. - Added Claude Agent harness support alongside OpenCode, including selectable harness and authentication options. - **Documentation** - Expanded setup, authentication, harness, model, sandbox, migration, and operational guidance for Claude Agent. - Updated architecture documentation and credits to include the Claude Agent SDK. - **Bug Fixes** - Improved connection handling so successful saves close forms reliably while refresh issues are reported separately. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Terraform Validation Results
Pushed by: @jasoncuriano, Action: |
joshkostal
approved these changes
Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Routine upstream sync per
upstream-sync-runbook.md. Brings the fork up tod017795a.upstream/main— no fork-local changespackage-lock.jsonuntouched (zero version/resolved/integrity changes)Verified locally before push:
npm run build(all packages)npm run typechecknpm test -w @open-inspect/control-planedist/index.js× 5Notes:
@open-inspect/sharedsubpath export (./harnesses) — cleansharedrebuild required, per the runbook.0075_session_harness,0076_anthropic_provider_accounts); Terraform applies them on deploy.🤖 Generated with Claude Code