summaryrefslogtreecommitdiffhomepage
path: root/plan.md
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-19 23:20:41 +0900
committerAdam Malczewski <[email protected]>2026-05-19 23:20:41 +0900
commita38d5b1279db6f9de5228c173019fc2ac08daec3 (patch)
tree32c3a535d0b74872ef952b4a44d4d5ba2ec9d638 /plan.md
parent0ae805b28b5160b8d9fb43635fa172961f6550cc (diff)
downloaddispatch-a38d5b1279db6f9de5228c173019fc2ac08daec3.tar.gz
dispatch-a38d5b1279db6f9de5228c173019fc2ac08daec3.zip
feat: Phase 2 — shell permissions, tree-sitter analysis, permission UI
Permission engine: - Rule-based engine: wildcard matching, last-match-wins, reject cascade - PermissionService with pending/approved state, PermissionChecker interface - dispatch.yaml config loader with per-permission pattern rules Shell tool: - run_shell tool with child_process spawn, timeout, streaming output - Tree-sitter static analysis (web-tree-sitter + tree-sitter-bash WASM) - BashArity command normalization for 'always allow' patterns - FILE_COMMANDS set: rm, cp, mv, mkdir, ls, find, grep, cat, etc. Agent loop refactored: - Removed maxSteps, manual step loop with tool execution - Permission checks on shell commands (external_directory only) - Permission checks on file tools outside workspace boundary - Symlink bypass fix (realpathSync), .. false positive fix - Shell output streaming via Promise.race + setImmediate polling API layer: - PermissionManager wraps PermissionService, broadcasts via WebSocket - WebSocket handles permission-reply messages from frontend - Config loaded from dispatch.yaml, converted to ruleset Frontend: - Permission prompt modal (native dialog, focus trap, ARIA) - Always-allow confirmation flow with pattern preview - Shell output display (live streaming + final parsed result) - Permission log panel (fixed bottom-right overlay) - Exit code badge (green 0, red non-zero) 134 tests, typecheck clean on all 3 packages
Diffstat (limited to 'plan.md')
-rw-r--r--plan.md101
1 files changed, 72 insertions, 29 deletions
diff --git a/plan.md b/plan.md
index dc672cb..1dca443 100644
--- a/plan.md
+++ b/plan.md
@@ -66,51 +66,94 @@ Open a browser, type "read the contents of package.json and summarize it," see t
**Goal:** Agent can run shell commands with directory-scoped permission controls. Usable on real projects.
-**Effort:** 1-2 weeks
+**Effort:** 2-3 weeks
-### Backend
+### Backend — Permission Engine
-- [ ] Shell tool:
- - `run_shell` — execute arbitrary commands, capture stdout/stderr/exit code
- - Streaming output for long-running commands
- - Working directory parameter (defaults to project root)
-- [ ] Directory permission system:
- - Current working directory + subdirectories: always allowed (read, write, execute)
- - Auto-allow list loaded from config file
- - All other directories: prompt user for permission before access
+- [ ] Rule-based permission engine:
+ - Rules: `{ permission, pattern, action }` where action is `allow | deny | ask`
+ - Wildcard glob matching on both permission name and target pattern
+ - Ordered ruleset, last-match-wins (user config overrides defaults)
+ - Default action when no rule matches: `"ask"`
- [ ] Permission grant types:
- - Per-request — allow this one operation
- - Per-session — allow this directory for the rest of the session
- - Permanent — add to auto-allow list in config
-- [ ] Permission prompt flow:
- - Agent calls a tool that touches an out-of-scope directory
- - API holds the request open (agent pauses)
- - WebSocket pushes a permission prompt to the frontend
- - User responds (approve/deny/always-allow)
- - API resolves, agent continues or gets a denial message
-- [ ] Basic config file loading (`dispatch.yaml`) for auto-allow list:
+ - Per-request — allow this one operation (no rule stored)
+ - Per-session — add to in-memory approved set for process lifetime
+ - Permanent — write to `dispatch.yaml` config
+- [ ] Reject cascade: rejecting one pending request auto-rejects all pending requests in that session
+- [ ] Path resolution:
+ - Working directory + all subdirectories: always in-scope (no prompt)
+ - All other paths: `external_directory` permission check
+ - `~` / `$HOME` expansion in config patterns
+
+### Backend — Config Format
+
+- [ ] `dispatch.yaml` permission block supporting per-permission patterns:
```yaml
permissions:
- auto_allow:
- - /tmp
- - ~/.config/dispatch
+ read: allow # shorthand: "read:*" = allow
+ edit:
+ "*": ask # prompt for edits by default
+ "src/**": allow # auto-allow edits inside src/
+ "/tmp/*": allow
+ external_directory:
+ "~/projects/*": allow
+ "/tmp/*": allow
+ bash:
+ "npm test": allow
+ "git commit *": allow
+ "git push *": ask
+ "*": ask # prompt for unknown commands
```
-- [ ] Apply permission checks to existing file tools (`read_file`, `write_file`) as well
+- [ ] Config hot-reload via chokidar
+
+### Backend — Shell Tool with Tree-Sitter Analysis
+
+- [ ] Dependencies: `web-tree-sitter` (WASM runtime), `tree-sitter-bash` (grammar WASM)
+ - ~2.5 MB total, loaded lazily on first shell command call
+- [ ] `run_shell` tool:
+ - Executes arbitrary commands via `child_process`, captures stdout/stderr/exit code
+ - Streaming output for long-running commands
+ - Configurable timeout (default 2 minutes)
+ - Working directory parameter (defaults to project root)
+- [ ] Tree-sitter static analysis pipeline:
+ 1. Parse command string into AST using `tree-sitter-bash`
+ 2. Walk all `command` nodes (recurses into pipelines, subshells, `&&`, `if` bodies)
+ 3. For file-touching commands (`rm`, `cp`, `mv`, `mkdir`, `touch`, `chmod`, `chown`, `cat`):
+ - Extract path arguments (skip flags like `-rf`)
+ - Resolve to absolute paths, check workspace boundary
+ - If outside workspace → add dir to `external_directory` permission ask
+ 4. Normalize command to pattern via `BashArity`:
+ - `git checkout main` → `"git checkout *"` (arity 2)
+ - `npm run dev --watch` → `"npm run dev *"` (arity 3)
+ - Unknown commands → fallback to command name only
+ 5. Fire two permission requests:
+ - `external_directory` — for any out-of-workspace paths detected
+ - `bash` — for the command pattern itself
+- [ ] Known gaps (documented, no fix needed for MVP):
+ - `find -exec rm`, `xargs`, `sudo` — can't see through these wrappers
+ - `$(...)` substitution paths — dynamic, skipped (still prompts for the command)
+ - Variable-stored paths — skipped
+ - Parse failure → hard error (no fallback), tool call aborts
+- [ ] Apply permission checks to existing file tools (`read_file`, `write_file`, `list_files`)
### Frontend
- [ ] Permission prompt modal:
- - Agent name
- - Target path
- - Operation type (read / write / execute)
+ - Agent name and AI's description of the operation
+ - For file ops: target path, operation type (read / write / execute)
+ - For shell commands: `$ command text` display with normalized "always allow" pattern preview
+ - For external directories: directory path and glob pattern
- Buttons: Approve / Deny / Always Allow
+ - "Always Allow" shows secondary confirmation listing the patterns that will be permanently approved
- [ ] Permission log panel: scrollable history of grants and denials
- [ ] Shell output display in chat: stdout/stderr with monospace formatting, exit code indicator
- [ ] Visual distinction between tool calls (file ops vs shell commands)
### Done When
-Ask the agent to "run the test suite." It executes `npm test` in the project dir (allowed). Then ask it to "check what's in /etc/hosts." Permission prompt appears. You approve. It reads the file and reports back. Next time it tries `/etc/`, it remembers your per-session grant.
+Ask the agent to "run the test suite." It executes `npm test` in the project dir (allowed). Then ask it to "check what's in /etc/hosts." Permission prompt appears showing the target path. You approve. It reads the file and reports back. Next time it tries `/etc/`, it remembers your per-session grant.
+
+Ask the agent to "clean up build artifacts with rm -rf dist." Permission prompt shows `$ rm -rf dist` with "always allow" pattern `"rm *"`. You click "Always Allow." Future `rm` commands auto-approve.
---
@@ -397,7 +440,7 @@ An agent edits a TypeScript file and introduces a type error. You see the error
| Phase | Scope | Effort | Cumulative |
|---|---|---|---|
| 1. Single Agent + UI | One agent, chat in browser | 2-3w | 2-3w |
-| 2. Shell Permissions | Safe shell access, permission prompts | 1-2w | 3-5w |
+| 2. Shell Permissions | Rule engine, tree-sitter shell analysis, permission prompts | 2-3w | 4-6w |
| 3. Config + Skills + Models | YAML config, skills dirs, model groups, key fallback | 2-3w | 5-8w |
| 4. Spawning + Tree | Multi-agent hierarchy, tree UI, user messaging | 2-3w | 7-11w |
| 5. Sessions | Persistence, fork, resume, model switch | 1-2w | 8-13w |