@parallel-cli/parallel 0.4.3 → 0.4.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  All notable changes to Parallel are documented here.
4
4
 
5
+ ## 0.4.5 - 2026-06-23
6
+
7
+ ### 0.4.5 Added
8
+
9
+ - Added explicit hub, focus, and attach input contexts with context-specific hints and filtered command suggestions.
10
+ - Added agent argument autocomplete for `/focus`, `/send`, `/attach`, `/pause`, `/resume`, `/stop`, `/restore`, and `/commit`.
11
+ - Added attach-terminal routing for `@all`, `@agent`, and `/send`, so dedicated terminals can broadcast or steer another agent through the main session.
12
+ - Added richer hub rows using latest useful agent signals, specialist badges, context percentage, and responsive timeline widths.
13
+ - Added durable session memory for claims, recent diff excerpts, file activity, work-map warnings, agent aliases, provider/model metadata, specialist, and context usage.
14
+ - Added shell mutation tracking: files changed by `run_command` now appear in live diffs, file activity, and agent commit ownership.
15
+ - Added a non-blocking work map that surfaces overlapping claims and repeated co-edit conflicts in `/board` and agent context.
16
+
17
+ ### 0.4.5 Changed
18
+
19
+ - Clarified agent naming around `Name: task` syntax in README examples.
20
+ - Made `/raw` visible only when it affects the active focus view.
21
+ - Reset focus scroll follow-tail behavior when switching focused agents.
22
+ - Improved session restore so `/restore` can target saved agents by name, alias, or id when their conversation is available.
23
+
24
+ ### 0.4.5 Fixed
25
+
26
+ - Fixed attach-terminal `@all` being treated as plain text to the attached agent.
27
+ - Fixed restored note ids drifting by resynchronizing blackboard note and change sequences after loading session data.
28
+ - Fixed shell-created edits being invisible to `/diff`, `/board`, and `/commit`.
29
+
30
+ ## 0.4.4 - 2026-06-23
31
+
32
+ ### 0.4.4 Added
33
+
34
+ - Added tool-level safeguards for `/ask` and `/plan`: ask agents cannot mutate project state, and plan agents must get explicit approval before mutating files or running risky shell commands.
35
+ - Added session-only provider setup for `/settings-session`, with an explicit choice between temporary session use and saving globally.
36
+ - Expanded `/doctor` into actionable readiness diagnostics for provider, model, key, local endpoints, attach socket, `git`, and `gh`.
37
+ - Added scrolling/windowing budgets to long TUI views such as board, notes, diffs, cost, skills, specialists, and sessions.
38
+ - Added saved-session restore hints and clearer `/restore` errors.
39
+
40
+ ### 0.4.4 Changed
41
+
42
+ - Reworked the README to render consistently on both GitHub and npm.
43
+ - Replaced wide Markdown tables and fixed-width command blocks with npm-friendly lists.
44
+ - Removed provider-specific environment variable guidance from the public README so provider setup remains neutral.
45
+ - Documented DeepSeek only as one provider preset in the Chinese provider group, not as a special standalone setup path.
46
+ - Made `@all` steer active agents directly instead of only posting a passive note.
47
+ - Made `/plan` timeouts safe by requiring manual approval before mutations are unlocked.
48
+ - Increased the saved sessions list window from 8 to 20 and shows `/save [name]` labels in `/sessions`.
49
+ - Bumped the TUI header version to `0.4.4`.
50
+
51
+ ### 0.4.4 Fixed
52
+
53
+ - Fixed `/commit message...` with exactly one agent so the first word is treated as part of the message, not as a missing agent name.
54
+ - Fixed `/project` and `/wizard` transitions by warning when active agents are running unless `--force` is passed.
55
+ - Fixed local/custom provider setup so localhost endpoints do not require an API key and placeholder models are not considered ready.
56
+ - Fixed stale focus after agents disappear through clear, stop, project switch, session load, or restore.
57
+ - Fixed `/settings-session` key entry so provider setup reaches the session-only vs global-save choice instead of returning early.
58
+ - Fixed first-run custom provider setup so it reviews/edits endpoints and skips API keys for localhost endpoints.
59
+ - Fixed filesystem boundary checks so agent tools reject sibling paths with a shared project-root prefix.
60
+ - Fixed TUI clipping risks by budgeting the hub by rendered rows and applying body-height windowing to notes/diffs.
61
+ - Fixed Settings Escape handling so typed inputs clear before navigating back.
62
+
5
63
  ## 0.4.3 - 2026-06-23
