diff --git a/AGENTS.md b/AGENTS.md index 380a9202683b..7d30d98f8b6d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ ## Project Snapshot -T3 Code is a minimal web GUI for using coding agents like Codex and Claude. +T3 Code is a minimal web GUI for using coding agents like Codex, Claude, Cursor, Grok, and OpenCode. This repository is a VERY EARLY WIP. Proposing sweeping changes that improve long-term maintainability is encouraged. @@ -26,11 +26,18 @@ Long term maintainability is a core priority. If you add new functionality, firs ## Package Roles -- `apps/server`: Node.js WebSocket server. Wraps Codex app-server (JSON-RPC over stdio), serves the React web app, and manages provider sessions. +- `apps/server`: Node.js WebSocket server (`t3`). Wraps provider CLIs (Codex app-server over JSON-RPC, plus Claude, Cursor, Grok, and OpenCode), serves the React web app, and manages provider sessions. - `apps/web`: React/Vite UI. Owns session UX, conversation/event rendering, and client-side state. Connects to the server via WebSocket. +- `apps/desktop`: Electron shell. Spawns a desktop-scoped `t3` backend process and loads the shared web app. +- `apps/mobile`: Expo/React Native app. Connects to a paired T3 Code server over the network. +- `apps/marketing`: Astro marketing site (landing, download, and legal pages). - `packages/contracts`: Shared effect/Schema schemas and TypeScript contracts for provider events, WebSocket protocol, and model/session types. Keep this package schema-only — no runtime logic. - `packages/shared`: Shared runtime utilities consumed by both server and client applications. Uses explicit subpath exports (e.g. `@t3tools/shared/git`) — no barrel index. - `packages/client-runtime`: Shared runtime package for sharing client code across web and mobile. +- `packages/ssh`: SSH launch/tunneling utilities for desktop-managed remote environments. +- `packages/tailscale`: Tailscale detection and Serve integration for remote access endpoints. +- `packages/effect-acp`: Effect-based Agent Client Protocol (ACP) client used by ACP providers. +- `packages/effect-codex-app-server`: Effect-based client for the Codex app-server JSON-RPC protocol. ## Reference Repos @@ -47,7 +54,7 @@ agents. - Prefer examples and patterns from the vendored source code over generated guesses or web search results. - Do not edit files under `.repos/` unless explicitly asked. - Do not import from `.repos/`; application code must continue importing from normal package dependencies. -- Manage vendored subtrees with `bun run sync:repos`; use `bun run sync:repos --repo ` to sync one +- Manage vendored subtrees with `vp run sync:repos`; use `vp run sync:repos --repo ` to sync one configured repository. - When updating a dependency with a configured vendored subtree, sync that subtree in the same change so `.repos/` matches the installed dependency version. diff --git a/README.md b/README.md index 6aebfc7e8b85..7d7f88045c43 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # T3 Code -T3 Code is a minimal web GUI for coding agents (currently Codex, Claude, Cursor, and OpenCode, more coming soon). +T3 Code is a minimal web GUI for coding agents (currently Codex, Claude, Cursor, Grok, and OpenCode, more coming soon). ## Installation > [!WARNING] -> T3 Code currently supports Codex, Claude, Cursor, and OpenCode. +> T3 Code currently supports Codex, Claude, Cursor, Grok, and OpenCode. > Install and authenticate at least one provider before use: > > - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login` > - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login` > - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `cursor-agent login` +> - Grok: install [Grok CLI](https://docs.x.ai/build/cli/reference) and run `grok login` > - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login` ### Run without installing diff --git a/docs/README.md b/docs/README.md index db32ff8468f8..00de8aaa0a89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,6 @@ - [Architecture](./architecture/overview.md) - [T3 Connect](./cloud/t3-connect-clerk.md) - [Integrations](./integrations/source-control-providers.md) -- [Mobile](./mobile/app.md) - [Operations](./operations/ci.md) - [Providers](./providers/codex.md) - [Reference](./reference/encyclopedia.md) diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index a7b777fb7732..58e2005bc0e6 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,6 +1,6 @@ # Architecture -T3 Code runs as a **Node.js WebSocket server** that wraps `codex app-server` (JSON-RPC over stdio) and serves a React web app. +T3 Code runs as a **Node.js WebSocket server** that wraps coding-agent CLIs (Codex, Claude, Cursor, Grok, OpenCode) and serves a React web app. The diagrams below use Codex (`codex app-server`, JSON-RPC over stdio) as the example provider. ``` ┌─────────────────────────────────┐ @@ -19,9 +19,11 @@ T3 Code runs as a **Node.js WebSocket server** that wraps `codex app-server` (JS │ CheckpointReactor │ │ RuntimeReceiptBus │ └──────────┬──────────────────────┘ - │ JSON-RPC over stdio + │ stdio (JSON-RPC / ACP) ┌──────────▼──────────────────────┐ -│ codex app-server │ +│ provider CLIs │ +│ codex · claude · cursor-agent │ +│ grok · opencode │ └─────────────────────────────────┘ ``` @@ -31,7 +33,7 @@ T3 Code runs as a **Node.js WebSocket server** that wraps `codex app-server` (JS - **Server**: `apps/server` is the main coordinator. It serves the web app, accepts WebSocket requests, waits for startup readiness before welcoming clients, and sends all outbound pushes through a single ordered push path. -- **Provider runtime**: `codex app-server` does the actual provider/session work. The server talks to it over JSON-RPC on stdio and translates those runtime events into the app's orchestration model. +- **Provider runtime**: the provider CLIs (for example `codex app-server`) do the actual provider/session work. The server talks to them over stdio (JSON-RPC or ACP) and translates those runtime events into the app's orchestration model. See [providers.md](./providers.md) for the driver list. - **Background workers**: Long-running async flows such as runtime ingestion, command reaction, and checkpoint processing run as queue-backed workers. This keeps work ordered, reduces timing races, and gives tests a deterministic way to wait for the system to go idle. @@ -92,7 +94,7 @@ sequenceDiagram 1. A user action in the browser becomes a typed request through `WsTransport` and the browser API layer in `nativeApi`. 2. `wsServer` decodes that request using the shared WebSocket contracts in `ws.ts` and routes it to the right service. -3. [`ProviderService`][8] starts or resumes a session and talks to `codex app-server` over JSON-RPC on stdio. +3. [`ProviderService`][8] starts or resumes a session and talks to the provider CLI (for example `codex app-server` over JSON-RPC on stdio). 4. Provider-native events are pulled back into the server by [`ProviderRuntimeIngestion`][9], which converts them into orchestration events. 5. [`OrchestrationEngine`][10] persists those events, updates the read model, and exposes them as domain events. 6. `wsServer` pushes those updates to the browser through `ServerPushBus` on channels defined in [`orchestration.ts`][11]. diff --git a/docs/architecture/providers.md b/docs/architecture/providers.md index 794b6aa5d5e5..1a79d29b2b77 100644 --- a/docs/architecture/providers.md +++ b/docs/architecture/providers.md @@ -1,23 +1,31 @@ # Provider architecture -The web app communicates with the server via WebSocket using a simple JSON-RPC-style protocol: +The clients (web, desktop, mobile) communicate with the server over WebSocket using Effect RPC. +The RPC surface is defined schema-first in `packages/contracts/src/rpc.ts` (`WS_METHODS` plus the +orchestration methods from `ORCHESTRATION_WS_METHODS`), so requests, results, errors, and streamed +events are all schema-validated at the transport boundary. -- **Request/Response**: `{ id, method, params }` → `{ id, result }` or `{ id, error }` -- **Push events**: typed envelopes with `channel`, `sequence` (monotonic per connection), and channel-specific `data` +## Built-in providers -Push channels: `server.welcome`, `server.configUpdated`, `terminal.event`, `orchestration.domainEvent`. Payloads are schema-validated at the transport boundary (`wsTransport.ts`). Decode failures produce structured `WsDecodeDiagnostic` with `code`, `reason`, and path info. +Providers are implemented as `ProviderDriver`s in `apps/server/src/provider/Drivers/` and +registered in `builtInDrivers.ts`. The built-in drivers are: -Methods mirror the `NativeApi` interface defined in `@t3tools/contracts`: +- **Codex** — wraps `codex app-server` (JSON-RPC over stdio) +- **Claude** — wraps the Claude Code CLI +- **Cursor** — wraps `cursor-agent` via ACP +- **Grok** — wraps the Grok CLI via ACP (`grok agent stdio`) +- **OpenCode** — wraps the OpenCode CLI -- `providers.startSession`, `providers.sendTurn`, `providers.interruptTurn` -- `providers.respondToRequest`, `providers.stopSession` -- `shell.openInEditor`, `server.getConfig` - -Codex is the only implemented provider. `claudeCode` is reserved in contracts/UI. +Each driver probes its CLI on `PATH`, reports an availability/auth snapshot, and adapts the +provider's runtime events into the shared orchestration model. ## Client transport -`wsTransport.ts` manages connection state: `connecting` → `open` → `reconnecting` → `closed` → `disposed`. Outbound requests are queued while disconnected and flushed on reconnect. Inbound pushes are decoded and validated at the boundary, then cached per channel. Subscribers can opt into `replayLatest` to receive the last push on subscribe. +`packages/client-runtime` owns the client connection stack: `connection/supervisor.ts` manages +per-environment connection lifecycle (connect, retry with backoff, reconnect on wakeups), and +`rpc/client.ts` exposes typed RPC access on top of it. Outbound requests fail fast or wait for a +live session depending on the call site; inbound payloads are decoded against the contracts +schemas at the boundary. ## Server-side orchestration layers diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 2206d53ee580..01ac8abac5a5 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -2,20 +2,20 @@ ```bash # Development (with hot reload) -bun run dev +vp run dev # Desktop development -bun run dev:desktop +vp run dev:desktop # Desktop development on an isolated port set -T3CODE_DEV_INSTANCE=feature-xyz bun run dev:desktop +T3CODE_DEV_INSTANCE=feature-xyz vp run dev:desktop # Production -bun run build -bun run start +vp run build +vp run start # Build a shareable macOS .dmg (arm64 by default) -bun run dist:desktop:dmg +vp run dist:desktop:dmg # Or from any project directory after publishing: npx t3 diff --git a/docs/reference/scripts.md b/docs/reference/scripts.md index 6bdea2666652..d99099248c91 100644 --- a/docs/reference/scripts.md +++ b/docs/reference/scripts.md @@ -1,7 +1,7 @@ # Scripts - `vp run dev` — Starts contracts, server, and web in watch mode. -- `vp run dev:server` — Starts just the WebSocket server. The server process runs on Bun (`@effect/platform-bun` + `BunPtyAdapter`), but task running uses `vp run`. +- `vp run dev:server` — Starts just the WebSocket server (Node.js via `node --watch`). - `vp run dev:web` — Starts just the Vite dev server for the web app. - Dev commands default `T3CODE_HOME` to `~/.t3` — the same shared home the desktop/production app uses. Override with `--home-dir` (see below) to keep dev state separate. - Override server CLI-equivalent flags from root dev commands with `--`, for example: diff --git a/docs/reference/workspace-layout.md b/docs/reference/workspace-layout.md index be88f2b603b5..b754879015e4 100644 --- a/docs/reference/workspace-layout.md +++ b/docs/reference/workspace-layout.md @@ -1,7 +1,19 @@ # Workspace layout -- `/apps/server`: Node.js WebSocket server. Wraps Codex app-server, serves the built web app, and opens the browser on start. +## Apps + +- `/apps/server`: Node.js WebSocket server (`t3`). Wraps the provider CLIs, serves the built web app, and opens the browser on start. - `/apps/web`: React + Vite UI. Session control, conversation, and provider event rendering. Connects to the server via WebSocket. - `/apps/desktop`: Electron shell. Spawns a desktop-scoped `t3` backend process and loads the shared web app. +- `/apps/mobile`: Expo/React Native app. Connects to a paired T3 Code server over the network. +- `/apps/marketing`: Astro marketing site (landing, download, and legal pages). + +## Packages + - `/packages/contracts`: Shared effect/Schema schemas and TypeScript contracts for provider events, WebSocket protocol, and model/session types. -- `/packages/shared`: Shared runtime utilities consumed by both server and web. Uses explicit subpath exports (e.g. `@t3tools/shared/git`, `@t3tools/shared/DrainableWorker`) — no barrel index. +- `/packages/shared`: Shared runtime utilities consumed by both server and clients. Uses explicit subpath exports (e.g. `@t3tools/shared/git`, `@t3tools/shared/DrainableWorker`) — no barrel index. +- `/packages/client-runtime`: Shared client runtime (connection supervision, RPC client, state) used by web and mobile. +- `/packages/ssh`: SSH launch/tunneling utilities for desktop-managed remote environments. +- `/packages/tailscale`: Tailscale detection and Serve integration for remote access endpoints. +- `/packages/effect-acp`: Effect-based Agent Client Protocol (ACP) client used by ACP providers. +- `/packages/effect-codex-app-server`: Effect-based client for the Codex app-server JSON-RPC protocol. diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index 254aa92c6a05..dcdedc7b542f 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -19,25 +19,35 @@ See the full schema for more details: [`packages/contracts/src/keybindings.ts`]( ```json [ + { "key": "mod+b", "command": "sidebar.toggle" }, { "key": "mod+j", "command": "terminal.toggle" }, + { "key": "mod+alt+b", "command": "rightPanel.toggle" }, { "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" }, + { "key": "mod+shift+d", "command": "terminal.splitVertical", "when": "terminalFocus" }, { "key": "mod+n", "command": "terminal.new", "when": "terminalFocus" }, { "key": "mod+w", "command": "terminal.close", "when": "terminalFocus" }, + { "key": "mod+d", "command": "diff.toggle", "when": "!terminalFocus" }, { "key": "mod+shift+j", "command": "preview.toggle" }, { "key": "mod+r", "command": "preview.refresh", "when": "previewFocus" }, { "key": "mod+l", "command": "preview.focusUrl", "when": "previewFocus" }, { "key": "mod+=", "command": "preview.zoomIn", "when": "previewFocus" }, + { "key": "mod++", "command": "preview.zoomIn", "when": "previewFocus" }, { "key": "mod+-", "command": "preview.zoomOut", "when": "previewFocus" }, { "key": "mod+0", "command": "preview.resetZoom", "when": "previewFocus" }, { "key": "mod+k", "command": "commandPalette.toggle", "when": "!terminalFocus" }, { "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" }, { "key": "mod+shift+o", "command": "chat.new", "when": "!terminalFocus" }, { "key": "mod+shift+n", "command": "chat.newLocal", "when": "!terminalFocus" }, - { "key": "mod+o", "command": "editor.openFavorite" } + { "key": "mod+shift+m", "command": "modelPicker.toggle", "when": "!terminalFocus" }, + { "key": "mod+o", "command": "editor.openFavorite" }, + { "key": "mod+shift+[", "command": "thread.previous" }, + { "key": "mod+shift+]", "command": "thread.next" } ] ``` -For most up to date defaults, see [`DEFAULT_KEYBINDINGS` in `apps/server/src/keybindings.ts`](../../apps/server/src/keybindings.ts) +Plus `mod+1` … `mod+9` bound to `thread.jump.{n}` (and `modelPicker.jump.{n}` while the model picker is open). + +For most up to date defaults, see [`DEFAULT_KEYBINDINGS` in `packages/shared/src/keybindings.ts`](../../packages/shared/src/keybindings.ts) ## Configuration @@ -53,10 +63,14 @@ Invalid rules are ignored. Invalid config files are ignored. Warnings are logged ### Available Commands +- `sidebar.toggle`: open/close the sidebar +- `rightPanel.toggle`: open/close the right panel - `terminal.toggle`: open/close terminal drawer - `terminal.split`: split terminal (in focused terminal context by default) +- `terminal.splitVertical`: split terminal vertically (in focused terminal context by default) - `terminal.new`: create new terminal (in focused terminal context by default) - `terminal.close`: close/kill the focused terminal (in focused terminal context by default) +- `diff.toggle`: open/close the diff view - `preview.toggle`: open/close the in-app browser preview panel (desktop app only) - `preview.refresh`: reload the active preview tab (in focused preview context by default) - `preview.focusUrl`: focus the URL input of the preview panel (in focused preview context by default) @@ -67,6 +81,10 @@ Invalid rules are ignored. Invalid config files are ignored. Warnings are logged - `chat.new`: create a new chat thread preserving the active thread's branch/worktree state - `chat.newLocal`: create a new chat thread for the active project in a new environment (local/worktree determined by app settings (default `local`)) - `editor.openFavorite`: open current project/worktree in the last-used editor +- `modelPicker.toggle`: open or close the model picker +- `modelPicker.jump.{n}`: select the nth model picker entry (`n` from 1 to 9) +- `thread.previous` / `thread.next`: switch to the previous/next thread +- `thread.jump.{n}`: jump to the nth thread (`n` from 1 to 9) - `script.{id}.run`: run a project script by id (for example `script.test.run`) ### Key Syntax