Gan-Xing/CodexNext

Your personal Codex control plane.

★ 0Forks 0TypeScriptGitHub ↗Compare

README

CodexNext

Your personal Codex control plane.

CodexNext turns codex app-server from a local CLI primitive into a browser-first control plane for real coding work. The long-term goal is not a chat wrapper or a model picker. It is a personal AI programming console where you can sign in from one Web entry, pair your own machines, open projects and sessions, control Codex turns, review approvals, recover history, and eventually use the same control surface from mobile clients.

The project started with a deliberately small Phase 1 smoke test: prove that a TypeScript client can control the real Codex app-server over stdio. It has since grown into a relay-first product path with Web login, device pairing, replayed session events, fast history switching, deployment tooling, and CodexProvider runtime support for non-default providers and models.

What CodexNext Is

CodexNext is a relay-first personal control plane for Codex app-server:

Browser / mobile Web / future mobile client
  -> CodexNext Web login
  -> Web HttpOnly cookie
  -> Web server relay bootstrap
  -> Control relay session
  -> Paired outbound agent
  -> Local Codex app-server on your machine

The normal product UX is relay-only. Users should not need an Agent URL, access token, ?agent=, ?token=, or ?ownerToken= in the browser.

Why It Exists

Codex CLI is powerful, but its native shape is local and terminal-centered. CodexNext builds the missing control surface around it:

  • one browser entry for multiple paired machines
  • real Codex sessions, history, turns, interrupts, steering, and approvals
  • local Codex execution that keeps sandboxing and permissions inside Codex
  • replay and recovery so refreshes, reconnects, and device changes do not lose the working context
  • provider/model configuration that belongs to the current device and runtime, not a hard-coded global UI setting
  • a shared relay-client boundary so Web and future mobile clients consume the same session, device, replay, and approval contracts

Current Status

CodexNext has implemented the original foundation phases and is now in the product-hardening stage.

  • Phase 1 local Codex app-server smoke test is implemented.
  • Phase 2 local interactive Web Console is implemented.
  • Phase 3 relay-first control plane is implemented.
  • Phase 3C runtime reliability and diagnostics gate is completed.
  • Shared Web/mobile relay-client boundaries are in progress.
  • Device-aware CodexProvider catalog and runtime checks are implemented.
  • In-session provider/model switching is implemented after the current turn completes, with active turns protected from unsafe runtime replacement.

What Works Today

  • Web login with an HttpOnly session cookie.
  • Relay-only browser bootstrap through the Web server.
  • Device pairing, device registry, presence, revoke, and outbound agent connections.
  • Remote control of local Codex app-server sessions from the Web UI.
  • Session creation, message sending, turn steering, interrupt, approvals, and history loading.
  • Replayable device events using device:replay for initial batches and device:event for live events.
  • Recent-first history loading and cached cold conversation switching.
  • Normalized Codex turn/item rendering, instead of flattening Codex data into lossy chat text.
  • CodexProvider runtime/catalog integration through the published provider package.
  • Provider/model selection scoped to the active device.
  • Same-session provider/model runtime updates after a turn completes.
  • Linux systemd, macOS launchd, and Windows WinSW deployment helpers.
  • codexnext doctor checks for local prerequisites, relay health, Web routing, agent health, Provider runtime/catalog, same-origin deployment, and public port exposure risks.

Included

  • packages/protocol
  • packages/codex-client
  • packages/relay-client
  • apps/agent
  • apps/control
  • apps/web
  • relay login gate
  • device pairing and revoke
  • recent-first history loading
  • shared recent-page cache for thread switching
  • docs and ADRs
  • codexnext doctor
  • codexnext goal-smoke
  • codexnext pair
  • codexnext connect

Not Included Yet

  • React Native / Expo mobile client
  • OAuth / passkeys
  • multi-user SaaS authorization
  • non-Codex CLIs
  • a rewritten Codex permission system

CodexNext intentionally does not replace Codex permissions. Permission mode, sandbox mode, approvals, and command execution enforcement remain inside the local Codex app-server.

Requirements

  • Node >= 24
  • pnpm
  • Codex CLI with codex app-server
  • a valid Codex login/session on each machine that will run an agent

Install

pnpm install

Verify

pnpm typecheck
pnpm test
pnpm --filter @codexnext/agent dev -- doctor
pnpm --filter @codexnext/agent dev -- doctor --relay https://<your-relay-host>
pnpm --filter @codexnext/agent dev -- doctor \
  --web https://<your-web-origin> \
  --relay https://<your-relay-host> \
  --require-same-origin \
  --require-agent \
  --require-provider \
  --expect-closed <public-host>:3002 \
  --expect-closed <public-host>:3922

