5195 lines
148 KiB
Markdown
5195 lines
148 KiB
Markdown
# Codex App-Server Account Feature - Detailed Implementation Plan
|
|
|
|
**Date**: 2026-04-20
|
|
**Status**: Reference-quality implementation plan
|
|
**Primary repo**: `claude_team`
|
|
**Secondary repo**: `agent_teams_orchestrator` only for later parity, not on the first critical path
|
|
**Canonical architecture reference**: [FEATURE_ARCHITECTURE_STANDARD.md](../FEATURE_ARCHITECTURE_STANDARD.md)
|
|
|
|
## Executive Summary
|
|
|
|
We should restore the old strong Codex subscription UX, but we should **not** bring back the old legacy Codex transport or legacy OAuth semantics.
|
|
|
|
The correct design is:
|
|
|
|
- keep **execution** on the current `codex-native` runtime path
|
|
- introduce a new dedicated feature slice: `src/features/codex-account`
|
|
- use **official `codex app-server` account APIs** as the managed-account control plane
|
|
- keep **app-owned API key storage** in the app
|
|
- explicitly merge the three different truths:
|
|
- managed ChatGPT account truth from `codex app-server`
|
|
- API key availability truth from app secure storage and ambient env detection
|
|
- real execution truth from `codex exec`
|
|
|
|
This feature should become the source of truth for:
|
|
|
|
- autodetect of an already logged-in Codex / ChatGPT account
|
|
- login / cancel / logout UI flow
|
|
- plan type display
|
|
- rate limit display
|
|
- subscription-first connection copy
|
|
- launch-readiness policy for ChatGPT-backed Codex
|
|
- deterministic per-launch auth-mode forcing
|
|
|
|
Core rule:
|
|
|
|
- `codex exec` remains the execution seam
|
|
- `codex app-server` becomes the account control-plane seam
|
|
- legacy Codex transport stays deleted
|
|
- legacy Codex OAuth stays deleted
|
|
- direct `auth.json` parsing stays forbidden
|
|
- `chatgptAuthTokens` host-managed mode stays out of scope
|
|
|
|
## Goals And Non-Goals
|
|
|
|
### Goals
|
|
|
|
- restore strong Codex subscription UX on top of the native runtime
|
|
- make managed ChatGPT account truth first-class again in the app
|
|
- keep API key support without letting it hijack subscription semantics
|
|
- keep runtime execution on `codex exec`
|
|
- keep ownership boundaries aligned with `FEATURE_ARCHITECTURE_STANDARD.md`
|
|
- make rollout safe through additive, testable composition
|
|
|
|
### Non-goals
|
|
|
|
This feature is **not** trying to:
|
|
|
|
- revive legacy Codex transport
|
|
- revive legacy Codex OAuth implementation details
|
|
- parse `~/.codex/auth.json`
|
|
- add browser-mode local app-server support in the first wave
|
|
- add app-server-managed API key login in the first wave
|
|
- add per-member auth preferences or per-member Codex backend preferences
|
|
- redesign the whole CLI shell UX for other providers
|
|
- bundle plugin/app-server enrichment beyond account control-plane needs
|
|
- solve orchestrator parity in the same first implementation
|
|
|
|
## Glossary
|
|
|
|
These terms are used repeatedly in the plan and must stay consistent.
|
|
|
|
### Preferred auth mode
|
|
|
|
Persisted user intent.
|
|
|
|
Allowed values:
|
|
|
|
- `auto`
|
|
- `chatgpt`
|
|
- `api_key`
|
|
|
|
### Effective auth mode
|
|
|
|
The auth mode the next launch will actually use after runtime evaluation.
|
|
|
|
Allowed values:
|
|
|
|
- `chatgpt`
|
|
- `api_key`
|
|
- `null`
|
|
|
|
### Snapshot state
|
|
|
|
High-level UI/account state describing what the app currently believes about Codex account
|
|
availability.
|
|
|
|
Examples:
|
|
|
|
- `managed_account_connected`
|
|
- `both_available`
|
|
- `degraded`
|
|
|
|
### Launch readiness
|
|
|
|
Execution-oriented state used to decide whether the app should launch Codex and under which auth
|
|
mode.
|
|
|
|
Examples:
|
|
|
|
- `ready_chatgpt`
|
|
- `ready_api_key`
|
|
- `missing_auth`
|
|
|
|
### Managed account
|
|
|
|
A ChatGPT-authenticated Codex account owned and persisted by Codex itself.
|
|
|
|
### API key availability
|
|
|
|
Whether the app can supply an OpenAI API key from its own secure storage or ambient env detection.
|
|
|
|
### Degraded
|
|
|
|
A state where the control plane cannot fully verify Codex account truth right now, but the app may
|
|
still have partial or fresh-enough knowledge to present a careful status and in some cases still
|
|
launch.
|
|
|
|
### Last-known-good snapshot
|
|
|
|
The most recent successful account snapshot that came from a real app-server read and passed normal
|
|
merge and validation logic.
|
|
|
|
### Freshness window
|
|
|
|
A bounded period during which the feature may temporarily reuse last-known-good managed-account
|
|
truth while the app-server is degraded.
|
|
|
|
### Control plane
|
|
|
|
The account-management seam implemented via `codex app-server`.
|
|
|
|
### Execution plane
|
|
|
|
The actual task-running seam implemented via `codex exec`.
|
|
|
|
## Chosen Plan Assessment
|
|
|
|
Chosen plan:
|
|
|
|
- full Codex app-server account seam as a dedicated feature slice, while keeping `codex-native` execution
|
|
|
|
Assessment:
|
|
|
|
- `🎯 9 🛡️ 9 🧠 7`
|
|
- estimated implementation size: `1800-3200` lines in `claude_team`, plus tests and docs
|
|
|
|
## Top 3 Viable Shapes
|
|
|
|
### 1. Full app-server account feature slice - chosen
|
|
|
|
`🎯 9 🛡️ 9 🧠 7`
|
|
Estimated size: `1800-3200` lines
|
|
|
|
Idea:
|
|
|
|
- build a dedicated `codex-account` feature
|
|
- use `codex app-server` for account state, login lifecycle, and rate limits
|
|
- keep API keys app-owned
|
|
- keep execution on `codex exec`
|
|
|
|
Why this wins:
|
|
|
|
- best long-term architecture
|
|
- truthful subscription UX
|
|
- avoids legacy transport return
|
|
- creates a clean seam between account control plane and execution plane
|
|
- aligns with the repo's feature architecture standard
|
|
|
|
Main cost:
|
|
|
|
- more moving parts than simple CLI probing
|
|
- requires careful runtime-policy integration
|
|
|
|
### 2. Hybrid read-via-app-server and login-via-cli
|
|
|
|
`🎯 8 🛡️ 8 🧠 6`
|
|
Estimated size: `1200-2200` lines
|
|
|
|
Idea:
|
|
|
|
- `account/read` and rate limits via app-server
|
|
- login/logout still via `codex login` / `codex logout`
|
|
|
|
Pros:
|
|
|
|
- simpler login integration
|
|
- still gets rich autodetect and plan truth
|
|
|
|
Cons:
|
|
|
|
- split control plane
|
|
- less internally coherent
|
|
- more transitional than final
|
|
|
|
### 3. CLI-only managed-account seam
|
|
|
|
`🎯 8 🛡️ 8 🧠 4`
|
|
Estimated size: `700-1200` lines
|
|
|
|
Idea:
|
|
|
|
- use `codex login status`, `codex login`, `codex logout`
|
|
- build UI around plain CLI probing
|
|
|
|
Pros:
|
|
|
|
- simpler
|
|
- safer if we were optimizing only for speed
|
|
|
|
Cons:
|
|
|
|
- poorer structured account metadata
|
|
- weak rate-limit surface
|
|
- less extensible
|
|
- does not justify a full feature slice as well
|
|
|
|
## Final Decision
|
|
|
|
We are taking option 1.
|
|
|
|
Reason:
|
|
|
|
- the user requirement is not just "support subscription somehow"
|
|
- the requirement is "bring back the strong legacy-quality Codex subscription UX, but on the native runtime"
|
|
- for that requirement, the app-server account seam is the cleanest and most future-proof architecture
|
|
|
|
## Resolved Decisions Register
|
|
|
|
This section keeps the most important architectural choices explicit, so they do not get
|
|
re-litigated ad hoc during implementation.
|
|
|
|
### Resolved - execution seam
|
|
|
|
Decision:
|
|
|
|
- keep execution on raw `codex exec`
|
|
|
|
Why:
|
|
|
|
- it matches the current cutover direction
|
|
- it avoids reopening the legacy transport question inside this feature
|
|
|
|
### Resolved - control-plane seam
|
|
|
|
Decision:
|
|
|
|
- use `codex app-server` for account lifecycle and rate-limit truth
|
|
|
|
Why:
|
|
|
|
- it is the official structured surface
|
|
- it supports managed account autodetect and login lifecycle without file parsing
|
|
|
|
### Resolved - secret ownership
|
|
|
|
Decision:
|
|
|
|
- ChatGPT managed auth belongs to Codex
|
|
- API key ownership remains app-owned
|
|
|
|
Why:
|
|
|
|
- avoids dual key stores
|
|
- keeps responsibility boundaries clear
|
|
|
|
### Resolved - Codex connection naming
|
|
|
|
Decision:
|
|
|
|
- persisted preference uses `preferredAuthMode: "auto" | "chatgpt" | "api_key"`
|
|
|
|
Why:
|
|
|
|
- `oauth` is legacy wording and no longer the right semantic label for Codex
|
|
|
|
### Resolved - browser mode
|
|
|
|
Decision:
|
|
|
|
- browser-mode app-server support is deferred
|
|
|
|
Why:
|
|
|
|
- desktop Electron path is the real target for the first implementation
|
|
- we should not hide platform limitations behind fake parity
|
|
|
|
### Resolved - device code
|
|
|
|
Decision:
|
|
|
|
- ChatGPT browser flow is first-class
|
|
- `chatgptDeviceCode` is deferred unless required by a real blocker
|
|
|
|
Why:
|
|
|
|
- browser flow better matches the intended legacy-quality desktop UX
|
|
|
|
### Resolved - degraded launch policy
|
|
|
|
Decision:
|
|
|
|
- degraded control-plane state may still be launchable only with positive current or sufficiently
|
|
fresh prior managed-account evidence
|
|
|
|
Why:
|
|
|
|
- prevents false hard blocks
|
|
- also prevents indefinite stale-account lies
|
|
|
|
## Problem Statement
|
|
|
|
### What is wrong today
|
|
|
|
The current codebase has fully cut over to `codex-native` execution, but the account and UX layer was flattened too far.
|
|
|
|
Current product truth:
|
|
|
|
- Codex runtime lane is native-only
|
|
- Codex UI is effectively API-key-only
|
|
- Codex managed ChatGPT subscription autodetect is gone from app UX
|
|
- Codex login/logout from app UI was removed
|
|
- current launch-readiness policy still assumes API key credentials are required
|
|
|
|
This creates a product and architecture mismatch:
|
|
|
|
- the real Codex native runtime supports ChatGPT account auth
|
|
- but the app currently acts as if only API keys exist
|
|
|
|
### Why this is dangerous
|
|
|
|
If we only restore cosmetic UI copy and do not change runtime policy, we will create a worse failure mode:
|
|
|
|
- UI says subscription is available
|
|
- but launch still fails because the app hard-gates on `OPENAI_API_KEY` or `CODEX_API_KEY`
|
|
|
|
That would be a deceptive product state.
|
|
|
|
So the plan must fix:
|
|
|
|
1. managed account detection
|
|
2. managed login flow
|
|
3. runtime launch policy
|
|
4. UI presentation
|
|
|
|
all together
|
|
|
|
## Confirmed Facts And Constraints
|
|
|
|
This section lists facts the plan relies on.
|
|
|
|
## Official Codex facts
|
|
|
|
Based on the current public OpenAI Codex docs, plus the protocol schemas generated from the
|
|
installed Codex binary on this machine:
|
|
|
|
- Codex supports both ChatGPT account auth and API key auth
|
|
- `codex app-server` supports:
|
|
- `account/read`
|
|
- `account/login/start`
|
|
- `account/login/cancel`
|
|
- `account/logout`
|
|
- `account/rateLimits/read`
|
|
- `account/updated`
|
|
- `account/login/completed`
|
|
- `account/rateLimits/updated`
|
|
- `account/read` returns:
|
|
- `account | null`
|
|
- `requiresOpenaiAuth`
|
|
- when `account.type === "chatgpt"`, the current schema requires:
|
|
- `email`
|
|
- `planType`
|
|
- when `account.type === "apiKey"`, the current schema exposes only:
|
|
- `type`
|
|
- current generated `account/updated` schema includes nullable:
|
|
- `authMode`
|
|
- `planType`
|
|
- current documented `authMode` values include:
|
|
- `apikey`
|
|
- `chatgpt`
|
|
- `chatgptAuthTokens`
|
|
- `null`
|
|
- current generated `account/rateLimits/read` schema exposes:
|
|
- `rateLimits`
|
|
- `rateLimitsByLimitId`
|
|
- per-snapshot `planType | null`
|
|
- Codex config supports:
|
|
- `forced_login_method = "chatgpt"`
|
|
- `forced_login_method = "api"`
|
|
- Codex tools accept config overrides via top-level `-c key=value`
|
|
- current app-server docs explicitly show ChatGPT browser flow via:
|
|
- `type: "chatgpt"`
|
|
- current app-server docs explicitly show externally managed token mode via:
|
|
- `type: "chatgptAuthTokens"`
|
|
- the generated protocol types explicitly mark `chatgptAuthTokens` as unstable / internal-only, so
|
|
the first implementation must not depend on it
|
|
|
|
Interpretation rule:
|
|
|
|
- steady-state managed-account identity must still come from successful `account/read`
|
|
- notification fields are freshness accelerators, not a durable replacement read model
|
|
|
|
Sources:
|
|
|
|
- [Authentication - Codex](https://developers.openai.com/codex/auth)
|
|
- [App Server - Codex](https://developers.openai.com/codex/app-server)
|
|
|
|
## Locally verified facts
|
|
|
|
Verified on this machine on 2026-04-20:
|
|
|
|
- `codex login status` returns `Logged in using ChatGPT`
|
|
- `codex app-server` starts locally
|
|
- `account/read` returned:
|
|
- `account.type = "chatgpt"`
|
|
- `email = "quantjumppro@gmail.com"`
|
|
- `planType = "pro"`
|
|
- `requiresOpenaiAuth = true`
|
|
|
|
Practical implication:
|
|
|
|
- managed-account autodetect is real
|
|
- it does not require reverse-engineering auth storage
|
|
|
|
## Current repo facts
|
|
|
|
Current relevant code facts:
|
|
|
|
- `recent-projects` already uses `codex app-server` over short-lived JSON-RPC stdio sessions
|
|
- generic Codex runtime status currently sits in:
|
|
- `src/main/services/runtime/ClaudeMultimodelBridgeService.ts`
|
|
- generic provider connection logic currently sits in:
|
|
- `src/main/services/runtime/ProviderConnectionService.ts`
|
|
- launch env assembly currently sits in:
|
|
- `src/main/services/runtime/providerAwareCliEnv.ts`
|
|
- Codex UI currently flattened to API-key-only sits in:
|
|
- `src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx`
|
|
- `src/renderer/components/runtime/providerConnectionUi.ts`
|
|
- `src/renderer/components/dashboard/CliStatusBanner.tsx`
|
|
- `src/renderer/components/settings/sections/CliStatusSection.tsx`
|
|
|
|
## Current schema facts
|
|
|
|
Current config and shared-type state:
|
|
|
|
- `providerConnections.codex` is currently `Record<string, never>`
|
|
- `configValidation` currently rejects real Codex connection fields except for ignored stale legacy fields
|
|
- shared `AppConfig` mirrors still declare Codex provider connections as empty
|
|
|
|
Practical implication:
|
|
|
|
- this feature requires deliberate config schema expansion and migration logic
|
|
|
|
## App-Server Compatibility Facts
|
|
|
|
Current local code and generated protocol schemas show:
|
|
|
|
- `recent-projects` already initializes app-server with:
|
|
- `experimentalApi: false`
|
|
- `optOutNotificationMethods`
|
|
- generated `initialize` response includes:
|
|
- `codexHome`
|
|
- `platformFamily`
|
|
- `platformOs`
|
|
- generated login response for `type: "chatgpt"` includes:
|
|
- `loginId`
|
|
- `authUrl`
|
|
|
|
Practical implication:
|
|
|
|
- the feature can and should stay on the stable app-server surface
|
|
- compatibility should be decided by an initialize-plus-required-method handshake, not by semver
|
|
parsing alone
|
|
- auth-root diagnostics can use `initialize.codexHome` as a first-class observed fact instead of
|
|
only inferring from environment variables
|
|
|
|
## Honest Confidence Hotspots
|
|
|
|
These are the places where confidence is lower than the rest of the plan and where we should be
|
|
deliberately conservative.
|
|
|
|
### Hotspot 1 - undocumented or lightly documented auth variants
|
|
|
|
Assessment:
|
|
|
|
- `🎯 6 🛡️ 9 🧠 3`
|
|
|
|
What we know:
|
|
|
|
- official app-server docs clearly show:
|
|
- `chatgpt`
|
|
- `apiKey`
|
|
- `chatgptAuthTokens`
|
|
- local schema / CLI evidence suggests more variants may exist
|
|
|
|
Plan decision:
|
|
|
|
- first implementation depends only on the clearly documented browser-flow `chatgpt` path
|
|
- do not build phase 1 around additional auth variants
|
|
|
|
Why this is the safe choice:
|
|
|
|
- avoids coupling to protocol surfaces that may be less stable or less publicly specified
|
|
|
|
### Hotspot 2 - exact ordering of app-server notifications
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 9 🧠 4`
|
|
|
|
What we know:
|
|
|
|
- docs and local evidence show `account/login/completed` and `account/updated`
|
|
- but we should not assume stronger guarantees than we have to
|
|
|
|
Plan decision:
|
|
|
|
- notifications accelerate freshness
|
|
- explicit snapshot refresh remains the recovery source of truth
|
|
|
|
Why this is the safe choice:
|
|
|
|
- even if event order changes slightly, the feature still converges to the correct steady state
|
|
|
|
### Hotspot 3 - rate-limits payload usefulness for the first UI
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 8 🧠 4`
|
|
|
|
What we know:
|
|
|
|
- app-server exposes ChatGPT rate-limit reads and updates
|
|
- exact display value of every field may not be necessary for phase 1 UX
|
|
|
|
Plan decision:
|
|
|
|
- keep rate-limits lazy
|
|
- present the minimal truthful subset first
|
|
- avoid making base account UX depend on rate-limit richness
|
|
|
|
Why this is the safe choice:
|
|
|
|
- avoids blocking the critical auth/launch story on secondary UI detail
|
|
|
|
### Hotspot 4 - `forced_login_method` long-term contract stability
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 8 🧠 3`
|
|
|
|
What we know:
|
|
|
|
- the auth docs and local behavior support the approach
|
|
- the CLI reference page is not the strongest canonical place for this exact contract
|
|
|
|
Plan decision:
|
|
|
|
- isolate this override behind the coordinator/env builder seam
|
|
- do not scatter it through renderer or generic shell logic
|
|
|
|
Why this is the safe choice:
|
|
|
|
- if the exact override mechanism ever changes, the blast radius stays small
|
|
|
|
### Hotspot 5 - precedence of account fields across read, notification, and rate-limit surfaces
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 9 🧠 4`
|
|
|
|
What we know:
|
|
|
|
- the generated protocol schema shows `account/updated` carries nullable `authMode` and `planType`
|
|
- `account/rateLimits/read` also carries `planType`
|
|
- notifications are best-effort and should not be treated as a durable replay log
|
|
|
|
Plan decision:
|
|
|
|
- the latest successful `account/read` owns steady-state account identity
|
|
- `account/updated` and rate-limit snapshots may refresh hints and trigger coalesced rereads
|
|
- they must never synthesize account presence on their own
|
|
|
|
Why this is the safe choice:
|
|
|
|
- avoids phantom login or phantom subscription UI caused by late, partial, or duplicated
|
|
notifications
|
|
|
|
### Hotspot 6 - binary compatibility and stable-vs-experimental app-server surface
|
|
|
|
Assessment:
|
|
|
|
- `🎯 8 🛡️ 9 🧠 4`
|
|
|
|
What we know:
|
|
|
|
- official app-server docs describe `initialize.params.capabilities.experimentalApi`
|
|
- current local `recent-projects` integration already uses `experimentalApi: false`
|
|
- generated `initialize` response exposes `codexHome`, `platformFamily`, and `platformOs`
|
|
|
|
Plan decision:
|
|
|
|
- first-wave `codex-account` must depend only on stable account APIs
|
|
- feature readiness must be gated by successful initialize plus required stable method support
|
|
- do not make product behavior depend on parsing CLI semver strings alone
|
|
|
|
Why this is the safe choice:
|
|
|
|
- reduces risk from partial protocol drift across installed Codex binaries
|
|
- keeps compatibility logic tied to the actual negotiated surface
|
|
|
|
### Hotspot 7 - managed workspace restriction behavior in admin-controlled installs
|
|
|
|
Assessment:
|
|
|
|
- `🎯 6 🛡️ 8 🧠 4`
|
|
|
|
What we know:
|
|
|
|
- official auth docs support `forced_chatgpt_workspace_id`
|
|
- official docs say mismatched credentials cause Codex to log the user out and exit
|
|
- the app does not currently own workspace selection or workspace switching for Codex
|
|
|
|
Plan decision:
|
|
|
|
- first wave treats workspace restriction as admin policy truth, not as generic missing-auth
|
|
- the UI must not invent a workspace picker or a fake remediation flow the app does not own
|
|
|
|
Why this is the safe choice:
|
|
|
|
- preserves truthful UX in managed environments without expanding scope into unsupported account
|
|
management
|
|
|
|
### Hotspot 8 - trust boundary around `authUrl` and sensitive login metadata
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 9 🧠 3`
|
|
|
|
What we know:
|
|
|
|
- `account/login/start { type: "chatgpt" }` returns `authUrl` and `loginId`
|
|
- existing generic `shell:openExternal` allows `http`, `https`, and `mailto`
|
|
- the feature only needs browser auth URLs, not general-purpose URL opening
|
|
|
|
Plan decision:
|
|
|
|
- keep raw `authUrl` handling in main only
|
|
- require a stricter feature-specific validation policy before opening:
|
|
- scheme must be `https`
|
|
- no renderer round-trip for the raw URL
|
|
- no raw URL logging
|
|
- treat `loginId` as process-lifecycle metadata, not as user-facing state
|
|
|
|
Why this is the safe choice:
|
|
|
|
- reduces accidental leakage of login URLs or correlation ids across IPC, logs, and renderer state
|
|
|
|
### Hotspot 9 - mutation races and app-server session topology
|
|
|
|
Assessment:
|
|
|
|
- `🎯 7 🛡️ 9 🧠 5`
|
|
|
|
What we know:
|
|
|
|
- `account/login/completed`, `account/login/cancel`, `account/logout`, and forced snapshot refreshes
|
|
can overlap in time
|
|
- app-server notifications are connection-scoped
|
|
- `recent-projects` already uses separate short-lived stdio app-server sessions
|
|
|
|
Plan decision:
|
|
|
|
- serialize mutating account operations in the main-process feature
|
|
- keep the login session on its own dedicated app-server connection
|
|
- keep passive reads on separate short-lived sessions
|
|
- let fresh steady-state snapshot truth settle races instead of trusting action intent alone
|
|
|
|
Why this is the safe choice:
|
|
|
|
- avoids cross-session notification bleed, duplicate side effects, and stale mutations reanimating
|
|
old UI state
|
|
|
|
## Pre-Implementation Confidence Burn-Down Checklist
|
|
|
|
These checks are the shortest path to reducing the remaining honest uncertainty before or during the
|
|
first implementation steps.
|
|
|
|
### Burn-down check 1 - capture real browser-login notification sequence
|
|
|
|
Goal:
|
|
|
|
- observe the real order of:
|
|
- `account/login/completed`
|
|
- `account/updated`
|
|
- any adjacent auth-related notifications
|
|
|
|
Why:
|
|
|
|
- confirms our event-ordering assumptions are conservative enough
|
|
|
|
Expected outcome:
|
|
|
|
- even if order differs slightly from expectation, the explicit refresh model remains valid
|
|
|
|
### Burn-down check 2 - capture one real `account/rateLimits/read` sample
|
|
|
|
Goal:
|
|
|
|
- verify the actual payload shape we want to expose in the first UI
|
|
|
|
Why:
|
|
|
|
- reduces uncertainty around which fields are worth surfacing in phase F
|
|
|
|
Expected outcome:
|
|
|
|
- confirm minimal truthful fields for initial UI
|
|
- defer decorative/secondary fields safely
|
|
|
|
### Burn-down check 3 - re-verify ChatGPT launch path with ambient API keys present
|
|
|
|
Goal:
|
|
|
|
- prove that the chosen env sanitization plus auth override actually forces the intended ChatGPT path
|
|
|
|
Why:
|
|
|
|
- this is the highest-value correctness risk in the whole feature
|
|
|
|
Expected outcome:
|
|
|
|
- capture one signoff artifact that launch uses ChatGPT semantics even when API-key env vars are
|
|
present in the parent shell
|
|
|
|
### Burn-down check 4 - verify logout semantics against stale cached state
|
|
|
|
Goal:
|
|
|
|
- prove that explicit logout wins over last-known-good snapshot reuse
|
|
|
|
Why:
|
|
|
|
- prevents the most embarrassing stale-account resurrection bug
|
|
|
|
Expected outcome:
|
|
|
|
- logout clears managed-account truth immediately
|
|
- degraded follow-up reads cannot resurrect it
|
|
|
|
### Burn-down check 5 - confirm app-server and exec share identical auth-store roots
|
|
|
|
Goal:
|
|
|
|
- compare resolved `HOME`, `USERPROFILE`, and `CODEX_HOME` across both paths
|
|
|
|
Why:
|
|
|
|
- split auth store is a high-severity, low-visibility failure mode
|
|
|
|
Expected outcome:
|
|
|
|
- one centralized env normalization path is sufficient for both control plane and execution plane
|
|
|
|
### Burn-down check 6 - capture one real `account/updated` payload sequence
|
|
|
|
Goal:
|
|
|
|
- observe whether `authMode` and `planType` arrive as expected across login, logout, and steady
|
|
state refreshes
|
|
|
|
Why:
|
|
|
|
- confirms the precedence rules stay conservative against nullable or partial notification payloads
|
|
|
|
Expected outcome:
|
|
|
|
- keep `account/read` as the steady-state owner
|
|
- use notifications only as accelerators and invalidation hints
|
|
|
|
### Burn-down check 7 - verify initialize handshake on the stable surface
|
|
|
|
Goal:
|
|
|
|
- confirm the feature can derive a compatibility verdict from stable initialize plus required method
|
|
support
|
|
|
|
Why:
|
|
|
|
- prevents false-ready UI on installs where `codex app-server` exists but the required account
|
|
surface is too old or otherwise incompatible
|
|
|
|
Expected outcome:
|
|
|
|
- initialize succeeds with `experimentalApi: false`
|
|
- the feature records `codexHome`, `platformFamily`, and `platformOs` for diagnostics
|
|
- unsupported/mismatched installs become `app-server-incompatible`, not generic auth failure
|
|
|
|
### Burn-down check 8 - capture one real ChatGPT login URL handling sample
|
|
|
|
Goal:
|
|
|
|
- validate the actual `authUrl` scheme/shape and prove the browser-open path can stay main-only
|
|
|
|
Why:
|
|
|
|
- closes the last trust-boundary uncertainty around login URL handling without expanding scope into
|
|
brittle host-specific assumptions
|
|
|
|
Expected outcome:
|
|
|
|
- the real login URL is `https`
|
|
- raw `authUrl` never needs to cross IPC
|
|
- logs can record only redacted/derived diagnostics such as scheme and hostname when necessary
|
|
|
|
### Burn-down check 9 - exercise cancel/completion/logout race resolution
|
|
|
|
Goal:
|
|
|
|
- prove that overlapping account mutations still converge to one truthful steady state
|
|
|
|
Why:
|
|
|
|
- this is one of the easiest places to create zombie pending state or accidental false logout
|
|
|
|
Expected outcome:
|
|
|
|
- cancel followed by late login completion does not force a false disconnect
|
|
- logout during pending login still ends in logged-out truth
|
|
- stale pre-mutation reads cannot overwrite post-mutation state
|
|
|
|
## Recommended Placement Of Burn-Down Checks In The Phase Plan
|
|
|
|
To keep momentum, we should not treat all uncertainty as a separate research project.
|
|
|
|
### Before or during Phase B
|
|
|
|
- Burn-down check 1
|
|
- Burn-down check 2
|
|
- Burn-down check 6
|
|
- Burn-down check 7
|
|
- Burn-down check 8
|
|
|
|
Reason:
|
|
|
|
- these directly shape contracts and event handling
|
|
|
|
### Before or during Phase D
|
|
|
|
- Burn-down check 3
|
|
- Burn-down check 5
|
|
|
|
Reason:
|
|
|
|
- these directly shape launch correctness and env routing
|
|
|
|
### Before or during Phase E
|
|
|
|
- Burn-down check 4
|
|
- Burn-down check 9
|
|
|
|
Reason:
|
|
|
|
- this directly shapes logout semantics, mutation ordering, and stale-state invalidation
|
|
|
|
## Uncertainty Triage - What Must Be Settled Before Broad Coding
|
|
|
|
Not every unknown deserves to block the feature. The plan should distinguish hard preconditions from
|
|
safe rollout follow-ups.
|
|
|
|
### Must be explicit before the feature spreads across shell and renderer
|
|
|
|
- account field precedence between `account/read`, `account/updated`, and `account/rateLimits/read`
|
|
- stable-surface compatibility gate for installed `codex app-server`
|
|
- containment rules for `authUrl`, `loginId`, and account email across main, IPC, and logs
|
|
- mutation serialization and race-settlement policy for login/cancel/logout
|
|
- shared auth-store root resolution for app-server and `codex exec`
|
|
- launch-time env sanitization when ambient API keys exist
|
|
- logout invalidation semantics and last-known-good clearing rules
|
|
|
|
Why:
|
|
|
|
- each of these can create silent false truth in UI or billing/auth mismatches at runtime
|
|
|
|
### Can be refined during rollout without invalidating the architecture
|
|
|
|
- how rich the first rate-limit UI should be
|
|
- whether degraded state needs an extra badge in addition to text
|
|
- whether we surface plan-type changes instantly from notifications or only after follow-up reads
|
|
- how aggressively background refresh should coalesce under bursty notification traffic
|
|
- whether managed-workspace restriction gets a dedicated visual badge or text-only treatment
|
|
|
|
Why:
|
|
|
|
- these change UX sharpness, not the core correctness contract
|
|
|
|
## Known Current Mismatches The Plan Must Explicitly Eliminate
|
|
|
|
These are not abstract concerns. They already exist in the current code and must be treated as
|
|
first-class implementation targets.
|
|
|
|
### Mismatch 1 - Codex connection truth is still API-key-first
|
|
|
|
Current reality:
|
|
|
|
- `ProviderConnectionService` still treats Codex readiness as "API key exists"
|
|
- `getConfiguredConnectionIssue()` still says Codex native requires `OPENAI_API_KEY` or
|
|
`CODEX_API_KEY`
|
|
|
|
Why this is dangerous:
|
|
|
|
- once ChatGPT account UX is restored, launch policy can still lie and hard-fail incorrectly
|
|
|
|
### Mismatch 2 - shell UI copy still flattens Codex to API key management
|
|
|
|
Current reality:
|
|
|
|
- `providerConnectionUi.ts` still uses:
|
|
- `Configure API key`
|
|
- `Saved API key available in Manage`
|
|
- `Codex native ready`
|
|
- there is no first-class Codex managed account summary
|
|
|
|
Why this is dangerous:
|
|
|
|
- the renderer will keep presenting the wrong mental model even if the runtime becomes correct
|
|
|
|
### Mismatch 3 - shell login/logout flow is terminal-modal based
|
|
|
|
Current reality:
|
|
|
|
- `CliStatusBanner.tsx`
|
|
- `CliStatusSection.tsx`
|
|
|
|
still drive provider login/logout through terminal modals and shell commands.
|
|
|
|
Why this is dangerous:
|
|
|
|
- even if app-server account logic exists, the visible UX would still route through the wrong seam
|
|
|
|
### Mismatch 4 - config schema has no real Codex connection preference
|
|
|
|
Current reality:
|
|
|
|
- `providerConnections.codex` is still effectively empty
|
|
- validation only tolerates stale legacy keys instead of modeling the current desired state
|
|
|
|
Why this is dangerous:
|
|
|
|
- renderer state, persistence, and launch policy can drift because there is no canonical stored
|
|
preference
|
|
|
|
### Mismatch 5 - app-server infrastructure is feature-owned by `recent-projects`
|
|
|
|
Current reality:
|
|
|
|
- generic JSON-RPC stdio transport is currently nested under `recent-projects`
|
|
|
|
Why this is dangerous:
|
|
|
|
- a second feature would either duplicate the transport or deep-import another feature's internals
|
|
|
|
### Mismatch 6 - current multimodel shell status cannot be the Codex account source of truth
|
|
|
|
Current reality:
|
|
|
|
- `CliProviderStatus` is useful for binary/backend/model truth
|
|
- it is not sufficient for login lifecycle, plan display, or dual-surface auth truth
|
|
|
|
Why this is dangerous:
|
|
|
|
- forcing all Codex account truth into `CliProviderStatus` would create a leaky, provider-specific
|
|
blob in generic shell status contracts
|
|
|
|
## Hard constraints
|
|
|
|
The implementation must respect all of the following:
|
|
|
|
1. Execution must stay on `codex-native` / `codex exec`.
|
|
2. The feature must not recreate legacy Codex transport.
|
|
3. The feature must not parse `~/.codex/auth.json`.
|
|
4. The feature must not duplicate API key storage responsibility into Codex-managed storage.
|
|
5. The feature must not hard-block launch only because app-server is transiently degraded.
|
|
6. The feature must keep auth truth and execution truth separate.
|
|
7. The feature must keep app-server and `codex exec` running against the same auth storage context.
|
|
|
|
## Why This Must Be A Feature Slice
|
|
|
|
This is not just another Codex-specific if-statement.
|
|
|
|
This work:
|
|
|
|
- spans `main -> preload -> renderer`
|
|
- owns transport wiring
|
|
- owns its own use cases
|
|
- owns provider-specific business policy
|
|
- is expected to grow
|
|
|
|
That matches the feature standard's criteria for a full slice.
|
|
|
|
So the implementation should **not** be buried into:
|
|
|
|
- `ProviderConnectionService`
|
|
- `ProviderRuntimeSettingsDialog`
|
|
- `CliStatusBanner`
|
|
- `CliStatusSection`
|
|
|
|
Those shell modules should consume this feature, not own it.
|
|
|
|
## Feature Topology
|
|
|
|
```text
|
|
src/features/codex-account/
|
|
contracts/
|
|
api.ts
|
|
channels.ts
|
|
dto.ts
|
|
events.ts
|
|
index.ts
|
|
core/
|
|
domain/
|
|
CodexConnectionPreference.ts
|
|
CodexManagedAccount.ts
|
|
CodexLaunchReadiness.ts
|
|
CodexConnectionSnapshot.ts
|
|
CodexLoginState.ts
|
|
application/
|
|
ports/
|
|
CodexManagedAccountSourcePort.ts
|
|
CodexManagedLoginPort.ts
|
|
CodexRateLimitSourcePort.ts
|
|
CodexApiKeyAvailabilityPort.ts
|
|
CodexBinaryResolverPort.ts
|
|
CodexShellEnvPort.ts
|
|
BrowserLauncherPort.ts
|
|
ClockPort.ts
|
|
LoggerPort.ts
|
|
use-cases/
|
|
GetCodexConnectionSnapshotUseCase.ts
|
|
RefreshCodexConnectionSnapshotUseCase.ts
|
|
StartCodexChatgptLoginUseCase.ts
|
|
CancelCodexLoginUseCase.ts
|
|
LogoutCodexManagedAccountUseCase.ts
|
|
ReadCodexRateLimitsUseCase.ts
|
|
EvaluateCodexLaunchReadinessUseCase.ts
|
|
main/
|
|
composition/
|
|
createCodexAccountFeature.ts
|
|
adapters/
|
|
input/
|
|
ipc/registerCodexAccountIpc.ts
|
|
output/
|
|
presenters/
|
|
CodexConnectionSnapshotPresenter.ts
|
|
CodexRateLimitsPresenter.ts
|
|
CodexAccountEventPresenter.ts
|
|
runtime/
|
|
ProviderConnectionApiKeySourceAdapter.ts
|
|
CodexLaunchReadinessRuntimeAdapter.ts
|
|
shell/
|
|
ElectronBrowserLauncherAdapter.ts
|
|
infrastructure/
|
|
cache/
|
|
InMemoryCodexAccountCache.ts
|
|
codex/
|
|
CodexAccountAppServerClient.ts
|
|
CodexLoginSessionManager.ts
|
|
CodexAccountEnvBuilder.ts
|
|
preload/
|
|
createCodexAccountBridge.ts
|
|
index.ts
|
|
renderer/
|
|
index.ts
|
|
adapters/
|
|
codexAccountViewModel.ts
|
|
codexProviderShellAdapter.ts
|
|
hooks/
|
|
useCodexAccount.ts
|
|
useCodexLoginFlow.ts
|
|
useCodexRateLimits.ts
|
|
ui/
|
|
CodexAccountConnectionPanel.tsx
|
|
CodexLoginPendingPanel.tsx
|
|
CodexRateLimitsPanel.tsx
|
|
CodexConnectionSummaryBadge.tsx
|
|
```
|
|
|
|
## Responsibility Split By Layer
|
|
|
|
### `contracts/`
|
|
|
|
Contains:
|
|
|
|
- DTOs
|
|
- event payloads
|
|
- IPC channel names
|
|
- preload API contract
|
|
|
|
Must not contain:
|
|
|
|
- Electron APIs
|
|
- runtime policy
|
|
- child-process details
|
|
|
|
### `core/domain/`
|
|
|
|
Contains:
|
|
|
|
- preference model
|
|
- managed-account model
|
|
- launch-readiness model
|
|
- invariants for combining account and API key truth
|
|
|
|
Must not contain:
|
|
|
|
- `ipcRenderer`
|
|
- `electron`
|
|
- shell env access
|
|
- JSON-RPC transport
|
|
|
|
### `core/application/`
|
|
|
|
Contains:
|
|
|
|
- use cases
|
|
- ports
|
|
- merge rules
|
|
- state transition rules
|
|
|
|
Must not contain:
|
|
|
|
- actual app-server spawn logic
|
|
- actual browser open logic
|
|
- app config singleton access
|
|
|
|
### `main/composition/`
|
|
|
|
Contains:
|
|
|
|
- wiring of ports to infrastructure
|
|
- export of a small feature facade
|
|
|
|
### `main/adapters/input/`
|
|
|
|
Contains:
|
|
|
|
- IPC registration only
|
|
|
|
### `main/adapters/output/`
|
|
|
|
Contains:
|
|
|
|
- translation from existing shell services into feature ports
|
|
- presenters for IPC-safe DTOs
|
|
|
|
### `main/infrastructure/`
|
|
|
|
Contains:
|
|
|
|
- app-server stdio JSON-RPC details
|
|
- login session lifecycle management
|
|
- env sanitization and assembly
|
|
- cache implementation
|
|
|
|
### `preload/`
|
|
|
|
Contains:
|
|
|
|
- bridge methods and event subscriptions
|
|
|
|
### `renderer/`
|
|
|
|
Contains:
|
|
|
|
- hooks
|
|
- view-model mapping
|
|
- Codex-specific UI pieces
|
|
|
|
## Feature Facade And Public Contract Shape
|
|
|
|
To keep SRP and interface segregation intact, the feature should expose a small facade rather than
|
|
letting shell code reach into individual infrastructure pieces.
|
|
|
|
Recommended main-process facade shape:
|
|
|
|
```ts
|
|
interface CodexAccountFeatureFacade {
|
|
getSnapshot(options?: { forceFresh?: boolean }): Promise<CodexConnectionSnapshotDto>;
|
|
refreshSnapshot(): Promise<CodexConnectionSnapshotDto>;
|
|
startChatgptLogin(): Promise<CodexLoginStateDto>;
|
|
cancelLogin(): Promise<CodexLoginStateDto>;
|
|
logout(): Promise<CodexConnectionSnapshotDto>;
|
|
getRateLimits(options?: { forceFresh?: boolean }): Promise<CodexRateLimitsDto | null>;
|
|
evaluateLaunchReadiness(options: {
|
|
binaryPath?: string | null;
|
|
preferredAuthMode?: 'auto' | 'chatgpt' | 'api_key' | null;
|
|
}): Promise<CodexLaunchReadinessDto>;
|
|
subscribe(listener: (event: CodexAccountEventDto) => void): () => void;
|
|
}
|
|
```
|
|
|
|
Design rule:
|
|
|
|
- the shell should depend on this facade or its IPC equivalent
|
|
- the shell should not call:
|
|
- `CodexAccountAppServerClient`
|
|
- `CodexLoginSessionManager`
|
|
- cache objects
|
|
- low-level env builders
|
|
|
|
This keeps the implementation open for extension without forcing broad shell rewrites later.
|
|
|
|
## Shared Infrastructure Extraction Plan
|
|
|
|
We already have generic app-server transport primitives hiding under `recent-projects`.
|
|
|
|
That code should be extracted before `codex-account` is built, otherwise `recent-projects` becomes the owner of unrelated infrastructure.
|
|
|
|
Recommended extraction target:
|
|
|
|
```text
|
|
src/main/services/infrastructure/codex-app-server/
|
|
JsonRpcStdioClient.ts
|
|
CodexAppServerSessionFactory.ts
|
|
codexAppServerDefaults.ts
|
|
```
|
|
|
|
What gets extracted:
|
|
|
|
- generic stdio JSON-RPC client
|
|
- generic initialize/initialized session bootstrap
|
|
- default request timeout values
|
|
- default suppressed notification configuration
|
|
|
|
What stays inside `recent-projects`:
|
|
|
|
- thread-list request logic
|
|
- recent-projects-specific source adapter
|
|
|
|
What stays inside `codex-account`:
|
|
|
|
- account request logic
|
|
- login lifecycle logic
|
|
- rate-limits logic
|
|
|
|
Important DRY rule:
|
|
|
|
- extract transport primitives
|
|
- do **not** create one giant "CodexService" that mixes unrelated product features
|
|
|
|
## Architecture Decision On Sources Of Truth
|
|
|
|
This feature only works if source-of-truth boundaries are explicit.
|
|
|
|
## Truth 1 - managed account truth
|
|
|
|
Source:
|
|
|
|
- `codex app-server account/read`
|
|
|
|
Used for:
|
|
|
|
- whether a managed ChatGPT account exists
|
|
- account email
|
|
- plan type
|
|
- account auth mode
|
|
|
|
## Truth 1a - field precedence inside managed account truth
|
|
|
|
The feature must not treat every app-server field as equally authoritative.
|
|
|
|
Precedence rules:
|
|
|
|
1. `account/read` owns steady-state identity:
|
|
- whether an account exists
|
|
- `account.type`
|
|
- `email`
|
|
- baseline `planType`
|
|
- `requiresOpenaiAuth`
|
|
2. `account/updated.authMode` may update an observed auth hint immediately, but must not by itself
|
|
create or delete a managed account.
|
|
3. `account/updated.planType` may refresh displayed subscription metadata when present, but if it
|
|
arrives as `null` or arrives without a known account, the feature should trigger a coalesced
|
|
follow-up `account/read` before changing steady-state account identity.
|
|
4. `account/rateLimits/read.planType` may corroborate subscription UI, but must not create account
|
|
presence or override a newer successful `account/read`.
|
|
5. explicit logout clears managed-account truth synchronously before any background refresh result
|
|
is accepted.
|
|
6. degraded reads may reuse last-known-good account truth only within the freshness window and only
|
|
if no explicit logout happened after that snapshot.
|
|
|
|
Implementation consequence:
|
|
|
|
- `planType` is a field with precedence and fallback rules
|
|
- it is not a standalone durable source of truth from notifications alone
|
|
|
|
## Truth 2 - API key truth
|
|
|
|
Source:
|
|
|
|
- existing app API key storage plus ambient env detection
|
|
|
|
Used for:
|
|
|
|
- whether the app can launch Codex using API key mode
|
|
- whether an app-managed OpenAI key is stored
|
|
- where the key comes from
|
|
|
|
## Truth 3 - execution truth
|
|
|
|
Source:
|
|
|
|
- `codex exec`
|
|
|
|
Used for:
|
|
|
|
- whether a real Codex run starts
|
|
- real failure or success at execution time
|
|
|
|
## Truth 4 - renderer shell status truth
|
|
|
|
Source:
|
|
|
|
- composed presentation model from:
|
|
- generic runtime provider status
|
|
- Codex account feature snapshot
|
|
|
|
Important design choice:
|
|
|
|
- we should **not** force all Codex account fields into `CliProviderStatus`
|
|
- `CliProviderStatus` remains the source of truth for:
|
|
- binary/runtime/backend/model status
|
|
- the feature snapshot remains the source of truth for:
|
|
- managed account
|
|
- preferred and effective auth mode
|
|
- launch readiness
|
|
- login lifecycle
|
|
- rate limits
|
|
|
|
The shell must compose these two bounded contexts at presentation time.
|
|
|
|
This is not "two conflicting sources of truth".
|
|
|
|
It is:
|
|
|
|
- one runtime status context
|
|
- one account status context
|
|
|
|
## Ownership Matrix
|
|
|
|
This table is the practical anti-bug contract for the feature.
|
|
|
|
| Concern | Canonical source | Owning layer | Persisted? | Cache TTL | Used by | Must not be inferred from |
|
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
| Codex binary path / binary installed | existing multimodel runtime status | shell runtime services | no | existing shell TTL | shell cards, launch gating | `codex-account` snapshot |
|
|
| Codex backend lane | existing runtime config `runtime.providerBackends.codex` | shell runtime services | yes | n/a | launch, status, provisioning | account auth state |
|
|
| Codex preferred auth mode | `providerConnections.codex.preferredAuthMode` | `codex-account` feature | yes | n/a | account panel, launch policy | `CliProviderStatus.authMethod` |
|
|
| Managed account presence | `account/read` | `codex-account` feature | no | 3-10s | account panel, readiness | local file parsing |
|
|
| Managed account email | `account/read` | `codex-account` feature | no | 3-10s | account panel | renderer local state |
|
|
| Managed account plan type | latest successful `account/read`; nullable refresh hints from `account/updated` and `account/rateLimits/read` | `codex-account` feature | no | 3-10s | subscription UI, rate limits | static plan assumptions or notification-only state |
|
|
| Requires OpenAI auth | `account/read.requiresOpenaiAuth` | `codex-account` feature | no | 3-10s | readiness messaging | provider id alone |
|
|
| API key availability | app secure storage plus ambient env detection | provider connection adapter | no | 0-3s | readiness, secondary badges | app-server account state |
|
|
| Effective auth mode for next launch | `EvaluateCodexLaunchReadinessUseCase` | `codex-account` feature | no | request-scoped | launch env builder | raw config alone |
|
|
| Login pending state | `CodexLoginSessionManager` | `codex-account` feature | no | live | UI pending panels | cached snapshot |
|
|
| Rate limits | `account/rateLimits/read` | `codex-account` feature | no | 30-60s | account detail panel | plan type alone |
|
|
| Final execution success / failure | `codex exec` | runtime execution lane | no | live | launch result UX | account snapshot |
|
|
|
|
Implementation rule:
|
|
|
|
- if a field is not canonical in this table, the layer may display it but must not own it
|
|
|
|
## Dependency Direction And Anti-Corruption Rules
|
|
|
|
To stay aligned with `FEATURE_ARCHITECTURE_STANDARD.md`, the new feature must preserve these
|
|
directions:
|
|
|
|
1. `renderer` depends on:
|
|
- `@features/codex-account/renderer`
|
|
- `@features/codex-account/contracts`
|
|
2. `preload` depends on:
|
|
- `@features/codex-account/contracts`
|
|
3. `main shell` depends on:
|
|
- `@features/codex-account/main`
|
|
4. `codex-account/core/*` depends only on:
|
|
- feature-local ports and domain models
|
|
5. `codex-account/main/infrastructure/*` may depend on:
|
|
- Electron
|
|
- child process / stdio
|
|
- shared shell env helpers
|
|
6. `recent-projects` must not become a transitive dependency of `codex-account`
|
|
|
|
Anti-corruption rule:
|
|
|
|
- the feature may adapt values out of `CliProviderStatus`
|
|
- it must not reshape its domain around `CliProviderStatus`
|
|
|
|
This is important because `CliProviderStatus` is a generic shell contract, not the Codex account
|
|
domain model.
|
|
|
|
## Dependency Enforcement Recommendations
|
|
|
|
The architecture standard already gives the broad rules. For this feature, we should make the most
|
|
important ones operational.
|
|
|
|
### Recommended import discipline
|
|
|
|
- shell code imports only:
|
|
- `@features/codex-account/main`
|
|
- `@features/codex-account/contracts`
|
|
- `@features/codex-account/renderer`
|
|
- `@features/codex-account/preload`
|
|
- tests may deep-import internals when needed
|
|
- production code outside the feature should not deep-import internal adapters, ports, or
|
|
infrastructure
|
|
|
|
### Recommended lint / review guardrails
|
|
|
|
Watch specifically for:
|
|
|
|
- `src/renderer/components/*` importing feature `main/*`
|
|
- feature `core/*` importing `electron`, child-process modules, or shell services
|
|
- unrelated features importing `codex-account/main/infrastructure/*`
|
|
- `recent-projects` becoming a transport owner again through back references
|
|
|
|
### Practical enforcement rule
|
|
|
|
If a shell file needs a new Codex-specific detail and that detail is not in the feature facade or
|
|
contracts yet:
|
|
|
|
- extend the facade or contracts
|
|
- do not bypass the boundary with a deep import
|
|
|
|
## Explicitly forbidden truth sources
|
|
|
|
- `~/.codex/auth.json`
|
|
- legacy OAuth state
|
|
- terminal output parsing as the steady-state account model
|
|
- `chatgptAuthTokens`
|
|
- stale renderer-only cached assumptions
|
|
|
|
## Domain Model
|
|
|
|
## Connection preference
|
|
|
|
New persisted preference:
|
|
|
|
- `auto`
|
|
- `chatgpt`
|
|
- `api_key`
|
|
|
|
Recommended config key:
|
|
|
|
- `providerConnections.codex.preferredAuthMode`
|
|
|
|
Important decision:
|
|
|
|
- do **not** reuse legacy `oauth` naming for Codex
|
|
|
|
Reason:
|
|
|
|
- `oauth` is legacy wording from the old implementation
|
|
- `chatgpt` maps much more clearly to the new managed-account seam
|
|
|
|
## Effective auth mode
|
|
|
|
Resolved per snapshot:
|
|
|
|
- `chatgpt`
|
|
- `api_key`
|
|
- `null`
|
|
|
|
This can differ from user preference when:
|
|
|
|
- preferred mode is `auto`
|
|
- one surface is unavailable
|
|
|
|
## Snapshot state
|
|
|
|
Recommended states:
|
|
|
|
- `runtime_missing`
|
|
- `checking`
|
|
- `not_connected`
|
|
- `managed_account_connected`
|
|
- `api_key_available`
|
|
- `both_available`
|
|
- `login_in_progress`
|
|
- `logout_in_progress`
|
|
- `degraded`
|
|
|
|
## Launch readiness
|
|
|
|
Recommended states:
|
|
|
|
- `ready_chatgpt`
|
|
- `ready_api_key`
|
|
- `ready_both`
|
|
- `warning_degraded_but_launchable`
|
|
- `missing_auth`
|
|
- `runtime_missing`
|
|
|
|
## Domain invariants
|
|
|
|
1. A managed ChatGPT account and an API key can both exist simultaneously.
|
|
2. API key presence must not erase managed account metadata.
|
|
3. Managed account presence must not erase API key availability.
|
|
4. Launch policy must resolve one effective auth mode per run.
|
|
5. Account metadata is display state, not secret state.
|
|
6. Login state is transient and process-owned.
|
|
7. A degraded app-server read must not automatically mean "logged out".
|
|
|
|
## State Resolution Matrix
|
|
|
|
This matrix is the fastest way to keep renderer, use cases, and runtime policy aligned.
|
|
|
|
| Binary available | Managed account | API key available | App-server health | Preferred auth | Snapshot state | Launch readiness | Effective auth mode | UI headline |
|
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
| no | any | any | any | any | `runtime_missing` | `runtime_missing` | `null` | Codex runtime missing |
|
|
| yes | no | no | healthy | auto | `not_connected` | `missing_auth` | `null` | Connect ChatGPT account or add API key |
|
|
| yes | yes | no | healthy | auto | `managed_account_connected` | `ready_chatgpt` | `chatgpt` | ChatGPT account connected |
|
|
| yes | no | yes | healthy | auto | `api_key_available` | `ready_api_key` | `api_key` | API key available |
|
|
| yes | yes | yes | healthy | auto | `both_available` | `ready_both` | `chatgpt` | ChatGPT account connected - API key also available |
|
|
| yes | yes | yes | healthy | chatgpt | `both_available` | `ready_chatgpt` | `chatgpt` | ChatGPT account preferred |
|
|
| yes | yes | yes | healthy | api_key | `both_available` | `ready_api_key` | `api_key` | API key preferred - ChatGPT account also connected |
|
|
| yes | yes | no | degraded | auto or chatgpt | `degraded` | `warning_degraded_but_launchable` | `chatgpt` | ChatGPT account detected - unable to fully verify right now |
|
|
| yes | no | yes | degraded | auto or api_key | `degraded` | `ready_api_key` | `api_key` | API key available - account status degraded |
|
|
| yes | no | no | degraded | auto | `degraded` | `missing_auth` or warning only if last good account still fresh | `null` or last-good-derived | Unable to verify Codex account state |
|
|
|
|
Interpretation rule:
|
|
|
|
- `snapshot state` is broader UX truth
|
|
- `launch readiness` is stricter execution truth
|
|
- they are related but must not be collapsed into one boolean
|
|
|
|
## Managed Workspace Restriction Policy
|
|
|
|
Official Codex auth/config docs allow administrators to set:
|
|
|
|
- `forced_login_method`
|
|
- `forced_chatgpt_workspace_id`
|
|
|
|
Docs also state that if active credentials do not match the configured restriction, Codex logs the
|
|
user out and exits.
|
|
|
|
### First-wave product policy
|
|
|
|
- do not add a workspace picker or workspace-switch UI in this feature
|
|
- do not pretend the app can resolve admin-managed workspace policy on the user's behalf
|
|
- do surface workspace restriction as a distinct normalized policy state when detected
|
|
|
|
### Expected UX treatment
|
|
|
|
If the installed Codex runtime is restricted to another ChatGPT workspace:
|
|
|
|
- do not show generic `Connect ChatGPT account` copy as the only explanation
|
|
- do not claim the subscription is simply missing
|
|
- show an admin-policy-oriented message such as:
|
|
- `This Codex installation is restricted to a different ChatGPT workspace`
|
|
|
|
### Architecture implication
|
|
|
|
Workspace restriction should be modeled as:
|
|
|
|
- a normalized error/policy category
|
|
- potentially `not_connected` or `missing_auth` at the raw launch-readiness layer
|
|
- but with policy-specific renderer messaging
|
|
|
|
This avoids exploding the core state machine while still keeping the UX honest.
|
|
|
|
## Config And Migration Plan
|
|
|
|
## New config shape
|
|
|
|
Update:
|
|
|
|
- `ConfigManager.ProviderConnectionsConfig`
|
|
- shared `AppConfig`
|
|
- config validation
|
|
- settings reset defaults
|
|
|
|
Recommended new shape:
|
|
|
|
```ts
|
|
providerConnections: {
|
|
anthropic: {
|
|
authMode: 'auto' | 'oauth' | 'api_key'
|
|
},
|
|
codex: {
|
|
preferredAuthMode: 'auto' | 'chatgpt' | 'api_key'
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration rules
|
|
|
|
On config read:
|
|
|
|
1. If `providerConnections.codex.preferredAuthMode` is missing:
|
|
- default to `auto`
|
|
2. If stale legacy `providerConnections.codex.authMode === 'oauth'`:
|
|
- migrate to `preferredAuthMode = 'chatgpt'`
|
|
3. If stale legacy `providerConnections.codex.authMode === 'api_key'`:
|
|
- migrate to `preferredAuthMode = 'api_key'`
|
|
4. If stale legacy `providerConnections.codex.authMode === 'auto'`:
|
|
- migrate to `preferredAuthMode = 'auto'`
|
|
5. Ignore `apiKeyBetaEnabled` completely after migration
|
|
6. Write back only the new shape
|
|
|
|
## Migration algorithm - exact behavior
|
|
|
|
This part needs to be exact because configuration drift is one of the easiest ways to create
|
|
hard-to-debug launch mismatches.
|
|
|
|
### Read-time normalization order
|
|
|
|
When config is loaded:
|
|
|
|
1. load raw JSON from disk
|
|
2. apply generic config defaults
|
|
3. normalize `runtime.providerBackends.codex`
|
|
4. normalize `providerConnections.codex`
|
|
5. validate the normalized shape
|
|
6. persist normalized config only if it changed materially
|
|
|
|
### Exact Codex connection normalization rules
|
|
|
|
Given `raw.providerConnections.codex`:
|
|
|
|
- if it is missing or not an object:
|
|
- replace with `{ preferredAuthMode: "auto" }`
|
|
- if `preferredAuthMode` exists and is one of:
|
|
- `auto`
|
|
- `chatgpt`
|
|
- `api_key`
|
|
- keep it
|
|
- otherwise, inspect stale keys in this order:
|
|
- `authMode === "oauth"` -> `preferredAuthMode = "chatgpt"`
|
|
- `authMode === "api_key"` -> `preferredAuthMode = "api_key"`
|
|
- `authMode === "auto"` -> `preferredAuthMode = "auto"`
|
|
- everything else -> `preferredAuthMode = "auto"`
|
|
|
|
Then:
|
|
|
|
- drop `authMode`
|
|
- drop `apiKeyBetaEnabled`
|
|
- drop any unknown Codex connection keys
|
|
|
|
### Backward-compatibility rule
|
|
|
|
Old configs must be:
|
|
|
|
- readable
|
|
- normalizable
|
|
- writable into the new shape
|
|
|
|
but they must not keep legacy Codex connection fields alive after one clean save cycle.
|
|
|
|
### Migration idempotency and corrupt-input hardening rules
|
|
|
|
Normalization must be safe under repeated reads and partially corrupted user config.
|
|
|
|
Rules:
|
|
|
|
1. repeated load -> normalize -> save cycles must converge to the same Codex connection subtree
|
|
2. non-object `providerConnections.codex` values must normalize to:
|
|
- `{ preferredAuthMode: "auto" }`
|
|
3. malformed or unknown Codex connection keys must not block app startup
|
|
4. already-normalized config should not be rewritten just because the normalizer ran again
|
|
5. backup/restore and import paths must reuse the same Codex normalizer instead of re-implementing
|
|
migration logic separately
|
|
|
|
Practical consequence:
|
|
|
|
- migration is a deterministic cleanup step, not an ongoing source of config churn
|
|
|
|
### Migration safety rule
|
|
|
|
Config migration must never infer:
|
|
|
|
- "user prefers API key" merely because an API key exists
|
|
- "user prefers ChatGPT" merely because a managed account exists
|
|
|
|
Preference is persisted user intent. Availability is runtime-observed fact. They must remain
|
|
distinct.
|
|
|
|
## Validation rules
|
|
|
|
`configValidation` must:
|
|
|
|
- accept only `preferredAuthMode` under `providerConnections.codex`
|
|
- accept values:
|
|
- `auto`
|
|
- `chatgpt`
|
|
- `api_key`
|
|
- tolerate stale legacy keys during migration path only if they are normalized before persistence
|
|
|
|
## Critical Runtime Policy
|
|
|
|
This is the highest-risk part of the feature.
|
|
|
|
### Why current policy is wrong
|
|
|
|
Today Codex launch policy effectively means:
|
|
|
|
- if no `OPENAI_API_KEY` or `CODEX_API_KEY`, Codex launch is treated as not ready
|
|
|
|
That is incompatible with the desired product once ChatGPT-managed auth comes back.
|
|
|
|
### New launch policy
|
|
|
|
The feature must own launch-readiness evaluation.
|
|
|
|
Inputs:
|
|
|
|
- Codex binary availability
|
|
- managed account presence
|
|
- API key availability
|
|
- preferred auth mode
|
|
- app-server health
|
|
|
|
Outputs:
|
|
|
|
- launch readiness state
|
|
- effective auth mode
|
|
- env mutation instructions
|
|
- user-facing advisory message
|
|
|
|
### Policy rules
|
|
|
|
1. If preferred mode is `chatgpt` and managed account exists:
|
|
- launch is ready
|
|
- effective auth mode is `chatgpt`
|
|
2. If preferred mode is `api_key` and API key exists:
|
|
- launch is ready
|
|
- effective auth mode is `api_key`
|
|
3. If preferred mode is `auto`:
|
|
- prefer ChatGPT when managed account exists
|
|
- otherwise use API key when available
|
|
- otherwise missing-auth
|
|
4. If app-server is degraded:
|
|
- do not hard-fail launch automatically unless there is explicit evidence auth is absent
|
|
- return warning-level degraded readiness where appropriate
|
|
|
|
### Why app-server degradation must not hard-block launch
|
|
|
|
`codex app-server` is an account control-plane seam.
|
|
|
|
It is **not** the execution seam.
|
|
|
|
A transient app-server issue must not cause the app to say:
|
|
|
|
- "Codex cannot launch"
|
|
|
|
when `codex exec` itself could still work.
|
|
|
|
That would create a false negative and a serious product bug.
|
|
|
|
## Launch Policy Decision Table
|
|
|
|
This table should directly drive the implementation of
|
|
`EvaluateCodexLaunchReadinessUseCase`.
|
|
|
|
| Preferred auth | Managed account detected | API key available | App-server degraded | Resulting readiness | Effective auth | Required exec env policy | User-facing message |
|
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
| `chatgpt` | yes | any | no | `ready_chatgpt` | `chatgpt` | strip API keys, `forced_login_method="chatgpt"` | Launch using ChatGPT account |
|
|
| `chatgpt` | yes | any | yes | `warning_degraded_but_launchable` | `chatgpt` | strip API keys, `forced_login_method="chatgpt"` | ChatGPT account detected - verification degraded |
|
|
| `chatgpt` | no | yes | no | `missing_auth` | `null` | no launch | Preferred ChatGPT account is not connected |
|
|
| `chatgpt` | no | no | no | `missing_auth` | `null` | no launch | Connect a ChatGPT account to use the selected auth mode |
|
|
| `api_key` | any | yes | any | `ready_api_key` | `api_key` | inject key, `forced_login_method="api"` | Launch using API key |
|
|
| `api_key` | any | no | any | `missing_auth` | `null` | no launch | Add an API key to use the selected auth mode |
|
|
| `auto` | yes | yes | no | `ready_both` | `chatgpt` | strip API keys, `forced_login_method="chatgpt"` | Auto selected ChatGPT account |
|
|
| `auto` | yes | no | no | `ready_chatgpt` | `chatgpt` | strip API keys, `forced_login_method="chatgpt"` | Auto selected ChatGPT account |
|
|
| `auto` | no | yes | no | `ready_api_key` | `api_key` | inject key, `forced_login_method="api"` | Auto selected API key |
|
|
| `auto` | yes | yes | yes | `warning_degraded_but_launchable` | `chatgpt` | strip API keys, `forced_login_method="chatgpt"` | Auto selected ChatGPT account - account verification degraded |
|
|
| `auto` | no | yes | yes | `ready_api_key` | `api_key` | inject key, `forced_login_method="api"` | Auto selected API key - account verification degraded |
|
|
| `auto` | no | no | yes | `missing_auth` unless last-good account freshness rule applies | `null` | no launch | Unable to verify Codex authentication |
|
|
|
|
Important interpretation:
|
|
|
|
- degraded app-server state does not grant permission to guess a missing managed account forever
|
|
- the only acceptable degraded-launch case is when there is positive current or sufficiently fresh
|
|
prior evidence that the managed account exists
|
|
|
|
## Core Algorithm Sketches
|
|
|
|
These sketches are intentionally close to implementation logic so that multiple contributors do not
|
|
invent subtly different policies.
|
|
|
|
### Snapshot merge algorithm - pseudocode
|
|
|
|
```ts
|
|
function buildCodexSnapshot(input: {
|
|
binaryAvailable: boolean;
|
|
preferredAuthMode: 'auto' | 'chatgpt' | 'api_key';
|
|
managedAccountResult:
|
|
| { kind: 'success'; account: ManagedAccount | null; requiresOpenaiAuth: boolean }
|
|
| { kind: 'degraded'; reason: string };
|
|
apiKeyAvailability: ApiKeyAvailability;
|
|
loginState: LoginState;
|
|
lastKnownGoodManagedAccount: ManagedAccount | null;
|
|
lastKnownGoodObservedAt: number | null;
|
|
now: number;
|
|
freshnessWindowMs: number;
|
|
}): CodexConnectionSnapshot {
|
|
if (!input.binaryAvailable) {
|
|
return runtimeMissingSnapshot(input.preferredAuthMode, input.loginState, input.now);
|
|
}
|
|
|
|
const managedContext = resolveManagedAccountContext({
|
|
managedAccountResult: input.managedAccountResult,
|
|
lastKnownGoodManagedAccount: input.lastKnownGoodManagedAccount,
|
|
lastKnownGoodObservedAt: input.lastKnownGoodObservedAt,
|
|
now: input.now,
|
|
freshnessWindowMs: input.freshnessWindowMs,
|
|
});
|
|
|
|
return mergeManagedAndApiKeyTruth({
|
|
preferredAuthMode: input.preferredAuthMode,
|
|
managedContext,
|
|
apiKeyAvailability: input.apiKeyAvailability,
|
|
loginState: input.loginState,
|
|
now: input.now,
|
|
});
|
|
}
|
|
```
|
|
|
|
### Launch readiness algorithm - pseudocode
|
|
|
|
```ts
|
|
function evaluateLaunchReadiness(snapshot: CodexConnectionSnapshot): CodexLaunchReadiness {
|
|
if (!snapshot.binaryAvailable) {
|
|
return { state: 'runtime_missing', effectiveAuthMode: null, message: 'Codex runtime missing' };
|
|
}
|
|
|
|
switch (snapshot.preferredAuthMode) {
|
|
case 'chatgpt':
|
|
if (snapshot.managedAccount?.type === 'chatgpt') {
|
|
return snapshot.state === 'degraded'
|
|
? degradedChatgptReady()
|
|
: chatgptReady();
|
|
}
|
|
return missingChatgptAuth();
|
|
|
|
case 'api_key':
|
|
return snapshot.apiKey.available ? apiKeyReady() : missingApiKeyAuth();
|
|
|
|
case 'auto':
|
|
if (snapshot.managedAccount?.type === 'chatgpt') {
|
|
return snapshot.state === 'degraded'
|
|
? degradedChatgptReady()
|
|
: (snapshot.apiKey.available ? readyBoth() : chatgptReady());
|
|
}
|
|
if (snapshot.apiKey.available) {
|
|
return apiKeyReady();
|
|
}
|
|
return missingAnyAuth();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Account event reconciliation algorithm - pseudocode
|
|
|
|
```ts
|
|
function onAccountUpdated(event: { authMode: AuthMode | null; planType: PlanType | null }) {
|
|
cache.lastObservedAuthMode = event.authMode ?? cache.lastObservedAuthMode ?? null;
|
|
|
|
if (event.planType !== null) {
|
|
cache.lastObservedPlanTypeHint = event.planType;
|
|
}
|
|
|
|
scheduleCoalescedSnapshotRefresh('account-updated');
|
|
}
|
|
```
|
|
|
|
Critical rule:
|
|
|
|
- notification handlers must not clear `managedAccount` only because `authMode` or `planType`
|
|
arrives as `null`
|
|
- destructive transitions belong to explicit logout handling and successful follow-up `account/read`
|
|
results
|
|
|
|
### Exec env mutation algorithm - pseudocode
|
|
|
|
```ts
|
|
function buildExecEnv(baseEnv: Env, readiness: CodexLaunchReadiness, apiKey: string | null): Env {
|
|
const env = { ...baseEnv };
|
|
|
|
if (readiness.effectiveAuthMode === 'chatgpt') {
|
|
delete env.OPENAI_API_KEY;
|
|
delete env.CODEX_API_KEY;
|
|
env.CODEX_FORCED_LOGIN_METHOD = 'chatgpt';
|
|
return env;
|
|
}
|
|
|
|
if (readiness.effectiveAuthMode === 'api_key') {
|
|
if (apiKey) {
|
|
env.OPENAI_API_KEY = apiKey;
|
|
env.CODEX_API_KEY = apiKey;
|
|
}
|
|
env.CODEX_FORCED_LOGIN_METHOD = 'api';
|
|
return env;
|
|
}
|
|
|
|
return env;
|
|
}
|
|
```
|
|
|
|
Implementation note:
|
|
|
|
- the real implementation should pass `forced_login_method` via Codex config override arguments, not
|
|
invent a new environment variable contract
|
|
- the pseudocode uses a symbolic field only to make the decision process readable
|
|
|
|
## Extremely important env semantics
|
|
|
|
This section is subtle and non-optional.
|
|
|
|
### Problem 1 - ambient API keys can poison managed-account autodetect
|
|
|
|
If an app-server child process inherits:
|
|
|
|
- `OPENAI_API_KEY`
|
|
- `CODEX_API_KEY`
|
|
|
|
then account reads may reflect API-key auth instead of the managed ChatGPT account the user expects to see.
|
|
|
|
### Problem 2 - execution can silently use the wrong auth surface
|
|
|
|
If ChatGPT mode is selected but execution still inherits:
|
|
|
|
- `OPENAI_API_KEY`
|
|
- `CODEX_API_KEY`
|
|
|
|
then the run may silently go through API-key auth.
|
|
|
|
That creates the worst possible bug:
|
|
|
|
- UI says subscription is active
|
|
- but runtime actually bills via API key
|
|
|
|
### Required env policy
|
|
|
|
For **managed-account control-plane sessions**:
|
|
|
|
- sanitize `OPENAI_API_KEY`
|
|
- sanitize `CODEX_API_KEY`
|
|
- do not set `forced_login_method`
|
|
- preserve the same resolved auth storage context as execution
|
|
|
|
Reason:
|
|
|
|
- control-plane reads should discover unbiased managed-account truth
|
|
|
|
For **execution sessions in `chatgpt` mode**:
|
|
|
|
- sanitize `OPENAI_API_KEY`
|
|
- sanitize `CODEX_API_KEY`
|
|
- pass `-c forced_login_method="chatgpt"`
|
|
|
|
For **execution sessions in `api_key` mode**:
|
|
|
|
- inject the resolved API key env
|
|
- pass `-c forced_login_method="api"`
|
|
|
|
For **execution sessions in `auto -> chatgpt` resolution**:
|
|
|
|
- same as `chatgpt`
|
|
|
|
For **execution sessions in `auto -> api_key` resolution**:
|
|
|
|
- same as `api_key`
|
|
|
|
### Shared auth storage context rule
|
|
|
|
`account/read`, login, logout, and `codex exec` must use the same resolved:
|
|
|
|
- `HOME`
|
|
- `USERPROFILE`
|
|
- `CODEX_HOME`
|
|
|
|
or the UI can observe one auth store while execution uses another.
|
|
|
|
That would create a second class of severe bug:
|
|
|
|
- UI sees a logged-in account
|
|
- but `codex exec` runs against a different auth store and fails
|
|
|
|
So the feature must centralize Codex env resolution.
|
|
|
|
## Platform-Specific Runtime Notes
|
|
|
|
This feature is cross-process and cross-platform enough that platform rules should be explicit.
|
|
|
|
### Auth-store env precedence
|
|
|
|
Recommended precedence for auth-store-related env resolution:
|
|
|
|
1. explicit per-call overrides provided by the app
|
|
2. resolved interactive shell env
|
|
3. process env fallback
|
|
|
|
### `HOME`, `USERPROFILE`, `CODEX_HOME`
|
|
|
|
Rules:
|
|
|
|
- always resolve a single canonical auth root
|
|
- materialize both `HOME` and `USERPROFILE` consistently to avoid child-process drift
|
|
- preserve an explicit `CODEX_HOME` if one exists
|
|
- do not silently invent different auth roots for app-server vs exec
|
|
|
|
Diagnostics rule:
|
|
|
|
- when available, compare the app's resolved auth root with `initialize.codexHome`
|
|
- treat disagreement as a first-class diagnostic signal, not as a silent implementation detail
|
|
|
|
### Windows nuance
|
|
|
|
On Windows, child processes may consult `USERPROFILE` even when `HOME` is also set.
|
|
|
|
Implementation implication:
|
|
|
|
- the env builder should not treat `HOME` alone as sufficient on Windows-capable flows
|
|
|
|
### macOS and Linux nuance
|
|
|
|
On Unix-like systems, `HOME` is usually the primary root.
|
|
|
|
Implementation implication:
|
|
|
|
- if `USERPROFILE` is absent, we should still fill it consistently once we have a canonical root so
|
|
both app-server and exec see the same store contract
|
|
|
|
### Browser-launch nuance
|
|
|
|
Desktop login should use the Electron/browser-launch adapter, not shell command guessing.
|
|
|
|
Implementation implication:
|
|
|
|
- browser opening belongs to the feature infrastructure adapter
|
|
- it should not be scattered across banner/section/dialog components
|
|
|
|
### PATH and shell env nuance
|
|
|
|
The feature should inherit enough environment to find the Codex binary and preserve normal shell
|
|
behavior, but auth-critical env values must still be rewritten deterministically.
|
|
|
|
Implementation implication:
|
|
|
|
- preserve normal shell-derived env where safe
|
|
- sanitize only the auth-critical variables the policy explicitly owns
|
|
|
|
## Concurrency, Ordering, And Race Policy
|
|
|
|
This section is necessary because the feature will combine:
|
|
|
|
- cached reads
|
|
- explicit refresh
|
|
- login notifications
|
|
- logout actions
|
|
- shell status refreshes
|
|
|
|
Without a strict ordering contract, the UI can easily regress into stale or contradictory state.
|
|
|
|
### Snapshot sequencing rule
|
|
|
|
Every refresh path should carry:
|
|
|
|
- `requestId`
|
|
- `startedAt`
|
|
- `observedAt`
|
|
|
|
The feature should only publish a snapshot if it is newer than the last settled snapshot for the
|
|
same source epoch.
|
|
|
|
Practical rule:
|
|
|
|
- a slow degraded read must not overwrite a newer successful read
|
|
- a pre-login cached snapshot must not overwrite a post-login snapshot
|
|
- a pre-logout cached snapshot must not overwrite a post-logout snapshot
|
|
|
|
### Single-flight rule
|
|
|
|
Passive snapshot refreshes from:
|
|
|
|
- dashboard banner
|
|
- settings dialog
|
|
- provider card refresh
|
|
|
|
must collapse into one in-flight promise per main-process feature instance.
|
|
|
|
### Login lifecycle exclusivity rule
|
|
|
|
At most one login session may be live at a time.
|
|
|
|
If the user clicks login repeatedly:
|
|
|
|
- return the current pending login state
|
|
- do not start parallel app-server login sessions
|
|
|
|
### Renderer subscription rule
|
|
|
|
Renderer hooks must treat `snapshot-updated` and `login-state-changed` as additive feature events,
|
|
not as implicit replacement for shell runtime status.
|
|
|
|
This avoids one subtle class of bug:
|
|
|
|
- account snapshot event arrives
|
|
- shell status is still loading
|
|
- UI accidentally treats feature snapshot as full provider status
|
|
|
|
### Cache invalidation rule
|
|
|
|
Invalidate snapshot cache immediately on:
|
|
|
|
- login start
|
|
- login completion
|
|
- logout success
|
|
- preferred auth mode change
|
|
- explicit manual refresh
|
|
|
|
Do not wait for TTL expiry after user-initiated auth transitions.
|
|
|
|
## Freshness Window And Last-Known-Good Policy
|
|
|
|
This is intentionally separated from generic cache policy because it affects safety semantics, not
|
|
just performance.
|
|
|
|
### Recommended default
|
|
|
|
- keep a `lastKnownGoodManagedAccountSnapshot`
|
|
- keep a `lastKnownGoodObservedAt`
|
|
- use a default freshness window around `60 seconds`
|
|
|
|
Why not longer by default:
|
|
|
|
- too long and the app starts lying after real logout or auth expiry
|
|
|
|
Why not shorter by default:
|
|
|
|
- too short and transient app-server failures collapse the UX into false logout too easily
|
|
|
|
### What may be carried forward
|
|
|
|
During a degraded control-plane read, the feature may carry forward only:
|
|
|
|
- managed account presence
|
|
- managed account email
|
|
- managed account plan type
|
|
- effective auth mode only if it was derived from the managed account path
|
|
|
|
### What must be recomputed fresh
|
|
|
|
The feature must recompute or re-read fresh:
|
|
|
|
- API key availability
|
|
- binary/runtime availability
|
|
- current preferred auth mode from config
|
|
- login pending state
|
|
- logout in progress state
|
|
|
|
### What must never be carried forward
|
|
|
|
Do not carry forward:
|
|
|
|
- pending login state
|
|
- failed login state
|
|
- logout in progress
|
|
- rate limit snapshots beyond their own TTL
|
|
- a degraded state as if it were a successful state
|
|
|
|
### Freshness expiration rule
|
|
|
|
Once the freshness window expires:
|
|
|
|
- the feature may still show `degraded`
|
|
- but it must stop treating stale managed-account evidence as sufficient for launchability
|
|
|
|
### Post-logout rule
|
|
|
|
After explicit logout success:
|
|
|
|
- clear the last-known-good managed-account snapshot immediately
|
|
- do not allow degraded reads to resurrect the old account state
|
|
|
|
## Feature Data Contracts
|
|
|
|
## DTOs
|
|
|
|
### `CodexConnectionSnapshotDto`
|
|
|
|
Fields:
|
|
|
|
- `state`
|
|
- `preferredAuthMode`
|
|
- `effectiveAuthMode`
|
|
- `binaryAvailable`
|
|
- `requiresOpenaiAuth`
|
|
- `managedAccount`
|
|
- `apiKey`
|
|
- `launchReadiness`
|
|
- `degradedReason`
|
|
- `login`
|
|
- `observedAt`
|
|
|
|
### `CodexManagedAccountDto`
|
|
|
|
Fields:
|
|
|
|
- `type: "chatgpt" | "apiKey" | null`
|
|
- `email?: string | null`
|
|
- `planType?: "free" | "go" | "plus" | "pro" | "team" | "business" | "enterprise" | "edu" | "unknown" | null`
|
|
|
|
Important note:
|
|
|
|
- we are not planning to use app-server `apiKey` login mode, but the DTO should still model it defensively because the protocol supports it
|
|
|
|
### `CodexApiKeyAvailabilityDto`
|
|
|
|
Fields:
|
|
|
|
- `available: boolean`
|
|
- `source: "stored" | "environment" | null`
|
|
- `label?: string | null`
|
|
|
|
### `CodexLaunchReadinessDto`
|
|
|
|
Fields:
|
|
|
|
- `state`
|
|
- `message`
|
|
- `effectiveAuthMode`
|
|
|
|
### `CodexLoginStateDto`
|
|
|
|
Fields:
|
|
|
|
- `state: "idle" | "starting" | "awaiting_browser" | "pending" | "completed" | "cancelled" | "failed"`
|
|
- `loginId?: string | null`
|
|
- `message?: string | null`
|
|
|
|
Sensitive-field rule:
|
|
|
|
- `loginId` is process-lifecycle metadata and should not be rendered to the user
|
|
- renderer-facing contracts may omit `loginId` entirely if cancellation and status updates can be
|
|
driven by main-owned session state
|
|
- raw `authUrl` must never appear in renderer-facing DTOs
|
|
|
|
### `CodexRateLimitsDto`
|
|
|
|
Fields:
|
|
|
|
- `rateLimits`
|
|
- `rateLimitsByLimitId?`
|
|
- `planType?`
|
|
- `observedAt`
|
|
|
|
## Event contract
|
|
|
|
Recommended event union:
|
|
|
|
- `snapshot-updated`
|
|
- `login-state-changed`
|
|
- `rate-limits-updated`
|
|
- `degraded`
|
|
|
|
These should be emitted over one feature event channel and consumed by renderer hooks.
|
|
|
|
## DTO And Event Shape Examples
|
|
|
|
The plan should include concrete examples so that main, preload, and renderer do not each invent
|
|
their own interpretation.
|
|
|
|
### Example `CodexConnectionSnapshotDto`
|
|
|
|
```json
|
|
{
|
|
"state": "both_available",
|
|
"preferredAuthMode": "auto",
|
|
"effectiveAuthMode": "chatgpt",
|
|
"binaryAvailable": true,
|
|
"requiresOpenaiAuth": true,
|
|
"managedAccount": {
|
|
"type": "chatgpt",
|
|
"email": "user@example.com",
|
|
"planType": "pro"
|
|
},
|
|
"apiKey": {
|
|
"available": true,
|
|
"source": "stored",
|
|
"label": "Stored in app"
|
|
},
|
|
"launchReadiness": {
|
|
"state": "ready_both",
|
|
"message": "ChatGPT account connected - API key also available",
|
|
"effectiveAuthMode": "chatgpt"
|
|
},
|
|
"degradedReason": null,
|
|
"login": {
|
|
"state": "idle",
|
|
"loginId": null,
|
|
"message": null
|
|
},
|
|
"observedAt": 1776640000000
|
|
}
|
|
```
|
|
|
|
### Example degraded snapshot
|
|
|
|
```json
|
|
{
|
|
"state": "degraded",
|
|
"preferredAuthMode": "auto",
|
|
"effectiveAuthMode": "chatgpt",
|
|
"binaryAvailable": true,
|
|
"requiresOpenaiAuth": true,
|
|
"managedAccount": {
|
|
"type": "chatgpt",
|
|
"email": "user@example.com",
|
|
"planType": "pro"
|
|
},
|
|
"apiKey": {
|
|
"available": false,
|
|
"source": null,
|
|
"label": null
|
|
},
|
|
"launchReadiness": {
|
|
"state": "warning_degraded_but_launchable",
|
|
"message": "ChatGPT account detected - verification degraded",
|
|
"effectiveAuthMode": "chatgpt"
|
|
},
|
|
"degradedReason": "app-server-timeout",
|
|
"login": {
|
|
"state": "idle",
|
|
"loginId": null,
|
|
"message": null
|
|
},
|
|
"observedAt": 1776640005000
|
|
}
|
|
```
|
|
|
|
### Example `login-state-changed` event
|
|
|
|
```json
|
|
{
|
|
"type": "login-state-changed",
|
|
"payload": {
|
|
"state": "pending",
|
|
"message": "Waiting for ChatGPT browser login to complete"
|
|
},
|
|
"observedAt": 1776640002000
|
|
}
|
|
```
|
|
|
|
### Example `snapshot-updated` event
|
|
|
|
```json
|
|
{
|
|
"type": "snapshot-updated",
|
|
"payload": {
|
|
"state": "managed_account_connected",
|
|
"preferredAuthMode": "chatgpt",
|
|
"effectiveAuthMode": "chatgpt"
|
|
},
|
|
"observedAt": 1776640008000
|
|
}
|
|
```
|
|
|
|
Contract rule:
|
|
|
|
- event payloads may be smaller than full DTOs
|
|
- snapshot read methods must still return the full DTO
|
|
- renderer must not assume an event payload is a complete replacement snapshot unless the contract
|
|
explicitly says so
|
|
|
|
Sensitive-field containment rule:
|
|
|
|
- `authUrl` must never be emitted over the feature event channel
|
|
- incremental events should not include full account email unless a full snapshot read is actually
|
|
required for UI rendering
|
|
- `loginId` should stay main-owned unless there is a concrete renderer need that cannot be solved by
|
|
process-owned cancel/status APIs
|
|
|
|
## Error Normalization Matrix
|
|
|
|
The feature should normalize raw transport/process failures into stable categories so renderer copy
|
|
and metrics stay coherent.
|
|
|
|
| Raw failure family | Normalized category | Typical feature impact | UI treatment |
|
|
| --- | --- | --- | --- |
|
|
| app-server initialize timeout | `app-server-timeout` | degraded snapshot or failed login start | degraded / retryable message |
|
|
| app-server process spawn failure | `app-server-unavailable` | degraded snapshot or hard login failure | binary/runtime dependent messaging |
|
|
| app-server initialize succeeds but required stable account surface is unavailable | `app-server-incompatible` | feature hidden/locked or hard login failure | update-runtime / incompatible-runtime messaging |
|
|
| login returned unsafe or unsupported browser URL | `unsafe-auth-url` | login fails before browser open | explicit security-oriented error, no open attempt |
|
|
| browser open failure | `browser-open-failed` | login failed | explicit action error |
|
|
| login cancelled by user | `login-cancelled` | login state settles to cancelled | non-destructive informational state |
|
|
| login completed with error | `login-failed` | login failed, snapshot refresh follows | explicit error |
|
|
| logout RPC failure | `logout-failed` | logout stays unresolved, snapshot preserved | explicit error |
|
|
| admin-managed workspace or login policy rejects current account | `workspace-restricted` | login blocked or account cleared by Codex policy | policy-specific guidance, not generic auth-missing |
|
|
| app restarted or shut down while login was pending | `login-session-lost` | pending login abandoned, fresh snapshot required on next startup | informational recovery message, settle to idle |
|
|
| rate-limits read failure | `rate-limits-unavailable` | rate-limit panel degraded only | non-blocking warning |
|
|
| stale result received after newer state | `stale-result-ignored` | no user-visible state change | debug-level logging only |
|
|
|
|
Normalization rule:
|
|
|
|
- renderer copy should key off normalized categories
|
|
- raw stderr / transport text may be attached for diagnostics, but should not drive UX wording
|
|
|
|
## Event Ordering And Delivery Rules
|
|
|
|
These rules matter because most user-visible bugs in this feature will come from correct data
|
|
arriving in the wrong order.
|
|
|
|
### Preferred event ordering
|
|
|
|
For login success:
|
|
|
|
1. `login-state-changed: starting`
|
|
2. `login-state-changed: awaiting_browser`
|
|
3. `login-state-changed: pending`
|
|
4. app-server `account/login/completed success=true`
|
|
5. `login-state-changed: completed`
|
|
6. forced snapshot refresh
|
|
7. `snapshot-updated`
|
|
8. optional `rate-limits-updated` after explicit or lazy read
|
|
9. `login-state-changed: idle` once settled
|
|
|
|
For logout success:
|
|
|
|
1. local logout action enters pending state
|
|
2. `account/logout`
|
|
3. clear last-known-good managed-account snapshot
|
|
4. forced snapshot refresh
|
|
5. `snapshot-updated`
|
|
6. local logout pending state clears
|
|
|
|
### Delivery rule
|
|
|
|
Feature event delivery should be best-effort and additive:
|
|
|
|
- missing one event must not make the renderer permanently stale
|
|
- the renderer must always be able to recover by reading a fresh snapshot
|
|
|
|
### Idempotency rule
|
|
|
|
Renderer and main-side subscribers should tolerate:
|
|
|
|
- duplicate `login-state-changed`
|
|
- duplicate `snapshot-updated`
|
|
- late degraded events that are older than the currently rendered snapshot
|
|
|
|
### Staleness rejection rule
|
|
|
|
If an incoming event or refresh result is older than the settled snapshot already in memory:
|
|
|
|
- ignore it
|
|
- log a low-level diagnostic if useful
|
|
|
|
Do not let older results reanimate older UI state.
|
|
|
|
## Cross-Window And Subscriber Coherence Rules
|
|
|
|
The main process must remain the single owner of mutable Codex account state.
|
|
|
|
### Main-process ownership rule
|
|
|
|
The following state must live in one main-process feature instance, not in renderer-local stores:
|
|
|
|
- latest settled snapshot
|
|
- last-known-good managed account snapshot
|
|
- active login session state
|
|
- rate-limit cache
|
|
- compatibility verdict for the installed app-server seam
|
|
|
|
### Renderer subscription rule
|
|
|
|
Any renderer may subscribe late, unsubscribe early, or restart independently.
|
|
|
|
Therefore:
|
|
|
|
- late subscribers must bootstrap from `getSnapshot()` and not rely on having seen past events
|
|
- feature events are accelerators, not the only source of truth
|
|
- one renderer closing must not cancel a login session started by another renderer
|
|
|
|
### Broadcast rule
|
|
|
|
If multiple renderer surfaces are open at once:
|
|
|
|
- all should receive the same normalized feature events from the same main-process owner
|
|
- no renderer should start a second login flow just because it missed an earlier local UI state
|
|
|
|
### Shutdown rule
|
|
|
|
If the last renderer unsubscribes while a login is pending:
|
|
|
|
- the main-process feature may keep the login session alive until:
|
|
- completion
|
|
- explicit cancel
|
|
- timeout
|
|
- app shutdown
|
|
|
|
This avoids coupling correctness to whichever window happened to open the flow.
|
|
|
|
## App Restart, Crash, And Pending Login Recovery Policy
|
|
|
|
Login session state is explicitly process-owned, not durable product state.
|
|
|
|
### Startup recovery rule
|
|
|
|
On app startup:
|
|
|
|
- do not restore a previously pending login from persisted config or renderer state
|
|
- initialize login state as `idle`
|
|
- perform a fresh snapshot read to determine the actual steady state
|
|
|
|
### Shutdown rule
|
|
|
|
On app shutdown while login is pending:
|
|
|
|
- do not block app quit waiting for remote login completion
|
|
- a best-effort `account/login/cancel` is optional, but correctness must not depend on it
|
|
- local session state should settle as lost/abandoned for diagnostics only
|
|
|
|
### Crash/reload rule
|
|
|
|
If the renderer reloads or the app restarts during login:
|
|
|
|
- the next session should not show a phantom permanently pending state
|
|
- the feature should converge via fresh `account/read`, not by trying to resume an old `loginId`
|
|
|
|
### Persistence rule
|
|
|
|
Do not persist:
|
|
|
|
- pending login state
|
|
- `loginId`
|
|
- `authUrl`
|
|
|
|
Persist only durable user intent:
|
|
|
|
- preferred auth mode
|
|
|
|
## Operation Serialization And Race Resolution Policy
|
|
|
|
The feature must treat auth mutations as serialized control-plane operations, not as unrelated UI
|
|
button handlers.
|
|
|
|
### Serialization rule
|
|
|
|
Serialize these operations through one main-process account-operation gate:
|
|
|
|
- start login
|
|
- cancel login
|
|
- logout
|
|
- explicit auth-recovery refresh that escalates to `refreshToken = true`
|
|
|
|
Passive reads may coalesce, but mutating operations must not run in parallel.
|
|
|
|
### Race resolution rule
|
|
|
|
If mutation intent and observed remote completion disagree, final truth comes from the freshest
|
|
successful post-mutation snapshot, not from the earlier button click alone.
|
|
|
|
### Required race outcomes
|
|
|
|
1. cancel requested, then late `login/completed success=true` arrives:
|
|
- do a forced snapshot refresh
|
|
- if the snapshot shows a connected managed account, show connected state
|
|
- do not force logout just to honor the earlier cancel intent
|
|
2. logout requested during pending login:
|
|
- best-effort cancel login first if needed
|
|
- then run logout
|
|
- final logged-out truth must win over any stale pre-logout account evidence
|
|
3. stale read started before logout settles after logout:
|
|
- ignore the stale read result
|
|
- never resurrect the old managed account from that stale result
|
|
|
|
### Preference-change rule
|
|
|
|
Changing preferred auth mode during a pending login affects future launch choice only.
|
|
|
|
It must not:
|
|
|
|
- silently cancel an in-flight login unless product explicitly chooses that UX later
|
|
- rewrite the meaning of a login session that already started under the prior preference
|
|
|
|
## Protocol Assumptions We Intentionally Avoid Relying On
|
|
|
|
The feature should stay robust even if some secondary protocol details vary.
|
|
|
|
Do not rely on:
|
|
|
|
- `account/updated` always arriving before or after `account/login/completed`
|
|
- rate-limit notifications being delivered during every app lifecycle
|
|
- undocumented auth variants being available in every Codex build
|
|
- raw CLI reference pages being the only authoritative source for auth override details
|
|
- exact raw transport error text remaining stable enough for renderer copy
|
|
|
|
Instead, rely on:
|
|
|
|
- explicit snapshot refresh for steady-state truth
|
|
- normalized error categories
|
|
- narrow, documented auth variants for first-wave behavior
|
|
|
|
## IPC channels
|
|
|
|
Recommended channels:
|
|
|
|
- `CODEX_ACCOUNT_GET_SNAPSHOT`
|
|
- `CODEX_ACCOUNT_REFRESH_SNAPSHOT`
|
|
- `CODEX_ACCOUNT_START_CHATGPT_LOGIN`
|
|
- `CODEX_ACCOUNT_CANCEL_LOGIN`
|
|
- `CODEX_ACCOUNT_LOGOUT`
|
|
- `CODEX_ACCOUNT_GET_RATE_LIMITS`
|
|
- `CODEX_ACCOUNT_EVENT`
|
|
|
|
## IPC Request / Response Matrix
|
|
|
|
This matrix keeps the cross-process contract explicit and prevents renderer/main drift.
|
|
|
|
| Channel | Request shape | Response shape | Side effects | Cache interaction | Failure shape |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| `CODEX_ACCOUNT_GET_SNAPSHOT` | `{ forceFresh?: boolean }` or no payload | `CodexConnectionSnapshotDto` | none | may serve cached snapshot unless `forceFresh` | returns rejected IPC promise with normalized error message |
|
|
| `CODEX_ACCOUNT_REFRESH_SNAPSHOT` | no payload | `CodexConnectionSnapshotDto` | forces control-plane refresh | bypasses normal snapshot TTL | returns rejected IPC promise with normalized error message |
|
|
| `CODEX_ACCOUNT_START_CHATGPT_LOGIN` | no payload | `CodexLoginStateDto` | starts login session, may open browser | invalidates snapshot cache on state transitions | returns failed login state or rejected IPC promise for hard failures |
|
|
| `CODEX_ACCOUNT_CANCEL_LOGIN` | no payload | `CodexLoginStateDto` | cancels active login if present | no steady-state cache effect except follow-up refresh | safe no-op if no active login |
|
|
| `CODEX_ACCOUNT_LOGOUT` | no payload | `CodexConnectionSnapshotDto` | logs out managed account and refreshes snapshot | clears last-known-good managed-account snapshot | rejected IPC promise or error-bearing snapshot depending on final implementation choice |
|
|
| `CODEX_ACCOUNT_GET_RATE_LIMITS` | `{ forceFresh?: boolean }` or no payload | `CodexRateLimitsDto \| null` | none | may serve rate-limit cache unless `forceFresh` | returns `null` or rejected IPC promise depending on error-handling contract |
|
|
| `CODEX_ACCOUNT_EVENT` | subscription only | `CodexAccountEventDto` | none | n/a | best-effort delivery only |
|
|
|
|
Contract rule:
|
|
|
|
- all IPC errors should be normalized into stable, user-safe messages
|
|
- renderer code must not depend on raw transport/process error text
|
|
|
|
## Electron API integration
|
|
|
|
Extend:
|
|
|
|
- `src/shared/types/api.ts`
|
|
- `src/preload/index.ts`
|
|
|
|
Pattern should match existing feature slices like `recent-projects`.
|
|
|
|
## Preload And Renderer API Shape
|
|
|
|
The renderer-facing API should be explicit so we do not leak main-process internals into UI code.
|
|
|
|
Recommended preload-facing contract:
|
|
|
|
```ts
|
|
export interface CodexAccountElectronApi {
|
|
getSnapshot: (options?: { forceFresh?: boolean }) => Promise<CodexConnectionSnapshotDto>;
|
|
refreshSnapshot: () => Promise<CodexConnectionSnapshotDto>;
|
|
startChatgptLogin: () => Promise<CodexLoginStateDto>;
|
|
cancelLogin: () => Promise<CodexLoginStateDto>;
|
|
logout: () => Promise<CodexConnectionSnapshotDto>;
|
|
getRateLimits: (options?: { forceFresh?: boolean }) => Promise<CodexRateLimitsDto | null>;
|
|
onEvent: (callback: (event: CodexAccountEventDto) => void) => () => void;
|
|
}
|
|
```
|
|
|
|
Integration rule:
|
|
|
|
- this contract belongs under `src/features/codex-account/contracts`
|
|
- `src/preload/index.ts` should only bridge it
|
|
- renderer hooks should consume this contract through the app API abstraction, not directly through
|
|
ad hoc `window.electronAPI` calls spread across components
|
|
|
|
## Account Control-Plane Flows
|
|
|
|
## App-Server Method Matrix
|
|
|
|
This matrix documents how the feature is expected to use the official app-server surface.
|
|
|
|
| Method / notification | Used in phase | Typical caller | Input | Expected output | Notes |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| `account/read` | B+ | snapshot use cases | `{ refreshToken?: boolean }` | current account plus `requiresOpenaiAuth` | passive reads should default `refreshToken` to `false` |
|
|
| `account/login/start` with `type: "chatgpt"` | E+ | login use case | no extra payload | `loginId` plus `authUrl` | browser flow is first-class |
|
|
| `account/login/cancel` | E+ | cancel use case / session manager | active `loginId` | success or no-op | safe to call only when login is active |
|
|
| `account/logout` | E+ | logout use case | none | logout acknowledgement | should be followed by forced snapshot refresh |
|
|
| `account/rateLimits/read` | F+ | rate-limit use case | none or method-specific default params | plan/rate-limit snapshot | should stay lazy by default |
|
|
| `account/updated` | E+ | login session manager / event bridge | notification only | auth mode plus plan changes | may arrive outside explicit reads |
|
|
| `account/login/completed` | E+ | login session manager | notification only | success or error for a specific `loginId` | must drive pending-state settlement |
|
|
| `account/rateLimits/updated` | F+ | optional rate-limit subscription handling | notification only | updated rate-limit view | should not be required for base snapshot correctness |
|
|
|
|
Usage rule:
|
|
|
|
- `account/read` is the canonical steady-state read path
|
|
- notifications are accelerators for freshness, not replacements for a recoverable read model
|
|
|
|
## `refreshToken` Usage Policy
|
|
|
|
Official app-server docs and local schema expose `account/read { refreshToken?: boolean }`.
|
|
|
|
This flag is powerful enough that the plan should constrain it explicitly.
|
|
|
|
### Default rule
|
|
|
|
- passive background reads use `refreshToken = false`
|
|
- normal explicit refresh also starts with `refreshToken = false`
|
|
|
|
### Escalation rule
|
|
|
|
Allow a one-time `refreshToken = true` read only when there is a concrete auth-staleness reason,
|
|
for example:
|
|
|
|
- a just-completed login has not converged after the first normal snapshot read
|
|
- explicit user recovery action after an auth-related degraded state
|
|
|
|
### Forbidden rule
|
|
|
|
Do not:
|
|
|
|
- set `refreshToken = true` on every poll
|
|
- loop repeated token-refresh reads in the background
|
|
- use token refresh as a substitute for the normal snapshot model
|
|
|
|
### Operational reason
|
|
|
|
- overusing token refresh increases latency and creates another path to false logout or confusing
|
|
transient state
|
|
|
|
## App-Server Compatibility Gate
|
|
|
|
The feature must not equate "binary exists" with "account seam is supported".
|
|
|
|
### Stable-surface rule
|
|
|
|
First-wave `codex-account` should initialize app-server with:
|
|
|
|
- `experimentalApi: false`
|
|
- only the notification subscriptions it actually needs
|
|
|
|
Do not opt into experimental API just to make the first wave easier.
|
|
|
|
### Required handshake contract
|
|
|
|
Before the feature reports the managed-account seam as supported, it must prove:
|
|
|
|
1. `codex app-server` starts
|
|
2. `initialize` succeeds on the stable surface
|
|
3. the initialize response yields diagnostics we can record:
|
|
- `codexHome`
|
|
- `platformFamily`
|
|
- `platformOs`
|
|
4. required stable methods behave as expected:
|
|
- `account/read`
|
|
- `account/login/start`
|
|
- `account/login/cancel`
|
|
- `account/logout`
|
|
- `account/rateLimits/read`
|
|
|
|
### Compatibility verdict rule
|
|
|
|
If initialize works but one of the required stable account methods is absent, rejected as
|
|
experimental-only, or otherwise incompatible with the expected shape:
|
|
|
|
- classify the feature state as `app-server-incompatible`
|
|
- do not classify it as generic missing auth
|
|
- do not offer misleading login or subscription controls
|
|
|
|
### Versioning rule
|
|
|
|
Prefer capability/protocol evidence over string-parsed `codex --version`.
|
|
|
|
Semver may still be logged for diagnostics, but:
|
|
|
|
- semver alone must not unlock the feature
|
|
- semver alone must not disable the feature when the stable handshake succeeds
|
|
|
|
## Flow 1 - autodetect existing account
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant UI as Renderer Hook
|
|
participant IPC as Feature IPC
|
|
participant UC as Get Snapshot Use Case
|
|
participant APP as App Server Client
|
|
participant KEY as API Key Adapter
|
|
|
|
UI->>IPC: getCodexConnectionSnapshot()
|
|
IPC->>UC: execute()
|
|
UC->>APP: account/read
|
|
UC->>KEY: getApiKeyAvailability()
|
|
APP-->>UC: managed account state
|
|
KEY-->>UC: api key availability
|
|
UC-->>IPC: merged snapshot
|
|
IPC-->>UI: snapshot dto
|
|
```
|
|
|
|
## Flow 2 - start ChatGPT login
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant UI as Renderer
|
|
participant IPC as Feature IPC
|
|
participant UC as Start Login Use Case
|
|
participant LOGIN as Login Session Manager
|
|
participant APP as App Server
|
|
participant BROWSER as Browser Launcher
|
|
|
|
UI->>IPC: startCodexChatgptLogin()
|
|
IPC->>UC: execute()
|
|
UC->>LOGIN: acquire or create login session
|
|
LOGIN->>APP: account/login/start(type=chatgpt)
|
|
APP-->>LOGIN: authUrl + loginId
|
|
LOGIN->>BROWSER: openExternal(authUrl)
|
|
LOGIN-->>IPC: login pending
|
|
IPC-->>UI: login state changed
|
|
APP-->>LOGIN: account/login/completed
|
|
LOGIN-->>IPC: snapshot refresh + login completed event
|
|
IPC-->>UI: snapshot updated
|
|
```
|
|
|
|
## Flow 3 - launch Codex
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant SHELL as Runtime Shell
|
|
participant FACADE as Codex Account Feature
|
|
participant READY as Launch Readiness UC
|
|
participant ENV as Provider Env Builder
|
|
participant EXEC as codex exec
|
|
|
|
SHELL->>FACADE: evaluateLaunchReadiness()
|
|
FACADE->>READY: execute()
|
|
READY-->>FACADE: readiness + effectiveAuthMode + env policy
|
|
FACADE-->>SHELL: launch policy
|
|
SHELL->>ENV: build env using policy
|
|
ENV-->>SHELL: sanitized/injected env
|
|
SHELL->>EXEC: launch codex exec
|
|
```
|
|
|
|
## Login State Machine
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> idle
|
|
idle --> starting: start login
|
|
starting --> awaiting_browser: authUrl received
|
|
awaiting_browser --> pending: browser opened
|
|
pending --> completed: account/login/completed success=true
|
|
pending --> failed: account/login/completed success=false
|
|
pending --> cancelled: account/login/cancel
|
|
awaiting_browser --> failed: browser open failed
|
|
starting --> failed: start request failed
|
|
completed --> idle: refresh settled
|
|
cancelled --> idle: refresh settled
|
|
failed --> idle: reset
|
|
```
|
|
|
|
## Subscription Lifecycle Semantics
|
|
|
|
This section describes the intended steady-state lifecycle of a managed ChatGPT-backed Codex
|
|
subscription as the app should understand it.
|
|
|
|
### Lifecycle phases
|
|
|
|
1. no managed account detected
|
|
2. login initiated
|
|
3. browser auth pending
|
|
4. managed account connected
|
|
5. temporarily degraded verification
|
|
6. explicit logout
|
|
|
|
### Important semantic rules
|
|
|
|
- `managed account connected` means the control plane has positive evidence of a ChatGPT-backed
|
|
account
|
|
- `degraded` does not mean disconnected
|
|
- explicit logout is stronger than cached prior evidence and must clear it immediately
|
|
- API key availability may coexist with any lifecycle phase except `runtime_missing`
|
|
|
|
### User-visible implication
|
|
|
|
The UI should present the managed account as:
|
|
|
|
- connected
|
|
- pending
|
|
- degraded
|
|
- disconnected
|
|
|
|
and should not collapse these into one binary `authenticated` flag for Codex.
|
|
|
|
## Main-Side Use Cases
|
|
|
|
### `GetCodexConnectionSnapshotUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- get a cached or fresh merged snapshot
|
|
- merge managed account and API key availability
|
|
- derive effective auth mode and launch readiness
|
|
|
|
Must not:
|
|
|
|
- open browser
|
|
- mutate config
|
|
|
|
### `RefreshCodexConnectionSnapshotUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- force a fresh read from app-server
|
|
- optionally request proactive token refresh only on explicit user action
|
|
|
|
Important nuance:
|
|
|
|
- do **not** set `refreshToken = true` on every passive read
|
|
- reserve that for explicit manual refresh or post-login reconciliation
|
|
|
|
### `StartCodexChatgptLoginUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- ensure a single in-flight login
|
|
- start login via app-server
|
|
- open browser
|
|
- update login state
|
|
|
|
Must handle:
|
|
|
|
- duplicate click while login already pending
|
|
- browser open failure
|
|
- login timeout
|
|
|
|
First implementation decision:
|
|
|
|
- browser flow via `type: "chatgpt"` is in scope
|
|
- device code flow via `type: "chatgptDeviceCode"` is explicitly deferred unless we discover a real
|
|
Electron/browser-launch blocker
|
|
|
|
Reason:
|
|
|
|
- browser flow matches the prior UX expectation more closely
|
|
- device-code support is valuable, but it is not required to restore the intended desktop UX
|
|
|
|
### `CancelCodexLoginUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- cancel active login if any
|
|
- cleanly tear down pending session
|
|
|
|
### `LogoutCodexManagedAccountUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- perform app-server `account/logout`
|
|
- refresh snapshot
|
|
|
|
### `ReadCodexRateLimitsUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- load rate limits lazily
|
|
- avoid blocking basic connection UI
|
|
|
|
### `EvaluateCodexLaunchReadinessUseCase`
|
|
|
|
Responsibilities:
|
|
|
|
- compute launch policy from feature truth
|
|
- return:
|
|
- readiness state
|
|
- effective auth mode
|
|
- env mutation instructions
|
|
- user-facing advisory
|
|
|
|
This use case must become the shell's single source of truth for Codex launch auth policy.
|
|
|
|
## Main Infrastructure Design
|
|
|
|
## `CodexAccountEnvBuilder`
|
|
|
|
Purpose:
|
|
|
|
- build consistent env for app-server account sessions
|
|
- build deterministic env mutation instructions for execution
|
|
|
|
Inputs:
|
|
|
|
- resolved shell env
|
|
- binary path
|
|
- selected auth mode
|
|
- API key value or absence
|
|
|
|
Outputs:
|
|
|
|
- account-session env
|
|
- exec-session env policy
|
|
|
|
This module exists because generic `buildProviderAwareCliEnv()` currently knows only the old Codex API-key-only world.
|
|
|
|
## `CodexAccountAppServerClient`
|
|
|
|
Purpose:
|
|
|
|
- short-lived request client for:
|
|
- `account/read`
|
|
- `account/logout`
|
|
- `account/rateLimits/read`
|
|
|
|
Behavior:
|
|
|
|
- request-scoped sessions only
|
|
- initialize and dispose per request
|
|
|
|
## `CodexLoginSessionManager`
|
|
|
|
Purpose:
|
|
|
|
- own one long-lived login session while login is pending
|
|
|
|
Responsibilities:
|
|
|
|
- start login
|
|
- observe notifications
|
|
- cancel login
|
|
- timeout pending login
|
|
- emit feature events
|
|
|
|
Important rule:
|
|
|
|
- there can be at most one active login session at a time
|
|
|
|
## Module Responsibility Matrix
|
|
|
|
This table is the SRP-oriented version of the implementation design.
|
|
|
|
| Module | Owns | Must not own | Typical collaborators |
|
|
| --- | --- | --- | --- |
|
|
| `CodexAccountAppServerClient` | request-scoped app-server RPC for account/read, logout, rate limits | login session lifecycle, renderer events, config | session factory, logger |
|
|
| `CodexLoginSessionManager` | long-lived login session, login notifications, timeout, cancel | generic account/read cache, API key storage, shell copy | app-server transport, browser launcher, logger |
|
|
| `CodexAccountEnvBuilder` | auth-store env normalization, auth-sensitive env mutation policy | launch decision semantics, config migration | shell env port, binary resolver port |
|
|
| `GetCodexConnectionSnapshotUseCase` | merge snapshot truth and caching policy | browser opening, low-level process logic | managed account source, api key source, cache, clock |
|
|
| `EvaluateCodexLaunchReadinessUseCase` | effective auth mode selection and readiness semantics | actual child-process spawning | snapshot/domain models |
|
|
| `CodexConnectionCoordinator` | shell-facing launch integration and env assembly handoff | account lifecycle, renderer subscriptions | feature facade, provider connection service |
|
|
| presenter adapters | stable DTO/event shaping | domain policy changes | use cases, contracts |
|
|
| renderer hooks | subscription orchestration and action wiring | business truth invention | preload API, view-model adapters |
|
|
| feature UI components | rendering | transport, config mutation, process logic | hooks, view models |
|
|
|
|
Review rule:
|
|
|
|
- if a new change makes one row start owning another row's responsibilities, stop and split the
|
|
concern before continuing
|
|
|
|
## Session And Timeout Policy
|
|
|
|
### Short-lived read sessions
|
|
|
|
Use for:
|
|
|
|
- account/read
|
|
- rateLimits/read
|
|
- logout
|
|
|
|
Recommended timeouts:
|
|
|
|
- initialize timeout: aligned with existing app-server defaults
|
|
- request timeout: short and bounded
|
|
|
|
### Long-lived login session
|
|
|
|
Use for:
|
|
|
|
- start login
|
|
- wait for `account/login/completed`
|
|
|
|
Recommended timeout:
|
|
|
|
- hard max pending duration around `10 minutes`
|
|
|
|
Reason:
|
|
|
|
- long enough for browser auth
|
|
- short enough to avoid zombie sessions
|
|
|
|
## App-Server Session Topology Rule
|
|
|
|
The feature should make session ownership explicit so connection-scoped notifications do not leak
|
|
between concerns.
|
|
|
|
### Topology
|
|
|
|
- passive reads:
|
|
- short-lived dedicated app-server sessions
|
|
- rate-limits reads:
|
|
- short-lived dedicated app-server sessions or reused read-session helper, but not the live login
|
|
session
|
|
- login flow:
|
|
- one dedicated long-lived session that owns:
|
|
- `account/login/start`
|
|
- login notifications
|
|
- optional `account/login/cancel`
|
|
|
|
### No-pooling rule
|
|
|
|
Do not multiplex in the first wave:
|
|
|
|
- `recent-projects` and `codex-account` over one shared live app-server child
|
|
- login notifications and passive reads over one generic pooled session
|
|
|
|
### Cleanup rule
|
|
|
|
When a feature-owned app-server session is disposed:
|
|
|
|
- kill the child deterministically
|
|
- reject outstanding requests
|
|
- ignore any late results or notifications from that disposed session generation
|
|
|
|
This keeps process cleanup and notification ownership unambiguous.
|
|
|
|
### Notification suppression policy
|
|
|
|
For read sessions:
|
|
|
|
- suppress noisy thread notifications
|
|
|
|
For login session:
|
|
|
|
- do **not** suppress:
|
|
- `account/login/completed`
|
|
- `account/updated`
|
|
|
|
## Timeout Defaults Table
|
|
|
|
These defaults should remain centralized so different callers do not invent incompatible timing.
|
|
|
|
| Interaction | Recommended default | Why |
|
|
| --- | --- | --- |
|
|
| app-server initialize for read sessions | align with existing shared app-server defaults | keeps transport consistent with `recent-projects` |
|
|
| `account/read` request timeout | short and bounded | passive reads should fail fast into degraded state |
|
|
| `account/logout` request timeout | short and bounded | logout should resolve or fail clearly |
|
|
| `account/rateLimits/read` timeout | short-medium | secondary UI should not hang the page |
|
|
| login pending max duration | around `10 minutes` | enough for browser auth, short enough to avoid zombie state |
|
|
| snapshot cache TTL | around `3-10 seconds` | enough dedupe without stale-feeling UI |
|
|
| rate-limits cache TTL | around `30-60 seconds` | secondary UI can be less fresh |
|
|
| freshness window for last-known-good managed account | around `60 seconds` | balances resilience and honesty |
|
|
|
|
Consistency rule:
|
|
|
|
- do not hardcode these values independently in multiple modules
|
|
- centralize them in feature-local configuration/constants or shared transport defaults where
|
|
appropriate
|
|
|
|
## Retry And Backoff Policy
|
|
|
|
Retries are one of the easiest ways to accidentally hide truth or create duplicate state
|
|
transitions. This feature should be conservative.
|
|
|
|
### What may retry automatically
|
|
|
|
- passive `account/read` refresh after a transient initialization or timeout failure
|
|
|
|
Recommended default:
|
|
|
|
- at most one immediate retry for passive reads
|
|
- only when the failure is clearly transport-level
|
|
|
|
### What should not auto-retry
|
|
|
|
- login start
|
|
- logout
|
|
- cancel login
|
|
- manual refresh button actions
|
|
- any request that already has a user-visible action outcome
|
|
|
|
Why:
|
|
|
|
- auto-retrying user actions can create duplicate browser flows, duplicate state transitions, or
|
|
surprising side effects
|
|
|
|
### Backoff rule
|
|
|
|
If passive background refresh keeps failing:
|
|
|
|
- do not spin
|
|
- let the feature surface `degraded`
|
|
- wait for next normal refresh trigger or explicit user action
|
|
|
|
### Timeout handling rule
|
|
|
|
Timeout must be surfaced as a first-class degraded reason category, not collapsed into generic
|
|
"not connected".
|
|
|
|
## Caching And Refresh Policy
|
|
|
|
### Snapshot cache
|
|
|
|
Recommended:
|
|
|
|
- in-memory cache in main feature
|
|
- small TTL around `3-10 seconds`
|
|
- single-flight refresh collapse
|
|
|
|
Reason:
|
|
|
|
- dashboard banner and settings dialog can ask for the same snapshot nearly simultaneously
|
|
|
|
### Rate-limits cache
|
|
|
|
Recommended:
|
|
|
|
- separate cache
|
|
- longer TTL around `30-60 seconds`
|
|
|
|
Reason:
|
|
|
|
- rate limits are secondary UI, not critical hot-path state
|
|
|
|
### Post-action invalidation
|
|
|
|
After:
|
|
|
|
- login success
|
|
- logout success
|
|
- explicit refresh
|
|
|
|
invalidate the snapshot cache immediately.
|
|
|
|
## Shell Integration Plan
|
|
|
|
This section makes the integration concrete.
|
|
|
|
## Main process composition
|
|
|
|
Add feature creation in:
|
|
|
|
- `src/main/index.ts`
|
|
|
|
Pattern:
|
|
|
|
- create the feature alongside `recent-projects`
|
|
- register its IPC handlers after feature construction
|
|
|
|
Later, if browser-mode support is desired:
|
|
|
|
- wire the feature into `src/main/standalone.ts`
|
|
- add HTTP adapter endpoints
|
|
|
|
## Preload integration
|
|
|
|
Add to:
|
|
|
|
- `src/preload/index.ts`
|
|
|
|
Pattern:
|
|
|
|
- same as `recent-projects`
|
|
- spread `createCodexAccountBridge()` into `window.electronAPI`
|
|
|
|
## Shared API type integration
|
|
|
|
Extend:
|
|
|
|
- `src/shared/types/api.ts`
|
|
|
|
with a new feature API contract interface.
|
|
|
|
## Renderer integration
|
|
|
|
Shell components that must stop owning Codex business logic:
|
|
|
|
- `ProviderRuntimeSettingsDialog`
|
|
- `CliStatusBanner`
|
|
- `CliStatusSection`
|
|
- `providerConnectionUi.ts`
|
|
|
|
How they should change:
|
|
|
|
1. `ProviderRuntimeSettingsDialog`
|
|
- for provider `codex`, render `CodexAccountConnectionPanel`
|
|
- stop hardcoding Codex as API-key-only
|
|
2. `CliStatusBanner`
|
|
- stop using terminal modal login/logout for Codex
|
|
- use feature hook actions
|
|
3. `CliStatusSection`
|
|
- same as banner
|
|
4. `providerConnectionUi.ts`
|
|
- stop flattening Codex auth summary
|
|
- use feature adapter output for Codex-specific text
|
|
|
|
## Legacy UX Parity Policy
|
|
|
|
The user requirement here is not just "make auth work". It is also "restore the good legacy Codex
|
|
subscription UX while keeping the new native runtime".
|
|
|
|
That means we should reuse the current shell surfaces and preserve their visual grammar, instead of
|
|
inventing a new Codex settings screen.
|
|
|
|
### Required UX shape
|
|
|
|
The feature should plug into the existing surfaces:
|
|
|
|
- provider manage dialog
|
|
- dashboard CLI banner
|
|
- settings CLI section
|
|
|
|
It should not introduce:
|
|
|
|
- a separate standalone Codex settings page
|
|
- a second disconnected login modal system
|
|
- a renderer-only fake status card
|
|
|
|
### Required visible information
|
|
|
|
For Codex, the composed UI should be able to show:
|
|
|
|
- current preferred auth mode
|
|
- managed account connected or not
|
|
- account email when available
|
|
- plan type when available
|
|
- API key also available or not
|
|
- effective launch mode in auto
|
|
- pending login / cancelling / logout states
|
|
- degraded-but-still-launchable states
|
|
|
|
### Required action set
|
|
|
|
For Codex, the composed UI should expose:
|
|
|
|
- connect ChatGPT account
|
|
- cancel login while pending
|
|
- disconnect managed account
|
|
- choose preferred auth mode
|
|
- manage API key without implying it is the only path
|
|
- refresh account state
|
|
|
|
### Copy policy
|
|
|
|
When Codex is managed-account-backed:
|
|
|
|
- do not flatten everything to "Connected via API key"
|
|
- do not label the primary action as `Configure API key`
|
|
- do not use Anthropic-specific `OAuth` wording
|
|
|
|
Preferred wording family:
|
|
|
|
- `ChatGPT account`
|
|
- `Codex subscription`
|
|
- `Plan`
|
|
- `API key also available`
|
|
- `Auto - prefer ChatGPT account`
|
|
|
|
### Visual ownership rule
|
|
|
|
The feature owns Codex-specific content blocks.
|
|
|
|
The shell owns:
|
|
|
|
- container cards
|
|
- section framing
|
|
- generic button spacing/layout primitives
|
|
- provider ordering
|
|
|
|
This preserves legacy familiarity without duplicating layout systems.
|
|
|
|
## Surface-By-Surface UI Contract
|
|
|
|
Each visible shell surface should have a clear responsibility so the same state is not explained in
|
|
three conflicting ways.
|
|
|
|
### `ProviderRuntimeSettingsDialog`
|
|
|
|
Purpose:
|
|
|
|
- authoritative management surface for Codex auth preference and account state
|
|
|
|
Must show:
|
|
|
|
- preferred auth mode selector
|
|
- managed account summary
|
|
- API key secondary availability
|
|
- login / cancel / logout actions
|
|
- degraded state explanation
|
|
- rate-limit section when requested or expanded
|
|
|
|
Must not:
|
|
|
|
- pretend to be a generic provider card when provider is `codex`
|
|
|
|
### `CliStatusBanner`
|
|
|
|
Purpose:
|
|
|
|
- concise dashboard summary and quick action entry point
|
|
|
|
Must show:
|
|
|
|
- one-line Codex status summary derived from the feature
|
|
- manage/open action into the richer settings surface
|
|
- degraded warning when appropriate
|
|
|
|
Must not:
|
|
|
|
- become the full account management UI
|
|
|
|
### `CliStatusSection`
|
|
|
|
Purpose:
|
|
|
|
- settings-level operational summary for the installed runtime
|
|
|
|
Must show:
|
|
|
|
- consistent Codex account summary
|
|
- launch-relevant status
|
|
- path into full manage dialog
|
|
|
|
Must not:
|
|
|
|
- use different wording than the manage dialog for the same auth truth
|
|
|
|
### Shared UI consistency rule
|
|
|
|
For the same underlying snapshot:
|
|
|
|
- headline wording
|
|
- effective auth mode wording
|
|
- degraded wording
|
|
- connect/disconnect affordances
|
|
|
|
must all stay semantically consistent across surfaces, even if the amount of detail differs.
|
|
|
|
## What remains shell-owned
|
|
|
|
- generic provider card structure
|
|
- generic runtime/backend status
|
|
- generic model availability presentation
|
|
|
|
## What becomes feature-owned
|
|
|
|
- Codex account summary copy
|
|
- Codex connect/disconnect actions
|
|
- Codex login pending UI
|
|
- Codex rate-limit presentation
|
|
- Codex auth-mode selection UI
|
|
|
|
## Renderer Hook Composition Contract
|
|
|
|
To keep renderer code aligned with the feature standard, hooks and adapters should have explicit
|
|
roles.
|
|
|
|
### `useCodexAccount`
|
|
|
|
Responsibilities:
|
|
|
|
- fetch and subscribe to the current snapshot
|
|
- expose refresh action
|
|
- expose derived loading/error/degraded flags for feature UI
|
|
|
|
Must not:
|
|
|
|
- open browser directly
|
|
- compute shell layout copy inline
|
|
|
|
### `useCodexLoginFlow`
|
|
|
|
Responsibilities:
|
|
|
|
- expose login, cancel, and logout actions
|
|
- surface pending action state and latest action error
|
|
|
|
Must not:
|
|
|
|
- own snapshot caching
|
|
- own rate-limit fetching
|
|
|
|
### `useCodexRateLimits`
|
|
|
|
Responsibilities:
|
|
|
|
- fetch rate limits lazily
|
|
- respect dedicated rate-limit TTL and pending state
|
|
|
|
Must not:
|
|
|
|
- block base snapshot rendering
|
|
|
|
### `codexAccountViewModel` / `codexProviderShellAdapter`
|
|
|
|
Responsibilities:
|
|
|
|
- merge feature DTOs with generic shell provider status into stable view models
|
|
- keep wording and badge semantics consistent across surfaces
|
|
|
|
Must not:
|
|
|
|
- call transport directly
|
|
- mutate app config
|
|
|
|
### UI component rule
|
|
|
|
Feature UI components should be as close to pure renderers as possible:
|
|
|
|
- inputs in
|
|
- callbacks out
|
|
|
|
This keeps renderer complexity low and makes snapshot-state regression tests much easier.
|
|
|
|
## Browser-mode policy
|
|
|
|
Initial implementation recommendation:
|
|
|
|
- Electron / preload path is the first-class path
|
|
- browser-mode support is explicitly deferred unless product requires it immediately
|
|
|
|
If browser mode is visible:
|
|
|
|
- Codex account feature should degrade honestly as unsupported or unavailable
|
|
- do not silently attempt local app-server control through browser mode without explicit HTTP support
|
|
|
|
This keeps the architecture clean and avoids half-working local machine assumptions in browser sessions.
|
|
|
|
## Browser Auth URL Handling Policy
|
|
|
|
The feature should treat login URLs as sensitive, short-lived control-plane data.
|
|
|
|
### Open rule
|
|
|
|
For Codex ChatGPT login:
|
|
|
|
- open the URL from main process only
|
|
- validate the URL before opening
|
|
- require `https:` scheme
|
|
- reject `http:`, `mailto:`, custom schemes, or malformed URLs for this feature-specific path
|
|
|
|
### Trust rule
|
|
|
|
Do not hardcode a hostname allowlist in the first wave unless official docs start guaranteeing a
|
|
stable host set.
|
|
|
|
Instead:
|
|
|
|
- trust app-server as the source of the URL
|
|
- enforce `https` scheme
|
|
- avoid logging the full URL
|
|
- record only derived diagnostics when needed, such as scheme/hostname
|
|
|
|
### IPC rule
|
|
|
|
- renderer asks to start login
|
|
- main starts login and opens the validated URL
|
|
- renderer never receives the raw `authUrl`
|
|
|
|
### Failure rule
|
|
|
|
If URL validation fails:
|
|
|
|
- classify as `unsafe-auth-url`
|
|
- fail login cleanly
|
|
- do not attempt browser open
|
|
|
|
## Runtime Integration Plan
|
|
|
|
This is where the feature touches existing runtime services.
|
|
|
|
## Existing problem
|
|
|
|
`providerAwareCliEnv.ts` and `ProviderConnectionService.ts` currently encode the rule:
|
|
|
|
- Codex readiness requires API key presence
|
|
|
|
That must change.
|
|
|
|
## Recommended integration pattern
|
|
|
|
Introduce a small runtime-side coordinator:
|
|
|
|
- `src/main/services/runtime/CodexConnectionCoordinator.ts`
|
|
|
|
Responsibilities:
|
|
|
|
- ask the feature for launch readiness
|
|
- ask `ProviderConnectionService` for API key value resolution when needed
|
|
- apply auth-mode-specific env mutation policy
|
|
|
|
Why a coordinator is better than stuffing more into `ProviderConnectionService`:
|
|
|
|
- keeps provider-generic code smaller
|
|
- avoids turning `ProviderConnectionService` into a God object
|
|
- keeps Codex feature policy close to the feature seam
|
|
|
|
## Responsibilities after the coordinator exists
|
|
|
|
### `ProviderConnectionService` keeps responsibility for:
|
|
|
|
- app-owned API key discovery
|
|
- app-owned API key injection primitives
|
|
- Anthropic-specific connection mode handling
|
|
|
|
### `CodexConnectionCoordinator` owns:
|
|
|
|
- choosing ChatGPT vs API key for Codex launch
|
|
- deciding which env vars must be sanitized
|
|
- deciding which `forced_login_method` override to pass
|
|
|
|
### `providerAwareCliEnv.ts` becomes:
|
|
|
|
- provider-generic env assembly plus delegation to Codex coordinator when provider is `codex`
|
|
|
|
### `ClaudeMultimodelBridgeService.ts` becomes:
|
|
|
|
- generic runtime status reader
|
|
- plus additive merge of Codex account snapshot for Codex-specific presentation
|
|
|
|
## Detailed Touch Points
|
|
|
|
Likely first-wave touch points:
|
|
|
|
- `src/main/index.ts`
|
|
- `src/preload/index.ts`
|
|
- `src/shared/types/api.ts`
|
|
- `src/main/services/infrastructure/ConfigManager.ts`
|
|
- `src/main/ipc/configValidation.ts`
|
|
- `src/shared/types/notifications.ts`
|
|
- `src/main/services/runtime/providerAwareCliEnv.ts`
|
|
- `src/main/services/runtime/ProviderConnectionService.ts`
|
|
- `src/main/services/runtime/ClaudeMultimodelBridgeService.ts`
|
|
- `src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx`
|
|
- `src/renderer/components/runtime/providerConnectionUi.ts`
|
|
- `src/renderer/components/dashboard/CliStatusBanner.tsx`
|
|
- `src/renderer/components/settings/sections/CliStatusSection.tsx`
|
|
|
|
Important design rule:
|
|
|
|
- shell files should mostly lose Codex-specific conditional logic, not gain more of it
|
|
|
|
## File-Level Implementation Map
|
|
|
|
This section translates the architecture into concrete repo edits so the work stays transparent.
|
|
|
|
### New feature files
|
|
|
|
Expected first-wave additions:
|
|
|
|
- `src/features/codex-account/contracts/api.ts`
|
|
- `src/features/codex-account/contracts/channels.ts`
|
|
- `src/features/codex-account/contracts/dto.ts`
|
|
- `src/features/codex-account/contracts/events.ts`
|
|
- `src/features/codex-account/contracts/index.ts`
|
|
- `src/features/codex-account/core/domain/*`
|
|
- `src/features/codex-account/core/application/ports/*`
|
|
- `src/features/codex-account/core/application/use-cases/*`
|
|
- `src/features/codex-account/main/composition/createCodexAccountFeature.ts`
|
|
- `src/features/codex-account/main/adapters/input/ipc/registerCodexAccountIpc.ts`
|
|
- `src/features/codex-account/main/adapters/output/presenters/*`
|
|
- `src/features/codex-account/main/adapters/output/runtime/*`
|
|
- `src/features/codex-account/main/infrastructure/cache/*`
|
|
- `src/features/codex-account/main/infrastructure/codex/*`
|
|
- `src/features/codex-account/preload/createCodexAccountBridge.ts`
|
|
- `src/features/codex-account/preload/index.ts`
|
|
- `src/features/codex-account/renderer/index.ts`
|
|
- `src/features/codex-account/renderer/adapters/*`
|
|
- `src/features/codex-account/renderer/hooks/*`
|
|
- `src/features/codex-account/renderer/ui/*`
|
|
|
|
### Existing files that should mostly gain composition hooks, not deep new business logic
|
|
|
|
#### `src/main/index.ts`
|
|
|
|
Should:
|
|
|
|
- instantiate the feature
|
|
- register IPC
|
|
- expose a small facade to shell services
|
|
|
|
Should not:
|
|
|
|
- implement account/read logic inline
|
|
- manage login state inline
|
|
|
|
#### `src/preload/index.ts`
|
|
|
|
Should:
|
|
|
|
- merge the feature bridge into `window.electronAPI`
|
|
|
|
Should not:
|
|
|
|
- contain auth logic
|
|
- transform account domain state into shell copy
|
|
|
|
#### `src/main/services/infrastructure/ConfigManager.ts`
|
|
|
|
Should:
|
|
|
|
- add persisted `providerConnections.codex.preferredAuthMode`
|
|
- normalize stale legacy Codex connection values
|
|
|
|
Should not:
|
|
|
|
- resolve runtime effective auth mode
|
|
|
|
#### `src/main/ipc/configValidation.ts`
|
|
|
|
Should:
|
|
|
|
- accept only the new normalized Codex connection field
|
|
- reject new unknown fields after migration normalization
|
|
|
|
Should not:
|
|
|
|
- infer defaults that belong to `ConfigManager`
|
|
|
|
#### `src/main/services/runtime/ProviderConnectionService.ts`
|
|
|
|
Should:
|
|
|
|
- remain owner of app API key storage lookup and injection primitives
|
|
|
|
Should not:
|
|
|
|
- remain final authority for Codex launch readiness
|
|
- own ChatGPT managed-account policy
|
|
|
|
#### `src/main/services/runtime/providerAwareCliEnv.ts`
|
|
|
|
Should:
|
|
|
|
- delegate Codex-specific env policy to the coordinator / feature seam
|
|
|
|
Should not:
|
|
|
|
- continue hardcoding API-key-only Codex logic
|
|
|
|
#### `src/main/services/runtime/ClaudeMultimodelBridgeService.ts`
|
|
|
|
Should:
|
|
|
|
- keep generic provider/runtime status probing
|
|
- optionally compose Codex account snapshot into Codex-facing presentation
|
|
|
|
Should not:
|
|
|
|
- become the owner of Codex account lifecycle
|
|
|
|
#### `src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx`
|
|
|
|
Should:
|
|
|
|
- host the feature-owned Codex panel
|
|
|
|
Should not:
|
|
|
|
- directly own Codex login flow logic
|
|
- directly derive Codex copy from generic provider flags alone
|
|
|
|
#### `src/renderer/components/runtime/providerConnectionUi.ts`
|
|
|
|
Should:
|
|
|
|
- become thinner for Codex
|
|
- consume feature adapters for Codex-specific labels
|
|
|
|
Should not:
|
|
|
|
- remain the place where Codex account semantics are invented
|
|
|
|
#### `src/renderer/components/dashboard/CliStatusBanner.tsx`
|
|
#### `src/renderer/components/settings/sections/CliStatusSection.tsx`
|
|
|
|
Should:
|
|
|
|
- call feature actions / hooks
|
|
- render feature-composed Codex status segments
|
|
|
|
Should not:
|
|
|
|
- keep normal Codex login/logout on terminal modal commands once the feature is complete
|
|
|
|
## Failure Modes And Safety Policy
|
|
|
|
### Failure mode: no Codex binary
|
|
|
|
Behavior:
|
|
|
|
- state becomes `runtime_missing`
|
|
- login actions disabled
|
|
- connection panel explains binary missing
|
|
|
|
### Failure mode: app-server initialize failure
|
|
|
|
Behavior:
|
|
|
|
- managed-account state becomes degraded
|
|
- API key availability remains visible
|
|
- do not auto-mark user as logged out
|
|
- do not hard-stop launch if execution could still work
|
|
|
|
Additional rule:
|
|
|
|
- if the last successful snapshot is still within the acceptable freshness window, prefer showing
|
|
`degraded` over collapsing to `not_connected`
|
|
|
|
### Failure mode: account read timeout
|
|
|
|
Behavior:
|
|
|
|
- same as degraded
|
|
- last good snapshot may be reused briefly if not stale beyond TTL
|
|
|
|
### Failure mode: login start failure
|
|
|
|
Behavior:
|
|
|
|
- `login.state = failed`
|
|
- snapshot remains refreshable
|
|
|
|
### Failure mode: unsafe auth URL
|
|
|
|
Behavior:
|
|
|
|
- `login.state = failed`
|
|
- do not attempt browser open
|
|
- surface a security-oriented error category rather than a generic browser failure
|
|
|
|
### Failure mode: browser open failure
|
|
|
|
Behavior:
|
|
|
|
- `login.state = failed`
|
|
- no implicit retry loop
|
|
- preserve returned login metadata in memory long enough to support explicit retry or diagnostics
|
|
|
|
### Failure mode: login completed false
|
|
|
|
Behavior:
|
|
|
|
- keep explicit error message
|
|
- invalidate login session
|
|
- refresh snapshot once
|
|
|
|
### Failure mode: logout failure
|
|
|
|
Behavior:
|
|
|
|
- keep current snapshot
|
|
- surface error
|
|
|
|
### Failure mode: app restart or shutdown during pending login
|
|
|
|
Behavior:
|
|
|
|
- next app session starts from `idle`
|
|
- no persisted phantom pending state
|
|
- fresh snapshot determines whether login actually completed elsewhere or must be retried
|
|
|
|
### Failure mode: rate-limits failure
|
|
|
|
Behavior:
|
|
|
|
- degrade only the rate-limit panel
|
|
- do not mark account disconnected
|
|
|
|
## Diagnostics And Logging Policy
|
|
|
|
We want enough observability to debug auth issues, but not enough to leak secrets or create false
|
|
confidence from logs.
|
|
|
|
### Recommended structured events
|
|
|
|
Add additive logs around:
|
|
|
|
- account snapshot refresh started
|
|
- account snapshot refresh settled
|
|
- login started
|
|
- login browser open attempted
|
|
- login completed
|
|
- login cancelled
|
|
- logout started
|
|
- logout settled
|
|
- launch readiness resolved
|
|
- execution auth mode resolved
|
|
- degraded account read
|
|
|
|
### Safe fields to log
|
|
|
|
- provider id
|
|
- backend id
|
|
- preferred auth mode
|
|
- effective auth mode
|
|
- snapshot state
|
|
- readiness state
|
|
- requiresOpenaiAuth
|
|
- binary available
|
|
- degraded reason category
|
|
|
|
### Fields to avoid logging verbatim
|
|
|
|
- `authUrl`
|
|
- API keys
|
|
- refresh tokens
|
|
- full account email if logging policy treats it as sensitive
|
|
- raw `loginId` unless support/debug mode explicitly needs it
|
|
|
|
Recommended compromise for email:
|
|
|
|
- either do not log it
|
|
- or log only a redacted form for support diagnostics
|
|
|
|
Recommended compromise for `loginId`:
|
|
|
|
- either do not log it
|
|
- or log only a short fingerprint / suffix that cannot be used as a live control token
|
|
|
|
Recommended compromise for `authUrl`:
|
|
|
|
- log at most:
|
|
- URL scheme
|
|
- hostname
|
|
- whether validation passed
|
|
- never log query parameters or full path verbatim
|
|
|
|
### Important anti-lie rule
|
|
|
|
Logs must distinguish:
|
|
|
|
- `app-server degraded`
|
|
- `managed account absent`
|
|
- `execution failed`
|
|
|
|
These are different operational truths and must not be collapsed into one generic auth error.
|
|
|
|
## Telemetry And Operational Metrics
|
|
|
|
We do not need heavy telemetry to build the feature, but we should design enough observability to
|
|
see whether rollout is healthy.
|
|
|
|
### Recommended counters
|
|
|
|
- `codex_account_snapshot_refresh_total`
|
|
- `codex_account_snapshot_refresh_degraded_total`
|
|
- `codex_account_login_start_total`
|
|
- `codex_account_login_success_total`
|
|
- `codex_account_login_failure_total`
|
|
- `codex_account_login_cancel_total`
|
|
- `codex_account_logout_total`
|
|
- `codex_account_launch_ready_chatgpt_total`
|
|
- `codex_account_launch_ready_api_key_total`
|
|
- `codex_account_launch_missing_auth_total`
|
|
|
|
### Recommended timers / histograms
|
|
|
|
- snapshot refresh latency
|
|
- app-server initialize latency
|
|
- `account/read` latency
|
|
- login time to completion
|
|
- logout latency
|
|
|
|
### Recommended rollout health signals
|
|
|
|
During rollout, we should watch for:
|
|
|
|
- degraded refresh rate
|
|
- login failure rate
|
|
- mismatch rate between expected and effective auth mode
|
|
- launch failures after a `ready_chatgpt` decision
|
|
|
|
### Anti-goal
|
|
|
|
Do not let telemetry become a hidden source of truth for auth semantics.
|
|
|
|
It is for diagnosis only, not for deciding whether the user is connected.
|
|
|
|
## Troubleshooting Playbook
|
|
|
|
When this feature misbehaves, these are the most likely symptom clusters and where to look first.
|
|
|
|
### Symptom - UI says ChatGPT account connected, but launch behaves like API key
|
|
|
|
Check:
|
|
|
|
- ChatGPT launch env sanitization
|
|
- `forced_login_method="chatgpt"` override actually being passed
|
|
- whether ambient `OPENAI_API_KEY` or `CODEX_API_KEY` survived into exec env
|
|
|
|
Likely owner:
|
|
|
|
- `CodexConnectionCoordinator`
|
|
- `CodexAccountEnvBuilder`
|
|
|
|
### Symptom - UI loses account state after a transient refresh failure
|
|
|
|
Check:
|
|
|
|
- degraded-path handling
|
|
- freshness window logic
|
|
- last-known-good snapshot clearing behavior
|
|
|
|
Likely owner:
|
|
|
|
- snapshot merge use case
|
|
- cache / freshness policy
|
|
|
|
### Symptom - login opens browser twice or gets stuck pending
|
|
|
|
Check:
|
|
|
|
- login session exclusivity guard
|
|
- duplicate-click handling
|
|
- notification delivery and timeout cleanup
|
|
|
|
Likely owner:
|
|
|
|
- `CodexLoginSessionManager`
|
|
|
|
### Symptom - login never resumes after app restart
|
|
|
|
Check:
|
|
|
|
- whether pending login was incorrectly persisted
|
|
- whether startup incorrectly restored stale renderer-local login state
|
|
- whether the first fresh snapshot after restart was skipped
|
|
|
|
Likely owner:
|
|
|
|
- startup recovery policy
|
|
- main-process feature initialization
|
|
|
|
### Symptom - cancel appears to work, but account still becomes connected
|
|
|
|
Check:
|
|
|
|
- whether cancel raced with a login that had already completed upstream
|
|
- whether the final post-cancel snapshot was used as the source of truth
|
|
- whether UI incorrectly treated cancel intent as stronger than fresh steady-state truth
|
|
|
|
Likely owner:
|
|
|
|
- operation serialization policy
|
|
- login session manager
|
|
- post-mutation snapshot settlement
|
|
|
|
### Symptom - renderer surfaces disagree about current state
|
|
|
|
Check:
|
|
|
|
- feature view-model adapters
|
|
- whether one surface is reading raw provider status instead of the feature snapshot
|
|
- whether stale event ordering is overriding newer state
|
|
|
|
Likely owner:
|
|
|
|
- renderer adapters / hooks
|
|
- shell composition boundary
|
|
|
|
### Symptom - app-server sees account, but exec does not
|
|
|
|
Check:
|
|
|
|
- resolved `HOME`
|
|
- resolved `USERPROFILE`
|
|
- resolved `CODEX_HOME`
|
|
- whether app-server and exec are built from the same auth-root normalization path
|
|
|
|
Likely owner:
|
|
|
|
- env builder
|
|
- shell env source adapter
|
|
|
|
## Security And Privacy Rules
|
|
|
|
These are non-negotiable.
|
|
|
|
1. Do not parse `auth.json`.
|
|
2. Do not copy Codex-managed tokens into app storage.
|
|
3. Do not log auth URLs verbatim if they may include sensitive values.
|
|
4. Do not persist login session ids beyond process lifetime.
|
|
5. Keep account metadata in memory by default.
|
|
6. Keep API keys in the app's existing secure storage only.
|
|
7. Do not use `account/login/start { type: "apiKey" }` in the first implementation.
|
|
8. Do not send raw `authUrl` over IPC to renderer.
|
|
9. Do not persist `loginId` or pending login state across restarts.
|
|
10. Validate login URLs with a feature-specific `https`-only policy before browser open.
|
|
|
|
### Why we should not use app-server API-key login mode
|
|
|
|
Because it would create two overlapping secret owners:
|
|
|
|
- app secure storage
|
|
- Codex internal storage
|
|
|
|
That violates:
|
|
|
|
- DRY
|
|
- single responsibility
|
|
- clear ownership
|
|
|
|
So the rule is:
|
|
|
|
- managed ChatGPT account is owned by Codex
|
|
- API key is owned by the app
|
|
|
|
## Security Review Checklist
|
|
|
|
Before rollout, the implementation should be reviewed against this checklist.
|
|
|
|
### Secret handling
|
|
|
|
- no API key written into feature logs
|
|
- no auth URL logged verbatim
|
|
- no ChatGPT managed token copied into app storage
|
|
- no legacy auth files parsed directly
|
|
|
|
### Process/env handling
|
|
|
|
- ChatGPT launches strip `OPENAI_API_KEY`
|
|
- ChatGPT launches strip `CODEX_API_KEY`
|
|
- API-key launches inject only the intended key
|
|
- app-server and exec use the same auth-store env roots
|
|
|
|
### Persistence handling
|
|
|
|
- only `preferredAuthMode` is persisted for Codex connection preference
|
|
- login ids are not persisted across process lifetime
|
|
- last-known-good managed account cache is memory-only
|
|
|
|
### UI honesty
|
|
|
|
- degraded control-plane state is not shown as confirmed logout
|
|
- API-key availability is not shown as managed-account connection
|
|
- managed-account connection is not shown as API-key billing
|
|
|
|
### Rollout safety
|
|
|
|
- no hidden automatic fallback from ChatGPT launch failure to API-key launch
|
|
- no normal UI path silently invokes legacy Codex transport
|
|
|
|
## Testing Strategy
|
|
|
|
## Unit tests - feature core
|
|
|
|
Add tests for:
|
|
|
|
- managed account + API key merge rules
|
|
- effective auth mode resolution
|
|
- launch readiness resolution
|
|
- degraded-state behavior
|
|
- migration defaults
|
|
|
|
## Unit tests - main infrastructure
|
|
|
|
Add tests for:
|
|
|
|
- `CodexAccountEnvBuilder`
|
|
- `CodexAccountAppServerClient`
|
|
- `CodexLoginSessionManager`
|
|
- cache TTL and single-flight behavior
|
|
- app-server timeout handling
|
|
- env sanitization rules
|
|
|
|
Critical cases:
|
|
|
|
1. managed account exists and ambient API key is present
|
|
2. ChatGPT mode must strip API keys
|
|
3. API-key mode must inject API key
|
|
4. account read must not inherit ambient API keys
|
|
5. same HOME / USERPROFILE / CODEX_HOME resolution used for read and exec
|
|
|
|
## Unit tests - renderer
|
|
|
|
Add tests for:
|
|
|
|
- ChatGPT connected state
|
|
- API-key only state
|
|
- both-available state
|
|
- degraded state
|
|
- runtime missing state
|
|
- login pending state
|
|
- plan type display
|
|
- rate-limit panel visibility
|
|
|
|
## Integration tests - shell
|
|
|
|
Update existing shell tests:
|
|
|
|
- `ProviderRuntimeSettingsDialog.test.ts`
|
|
- `CliStatusVisibility.test.ts`
|
|
- `providerAwareCliEnv.test.ts`
|
|
- `ProviderConnectionService.test.ts`
|
|
- `ClaudeMultimodelBridgeService.test.ts`
|
|
|
|
## Test Matrix - must-cover scenarios
|
|
|
|
This matrix should be used to ensure we are not only testing the happy path.
|
|
|
|
| Scenario | Snapshot expectation | Launch expectation | UI expectation | Test level |
|
|
| --- | --- | --- | --- | --- |
|
|
| Binary missing | `runtime_missing` | blocked | login disabled, missing runtime copy | unit + renderer |
|
|
| Managed account only | `managed_account_connected` | ChatGPT launch | plan/email visible | unit + integration |
|
|
| API key only | `api_key_available` | API-key launch | API key available copy | unit + integration |
|
|
| Both available with `auto` | `both_available` | ChatGPT launch | Auto prefers ChatGPT | unit + integration |
|
|
| Both available with `api_key` | `both_available` | API-key launch | API key preferred copy | unit + integration |
|
|
| Managed account detected but app-server degraded | `degraded` | warning launchable | degraded banner, not false logout | unit + integration |
|
|
| No auth and app-server degraded | `degraded` or `not_connected` depending freshness | blocked unless freshness rule applies | unable to verify copy | unit |
|
|
| App-server stable handshake incompatible | feature locked or degraded with incompatibility verdict | no misleading login affordance | update-runtime / incompatible-runtime copy | unit + integration |
|
|
| Login pending | `login_in_progress` or pending login state | no duplicate login start | cancel action visible | unit + renderer |
|
|
| Two renderer subscribers during one login flow | shared pending state in both surfaces | one login only | consistent pending/cancel UI in both places | unit + renderer integration |
|
|
| Pending login lost on app restart | fresh snapshot wins, no phantom pending state | login can be retried cleanly | informational recovery or silent idle reset | unit + integration |
|
|
| Unsafe login URL returned | login fails before open | no browser open side effect | explicit safe error state | unit |
|
|
| Cancel races with late login completion | freshest post-race snapshot wins | no forced false logout | pending clears into truthful connected or idle state | unit + integration |
|
|
| Logout races with stale pre-logout read | post-logout truth wins | no resurrection of old account | disconnected state remains stable | unit + integration |
|
|
| Corrupted legacy Codex config subtree | normalizes to safe default | no launch-preference crash | settings load with sane default copy | unit |
|
|
| Browser open failure | failed login state | no launch behavior change | explicit error surfaced | unit + renderer |
|
|
| Logout success | no managed account | auto may fall back to API key | UI clears managed account | integration |
|
|
| Managed workspace restriction | no phantom managed account | launch blocked with policy truth | workspace-restricted copy, no fake workspace switcher | unit + renderer |
|
|
| Stale slow read after fast successful read | latest successful snapshot preserved | none | no regression flicker | unit |
|
|
| Ambient API key present during ChatGPT launch | managed account still primary | keys stripped | no API-key-primary wording | unit + integration |
|
|
|
|
## Test doubles and harness requirements
|
|
|
|
To keep the implementation testable, we should plan the following fakes:
|
|
|
|
- fake app-server client for deterministic `account/read`, `account/logout`, and rate-limit reads
|
|
- fake app-server initialize handshake that can:
|
|
- succeed with stable account support
|
|
- succeed but reject required methods as incompatible
|
|
- return diagnostic `codexHome` / platform metadata
|
|
- fake login session transport that can emit:
|
|
- success
|
|
- failure
|
|
- timeout
|
|
- duplicate notification
|
|
- late success after cancel intent
|
|
- fake browser launcher that can:
|
|
- succeed
|
|
- fail
|
|
- fake login URL validator that can:
|
|
- accept valid `https` URLs
|
|
- reject unsafe schemes or malformed values
|
|
- fake clock for TTL and freshness testing
|
|
- fake API key source adapter
|
|
- fake shell env source for `HOME` / `USERPROFILE` / `CODEX_HOME` determinism
|
|
- fake config input fixtures covering:
|
|
- missing Codex subtree
|
|
- stale legacy keys
|
|
- malformed non-object Codex subtree
|
|
|
|
Important rule:
|
|
|
|
- do not make most tests spawn the real `codex app-server`
|
|
- reserve real app-server checks for live signoff and a very small number of high-value integration tests
|
|
|
|
## Live signoff
|
|
|
|
Required live or semi-live signoff:
|
|
|
|
1. already logged-in ChatGPT account autodetects without relogin
|
|
2. launch succeeds with ChatGPT auth and no API key
|
|
3. launch succeeds with API key mode
|
|
4. both-available state prefers ChatGPT in auto mode
|
|
5. chatgpt mode strips API keys from the exec env
|
|
6. login opens browser and completes
|
|
7. logout clears managed account state
|
|
8. initialize diagnostics capture the expected `codexHome` and compatibility verdict
|
|
9. if a managed-policy environment is available, workspace restriction surfaces policy-specific copy
|
|
10. restarting during a pending login does not leave the next app session stuck pending
|
|
8. app-server degradation does not falsely report logged out
|
|
9. app-server degradation does not falsely hard-block `codex exec`
|
|
|
|
## Manual QA And Failure Injection Checklist
|
|
|
|
In addition to automated tests, the following manual checks are high value because they exercise
|
|
real process boundaries and browser behavior.
|
|
|
|
### Happy-path manual checks
|
|
|
|
- open the app with an already logged-in ChatGPT-backed Codex account
|
|
- verify autodetect without relogin
|
|
- verify `auto` chooses ChatGPT
|
|
- verify explicit `api_key` preference still works when an API key exists
|
|
|
|
### Failure-injection manual checks
|
|
|
|
- break or suspend app-server startup and verify the UI shows `degraded`, not false logout
|
|
- remove the stored API key and verify `api_key` preference becomes `missing_auth`
|
|
- keep an ambient API key in the shell env and verify explicit ChatGPT launch still strips it
|
|
- trigger login and close/cancel before completion
|
|
- trigger login and force browser-open failure if possible via a fake or test harness
|
|
- logout and verify stale degraded reads do not resurrect the old account state
|
|
|
|
### Visual parity checks
|
|
|
|
- compare dashboard banner, settings section, and manage dialog for the same state
|
|
- verify wording consistency for:
|
|
- connected ChatGPT account
|
|
- API-key-only availability
|
|
- both available
|
|
- degraded
|
|
- runtime missing
|
|
|
|
## Signoff Artifacts And Evidence Discipline
|
|
|
|
To keep rollout transparent, each significant phase should leave behind explicit evidence.
|
|
|
|
### Recommended evidence file
|
|
|
|
Create and maintain:
|
|
|
|
- `docs/research/codex-app-server-account-signoff-evidence.md`
|
|
|
|
### What evidence should include
|
|
|
|
For each completed phase:
|
|
|
|
- date
|
|
- branch / commit SHA
|
|
- what scenarios were exercised
|
|
- what test suites were run
|
|
- what live manual checks were run
|
|
- whether any known gaps remain
|
|
|
|
### Minimum evidence snippets
|
|
|
|
Capture:
|
|
|
|
- one managed-account autodetect result
|
|
- one API-key-only result
|
|
- one both-available result
|
|
- one degraded-path result
|
|
- one ChatGPT launch proof with API keys absent from effective exec env
|
|
|
|
### Anti-handwave rule
|
|
|
|
Do not mark a phase "done" based only on:
|
|
|
|
- unit tests
|
|
- visual inspection
|
|
- a single happy-path local login
|
|
|
|
We need evidence that the failure and degraded paths behave truthfully too.
|
|
|
|
## Recommended Signoff Commands
|
|
|
|
These commands should be treated as the default signoff baseline for this repo unless implementation
|
|
details require a small adjustment.
|
|
|
|
### Targeted runtime and UI suite
|
|
|
|
```bash
|
|
pnpm test -- \
|
|
test/main/services/runtime/providerAwareCliEnv.test.ts \
|
|
test/main/services/runtime/ProviderConnectionService.test.ts \
|
|
test/main/services/runtime/ClaudeMultimodelBridgeService.test.ts \
|
|
test/main/ipc/configValidation.test.ts \
|
|
test/renderer/components/runtime/ProviderRuntimeSettingsDialog.test.ts \
|
|
test/renderer/components/runtime/providerConnectionUi.test.ts \
|
|
test/renderer/components/cli/CliStatusVisibility.test.ts
|
|
```
|
|
|
|
### Targeted `recent-projects` safety suite
|
|
|
|
```bash
|
|
pnpm test -- \
|
|
test/features/recent-projects/main/infrastructure/CodexAppServerClient.test.ts \
|
|
test/features/recent-projects/main/adapters/output/CodexRecentProjectsSourceAdapter.test.ts \
|
|
test/features/recent-projects/core/application/ListDashboardRecentProjectsUseCase.test.ts \
|
|
test/features/recent-projects/contracts/normalizeDashboardRecentProjectsPayload.test.ts \
|
|
test/features/recent-projects/renderer/adapters/RecentProjectsSectionAdapter.test.ts
|
|
```
|
|
|
|
### Typecheck baseline
|
|
|
|
```bash
|
|
pnpm typecheck
|
|
```
|
|
|
|
### Lint baseline
|
|
|
|
```bash
|
|
pnpm lint
|
|
```
|
|
|
|
### Full repo safety pass before wider rollout
|
|
|
|
```bash
|
|
pnpm test
|
|
pnpm typecheck
|
|
pnpm lint
|
|
```
|
|
|
|
### Live manual signoff
|
|
|
|
There is no single universal script for this yet, so the plan should expect a small manual desktop
|
|
signoff pass covering:
|
|
|
|
- autodetect existing ChatGPT account
|
|
- ChatGPT-backed launch without API key
|
|
- API-key-backed launch
|
|
- degraded-path truthfulness
|
|
- logout and stale-state non-resurrection
|
|
|
|
## Release Readiness Checklist
|
|
|
|
Before wider rollout or merge into the main delivery branch, the implementation should satisfy this
|
|
checklist end-to-end.
|
|
|
|
### Contract readiness
|
|
|
|
- feature contracts compile cleanly
|
|
- preload bridge matches contracts
|
|
- renderer hooks consume only the supported API surface
|
|
|
|
### Runtime readiness
|
|
|
|
- ChatGPT and API-key launches both work
|
|
- exec env sanitization is verified
|
|
- app-server degradation is truthful and non-destructive
|
|
|
|
### UI readiness
|
|
|
|
- dashboard, settings, and manage dialog agree on the same state
|
|
- Codex wording no longer flattens everything to API key
|
|
- terminal-modal Codex login path is removed or intentionally retained only per rollout policy
|
|
|
|
### Safety readiness
|
|
|
|
- security checklist is green
|
|
- troubleshooting playbook scenarios have at least been spot-checked
|
|
- signoff evidence file has current results
|
|
|
|
### Regression readiness
|
|
|
|
- targeted runtime/UI suites pass
|
|
- targeted `recent-projects` safety suite passes
|
|
- typecheck and lint pass
|
|
|
|
## Post-Release Triage Checklist
|
|
|
|
If issues appear after internal rollout, use this triage order before making design changes.
|
|
|
|
### 1. Determine the symptom class
|
|
|
|
- wrong UI state
|
|
- wrong launch auth mode
|
|
- login lifecycle bug
|
|
- degraded-state regression
|
|
- rate-limit-only issue
|
|
|
|
### 2. Confirm whether steady-state snapshot is correct
|
|
|
|
Ask:
|
|
|
|
- does a forced fresh snapshot show the right account truth?
|
|
|
|
If yes:
|
|
|
|
- the bug is more likely in event ordering, caching, or renderer composition
|
|
|
|
If no:
|
|
|
|
- the bug is more likely in control-plane reads, env routing, or migration/config
|
|
|
|
### 3. Confirm whether execution env matches launch decision
|
|
|
|
Ask:
|
|
|
|
- did the launch decision say `chatgpt` or `api_key`?
|
|
- did the actual exec env honor that?
|
|
|
|
### 4. Confirm whether auth-store roots match
|
|
|
|
Ask:
|
|
|
|
- did app-server and exec use the same `HOME` / `USERPROFILE` / `CODEX_HOME`?
|
|
|
|
### 5. Only then consider rollback or feature hiding
|
|
|
|
The first response should usually be:
|
|
|
|
- diagnose
|
|
- patch the narrow seam
|
|
|
|
not:
|
|
|
|
- broaden fallback
|
|
- revive legacy transport
|
|
|
|
## Rollout Plan
|
|
|
|
## Recommended rollout shape
|
|
|
|
1. land feature behind internal enablement
|
|
2. wire read-only autodetect first
|
|
3. wire launch policy next
|
|
4. wire login/logout UI after launch policy is correct
|
|
5. remove Codex terminal modal login path only after feature login is green
|
|
|
|
## Why this order matters
|
|
|
|
The most dangerous intermediate state is:
|
|
|
|
- UI claims subscription support
|
|
- runtime still hard-requires API key
|
|
|
|
So launch policy must land before or together with user-facing subscription affordances.
|
|
|
|
## Safe Disable And Rollback Policy
|
|
|
|
This feature should be additive enough that we can disable exposure without corrupting runtime
|
|
truth.
|
|
|
|
### Safe disable rule
|
|
|
|
If the feature must be temporarily hidden during rollout:
|
|
|
|
- hide Codex managed-account UI affordances
|
|
- keep persisted `preferredAuthMode` readable
|
|
- do not auto-rewrite user preference
|
|
- do not silently remap ChatGPT preference to API-key preference
|
|
|
|
### What rollback must not do
|
|
|
|
Do not respond to rollout stress by:
|
|
|
|
- reintroducing legacy Codex transport
|
|
- auto-falling back from failed ChatGPT launch to API key without user intent
|
|
- erasing Codex account metadata from config or UI state
|
|
|
|
### Acceptable temporary rollback shape
|
|
|
|
If we need a temporary rollback during early rollout:
|
|
|
|
- feature UI can be hidden or marked internal
|
|
- launch policy can remain additive and guarded
|
|
- the old terminal-modal Codex login path may remain only until the feature is fully proven
|
|
|
|
This preserves correctness without lying about runtime semantics.
|
|
|
|
## Remaining Open Questions And Recommended Defaults
|
|
|
|
The plan is intentionally decisive, but a few implementation choices can still remain configurable.
|
|
These should not block the first pass because we already have recommended defaults.
|
|
|
|
### Open question 1 - exact freshness window duration
|
|
|
|
Recommended default:
|
|
|
|
- `60 seconds`
|
|
|
|
Rationale:
|
|
|
|
- long enough to survive brief app-server instability
|
|
- short enough to avoid long-lived stale-account lies
|
|
|
|
### Open question 2 - whether rate limits should auto-load or stay lazy
|
|
|
|
Recommended default:
|
|
|
|
- lazy-load rate limits
|
|
|
|
Rationale:
|
|
|
|
- account summary and launch readiness are higher priority
|
|
- rate limits should not slow the base snapshot path
|
|
|
|
### Open question 3 - whether device-code login should land in phase 1
|
|
|
|
Recommended default:
|
|
|
|
- no, keep it deferred
|
|
|
|
Rationale:
|
|
|
|
- browser flow is more aligned with the intended UX
|
|
- device code adds scope without unblocking the primary desktop path
|
|
|
|
### Open question 4 - whether to expose degraded state as a separate badge vs text-only warning
|
|
|
|
Recommended default:
|
|
|
|
- keep degraded as explicit text/state first, add badge only if it materially improves clarity
|
|
|
|
Rationale:
|
|
|
|
- textual honesty is more important than visual flourish
|
|
|
|
### Open question 5 - whether to hard-pin a minimum Codex binary version
|
|
|
|
Recommended default:
|
|
|
|
- no hard semver pin for the first wave beyond diagnostics and troubleshooting
|
|
|
|
Rationale:
|
|
|
|
- protocol/capability handshake is a safer unlock mechanism than guessing from version strings
|
|
- hard pins are still possible later if rollout evidence shows a real compatibility floor
|
|
|
|
### Open question 6 - whether Codex account snapshot should be folded more deeply into generic runtime status
|
|
|
|
Recommended default:
|
|
|
|
- no, keep it as a separate feature snapshot and compose at the shell boundary
|
|
|
|
Rationale:
|
|
|
|
- preserves clean bounded contexts
|
|
- avoids poisoning generic provider contracts with Codex-specific semantics
|
|
|
|
### Open question 7 - whether to hard-allowlist ChatGPT login hostnames
|
|
|
|
Recommended default:
|
|
|
|
- no hard hostname allowlist in the first wave, only strict `https` validation plus redacted
|
|
diagnostics
|
|
|
|
Rationale:
|
|
|
|
- current docs and schema establish the existence of `authUrl`, but not a future-proof hostname
|
|
contract
|
|
- strict scheme validation gives strong safety without coupling the feature to undocumented host
|
|
choices
|
|
|
|
### Open question 8 - whether explicit user refresh should immediately escalate to `refreshToken = true`
|
|
|
|
Recommended default:
|
|
|
|
- no, start with a normal read and escalate only on concrete auth-staleness evidence
|
|
|
|
Rationale:
|
|
|
|
- keeps manual refresh predictable without overusing token refresh as a blunt instrument
|
|
- preserves a clear distinction between ordinary state refresh and auth recovery
|
|
|
|
## Risk Register
|
|
|
|
This section lists the most important remaining implementation risks and how the plan contains them.
|
|
|
|
### Risk 1 - false subscription billing semantics
|
|
|
|
Failure shape:
|
|
|
|
- UI says ChatGPT account is active
|
|
- launch silently uses API key
|
|
|
|
Mitigation:
|
|
|
|
- ChatGPT execution must strip `OPENAI_API_KEY` and `CODEX_API_KEY`
|
|
- force `forced_login_method="chatgpt"`
|
|
- add dedicated tests for this exact case
|
|
|
|
### Risk 2 - split auth store between control plane and execution
|
|
|
|
Failure shape:
|
|
|
|
- app-server sees logged-in account
|
|
- `codex exec` uses different `HOME` / `CODEX_HOME`
|
|
|
|
Mitigation:
|
|
|
|
- centralize env resolution
|
|
- test exact parity of resolved auth store env for read/login/logout/exec
|
|
|
|
### Risk 3 - false logout on transient app-server failure
|
|
|
|
Failure shape:
|
|
|
|
- timeout or initialize failure
|
|
- UI collapses to `not connected`
|
|
|
|
Mitigation:
|
|
|
|
- degraded state is first-class
|
|
- freshness window can preserve last-known-good account truth temporarily
|
|
|
|
### Risk 4 - shell regains ownership and recreates coupling
|
|
|
|
Failure shape:
|
|
|
|
- banner/dialog/section each re-implement Codex logic differently
|
|
|
|
Mitigation:
|
|
|
|
- feature owns Codex semantics
|
|
- shell only hosts composition and generic layout
|
|
|
|
### Risk 5 - config drift creates silent preference flips
|
|
|
|
Failure shape:
|
|
|
|
- presence of API key or account implicitly changes user preference
|
|
|
|
Mitigation:
|
|
|
|
- explicit separation of persisted preference and observed availability
|
|
- one-way normalization with no inferred preference writes
|
|
|
|
### Risk 6 - rollout exposes login before launch policy is correct
|
|
|
|
Failure shape:
|
|
|
|
- user can log into ChatGPT in UI
|
|
- runtime still blocks without API key
|
|
|
|
Mitigation:
|
|
|
|
- rollout gate order enforces launch policy before or alongside subscription UX
|
|
|
|
### Risk 7 - over-expansion of scope into browser mode or apiKey app-server login
|
|
|
|
Failure shape:
|
|
|
|
- feature accumulates multiple control planes in first wave
|
|
|
|
Mitigation:
|
|
|
|
- browser mode explicitly deferred
|
|
- app-server `apiKey` login explicitly out of first implementation
|
|
|
|
## Follow-On Plan For `agent_teams_orchestrator`
|
|
|
|
This feature is intentionally scoped to `claude_team` first, but we should document the expected
|
|
later parity path now so the first implementation does not paint us into a corner.
|
|
|
|
### What should carry over later
|
|
|
|
- normalized Codex auth vocabulary
|
|
- `preferredAuthMode`
|
|
- `effectiveAuthMode`
|
|
- `degraded`
|
|
- managed-account vs API-key truth
|
|
- same launch-readiness semantics
|
|
- same exec env sanitization rules
|
|
- same no-silent-fallback rule
|
|
|
|
### What should not be copied blindly
|
|
|
|
- Electron-specific browser launch adapters
|
|
- preload contracts
|
|
- renderer hook design
|
|
|
|
### Suggested parity order
|
|
|
|
1. share only pure policy and data-shape concepts first
|
|
2. extract any provider-agnostic launch-readiness helpers only if duplication is real
|
|
3. keep transport/UI/process integration repo-specific
|
|
|
|
### Important guardrail
|
|
|
|
Do not prematurely contort the `claude_team` implementation around orchestrator parity if it makes
|
|
the desktop feature worse or harder to reason about.
|
|
|
|
## Phase Gates And Go / No-Go Rules
|
|
|
|
This section is the release-quality version of the rollout.
|
|
|
|
### Gate 1 - read-only truth is trustworthy
|
|
|
|
Must be true before exposing any new login affordance:
|
|
|
|
- autodetect works for already logged-in ChatGPT users
|
|
- API key availability still shows correctly
|
|
- degraded account reads do not erase last-known-good truth immediately
|
|
- no current non-Codex provider behavior regresses
|
|
|
|
### Gate 2 - launch policy is trustworthy
|
|
|
|
Must be true before defaulting UI toward subscription messaging:
|
|
|
|
- ChatGPT-backed launch works with no API key present
|
|
- ChatGPT-backed launch strips API keys from env
|
|
- API-key launch still works
|
|
- auto mode resolves deterministically and observably
|
|
|
|
### Gate 3 - interactive login is trustworthy
|
|
|
|
Must be true before removing terminal-modal login for Codex:
|
|
|
|
- browser login starts reliably
|
|
- duplicate clicks do not create duplicate sessions
|
|
- cancel works
|
|
- logout works
|
|
- stale cached snapshots do not reappear after login/logout transitions
|
|
|
|
### Gate 4 - UI parity is trustworthy
|
|
|
|
Must be true before calling the feature complete:
|
|
|
|
- no normal Codex UI path still says API key is the only connection mechanism
|
|
- dashboard and settings surfaces agree on account truth
|
|
- the rendered Codex panel uses the same account truth as launch policy
|
|
|
|
### Hard no-go conditions
|
|
|
|
Do not ship the feature if any of these remain true:
|
|
|
|
- ChatGPT mode can still execute with inherited API keys
|
|
- app-server and exec use different auth storage roots
|
|
- degraded app-server reads collapse to false logout
|
|
- `ProviderConnectionService` remains the final readiness authority for Codex
|
|
- Codex login in normal UI still routes through terminal modal commands
|
|
|
|
## Implementation Phases And Commit Boundaries
|
|
|
|
### Phase A - shared transport extraction
|
|
|
|
Goal:
|
|
|
|
- extract reusable app-server transport primitives from `recent-projects`
|
|
|
|
Estimated size:
|
|
|
|
- `250-450` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `refactor(codex-account): extract shared app-server transport`
|
|
|
|
Primary deliverables:
|
|
|
|
- extracted generic JSON-RPC stdio primitives
|
|
- no deep import from `recent-projects` into the new feature
|
|
- `recent-projects` still green on the extracted transport
|
|
|
|
Acceptance criteria:
|
|
|
|
- no behavior change for `recent-projects`
|
|
- extracted transport owns initialize/initialized handshake
|
|
- transport defaults are centralized in one place
|
|
|
|
### Phase B - feature skeleton and read-only snapshot
|
|
|
|
Goal:
|
|
|
|
- create the feature slice
|
|
- implement `account/read`
|
|
- implement snapshot DTO and IPC bridge
|
|
|
|
Estimated size:
|
|
|
|
- `450-750` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `feat(codex-account): add managed account snapshot feature`
|
|
|
|
Primary deliverables:
|
|
|
|
- feature slice exists with contracts, main, preload, renderer entrypoints
|
|
- `account/read` wired through feature IPC
|
|
- API key availability merged into snapshot
|
|
- cached snapshot and degraded state logic working
|
|
|
|
Acceptance criteria:
|
|
|
|
- already logged-in ChatGPT user is detected
|
|
- API-key-only user is detected
|
|
- degraded app-server read does not present false logout
|
|
|
|
### Phase C - config, migration, and renderer composition
|
|
|
|
Goal:
|
|
|
|
- add `preferredAuthMode`
|
|
- integrate feature panel into runtime settings and provider cards
|
|
|
|
Estimated size:
|
|
|
|
- `300-550` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `feat(codex-account): add codex auth preference and shell composition`
|
|
|
|
Primary deliverables:
|
|
|
|
- persisted `preferredAuthMode`
|
|
- config migration on load and save
|
|
- shell surfaces render the feature-owned Codex panel
|
|
|
|
Acceptance criteria:
|
|
|
|
- stale legacy Codex config normalizes forward
|
|
- renderer can display auto/chatgpt/api_key preference correctly
|
|
- generic shell layout remains unchanged for non-Codex providers
|
|
|
|
### Phase D - runtime launch policy integration
|
|
|
|
Goal:
|
|
|
|
- remove API-key-only hard gate
|
|
- add env sanitization and `forced_login_method` policy
|
|
|
|
Estimated size:
|
|
|
|
- `300-600` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `feat(codex-account): wire codex launch readiness policy`
|
|
|
|
Primary deliverables:
|
|
|
|
- Codex launch readiness no longer owned by API-key-only logic
|
|
- ChatGPT env sanitization wired
|
|
- `forced_login_method` wired per effective auth mode
|
|
|
|
Acceptance criteria:
|
|
|
|
- ChatGPT launch works without API key
|
|
- API-key launch still works
|
|
- ambient API keys cannot hijack ChatGPT launch mode
|
|
|
|
### Phase E - login, cancel, logout
|
|
|
|
Goal:
|
|
|
|
- full managed login lifecycle via app-server
|
|
|
|
Estimated size:
|
|
|
|
- `350-650` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `feat(codex-account): add codex app-server login lifecycle`
|
|
|
|
Primary deliverables:
|
|
|
|
- browser login flow
|
|
- cancel flow
|
|
- logout flow
|
|
- live login session manager with timeout and duplicate-click safety
|
|
|
|
Acceptance criteria:
|
|
|
|
- one login session at a time
|
|
- login success refreshes snapshot
|
|
- logout clears managed account state in UI
|
|
|
|
### Phase F - rate limits and final shell cleanup
|
|
|
|
Goal:
|
|
|
|
- add rate limits
|
|
- remove Codex terminal modal auth path
|
|
|
|
Estimated size:
|
|
|
|
- `150-300` lines
|
|
|
|
Suggested commit:
|
|
|
|
- `refactor(codex-account): finalize native codex account ui`
|
|
|
|
Primary deliverables:
|
|
|
|
- rate-limit panel
|
|
- final Codex copy cleanup
|
|
- terminal-modal Codex login/logout removed from normal UI paths
|
|
|
|
Acceptance criteria:
|
|
|
|
- dashboard and settings show consistent Codex account story
|
|
- plan type and rate limits are visible when available
|
|
- no normal Codex UI path still presents API key as the only connection method
|
|
|
|
## Phase-By-Phase Task Checklist
|
|
|
|
This section gives the recommended execution order inside each phase so implementation can proceed
|
|
with fewer ambiguous jumps.
|
|
|
|
### Phase A checklist
|
|
|
|
1. extract generic JSON-RPC stdio transport primitives out of `recent-projects`
|
|
2. move initialize/initialized handshake helpers into shared Codex app-server infrastructure
|
|
3. repoint `recent-projects` to the extracted transport
|
|
4. verify no behavior change in `recent-projects`
|
|
|
|
### Phase B checklist
|
|
|
|
1. scaffold `src/features/codex-account` public entrypoints
|
|
2. define DTOs, channels, and event contracts
|
|
3. implement `account/read` client
|
|
4. implement managed-account plus API-key merge logic
|
|
5. add cache and single-flight snapshot reads
|
|
6. register IPC and preload bridge
|
|
7. verify read-only snapshot in renderer/devtools path
|
|
|
|
### Phase C checklist
|
|
|
|
1. add `providerConnections.codex.preferredAuthMode` to config types
|
|
2. implement read-time and write-time normalization
|
|
3. update validation to the new shape
|
|
4. create feature-owned Codex panel and adapters
|
|
5. integrate panel into runtime settings/manage surfaces
|
|
6. remove API-key-only wording from Codex-specific renderer paths
|
|
|
|
### Phase D checklist
|
|
|
|
1. implement launch-readiness use case
|
|
2. implement auth-specific exec env policy
|
|
3. introduce runtime coordinator for Codex launch decisions
|
|
4. delegate Codex env assembly away from API-key-only logic
|
|
5. verify ChatGPT launch, API-key launch, and degraded-path behavior
|
|
|
|
### Phase E checklist
|
|
|
|
1. implement login session manager
|
|
2. implement browser login start flow
|
|
3. implement cancel flow
|
|
4. implement logout flow
|
|
5. wire login events into snapshot refresh and renderer subscriptions
|
|
6. verify duplicate-click safety and timeout behavior
|
|
|
|
### Phase F checklist
|
|
|
|
1. implement lazy rate-limit reads and cache
|
|
2. surface plan/rate-limit info in feature UI
|
|
3. remove normal Codex terminal-modal login/logout path
|
|
4. harmonize dashboard/settings/manage wording
|
|
5. capture final signoff evidence and residual known gaps
|
|
|
|
## PR Slicing And Review Discipline
|
|
|
|
Even with a strong plan, this feature can create bugs if we ship overly broad mixed PRs.
|
|
|
|
### Recommended slicing rule
|
|
|
|
Prefer one phase per PR when possible.
|
|
|
|
If a phase becomes too wide, split it by seam, not by random files.
|
|
|
|
Good split examples:
|
|
|
|
- transport extraction
|
|
- read-only snapshot and IPC
|
|
- config migration and shell composition
|
|
- runtime launch policy
|
|
- login lifecycle
|
|
- rate limits and cleanup
|
|
|
|
Bad split examples:
|
|
|
|
- "main changes" vs "renderer changes" when both are needed to make one behavior coherent
|
|
- mixing migration, login, and runtime policy into one giant PR
|
|
|
|
### Review checklist for each PR
|
|
|
|
Every PR should answer:
|
|
|
|
1. what is the new source of truth introduced or changed
|
|
2. what existing source of truth stops owning that behavior
|
|
3. what failure mode is newly covered
|
|
4. what migration risk exists
|
|
5. what tests prove the behavior
|
|
|
|
### Green-state rule
|
|
|
|
Each PR should leave the app in a coherent state:
|
|
|
|
- no UI promise without runtime support
|
|
- no runtime support hidden behind stale UI wording
|
|
- no half-migrated config shape exposed to renderer code
|
|
|
|
## Implementation Anti-Patterns To Avoid
|
|
|
|
These are the failure modes most likely to reintroduce the problems this plan is trying to solve.
|
|
|
|
### Anti-pattern 1 - generic shell service quietly regains Codex policy
|
|
|
|
Bad shape:
|
|
|
|
- `ProviderConnectionService` or `ClaudeMultimodelBridgeService` starts accumulating special-case
|
|
Codex account logic again
|
|
|
|
Why it is bad:
|
|
|
|
- recreates the coupling we are explicitly trying to remove
|
|
|
|
### Anti-pattern 2 - renderer computes business truth from badges and booleans
|
|
|
|
Bad shape:
|
|
|
|
- UI derives Codex auth semantics from generic `authenticated`, `authMethod`, or `statusMessage`
|
|
flags alone
|
|
|
|
Why it is bad:
|
|
|
|
- produces inconsistent copy and launch assumptions across surfaces
|
|
|
|
### Anti-pattern 3 - migration infers preference from availability
|
|
|
|
Bad shape:
|
|
|
|
- existing API key or managed account silently rewrites `preferredAuthMode`
|
|
|
|
Why it is bad:
|
|
|
|
- mutates user intent based on incidental environment state
|
|
|
|
### Anti-pattern 4 - degraded becomes a synonym for connected
|
|
|
|
Bad shape:
|
|
|
|
- stale evidence is treated as permanently sufficient proof of current auth state
|
|
|
|
Why it is bad:
|
|
|
|
- creates false readiness and false billing semantics
|
|
|
|
### Anti-pattern 5 - launch fallback becomes silent
|
|
|
|
Bad shape:
|
|
|
|
- failed ChatGPT launch silently retries with API key
|
|
|
|
Why it is bad:
|
|
|
|
- destroys trust in the runtime story and billing expectations
|
|
|
|
## Definition Of Done
|
|
|
|
This feature is done only when all of the following are true:
|
|
|
|
1. A previously logged-in ChatGPT Codex account autodetects automatically.
|
|
2. The UI clearly distinguishes managed account and API key availability.
|
|
3. `auto` mode works and prefers ChatGPT when available.
|
|
4. Launch policy no longer falsely requires API key when managed account exists.
|
|
5. ChatGPT-mode execution sanitizes API-key env vars.
|
|
6. API-key mode still works.
|
|
7. Login, cancel, and logout work from the real UI.
|
|
8. Codex terminal-login path is no longer used in normal UI flows.
|
|
9. `recent-projects` remains green.
|
|
10. Existing non-Codex provider UX remains unchanged.
|
|
|
|
## Explicit Rejections
|
|
|
|
The following approaches are intentionally rejected.
|
|
|
|
### Rejected - read `~/.codex/auth.json`
|
|
|
|
Reason:
|
|
|
|
- brittle
|
|
- storage backend may vary
|
|
- security-sensitive
|
|
|
|
### Rejected - revive legacy Codex OAuth transport
|
|
|
|
Reason:
|
|
|
|
- wrong architecture
|
|
- incompatible with native-only cutover intent
|
|
|
|
### Rejected - put all Codex account logic into `ProviderConnectionService`
|
|
|
|
Reason:
|
|
|
|
- violates SRP
|
|
- creates provider-specific policy blob
|
|
- fights the feature architecture standard
|
|
|
|
### Rejected - hard-block launch whenever app-server is degraded
|
|
|
|
Reason:
|
|
|
|
- false negative risk
|
|
- execution seam and account control plane are different
|
|
|
|
### Rejected - use app-server API-key login mode in the first wave
|
|
|
|
Reason:
|
|
|
|
- creates dual key stores
|
|
- unclear ownership
|
|
|
|
## Final Recommendation
|
|
|
|
Implement this as a full feature slice with:
|
|
|
|
- extracted shared app-server transport primitives
|
|
- app-server-managed account truth
|
|
- app-owned API key truth
|
|
- feature-owned launch-readiness policy
|
|
- shell integration through composition, not more Codex branches in shell components
|
|
|
|
The critical correctness points are:
|
|
|
|
- unify auth storage context across app-server and exec
|
|
- sanitize API-key env vars in ChatGPT mode
|
|
- do not let app-server degradation become a false execution blocker
|
|
|
|
If those three rules are followed, this plan should fit the orchestrator/UI architecture cleanly and restore the right Codex UX without reintroducing the old legacy path.
|