6
64
 
7
65
  ### 0.4.3 Added
@@ -32,7 +90,7 @@ All notable changes to Parallel are documented here.
32
90
  - Fixed session settings accidentally behaving like global provider mutation in several flows.
33
91
  - Fixed bare model IDs containing `:` such as `qwen3-coder:480b`.
34
92
  - Fixed stale default provider normalization during config load and provider removal.
35
- - Fixed `DEEPSEEK_API_KEY` overriding whichever provider happened to be default; it now targets DeepSeek only.
93
+ - Fixed provider-specific environment overrides leaking into whichever provider happened to be default.
36
94
  - Fixed local endpoints being blocked by missing API keys.
37
95
  - Fixed sensitive `/key ...` entries being stored in input history.
38
96
  - Fixed tiny pseudo-TTY dimensions causing negative string repeat values in the TUI header.
@@ -49,7 +107,7 @@ All notable changes to Parallel are documented here.
49
107
 
50
108
  ### 0.4.2 Changed
51
109
 
52
- - Reworked the README provider section to be provider-agnostic instead of DeepSeek-centered.
110
+ - Reworked the README provider section to be provider-agnostic.
53
111
  - Updated provider tables, endpoint documentation, and model catalog references.
54
112
  - Removed internal docs from remote tracking and kept them out of the public package.
55
113
 
package/README.md CHANGED
@@ -16,9 +16,12 @@ Parallel lets you run several AI coding agents on the same repository at the sam
16
16
  - Run multiple agents in parallel on one project.
17
17
  - Choose explicit modes: `/ask`, `/task`, and `/plan`.
18
18
  - Type plain text to launch a task agent immediately.
19
+ - Use context-aware input in the hub, focus view, and attached agent terminals.
19
20
  - Steer one agent with `@a1 ...` or broadcast with `@all ...`.
20
21
  - Open dedicated agent terminals with native scrollback.
22
+ - See a richer live hub with latest agent signals, mode badges, context usage, and responsive timelines.
21
23
  - Review agents, notes, file activity, diffs, cost, skills, specialists, and saved sessions from the TUI.
24
+ - Track shell-created file mutations in the same live diff feed as agent edits.
22
25
  - Configure OpenAI-compatible providers through a guided wizard and settings panel.
23
26
  - Use 29 provider presets across Western, Chinese, Gateway, Inference, and Local categories.
24
27
  - Support local no-key endpoints such as Ollama and vLLM/SGLang.
@@ -79,9 +82,9 @@ Plain text launches a `/task` agent. You can launch another agent while the firs
79
82
  Use explicit modes when intent matters:
80
83
 
81
84
  ```text
82
- /ask reviewer should we split the CLI parser?
83
- /plan migration propose the safest rollout for the config change
84
- /task builder implement the approved plan
85
+ /ask Reviewer: should we split the CLI parser?
86
+ /plan Migration: propose the safest rollout for the config change
87
+ /task Builder: implement the approved plan
85
88
  ```
86
89
 
87
90
  Steer a running agent:
@@ -96,13 +99,13 @@ Broadcast to every agent:
96
99
  @all stop changing public interfaces until the test agent finishes
97
100
  ```
98
101
 
102
+ `@all` steers active agents in real time. Finished, stopped, or errored agents are not relaunched by a broadcast.
103
+
99
104
  ## Agent Modes
100
105
 
101
- | Mode | Use it for | Behavior |
102
- | --- | --- | --- |
103
- | `/ask` | Questions, reviews, audits, tradeoffs | Answers and advises without editing files. |
104
- | `/task` | Implementation work | Executes, edits, validates, and summarizes. |
105
- | `/plan` | Risky or unclear work | Inspects first, presents a plan, then edits only after approval. |
106
+ - `/ask`: questions, reviews, audits, and tradeoffs. The agent answers and advises; mutating tools and shell commands are blocked.
107
+ - `/task`: implementation work. The agent can execute, edit, validate, and summarize.
108
+ - `/plan`: risky or unclear work. The agent inspects first, presents a plan, then edits only after explicit approval. A timeout does not approve the plan.
106
109
 
107
110
  Aliases:
108
111
 
@@ -122,29 +125,42 @@ The main TUI is the Parallel hub. It is designed to answer:
122
125
  - what changed in the project
123
126
  - what model, provider, shell mode, and cost are active
124
127
 
125
- Common hub commands:
128
+ Input has three explicit contexts:
129
+
130
+ - Hub: plain text launches a new `/task` agent. Slash suggestions show hub commands and agent arguments autocomplete for `/focus`, `/send`, `/attach`, `/pause`, `/resume`, `/stop`, `/restore`, and `/commit`.
131
+ - Focus: after `/focus a1`, plain text talks to the focused agent instead of spawning a new one. `/raw` affects this view only.
132
+ - Attach: in `parallel attach a1`, plain text steers the attached agent. `/task`, `/ask`, and `/plan` spawn new agents from that terminal, while `@all ...`, `@a2 ...`, and `/send ...` route instructions through the main session.
133
+
134
+ Use `Name: task` when naming an agent:
126
135
 
127
136
  ```text
