# Selesai in T3 Code (ACP)

Selesai ships an Agent Client Protocol (ACP) server, `selesai-acp`. ACP hosts can start it as a local command, and T3 Code does exactly that through its **Local ACP command** provider: T3 speaks ACP to `selesai-acp`, and `selesai-acp` owns a normal Selesai RPC session. Models, providers, credentials, tools, extensions, MCP servers and transcripts all stay in Selesai.

**Tested target:** T3 Code v0.1.6 (source commit `2359643c`), with Selesai built from this repository and a local fake model. Every behavior described below was exercised in that setup, except the items listed under [Checks that were not run](#checks-that-were-not-run). Only that T3 version was tested.

## Requirements

- Selesai installed **on the machine that runs the T3 server**, under the same user account T3 runs as. T3 starts the command on the server, not in the browser.
- Node.js 22.19.0 or newer for the ACP process and for the Selesai child process it starts. The command runs with the T3 server's environment, and Selesai starts its child with `node` resolved from `PATH` (`src/modes/rpc/rpc-client.ts`). If the server's `node` is older, either put a newer Node first on the instance's `PATH` environment row, or set the command to a Node 22.19.0-or-newer binary and pass the absolute `dist/acp-entry.js` path as the first argument. The integration harness used the second form.
- Selesai provider credentials on that machine. Run `selesai` and use `/login`, or configure an API key through the environment or the auth file. See [Providers](providers.md).
- The T3 provider dialog has environment rows; use them when the T3 server's environment does not already contain the variables your Selesai install needs (for example `HOME`, a provider API key, or `SELESAI_CODING_AGENT_DIR`).

## Add Selesai to T3 Code

1. In T3 Code, open **Settings → Providers → Add provider**.
2. Choose **Show more**, then **Local ACP command**, then **Next**.
3. **Command:** `selesai-acp`, or the absolute path to `dist/acp-entry.js` inside your Selesai installation or checkout.
4. **Arguments:** add one row per literal argument, in order. For a pinned provider and model, four rows:

   | Row | Value |
   | --- | --- |
   | 1 | `--provider` |
   | 2 | `<provider>` |
   | 3 | `--model` |
   | 4 | `<model>` |

   `--session-dir <path>` is optional. Each flag and its value are separate argument rows; nothing is shell-split or quoted.
5. **Environment:** add rows only if the server needs them.
6. **Next**, set the display name to **Selesai**, then **Add instance**.
7. Open a thread, pick Selesai in the composer, and send a message.

**Windows:** T3 launches Local ACP commands without a shell, so it cannot run npm's `selesai-acp.cmd` or `.ps1` shims, and a bare `selesai-acp` is not found. Use `node` (a real executable) as the **Command** and the entry script as the **first argument row**. Run this once in a terminal and copy the rows it prints:

```
selesai-acp --print-t3-config
```

```
Command:
  node

Arguments (one row each, in order):
  1. C:\Users\<you>\AppData\Roaming\npm\node_modules\@selesai\code\dist\acp-entry.js
```

Add `--provider`/`--model` before `--print-t3-config` to include them in the printed rows.

Do **not** put `selesai --mode rpc` in the ACP command field. That is Selesai's own RPC mode and it is not an ACP server, so T3 cannot initialize it. `selesai-acp` is the ACP entry point, and it starts the RPC child itself.

With no `--provider`/`--model` arguments, the child uses Selesai's configured default model, and the model picker can switch it for the session. `selesai-acp --help` lists the accepted flags.

## Streaming and Stop

T3 v0.1.6 streams assistant text **by paragraph**: its `responseStreamingMode` setting defaults to `paragraph`, and its server publishes a streaming message only once the text contains a completed paragraph (a blank line, a list item, a heading, or a closing fence). As a result:

- A long single paragraph appears only when it is finished, or when the turn ends.
- An answer made of several paragraphs appears paragraph by paragraph while the model is still running.

This is T3's rendering rule, not a Selesai delay. A counter pass on that build showed live text rendering as soon as the text crossed a paragraph boundary.

**Stop generation** sends `session/cancel`. Selesai aborts the child's run and answers the ACP prompt with `cancelled`. In the real-T3 runs the ACP cancellation response was sent 57–109 ms after the click, T3 left the running state after 295–333 ms, and the next prompt in the same thread worked. After an interrupted turn T3 shows **Resume thread** instead of the normal submit button.

## Models and thinking

- The model picker lists Selesai's available models and sends `session/set_config_option` when you choose one. The next turn runs on the new model, and T3 receives the full option list again.
- The choice applies to **that Selesai session** and is recorded in its transcript. It does not rewrite Selesai's saved default model (`defaultModel`/`defaultProvider` in settings). A new T3 thread starts with the model from the instance's `--provider`/`--model` arguments, or with Selesai's configured default when those are omitted. (`src/modes/acp/adapter.ts` sends RPC `set_model`; `src/core/agent-session.ts` persists a default only when a caller asks it to, which only the TUI does.)
- **The thinking-level picker is not usable in T3 v0.1.6.** Selesai advertises the model's thinking levels (for the test run's reasoning model: `off`, `minimal`, `low`, `medium`, `high`, with the current value), but T3's UI drops those descriptors. With a reasoning default model T3 rendered no thinking control at all; with a non-reasoning default the only choice was `Off`. T3 never sent a `thinking_level` change, so Selesai's own configured default level applies. This is documented, not worked around.
- Configuration changes are refused while a prompt is running. T3 only changes them between turns.

## Plan and build mode

Selesai advertises two session modes, `build` (default) and `plan`. T3's composer shows a Plan toggle once you enable plan mode in T3's settings; switching it sends `session/set_mode`.

- **Plan mode is enforced inside the Selesai child.** It declares only the readers (`read`, `grep`, `find`, `ls`), `todo_write`, and MCP tools whose server marks them read-only, and it blocks every other tool call — `write`, `edit`, `bash`, PowerShell, unproven MCP tools, and custom or extension tools. A blocked call returns a "Plan mode is read-only" result to the model. In the real-T3 run a write was blocked with **no** permission request.
- **Build mode** restores the tool set the session started with and sends gated tools through the approval flow below.
- Plan mode follows T3's interaction mode. T3 re-applies it after a session starts or loads, and every new Selesai child starts in build mode.

## Approvals

In an ACP session, Selesai asks T3 before running any tool that plan mode does not allow: file writes and edits, shell commands, MCP tools that are not marked read-only, and custom or extension tools. The read-only tools listed above run without asking.

T3's runtime mode answers the request. The policy applies per turn, from the turn after you change it:

| T3 runtime mode | Edits (file writes) | Execute (commands) |
| --- | --- | --- |
| Supervised | asks | asks |
| Auto-accept edits | allowed, no prompt | asks |
| Full access | Selesai still sends the permission request; T3 answers `allow_once` with no UI | same |

Supervised, Auto-accept edits and Full access were exercised with a file write. The Execute column comes from T3's own policy code (`clientPolicy.ts`) and was **not** exercised with an execute tool by a Selesai run.

- In Supervised, the approval shows **Approve** and **Decline**; **Cancel** is under **More approval options**. Approve runs the tool. Decline and Cancel do not, and the model receives a "Tool approval was not granted" result.
- Selesai keeps **no grant memory**. Even **Always allow this session** (`allow_always`) is not remembered by Selesai: the next gated call asks again. T3 is the side that records a session-wide allow, if it offers one.
- Approvals fail closed. Decline, Cancel, Stop, a closed connection, a child exit, a timeout, a handler error, or an option that was not offered all leave the tool unrun.
- The approval channel is ACP-only and cannot be forged by an ordinary extension: the child checks the caller against the shipped fixed extension, and the channel is disabled outside ACP sessions. TUI, print and plain RPC behavior is unchanged.

## Extension dialogs

When an extension asks a question during a turn, T3 shows it in its **Input requested** panel. Selesai maps the dialog methods T3 can represent:

- `select` becomes a single-choice list.
- `confirm` becomes true/false.
- `input` and `editor` become a text answer.

All four forms were answered in the real-T3 runs.

- A `confirm` answer must be `true` or `false`. Free text is refused and treated as a cancel, and the extension sees the cancellation.
- Dialogs T3 cannot represent (`multiselect`, secret inputs, empty forms) and answers that do not match the offered schema are cancelled, so the extension never hangs.
- Extension dialogs are only available during a turn. A dialog raised while no turn is active is cancelled.
- There is **no dismiss button** for ACP questions in T3 v0.1.6; its ACP questions are live requests without a dismiss capability. To abandon a pending question, press **Stop**: Selesai cancels the dialog and the turn ends.

## Thinking, plan and usage display

- Reasoning reaches T3 as `agent_thought_chunk` updates. The real-T3 run recorded them on the wire; how T3 renders a thinking row was not asserted separately.
- The todo list reaches T3 as a `session/update` plan frame whose entries are `{content, status, priority}`. T3 decides how to display it; the real-T3 run asserted the plan frame itself, not a particular plan widget.
- The usage/context meter comes from `usage_update` frames and appears as T3's **Context window N% used** button after you enable T3's context-window meter setting (it is off by default). The display run measured `Context window 18% used`.
- In plan mode T3 also receives `current_mode_update`, so its mode toggle follows the session.

### Usage page: saved token totals and costs

