Add a native, enforceable permission system (auto/plan/readonly/ask, JSON-driven, JS + Rust)#272
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #271
Add --read-only and --disable-tools flags (plus LINK_ASSISTANT_AGENT_READ_ONLY and LINK_ASSISTANT_AGENT_DISABLE_TOOLS env vars) that disable filesystem-mutating and shell tools (bash, edit, write, multiedit, patch). Enforcement is applied at tool resolution (so the model never sees denied tools) and inside the batch tool (so denied tools cannot be invoked indirectly). A read-only note is added to the environment system prompt. This makes agent-commander's uniform --read-only flag enforceable for the agent tool.
Working session summaryAll CI checks pass. The work is complete. SummaryI implemented a native, enforceable read-only / planning mode for What was added
How it's enforced (not prompt-only)A single source of truth,
A Verification
Docs / release
PR: #272 One design note worth surfacing for review: in read-only mode the This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:
Total: (6.2K new + 90.2K cache writes + 5.5M cache reads) input tokens, 28.8K output tokens, $4.063988 cost 🤖 Models used:
📎 Log file uploaded as Gist (2111KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
|
I think my comment #271 (comment) and the issue itself are not fully implemented. |
|
🤖 AI Work Session Started Starting automated work session at 2026-06-17T13:51:26.379Z The PR has been converted to draft mode while work is in progress. This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback. |
Port OpenCode's permission system back into the JS agent, driven entirely over JSON (no TUI). Default --permission-mode auto preserves full auto behavior. plan/readonly/ask modes plus an OpenCode-compatible --permission override enforce read-only / planning / per-command approval. - js/src/permission/index.ts: Permission namespace (policy, modes, ask/respond) - tool enforcement in bash/edit/write/patch/webfetch - JSON I/O: permission_request event + permission_response frame (text & stream-json) - CLI flags --permission-mode / --permission + config + env vars - js/tests/permission.test.ts (22 tests) - docs/permissions.md + docs/case-studies/issue-271 (analysis, research, data) Refs #271
) - rust/src/permission.rs: Mode/Action/Policy, mode policy, --permission override parse+merge, read-only bash allowlist, structured wildcard matcher, evaluate_bash, and permission_request/permission_response JSON schemas, with 24 unit tests. Mirrors js/src/permission/index.ts. - rust/src/cli.rs: --permission-mode / --permission flags (env-backed), early policy validation (fail clearly on invalid mode/JSON), verbose output line. - README.md / TOOLS.md: document the permission system + link docs/permissions.md. - js/.changeset + rust/changelog.d: version-bump fragments.
Working session summaryEverything is complete and verified. Here's a summary of the work finished this session. Done — PR #272 ready for reviewThe remaining work from the issue #271 spec is now complete. The earlier session finished the JavaScript implementation and all the docs/case-study; this session completed the Rust half and finalized the PR. Rust permission system (the last missing piece — "fully implement in both JavaScript and Rust")
Docs & release
Verification
PR #272 is updated (new title/description) and marked ready for review: #272 The default This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:Claude Opus 4.8: (4 sub-sessions)
Total: (89.9K new + 714.9K cache writes + 16.9M cache reads) input tokens, 191.6K output tokens, $18.146859 cost Claude Haiku 4.5:
Total: (355.5K new + 128.5K cache writes + 2.0M cache reads) input tokens, 28.6K output tokens, $1.007419 cost 🤖 Models used:
📎 Log file uploaded as Gist (8553KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
Summary
Adds a native, enforceable permission system to
@link-assistant/agent, ported from OpenCode and fully controllable over JSON with no TUI. This closes issue #271 in two layers:--read-only/--disable-toolsremove mutating tools from the model entirely.auto/plan/readonly/askmodes and a per-command JSON approval protocol.The default mode is
auto(full autonomy, never asks), so existing behavior is unchanged and the full-auto path has zero added overhead.Fixes #271
Permission modes
--permission-mode <mode>(envLINK_ASSISTANT_AGENT_PERMISSION_MODE):edit/write/patchbashwebfetchauto(default)planreadonlyask--permission '<json>'(envLINK_ASSISTANT_AGENT_PERMISSION) takes an OpenCode-compatible{edit, bash, webfetch}policy (wherebashis a string or a{glob: action}map) and is merged on top of the mode (override wins; bash maps merge key-by-key).JSON protocol (no TUI)
When a tool needs approval the agent emits a
permission_requestevent on stdout; the consumer replies with apermission_responseframe on stdin:{ "type": "permission_response", "permissionID": "per_xyz", "response": "once" }responseisonce(allow this call),always(allow + auto-approve later matching requests this session), orreject(deny →RejectedError). Accepted in both text and--input-format stream-jsonmodes.auto/readonlynever ask (any input mode);plan/askemit requests mid-turn and require a streaming input mode.Full protocol, modes, env vars, and worked examples:
docs/permissions.md.Implementation (both JavaScript and Rust)
JavaScript — full runtime enforcement in the agent loop:
js/src/permission/index.ts—Permissionnamespace ported from OpenCode (ask/respond/RejectedError,permission.updated/permission.repliedbus events,once/always/reject, per-tool glob rules with "last matching rule wins", read-only bash allowlist, mode/override resolution).tool/{bash,edit,write,patch,webfetch}.ts.bashparses the command with tree-sitter and evaluates each command node independently; the parse is skipped entirely inautomode (Permission.bashEnforced()short-circuit → zero overhead).cli/{event-handler,input-queue,continuous-mode,run-options}.jsand config inconfig/config.ts.Rust —
rust/src/permission.rsmirrors the JS policy core:Mode/Action/Policy, mode policy,--permissionparse+merge, read-only bash allowlist, structured wildcard matcher,evaluate_bash, andpermission_request/permission_responseserde schemas.--permission-mode/--permissionclap flags (env-backed) with early validation that fails clearly on an invalid mode or override JSON. The Rust binary has no model agent loop yet, so enforcement there is policy-resolution + JSON-schema parity (documented honestly in the case study).Case study & research
Per the issue directive,
docs/case-studies/issue-271/contains:README.md— deep analysis, per-requirement (R1–R10) solution table, root cause, architecture, alternatives, prior-art reuse, verification.research-permissions-landscape.md— sourced survey of OpenCode, Claude Code, MCP elicitation, Codex, Gemini/Qwen, and Vercel AI SDK.research-data.json— machine-readable facts.Tests
js/tests/permission.test.ts— 22 tests (mode policy, override parse/merge,policy()resolution,evaluateBash,bashEnforced,check()allow/deny, full ask→respond lifecycle foronce/reject/always).rust—cargo test --lib permission: 24 tests (mode policy, override parse/merge, bash evaluation, wildcard matching, JSON (de)serialization).js/tests/read-only.test.ts— unchanged, still green (hard tool-removal layer).cargo build+cargo fmt --checkclean.Reproduce
Docs / release
docs/permissions.md(new),docs/case-studies/issue-271/*(new).README.md/TOOLS.mddocument the permission system and link the docs.js/.changeset/permission-system.md(minor) +rust/changelog.d/...permission_system.md(minor).