128
- /agents agent overview
129
- /focus a1 inspect and steer one agent
130
- /raw toggle raw detail in focus view
131
- /board shared blackboard, claims, notes, file activity
132
- /diff live diff history
133
- /cost token and cost breakdown
134
- /sessions saved sessions
135
- /settings global settings
136
- /settings-session session-only settings
137
- /project [folder] change project folder
138
- /wizard rerun setup wizard
137
+ /task Tests: add regression coverage for the auth middleware
138
+ /plan Migration: outline the safest database rollout
139
139
  ```
140
140
 
141
+ Common hub commands:
142
+
143
+ - `/agents`: agent overview.
144
+ - `/focus a1`: inspect and steer one agent.
145
+ - `/raw`: toggle raw detail in focus view.
146
+ - `/board`: shared blackboard, claims, notes, and file activity.
147
+ - `/diff`: live diff history.
148
+ - `/cost`: token and cost breakdown.
149
+ - `/sessions`: saved sessions.
150
+ - `/settings`: global settings.
151
+ - `/settings-session`: session-only settings.
152
+ - `/project [folder]`: change project folder.
153
+ - `/wizard`: rerun setup wizard.
154
+
155
+ Commands are typed in the control room input. When a long view is open, use Escape to return to the agents view/input.
156
+
141
157
  Keyboard behavior:
142
158
 
143
159
  - `/` opens slash command suggestions.
144
160
  - Up/Down selects suggestions when a suggestion menu is open.
145
161
  - Enter accepts the selected suggestion.
146
162
  - Tab or Right accepts the best completion.
147
- - Up/Down scrolls long views such as `/help`.
163
+ - PgUp/PgDn scrolls the hub or focus view even while the input is active. Up/Down scrolls long views and navigates suggestions/history.
148
164
  - Escape returns to the agents view or clears the input.
149
165
 
150
166
  Best terminal size is around `120x34`. Parallel adapts to smaller terminals, but the hub is most readable with enough width for model, folder, status, and agent summaries.
@@ -170,9 +186,12 @@ From an attached terminal:
170
186
 
171
187
  ```text
172
188
  plain text sends a message to this agent
173
- /task write parser regression tests
174
- /ask is this result safe to merge?
175
- /plan prepare a migration plan
189
+ @all pause public interface changes until tests finish
190
+ @a2 re-read the API client before editing it
191
+ /send a2 check the new parser contract
192
+ /task Tests: write parser regression tests
193
+ /ask Reviewer: is this result safe to merge?
194
+ /plan Migration: prepare a migration plan
176
195
  /raw
177
196
  /quit
