Skip to content

feat(tui): engine-local tool rows and approval safety (T4 slice) - #430

Open
BunsDev wants to merge 5 commits into
feat/410-next-shellfrom
feat/411-tool-approval
Open

BunsDev wants to merge 5 commits into
feat/410-next-shellfrom
feat/411-tool-approval

Conversation

@BunsDev

@BunsDev BunsDev commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Refs #411. Stacked on #429 (#410). Retarget to main after the stack merges. Classic stays the default renderer.

What this ships

The engine-local slice of T4 in Next (COVEN_CODE_UI=next): readable tool rows, an approval panel that says who enforces it, and approval keys that a stray keystroke cannot trigger.

  • Tool rows show the tool, a short target (command, path, pattern or URL, relative to the project, first line only) and an honest state. The user's decision is shown apart from the result: allowed, running is not success, and a denied call says denied.
  • Cancelled tools stay visible as cancel requested, then cancelled, instead of vanishing. The engine's closing error result updates that row; it does not add a second failed row.
  • Tool detail in the Activity inspector shows the sanitized input, up to six output lines and the owner: the engine-local tool in this process.
  • Approval panel: Next draws its own when no other modal is open. It names the tool and scope (engine-local), the project, who enforces it (this process; no daemon protection applies), and what each key does, including that p saves a persistent allow rule. Space is given out by priority: every option first, then the danger note and title, then as much of the input as fits. If input does not fit, the panel says how many rows are hidden.
  • Hostile text: tool input and output, model text, status, permission fields and the draft go through terminal_safe, which shows C0 and DEL in caret notation and replaces C1 and bidi controls. Ratatui already drops control characters, so this is defense in depth that makes them visible.

Classic is unchanged

The approval guards are applied only in Next, through PermissionRequest::guarded:

  • A request opens with No, deny focused.
  • Keys other than Escape count only after 400 ms with no key pressed, and each ignored key restarts the wait.

Classic opens requests on the first option and accepts keys at once, as before. Its permission goldens match the #410 base byte for byte. Classic still clears its tool rows on cancel. Offering the same guards to Classic would be a separate decision.

Review

An independent reviewer read the first version. Resolved:

Finding Resolution
Decisions were keyed by the synthetic perm-… request id, so no real row ever showed allowed or denied A task-local carries the model's tool-use id into the request (call_id); keyboard and remote-control decisions record against it
A cancelled tool showed twice: cancelled and error The closing durable result updates the interrupted row
The arming delay protected only the first 400 ms It is now a quiet gap that every ignored key restarts (Next)
The panel showed two command lines with no marker and could cut options Priority fitting with a hidden-rows count; options are never dropped
Remote cancel left tools running; interrupted rows and decisions were never cleared Remote cancel interrupts rows; clear, resume and rewind drop them; decisions are pruned with their rows

New tests fail without their fixes: the quiet-gap test, the duplicate-row test and the whole-command test were each run red against the code they cover.

Not changed, recorded here instead:

  • Ctrl+C while an approval is open does not cancel the turn, and the worker waits on the decision. This predates this PR, in both renderers.
  • Typing p or P still decides at once once armed; they are not focus-only.
  • A late ToolEnd for an interrupted call is dropped; the durable result covers it.

Verification

All results are on the head of this PR.

Check Result
cargo test -p claurst-tui --locked --test next_tools 11 passed
cargo test --workspace --locked --no-fail-fast 2851 passed, 0 failed, 52 ignored
cargo clippy --workspace --all-targets --locked -- -D warnings clean
Full tmux suite at 120x40 (real binary, disposable home) 75/75

There is no tmux approval journey. The fake claude runs tools inside its own process, so it never reaches the engine-local permission path; a real-binary approval check needs a fake provider that emits a tool use.

Still open for #411

Next draws its own approval panel and honest tool rows: target, state,
the user's decision kept apart from the execution result, and
interrupted calls shown as cancel requested, then cancelled.

Approval safety, shared with Classic: a request opens focused on deny,
and keys other than Escape count only after a 400 ms quiet gap, which
each ignored key restarts. The panel never drops options or silently
shortens the command; hidden input rows are counted.

Decisions are recorded against the model's tool-use id, carried to the
permission request through a task-local set around each tool call.
Untrusted text is shown with control, C1 and bidi characters made
visible.

Refs #411
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 2, 2026 5:59am UTC

Request Review

Deny focus and the quiet-gap arming are now applied by
PermissionRequest::guarded, which the loop uses for the Next renderer
only. Classic opens requests on the first option and accepts keys at
once, as before; its permission goldens match the #410 base again.
Classic also clears its tool rows on cancel as before.

Refs #411

This branch was successfully deployed

1 active deployment
Preview — e88b7d54 Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant