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.

🛟 Updated. 28d5985 · on v0.9.0 · k33g · 11h ago
2026-09-15-acp-commands-mentions.md · 37 lines · 4.8 KBmarkdown
Blame HistoryOpen raw

Handoff — 2026-09-15 — / commands and @ mentions in agent windows

State

Done, tested, gate green, documented, not committed. Working tree on main (v0.7.0 + this), 12 files changed and 9 new, nothing staged. The five editors carry the documentation and the starter-file change, also uncommitted; their code is unchanged and their suites pass.

What was built: typing / as the first character of an agent window's box lists the agent's available_commands_update commands with descriptions and hints; typing @ anywhere lists the project's files. ↑↓ move, Tab completes, Enter completes-or-sends, Esc closes until the text changes, a click takes a line. A command is sent as text (/web query); a mention is sent as an embedded resource when the agent declared promptCapabilities.embeddedContext, a resource_link otherwise, in place of the name, with text blocks either side.

In flight

Why does mini-me's command list not show? (2026-09-16.) The user typed / in a window on mini-me and saw nothing. Their binary has the feature (checked: go version -m shows turbo-core (devel), the label string is in it). Added TURBO_ACP_TRACE and the unreadable-update line in Agent status so the next step is possible. Next step is theirs: run TURBO_ACP_TRACE=/tmp/acp.log ./bin/turbo-go, open mini-me, type a prompt, then open Agent ▸ Agent status and send the log. Three outcomes: (1) no available_commands_update in the trace → mini-me does not send it over ACP and the question moves to mini-me; (2) it is there and the status says it could not be read → fix the decoding to that shape, and replay it in fakeagent_test.go; (3) it is there and decoded → the picker itself, Session.Commands() non-empty but choices() returning nothing; check onInput, cursor == 0, single line.

Next steps

  1. Read the diffacp/picker.go, acp/picker_draw.go, acp/mention.go are new and small; the rest is threading.
  2. Try it against a real agent that announces commands. The user's mini-me (mm -acp, AGENT_CONFIG=…/agent.llamacpp.yaml) is the obvious one — an [[agent]] block for it is command = "mm", args = ["-acp"], env = { AGENT_CONFIG = "…" }. Watch for: does its available_commands_update arrive before session/new returns (the client stores it either way, but the rule label only updates on the next frame), and does it declare embeddedContext.
  3. Then the release order: turbo-core tag → each editor go getGOWORK=off make check → tag. The editors' docs already describe the new behaviour, so they are ahead of the binary until then.
  4. Decide about a branch. Previous cycles used feature/acp; this one sits on main because nobody was there to ask.

Open questions

  • Should Enter on a command that takes input but has none typed send anyway? It does now (/web + Enter sends /web). Zed inserts and waits. The hint in the popup is the only nudge.
  • Should the popup also list files when @ is typed with the transcript focused? No — the picker is the box's. Nobody has asked.
  • Is 5 000 files / 200 shown the right cap? Guessed, not measured. A monorepo will tell.

Watch out for

  • The picker's list is recomputed on every key and every framerefresh() is the single entry point and both handlePickerKey and drawPicker go through it, so the highlighted index can never refer to a different list than the one drawn. If you add a stored list, you will reintroduce exactly the desynchronisation this avoids.
  • Files() is called once per opening of the @ list, and the answer cached in picker.files while the word keeps its @. Calling it per frame walks the project sixty times a second. refresh() drops the cache when the word stops being an @ word.
  • HandleKey wraps everything in a before/after Input() comparison to lift Esc's dismissal. It is cheap (one strings.Join) but it runs on every key.
  • mentions() on Send consults fileList(), which walks the project if the list is not cached — so a prompt with an @ word sent without the popup having opened costs one walk. Acceptable; noted.
  • cp is still corrupt for fresh inodes in this sandbox, and this time recreating the files did not help. release_test.go fails on it; nothing else does. Do not chase it as a code defect. Use cat a > b, never cp.
  • drawLine has seven parameters and the gate did not flag it; drawChoice had six and was flagged. The rule is not simply "> 5" — do not reason from the count alone, run the measurement.

Never touched by a human

Everything: the popup on a real terminal, its colours under the eight themes, Tab vs Enter on a real keyboard, a mention of a file with unsaved changes reaching a real agent, a command actually being answered by an agent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Handoff — 2026-09-15 — `/` commands and `@` mentions in agent windows

## State

