@parallel-cli/parallel 0.4.6 → 0.4.8

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,48 @@
2
2
 
3
3
  All notable changes to Parallel are documented here.
4
4
 
5
+ ## 0.4.8 - 2026-06-24
6
+
7
+ ### 0.4.8 Changed
8
+
9
+ - Added clearer attached-terminal control with `/stop`, visible stop hints for active agents, and command palette support in attached terminals.
10
+ - Made hidden Hub progress explicit by showing `+N steps` with direct `full /focus aN` and `term /attach aN` shortcuts when rows are truncated.
11
+ - Froze elapsed-time telemetry once agents reach `done`, `error`, or `stopped` so finished agents no longer keep counting.
12
+
13
+ ### 0.4.8 Fixed
14
+
15
+ - Fixed OpenAI-compatible `tool_calls` history failures by recording assistant tool calls and all matching tool results atomically, even when a task completes early or an agent is stopped.
16
+ - Repaired restored conversations with missing tool results before the next model call to prevent 400 errors after interrupted runs.
17
+ - Restored a bounded live activity timeline in attached terminals while preserving append-only native scrollback for final results.
18
+
19
+ ## 0.4.7 - 2026-06-24
20
+
21
+ ### 0.4.7 Added
22
+
23
+ - Added file revision safety for shared-tree co-editing so stale `write_file` retries and `edit_file` calls must re-read and merge concurrent work before mutating.
24
+ - Added immediate agent-to-agent note nudging with duplicate-note suppression, active-agent checks, and rate-limit safeguards.
25
+ - Added visible coordination signals in the Hub, `/board`, `/diff`, and timelines for claims, work-map warnings, notes, approvals, and questions.
26
+ - Added `/review [agent|all] [prompt]`, a lightweight ask-mode reviewer that returns `APPROVE`, `REVISE`, or `BLOCK` with risks, tests to run, and files to inspect.
27
+ - Added Cursor-style per-agent progress steps through `update_steps`, visible in the Hub and attached terminals.
28
+ - Added batched read-only inspection tools (`read_many`, `inspect_project`) plus lightweight performance counters for model turns, tool calls, shell commands, and context usage.
29
+
30
+ ### 0.4.7 Changed
31
+
32
+ - Reworked `/board` into a coordination surface with Work map warnings first, agent/path/time metadata, and suggestions to inspect `/focus` or `/diff`.
33
+ - Prioritized incoming notes and work-map context in agent live context so coordination updates are handled before ordinary activity.
34
+ - Updated product messaging around shared awareness: agents work like a live team on one working tree, not isolated background jobs.
35
+ - Reworked agent rows to keep the Hub compact with cropped summaries, cream bullet recaps capped at four lines, mode badges, and visible `full /focus aN` plus `term /attach aN` shortcuts.
36
+ - Reworked dedicated agent terminals with a launch header, hidden raw launch noise in normal mode, append-only final results, and a small live status region so native scrollback remains usable.
37
+ - Tuned agent prompts by mode so `/ask`, `/task`, `/plan`, and `/review` use clearer contracts, early progress steps, batched inspection, and less redundant shell micro-commanding.
38
+
39
+ ### 0.4.7 Fixed
40
+
41
+ - Fixed the previous collision retry weakness where a stale writer could receive an adaptation diff and then overwrite without a real re-read.
42
+ - Fixed `edit_file` allowing targeted edits on a stale file as long as `old_string` still existed.
43
+ - Fixed timeline narration being hardcoded in French by routing narration through i18n.
44
+ - Fixed `claim_files` appearing as generic file activity instead of coordination activity.
45
+ - Fixed completed attached terminals repainting large dynamic result panels that could trap mouse-wheel scroll at the top.
46
+
5
47
  ## 0.4.6 - 2026-06-24
6
48
 
7
49
  ### 0.4.6 Added
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Parallel
2
2
 
3
- Real-time multi-agent coding from one terminal control room.
3
+ Real-time coding agents that work like a live team, not isolated background jobs.
4
4
 
5
- Parallel lets you run several AI coding agents on the same repository at the same time. Each agent has its own task, mode, live activity timeline, model, and shared session context. The TUI stays keyboard-first so you can launch work, inspect progress, answer approvals, steer agents, and review changes without leaving the terminal.
5
+ Parallel lets several AI coding agents co-edit the same repository at the same time with shared awareness injected before every model action. Each agent has its own task, mode, live activity timeline, model, and visible coordination context. The TUI stays keyboard-first so you can launch work, inspect progress, answer approvals, steer agents, review risks, and reconcile changes without leaving the terminal.
6
6
 
