Skip to content

feat(tui): auto permission classifier - a middle tier between build and yolo - #137

Draft
wesleymatosdev wants to merge 2 commits into
kingsword09:mainfrom
wesleymatosdev:feat/auto-permission-classifier
Draft

feat(tui): auto permission classifier - a middle tier between build and yolo#137
wesleymatosdev wants to merge 2 commits into
kingsword09:mainfrom
wesleymatosdev:feat/auto-permission-classifier

Conversation

@wesleymatosdev

Copy link
Copy Markdown
Contributor

feat(tui): auto permission classifier — a middle tier between build and yolo

Why

Claude Code ships an auto permission mode: a classifier answers most prompts automatically and only escalates the gray zone to the user. ZCode's terminal client currently offers only two poles — build (prompt for everything) and yolo (prompt for nothing). The runtime reserves an auto mode value but currently denies all tools in it (Auto mode is reserved but not implemented yet), so there is no way to run "safe things automatically, ask me about the rest."

This PR adds that middle tier in the TUI, where the permission UX lives, without touching the vendored runtime.

What it does

Classifier at the dialog seam. requestToolPermission() consults a static policy before rendering the choice dialog:

  • allow — read-only shapes: git status/log/diff/show/branch, ls, pwd, cat, head, tail, wc, rg, grep, find, which, file, stat, plus the Read/Glob/Grep/TodoRead/WebSearch tools
  • deny (with reason) — rm -rf, sudo, curl|sh, git push --force, git reset --hard (soft), and credential paths (.env, .ssh, .aws, .gnupg, .kube, .netrc, .npmrc, *.pem, id_rsa, …) even for reads
  • unmatchednull → the normal human dialog, unchanged

A verdict returns the same response object the dialog returns ({ decision, reason }), so runtime semantics are untouched. Every auto-decision prints a transcript notice (auto-permissions · ALLOW · Write · …) so users can always see what was decided and why.

Opt-in auto client mode. Shift+Tab and the /mode picker now cycle a client-side list — build → edit → auto → yolo → plan. Selecting auto pins the runtime to build (so prompts still reach the client) while the footer shows auto and the classifier decides prompts. Typed /mode commands still execute in the runtime (which owns its enum) and exit the overlay. One-shot runs can boot into the overlay with ZCODE_CLIENT_MODE=auto, honored only over a build runtime.

Policy is data. ZCODE_AUTO_PERMISSIONS_CONFIG=<path> overrides the built-in rules point-wise (allow / softDeny / hardDeny rule arrays with tool, commandPrefix, commandRegex, pathPrefix, pathRegex, note; defaults.unmatched: ask|allow|deny). Malformed or missing files fall back to built-ins. Defaults are conservative: unmatched → ask.

Safety properties (structural, not disciplinary)

  • Classification runs only where a prompt would have rendered — it cannot widen yolo or affect non-prompting paths.
  • Runtime-side explicit deny rules never prompt, so they can never be overridden.
  • AskUserQuestion and plan-approval flows are never auto-answered (explicit gate).
  • Any internal error defers to the dialog — a broken policy cannot wedge a session into deny.

Testing

  • test/auto-permissions.test.ts (8) — classification semantics, precedence, credential paths, config load/fallback, dialog-response mapping
  • test/shortcuts-auto.test.ts (9) — client mode cycle (official modes/nextMode contract unchanged), runtime-vs-client normalization, boot gating, classification gate
  • bun run typecheck clean; biome check clean on all touched files; full suite delta vs main verified zero (pre-existing env failures identical on a clean-main worktree)
  • Live A/B against the real runtime over a PTY (isolated HOME, identical prompt, only the TUI bundle swapped): stock renders the write dialog and writes nothing; the patched client in auto prints the allow notice, executes the covered write with no dialog, and still renders the dialog for an uncovered write. This run also caught a real integration bug (runtime mode echoes clearing the overlay), now fixed and covered.

Adds a middle permission tier to the terminal client: before rendering
the permission dialog, requestToolPermission() consults a static policy
(read-only command prefixes and tools are allowed; rm -rf, sudo,
curl|sh, force pushes and credential paths are denied with a reason;
everything else falls through to the normal dialog).

The classifier returns the same response objects the dialog produces,
so runtime behavior is unchanged; verdicts are surfaced as TUI notices
for transparency. Policy defaults live in code and can be overridden
point-wise via ZCODE_AUTO_PERMISSIONS_CONFIG (unmatched defaults to
ask, so the classifier is strictly fail-open toward the human dialog).

Verified: bun test (unit), typecheck and biome clean, plus a live A/B
over a PTY against the real runtime: the stock bundle renders the write
dialog and writes nothing; the patched bundle auto-approves the covered
write with a visible notice and still defers uncovered writes.
The runtime's mode enum has no auto value (its reserved one denies
everything), so auto ships as a client-side overlay: Shift+Tab and the
/mode picker cycle a client list (build, edit, auto, yolo, plan) where
selecting auto pins the runtime to build and routes permission prompts
through the classifier. One-shot runs can boot into the overlay via
ZCODE_CLIENT_MODE=auto (only over a build runtime). Typed /mode
commands still execute in the runtime and exit the overlay; runtime
mode echoes during a session no longer clear it.

Classification is gated on the overlay being active, so build/edit
users see exactly the behavior they chose; AskUserQuestion and plan
approvals are never auto-answered.
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