The **Usage page is separate from the context meter**. In the inspected T3 build, it scans built-in providers' transcript directories; Local ACP instances are not Usage sources. Sending more ACP `usage_update` frames does not add historical usage there.

Selesai already saves Pi-compatible token and cost records. Reuse T3's existing Pi transcript reader:

1. Keep your **Selesai Local ACP** instance for chats. Add a separate **Pi** provider instance under **Settings → Providers**, named **Selesai usage**. No Pi installation or chat with that instance is required for transcript scanning; its executable-health warning does not gate the Usage reader.
2. On the **Pi instance**, add an Environment row with name `PI_CODING_AGENT_SESSION_DIR` and the **absolute path on the T3 server** to your Selesai sessions root. The default is `~/.selesai/agent/sessions` (expand `~` to your home directory). With a custom agent directory, use its `sessions` subdirectory.
3. If `--session-dir`, `SELESAI_CODING_AGENT_SESSION_DIR`, or Selesai's `sessionDir` setting overrides storage, point the reader at that directory instead. Relative overrides are resolved against the thread's project directory, not the terminal where setup is printed. Add a Pi instance for each distinct root you want included.
4. Refresh **Usage**, choose a date range covering your sessions, and include **Pi** in the provider filter.

`selesai-acp --print-t3-config` now prints this setup alongside the ACP command rows. Run it with the same flags and environment as the ACP instance; check any settings-based storage override separately. It only prints instructions—it does not modify T3 settings.

**Limits:** T3 labels these records **Pi**, even when the provider instance is named Selesai usage. The root includes saved Selesai CLI and ACP sessions, not just T3 chats. Existing history can be scanned without replaying prompts, exporting duplicate files, or changing the active chat provider. A dedicated **Selesai** label/source requires a change in T3 itself. This path was checked against the installed reader implementation, not through a browser UI run.

## Slash commands

Selesai advertises its prompt templates, commands and skill commands when a session starts or resumes. T3 keeps that list for the provider instance; type `/` in the composer to open the menu. Skills are advertised as `$name`, so they appear in T3's `$` menu; T3 sends a picked skill as plain `$name` text and Selesai rewrites a leading `$name` of a known skill to `/skill:name`. In the real-T3 run an advertised command ran with its argument.

The list is sent once per session start because Selesai has no change event for it. New or edited prompt templates, commands or skills appear in a new session or on resume, not in a running one.