178
197
  ```
@@ -202,101 +221,93 @@ Provider setup is guided in both the first-run wizard and `/settings`:
202
221
  4. Enter the provider API key if the provider requires one.
203
222
  5. Save globally or use the provider/model for the current session.
204
223
 
205
- Local providers such as Ollama and vLLM/SGLang do not require an API key. You can still review and edit their endpoints.
224
+ Local providers such as Ollama, vLLM/SGLang, and custom localhost OpenAI-compatible endpoints do not require an API key. You can still review and edit their endpoints. Ollama/local OpenAI-compatible endpoints can detect models from `/models`; vLLM/SGLang requires replacing the `your-model-here` placeholder before it is considered ready.
206
225
 
207
226
  Useful settings commands:
208
227
 
209
- ```text
210
- /settings global language, providers, keys, defaults, approvals
211
- /settings-session temporary model, provider, approvals, sound
212
- /model show current session model
213
- /model provider:id switch model for this session
214
- /doctor check provider/model/API key readiness
215
- ```
228
+ - `/settings`: global language, providers, keys, defaults, and approvals.
229
+ - `/settings-session`: temporary model, provider, approvals, and sound. New providers can be used for this session only or saved globally.
230
+ - `/model`: show the current session model.
231
+ - `/model provider:id`: switch model for this session.
232
+ - `/doctor`: check provider, model, API key, local endpoint reachability, attach socket, `git`, and `gh`.
216
233
 
217
234
  Configuration is stored in `~/.parallel/config.json`.
218
235
 
219
236
  Environment variables:
220
237
 
221
- | Variable | Purpose |
222
- | --- | --- |
223
- | `PARALLEL_API_KEY` | API key for the current default provider. |
224
- | `DEEPSEEK_API_KEY` | API key for the DeepSeek provider only. |
225
- | `PARALLEL_BASE_URL` | Override the default provider base URL. |
226
- | `PARALLEL_MODEL` | Override the session model. |
227
- | `PARALLEL_NO_ALT_SCREEN=1` | Disable the alternate terminal screen. |
238
+ - `PARALLEL_API_KEY`: API key for the current default provider.
239
+ - `PARALLEL_BASE_URL`: override the default provider base URL.
240
+ - `PARALLEL_MODEL`: override the session model.
241
+ - `PARALLEL_NO_ALT_SCREEN=1`: disable the alternate terminal screen.
228
242
 
229
243
  ## Commands
230
244
 
231
245
  ### Create Agents
232
246
 
233
- | Command | Description |
234
- | --- | --- |
235
- | `/ask [Name:] <question> [--model=m]` | Launch an ask-only agent. |
236
- | `/task [Name:] <task> [--model=m] [#skill]` | Launch a task agent. Plain text does the same. |
237
- | `/plan [Name:] <task> [--model=m]` | Launch a plan-first agent. |
238
- | `/issue <n>` | Spawn a task from a GitHub issue using the `gh` CLI. |
239
- | `/specialist <name> <task>` | Spawn with a specialist persona. |
240
- | `/specialist new <name> [global]` | Create a specialist template. |
241
- | `/skill new <name> [global]` | Create a skill template. |
247
+ - `/ask [Name:] <question> [--model=m]`: launch an ask-only agent.
248
+ - `/task [Name:] <task> [--model=m] [#skill]`: launch a task agent. Plain text does the same.
249
+ - `/plan [Name:] <task> [--model=m]`: launch a plan-first agent. It cannot mutate files or run risky shell commands until you manually approve the plan.
250
+ - `/issue <n>`: spawn a task from a GitHub issue. Requires the `gh` CLI, a GitHub repository, and `gh auth login`.
251
+ - `/specialist <name> <task>`: spawn with a specialist persona.
252
+ - `/specialist new <name> [global]`: create a specialist template.
253
+ - `/skill new <name> [global]`: create a skill template.
242
254
 
243
255
  ### Steer Agents
244
256
 
245
- | Command | Description |
246
- | --- | --- |
247
- | `@agent <message>` | Send a live instruction to one agent. |
248
- | `@all <message>` | Broadcast an instruction to all agents. |
249
- | `/send <agent\|all> <message>` | Command form of live steering. |
250
- | `/attach <agent\|on\|off>` | Open an agent terminal or toggle automatic terminals. |
251
- | `/focus <agent\|off>` | Route plain input to one agent instead of spawning new agents. |
252
- | `/pause <agent\|all>` | Pause at the next action boundary. |
253
- | `/resume <agent\|all>` | Resume paused agents. |
254
- | `/stop <agent\|all>` | Stop running agents. |
255
- | `/clear` | Remove finished agents from the current display. |
256
- | `/raw` | Toggle conversation-raw view. |
257
- | `/copy` | Copy the latest completed result to clipboard. |
257
+ - `@agent <message>`: send a live instruction to one agent.
258
+ - `@all <message>`: broadcast an instruction to all agents.
259
+ - `/send <agent|all> <message>`: command form of live steering.
260
+ - `/attach <agent|on|off>`: open an agent terminal or toggle automatic terminals.
261
+ - `/focus <agent|off>`: route plain input to one agent instead of spawning new agents.
262
+ - `/pause <agent|all>`: pause at the next action boundary.
263
+ - `/resume <agent|all>`: resume paused agents.
264
+ - `/stop <agent|all>`: stop running agents.
265
+ - `/clear`: remove finished agents from the current display.
266
+ - `/raw`: toggle conversation-raw view.
267
+ - `/copy`: copy the latest completed result to clipboard.
258
268
 
259
269
  ### Git Safety
260
270
 
261
- | Command | Description |
262
- | --- | --- |
263
- | `/undo [agent]` | Revert the last file change made by an agent, with conflict detection. |
264
- | `/commit [agent\|all] [message]` | Commit files touched by an agent or by all agents. |
265
- | `/autocommit <on\|off>` | Commit each agent's changes automatically when it finishes. |
271
+ - `/undo [agent]`: revert the last file change made by an agent, with conflict detection.
272
+ - `/commit [agent|all] [message]`: commit only files touched by the selected agent or by all agents. It does not run `git add -A`. With exactly one agent, `/commit message...` uses that agent and treats the rest as the message.
273
+ - `/autocommit <on|off>`: commit each agent's touched files automatically when it finishes. This is session-only.
266
274
 
267
275
  ### Views And Sessions
268
276
 
269
- | Command | Description |
270
- | --- | --- |
271
- | `/agents` | Agent overview. |
272
- | `/board` | Shared blackboard, file activity, claims, and notes. |
273
- | `/notes` | Full notes history. |
274
- | `/diff` | Live diff history. |
275
- | `/cost` | Token and cost breakdown. |
276
- | `/status` | Session model, approval mode, agents, cost snapshot. |
277
- | `/skills` | Available skills. |
278
- | `/specialists` | Available specialists. |
279
- | `/save [name]` | Save the current session. |
280
- | `/sessions` | List saved sessions. |
281
- | `/session <n\|latest>` | Restore a saved session. |
282
- | `/restore <agent>` | Relaunch a restored agent with its conversation history. |
277
+ - `/agents`: agent overview.
278
+ - `/board`: shared blackboard, file activity, claims, and notes.
279
+ - `/notes`: full notes history.
280
+ - `/diff`: live diff history.
281
+ - `/cost`: token and cost breakdown.
282
+ - `/status`: session model, approval mode, agents, and cost snapshot.
283
+ - `/skills`: available skills.
284
+ - `/specialists`: available specialists.
285
+ - `/save [name]`: save the current session.
286
+ - `/sessions`: list saved sessions.
287
+ - `/session <n|latest>`: load a saved session snapshot. If active agents are running, use `/session <n|latest> --force` after saving/stopping what you need.
288
+ - `/restore <agent>`: relaunch a restored agent by name, alias, or saved id when its conversation history is still available.
289
+
290
+ Session memory has two layers:
291
+
292
+ - Live memory: active agents see statuses, notes, claims, work-map warnings, file activity, and recent diffs before every model action.
293
+ - Durable memory: `/save` and autosave persist notes, claims, recent diff excerpts, file activity, work-map warnings, agent aliases, model/provider metadata, context usage, and conversation paths for restore.
294
+
295
+ Restore is best effort and explicit. `/session` reloads coordination memory into the blackboard; `/restore <agent>` relaunches an agent only when the saved conversation file still exists. Restored agents keep their prior task, mode, model, specialist, and conversation when available.
283
296
 
284
297
  ### Settings And Exit
285
298
 
286
- | Command | Description |
287
- | --- | --- |
288
- | `/model [[provider:]model]` | Show or switch the session model. |
289
- | `/approvals <ask\|auto\|auto-safe\|yolo>` | Set shell approvals for this session. |
290
- | `/sound <on\|off>` | Toggle terminal bell notifications. |
291
- | `/settings` | Edit global language, providers, keys, defaults, and approvals. |
292
- | `/settings-session` | Edit session-only model, approvals, and sound. |
293
- | `/project [folder]` | Change project folder or reopen the folder picker. |
294
- | `/folder [folder]` | Alias for `/project`. |
295
- | `/wizard` | Relaunch the setup wizard. |
296
- | `/setup` | Alias for `/wizard`. |
297
- | `/doctor` | Check provider, key, and model configuration. |
298
- | `/help` | Full command reference. |
299
- | `/quit` | Save the session and exit. |
299
+ - `/model [[provider:]model]`: show or switch the session model.
300
+ - `/approvals <ask|auto|auto-safe|yolo>`: set shell approvals for this session.
301
+ - `/sound <on|off>`: toggle terminal bell notifications.
302
+ - `/settings`: edit global language, providers, keys, defaults, and approvals.
303
+ - `/settings-session`: edit session-only model, provider, approvals, and sound.
304
+ - `/project [folder]`: change project folder or reopen the folder picker. If agents are active, use `/project [folder] --force` after saving/stopping what you need.
305
+ - `/folder [folder]`: alias for `/project`.
306
+ - `/wizard`: relaunch the setup wizard. If agents are active, use `/wizard --force` after saving/stopping what you need.
307
+ - `/setup`: alias for `/wizard`.
308
+ - `/doctor`: run local readiness diagnostics for provider, key, model, endpoint, attach socket, and Git tooling.
309
+ - `/help`: full command reference.
310
+ - `/quit`: save the session and exit.
300
311
 
301
312
  When there is exactly one agent, commands such as `/undo`, `/focus`, `/pause`, `/resume`, `/stop`, and `/commit` can omit the agent name.
302
313
 
@@ -310,11 +321,9 @@ Parallel separates agent modes from shell approval behavior.
310
321
  /approvals yolo
311
322
  ```
312
323
 
313
- | Mode | Behavior |
314
- | --- | --- |
315
- | `ask` | Ask before shell commands unless explicitly allowed. |
316
- | `auto-safe` | Auto-approve safe inspection/build/test commands and ask for risky commands. |
317
- | `yolo` | Auto-approve every shell command. Intended for trusted/headless usage only. |
324
+ - `ask`: ask before shell commands unless explicitly allowed.
325
+ - `auto-safe`: auto-approve safe inspection/build/test commands and ask for risky commands.
326
+ - `yolo`: auto-approve every shell command. Intended for trusted/headless usage only.
318
327
 
319
328
  `auto` is accepted as a compatibility spelling for `auto-safe`.
320
329
 
@@ -353,6 +362,10 @@ When an agent writes a file:
353
362
 
354
363
  This keeps agents moving without allowing silent overwrites.
355
364
 
365
+ Commands run through `run_command` are also snapshotted before and after execution. If a shell command edits, creates, or deletes tracked project files, Parallel records those mutations in `/diff`, `/board`, and `/commit` ownership just like tool-based edits.
366
+
367
+ The work map is advisory, not a lock. Agents can declare claims with `claim_files`; Parallel detects overlapping claims and repeated conflicts, then shows non-blocking warnings in `/board` and injects them into agent context so agents can coordinate before collisions become expensive.
368
+
356
369
  ## Headless Mode
357
370
 
358
371
  For CI and scripts, run without the TUI:
@@ -27,7 +27,7 @@ ${mode === 'ask'
27
27
  - Explore first with read-only tools.
28
28
  - Before modifying any file or running mutating commands, call ask_user with a concrete implementation plan.
29
29
  - The plan must include steps, files you expect to touch, risks, and validation.
30
- - Use options ["Approve", "Revise"], recommended "Approve".
30
+ - Use options ["Approve", "Revise"], recommended "Revise" so timeout never approves changes.
31
31
  - Start editing only after explicit "Approve".
32
32
  - Finish with task_complete using this user-facing structure in ${userLang}: "Plan appliqué", "Ce que j’ai modifié", "Validation", "Risques restants".`
33
33
  : `TASK MODE:
@@ -97,7 +97,7 @@ export class Agent {
97
97
  this.llm = opts.llm;
98
98
  this.board = opts.board;
99
99
  this.maxSteps = opts.maxSteps;
100
- this.executor = new ToolExecutor(opts.board, opts.id, opts.name, opts.projectRoot, opts.requestApproval, opts.requestQuestion, opts.skills);
100
+ this.executor = new ToolExecutor(opts.board, opts.id, opts.name, opts.projectRoot, opts.requestApproval, opts.requestQuestion, opts.skills, opts.mode);
101
101
  const info = {
102
102
  id: opts.id,
103
103
  name: opts.name,
@@ -2,8 +2,21 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { exec } from 'node:child_process';
4
4
  import * as Diff from 'diff';
5
- const IGNORED = new Set(['node_modules', '.git', '.parallel', 'dist', '__pycache__', '.venv', 'venv']);
5
+ const IGNORED = new Set(['node_modules', '.git', '.parallel', '.cursor', 'dist', '__pycache__', '.venv', 'venv']);
6
6
  const MAX_OUTPUT = 12_000;
7
+ const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'claim_files', 'remember']);
8
+ function isMutatingShell(command) {
9
+ const c = command.toLowerCase();
10
+ if (/\b(rm|mv|cp|chmod|chown|mkdir|touch|truncate)\b/.test(c))
11
+ return true;
12
+ if (/\b(git\s+(add|commit|push|pull|merge|rebase|checkout|switch|reset|clean|stash|tag))\b/.test(c))
13
+ return true;
14
+ if (/\b(npm|pnpm|yarn)\s+(install|add|remove|update|audit\s+fix)\b/.test(c))
15
+ return true;
16
+ if (/[>|]\s*(sh|bash)\b/.test(c) || /\b(curl|wget)\b.*\|\s*(sh|bash)/.test(c))
17
+ return true;
18
+ return false;
19
+ }
7
20
  export const TOOL_DEFINITIONS = [
8
21
  {
9
22
  type: 'function',
@@ -228,11 +241,13 @@ export class ToolExecutor {
228
241
  requestApproval;
229
242
  requestQuestion;
230
243
  skills;
244
+ mode;
231
245
  /** Last content this agent has seen for each file — basis of adaptive merging. */
232
246
  lastRead = new Map();
233
247
  /** Questions already asked — capped at 3 per task. */
234
248
  questionsAsked = 0;
235
- constructor(board, agentId, agentName, projectRoot, requestApproval, requestQuestion, skills) {
249
+ planApproved = false;
250
+ constructor(board, agentId, agentName, projectRoot, requestApproval, requestQuestion, skills, mode = 'task') {
236
251
  this.board = board;
237
252
  this.agentId = agentId;
238
253
  this.agentName = agentName;
@@ -240,10 +255,13 @@ export class ToolExecutor {
240
255
  this.requestApproval = requestApproval;
241
256
  this.requestQuestion = requestQuestion;
242
257
  this.skills = skills;
258
+ this.mode = mode;
243
259
  }
244
260
  resolve(rel) {
245
- const abs = path.resolve(this.projectRoot, rel);
246
- if (!abs.startsWith(path.resolve(this.projectRoot))) {
261
+ const root = path.resolve(this.projectRoot);
262
+ const abs = path.resolve(root, rel);
263
+ const relative = path.relative(root, abs);
264
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
247
265
  throw new Error(`Path outside the project refused: ${rel}`);
248
266
  }
249
267
  return abs;
@@ -251,8 +269,65 @@ export class ToolExecutor {
251
269
  relOf(p) {
252
270
  return path.relative(this.projectRoot, this.resolve(p)) || '.';
253
271
  }
272
+ snapshotProject() {
273
+ const snapshot = new Map();
274
+ const walk = (dir, depth) => {
275
+ if (depth > 8)
276
+ return;
277
+ let entries;
278
+ try {
279
+ entries = fs.readdirSync(dir, { withFileTypes: true });
280
+ }
281
+ catch {
282
+ return;
283
+ }
284
+ for (const e of entries) {
285
+ if (IGNORED.has(e.name) || e.name.startsWith('.git'))
286
+ continue;
287
+ const full = path.join(dir, e.name);
288
+ const relPath = path.relative(this.projectRoot, full);
289
+ if (e.isDirectory()) {
290
+ walk(full, depth + 1);
291
+ continue;
292
+ }
293
+ if (!e.isFile())
294
+ continue;
295
+ try {
296
+ const stat = fs.statSync(full);
297
+ if (stat.size > 750_000)
298
+ continue;
299
+ snapshot.set(relPath, fs.readFileSync(full, 'utf8'));
300
+ }
301
+ catch {
302
+ /* binary/unreadable files are ignored for diff tracking */
303
+ }
304
+ }
305
+ };
306
+ walk(this.projectRoot, 0);
307
+ return snapshot;
308
+ }
309
+ recordShellMutations(before) {
310
+ const after = this.snapshotProject();
311
+ const paths = new Set([...before.keys(), ...after.keys()]);
312
+ let count = 0;
313
+ for (const relPath of [...paths].sort()) {
314
+ const oldContent = before.get(relPath);
315
+ const newContent = after.get(relPath);
316
+ if (oldContent === newContent)
317
+ continue;
318
+ this.board.addChange(this.agentId, relPath, oldContent ?? '', newContent ?? '');
319
+ this.board.recordActivity(relPath, this.agentId, 'shell');
320
+ count++;
321
+ }
322
+ if (count > 0)
323
+ this.board.log(this.agentId, 'tool', `✏ shell changed ${count} file${count === 1 ? '' : 's'}`);
324
+ return count;
325
+ }
254
326
  async execute(name, args) {
255
327
  try {
328
+ const guard = this.guardTool(name, args);
329
+ if (guard)
330
+ return guard;
256
331
  switch (name) {
257
332
  case 'list_files':
258
333
  return this.listFiles(args?.path ?? '.');
@@ -293,6 +368,22 @@ export class ToolExecutor {
293
368
  return `ERROR: ${err?.message ?? String(err)}`;
294
369
  }
295
370
  }
371
+ guardTool(name, args) {
372
+ if (this.mode === 'ask') {
373
+ if (MUTATING_TOOLS.has(name) || name === 'run_command') {
374
+ return `DENIED: this agent is in /ask mode. It can inspect and advise, but cannot modify files, run shell commands, claim files, or write memory.`;
375
+ }
376
+ }
377
+ if (this.mode === 'plan' && !this.planApproved) {
378
+ if (MUTATING_TOOLS.has(name)) {
379
+ return `DENIED: this agent is in /plan mode and the plan has not been approved yet. Present the plan with ask_user and wait for "Approve" before modifying project state.`;
380
+ }
381
+ if (name === 'run_command' && isMutatingShell(String(args?.command ?? ''))) {
382
+ return `DENIED: this shell command looks mutating. In /plan mode, run only read-only inspection before approval; ask_user for plan approval first.`;
383
+ }
384
+ }
385
+ return null;
386
+ }
296
387
  /**
297
388
  * Ask the user a multiple-choice question. NEVER blocks forever: the UI shows
298
389
  * a visible 30s countdown and falls back to the recommended option (auto-run).
@@ -312,9 +403,14 @@ export class ToolExecutor {
312
403
  this.questionsAsked++;
313
404
  this.board.setAgentState(this.agentId, 'waiting', `question: ${question.slice(0, 60)}`);
314
405
  this.board.log(this.agentId, 'note', `❓ ${question}`);
315
- const answer = await this.requestQuestion(this.agentId, question, options, recommended);
406
+ const response = await this.requestQuestion(this.agentId, question, options, recommended);
407
+ const answer = response.answer;
408
+ if (this.mode === 'plan' && !response.auto && answer.trim().toLowerCase().startsWith('approve')) {
409
+ this.planApproved = true;
410
+ }
316
411
  this.board.setAgentState(this.agentId, 'working');
317
- return `The user answered: "${answer}". Act on this choice now (${3 - this.questionsAsked} question(s) left for this task).`;
412
+ const source = response.auto ? 'The timeout auto-selected' : 'The user answered';
413
+ return `${source}: "${answer}". Act on this choice now (${3 - this.questionsAsked} question(s) left for this task).`;
318
414
  }
319
415
  /** Declare (advisory) work areas — visible to the user and the other agents. */
320
416
  claimFiles(args) {
@@ -550,6 +646,7 @@ export class ToolExecutor {
550
646
  }
551
647
  this.board.setAgentState(this.agentId, 'working', `$ ${command.slice(0, 60)}`);
552
648
  this.board.log(this.agentId, 'tool', `$ ${command}`);
649
+ const before = this.snapshotProject();
553
650
  return new Promise((resolve) => {
554
651
  exec(command, { cwd: this.projectRoot, timeout: 120_000, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
555
652
  let out = '';
@@ -564,8 +661,9 @@ export class ToolExecutor {
564
661
  if (out.length > MAX_OUTPUT)
565
662
  out = out.slice(0, MAX_OUTPUT) + '\n... (output truncated)';
566
663
  const result = out || '(no output, success)';
664
+ const changed = this.recordShellMutations(before);
567
665
  this.board.log(this.agentId, 'tool_result', result);
568
- resolve(result);
666
+ resolve(changed > 0 ? `${result}\n\nTracked shell mutations: ${changed} file${changed === 1 ? '' : 's'}.` : result);
569
667
  });
570
668
  });
571
669
  }