7
- > One hub. Many agents. Shared context. Human in control.
7
+ > One working tree. Many agents. Shared awareness. Human in control.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@parallel-cli/parallel?color=blue)](https://www.npmjs.com/package/@parallel-cli/parallel)
10
10
  ![node version](https://img.shields.io/node/v/@parallel-cli/parallel)
@@ -13,14 +13,18 @@ Parallel lets you run several AI coding agents on the same repository at the sam
13
13
 
14
14
  ## Highlights
15
15
 
16
- - Run multiple agents in parallel on one project.
17
- - Choose explicit modes: `/ask`, `/task`, and `/plan`.
16
+ - Run multiple agents as a live team on one shared working tree.
17
+ - Choose explicit modes: `/ask`, `/task`, `/plan`, and `/review`.
18
18
  - Type plain text to launch a task agent immediately.
19
19
  - Use context-aware input in the hub, focus view, and attached agent terminals.
20
20
  - Steer one agent with `@a1 ...` or broadcast with `@all ...`.
21
21
  - Open dedicated agent terminals with native scrollback.
22
22
  - Use a cleaner Codex-like hub with a framed header, focused prompt bar, and quieter empty state.
23
23
  - Review agents, notes, file activity, diffs, cost, skills, specialists, and saved sessions from the TUI.
24
+ - Use `/review [agent|all]` to spawn a lightweight ask-mode reviewer with verdict, risks, tests, and files to inspect.
25
+ - Avoid lost work with file revision safety: stale `write_file` and `edit_file` calls must re-read and merge before retrying.
26
+ - Nudge agents immediately when another agent posts a targeted note, with dedupe and rate-limit safeguards.
27
+ - See overlapping claims and repeated co-edit conflicts in the Hub, `/board`, `/diff`, and agent timelines.
24
28
  - Track shell-created file mutations in the same live diff feed as agent edits.
25
29
  - Configure OpenAI-compatible providers through a guided wizard and settings panel.
26
30
  - Use 29 provider presets across Western, Chinese, Gateway, Inference, and Local categories.
@@ -86,6 +90,7 @@ Use explicit modes when intent matters:
86
90
  /ask Reviewer: should we split the CLI parser?
87
91
  /plan Migration: propose the safest rollout for the config change
88
92
  /task Builder: implement the approved plan
93
+ /review all before we commit
89
94
  ```
90
95
 
91
96
  Steer a running agent:
@@ -102,11 +107,36 @@ Broadcast to every agent:
102
107
 
103
108
  `@all` steers active agents in real time. Finished, stopped, or errored agents are not relaunched by a broadcast.
104
109
 
110
+ ## Best Use Case: Coupled Work
111
+
112
+ Parallel is strongest when the work is too coupled for isolated fan-out and too large for one agent.
113
+
114
+ Example live-team flow:
115
+
116
+ ```text
117
+ /task API: define the new billing quote contract in src/api/quotes.ts
118
+ /task Client: build the UI client against the quote contract
119
+ /task Tests: write integration coverage for quote creation and validation
120
+ ```
121
+
122
+ The API agent can claim `src/api`, post a note with the proposed signature, and update the contract. The client agent sees that note and the diff before its next model action, re-reads the file, and adapts without inventing a second interface. The tests agent watches both sides, adds coverage, and can ask for clarification before the three agents collide.
123
+
124
+ Before committing:
125
+
126
+ ```text
127
+ /review all verify the API contract, client usage, and tests agree
128
+ ```
129
+
130
+ The reviewer is ask-only: it does not edit, does not gate the session globally, and returns a structured verdict with risks and validation steps.
131
+
105
132
  ## Agent Modes
106
133
 
107
134
  - `/ask`: questions, reviews, audits, and tradeoffs. The agent answers and advises; mutating tools and shell commands are blocked.
108
- - `/task`: implementation work. The agent can execute, edit, validate, and summarize.
135
+ - `/task`: implementation work. The agent can execute, edit, validate, and summarize. It may also conclude that no file change is needed when the task is a verification.
109
136
  - `/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.
137
+ - `/review`: lightweight reviewer around `/ask`. It inspects the current shared-tree work and returns `APPROVE`, `REVISE`, or `BLOCK` with risks, tests to run, and files to inspect.
138
+
139
+ Task and plan agents maintain a small Cursor-style checklist with one active step at a time. The runtime also encourages batched inspection through `read_many` and `inspect_project` so agents avoid slow chains of tiny read-only shell commands.
110
140
 
111
141
  Aliases:
112
142
 
@@ -118,7 +148,7 @@ Plain text is equivalent to `/task`.
118
148
 
119
149
  ## Control Room
120
150
 
121
- The main TUI is the Parallel hub. The default view stays intentionally quiet: a Codex-like framed header, cream-toned accents, a focused prompt block, and detailed status moved into explicit views.
151
+ The main TUI is the Parallel hub. The default view stays intentionally quiet: a Codex-like framed header, cream-toned accents, a focused prompt block, compact cropped agent rows, and detailed status moved into explicit views.
122
152
 
123
153
  It is designed to answer:
124
154
 
@@ -128,11 +158,16 @@ It is designed to answer:
128
158
  - what changed in the project
129
159
  - what model, provider, shell mode, and cost are active
130
160
 
161
+ Agent rows stay compact even when summaries are long. Use the row shortcuts to expand the right level of detail:
162
+
163
+ - `full /focus a1`: open the full in-Hub transcript and result for one agent.
164
+ - `term /attach a1`: reopen that agent's dedicated terminal.
165
+
131
166
  Input has three explicit contexts:
132
167
 
133
168
  - 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`.
134
169
  - Focus: after `/focus a1`, plain text talks to the focused agent instead of spawning a new one. `/raw` affects this view only.
135
- - Attach: in `parallel attach a1`, the same minimal prompt UI 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.
170
+ - Attach: in `parallel attach a1`, the same minimal prompt UI steers the attached agent. `/stop` stops the attached agent, `/task`, `/ask`, and `/plan` spawn new agents from that terminal, while `@all ...`, `@a2 ...`, and `/send ...` route instructions through the main session.
136
171
 
137
172
  Use `Name: task` when naming an agent:
138
173
 
@@ -146,6 +181,8 @@ Common hub commands:
146
181
  - `/agents`: agent overview.
147
182
  - `/focus a1`: inspect and steer one agent.
148
183
  - `/raw`: toggle raw detail in focus view.
184
+ - `/attach a1`: open or reopen an agent's dedicated terminal.
185
+ - `/review all`: ask-mode reviewer with verdict, risks, tests, and files to inspect.
149
186
  - `/board`: shared blackboard, claims, notes, and file activity.
150
187
  - `/diff`: live diff history.
151
188
  - `/cost`: token and cost breakdown.
@@ -180,8 +217,10 @@ parallel attach a1 --root .
180
217
 
181
218
  Attached terminals show:
182
219
 
220
+ - a launch header with agent, mode, model, and task
183
221
  - the selected agent's live timeline
184
222
  - native terminal scrollback
223
+ - append-only final results after `done`, `error`, or `stopped`
185
224
  - model, elapsed time, context, and cost
186
225
  - other agents' current state
187
226
  - approval and question prompts for that agent
@@ -197,6 +236,8 @@ plain text sends a message to this agent
197
236
  /task Tests: write parser regression tests
198
237
  /ask Reviewer: is this result safe to merge?
199
238
  /plan Migration: prepare a migration plan
239
+ /review all before commit
240
+ /stop
200
241
  /raw
201
242
  /quit
202
243
  ```
@@ -265,6 +306,7 @@ If the update succeeds, restart Parallel to run the new version. Use `parallel -
265
306
  - `/ask [Name:] <question> [--model=m]`: launch an ask-only agent.
266
307
  - `/task [Name:] <task> [--model=m] [#skill]`: launch a task agent. Plain text does the same.
267
308
  - `/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.
309
+ - `/review [agent|all] [prompt]`: launch a lightweight ask-mode reviewer for one agent or the whole shared tree.
268
310
  - `/issue <n>`: spawn a task from a GitHub issue. Requires the `gh` CLI, a GitHub repository, and `gh auth login`.
269
311
  - `/specialist <name> <task>`: spawn with a specialist persona.
270
312
  - `/specialist new <name> [global]`: create a specialist template.
@@ -293,7 +335,7 @@ If the update succeeds, restart Parallel to run the new version. Use `parallel -
293
335
  ### Views And Sessions
294
336
 
295
337
  - `/agents`: agent overview.
296
- - `/board`: shared blackboard, file activity, claims, and notes.
338
+ - `/board`: shared blackboard, work-map warnings, claims, file activity, and notes.
297
339
  - `/notes`: full notes history.
298
340
  - `/diff`: live diff history.
299
341
  - `/cost`: token and cost breakdown.
@@ -375,15 +417,21 @@ When an agent writes a file:
375
417
 
376
418
  1. Parallel checks whether the file changed since that agent last read it.
377
419
  2. If the file is unchanged, the write proceeds.
378
- 3. If another agent changed it, the write is rejected once.
379
- 4. The agent receives the other change as context, re-reads the file, merges both intentions, and retries.
420
+ 3. If another agent or shell command changed it, the write is rejected with an adaptation diff.
421
+ 4. The stale baseline is not silently synchronized. The agent must call `read_file`, merge both intentions on top of the current revision, and retry.
380
422
 
381
- This keeps agents moving without allowing silent overwrites.
423
+ The same stale-read guard applies to `edit_file`, even when `old_string` still exists. This keeps agents moving without allowing silent overwrites.
382
424
 
383
425
  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.
384
426
 
385
427
  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.
386
428
 
429
+ ## Shared-Tree Vs Isolated Agents
430
+
431
+ Use Parallel's shared-tree mode when agents need to negotiate live contracts: API and client, schema and tests, parser and docs, or any change where one agent's edit should affect another agent's next step.
432
+
433
+ Use isolated agents or separate worktrees for embarrassingly parallel work: unrelated files, independent chores, or experiments you only want to merge after review. Parallel's identity is the shared-tree team workflow; isolation is a future complement, not the default.
434
+
387
435
  ## Headless Mode
388
436
 
389
437
  For CI and scripts, run without the TUI:
@@ -19,12 +19,14 @@ AGENT MODE: ${mode}
19
19
  ${mode === 'ask'
20
20
  ? `ASK MODE:
21
21
  - You are advisory only. Do not modify files.
22
- - You may inspect with list_files/read_file/search and safe read-only commands when useful.
22
+ - Prefer search/read_file/read_many/inspect_project over shell. Use safe read-only commands only when internal tools are insufficient.
23
+ - Keep the investigation short and targeted. Do not run heavy validation loops unless the user explicitly asks.
23
24
  - Do not run mutating commands, write files, edit files, claim files, or commit.
24
25
  - Finish with task_complete using this user-facing structure in ${userLang}: "Réponse courte", "Recommandation", "Pourquoi", "Prochaines étapes".`
25
26
  : mode === 'plan'
26
27
  ? `PLAN MODE:
27
28
  - Explore first with read-only tools.
29
+ - Batch independent reads/searches with read_many or inspect_project. Keep exploration broad enough to be correct but bounded.
28
30
  - Before modifying any file or running mutating commands, call ask_user with a concrete implementation plan.
29
31
  - The plan must include steps, files you expect to touch, risks, and validation.
30
32
  - Use options ["Approve", "Revise"], recommended "Revise" so timeout never approves changes.
@@ -32,7 +34,8 @@ ${mode === 'ask'
32
34
  - Finish with task_complete using this user-facing structure in ${userLang}: "Plan appliqué", "Ce que j’ai modifié", "Validation", "Risques restants".`
33
35
  : `TASK MODE:
34
36
  - Execute the user's objective end-to-end.
35
- - Explore, edit, validate, and summarize.
37
+ - Use this loop: create visible steps, batch inspect, act, batch validate, summarize.
38
+ - If the task is a verification/audit and the correct outcome is no file changes, that is valid task work. Say explicitly in task_complete that no modification was necessary and why.
36
39
  - Ask the user only when blocked or when a risky product decision cannot be inferred.
37
40
  - Finish with task_complete using this user-facing structure in ${userLang}: "Ce que j’ai fait", "Ce que j’ai vérifié", "Résultat", "Détails techniques".`}
38
41
  ${skillsList
@@ -59,13 +62,16 @@ PARALLEL'S PHILOSOPHY — REAL-TIME CO-EDITING, NEVER ANY BLOCKING:
59
62
  8. MAKE THE SHARED AWARENESS VISIBLE: when another agent's work influenced a decision of yours (you reused their function, adapted to their diff, avoided their work area), SAY IT explicitly — in a post_note to them ("I saw you changed X, so I did Y") and in your task_complete summary (name the agent and what you built on). The user must be able to SEE that you worked as a team, not as isolated bots.
60
63
 
61
64
  WORK METHOD:
62
- - Explore first (list_files, read_file, search) before modifying.
65
+ - For non-trivial work, call update_steps early with 3-6 concrete steps. Keep exactly one step active and mark steps done as you complete them.
66
+ - Explore first before modifying. Decide all independent reads/searches you need, then batch them with read_many or inspect_project instead of calling tools one by one.
67
+ - Use run_command for builds/tests/validation and genuinely useful shell scripts. Do NOT spend many turns running grep/head/tail/wc/awk cascades; batch independent shell checks into one labelled command or use inspect_project.
63
68
  - Declare your work area with claim_files when you start (and when it changes): it prevents collisions without ever locking anything.
64
69
  - If you discover a durable, non-obvious fact about the project (convention, decision, pitfall), save it with remember(fact) for future agents.
65
70
  - wait_for_agent exists for hard dependencies only — prefer progressing on another part of your task over waiting.
66
71
  - Prefer edit_file (targeted changes) over write_file (full rewrite): targeted edits coexist better in parallel.
67
72
  - Make minimal changes; do not rewrite what already works.
68
73
  - Verify your work when relevant (run_command for tests/build), then finish with task_complete.
74
+ - Performance discipline: minimize model turns. If multiple tool calls are independent, make them in the same assistant turn. If several shell checks are independent, run one labelled command.
69
75
  - The task_complete summary is user-facing. Make it structured and specific in ${userLang}, not just "done":
70
76
  1. What I did — concrete outcome in 1-2 sentences.
71
77
  2. Files changed or inspected — mention paths when relevant.
@@ -76,6 +82,29 @@ WORK METHOD:
76
82
  LANGUAGE: write notes addressed to "user" and your task_complete summary in ${userLang}. Notes to other agents and code stay in English.`;
77
83
  /** Assumed context window (tokens) when the provider does not advertise one. */
78
84
  const CONTEXT_WINDOW = 128_000;
85
+ const EMPTY_PERF = {
86
+ modelTurns: 0,
87
+ toolCalls: 0,
88
+ shellCommands: 0,
89
+ shellMs: 0,
90
+ readOnlyShellCommands: 0,
91
+ };
92
+ function noChangeTaskLine() {
93
+ switch (getLang()) {
94
+ case 'fr':
95
+ return 'Mode task: vérification sans changement de fichier nécessaire.';
96
+ case 'es':
97
+ return 'Modo task: verificación sin cambios de archivos necesarios.';
98
+ case 'zh':
99
+ return 'Task 模式:已完成验证,无需修改文件。';
100
+ case 'en':
101
+ default:
102
+ return 'Task mode: verification completed with no file changes needed.';
103
+ }
104
+ }
105
+ function isReadOnlyShell(command) {
106
+ return /\b(grep|rg|head|tail|wc|awk|sed|cat|ls|find)\b/.test(command) && !/[>|;]/.test(command);
107
+ }
79
108
  export class Agent {
80
109
  opts;
81
110
  id;
@@ -90,6 +119,7 @@ export class Agent {
90
119
  stopped = false;
91
120
  lastNoteId = 0;
92
121
  lastChangeId = 0;
122
+ readOnlyShellStreak = 0;
93
123
  constructor(opts) {
94
124
  this.opts = opts;
95
125
  this.id = opts.id;
@@ -114,6 +144,8 @@ export class Agent {
114
144
  cost: opts.price ? 0 : null,
115
145
  startedAt: Date.now(),
116
146
  specialist: opts.specialist?.name,
147
+ progressSteps: [],
148
+ perf: { ...EMPTY_PERF },
117
149
  };
118
150
  this.board.registerAgent(info);
119
151
  // Skip notes/changes that existed before this agent was born
@@ -165,9 +197,13 @@ export class Agent {
165
197
  * A note addressed to this agent just arrived: interrupt the current model
166
198
  * call so the next turn (which injects unread notes) starts immediately.
167
199
  */
168
- nudge() {
200
+ nudge(reason = 'reading team update') {
201
+ if (this.finished || this.stopped || this.paused)
202
+ return false;
169
203
  this.steered = true;
204
+ this.board.setAgentState(this.id, 'listening', reason);
170
205
  this.llmAbort?.abort();
206
+ return true;
171
207
  }
172
208
  /**
173
209
  * Append a message to the in-memory history AND to the conversation file
@@ -189,6 +225,52 @@ export class Agent {
189
225
  await new Promise((r) => setTimeout(r, 300));
190
226
  }
191
227
  }
228
+ repairToolCallHistory() {
229
+ const repaired = [];
230
+ for (let i = 0; i < this.history.length; i++) {
231
+ const msg = this.history[i];
232
+ if (msg.role === 'tool') {
233
+ // Orphan tool messages make OpenAI-compatible APIs reject the whole
234
+ // request. Valid tool messages are consumed immediately after their
235
+ // assistant tool_calls block below.
236
+ continue;
237
+ }
238
+ repaired.push(this.history[i]);
239
+ const toolCalls = msg.role === 'assistant' && Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
240
+ if (toolCalls.length === 0)
241
+ continue;
242
+ const seen = new Set();
243
+ while (i + 1 < this.history.length && this.history[i + 1].role === 'tool') {
244
+ const toolMsg = this.history[++i];
245
+ if (toolMsg.tool_call_id)
246
+ seen.add(String(toolMsg.tool_call_id));
247
+ repaired.push(toolMsg);
248
+ }
249
+ for (const tc of toolCalls) {
250
+ const id = String(tc.id ?? '');
251
+ if (!id || seen.has(id))
252
+ continue;
253
+ repaired.push({
254
+ role: 'tool',
255
+ tool_call_id: id,
256
+ content: 'Skipped: missing tool result repaired before the next model call.',
257
+ });
258
+ }
259
+ }
260
+ this.history = repaired;
261
+ }
262
+ updatePerf(delta) {
263
+ const current = this.board.agents.get(this.id)?.perf ?? EMPTY_PERF;
264
+ this.board.updateAgent(this.id, {
265
+ perf: {
266
+ modelTurns: current.modelTurns + (delta.modelTurns ?? 0),
267
+ toolCalls: current.toolCalls + (delta.toolCalls ?? 0),
268
+ shellCommands: current.shellCommands + (delta.shellCommands ?? 0),
269
+ shellMs: current.shellMs + (delta.shellMs ?? 0),
270
+ readOnlyShellCommands: current.readOnlyShellCommands + (delta.readOnlyShellCommands ?? 0),
271
+ },
272
+ });
273
+ }
192
274
  /**
193
275
  * Build the live context injected before EVERY model call:
194
276
  * other agents' status + their fresh diffs + unread notes.
@@ -197,6 +279,15 @@ export class Agent {
197
279
  liveContext() {
198
280
  let hasNews = false;
199
281
  const parts = ['[REAL TIME]', this.board.snapshotFor(this.id)];
282
+ const notes = this.board.notesFor(this.name, this.lastNoteId);
283
+ if (notes.length > 0) {
284
+ this.lastNoteId = notes[notes.length - 1].id;
285
+ hasNews = true;
286
+ parts.push('\n[PRIORITY NOTES RECEIVED — take them into account now]');
287
+ for (const n of notes) {
288
+ parts.push(` • from ${n.from}: ${n.content}`);
289
+ }
290
+ }
200
291
  const changes = this.board.changesSince(this.id, this.lastChangeId);
201
292
  if (changes.length > 0) {
202
293
  this.lastChangeId = changes[changes.length - 1].id;
@@ -219,15 +310,6 @@ export class Agent {
219
310
  }
220
311
  parts.push('Analyze these diffs: if any of them touches your work area, re-read the affected file and adapt. NEVER undo these changes.');
221
312
  }
222
- const notes = this.board.notesFor(this.name, this.lastNoteId);
223
- if (notes.length > 0) {
224
- this.lastNoteId = notes[notes.length - 1].id;
225
- hasNews = true;
226
- parts.push('\n[NOTES RECEIVED — take them into account now]');
227
- for (const n of notes) {
228
- parts.push(` • from ${n.from}: ${n.content}`);
229
- }
230
- }
231
313
  parts.push('\nContinue your task taking the above into account. Use tools, or task_complete if finished.');
232
314
  return { text: parts.join('\n'), hasNews };
233
315
  }
@@ -292,6 +374,7 @@ export class Agent {
292
374
  if (this.stopped)
293
375
  break;
294
376
  }
377
+ this.repairToolCallHistory();
295
378
  const messages = [
296
379
  ...this.history,
297
380
  { role: 'user', content: live.text },
@@ -322,6 +405,7 @@ export class Agent {
322
405
  this.llmAbort = null;
323
406
  }
324
407
  this.steered = false;
408
+ this.updatePerf({ modelTurns: 1 });
325
409
  const a = this.board.agents.get(this.id);
326
410
  if (a) {
327
411
  // Real-time financial view: accrue the cost of this round immediately.
@@ -336,15 +420,15 @@ export class Agent {
336
420
  });
337
421
  }
338
422
  const msg = res.message;
339
- // Persist this round into history (live context is NOT kept — rebuilt fresh each turn).
340
- this.record({ role: 'user', content: '[real-time state consulted]' });
341
- this.record(msg);
342
423
  if (msg.content && msg.content.trim()) {
343
424
  // "✻" marks thinking/commentary steps — visually distinct from tool lines.
344
425
  this.board.log(this.id, 'llm', `✻ ${msg.content.trim().slice(0, 500)}`);
345
426
  }
346
427
  const toolCalls = msg.tool_calls ?? [];
347
428
  if (toolCalls.length === 0) {
429
+ // Persist this round into history (live context is NOT kept — rebuilt fresh each turn).
430
+ this.record({ role: 'user', content: '[real-time state consulted]' });
431
+ this.record(msg);
348
432
  this.record({
349
433
  role: 'user',
350
434
  content: 'No tool was called. If your task is finished and verified, call task_complete. Otherwise, continue with tool calls.',
@@ -353,21 +437,33 @@ export class Agent {
353
437
  }
354
438
  this.board.setAgentState(this.id, 'working');
355
439
  let completed = false;
356
- for (const tc of toolCalls) {
357
- if (this.stopped)
440
+ const toolResults = [];
441
+ const postToolMessages = [];
442
+ const addToolResult = (toolCallId, content) => {
443
+ toolResults.push({ role: 'tool', tool_call_id: toolCallId, content });
444
+ };
445
+ const addSkippedToolResults = (startIndex, content) => {
446
+ for (const remaining of toolCalls.slice(startIndex)) {
447
+ if (remaining.id)
448
+ addToolResult(remaining.id, content);
449
+ }
450
+ };
451
+ for (let i = 0; i < toolCalls.length; i++) {
452
+ const tc = toolCalls[i];
453
+ if (this.stopped) {
454
+ addSkippedToolResults(i, 'Skipped: the agent was stopped before this tool call executed.');
358
455
  break;
359
- if (tc.type !== 'function')
456
+ }
457
+ if (tc.type !== 'function') {
458
+ addToolResult(tc.id, 'ERROR: unsupported tool call type.');
360
459
  continue;
460
+ }
361
461
  let args = {};
362
462
  try {
363
463
  args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
364
464
  }
365
465
  catch {
366
- this.record({
367
- role: 'tool',
368
- tool_call_id: tc.id,
369
- content: 'ERROR: invalid JSON arguments.',
370
- });
466
+ addToolResult(tc.id, 'ERROR: invalid JSON arguments.');
371
467
  continue;
372
468
  }
373
469
  const label = this.describeCall(tc.function.name, args);
@@ -378,20 +474,65 @@ export class Agent {
378
474
  this.board.log(this.id, 'tool', label);
379
475
  }
380
476
  this.board.updateAgent(this.id, { currentAction: label.slice(0, 80) });
381
- const result = await this.executor.execute(tc.function.name, args);
477
+ const shellStartedAt = tc.function.name === 'run_command' ? Date.now() : 0;
478
+ let result;
479
+ try {
480
+ result = await this.executor.execute(tc.function.name, args);
481
+ }
482
+ catch (err) {
483
+ result = `ERROR: ${err?.message ?? String(err)}`;
484
+ }
485
+ const shellMs = shellStartedAt ? Date.now() - shellStartedAt : 0;
486
+ const readOnlyShell = tc.function.name === 'run_command' && isReadOnlyShell(String(args.command ?? ''));
487
+ this.updatePerf({
488
+ toolCalls: 1,
489
+ shellCommands: tc.function.name === 'run_command' ? 1 : 0,
490
+ shellMs,
491
+ readOnlyShellCommands: readOnlyShell ? 1 : 0,
492
+ });
493
+ if (readOnlyShell) {
494
+ this.readOnlyShellStreak++;
495
+ if (this.readOnlyShellStreak >= 3) {
496
+ postToolMessages.push({
497
+ role: 'user',
498
+ content: '[PERFORMANCE CORRECTION] You are using several read-only shell micro-commands. Batch the next inspection with read_many/inspect_project or a single labelled shell command, then continue.',
499
+ });
500
+ this.readOnlyShellStreak = 0;
501
+ }
502
+ }
503
+ else if (tc.function.name !== 'update_status' && tc.function.name !== 'update_steps') {
504
+ this.readOnlyShellStreak = 0;
505
+ }
382
506
  if (result === '__TASK_COMPLETE__') {
383
507
  completed = true;
384
508
  const summary = String(args.summary ?? 'Task complete.');
385
- this.board.updateAgent(this.id, { lastResult: summary });
509
+ const changedByThisAgent = this.board.changes.filter((c) => c.agentId === this.id).length;
510
+ const noChangePrefix = this.opts.mode === 'task' && changedByThisAgent === 0
511
+ ? `${noChangeTaskLine()}\n\n`
512
+ : '';
513
+ this.board.updateAgent(this.id, {
514
+ lastResult: `${noChangePrefix}${summary}`,
515
+ progressSteps: (this.board.agents.get(this.id)?.progressSteps ?? []).map((s) => ({ ...s, status: 'done' })),
516
+ });
386
517
  // ONE short headline note (the full summary lives in lastResult and
387
518
  // is rendered as the agent's recap) — no duplicated walls of text.
388
519
  const headline = summary.split('\n').find((l) => l.trim())?.trim() ?? 'Task complete.';
389
520
  this.board.addNote(this.name, 'all', `✅ ${headline.slice(0, 160)}`);
390
- this.record({ role: 'tool', tool_call_id: tc.id, content: 'OK, task closed.' });
521
+ addToolResult(tc.id, 'OK, task closed.');
522
+ addSkippedToolResults(i + 1, 'Skipped: task_complete closed the task before this tool call executed.');
391
523
  break;
392
524
  }
393
- this.record({ role: 'tool', tool_call_id: tc.id, content: result });
525
+ addToolResult(tc.id, result);
394
526
  }
527
+ // OpenAI-compatible APIs require assistant tool_calls to be followed
528
+ // immediately by one tool result per tool_call_id. Keep this block
529
+ // atomic so aborted/skipped tools never poison a later turn or restore.
530
+ this.record({ role: 'user', content: '[real-time state consulted]' });
531
+ this.record(msg);
532
+ for (const toolResult of toolResults)
533
+ this.record(toolResult);
534
+ for (const postToolMessage of postToolMessages)
535
+ this.record(postToolMessage);
395
536
  if (completed) {
396
537
  this.board.setAgentState(this.id, 'done', 'done ✅');
397
538
  return;
@@ -419,6 +560,8 @@ export class Agent {
419
560
  switch (name) {
420
561
  case 'read_file':
421
562
  return `📖 read ${args.path}`;
563
+ case 'read_many':
564
+ return `📚 read ${Array.isArray(args.paths) ? args.paths.slice(0, 3).join(', ') : 'files'}`;
422
565
  case 'write_file':
423
566
  return `✏ write ${args.path}`;
424
567
  case 'edit_file':
@@ -427,12 +570,16 @@ export class Agent {
427
570
  return `📁 ls ${args.path ?? '.'}`;
428
571
  case 'search':
429
572
  return `🔍 search /${args.pattern}/`;
573
+ case 'inspect_project':
574
+ return `🔎 inspect project`;
430
575
  case 'run_command':
431
576
  return `$ ${args.command}`;
432
577
  case 'post_note':
433
578
  return `✉ note → ${args.to}`;
434
579
  case 'update_status':
435
580
  return `📢 ${args.status}`;
581
+ case 'update_steps':
582
+ return `☑ update steps`;
436
583
  case 'ask_user':
437
584
  return `❓ ${String(args.question ?? '').slice(0, 60)}`;
438
585
  case 'load_skill':