@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 +42 -0
- package/README.md +60 -12
- package/dist/agents/agent.js +175 -28
- package/dist/agents/tools.js +218 -20
- package/dist/commands.js +53 -0
- package/dist/controller.js +26 -13
- package/dist/coordination/blackboard.js +44 -2
- package/dist/i18n.js +69 -13
- package/dist/index.js +1 -0
- package/dist/server.js +9 -0
- package/dist/ui/AgentPanel.js +99 -25
- package/dist/ui/App.js +13 -6
- package/dist/ui/AttachApp.js +109 -15
- package/dist/ui/CommandInput.js +12 -2
- package/dist/ui/Timeline.js +5 -0
- package/dist/ui/events.js +20 -17
- package/dist/ui/theme.js +2 -2
- package/dist/ui/tokens.js +2 -2
- package/dist/ui/views.js +17 -5
- package/dist/version.js +1 -1
- package/package.json +2 -2
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
|
|
3
|
+
Real-time coding agents that work like a live team, not isolated background jobs.
|
|
4
4
|
|
|
5
|
-
Parallel lets
|
|
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
|
|
7
|
+
> One working tree. Many agents. Shared awareness. Human in control.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@parallel-cli/parallel)
|
|
10
10
|

|
|
@@ -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
|
|
17
|
-
- Choose explicit modes: `/ask`, `/task`, and `/
|
|
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,
|
|
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
|
|
379
|
-
4. The
|
|
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:
|
package/dist/agents/agent.js
CHANGED
|
@@ -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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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':
|