**`/compact`** is the one built-in Selesai advertises as runnable from prompt text; it is listed as `compact` without the interactive-only marker. Sending `/compact` or `/compact <instructions>` runs manual compaction: the optional text becomes the summary focus, and the ACP prompt's `end_turn` response is sent only after compaction finishes. No assistant summary message is added to the transcript; the summary is stored as a compaction entry and the next turn continues from it. A failed compaction rejects that prompt with Selesai's generic rejected-prompt error; the detailed compaction reason is not copied into the ACP error, and the same session accepts the next prompt. Cancelling while the summary request is still pending answers `cancelled`, records no incomplete summary and leaves the session usable; cancellation is not a rollback, so a summary that already completed and persisted before the cancel arrived remains. Terminal-only built-ins (`/settings`, `/model`, `/tree`, `/quit` and the rest) keep `interactiveOnly: true` and are filtered out of the ACP command list, so they do not appear as dead entries. This was exercised against the built ACP server with a real RPC child and a local loopback model, not a fresh T3 UI run (see [Checks that were not run](#checks-that-were-not-run)).

Project prompt templates such as `.selesai/prompts/deploy.md` appear as `/deploy` only when the session runs in that project and the folder is trusted. T3 caches the command list per provider instance, not per project, so a session in another project can replace the menu. If a project command is missing, restart that project's agent session and send a normal message to reconnect before checking `/` again; refreshing provider status alone may use a probe outside the project. A known template can still be typed directly, but sending it executes its instructions—do not submit a release or deployment prompt just to test discovery.

**File changes.** A completed `edit` call carries a diff built from the unified patch the edit tool already produces, so T3 can draw it in the tool row. A `write` call carries the new file text and T3 shows it as an added file; an overwrite is not told apart from a new file. Failed calls and other tools carry no diff, and a patch larger than 64 KiB is left out (T3's own change summary still lists the file). Implemented in `src/modes/acp/mapping.ts` (`diffContent`).

## Images

Attach images in T3 as usual. Selesai accepts `image/png`, `image/jpeg`, `image/gif` and `image/webp`, up to 4 MiB per image and 8 MiB per prompt after decoding; anything larger or another type is rejected as an invalid prompt (`src/modes/acp/mapping.ts`). A png attachment was verified end to end.

An image-only message reaches Selesai as T3's placeholder text plus the image, so the model sees the image with a short "user attached …" text. That is expected.

## History: reopen, list and import

**Reopening** a thread that T3 created sends `session/load`. Selesai replays that session's transcript (user, assistant and thought chunks, in order, on the active branch) before it answers the load, and the thread then continues with its Selesai context and its model and mode restored.

Replay is display-only and bounded: at most 512 KiB of text, 128 KiB per message and 512 updates; a longer transcript keeps its newest messages. Tool calls, plans and mode changes are not replayed (`src/modes/acp/history.ts`).

**List and import:** open **Settings → the Selesai provider card → Native sessions → List sessions**. Choose the project in **Project for ACP sessions**; the list is limited to that project's directory and to 256 rows. **Import** creates a T3 thread bound to the chosen Selesai session.

- If the **Native sessions** section is missing, click **Refresh provider status** on the card. T3 does not re-probe the provider by itself after an interrupted status check, so the section stays hidden until a refresh completes.
- In the tested run the imported thread did not appear in the sidebar and T3 showed no link to it; it was reachable only at its own thread URL.
- T3 does not render the imported session's earlier turns, before or after the thread's first turn, so the imported thread starts visually empty. Its first turn still runs `session/load`, restores the Selesai context, and continues the conversation. Rendering imported history is best-effort in T3 v0.1.6.

A missing or mismatched session fails instead of creating a new transcript. If T3 reports `Resource not found` for a deleted Selesai session, run **Restart agent session** from the command palette and send the next prompt; on that fresh provider session T3 falls back to a new Selesai session.

## Fork

Selesai does not advertise ACP fork. **Fork from this response** in T3 still works: T3 uses its portable context handoff, which opens a new Selesai session and sends the earlier turns as handoff context together with your new message. The original session is untouched.

## MCP tools

T3 passes its own MCP servers to the Selesai session as stdio servers (command, arguments, environment), so T3's tools appear to the model as `mcp__<server>__<tool>` and run inside Selesai's child. In plan mode only MCP tools that the server marks read-only stay available. The real-T3 run saw T3's `t3-code` server advertised and the model called one of its tools.

Selesai does not advertise ACP MCP support, so T3 uses its stdio path. HTTP or SSE MCP entries are rejected as an invalid configuration.

## Stop, disconnects and failures

- **Stop** is described under [Streaming and Stop](#streaming-and-stop).
- **Model error:** T3 shows the error and the next turn works.
- **Child failure:** if the Selesai process exits, T3 shows a safe error instead of a partial answer, and no Selesai process is left behind.
- **Disconnect, or T3 stops the agent session:** the ACP connection closes, Selesai releases the session atomically, refuses new commands, cancels pending dialogs and approvals (denying the tool), and stops the child it owns. Background work is not continued for a closed connection; if Selesai's atomic release is refused by a blocker, it logs that background work may be lost on disconnect.
- T3 restarts the provider session in some situations. A transport restart inside the same provider session surfaces as an error until the provider session itself is restarted.

## Trust model

- The ACP server is a **local process** that T3 starts on the server machine with the permissions of the user running T3. It is not a sandbox: the Selesai child runs with that user's credentials, filesystem access and shell. See [Security](security.md).
- Approvals are the only trust change in an ACP session: gated tools wait for T3's answer and every non-allow outcome denies. Selesai keeps no allow-list of its own.
- T3's runtime mode is the authority. Full access answers Selesai's approval request automatically with no UI, so a missing prompt does not mean the tool did not ask.
- The approval channel cannot be raised by another extension, and it exists only in ACP sessions.
- Plan mode is a guardrail for the child's tools, not a sandbox; reads stay allowed.

## Checks that were not run

- Thinking-level selection in T3: the picker is unusable in v0.1.6, so a chosen level's effect on the next turn was not measured.
- Dialog dismiss in T3: no dismiss control exists; only Stop was tested.
- How T3 renders ACP thinking rows and the plan list: the frames were asserted, the widgets were not.
- Rendered imported history in T3: not rendered; recorded as best-effort.
- The Execute column of the approval matrix: from T3's policy code, not exercised with an execute tool.
- Disconnect as a dedicated real-T3 scenario; the code path and the ACP tests cover it.
- A fresh T3 v0.1.6 UI run of `/compact` (the **Compact context** control). The command is verified over the built ACP server with a real RPC child and a loopback model, not in a browser.
- A rendered diff in T3 for `edit` and `write` calls: the diff entries are asserted at the mapper, not drawn in T3.
- T3 versions other than v0.1.6.

## See also

- [CLI Integration](cli-integration.md) for Selesai's own modes.
- [RPC Mode](rpc.md) for the protocol `selesai-acp` uses to drive its child.
- [T3 Code provider setup](https://github.com/pingdotgg/t3code) — T3's own repository.
