| @@ -0,0 +1,459 @@ |
| 1 | + # Codey inside T3 Code — implementation plan |
| 2 | + |
| 3 | + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | + |
| 5 | + **Goal:** Run Codey as an agent inside T3 Code without touching T3 Code's |
| 6 | + source. The user enables the Grok provider in **Settings → Providers**, sets |
| 7 | + **Binary path** to the `codey` binary, and gets Codey threads with streaming |
| 8 | + text, visible tool calls, working approvals and a stop button. |
| 9 | + |
| 10 | + **Architecture:** Codey grows a third front end alongside the TUI and `-p`. |
| 11 | + `internal/acp` owns the wire — newline-delimited JSON-RPC 2.0 over stdin and |
| 12 | + stdout, with the handful of ACP methods T3 Code actually exercises. |
| 13 | + `internal/serve` is the bridge: one `*agent.Agent` per ACP session, its |
| 14 | + `Observe` callback translated into `session/update` notifications and its |
| 15 | + `Approve` gate translated into `session/request_permission` round trips. |
| 16 | + `cmd/codey` grows an `agent stdio` verb plus the two shell probes T3 Code runs |
| 17 | + before it will show the provider as ready. Nothing in `internal/agent`, |
| 18 | + `internal/llm` or `internal/tui` changes in phase 1. |
| 19 | + |
| 20 | + **Tech Stack:** Go 1.26, stdlib only — `encoding/json`, `bufio`, `context`, |
| 21 | + `sync`. No ACP library exists for Go and the surface is five methods; hand- |
| 22 | + rolling it is smaller than a dependency. |
| 23 | + |
| 24 | + **Spec:** `docs/superpowers/specs/2026-09-19-t3code-provider-design.md` — read |
| 25 | + it first. It carries the evidence for why the Grok slot and not Codex's |
| 26 | + app-server, and the exact field-level contract quoted out of T3 Code's source. |
| 27 | + |
| 28 | + ## Global Constraints |
| 29 | + |
| 30 | + - Go module is `codey`; import paths are `codey/internal/...`. |
| 31 | + - **Stdout belongs to the protocol.** In `agent stdio` mode nothing may write |
| 32 | + to stdout except the JSON-RPC encoder — no `fmt.Println`, no metrics logger, |
| 33 | + no stray `log`. Diagnostics go to stderr, which T3 Code drains and (on the |
| 34 | + Grok path) discards. A single stray line desynchronises the parser and the |
| 35 | + thread dies with a decode error. |
| 36 | + - **`jsonrpc: "2.0"` is mandatory on every message we send.** T3 Code's ACP |
| 37 | + codec declares it `Schema.Literal("2.0")` on requests, notifications and |
| 38 | + responses (`packages/effect-acp/src/_internal/shared.ts:83-103`). This is the |
| 39 | + opposite of their Codex path, where the field must be absent — do not carry |
| 40 | + habits between the two. |
| 41 | + - **A notification must have no `id` key.** Not `null`, absent. |
| 42 | + - **Never leave an inbound request unanswered**, including methods we do not |
| 43 | + implement: answer `-32601`. T3 Code answers our requests on its read loop, so |
| 44 | + a hang on either side stalls the whole session. |
| 45 | + - T3 Code's inbound notification queue is `sliding(32)` |
| 46 | + (`packages/effect-acp/src/protocol.ts:89`). Coalesce text deltas rather than |
| 47 | + emitting one notification per token. |
| 48 | + - `internal/acp` imports no other `internal` package and knows nothing about |
| 49 | + agents: it is a JSON-RPC peer with ACP's types. `internal/serve` may import |
| 50 | + `internal/acp`, `internal/agent`, `internal/tools`, `internal/approval`; |
| 51 | + nothing imports `internal/serve` back except `cmd/codey`. |
| 52 | + - Every test is offline and in-process: a `net.Pipe` or an `io.Pipe` pair, no |
| 53 | + subprocess, no network, no sleeping on wall-clock time. |
| 54 | + - New exported identifiers get a doc comment in the existing house style. |
| 55 | + - Run `gofmt -w` on every file you touch, and `go test ./...` before each task |
| 56 | + is ticked off. |
| 57 | + |
| 58 | + --- |
| 59 | + |
| 60 | + ## Phase 1 — a text turn appears in T3 Code |
| 61 | + |
| 62 | + ### Task 1: `internal/acp` — types and the JSON-RPC peer |
| 63 | + |
| 64 | + **Files:** |
| 65 | + - Create: `internal/acp/wire.go`, `internal/acp/types.go`, `internal/acp/conn.go`, `internal/acp/conn_test.go` |
| 66 | + |
| 67 | + **Interfaces:** |
| 68 | + - Consumes: nothing. |
| 69 | + - Produces: |
| 70 | + ```go |
| 71 | + // wire.go |
| 72 | + type Message struct { |
| 73 | + JSONRPC string `json:"jsonrpc"` |
| 74 | + ID *json.RawMessage `json:"id,omitempty"` |
| 75 | + Method string `json:"method,omitempty"` |
| 76 | + Params json.RawMessage `json:"params,omitempty"` |
| 77 | + Result json.RawMessage `json:"result,omitempty"` |
| 78 | + Error *Error `json:"error,omitempty"` |
| 79 | + } |
| 80 | + type Error struct { Code int `json:"code"`; Message string `json:"message"`; Data any `json:"data,omitempty"` } |
| 81 | + |
| 82 | + const ( |
| 83 | + CodeParse = -32700; CodeInvalidRequest = -32600 |
| 84 | + CodeMethodNotFound = -32601; CodeInvalidParams = -32602; CodeInternal = -32603 |
| 85 | + ) |
| 86 | + |
| 87 | + // conn.go |
| 88 | + type Handler interface { |
| 89 | + Initialize(context.Context, InitializeRequest) (InitializeResponse, error) |
| 90 | + Authenticate(context.Context, AuthenticateRequest) error |
| 91 | + NewSession(context.Context, NewSessionRequest) (NewSessionResponse, error) |
| 92 | + Prompt(context.Context, PromptRequest) (PromptResponse, error) |
| 93 | + Cancel(context.Context, CancelParams) |
| 94 | + } |
| 95 | + type Conn struct{ /* unexported */ } |
| 96 | + func NewConn(r io.Reader, w io.Writer, h Handler) *Conn |
| 97 | + func (c *Conn) Serve(ctx context.Context) error // returns nil on clean EOF |
| 98 | + func (c *Conn) Notify(ctx context.Context, method string, params any) error |
| 99 | + func (c *Conn) Request(ctx context.Context, method string, params any, out any) error |
| 100 | + ``` |
| 101 | + |
| 102 | + `types.go` holds the ACP structs, spelled exactly as the spec quotes them: |
| 103 | + `InitializeRequest{ProtocolVersion int, ClientCapabilities, ClientInfo}`, |
| 104 | + `InitializeResponse{ProtocolVersion int, AgentInfo, AgentCapabilities, AuthMethods}`, |
| 105 | + `AuthenticateRequest{MethodID string}`, `NewSessionRequest{Cwd string, |
| 106 | + McpServers []json.RawMessage, AdditionalDirectories []string}`, |
| 107 | + `NewSessionResponse{SessionID string, Models *ModelState}`, |
| 108 | + `PromptRequest{SessionID string, Prompt []ContentBlock}`, |
| 109 | + `PromptResponse{StopReason string}`, `SessionNotification{SessionID string, |
| 110 | + Update Update}`, `RequestPermissionParams/Result`. |
| 111 | + `ContentBlock` is a tagged struct (`Type`, `Text`, `Data`, `MimeType`, `Path`) |
| 112 | + — decode leniently, we only read `text`. |
| 113 | + |
| 114 | + **Behaviour that the tests pin down:** |
| 115 | + |
| 116 | + Reader goroutine owns the decoder; **each inbound request is dispatched on its |
| 117 | + own goroutine** so that a `session/cancel` notification arriving mid-prompt is |
| 118 | + seen immediately. A single mutex serialises writes. Outbound request ids are |
| 119 | + integers from a counter; replies are correlated through a `map[string]chan` |
| 120 | + keyed by `string(id)`. |
| 121 | + |
| 122 | + - [ ] **Step 1: Write the failing tests** |
| 123 | + |
| 124 | + `conn_test.go` drives a `Conn` over an `io.Pipe` pair with a stub handler |
| 125 | + and asserts: every outbound line carries `"jsonrpc":"2.0"`; a notification |
| 126 | + line has no `id` key at all (assert on the raw bytes, not on a decoded map); |
| 127 | + a request for an unknown method comes back as `-32601` with the same `id`; a |
| 128 | + handler returning an error becomes `-32603` and does not kill the connection; |
| 129 | + malformed JSON on a line is answered `-32700` where an id can be recovered |
| 130 | + and skipped otherwise, and the loop survives; `Request` blocks until the |
| 131 | + matching response arrives and returns the decoded result; `Request` with a |
| 132 | + cancelled context returns `ctx.Err()` and drops the pending entry; a |
| 133 | + `session/cancel` notification is delivered while a `session/prompt` handler |
| 134 | + is still running; clean EOF on the reader returns `nil` from `Serve`. |
| 135 | + |
| 136 | + - [ ] **Step 2: Implement `wire.go` and `types.go`** |
| 137 | + |
| 138 | + Encoding is one compact JSON object per line. `ID` is kept as |
| 139 | + `json.RawMessage` so a string id and a numeric id both round-trip byte-exact. |
| 140 | + |
| 141 | + - [ ] **Step 3: Implement `conn.go`** |
| 142 | + |
| 143 | + - [ ] **Step 4: Verify** |
| 144 | + |
| 145 | + ```bash |
| 146 | + gofmt -w internal/acp && go test ./internal/acp/... |
| 147 | + ``` |
| 148 | + |
| 149 | + --- |
| 150 | + |
| 151 | + ### Task 2: `internal/serve` — the session bridge, text only |
| 152 | + |
| 153 | + **Files:** |
| 154 | + - Create: `internal/serve/serve.go`, `internal/serve/session.go`, `internal/serve/update.go`, `internal/serve/serve_test.go` |
| 155 | + |
| 156 | + **Interfaces:** |
| 157 | + - Consumes: `internal/acp`, `internal/agent`, `internal/tools`, `internal/metrics`. |
| 158 | + - Produces: |
| 159 | + ```go |
| 160 | + type Options struct { |
| 161 | + NewAgent func(root string) (*agent.Agent, error) // one agent per session |
| 162 | + Log *metrics.Logger |
| 163 | + } |
| 164 | + type Server struct{ /* unexported */ } |
| 165 | + func New(opts Options) *Server |
| 166 | + func (s *Server) Run(ctx context.Context, r io.Reader, w io.Writer) error |
| 167 | + ``` |
| 168 | + |
| 169 | + `NewAgent` is a factory taking the session's `cwd`, mirroring the |
| 170 | + `toolsIn(root)` factory `cmd/codey/main.go:286` already uses to rebuild a |
| 171 | + tool set against a worktree. T3 Code hands us a cwd per `session/new` and it |
| 172 | + is not necessarily the process's own. |
| 173 | + |
| 174 | + **Behaviour:** |
| 175 | + |
| 176 | + - `initialize` → `{protocolVersion: 1, agentInfo: {name: "codey", version}, |
| 177 | + agentCapabilities: {promptCapabilities: {image: true}}, authMethods: []}`. |
| 178 | + Accept any `protocolVersion` the client offers and answer with `1`; T3 Code |
| 179 | + sends `1` today (`acp/AcpSessionRuntime.ts:696-705`). |
| 180 | + - `authenticate` → `{}` for any `methodId`. T3 Code sends `cached_token` or |
| 181 | + `xai.api_key` and does not catch a failure here, so this must never error. |
| 182 | + - `session/new` → build an agent rooted at `params.cwd`, store it under a fresh |
| 183 | + session id (`codey-` + 16 random hex), reply `{sessionId}`. Ignore |
| 184 | + `mcpServers`. |
| 185 | + - `session/prompt` → concatenate the text blocks, run the agent, stream, reply |
| 186 | + `{stopReason: "end_turn"}`. `"cancelled"` if the session was cancelled, |
| 187 | + `"refusal"` on a hard agent error after emitting the error text as a chunk. |
| 188 | + - `session/cancel` → cancel that session's in-flight turn context. Idempotent, |
| 189 | + and harmless when nothing is running. |
| 190 | + |
| 191 | + `update.go` owns the translation, and is the file worth unit-testing hardest: |
| 192 | + |
| 193 | + | `agent.Event` | `session/update` | |
| 194 | + |---|---| |
| 195 | + | `EventTextDelta` | `agent_message_chunk` with `content: {type: "text", text}` | |
| 196 | + | `EventText` (non-streaming) | same, once | |
| 197 | + | everything else | dropped in phase 1 | |
| 198 | + |
| 199 | + Deltas are coalesced: buffer until 50 ms have passed or 512 bytes have |
| 200 | + accumulated, then flush one notification. This is what keeps us clear of T3 |
| 201 | + Code's `sliding(32)` queue on a fast model. |
| 202 | + |
| 203 | + - [ ] **Step 1: Write the failing tests** |
| 204 | + |
| 205 | + Drive `Server.Run` over pipes with a fake `NewAgent` whose `Run` emits a |
| 206 | + scripted `[]agent.Event`. Assert: the full handshake in order |
| 207 | + (`initialize` → `authenticate` → `session/new` → `session/prompt`) produces a |
| 208 | + session id and a `{stopReason:"end_turn"}`; the text arrives as |
| 209 | + `session/update` notifications carrying the session id and reassembling to |
| 210 | + the original string; deltas are coalesced rather than one-per-event; a |
| 211 | + `session/prompt` for an unknown session id is an error, not a panic; a |
| 212 | + `session/cancel` during a turn cancels the agent's context and the prompt |
| 213 | + replies `{stopReason:"cancelled"}`; two sessions run concurrently without |
| 214 | + their updates crossing over. |
| 215 | + |
| 216 | + - [ ] **Step 2: Implement `session.go`** — the session table, per-session |
| 217 | + mutex, per-turn cancel func. |
| 218 | + |
| 219 | + - [ ] **Step 3: Implement `update.go`** — the event translation and the |
| 220 | + coalescer. The coalescer takes a clock function so tests do not sleep. |
| 221 | + |
| 222 | + - [ ] **Step 4: Implement `serve.go`** — wire the handler methods onto the |
| 223 | + session table. |
| 224 | + |
| 225 | + - [ ] **Step 5: Verify** |
| 226 | + |
| 227 | + ```bash |
| 228 | + gofmt -w internal/serve && go test ./internal/serve/... |
| 229 | + ``` |
| 230 | + |
| 231 | + --- |
| 232 | + |
| 233 | + ### Task 3: `cmd/codey` — the `agent stdio` verb and the two probes |
| 234 | + |
| 235 | + **Files:** |
| 236 | + - Modify: `cmd/codey/main.go` |
| 237 | + - Create: `cmd/codey/agentcmd.go`, `cmd/codey/agentcmd_test.go` |
| 238 | + |
| 239 | + **Behaviour:** |
| 240 | + |
| 241 | + Argument parsing has to be positional-agnostic, because T3 Code splits its |
| 242 | + flags around the subcommand: `--permission-mode default agent stdio`, but |
| 243 | + `agent --always-approve stdio` (`acp/GrokAcpSupport.ts:33-46`). Scan `os.Args` |
| 244 | + for the bare words `agent` and `stdio` and for the three flags anywhere in the |
| 245 | + line; ignore anything else rather than erroring, since `launchArgs` is a free |
| 246 | + text field the user can put anything in. |
| 247 | + |
| 248 | + Three entry points: |
| 249 | + |
| 250 | + - `codey --version` → one line, `codey <semver>`, exit 0. Must match |
| 251 | + `/\bv?(\d+\.\d+\.\d+)\b/` (`apps/server/src/provider/providerSnapshot.ts:114-119`). |
| 252 | + Keep the existing `-v` verbose flag working; `--version` is new and distinct. |
| 253 | + - `codey models` → exit 0, and print, in this order: |
| 254 | + ``` |
| 255 | + You are logged in as <endpoint or account>. |
| 256 | + * codey-opus-5 (default) |
| 257 | + - codey-sonnet-5 |
| 258 | + ``` |
| 259 | + T3 Code reads login state off `/you are logged in/i` and models off |
| 260 | + `^\s*[*-]\s+(\S+)` bullet lines, with `(default)` marking the default |
| 261 | + (`Layers/GrokProvider.ts:258-286`). Source the list from the same |
| 262 | + `GET /v1/models` the TUI model picker uses; on failure print the configured |
| 263 | + model alone and still exit 0 — a non-zero exit makes T3 Code discard the |
| 264 | + whole listing. |
| 265 | + - `codey agent [--permission-mode X] [--always-approve] stdio` → build the |
| 266 | + `serve.Server` and run it on stdin/stdout. |
| 267 | + |
| 268 | + Permission modes map onto the existing `approval.Policy`: |
| 269 | + |
| 270 | + | T3 Code mode | Codey behaviour | |
| 271 | + |---|---| |
| 272 | + | `default` (or absent) | ask for every mutating tool | |
| 273 | + | `acceptEdits` | auto-approve `edit`/`write`, ask for `bash` | |
| 274 | + | `auto` | auto-approve all but ask for anything the policy calls non-rememberable | |
| 275 | + | `--always-approve` | the equivalent of `-yolo` | |
| 276 | + |
| 277 | + - [ ] **Step 1: Write the failing tests** |
| 278 | + |
| 279 | + `agentcmd_test.go` tests the argument scanner as a pure function over |
| 280 | + `[]string` — all five argv shapes T3 Code produces, plus junk from |
| 281 | + `launchArgs`, plus `--version` and `models`. Then a test that asserts the |
| 282 | + version line matches T3 Code's regex and the `models` output parses under a |
| 283 | + Go transliteration of their bullet parser. |
| 284 | + |
| 285 | + - [ ] **Step 2: Implement the scanner and the three entry points** |
| 286 | + |
| 287 | + Take the branch as early in `main` as possible — before the metrics logger |
| 288 | + opens, before the wizard, before anything can print to stdout. |
| 289 | + |
| 290 | + - [ ] **Step 3: Verify** |
| 291 | + |
| 292 | + ```bash |
| 293 | + gofmt -w ./cmd/codey && go test ./... && go build -o /tmp/codey ./cmd/codey |
| 294 | + /tmp/codey --version && /tmp/codey models |
| 295 | + ``` |
| 296 | + |
| 297 | + --- |
| 298 | + |
| 299 | + ### Task 4: End-to-end against the real T3 Code |
| 300 | + |
| 301 | + **Files:** |
| 302 | + - Create: `internal/serve/conformance_test.go` |
| 303 | + |
| 304 | + **Steps:** |
| 305 | + |
| 306 | + - [ ] **Step 1: A conformance test that replays T3 Code's exact handshake** |
| 307 | + |
| 308 | + Hard-code the literal JSON T3 Code sends — `initialize` with |
| 309 | + `{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false},"clientInfo":{"name":"t3-code","version":"0.0.0"}}`, |
| 310 | + then `authenticate` with `{"methodId":"cached_token"}`, then `session/new`, |
| 311 | + then `session/prompt` — and assert our replies decode against the shapes in |
| 312 | + the design doc. This file is the early-warning system for their churn: when |
| 313 | + a nightly breaks us, this is what tells us where. |
| 314 | + |
| 315 | + - [ ] **Step 2: Wire it up by hand** |
| 316 | + |
| 317 | + In T3 Code: Settings → Providers → Grok → enable, Binary path = the built |
| 318 | + `codey`, instance display name "Codey". Or edit `~/.t3/userdata/settings.json` |
| 319 | + directly (restart the app afterwards): |
| 320 | + |
| 321 | + ```json |
| 322 | + "providerInstances": { |
| 323 | + "codey": { |
| 324 | + "driver": "grok", |
| 325 | + "displayName": "Codey", |
| 326 | + "enabled": true, |
| 327 | + "config": { "binaryPath": "/home/joni/Work/GO/coding-harness/codey" }, |
| 328 | + "environment": [ |
| 329 | + { "name": "ANTHROPIC_API_KEY", "value": "sk-ant-...", "sensitive": true } |
| 330 | + ] |
| 331 | + } |
| 332 | + } |
| 333 | + ``` |
| 334 | + |
| 335 | + The per-instance `environment` array is how Codey gets its credential without |
| 336 | + inheriting whatever the desktop app was launched with. |
| 337 | + |
| 338 | + - [ ] **Step 3: Confirm the provider card and one turn** |
| 339 | + |
| 340 | + The card should read installed, authenticated, with our model list. Start a |
| 341 | + thread, ask something that needs no tools, and watch the text stream in. |
| 342 | + |
| 343 | + - [ ] **Step 4: Capture the traffic for the next phase** |
| 344 | + |
| 345 | + T3 Code logs every provider event to |
| 346 | + `~/.t3/userdata/logs/provider/events.<threadId>.log`, tagged `CANON:` for its |
| 347 | + normalised model and `NTIVE:` for the raw wire. That file is the ground truth |
| 348 | + for anything phase 2 gets wrong. |
| 349 | + |
| 350 | + --- |
| 351 | + |
| 352 | + ## Phase 2 — a first-class provider |
| 353 | + |
| 354 | + ### Task 5: Tool calls become visible |
| 355 | + |
| 356 | + **Files:** |
| 357 | + - Modify: `internal/agent/loop.go` (add `ID` to `Event`), `internal/serve/update.go` |
| 358 | + - Modify: `internal/agent/loop_test.go`, `internal/serve/serve_test.go` |
| 359 | + |
| 360 | + `agent.Event` carries `Tool string` but nothing pairing a start with its result |
| 361 | + (`internal/agent/loop.go:180-191`). ACP keys `tool_call` and `tool_call_update` |
| 362 | + by `toolCallId`. Pairing by order would work today because dispatch is |
| 363 | + sequential, but it is a landmine the first time it is not. |
| 364 | + |
| 365 | + - [ ] **Step 1:** Add `ID string` to `agent.Event`, populated from the |
| 366 | + `tool_use` block's id on `EventToolStart` and echoed on `EventToolResult`. |
| 367 | + Existing observers ignore it; the TUI is unaffected. |
| 368 | + - [ ] **Step 2:** Emit `tool_call` on start with |
| 369 | + `{toolCallId, title, kind, status: "pending", rawInput}` and |
| 370 | + `tool_call_update` on result with `status: "completed"|"failed"` and |
| 371 | + `content: [{type: "content", content: {type: "text", text}}]`. |
| 372 | + `kind` maps our tools onto ACP's vocabulary: `read`, `edit`, `execute`, |
| 373 | + `search`, `fetch`, `think`, `other`. |
| 374 | + - [ ] **Step 3:** Verify the tool rows render in T3 Code, and that a failed |
| 375 | + tool shows as failed rather than silently completing. |
| 376 | + |
| 377 | + ### Task 6: Approvals |
| 378 | + |
| 379 | + **Files:** |
| 380 | + - Modify: `internal/serve/serve.go`, `internal/serve/session.go` |
| 381 | + |
| 382 | + - [ ] **Step 1:** Implement an `approval.Prompt` that sends |
| 383 | + `session/request_permission` with |
| 384 | + `options: [{optionId:"allow-once", name:"Allow", kind:"allow_once"}, |
| 385 | + {optionId:"allow-always", name:"Always allow", kind:"allow_always"}, |
| 386 | + {optionId:"reject-once", name:"Reject", kind:"reject_once"}]` |
| 387 | + and blocks on the reply. Use exactly those three ids: Grok echoes back one of |
| 388 | + ours, but Cursor's adapter hardcodes those literals |
| 389 | + (`acp/AcpAdapterSupport.ts:46-55`), so choosing them now means the same binary |
| 390 | + works in a Cursor instance too. |
| 391 | + - [ ] **Step 2:** Map the reply — `selected`/`allow-always` → |
| 392 | + `approval.AlwaysThisCommand`, `selected`/`allow-once` → `Once`, |
| 393 | + `selected`/`reject-once` → `Deny`, `cancelled` → abort the turn. |
| 394 | + - [ ] **Step 3:** Respect the launch permission mode: in `--always-approve` no |
| 395 | + request is sent at all. |
| 396 | + - [ ] **Step 4:** Verify against T3 Code's approval panel, including the |
| 397 | + interaction with its own permission-mode selector, and confirm that |
| 398 | + cancelling a pending approval does not wedge the session. |
| 399 | + |
| 400 | + ### Task 7: Models, effort and the picker |
| 401 | + |
| 402 | + **Files:** |
| 403 | + - Modify: `internal/serve/serve.go`, `cmd/codey/agentcmd.go` |
| 404 | + |
| 405 | + - [ ] **Step 1:** Return the model list in `session/new` as |
| 406 | + `{models: {currentModelId, availableModels: [{modelId, name}]}}`. T3 Code's |
| 407 | + Grok path reads it (`acp/GrokAcpSupport.ts:124-154`) and prefers it over the |
| 408 | + `codey models` output. |
| 409 | + - [ ] **Step 2:** Handle `session/set_model` / `session/set_config_option` by |
| 410 | + switching the session's model, and reply `{}` to any config option we do not |
| 411 | + recognise rather than erroring — Cursor's adapter sends one unconditionally. |
| 412 | + - [ ] **Step 3:** Carry reasoning effort through `_meta.reasoningEffort` if it |
| 413 | + is present on the selected model. |
| 414 | + |
| 415 | + --- |
| 416 | + |
| 417 | + ## Phase 3 — polish, in rough order of value |
| 418 | + |
| 419 | + - [ ] **Reasoning.** Surface thinking blocks from the loop as |
| 420 | + `agent_thought_chunk`. Needs a new event kind in `internal/agent`; today the |
| 421 | + loop never reports them to observers. |
| 422 | + - [ ] **Diffs.** Emit `tool_call` content of kind `diff` with `path`, `oldText`, |
| 423 | + `newText` from `edit`/`write`, so T3 Code's diff viewer lights up instead of |
| 424 | + showing our text summary. |
| 425 | + - [ ] **Resume.** Advertise `agentCapabilities.sessionCapabilities.resume` and |
| 426 | + implement `session/load` on top of `agent.ResolveSession`, replaying the |
| 427 | + history as `session/update`s. Until we advertise it, T3 Code will not ask |
| 428 | + (`acp/AcpSessionRuntime.ts:726`), so this is purely additive. |
| 429 | + - [ ] **Plans.** Map our todo-ish output onto the `plan` update, or skip it — |
| 430 | + it is optional and we have no plan object today. |
| 431 | + - [ ] **Slash commands.** `available_commands_update` can expose `/compact` and |
| 432 | + the skills list in T3 Code's composer. |
| 433 | + - [ ] **Usage.** Feed token counts somewhere visible; the Grok path has no |
| 434 | + usage channel, so this may be the first thing that argues for the fork. |
| 435 | + - [ ] **README.** A "Using Codey from T3 Code" section: the settings recipe, |
| 436 | + what the Grok branding means, and the fact that it says Grok on the tin. |
| 437 | + |
| 438 | + --- |
| 439 | + |
| 440 | + ## How we will know it works |
| 441 | + |
| 442 | + 1. `go test ./...` green, including the conformance test. |
| 443 | + 2. `codey --version` and `codey models` satisfy T3 Code's parsers — asserted in |
| 444 | + Go, then observed on the provider card. |
| 445 | + 3. A thread in T3 Code: text streams, tool rows appear and complete, an |
| 446 | + approval prompt appears and is answerable, the stop button ends the turn, |
| 447 | + and the thread survives a second prompt in the same session. |
| 448 | + 4. `~/.t3/userdata/logs/provider/events.<threadId>.log` contains no decode |
| 449 | + errors and no `warning` rows we did not intend. |
| 450 | + |
| 451 | + ## Out of scope |
| 452 | + |
| 453 | + - Any change to T3 Code's source. The fork — a `codey` driver in |
| 454 | + `BUILT_IN_DRIVERS` — is discussed at the end of the design doc and stays a |
| 455 | + later decision. Nothing in this plan is wasted if we take it: the same ACP |
| 456 | + server is what such a driver would talk to. |
| 457 | + - The Codex app-server path. Specified in the design doc's appendix as the |
| 458 | + fallback, not built. |
| 459 | + - MCP. T3 Code offers us an MCP server on `session/new`; we ignore it. |