You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -91,6 +91,16 @@ In TUI chat mode there is no completion gate — the session stays open across t
91
91
- Both settings files and the project/global grant store (`.corbits/permissions.json`) are on the secret-guard denylist for path-keyed tools, so the agent cannot `read_file` its own credentials or persist standing auto-approvals. Shell commands that reference them still require explicit operator approval.
92
92
- Credential-surface ownership: each auth store module enumerates its own files (`*_AUTH_FILENAME` / `MCP_AUTH_DIRNAME`), the data-only registry in `src/auth/credential-surface.ts` turns them into denylist patterns, `secret-guard-plugin.ts` owns matching (lexical plus realpath), and `@mention` resolution consumes the resolved check — never the registry directly. A new `*-auth.json` token store is denied only once its store module exports its filename and the registry lists it; the coverage test scans store sources for `*-auth.json` literals (registered dirnames get an includes-check instead) and fails the build until both exist.
93
93
94
+
### Inference credential recovery
95
+
96
+
Every inference source registers both live credential material and explicit provenance: OAuth credentials identify their provider and named profile, while API-key and keyless sources are classified separately. The harness freezes the complete source at call start, so classification, the one permitted recovery attempt, and any terminal diagnostic retain the exact provider/profile identity that actually failed even if the session changes provider concurrently.
97
+
98
+
On the first credential failure for an uncommitted call, the retry policy may refresh an OAuth credential from that same named profile and retry once immediately at the harness boundary. It does not substitute another profile or provider. Credential failures that survive that same-source attempt are terminal to the reactor's silent source-failover path. API-key, keyless, missing-provenance, and later credential failures do not enter OAuth refresh recovery.
99
+
100
+
OAuth stores serialize refresh writes and treat the value observed under the store lock as authoritative. A process that loses a compare-and-swap race adopts the concurrent winner, including its complete token set, instead of publishing or continuing with stale loser material. Credential material and refresh tokens are sanitized recursively before reactor events, run records, worker reports, terminal diagnostics, logs, or other diagnostic sinks can receive them.
101
+
102
+
The interactive TUI may offer an explicit alternate-provider/model selector only after the same-profile retry also ends in a terminal credential failure. That choice is generation-scoped and consumed once. It may continue the original operator message only when the failed attempt emitted no committing inference event; after any commitment it switches the live provider without replaying the message. `/connect` replaces or adds credentials and `/model` switches the live source explicitly. Exec and fleet workers use the same one-shot same-profile recovery but never open an auth or alternate-provider prompt; an exhausted failure terminates that attempt with a sanitized recovery diagnostic.
103
+
94
104
### TUI Runner (`src/tui/runner/`)
95
105
96
106
- Builds a chat-mode agent using the `ChatDirector`
Copy file name to clipboardExpand all lines: docs/IMPLEMENTATION.md
+16Lines changed: 16 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -382,6 +382,22 @@ Providers and credentials are read exclusively from settings files: the global `
382
382
383
383
**Models-first connect.** There is no standalone `/login` command. `/model` opens on a flat **models-only** list (Recent, Favorites, then connected provider/model rows) built by `buildModelsFirstList` (`src/tui/model-picker.ts`); type-to-filter owns printable keys. Selecting a row runs `applyLiveModelSwitch` (`src/session/live-model-switch.ts`) so inference sources, permission-gate identity, grant persistence identity, and advertised tool schemas cut over together. **Alt+A** or `/connect` opens Connect via `addProviderSelectorChoices` (`src/tui/provider-setup.ts`), which lists every first-class kind including Custom — never bare `c` / Ctrl+A, and never in-list “connect →” rows. First-class API-key rows use a named-instance + auth-only form (instance name, key; catalog base URL is display-only); Custom keeps the full manual form. **Alt+F** toggles favorites; recent/favorite pairs live in global settings (`recentModels` / `favoriteModels`). **Alt+D** sets the default via `setDefaultModel` (global `defaultProvider` + that provider's `defaultModel`) plus `persistConnectedSelection` without switching the live session. First-class providers ship from `packages/first-class-providers` (corbits-agnostic defs) and `packages/opencode-go` (Go catalog, auth validate, multi-protocol endpoints, usage). OAuth providers open the existing browser login modal with a named account step; API-key providers share the same multi-instance naming and pre-seed models on save so selection works without restart. Both OAuth and API-key (including Custom) connects share `persistConnectedSelection` in `provider-setup-submit.ts` so project-local provider/model selection is written alongside global credentials. OpenCode Go forces `OPENCODE_GO_BASE_URL` when `opencodeGo` is set so subscription traffic is not billed as Zen PAYG.
384
384
385
+
### Inference authentication recovery
386
+
387
+
`src/config/source-credentials.ts` stores each source's live `CredentialMaterial` beside `SourceCredentialProvenance`: `{ kind: "oauth", provider, profile }`, `{ kind: "api-key" }`, or `{ kind: "keyless" }`. Codex and xAI source builders register the exact named OAuth profile plus any credential-derived identity header in the same material record. The vendored harness resolves that record at send time and shallow-freezes the complete call-start source; retries therefore keep the original source identity rather than consulting mutable session selection.
388
+
389
+
`createCorbitsRetryPolicy` normalizes the attempt error before counting credential failures. On ordinal 1 only, an OAuth source calls `refreshSourceCredentialByProvenance` for that exact provider/profile and retries immediately. A refresh error is replaced with a profile-specific `/connect` diagnostic. Non-OAuth credentials, unknown provenance, later credential failures, and a failed post-refresh retry abort. The vendored harness treats the resulting `credential_failure` as terminal instead of feeding it to automatic source failover.
390
+
391
+
Codex and xAI refresh sessions use the auth store's locked compare-and-swap update. `updateTokens` returns the authoritative profile observed under the lock; callers replace the complete mutable token object with that winner. If a refresh request loses to a concurrent process, the valid stored winner is adopted rather than overwritten, including removal of optional fields absent from the winner. The in-memory source credential cell also rotates only if the record used to start refresh is still current.
392
+
393
+
`sanitizeDiagnosticText` strips terminal controls, replaces exact configured credentials, and scrubs secret-shaped text; `sanitizeDiagnosticValue` applies the same rules recursively. Refresh failures sanitize the refresh token through the error's message, stack, detail, and cause chain before it leaves the auth boundary. TUI, exec, and worker event paths sanitize before run sinks, persistent error records, terminal/UI notices, logs, and parent-facing worker output.
394
+
395
+
TUI recovery state lives in `src/tui/runner/credential-recovery.ts`. It arms only for an operator-originated send that observed both the automatic credential retry and a terminal credential failure, and only when another configured provider/model can be assembled. A monotonically increasing generation makes stale accept/cancel actions inert, and acceptance consumes the generation once. The selector excludes the failed provider. It switches the live provider/model and sends a content-less, generation-correlated director continuation only when no committing inference event occurred; after text, tool, or other committed output it switches without replay. Provider/model option IDs are opaque internal identities used to preserve arbitrary provider and model strings; only their labels are UI contract.
396
+
397
+
Exec and fleet workers install the same retry policy and sanitization but no recovery selector or credential prompt, including TTY exec. Their exhausted credential failure is terminal and reports how to repair the profile for a later run. In the TUI, `/connect` reauthorizes or adds a profile and refreshes the live catalog, while `/model` switches to a connected provider/model; either is available after a terminal failure.
398
+
399
+
This release changes no settings, OAuth-store, or session persistence format and runs no migration. New sessions and resumed sessions resolve provider selection and credentials from the stores as they exist when the process starts or rebuilds; persisted transcripts do not pin old credential material. A Corbits process already running during upgrade still has the old in-memory harness and credential cells and must be restarted to acquire this recovery behavior.
400
+
385
401
**OpenCode Go multi-protocol.** Selectable ids come from live `GET /zen/go/v1/models` (process-cached; packaged seed when cold or the fetch fails). Byte and model-count caps fail closed as malformed — never a truncated prefix. Protocol routing stays on the local map; unknown live ids use Chat Completions. `buildGoSource` / `resolveGoEndpoint` pick the adapter and base URL per model (not a single provider-wide OpenAI route). When Go is the active provider, subscription usage is fetched for the status bar and omitted on auth/network failure.
Copy file name to clipboardExpand all lines: docs/TUI.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -572,6 +572,12 @@ pair as the default (global `defaultProvider` + that provider's `defaultModel`
572
572
active, bare `j`/`k` type into the filter rather than moving the highlight —
573
573
use arrow keys (or the filtered list's navigation) to move.
574
574
575
+
After an operator-originated send fails authentication, Corbits first attempts one silent OAuth refresh and retry against the same named profile. If that retry also ends in a terminal credential failure, the shell waits until it is idle and may open a dedicated model/provider picker containing only assemblable models from providers other than the failed provider. It does not appear for API-key failures, the first failure before recovery is attempted, non-credential terminal errors, sends not originated by the operator, or when no alternate is available. Escape consumes the recovery offer without changing provider or replaying input.
576
+
577
+
Each recovery offer belongs to the failed send's generation. Enter accepts a displayed provider/model identity once; stale, malformed, duplicate, interrupted, cleared, or superseded acceptance is inert. The selected provider/model becomes live. If the failed attempt emitted no assistant text, tool activity, or other committing inference event, Corbits continues the preserved original operator message once on the selected provider. If anything committed, it **never replays the message**: selection only switches the live provider for the next operator action. The internal option IDs that preserve arbitrary provider/model names are opaque implementation details and are never shown as user-facing syntax.
578
+
579
+
The terminal failure remains visible and names `/connect` as the path to reauthorize a profile. `/connect` refreshes the provider catalog after successful authorization; `/model` remains available to switch explicitly to any connected provider/model. Neither command retroactively replays committed work.
580
+
575
581
`/mcp` uses the same longest-first overlay-hint footer as the model picker:
576
582
**Alt+A** add (omitted while local MCP settings shadow global), **Alt+D**
577
583
disable, **Alt+R** remove — never bare letters. A remove confirm drops those
0 commit comments