zcode-acp-server 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -343
- package/README.zh-CN.md +24 -162
- package/dist/backend/sandbox.d.ts.map +1 -1
- package/dist/backend/sandbox.js +13 -1
- package/dist/backend/sandbox.js.map +1 -1
- package/dist/config/provider-registry.d.ts +6 -0
- package/dist/config/provider-registry.d.ts.map +1 -1
- package/dist/config/provider-registry.js +18 -2
- package/dist/config/provider-registry.js.map +1 -1
- package/dist/handlers/session.d.ts.map +1 -1
- package/dist/handlers/session.js +9 -0
- package/dist/handlers/session.js.map +1 -1
- package/docs/CLI.md +188 -0
- package/docs/REMOTE.md +115 -0
- package/docs/SANDBOX.md +56 -0
- package/package.json +1 -1
package/docs/CLI.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Unified CLI (`zcode-acp`)
|
|
2
|
+
|
|
3
|
+
Every surface of this package is available under one command — `zcode-acp` —
|
|
4
|
+
installed alongside the `zcode-acp-server` bin your editor configures.
|
|
5
|
+
|
|
6
|
+
## Interactive REPL
|
|
7
|
+
|
|
8
|
+
Bare `zcode-acp` opens an interactive terminal chat against this same bridge
|
|
9
|
+
(built with [Ink](https://github.com/vadimdemedes/ink), the same renderer
|
|
10
|
+
Claude Code and Gemini CLI use). Completed messages print once into the
|
|
11
|
+
terminal's **native scrollback** — smooth wheel scrolling, text selection,
|
|
12
|
+
search, and history that survives exit are all just your terminal, unchanged.
|
|
13
|
+
Only a compact dynamic footer ever repaints: the live-turn tail (capped at
|
|
14
|
+
half the screen), queued-prompt panel, completion menu, and the prompt box.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
zcode-acp # chat in this directory
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A startup welcome panel (version, session directory, seeded config, key
|
|
21
|
+
hints) lands in scrollback first. Streaming output has code-fence coloring,
|
|
22
|
+
dim thinking lines, and live tool rows. The prompt line wraps across rows
|
|
23
|
+
with a CJK-aware block cursor — `←`/`→` (or Ctrl-B/Ctrl-F) move inside the
|
|
24
|
+
typed text, Backspace/Delete edit at the caret, Ctrl-A/Ctrl-E jump to the
|
|
25
|
+
line's ends, Ctrl-U clears the line; completion keeps precedence while its
|
|
26
|
+
menu is open. The status row carries a compact plan-quota readout
|
|
27
|
+
(`5h 16% · wk 4%`) refreshed every 10 minutes — `/quota` prints the full
|
|
28
|
+
card. Pasted or dragged-in content (error logs, file drops) is sanitized and
|
|
29
|
+
size-capped before it reaches the editor, so long pastes batch cleanly.
|
|
30
|
+
`esc` interrupts a running turn (immediately — the bridge resolves the prompt
|
|
31
|
+
as cancelled without waiting for the backend); `Ctrl-C` also interrupts, and
|
|
32
|
+
while idle press it twice to quit. `/exit` (or a bare `exit`, shell-style)
|
|
33
|
+
leaves; the session itself persists
|
|
34
|
+
in the ZCode backend and is available to your editor.
|
|
35
|
+
|
|
36
|
+
Sending a follow-up right after an interrupt waits for the backend to finish
|
|
37
|
+
the cancelled generation first — a `[上一个回复仍在生成,等待结束后发送…]`
|
|
38
|
+
note explains the pause (the Aug-28 app-server accepts mid-generation sends
|
|
39
|
+
as steer input but drops them when the old turn ends; the bridge polls until
|
|
40
|
+
the session is idle, up to 90s, so the message actually runs).
|
|
41
|
+
|
|
42
|
+
Messages typed while a turn is running (or the session is still starting) are
|
|
43
|
+
queued, not lost: each shows up in the transcript immediately and a `⏸ queued`
|
|
44
|
+
panel above the prompt box lists everything waiting to run. When the current
|
|
45
|
+
turn ends the queue drains one prompt at a time — through the same command
|
|
46
|
+
parsing as direct submits, so queued `/help` and `/exit` still work.
|
|
47
|
+
|
|
48
|
+
`/sessions` lists this project's previous conversations in an 8-row sliding
|
|
49
|
+
window (position counter, plus "N newer above / N older below" hints); arrow
|
|
50
|
+
keys move over the full list even though only part of it renders. Resuming
|
|
51
|
+
loads the most recent tail of the conversation (last 50 messages, turn-aligned)
|
|
52
|
+
instead of dumping thousands of lines at once — when older history exists the
|
|
53
|
+
note says exactly what was loaded:
|
|
54
|
+
`resumed "<title>" — showing last 50 of 1234 messages`.
|
|
55
|
+
|
|
56
|
+
The full-width prompt box mirrors the editor's dropdowns: its bottom row shows
|
|
57
|
+
the current `model · mode · thought level`, and typing `/` opens an interactive
|
|
58
|
+
completion menu — `↑`/`↓` move, `enter` picks the highlighted entry (or `tab` /
|
|
59
|
+
`→`; typing the exact form already sends), `esc` dismisses. After picking
|
|
60
|
+
`/model`, `/mode`, or `/thought` the same menu lists the config options (the
|
|
61
|
+
current one marked `●`) and **enter on a row switches immediately** — no second
|
|
62
|
+
confirmation. Argument-free commands (`/exit`, `/help`, `/sessions`, `/new`,
|
|
63
|
+
`/compact`, `/mcp`, `/quota`) run on pick as well; every other completion
|
|
64
|
+
(skills, plugins) only fills the line, since those usually expect arguments.
|
|
65
|
+
The arg-less forms still print a static listing over the same slash-command
|
|
66
|
+
path the editor uses. `/help` lists every command the bridge advertises,
|
|
67
|
+
including plugin commands. `/new` swaps in a fresh session without leaving
|
|
68
|
+
the terminal (the old conversation stays in `/sessions` and in scrollback).
|
|
69
|
+
|
|
70
|
+
Submitted prompts are history: `↑`/`↓` (with the completion menu closed)
|
|
71
|
+
recall them per project across restarts — the first `↑` stashes the draft
|
|
72
|
+
and `↓` past the newest entry restores it. Pasted text folds to a single
|
|
73
|
+
line (newlines and tabs become spaces), so a multi-paragraph paste lands in
|
|
74
|
+
the box as one prompt instead of firing line-by-line. While a reply streams,
|
|
75
|
+
the footer shows a live status row — `⠋ working… (12s · esc to interrupt)` —
|
|
76
|
+
so stretches with no streamed output (long tool calls) still visibly tick.
|
|
77
|
+
|
|
78
|
+
Unexpected internal errors never take the REPL down silently: they print to
|
|
79
|
+
stderr and surface as an `-- error absorbed: …` note in the transcript while
|
|
80
|
+
everything keeps running. Only repeated failures within ten seconds trip a
|
|
81
|
+
breaker that shuts the UI down cleanly.
|
|
82
|
+
|
|
83
|
+
While remote access is enabled, turns started from other clients (the mobile
|
|
84
|
+
app, a second editor) render live in the REPL too, and questions or permission
|
|
85
|
+
requests answered elsewhere dismiss the local picker automatically.
|
|
86
|
+
|
|
87
|
+
Without a TTY (pipes, Windows editor shims — where the bin name is lost from
|
|
88
|
+
`argv`), bare `zcode-acp` falls back to the stdio server, so editor configs
|
|
89
|
+
pointing at either bin name keep working. Ask for the REPL explicitly with
|
|
90
|
+
`zcode-acp repl`; without a TTY that errors instead of falling back.
|
|
91
|
+
|
|
92
|
+
## Quota cards
|
|
93
|
+
|
|
94
|
+
Check plan usage from the terminal — no editor or running server needed. By
|
|
95
|
+
default it shows both **GLM Coding Plan** and **Opencode Go** in one card;
|
|
96
|
+
pass a provider to focus on one.
|
|
97
|
+
|
|
98
|
+
GLM credentials are read from `~/.zcode/v2/config.json`. Opencode Go
|
|
99
|
+
credentials come from environment variables (the dashboard needs a browser
|
|
100
|
+
cookie — see [Opencode Go setup](#opencode-go-setup) below).
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Both providers (default): GLM + Opencode Go in one card
|
|
104
|
+
zcode-acp quota
|
|
105
|
+
|
|
106
|
+
# Focus on one provider
|
|
107
|
+
zcode-acp quota glm # GLM Coding Plan only
|
|
108
|
+
zcode-acp quota go # Opencode Go only (rolling + weekly + monthly)
|
|
109
|
+
|
|
110
|
+
# Live monitor: clear the screen and refresh every 30s (default)
|
|
111
|
+
zcode-acp quota -w
|
|
112
|
+
zcode-acp quota go -w # watch Opencode Go only
|
|
113
|
+
|
|
114
|
+
# Refresh at a custom interval (seconds; minimum 10)
|
|
115
|
+
zcode-acp quota --watch --interval 60
|
|
116
|
+
|
|
117
|
+
# Plain monochrome bars (color is the default on a terminal)
|
|
118
|
+
zcode-acp quota --plain
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
By default the CLI renders heat-colored (green→yellow→red) progress bars with
|
|
122
|
+
the usage numbers overlaid inside the bar, so each line stays short. Pass
|
|
123
|
+
`--plain` (or `-p`) for the classic monochrome `█`/`░` layout. Color is also
|
|
124
|
+
disabled automatically when stdout is piped or redirected, so captured output
|
|
125
|
+
stays clean.
|
|
126
|
+
|
|
127
|
+
The watch mode clears and redraws the card in place, like `top`/`htop`. Press
|
|
128
|
+
`Ctrl-C` to exit. The 10s minimum exists because the quota API is cached for
|
|
129
|
+
10s internally — a shorter interval would just keep returning the stale cached
|
|
130
|
+
value.
|
|
131
|
+
|
|
132
|
+
When the package isn't globally installed, run the built file directly:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
node dist/cli.js quota -w
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Opencode Go setup
|
|
139
|
+
|
|
140
|
+
Opencode Go has no JSON API for subscription usage — the CLI scrapes the
|
|
141
|
+
authenticated dashboard at `opencode.ai/workspace/<id>/go`, so it needs your
|
|
142
|
+
browser `auth` cookie. Credentials are read from two sources, **merged
|
|
143
|
+
field-by-field with environment variables taking precedence** over the config
|
|
144
|
+
file:
|
|
145
|
+
|
|
146
|
+
- **Config file**: `~/.pi/agent/opencode-go.json` — same convention as the
|
|
147
|
+
`@beyona/pi-zai-usage` Pi extension, so if you already configured it there
|
|
148
|
+
you're done.
|
|
149
|
+
```json
|
|
150
|
+
{ "workspaceId": "wrk_your_workspace_id", "authCookie": "Fe26.2**your_cookie_value" }
|
|
151
|
+
```
|
|
152
|
+
- **Environment variables** (override the matching file field):
|
|
153
|
+
```bash
|
|
154
|
+
export OPENCODE_GO_WORKSPACE_ID="wrk_your_workspace_id"
|
|
155
|
+
export OPENCODE_GO_AUTH_COOKIE="Fe26.2**your_cookie_value"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
How to get the values:
|
|
159
|
+
|
|
160
|
+
1. **Workspace ID** — open `https://opencode.ai`, navigate to your Go
|
|
161
|
+
workspace, and copy the `wrk_…` id from the URL
|
|
162
|
+
(`https://opencode.ai/workspace/<wrk_…>/go`).
|
|
163
|
+
2. **Auth cookie** — open browser DevTools (F12) → Application → Cookies →
|
|
164
|
+
`opencode.ai` → copy the value of the cookie named `auth` (it starts with
|
|
165
|
+
`Fe26.2**`).
|
|
166
|
+
|
|
167
|
+
Without credentials, the default dual-provider mode silently shows GLM only
|
|
168
|
+
(no error). Running `zcode-acp quota go` without credentials prints a setup hint.
|
|
169
|
+
|
|
170
|
+
## Hub and server subcommands
|
|
171
|
+
|
|
172
|
+
`zcode-acp hub` runs the remote-access hub daemon manually (normally
|
|
173
|
+
auto-spawned by bridges — see [Remote Access](REMOTE.md)). `zcode-acp
|
|
174
|
+
server` speaks ACP on stdio — that is what editors invoke through the
|
|
175
|
+
`zcode-acp-server` bin; you rarely need it by hand.
|
|
176
|
+
|
|
177
|
+
## Upgrading from 0.11
|
|
178
|
+
|
|
179
|
+
0.12.0 folds the old standalone bins into the unified CLI (see
|
|
180
|
+
[ADR-0007](adr/0007-unified-cli-entry-and-bin-pruning.md)):
|
|
181
|
+
|
|
182
|
+
| Old (≤0.11) | New (0.12) |
|
|
183
|
+
| -------------------- | ------------------------------------- |
|
|
184
|
+
| `zcode-acp-server` | unchanged (kept for editor configs) |
|
|
185
|
+
| `zcode-quota [args]` | `zcode-acp quota [args]` (same flags) |
|
|
186
|
+
| `zcode-acp-hub` | `zcode-acp hub` |
|
|
187
|
+
|
|
188
|
+
Editor configs referencing `zcode-acp-server` keep working unchanged.
|
package/docs/REMOTE.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Remote Access
|
|
2
|
+
|
|
3
|
+
With `ZCODE_ACP_REMOTE=1` the bridge additionally accepts ACP connections over
|
|
4
|
+
WebSocket, so a phone or browser can watch and drive the **same sessions** as
|
|
5
|
+
your editor. Zed (or any ACP editor over stdio) remains the primary client and
|
|
6
|
+
owns the process: when the editor disconnects, the bridge — and every remote
|
|
7
|
+
attachment — exits with it.
|
|
8
|
+
|
|
9
|
+
A ready-made client — Android APK plus a self-hostable web build — lives at
|
|
10
|
+
[william0wang/zcode-acp-remote](https://github.com/william0wang/zcode-acp-remote).
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
phone / browser ──WS── tunnel ── hub (127.0.0.1:8377, single entry)
|
|
14
|
+
│ byte-level proxy
|
|
15
|
+
▼
|
|
16
|
+
bridge ACP endpoint (127.0.0.1:8378+n)
|
|
17
|
+
│ same AgentApp as stdio
|
|
18
|
+
Zed ──────── stdio ────────────────┘
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Enable it per-agent in Zed's settings (Zed merges these into the agent's
|
|
22
|
+
environment):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
"agents": {
|
|
26
|
+
"ZCode": {
|
|
27
|
+
"command": "zcode-acp-server",
|
|
28
|
+
"env": {
|
|
29
|
+
"ZCODE_ACP_REMOTE": "1",
|
|
30
|
+
"ZCODE_ACP_REMOTE_TOKEN": "<a-long-random-secret>"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Hub.** The first bridge with remote enabled spawns the hub daemon as a
|
|
37
|
+
detached, machine-level singleton on `ZCODE_ACP_HUB_PORT` (it can also be run
|
|
38
|
+
manually). It does three things only: token auth, instance discovery, and
|
|
39
|
+
byte-level proxying (ACP WebSocket plus read-only session files) — no session
|
|
40
|
+
state, no path semantics. It exits after ~10 idle minutes and is re-spawned
|
|
41
|
+
on demand. Each bridge registers every 10s as a heartbeat and drops out of
|
|
42
|
+
discovery ~30s after it stops.
|
|
43
|
+
|
|
44
|
+
**Discovery API** (for client authors; fields are additive-only):
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
GET /api/instances → [{"id","port","pid","startedAt","workspace",
|
|
48
|
+
"origin","sessions":[{"sessionId","title?","updatedAt"}]}]
|
|
49
|
+
GET /api/instances?probe=1 → same list, but unreachable bridges are pruned first
|
|
50
|
+
WS /acp?instance=<id> → proxied to that bridge's endpoint
|
|
51
|
+
GET /api/instances/{id}/fs/… → read-only session files (list + raw bytes, ADR-0004)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`origin` labels how an instance was started: `"editor"` (a bridge an editor
|
|
55
|
+
spawned over stdio) or `"serve"` (a headless bridge created remotely, see
|
|
56
|
+
below).
|
|
57
|
+
|
|
58
|
+
**Remote session-create** (ADR-0014). A remote client can start a NEW agent
|
|
59
|
+
session in any of the machine's known projects — no editor required:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
GET /api/projects → [{"workspacePath","sessions","lastActive"}]
|
|
63
|
+
POST /api/instances {workspacePath} → {"id","reused"}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`/api/projects` aggregates the App's tasks index: every workspace that ever
|
|
67
|
+
ran a session (system temp trees, `~/.zcode` itself, and vanished
|
|
68
|
+
directories filtered out), newest activity first. The list gates the POST —
|
|
69
|
+
paths outside it get 403 (a convenience bound, not a security boundary: a
|
|
70
|
+
token holder can already drive an editor-bridge session in any cwd; the
|
|
71
|
+
trust boundary is the token). On create the hub
|
|
72
|
+
spawns `zcode-acp serve` — a headless bridge — in the project's cwd; it
|
|
73
|
+
registers back within seconds and is reachable like any other instance. A
|
|
74
|
+
live serve instance for the same workspace is reused instead of re-spawned
|
|
75
|
+
(`reused:true`). The serve bridge lives for remote interest only: it exits
|
|
76
|
+
~10 minutes after the last client detaches and the last turn finishes, and
|
|
77
|
+
its `session/new` always uses the project cwd regardless of what a client
|
|
78
|
+
sends.
|
|
79
|
+
|
|
80
|
+
`sessions` lists the project's **currently running** conversations (live
|
|
81
|
+
editor tabs and remote attachments) under the same ACP session ids the
|
|
82
|
+
editor uses — attaching by id joins the conversation's live notification
|
|
83
|
+
stream, and the hub dedupes sessions shared by several bridges of the same
|
|
84
|
+
project.
|
|
85
|
+
|
|
86
|
+
Auth is `Authorization: Bearer <token>` or `?token=` (browsers cannot set WS
|
|
87
|
+
headers); `/api/*` sends `Access-Control-Allow-Origin: *` — the token is the
|
|
88
|
+
security boundary. A proxied connection stays bound to one instance; switching
|
|
89
|
+
instances means reconnecting. Remote clients can also pull plan quota via the
|
|
90
|
+
non-standard `account/usage_stats` ACP method (no session required), and
|
|
91
|
+
browse/download the files of a session's project through the `/fs` routes
|
|
92
|
+
above. During replay, compaction summaries and rewritten tool calls arrive as
|
|
93
|
+
collapsed `tool_call` updates instead of walls of text
|
|
94
|
+
([REPLAY-GUIDE.md](REPLAY-GUIDE.md)).
|
|
95
|
+
|
|
96
|
+
Building a remote client — web, mobile, or CLI? The full integration contract
|
|
97
|
+
(endpoints, framing, lifecycle timings, failure recovery, platform notes)
|
|
98
|
+
lives in [REMOTE-CLIENTS.md](REMOTE-CLIENTS.md).
|
|
99
|
+
|
|
100
|
+
**Semantics.** All agent notifications are broadcast to every client.
|
|
101
|
+
Permission / elicitation requests go to every client and the **first answer
|
|
102
|
+
wins**; losing clients receive `$/cancel_request` so their dialogs close.
|
|
103
|
+
Concurrent prompts for one session are serialized exactly as they are for a
|
|
104
|
+
single editor. Capabilities declared by any client are OR-merged.
|
|
105
|
+
|
|
106
|
+
**Tunnels.** Designed for one-port tunnels (Cloudflare Tunnel, frp): map the
|
|
107
|
+
hub port only. frp's `tcp` mode passes WebSocket as-is; Cloudflare Tunnel
|
|
108
|
+
drops idle WebSocket connections, so the hub sends 30s keepalive pings on both
|
|
109
|
+
legs. The bridge endpoint itself is loopback-only and never exposed.
|
|
110
|
+
|
|
111
|
+
**Binding beyond loopback.** The hub speaks plain HTTP/WS — the token travels
|
|
112
|
+
and authorizes in cleartext, so `ZCODE_ACP_HUB_HOST=0.0.0.0` (needed only when
|
|
113
|
+
the tunnel agent runs in its own container) is exactly as safe as the network
|
|
114
|
+
it lands on. Keep the bind loopback unless that interface is private to the
|
|
115
|
+
tunnel agent, and put TLS in front before mapping it anywhere untrusted.
|
package/docs/SANDBOX.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Sandbox
|
|
2
|
+
|
|
3
|
+
Optional macOS Seatbelt confinement for the file writes the agent performs.
|
|
4
|
+
This page is the full manual; the README keeps only a summary.
|
|
5
|
+
|
|
6
|
+
Two switches arm the sandbox (macOS only), whichever comes first:
|
|
7
|
+
|
|
8
|
+
- globally: `ZCODE_ACP_SANDBOX=1`, or
|
|
9
|
+
- per project: `"enabled": true` in `<workspace>/.zcode/acp/sandbox.json`.
|
|
10
|
+
The bridge auto-creates that file with `"enabled": false` the first time
|
|
11
|
+
you open the workspace — flip the flag to opt this project in, no global
|
|
12
|
+
env needed. Flipping it mid-run takes effect on the next prompt (the
|
|
13
|
+
backend restarts sandboxed); flipping back takes effect the next time the
|
|
14
|
+
backend restarts on its own.
|
|
15
|
+
|
|
16
|
+
Once armed, the zcode backend subprocess — and every Bash/Edit/Write it
|
|
17
|
+
performs, including all child processes — runs wrapped in a
|
|
18
|
+
Seatbelt (`sandbox-exec`) profile that denies file writes everywhere except:
|
|
19
|
+
|
|
20
|
+
- the workspace root(s) of your live sessions,
|
|
21
|
+
- `~/.zcode*` (the backend's own sessions/db/logs),
|
|
22
|
+
- the well-known system temp trees — `/tmp` (→ `/private/tmp`), `/var/tmp`,
|
|
23
|
+
and the per-user `/var/folders` tree that `$TMPDIR` lives in — plus
|
|
24
|
+
regenerable tool caches (`~/Library/Caches`, `~/.cache`, `~/.npm`,
|
|
25
|
+
`~/Library/pnpm`, `~/.node-gyp`),
|
|
26
|
+
- paths granted via the per-project config or the allow popup.
|
|
27
|
+
|
|
28
|
+
Reads and process execution stay open: deletion (`rm`, `mv`, truncation) is a
|
|
29
|
+
write-class syscall, so the write denial stops it regardless of which binary
|
|
30
|
+
performs it — including `/bin/rm`, `python shutil.rmtree`, or shell
|
|
31
|
+
redirections.
|
|
32
|
+
|
|
33
|
+
When a write outside the whitelist is attempted, the tool fails with
|
|
34
|
+
`Operation not permitted` and the bridge asks via the editor's permission
|
|
35
|
+
popup with four choices: allow **once**, allow **always**, reject **once**,
|
|
36
|
+
reject **always**. The "always" choices are persisted by the bridge into
|
|
37
|
+
`<workspace>/.zcode/acp/sandbox.json` (created on first run — allows to the
|
|
38
|
+
`allow` list, rejections to a `deny` list that suppresses future asks; edit
|
|
39
|
+
the file to undo either). "Once" choices and dismissed popups persist
|
|
40
|
+
nothing and will ask again. The agent itself cannot edit
|
|
41
|
+
that file — the sandbox denies
|
|
42
|
+
writes to `.zcode/acp/` inside the workspace while the bridge (outside the
|
|
43
|
+
sandbox) writes it on your behalf. After an allow, the backend restarts with
|
|
44
|
+
the widened profile (a few seconds; the bridge auto-continues the interrupted
|
|
45
|
+
task). Set `"strictGit": true` in the config to also put `.git` behind the
|
|
46
|
+
popup.
|
|
47
|
+
|
|
48
|
+
Ordinary filesystem permission failures (`Permission denied`, EACCES — a
|
|
49
|
+
chmod/ownership problem no popup can fix) are not sandbox denials; the bridge
|
|
50
|
+
surfaces them as a one-time hint instead of raising an ask.
|
|
51
|
+
|
|
52
|
+
This targets accident prevention, not malice: indirect escapes (an
|
|
53
|
+
agent-edited `.bashrc`, build scripts, or git hooks that you later run
|
|
54
|
+
yourself outside the sandbox) are out of scope — treat its output like any
|
|
55
|
+
other code review. Verify a profile manually with
|
|
56
|
+
`bash scripts/verify-sandbox.sh` (macOS, after `pnpm build`).
|