Relay-Only Product Topology

Think in service roles, not in one fixed machine layout:

  • control
    • device presence
    • relay RPC
    • event replay
    • stale presence
    • pairing / revoke
    • audit log
  • web
    • login page
    • HttpOnly cookie session
    • relay session bootstrap
    • browser/mobile UI
  • agent
    • one controllable Codex machine
    • outbound connection to control
    • local Codex execution
    • approvals still enforced by Codex itself

Common topology:

  • one server runs control + web + agent
  • every additional machine runs agent
  • browsers and phones open only the Web URL

Start The Relay Control Plane

Generate a password hash for the Web login gate:

node -e 'const {randomBytes,scryptSync}=require("node:crypto");const password=process.argv[1];const salt=randomBytes(16);const hash=scryptSync(password,salt,64);console.log(`scrypt$${salt.toString("base64url")}$${hash.toString("base64url")}`)' "your-password"

Start the control server:

pnpm --filter @codexnext/control dev -- \
  --owner-token "$CODEXNEXT_OWNER_TOKEN" \
  --host 0.0.0.0 \
  --port 3922 \
  --production \
  --allow-origin https://your-web-origin.example

Start the Web app:

CODEXNEXT_RELAY_URL=http://127.0.0.1:3922 \
CODEXNEXT_OWNER_TOKEN="$CODEXNEXT_OWNER_TOKEN" \
CODEXNEXT_WEB_AUTH_PASSWORD_HASH="$CODEXNEXT_WEB_AUTH_PASSWORD_HASH" \
CODEXNEXT_WEB_SESSION_SECRET="$CODEXNEXT_WEB_SESSION_SECRET" \
CODEXNEXT_PUBLIC_ORIGIN=https://your-web-origin.example \
pnpm --filter @codexnext/web dev

Pair a machine into the relay:

pnpm --filter @codexnext/agent dev -- pair --relay https://<your-relay-host>

After pairing, the machine appears in the Web UI automatically.

Long-Running Deployment

For Linux systemd, macOS launchd, and service-role deployment examples, see docs/RELAY_DEPLOYMENT.md.

Bundled helpers currently cover:

  • Linux systemd: any subset of control,web,agent
  • macOS launchd: bundled helper currently targets agent
  • Windows WinSW: XML templates for control, web, and agent

Linux install examples:

./scripts/ops/install-linux-services.sh
./scripts/ops/install-linux-services.sh --roles agent
./scripts/ops/install-linux-services.sh --roles control,web

macOS agent install example:

./scripts/ops/install-macos-agent.sh

The Linux systemd installer auto-detects a compatible Node >= 24 + pnpm runtime path for the service user and writes it into the installed units. If agent is among the selected roles, the detected runtime must also support node:sqlite.