**Done, tested, gate green, documented, not committed.** Working tree on `main` (v0.7.0 + this), 12 files changed and 9 new, nothing staged. The five editors carry the documentation and the starter-file change, also uncommitted; their code is unchanged and their suites pass.

What was built: typing `/` as the first character of an agent window's box lists the agent's `available_commands_update` commands with descriptions and hints; typing `@` anywhere lists the project's files. `↑↓` move, `Tab` completes, `Enter` completes-or-sends, `Esc` closes until the text changes, a click takes a line. A command is sent as text (`/web query`); a mention is sent as an embedded `resource` when the agent declared `promptCapabilities.embeddedContext`, a `resource_link` otherwise, in place of the name, with text blocks either side.

## In flight

**Why does mini-me's command list not show?** (2026-09-16.) The user typed `/` in a window on mini-me and saw nothing. Their binary has the feature (checked: `go version -m` shows turbo-core `(devel)`, the label string is in it). Added `TURBO_ACP_TRACE` and the unreadable-update line in Agent status so the next step is possible. **Next step is theirs**: run `TURBO_ACP_TRACE=/tmp/acp.log ./bin/turbo-go`, open mini-me, type a prompt, then open **Agent ▸ Agent status** and send the log. Three outcomes: (1) no `available_commands_update` in the trace → mini-me does not send it over ACP and the question moves to mini-me; (2) it is there and the status says it could not be read → fix the decoding to that shape, and replay it in `fakeagent_test.go`; (3) it is there and decoded → the picker itself, `Session.Commands()` non-empty but `choices()` returning nothing; check `onInput`, `cursor == 0`, single line.

## Next steps

1. **Read the diff**`acp/picker.go`, `acp/picker_draw.go`, `acp/mention.go` are new and small; the rest is threading.
2. **Try it against a real agent that announces commands.** The user's `mini-me` (`mm -acp`, `AGENT_CONFIG=…/agent.llamacpp.yaml`) is the obvious one — an `[[agent]]` block for it is `command = "mm"`, `args = ["-acp"]`, `env = { AGENT_CONFIG = "…" }`. Watch for: does its `available_commands_update` arrive before `session/new` returns (the client stores it either way, but the rule label only updates on the next frame), and does it declare `embeddedContext`.
3. **Then the release order**: turbo-core tag → each editor `go get``GOWORK=off make check` → tag. The editors' docs already describe the new behaviour, so they are ahead of the binary until then.
4. Decide about a branch. Previous cycles used `feature/acp`; this one sits on `main` because nobody was there to ask.

## Open questions

- **Should `Enter` on a command that takes input but has none typed send anyway?** It does now (`/web` + `Enter` sends `/web`). Zed inserts and waits. The hint in the popup is the only nudge.
- **Should the popup also list files when `@` is typed with the transcript focused?** No — the picker is the box's. Nobody has asked.
- **Is 5 000 files / 200 shown the right cap?** Guessed, not measured. A monorepo will tell.

## Watch out for

- **The picker's list is recomputed on every key and every frame** — `refresh()` is the single entry point and both `handlePickerKey` and `drawPicker` go through it, so the highlighted index can never refer to a different list than the one drawn. If you add a stored list, you will reintroduce exactly the desynchronisation this avoids.
- **`Files()` is called once per opening of the `@` list, and the answer cached in `picker.files` while the word keeps its `@`.** Calling it per frame walks the project sixty times a second. `refresh()` drops the cache when the word stops being an `@` word.
- **`HandleKey` wraps everything in a before/after `Input()` comparison** to lift `Esc`'s dismissal. It is cheap (one `strings.Join`) but it runs on every key.
- **`mentions()` on `Send` consults `fileList()`**, which walks the project if the list is not cached — so a prompt with an `@` word sent without the popup having opened costs one walk. Acceptable; noted.
- **`cp` is still corrupt for fresh inodes in this sandbox**, and this time recreating the files did not help. `release_test.go` fails on it; nothing else does. Do not chase it as a code defect. Use `cat a > b`, never `cp`.
- **`drawLine` has seven parameters and the gate did not flag it; `drawChoice` had six and was flagged.** The rule is not simply "> 5" — do not reason from the count alone, run the measurement.

## Never touched by a human

Everything: the popup on a real terminal, its colours under the eight themes, `Tab` vs `Enter` on a real keyboard, a mention of a file with unsaved changes reaching a real agent, a command actually being *answered* by an agent.