turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md · 47 lines · 4.9 KBmarkdown Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1# acp
2
3A client for the [Agent Client Protocol](https://agentclientprotocol.com): it starts a coding agent as a child process, holds a conversation with it, and keeps a model of that conversation a window can draw.
4
5The editor holds no API key, knows no provider, and implements no tool-calling loop. All of that is the agent's own configuration, in a file this package does not read.
6
7## Layers
8
9| File | What it is |
10| --- | --- |
11| `config.go` | `acp.toml`: the agents a project and a user list, merged, with the project's winning |
12| `create.go` | The starter file a menu item writes |
13| `framing.go` | Newline-delimited messages, as a `jsonrpc.Framer` |
14| `protocol.go` | The wire types, and every method name in one place |
15| `process.go` | The child: its pipes joined into one stream, its standard error drained |
16| `trace.go` | `TURBO_ACP_TRACE=<file>`: every line in both directions, for "what did the agent actually send?" |
17| `session.go` | The conversation: handshake, prompts, cancellation, permissions, the file methods |
18| `transcript.go` | The conversation as a **value** — no terminal, no colours |
19| `session_files.go` | The file half: `fs/read_text_file`, `fs/write_text_file`, and reading a mentioned file's text |
20| `mention.go` | A file named with `@`, and the content blocks a prompt becomes — text, `resource`, `resource_link` |
21| `render.go` | Entries into drawn lines: fences, wrapping, styles |
22| `view.go` `view_draw.go` `view_events.go` | The widget: the conversation above, a box to type in below |
23| `picker.go` `picker_draw.go` | The popup `/` and `@` open in the box: the agent's commands, the project's files |
24
25`NewSession` takes an `io.ReadWriteCloser`, not a command — the same seam [`lsp`](../lsp/) has, and for the same reason. The whole client is driven from a test against an agent **in the same process**, over `net.Pipe`: real framing, real concurrency, real decoding, no subprocess to reap and no model to reach. `Start` is the thin layer that builds that stream out of a child process's pipes.
26
27## Seven things that are easy to get wrong
28
29**The agent numbers its requests from one, and so do we.** `docker agent` really does send `id: 1` while a call of ours carrying the same id is in flight. The two directions are separate id spaces, which is `jsonrpc`'s business — but it is why this client could not have been built on a connection that shared one map.
30
31**A permission cannot be answered where it arrives.** `session/request_permission` reaches the reading goroutine, and the answer comes from a dialog somebody has to look at. `Session` records a `*Permission` and wakes the event loop; the dialog opens on the next turn and calls `Answer`. `Answer` and `Cancel` write on a goroutine of their own: an agent that has stopped reading must not be able to freeze the editor on the keystroke that answers it.
32
33**`content` is two different shapes under one name.** A message chunk carries a single content block; a `tool_call_update` carries an array of them. It is kept as `json.RawMessage` and decoded per kind — decoding eagerly into either breaks on the other, and only once an agent uses a tool.
34
35**A reply arrives one token at a time.** `"I"`, `" found"`, `" agent"`, `".yaml"`. `Transcript` coalesces a run of chunks into one entry; an entry per chunk could be neither wrapped nor told apart from a fenced code block.
36
37**A command is a text prompt, not a method.** `available_commands_update` tells the client what the agent answers to — `/web`, `/compact` — and the client sends the command as the text `"/web agent client protocol"`, exactly as Zed does. There is nothing else on the wire, so an agent that documents its commands for Zed has documented them for this editor. The picker that lists them is a convenience over that fact, not a protocol feature.
38
39**A mention takes the name out of the text.** `@app/menus.go` in the box becomes a content block *in place* — the file's text as a `resource` when the agent declared `promptCapabilities.embeddedContext`, a `resource_link` otherwise — with text blocks either side. The conversation keeps what was typed; the wire does not carry the name twice.
40
41**Only a fence makes a block code.** No scanner is guessed at from the shape of the text, which is the rule every scanner in this library already follows. A fence naming a language nothing colours is drawn plainly.
42
43## Tests
44
45`go test ./acp/` drives the client against a fake agent written by hand — deliberately not built on this package's own types, because a peer that shared them could not catch the client encoding a field wrongly. The shapes it replays are copied from a recorded conversation with `docker agent` v1.139.0 against a local llama.cpp, not invented.
46
47The drawing tests use a session whose agent never answers, so the conversation is whatever the test puts into it. A test that asserts on a screen while a live process writes to it passes or fails by luck, and one such test hid a real fault here for a whole session.