Startup helpers also self-heal PATH on launch. If the current environment does not provide a compatible Node >= 24 + pnpm runtime, they probe common locations such as PATH, ~/.local/share/pnpm, ~/.local/bin, ~/bin, and ~/.nvm/versions/node/*/bin. The agent helper also auto-discovers a usable codex binary from common locations.

Diagnostics

Use doctor before and after deployment:

pnpm --filter @codexnext/agent dev -- doctor
pnpm --filter @codexnext/agent dev -- doctor --relay https://<your-relay-host>
pnpm --filter @codexnext/agent dev -- doctor \
  --web https://<your-web-origin> \
  --relay https://<your-relay-host> \
  --require-same-origin \
  --require-agent \
  --require-provider \
  --expect-closed <public-host>:3002 \
  --expect-closed <public-host>:3922

Set CODEXNEXT_OWNER_TOKEN only in a trusted shell when using --require-agent or --require-provider; doctor exchanges it for a short relay session and never prints the raw token.

Doctor checks Node, pnpm, Codex CLI, device identity file permissions, relay health, Web/control env presence, production origin risks, Web auth status, relay session bootstrap routing, Socket.IO routing, Agent health, Provider runtime/catalog, expected-closed public ports, and hidden direct-mode env state. It reports secret presence and risk without printing raw token values.

Roadmap

The current roadmap keeps the original arc intact:

  • Finish the shared relay-client boundary for Web and mobile consumers.
  • Decide and scaffold the first mobile client path.
  • Make device, machine, session, thread, workspace, and browser-client identity rules explicit across multiple clients.
  • Harden conflict handling when several browser/mobile clients control the same device or session.
  • Continue Provider runtime work so configured providers and models are discoverable, device-aware, and safe to switch.
  • Expand slash-command workflows such as fast mode, MCP setup, personality, code review, initialization, and other CodexNext-specific commands.

See docs/ROADMAP.md for phase details.

Security Notes

  • Public relay Web requires login.
  • ownerToken is server-only.
  • Relay session tokens are issued after login and should not be persisted client-side.
  • Relay full-access follows Codex by default; set CODEXNEXT_DISABLE_RELAY_FULL_ACCESS=1 on the control server only if you intentionally want an extra relay-only safety gate.
  • Relay reconnect uses device:replay initial batches and device:event live events.
  • Approvals and sandbox enforcement remain Codex-native.
  • Shared relay client helpers define the Web/mobile replay auth boundary without storing owner or device tokens client-side.

Hidden Dev-Only Direct Mode

Direct mode is no longer part of the normal product path.

A hidden local troubleshooting path still exists for development only:

CODEXNEXT_ENABLE_DEV_DIRECT=1 pnpm --filter @codexnext/agent dev -- dev-serve --host 127.0.0.1 --port 17361

This command is intentionally hidden from normal UX and does not print tokenized Web URLs.

Maintainer Guardrails

These guardrails protect the shipped chat path and relay product surface. Keep them intact when changing chat state, history, provider runtime, sidebar, rendering, deployment, or diagnostics.

Conversation state guardrails
  • The chat canvas reads from the normalized conversation store as the only live source of truth. Derived history, sidebar rows, or legacy chatItems mirrors may reconcile into that store, but must not replace live/pending/streaming state wholesale.
  • Conversation identity is canonicalized by conversationKey = threadId ?? sessionId ?? pendingClientId. RPC acknowledgements may remap aliases from an optimistic key to a real session/thread key, but the UI must not fork a second independent conversation.
  • Sending a message must write a client message id, optimistic user message, and thinking placeholder immediately. The outbox state machine is pending -> sent -> streaming -> complete/failed; an RPC ack only means the backend accepted the turn, not that streaming is complete.
  • Socket replay/live events may append or reconcile entities by sequence, message id, turn id, and client message id. They must not silently switch the user's selected conversation, reorder the active thread, or overwrite a live pending turn.
  • History hydration is per-turn reconciliation. It may confirm completed turns and fill gaps, but it must preserve current live, pending, and streaming items unless the matching client message id, turn id, or message id proves replacement is correct.
  • Development tracing must cover submit intent, queued state, RPC start, ack, socket receive, reducer apply, stream seen, selected conversation render, and reconciliation or failure. Render traces must stay summarized; never log the full visible message list on every render.
Codex app-server semantic guardrails

CodexNext must preserve Codex app-server turn/item semantics. Do not collapse official app-server ThreadItem data into flat chat text as the integration boundary.

  • Status: completed - Protocol schemas use official turn fields: itemsView, status, error, startedAt, completedAt, and durationMs are required on app-server turns.
  • Status: completed - Protocol schemas require app-server item id and type, and expose item render classification for user, assistant, process, and metadata items.
  • Status: completed - Agent event adaptation records app-server item lifecycle, reasoning deltas, MCP progress, and process output as structured local events.
  • Status: completed - Historical thread/read / thread/turns/list data and realtime app-server notifications enter the same normalized turn store before chat rendering. Refresh, cold switching, replay, and live streaming must project from that store instead of maintaining separate history/live UI paths.
  • Status: completed - Submit reconciliation uses historical CodexThreadTurn.items to confirm submitted user input and later assistant or command responses. It must not decide delivery state from legacy flat LocalCodexHistoryMessage[] pages.
  • Status: completed - Web chat state no longer exposes flat message hydration helpers. History hydration and older-page prepending must use hydrateSessionFromTurns / prependSessionHistoryTurns with app-server turn data.
  • Status: completed - History hydration guards read normalized page state (historyPages[sourceKey]) through chat-state selectors. They must not scan ChatItem projection ids such as history-${sessionId}-....
  • Status: completed - History turn hydration and prepend reconciliation preserve local live items from the selected conversation projection only. They must not fall back to global workspace.chatItems.
  • Status: completed - Local submit state, thinking feedback, ack binding, agent errors, legacy assistant/command/diff deltas, and outbox recovery are represented as turn/items first. ChatItem is a projection for the current renderer, not a business-state write target for those flows.
  • Status: completed - TurnGroup is a read-only projection derived from normalized turns. It classifies userMessage as user input, process item types as process, agentMessage as answer, and carries status/timing without writing back to the store.
  • Status: completed - The chat canvas receives TurnGroup projections as the primary render input. Legacy ChatItem[] is only a projection fallback at the renderer boundary and must not become a second live source of truth.
  • Status: completed - Chat rendering uses ChatRenderItem as the renderer DTO. TurnGroupItem must not carry chatItem; render items are derived at the UI boundary and must never be written back to the turn store.
  • Status: completed - Chat header titles and the summary sheet read the selected conversation's TurnGroup projection. They must not accept ChatItem[] fallback inputs.
  • Status: completed - Sidebar session rows receive precomputed titles from per-session TurnGroup projections. Sidebar grouping must not inspect global chatItems.
  • Status: completed - Development render traces and turn completion checks read TurnGroup / normalized turn selectors. They must stay summary-only and must not scan global workspace.chatItems.
  • Status: completed - Completed turns with process items render a turn-level process summary such as 已处理 5m 58s; running or failed turns keep process, approval, and error rows visible. Assistant answer items remain expanded and are not hidden inside the process summary.
  • Status: completed - High-content blocks use a shared thin CollapsibleBlock wrapper. Markdown, code highlighting, diff parsing, and virtualization continue to use the installed render stack (react-markdown, remark-gfm, rehype-highlight, and @tanstack/react-virtual) instead of a custom renderer or a new dependency.
  • Status: completed - Model selection remains part of the start/resume/turn path. Schema or adapter work must not drop the selected model when switching or sending.
Conversation performance guardrails

Cold conversation switching is a product-critical path. The implemented architecture is local-first: clicking a conversation commits selection immediately, renders normalized in-memory or persisted cache first, and lets network history refresh run in the background.

  • Selection should commit in under 50 ms. Do not block the click path on getCodexHistoryTurns, listSessions, event replay, or history hydration.
  • Show the best local state first: in-memory conversation, persisted conversation cache, or a lightweight thread skeleton using sidebar title and preview metadata. Avoid empty waits for unopened conversations.
  • Persist a bounded recent normalized turn cache per conversation in IndexedDB, separate from the outbox and legacy ChatItem projections. The outbox is for unsent/in-flight recovery; it is not enough for fast cold thread switching.
  • Revalidate stale conversations in the background and merge by message id, turn id, and client message id. Users should not experience "loading the whole history" as the primary interaction.
  • Prefetch visible sidebar threads, pinned threads, and recent threads during idle time with a small concurrency budget.
  • Large histories must use virtualized rendering or an equivalent windowing strategy. The chat canvas must not render hundreds of Markdown/code-highlighted messages in one synchronous pass.
  • Development render logs must remain summary-only: selected key, message count, latest sequence, status counts, and latest item metadata. Do not log every visible message on each render.

All seven cold conversation switching requirements are implemented:

  • Status: completed - Conversation selection is local-first and must not await getCodexHistoryTurns, listSessions, replay, or history hydration.
  • Status: completed - The chat surface renders the best local state first: normalized in-memory conversation, persisted conversation cache, or a lightweight thread skeleton.
  • Status: completed - Recent conversation bodies are persisted in bounded IndexedDB cache as normalized turnOrder / turns data, separate from the outbox recovery layer. ChatItem[] is only a renderer fallback projection.
  • Status: completed - Network history refresh runs as background stale-while-revalidate and reconciles by message id, turn id, and client message id.
  • Status: completed - Sidebar history prefetch runs during idle time for visible, pinned, and recent threads with bounded concurrency.
  • Status: completed - Large chat histories use virtualized rendering so the UI does not synchronously render hundreds of Markdown/code-highlighted messages.
  • Status: completed - Development render logs are summary-only and must not write the full visible message list per render.
UX regression guardrails
  • Development-only Next.js route indicators must not cover mobile composer controls. Keep dev overlays away from the bottom-left composer action area.
  • Mobile chat states must prioritize the conversation viewport. Loading and empty states should be lightweight; they must not take over the screen like a desktop card.
  • Sidebar thread titles must be readable summaries. Terminal output, build logs, stack traces, and prompt noise should be collapsed into the user command or the most useful diagnostic line.
  • Fresh browsers with no localStorage must recover relay devices after Web session bootstrap. They should not strand the user on "connect device" when the relay already has online devices.
  • Sidebar action controls must remain discoverable: thread rows expose pin and archive with concise aria-label/title text, and mobile keeps the actions accessible without hover.
  • These UX rules are additive to the conversation state/performance guardrails above. Do not fix visual polish by weakening normalized conversation state, optimistic outbox, reconciliation, cache-first switching, virtualization, or summary-only dev traces.

Contributors

Gan-Xing

Issues