Add batch reads, @file completion, interruptible commands, tool sets
Tools, six built-in to fourteen: - read_many_files: up to 20 paths read concurrently, each with its own window. An unreadable path is reported in its own block instead of throwing. - multi_edit: several edits to one file, validated in memory first so a late failure cannot leave the file half-written. - list_dir: ignore-aware depth-limited tree. - git_status/diff/log/show/blame: read-only, spawned with a fixed argv rather than a shell string, which is what makes them safe to auto-approve. toolSets gates them. core is always on; edit-plus and git are optional. A disabled set reaches neither the wire nor the system prompt, since a prompt naming an absent tool teaches calls that cannot succeed. Interface: - Reasoning streams to a collapsed panel, ctrl-r expands, dropped when the turn ends: it is progress, not the answer. - The tool in flight is named from tool-input-start, before its arguments finish streaming, and cleared on its result. - Prompts typed mid-turn queue and drain in order. esc clears the queue as well as aborting. - @ opens a path picker fed by the ignore-aware walker. Prefix matches rank above substring matches, so @src/ means "under src/". The walk runs on the first @, not at startup. ctrl-c kills the command in flight and keeps the turn. The call throws rather than returning, so the model cannot read a killed command as one that ran and failed on its own terms. The kill takes the whole process tree: killing cmd /c alone left the real command holding both pipes open, so the read never returned and the interrupt did nothing for 19 seconds. Two pruning fixes: - A tool result whose tool call was pruned is now dropped with it. Pruning counts messages, so the cut landed between an assistant tool-call and the tool message answering it, producing 400 "No tool call found for function call output with call_id ...". The reverse pairing is left alone: a call awaiting its result is what a suspended approval looks like. - ignore.ts called statFs without importing it, so walk() crashed on the first symlink. 482 tests, up from 404. Docs synced across README, ROADMAP, TODO, and all of docs/: tool sets, the new tools, ctrl-c semantics, the tool-start event, and the two hand-maintained tool-name lists recorded as a known weakness.
This commit is contained in:
@@ -8,74 +8,71 @@ Longer-term direction lives in [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
## Now
|
||||
|
||||
### Show reasoning in the transcript
|
||||
### Summarize the pruned span
|
||||
|
||||
`src/session.ts` already yields `{ type: 'reasoning' }`; `src/ui/App.tsx` ignores it.
|
||||
Compaction tells the model the history was pruned but not what was in it, so it can
|
||||
confidently contradict a decision it made forty messages ago.
|
||||
|
||||
- [ ] Accumulate reasoning deltas into their own buffer, separate from `text`
|
||||
- [ ] Render as a dim collapsed panel: `thinking... 412 tokens`, expandable with a key
|
||||
- [ ] Drop it from the transcript when the turn ends — reasoning is not part of the answer
|
||||
- [ ] Test: a model emitting `reasoning-delta` puts text on screen before any `text-delta`
|
||||
- [ ] Summarize the discarded messages before dropping them
|
||||
- [ ] Inject the summary in place of the count
|
||||
- [ ] Budget it: a summary that grows with the session defeats the point
|
||||
- [ ] Test: a pruned decision is still recoverable from the summary
|
||||
|
||||
### Show the file being touched
|
||||
### A cheaper model for subagents
|
||||
|
||||
`tool-call` carries the path but the transcript only shows a summary after the call returns.
|
||||
The subagent shares the parent's model. An `explore` run is search, not reasoning, and it
|
||||
currently pays the parent's per-token rate.
|
||||
|
||||
- [ ] Render an active-tool line while a call is in flight: `read src/session.ts`
|
||||
- [ ] Clear it on `tool-result` or `tool-error`
|
||||
- [ ] Test: a slow tool leaves its line on screen for the duration
|
||||
- [ ] `subagentModel` in config, defaulting to the parent
|
||||
- [ ] `/cost` separates parent from subagent spend
|
||||
- [ ] Test: the subagent's calls go to the configured model, the parent's do not
|
||||
|
||||
### Queue prompts typed during a turn
|
||||
### A spend ceiling
|
||||
|
||||
- [ ] Keep `PromptInput` mounted while `busy`, alongside the spinner
|
||||
- [ ] Submitting while busy appends to a queue and shows `queued: 2`
|
||||
- [ ] Drain the queue in order when the turn ends
|
||||
- [ ] `esc` clears the queue as well as aborting
|
||||
- [ ] Test: two prompts typed during a turn run in order afterwards
|
||||
A headless run that loops costs real money with nothing to stop it.
|
||||
|
||||
- [ ] `maxSpendUsd` in config, checked after every turn
|
||||
- [ ] Warn at 80%, refuse to start another turn at 100%
|
||||
- [ ] Headless exits non-zero with the ceiling named, rather than stopping silently
|
||||
- [ ] Test: a session past its ceiling refuses the next turn and says why
|
||||
|
||||
---
|
||||
|
||||
## Next
|
||||
|
||||
### `activeTools` gating per tool set
|
||||
### Summarize the pruned span
|
||||
|
||||
Needed before the tool count grows. Measured at 553 chars of schema per tool per request.
|
||||
Compaction tells the model the history was pruned but not what was in it, so it can
|
||||
confidently contradict a decision it made forty messages ago.
|
||||
|
||||
- [ ] `toolSets` in config: which sets are live
|
||||
- [ ] Sets: `core`, `git`, `edit-plus`, `net`
|
||||
- [ ] `prepareStep` narrows `activeTools` to the enabled sets
|
||||
- [ ] `/tools` shows which set each tool came from
|
||||
- [ ] Test: a disabled set's tools reach neither the wire nor the prompt
|
||||
- [ ] Summarize the discarded messages before dropping them
|
||||
- [ ] Inject the summary in place of the count
|
||||
- [ ] Budget it: a summary that grows with the session defeats the point
|
||||
- [ ] Test: a pruned decision is still recoverable from the summary
|
||||
|
||||
### `multi_edit`
|
||||
### `web_fetch`
|
||||
|
||||
- [ ] Several `{ oldString, newString }` edits against one file
|
||||
- [ ] Atomic: any failing match aborts the whole call, file untouched
|
||||
- [ ] Each edit applied to the result of the previous one
|
||||
- [ ] Approval prompt shows one combined diff
|
||||
- [ ] Test: a failing second edit leaves the file exactly as it was
|
||||
- [ ] URL to markdown, size-capped
|
||||
- [ ] Belongs to a `net` set, off by default — it is the one tool that leaves the machine
|
||||
- [ ] Test: a redirect is followed, an oversized body is truncated with a note
|
||||
|
||||
### `list_dir`
|
||||
### Derive the tool-name lists
|
||||
|
||||
- [ ] Tree view honouring `.gitignore`, depth-limited, entry-capped
|
||||
- [ ] Marks directories and shows file sizes
|
||||
- [ ] Test: respects ignore rules, stops at the depth limit
|
||||
`TOOL_SETS` and `MUTATING_TOOLS` both list names by hand. A tool added to one and forgotten
|
||||
in the other is a silently ungated write, which is the worst kind of bug this codebase can
|
||||
have.
|
||||
|
||||
### Git read-only tools
|
||||
- [ ] Mark each tool as mutating where it is defined, not in a list beside it
|
||||
- [ ] `TOOL_SETS` covers every registered tool, checked rather than assumed
|
||||
- [ ] Test: a tool in no set, or a mutating tool outside `MUTATING_TOOLS`, fails the suite
|
||||
|
||||
All approval-free, since none can mutate.
|
||||
### Subagent parallelism
|
||||
|
||||
- [ ] `git_status`, `git_diff`, `git_log`, `git_show`, `git_blame`
|
||||
- [ ] Structured output, not raw porcelain
|
||||
- [ ] Fail clearly outside a repo instead of returning git's error text
|
||||
- [ ] Test: each returns something usable in a temp repo, and a clean error outside one
|
||||
Two independent searches run sequentially. The panel already renders several agents; the loop
|
||||
does not fan out.
|
||||
|
||||
### `@file` completion
|
||||
|
||||
- [ ] `@` in the input opens a path picker fed by the ignore-aware walker
|
||||
- [ ] Tab completes, continued typing narrows
|
||||
- [ ] Completed path inserted as a plain relative path
|
||||
- [ ] Test: `@src/` narrows to files under `src/`
|
||||
- [ ] `task` accepts several investigations and runs them together
|
||||
- [ ] Test: two delegated searches overlap in time rather than queueing
|
||||
|
||||
---
|
||||
|
||||
@@ -85,9 +82,8 @@ All approval-free, since none can mutate.
|
||||
- [ ] `estimateTokens` divides JSON length by four. Good enough for a compaction threshold,
|
||||
wrong enough to mislead in `/cost`. Either label it an estimate everywhere or use a
|
||||
real tokenizer
|
||||
- [ ] The subagent shares the parent's model. A cheaper model for search would cut cost
|
||||
substantially on `explore` runs
|
||||
- [ ] No spend ceiling. A headless run that loops costs real money with nothing to stop it
|
||||
- [ ] `listPaths` walks up to 5000 files once per session. Fine for a repo, wasteful in a
|
||||
monorepo, and it never notices a file created after the first `@`
|
||||
|
||||
---
|
||||
|
||||
@@ -98,12 +94,36 @@ Not bugs exactly, but things that will bite someone.
|
||||
- **`/clear` wipes the terminal scrollback.** `<Static>` output is already committed, so
|
||||
clearing React state alone leaves it on screen. The escape sequence works but takes the
|
||||
user's earlier terminal history with it.
|
||||
- **Compaction is lossy in a way the model cannot see.** It is told the history was pruned,
|
||||
but not what was in the pruned part. A summary of the discarded span would be better than
|
||||
a count.
|
||||
- **Memory has no conflict resolution.** Two contradictory notes both persist and both get
|
||||
injected. `/memory` may merge them, or may keep both.
|
||||
- **`bash` cannot be interrupted independently.** `esc` aborts the whole turn, killing the
|
||||
command. There is no way to stop a runaway command and keep the turn.
|
||||
- **Windows `cmd /c` differs from `bash -lc`.** A command the model writes for one shell may
|
||||
fail on the other. The prompt states the platform; it does not translate.
|
||||
- **An unknown name in `toolSets` is dropped silently.** The header line shows which sets
|
||||
actually loaded, but a typo reads as "that set is off" rather than as a mistake.
|
||||
- **The reasoning panel is per-turn, not per-step.** Reasoning from an early step stays on
|
||||
screen through later ones until the turn ends.
|
||||
- **An interrupted command's effects are unknown, and the model is told so.** Nothing can know
|
||||
how far a half-run migration got.
|
||||
- **`@` completion lists files, not directories.** `@src/` narrows correctly, but you cannot
|
||||
complete to `src/` itself, because the walker only yields files.
|
||||
|
||||
---
|
||||
|
||||
## Done
|
||||
|
||||
Kept for one release, then deleted.
|
||||
|
||||
- [x] Reasoning streamed to a collapsed panel, `ctrl-r` to expand, dropped when the turn ends
|
||||
- [x] The tool in flight named on screen from `tool-input-start` until its result arrives
|
||||
- [x] Prompts typed during a turn queue and drain in order; `esc` clears the queue
|
||||
- [x] `toolSets` gating, so a disabled set reaches neither the wire nor the prompt
|
||||
- [x] `multi_edit`, atomic across several edits to one file
|
||||
- [x] `list_dir`, ignore-aware and depth-limited
|
||||
- [x] Read-only git tools: `git_status` `git_diff` `git_log` `git_show` `git_blame`
|
||||
- [x] Orphaned tool results dropped during pruning, fixing the 400 "No tool call found for
|
||||
function call output with call_id ..."
|
||||
- [x] `read_many_files`, concurrent, one labelled block per file, a bad path reported in place
|
||||
- [x] `@file` completion: picker fed by the ignore-aware walker, tab inserts a relative path
|
||||
- [x] `ctrl-c` kills the running command and keeps the turn. The kill takes the whole process
|
||||
tree: killing `cmd /c` alone left the real command holding both pipes open, so the
|
||||
interrupt appeared to do nothing for 19 seconds
|
||||
|
||||
Reference in New Issue
Block a user