@gjczone/pi-swarm 0.4.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -89
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -16
- package/dist/index.js.map +1 -1
- package/dist/shared/controller.d.ts +19 -5
- package/dist/shared/controller.d.ts.map +1 -1
- package/dist/shared/controller.js +139 -17
- package/dist/shared/controller.js.map +1 -1
- package/dist/shared/render.d.ts +1 -12
- package/dist/shared/render.d.ts.map +1 -1
- package/dist/shared/render.js +4 -37
- package/dist/shared/render.js.map +1 -1
- package/dist/shared/spawner.js +49 -5
- package/dist/shared/spawner.js.map +1 -1
- package/dist/shared/types.d.ts +38 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/worktree.d.ts +2 -0
- package/dist/shared/worktree.d.ts.map +1 -1
- package/dist/shared/worktree.js +10 -3
- package/dist/shared/worktree.js.map +1 -1
- package/dist/shared/xml.d.ts +18 -0
- package/dist/shared/xml.d.ts.map +1 -0
- package/dist/shared/xml.js +31 -0
- package/dist/shared/xml.js.map +1 -0
- package/dist/state/recovery.d.ts.map +1 -1
- package/dist/state/recovery.js +25 -4
- package/dist/state/recovery.js.map +1 -1
- package/dist/swarm/command.d.ts +4 -5
- package/dist/swarm/command.d.ts.map +1 -1
- package/dist/swarm/command.js +26 -74
- package/dist/swarm/command.js.map +1 -1
- package/dist/swarm/mode.d.ts +1 -1
- package/dist/swarm/mode.js +2 -2
- package/dist/swarm/mode.js.map +1 -1
- package/dist/swarm/tool.d.ts +4 -4
- package/dist/swarm/tool.d.ts.map +1 -1
- package/dist/swarm/tool.js +115 -160
- package/dist/swarm/tool.js.map +1 -1
- package/dist/team/command.d.ts +2 -4
- package/dist/team/command.d.ts.map +1 -1
- package/dist/team/command.js +5 -13
- package/dist/team/command.js.map +1 -1
- package/dist/team/mailbox.d.ts +7 -0
- package/dist/team/mailbox.d.ts.map +1 -1
- package/dist/team/mailbox.js +99 -13
- package/dist/team/mailbox.js.map +1 -1
- package/dist/tui/progress.d.ts +26 -9
- package/dist/tui/progress.d.ts.map +1 -1
- package/dist/tui/progress.js +262 -163
- package/dist/tui/progress.js.map +1 -1
- package/dist/tui/swarm-markers.d.ts +1 -1
- package/dist/tui/swarm-markers.js +1 -1
- package/package.json +13 -2
- package/dist/team/supervisor.d.ts +0 -171
- package/dist/team/supervisor.d.ts.map +0 -1
- package/dist/team/supervisor.js +0 -700
- package/dist/team/supervisor.js.map +0 -1
- package/dist/team/task-graph.d.ts +0 -64
- package/dist/team/task-graph.d.ts.map +0 -1
- package/dist/team/task-graph.js +0 -216
- package/dist/team/task-graph.js.map +0 -1
- package/dist/team/tool.d.ts +0 -11
- package/dist/team/tool.d.ts.map +0 -1
- package/dist/team/tool.js +0 -455
- package/dist/team/tool.js.map +0 -1
- package/dist/tui/permission-prompt.d.ts +0 -26
- package/dist/tui/permission-prompt.d.ts.map +0 -1
- package/dist/tui/permission-prompt.js +0 -98
- package/dist/tui/permission-prompt.js.map +0 -1
- package/dist/tui/team-dashboard.d.ts +0 -48
- package/dist/tui/team-dashboard.d.ts.map +0 -1
- package/dist/tui/team-dashboard.js +0 -249
- package/dist/tui/team-dashboard.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,36 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-swarm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent Swarm & Team extension for pi-coding-agent. Run 1 to 128 parallel subagents or collaborative role-based teams — no preset configuration needed.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## What It Does
|
|
8
|
-
|
|
9
|
-
**Swarm** — 1 to 128 parallel agents. Like kimi-code's AgentSwarm: one template, many items, running simultaneously. Also works for single subagent delegation. Each agent is an isolated `pi --print` child process with its own context window.
|
|
10
|
-
|
|
11
|
-
**Team** — collaborative agents. Like Claude Code's agent teams or pi-crew: role-based agents (explorer, planner, coder, reviewer, tester) working in sequence. Each phase agent receives context from previous phases via a shared mailbox. Every agent runs as an independent child process. Optional per-role model tier routing: use a cheaper/faster model for exploration while keeping reasoning-heavy roles on the default model.
|
|
12
|
-
|
|
13
|
-
**Worktree Isolation** — each subagent runs in a temporary git worktree by default, so parallel agents cannot interfere with each other's file changes. On completion, changes are committed to a named branch for safe merging. Non-git repos fall back to regular directory mode.
|
|
14
|
-
|
|
15
|
-
**Real-time Mailbox** — team agents can send and receive messages during execution, not just between phases. Messages are delivered in near-real-time via file polling.
|
|
16
|
-
|
|
17
|
-
All agents are created on-the-fly. No `agents/*.md` files. The main agent decides what to spawn based on the task.
|
|
18
|
-
|
|
19
|
-
## Install
|
|
5
|
+
## Installation
|
|
20
6
|
|
|
21
7
|
```bash
|
|
22
8
|
pi install npm:@gjczone/pi-swarm@latest
|
|
23
9
|
```
|
|
24
10
|
|
|
25
|
-
##
|
|
11
|
+
## Core Features
|
|
26
12
|
|
|
27
|
-
|
|
13
|
+
| Feature | When to Use | What It Does |
|
|
14
|
+
|---------|-------------|--------------|
|
|
15
|
+
| **Swarm** | Run the same task across many items in parallel | Spawns 1-20 subagents from an item template. Optional mailbox mode for inter-agent communication. |
|
|
16
|
+
| **/swarm** | Trigger a swarm from chat | Shortcut for the Swarm tool. Usage: `/swarm <task>` |
|
|
17
|
+
| **/swarm-team** | Trigger a collaborative swarm | Shortcut for Swarm with mailbox enabled. Usage: `/swarm-team <goal>` |
|
|
18
|
+
| **Mailbox Mode** | Need agents to share findings | When `mailbox: true`, agents get inbox/outbox and can exchange messages during execution. |
|
|
19
|
+
| **Worktree Isolation** | Parallel agents modifying the same repo | Each subagent runs in a temporary git worktree. Changes commit to named branches for safe merging. Non-git repos fall back to regular directories. |
|
|
20
|
+
| **Live TUI Progress** | Monitor running swarms | Braille progress bars, grid layout, scrolling model output. Single-agent compact mode. |
|
|
21
|
+
| **Rate-Limit Retry** | Avoid API quota exhaustion | Auto-suspends on rate-limit errors and retries with exponential backoff (3s, 6s, 12s...). |
|
|
22
|
+
| **Crash Recovery** | Survive unexpected termination | Durable file-based state. Resume incomplete runs automatically. Completed runs auto-clean after 7 days. |
|
|
28
23
|
|
|
29
|
-
|
|
24
|
+
## Usage Examples
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
Audit src/auth.ts for security issues — use a subagent
|
|
33
|
-
```
|
|
26
|
+
### Swarm — parallel or collaborative
|
|
34
27
|
|
|
35
28
|
```
|
|
36
29
|
Review every file in src/ for bugs — use a swarm
|
|
@@ -40,73 +33,21 @@ Review every file in src/ for bugs — use a swarm
|
|
|
40
33
|
Run a security audit on these five packages in parallel: auth, api, db, cache, middleware
|
|
41
34
|
```
|
|
42
35
|
|
|
43
|
-
Or the slash command:
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
/swarm Find deprecated API usage across the codebase
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Team — "Plan this, build it, review it"
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
Implement user login with JWT — use a team with planner, coder, and reviewer
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
Add Redis caching — explore the codebase first, then plan, implement, review, test
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Or:
|
|
60
|
-
|
|
61
36
|
```
|
|
62
|
-
|
|
37
|
+
Implement user login with JWT — use a swarm with mailbox so agents can collaborate
|
|
63
38
|
```
|
|
64
39
|
|
|
65
|
-
### Resume Failed Work
|
|
66
|
-
|
|
67
|
-
If agents fail, the LLM gets `resume_agent_ids` and can retry:
|
|
68
|
-
|
|
69
40
|
```
|
|
70
|
-
|
|
41
|
+
Add Redis caching — explore first, then implement based on findings
|
|
71
42
|
```
|
|
72
43
|
|
|
73
|
-
### Cancel
|
|
74
|
-
|
|
75
|
-
Press `Ctrl+C` during a swarm or team run. Completed agents are preserved and results are final (no post-cancellation mutation). In-progress agents are cancelled gracefully and their partial work discarded. For teams, completed phases are saved and returned as partial results. Timeout errors are correctly surfaced instead of being lost in abort/exit races.
|
|
76
|
-
|
|
77
|
-
## Runtime Files
|
|
78
|
-
|
|
79
|
-
State is stored under `.pi/swarm/state/`. The extension auto-creates `.pi/` if it doesn't exist, and auto-appends `.pi/swarm/state/` to the project's `.gitignore`.
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
.pi/swarm/state/runs/{runId}/
|
|
83
|
-
manifest.json # Run metadata, agent IDs, timestamps
|
|
84
|
-
tasks.json # Task graph, per-phase status
|
|
85
|
-
events.jsonl # Append-only event log
|
|
86
|
-
agents/{agentId}/
|
|
87
|
-
status.json # Per-agent status snapshot
|
|
88
|
-
output.log # Full agent session output (header, raw stdout, footer)
|
|
89
|
-
mailbox/ # Team inter-agent messages
|
|
90
|
-
inbox.jsonl
|
|
91
|
-
outbox.jsonl
|
|
92
|
-
delivery.json
|
|
93
|
-
tasks/{roleName}/
|
|
94
|
-
inbox.jsonl # Per-role real-time inbox
|
|
95
|
-
outbox.jsonl # Per-role real-time outbox
|
|
96
|
-
```
|
|
44
|
+
### Cancel mid-run
|
|
97
45
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Runs auto-clean: completed runs deleted after 7 days, stale runs (30min no heartbeat) marked abandoned.
|
|
46
|
+
Press `Ctrl+C` during a swarm run. Completed agents are preserved, in-progress agents are cancelled gracefully.
|
|
101
47
|
|
|
102
48
|
## Settings
|
|
103
49
|
|
|
104
|
-
Default max concurrency is **5**.
|
|
105
|
-
|
|
106
|
-
| Settings file | Scope |
|
|
107
|
-
| --------------------------- | --------------------------- |
|
|
108
|
-
| `.pi/settings.json` | Project (current directory) |
|
|
109
|
-
| `~/.pi/agent/settings.json` | Global (all projects) |
|
|
50
|
+
Default max concurrency is **5**. Adjust in `.pi/settings.json` (project) or `~/.pi/agent/settings.json` (global):
|
|
110
51
|
|
|
111
52
|
```json
|
|
112
53
|
{
|
|
@@ -116,13 +57,7 @@ Default max concurrency is **5**. Recommended: **3-10**. Can be set to any posit
|
|
|
116
57
|
}
|
|
117
58
|
```
|
|
118
59
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Lower values (3-5) are safer for API rate limits. Values above 10 work if your provider allows high concurrent requests. No hard upper limit.
|
|
122
|
-
|
|
123
|
-
## Team Model Tier
|
|
124
|
-
|
|
125
|
-
When using `SwarmTeam`, you can configure a lightweight model for exploration roles to reduce costs:
|
|
60
|
+
Configure a lightweight model for simple/exploratory subagent tasks. The LLM reads this setting and passes `model` explicitly when appropriate:
|
|
126
61
|
|
|
127
62
|
```json
|
|
128
63
|
{
|
|
@@ -132,12 +67,23 @@ When using `SwarmTeam`, you can configure a lightweight model for exploration ro
|
|
|
132
67
|
}
|
|
133
68
|
```
|
|
134
69
|
|
|
135
|
-
|
|
70
|
+
When to use small model: exploration, straightforward execution, tasks with clear instructions.
|
|
71
|
+
When NOT to use: review, planning, complex analysis, architecture decisions.
|
|
72
|
+
|
|
73
|
+
## Supported Platforms
|
|
74
|
+
|
|
75
|
+
| Platform | Status |
|
|
76
|
+
|----------|--------|
|
|
77
|
+
| pi-coding-agent | Required runtime |
|
|
78
|
+
| Node.js >= 18 | Required |
|
|
79
|
+
| Linux / macOS / Windows | Supported |
|
|
136
80
|
|
|
137
81
|
## Credits
|
|
138
82
|
|
|
139
|
-
|
|
83
|
+
Architecture references [MoonshotAI/kimi-code](https://github.com/MoonshotAI/kimi-code). Team communication patterns inspired by [pi-crew](https://github.com/baphuongna/pi-crew). Agent team workflow approach inspired by Claude Code.
|
|
140
84
|
|
|
141
85
|
## License
|
|
142
86
|
|
|
143
87
|
[MIT](LICENSE)
|
|
88
|
+
|
|
89
|
+
|
package/dist/index.d.ts
CHANGED
|
@@ -5,20 +5,13 @@
|
|
|
5
5
|
* the AgentSwarm tool, /swarm command, swarm-mode state machine,
|
|
6
6
|
* and TUI progress components.
|
|
7
7
|
*
|
|
8
|
-
* Credit: AgentSwarm architecture
|
|
8
|
+
* Credit: AgentSwarm architecture references MoonshotAI/kimi-code.
|
|
9
9
|
* Team/mailbox patterns inspired by pi-crew.
|
|
10
10
|
*/
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Priority: "swarm-team" / "swarm team" > "swarm" alone.
|
|
16
|
-
* Returns null when no keyword matches.
|
|
17
|
-
*
|
|
18
|
-
* 业务说明:根据用户输入中的关键词判断应该激活 AgentSwarm 还是 SwarmTeam。
|
|
19
|
-
* "swarm-team" 或 "swarm team" 激活 team 模式,单独的 "swarm" 激活 swarm 模式。
|
|
20
|
-
* "swarm-team" 中包含 "swarm" 子串,因此 team 检查必须在 swarm 之前。
|
|
13
|
+
* Return "swarm" when the input contains the word "swarm", otherwise null.
|
|
21
14
|
*/
|
|
22
|
-
export declare function resolveKeywordMode(text: string): "swarm" |
|
|
15
|
+
export declare function resolveKeywordMode(text: string): "swarm" | null;
|
|
23
16
|
export default function (pi: ExtensionAPI): void;
|
|
24
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AA2FpE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAI/D;AAED,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CAwH/C"}
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
* the AgentSwarm tool, /swarm command, swarm-mode state machine,
|
|
6
6
|
* and TUI progress components.
|
|
7
7
|
*
|
|
8
|
-
* Credit: AgentSwarm architecture
|
|
8
|
+
* Credit: AgentSwarm architecture references MoonshotAI/kimi-code.
|
|
9
9
|
* Team/mailbox patterns inspired by pi-crew.
|
|
10
10
|
*/
|
|
11
11
|
import { registerAgentSwarmTool } from "./swarm/tool.js";
|
|
12
12
|
import { registerSwarmCommand, } from "./swarm/command.js";
|
|
13
|
-
import { registerSwarmTeamTool } from "./team/tool.js";
|
|
14
|
-
import { registerTeamCommand } from "./team/command.js";
|
|
15
13
|
import { recoverRuns } from "./state/recovery.js";
|
|
16
14
|
import { SwarmModeMarkerComponent, } from "./tui/swarm-markers.js";
|
|
17
15
|
import { pruneWorktrees } from "./shared/worktree.js";
|
|
@@ -81,19 +79,10 @@ function findGitignore(cwd) {
|
|
|
81
79
|
// Keyword mode resolver (exported for testing)
|
|
82
80
|
// ---------------------------------------------------------------------------
|
|
83
81
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* Priority: "swarm-team" / "swarm team" > "swarm" alone.
|
|
87
|
-
* Returns null when no keyword matches.
|
|
88
|
-
*
|
|
89
|
-
* 业务说明:根据用户输入中的关键词判断应该激活 AgentSwarm 还是 SwarmTeam。
|
|
90
|
-
* "swarm-team" 或 "swarm team" 激活 team 模式,单独的 "swarm" 激活 swarm 模式。
|
|
91
|
-
* "swarm-team" 中包含 "swarm" 子串,因此 team 检查必须在 swarm 之前。
|
|
82
|
+
* Return "swarm" when the input contains the word "swarm", otherwise null.
|
|
92
83
|
*/
|
|
93
84
|
export function resolveKeywordMode(text) {
|
|
94
85
|
const t = text.toLowerCase();
|
|
95
|
-
if (t.includes("swarm-team") || t.includes("swarm team"))
|
|
96
|
-
return "team";
|
|
97
86
|
if (t.includes("swarm"))
|
|
98
87
|
return "swarm";
|
|
99
88
|
return null;
|
|
@@ -133,7 +122,8 @@ export default function (pi) {
|
|
|
133
122
|
return tools !== undefined && tools.length > 0;
|
|
134
123
|
}
|
|
135
124
|
catch {
|
|
136
|
-
|
|
125
|
+
console.error("[pi-swarm] Failed to check active model availability, assuming no model.");
|
|
126
|
+
return false;
|
|
137
127
|
}
|
|
138
128
|
},
|
|
139
129
|
};
|
|
@@ -202,8 +192,6 @@ export default function (pi) {
|
|
|
202
192
|
});
|
|
203
193
|
registerAgentSwarmTool(pi);
|
|
204
194
|
registerSwarmCommand(pi, commandHost);
|
|
205
|
-
registerSwarmTeamTool(pi);
|
|
206
|
-
registerTeamCommand(pi, commandHost);
|
|
207
195
|
}
|
|
208
196
|
// ---------------------------------------------------------------------------
|
|
209
197
|
// Marker helpers
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EACL,oBAAoB,GAErB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* controller — concurrency controller for subagent batches.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Architecture reference: AgentSwarm pattern.
|
|
5
5
|
*
|
|
6
6
|
* Two-phase scheduling:
|
|
7
7
|
* Normal phase: ramp-up (5 initial, +1 every 700ms).
|
|
@@ -38,6 +38,9 @@ export declare class SubagentBatchController<T> {
|
|
|
38
38
|
private startedAt;
|
|
39
39
|
private startedSuccessCount;
|
|
40
40
|
private readonly onProgress?;
|
|
41
|
+
private eventLog;
|
|
42
|
+
private nextEventId;
|
|
43
|
+
private completionTimesMs;
|
|
41
44
|
constructor(launcher: SubagentBatchLauncher, tasks: readonly QueuedSubagentTask<T>[], options?: SubagentBatchOptions);
|
|
42
45
|
/**
|
|
43
46
|
* Run the batch. Returns a promise that resolves when all tasks
|
|
@@ -60,6 +63,8 @@ export declare class SubagentBatchController<T> {
|
|
|
60
63
|
private recoverRateLimitCapacity;
|
|
61
64
|
private finishIfComplete;
|
|
62
65
|
private emitProgress;
|
|
66
|
+
/** Add an event to the event log, keeping it bounded. */
|
|
67
|
+
private addEvent;
|
|
63
68
|
private finish;
|
|
64
69
|
private finishWithUserCancellation;
|
|
65
70
|
private fail;
|
|
@@ -82,9 +87,18 @@ export declare class SubagentBatchController<T> {
|
|
|
82
87
|
* 2. `~/.pi/agent/settings.json` → `pi-swarm.maxConcurrency` (global)
|
|
83
88
|
* 3. `PI_SWARM_MAX_CONCURRENCY` env var
|
|
84
89
|
*
|
|
85
|
-
*
|
|
86
|
-
* integer; invalid input throws so a
|
|
87
|
-
* reverts to uncapped.
|
|
90
|
+
* Falls back to DEFAULT_MAX_CONCURRENCY (5) when unset. A present
|
|
91
|
+
* value must be a positive integer; invalid input throws so a
|
|
92
|
+
* misconfigured cap never silently reverts to uncapped.
|
|
88
93
|
*/
|
|
89
|
-
export declare function resolveSwarmMaxConcurrency(cwd?: string): number
|
|
94
|
+
export declare function resolveSwarmMaxConcurrency(cwd?: string): number;
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the optional small model from pi settings.
|
|
97
|
+
* Used as the default model for simple subagent tasks.
|
|
98
|
+
*
|
|
99
|
+
* Priority:
|
|
100
|
+
* 1. `.pi/settings.json` → `pi-swarm.smallModel`
|
|
101
|
+
* 2. `~/.pi/agent/settings.json` → `pi-swarm.smallModel`
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveSwarmSmallModel(cwd?: string): string | undefined;
|
|
90
104
|
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/shared/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/shared/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* controller — concurrency controller for subagent batches.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Architecture reference: AgentSwarm pattern.
|
|
5
5
|
*
|
|
6
6
|
* Two-phase scheduling:
|
|
7
7
|
* Normal phase: ramp-up (5 initial, +1 every 700ms).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
|
-
// Constants
|
|
16
|
+
// Constants
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
const INITIAL_LAUNCH_LIMIT = 5;
|
|
19
19
|
const INITIAL_LAUNCH_INTERVAL_MS = 700;
|
|
@@ -23,6 +23,7 @@ const RATE_LIMIT_CAPACITY_SHRINK_INTERVAL_MS = 2000;
|
|
|
23
23
|
const RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS = 3 * 60 * 1000; // 3 minutes
|
|
24
24
|
const AGENT_SWARM_MAX_CONCURRENCY_ENV = "PI_SWARM_MAX_CONCURRENCY";
|
|
25
25
|
const DEFAULT_MAX_CONCURRENCY = 5;
|
|
26
|
+
const RATE_LIMIT_SUSPENDED_REASON = "Rate limit reached — agent suspended";
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Abort helpers
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
@@ -36,10 +37,7 @@ function isUserCancellation(reason) {
|
|
|
36
37
|
const msg = reason instanceof Error
|
|
37
38
|
? reason.message.toLowerCase()
|
|
38
39
|
: String(reason).toLowerCase();
|
|
39
|
-
return (msg.includes("user") ||
|
|
40
|
-
msg.includes("cancel") ||
|
|
41
|
-
msg.includes("interrupt") ||
|
|
42
|
-
msg.includes("abort"));
|
|
40
|
+
return (msg.includes("user") || msg.includes("cancel") || msg.includes("interrupt"));
|
|
43
41
|
}
|
|
44
42
|
function userCancellationReason() {
|
|
45
43
|
return new Error("User cancelled");
|
|
@@ -91,6 +89,11 @@ export class SubagentBatchController {
|
|
|
91
89
|
startedAt = 0;
|
|
92
90
|
startedSuccessCount = 0;
|
|
93
91
|
onProgress;
|
|
92
|
+
// Event log
|
|
93
|
+
eventLog = [];
|
|
94
|
+
nextEventId = 1;
|
|
95
|
+
// ETA tracking: track completion timestamps for average calculation
|
|
96
|
+
completionTimesMs = [];
|
|
94
97
|
constructor(launcher, tasks, options = {}) {
|
|
95
98
|
this.launcher = launcher;
|
|
96
99
|
this.maxConcurrency = options.maxConcurrency;
|
|
@@ -108,6 +111,7 @@ export class SubagentBatchController {
|
|
|
108
111
|
cacheWrite: 0,
|
|
109
112
|
totalTokens: 0,
|
|
110
113
|
},
|
|
114
|
+
progressTick: 0,
|
|
111
115
|
}));
|
|
112
116
|
this.pending = [...this.states];
|
|
113
117
|
this.results = Array.from({
|
|
@@ -235,7 +239,9 @@ export class SubagentBatchController {
|
|
|
235
239
|
return;
|
|
236
240
|
const now = Date.now();
|
|
237
241
|
this.recoverRateLimitCapacity(now);
|
|
238
|
-
if (this.active.size >= this.rateLimitCapacity
|
|
242
|
+
if (this.active.size >= this.rateLimitCapacity ||
|
|
243
|
+
(this.maxConcurrency !== undefined &&
|
|
244
|
+
this.active.size >= this.maxConcurrency)) {
|
|
239
245
|
this.scheduleRateLimitWakeup(this.nextRateLimitCapacityRecoveryAt(), now);
|
|
240
246
|
return;
|
|
241
247
|
}
|
|
@@ -271,6 +277,21 @@ export class SubagentBatchController {
|
|
|
271
277
|
attempt.cleanup = this.linkAttemptSignals(attempt, state.task);
|
|
272
278
|
this.active.add(attempt);
|
|
273
279
|
attempt.state.started = true;
|
|
280
|
+
attempt.state.startedAt = Date.now();
|
|
281
|
+
// Add event to log
|
|
282
|
+
this.addEvent({
|
|
283
|
+
id: this.nextEventId++,
|
|
284
|
+
agentId: undefined,
|
|
285
|
+
timestamp: Date.now(),
|
|
286
|
+
type: "started",
|
|
287
|
+
detail: state.task.swarmItem
|
|
288
|
+
? `Started: ${state.task.swarmItem.slice(0, 60)}`
|
|
289
|
+
: `Started task ${state.index + 1}`,
|
|
290
|
+
});
|
|
291
|
+
// Trim event log to last 50 entries
|
|
292
|
+
if (this.eventLog.length > 50) {
|
|
293
|
+
this.eventLog = this.eventLog.slice(-50);
|
|
294
|
+
}
|
|
274
295
|
// A task transitioned from queued to working
|
|
275
296
|
this.emitProgress();
|
|
276
297
|
this.runAttempt(attempt).then((outcome) => {
|
|
@@ -295,6 +316,16 @@ export class SubagentBatchController {
|
|
|
295
316
|
onUsage: (usage) => {
|
|
296
317
|
attempt.state.usage = { ...usage };
|
|
297
318
|
this.emitProgress();
|
|
319
|
+
// Forward to task-level callback (used by team mode to update supervisor phase usage)
|
|
320
|
+
task.onUsage?.(usage);
|
|
321
|
+
},
|
|
322
|
+
onActivity: (tool, activity) => {
|
|
323
|
+
attempt.state.currentTool = tool;
|
|
324
|
+
attempt.state.activity = activity;
|
|
325
|
+
attempt.state.progressTick += 1;
|
|
326
|
+
this.emitProgress();
|
|
327
|
+
// Forward to task-level callback (used by team mode to update phase activity)
|
|
328
|
+
task.onActivity?.(tool, activity);
|
|
298
329
|
},
|
|
299
330
|
onMessage: task.onMessage,
|
|
300
331
|
suppressRateLimitFailureEvent: true,
|
|
@@ -343,6 +374,21 @@ export class SubagentBatchController {
|
|
|
343
374
|
if (completion.usage) {
|
|
344
375
|
attempt.state.usage = { ...completion.usage };
|
|
345
376
|
}
|
|
377
|
+
const completedAt = Date.now();
|
|
378
|
+
attempt.state.completedAt = completedAt;
|
|
379
|
+
if (attempt.state.startedAt) {
|
|
380
|
+
this.completionTimesMs.push(completedAt - attempt.state.startedAt);
|
|
381
|
+
}
|
|
382
|
+
// Add event to log
|
|
383
|
+
this.addEvent({
|
|
384
|
+
id: this.nextEventId++,
|
|
385
|
+
agentId: handle.agentId,
|
|
386
|
+
timestamp: completedAt,
|
|
387
|
+
type: "completed",
|
|
388
|
+
detail: attempt.state.task.swarmItem
|
|
389
|
+
? `Agent completed: ${attempt.state.task.swarmItem.slice(0, 60)}`
|
|
390
|
+
: `Agent completed`,
|
|
391
|
+
});
|
|
346
392
|
return {
|
|
347
393
|
task,
|
|
348
394
|
agentId: handle.agentId,
|
|
@@ -408,6 +454,13 @@ export class SubagentBatchController {
|
|
|
408
454
|
// Save agent id for retry and requeue at front
|
|
409
455
|
state.retryAgentId = state.agentId ?? state.retryAgentId;
|
|
410
456
|
state.retryCount += 1;
|
|
457
|
+
// Notify external listeners that this agent was suspended
|
|
458
|
+
if (state.agentId) {
|
|
459
|
+
this.launcher.suspended?.({
|
|
460
|
+
agentId: state.agentId,
|
|
461
|
+
reason: RATE_LIMIT_SUSPENDED_REASON,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
411
464
|
// Exponential backoff
|
|
412
465
|
const delay = RATE_LIMIT_RETRY_BASE_MS *
|
|
413
466
|
Math.pow(RATE_LIMIT_RETRY_FACTOR, state.retryCount - 1);
|
|
@@ -424,10 +477,7 @@ export class SubagentBatchController {
|
|
|
424
477
|
}
|
|
425
478
|
failedAttemptOutcome(attempt, error) {
|
|
426
479
|
const task = attempt.state.task;
|
|
427
|
-
const isAbort = error
|
|
428
|
-
(error.message.includes("abort") ||
|
|
429
|
-
error.message.includes("cancel") ||
|
|
430
|
-
error.name === "AbortError");
|
|
480
|
+
const isAbort = isUserCancellation(error);
|
|
431
481
|
const status = isAbort ? "aborted" : "failed";
|
|
432
482
|
let errorMessage;
|
|
433
483
|
if (attempt.timedOut && task.timeout !== undefined) {
|
|
@@ -454,7 +504,12 @@ export class SubagentBatchController {
|
|
|
454
504
|
enterRateLimitPhase() {
|
|
455
505
|
this.rateLimitMode = true;
|
|
456
506
|
this.clearNormalTimer();
|
|
457
|
-
|
|
507
|
+
// Use startedSuccessCount (count of agents that fully booted during
|
|
508
|
+
// the normal phase) so capacity reflects true past throughput rather
|
|
509
|
+
// than only currently-active attempts (which may already be finishing).
|
|
510
|
+
this.rateLimitCapacity = Math.max(1, this.maxConcurrency !== undefined
|
|
511
|
+
? Math.min(this.maxConcurrency, this.startedSuccessCount)
|
|
512
|
+
: this.startedSuccessCount);
|
|
458
513
|
this.lastRateLimitAt = Date.now();
|
|
459
514
|
this.globalRetryIntervalMs = RATE_LIMIT_RETRY_BASE_MS;
|
|
460
515
|
this.nextRateLimitLaunchAt = Date.now() + RATE_LIMIT_RETRY_BASE_MS;
|
|
@@ -479,6 +534,10 @@ export class SubagentBatchController {
|
|
|
479
534
|
return;
|
|
480
535
|
this.lastCapacityRecoveryAt = now;
|
|
481
536
|
this.rateLimitCapacity += 1;
|
|
537
|
+
if (this.maxConcurrency !== undefined &&
|
|
538
|
+
this.rateLimitCapacity > this.maxConcurrency) {
|
|
539
|
+
this.rateLimitCapacity = this.maxConcurrency;
|
|
540
|
+
}
|
|
482
541
|
}
|
|
483
542
|
// -----------------------------------------------------------------------
|
|
484
543
|
// Completion
|
|
@@ -544,9 +603,25 @@ export class SubagentBatchController {
|
|
|
544
603
|
item: state.task.swarmItem,
|
|
545
604
|
error,
|
|
546
605
|
usage: memberUsage,
|
|
606
|
+
currentTool: state.currentTool,
|
|
607
|
+
activity: state.activity,
|
|
608
|
+
progressTick: state.progressTick,
|
|
547
609
|
});
|
|
548
610
|
}
|
|
549
611
|
const queued = this.states.length - completed - failed - active;
|
|
612
|
+
// Calculate ETA based on average completion time
|
|
613
|
+
let estimatedRemainingMs;
|
|
614
|
+
const totalFinished = completed + failed;
|
|
615
|
+
const totalRemaining = queued + active;
|
|
616
|
+
if (totalFinished > 0 && totalRemaining > 0) {
|
|
617
|
+
const avgTimeMs = this.completionTimesMs.length > 0
|
|
618
|
+
? this.completionTimesMs.reduce((a, b) => a + b, 0) /
|
|
619
|
+
this.completionTimesMs.length
|
|
620
|
+
: 0;
|
|
621
|
+
if (avgTimeMs > 0) {
|
|
622
|
+
estimatedRemainingMs = avgTimeMs * totalRemaining;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
550
625
|
this.onProgress({
|
|
551
626
|
total: this.states.length,
|
|
552
627
|
completed,
|
|
@@ -556,8 +631,18 @@ export class SubagentBatchController {
|
|
|
556
631
|
members,
|
|
557
632
|
totalUsage,
|
|
558
633
|
startedAt: this.startedAt,
|
|
634
|
+
estimatedRemainingMs,
|
|
635
|
+
eventLog: [...this.eventLog],
|
|
559
636
|
});
|
|
560
637
|
}
|
|
638
|
+
/** Add an event to the event log, keeping it bounded. */
|
|
639
|
+
addEvent(event) {
|
|
640
|
+
this.eventLog.push(event);
|
|
641
|
+
// Keep max 100 events
|
|
642
|
+
if (this.eventLog.length > 100) {
|
|
643
|
+
this.eventLog = this.eventLog.slice(-100);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
561
646
|
finish(results) {
|
|
562
647
|
if (this.finished)
|
|
563
648
|
return;
|
|
@@ -655,6 +740,17 @@ export class SubagentBatchController {
|
|
|
655
740
|
return;
|
|
656
741
|
attempt.ready = true;
|
|
657
742
|
this.startedSuccessCount += 1;
|
|
743
|
+
// If we are in rate-limit mode, reset the global retry interval
|
|
744
|
+
// so the next launch uses the base delay rather than an accumulated
|
|
745
|
+
// exponential backoff, and re-arm the scheduler immediately.
|
|
746
|
+
if (this.rateLimitMode) {
|
|
747
|
+
this.globalRetryIntervalMs = RATE_LIMIT_RETRY_BASE_MS;
|
|
748
|
+
this.nextRateLimitLaunchAt = Date.now() + this.globalRetryIntervalMs;
|
|
749
|
+
// Clear any pending rate-limit timer so schedule() re-computes
|
|
750
|
+
// the next wakeup from the new (shorter) interval.
|
|
751
|
+
this.clearRateLimitTimer();
|
|
752
|
+
this.schedule();
|
|
753
|
+
}
|
|
658
754
|
}
|
|
659
755
|
countReadyActive() {
|
|
660
756
|
let count = 0;
|
|
@@ -728,9 +824,9 @@ export class SubagentBatchController {
|
|
|
728
824
|
* 2. `~/.pi/agent/settings.json` → `pi-swarm.maxConcurrency` (global)
|
|
729
825
|
* 3. `PI_SWARM_MAX_CONCURRENCY` env var
|
|
730
826
|
*
|
|
731
|
-
*
|
|
732
|
-
* integer; invalid input throws so a
|
|
733
|
-
* reverts to uncapped.
|
|
827
|
+
* Falls back to DEFAULT_MAX_CONCURRENCY (5) when unset. A present
|
|
828
|
+
* value must be a positive integer; invalid input throws so a
|
|
829
|
+
* misconfigured cap never silently reverts to uncapped.
|
|
734
830
|
*/
|
|
735
831
|
export function resolveSwarmMaxConcurrency(cwd) {
|
|
736
832
|
// 1. Project-local settings
|
|
@@ -751,12 +847,12 @@ export function resolveSwarmMaxConcurrency(cwd) {
|
|
|
751
847
|
if (raw !== undefined && raw.trim() !== "") {
|
|
752
848
|
return validateConcurrency(Number(raw), AGENT_SWARM_MAX_CONCURRENCY_ENV);
|
|
753
849
|
}
|
|
754
|
-
// 4. Default
|
|
850
|
+
// 4. Default (always reached — value guaranteed)
|
|
755
851
|
return DEFAULT_MAX_CONCURRENCY;
|
|
756
852
|
}
|
|
757
853
|
function validateConcurrency(value, source) {
|
|
758
854
|
if (value === undefined || value === null)
|
|
759
|
-
return
|
|
855
|
+
return DEFAULT_MAX_CONCURRENCY;
|
|
760
856
|
const num = Number(value);
|
|
761
857
|
if (!Number.isInteger(num) || num <= 0) {
|
|
762
858
|
throw new Error(`pi-swarm.maxConcurrency in ${source} must be a positive integer, got ${JSON.stringify(value)}.`);
|
|
@@ -780,4 +876,30 @@ function getSettingsMaxConcurrency(settings) {
|
|
|
780
876
|
const swarm = settings["pi-swarm"];
|
|
781
877
|
return swarm?.maxConcurrency;
|
|
782
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Resolve the optional small model from pi settings.
|
|
881
|
+
* Used as the default model for simple subagent tasks.
|
|
882
|
+
*
|
|
883
|
+
* Priority:
|
|
884
|
+
* 1. `.pi/settings.json` → `pi-swarm.smallModel`
|
|
885
|
+
* 2. `~/.pi/agent/settings.json` → `pi-swarm.smallModel`
|
|
886
|
+
*/
|
|
887
|
+
export function resolveSwarmSmallModel(cwd) {
|
|
888
|
+
const projectSettings = readPiSettings(path.join(cwd ?? process.cwd(), ".pi", "settings.json"));
|
|
889
|
+
const projectValue = getSettingsSmallModel(projectSettings);
|
|
890
|
+
if (projectValue !== undefined)
|
|
891
|
+
return projectValue;
|
|
892
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
893
|
+
const globalSettings = readPiSettings(path.join(home, ".pi", "agent", "settings.json"));
|
|
894
|
+
return getSettingsSmallModel(globalSettings);
|
|
895
|
+
}
|
|
896
|
+
function getSettingsSmallModel(settings) {
|
|
897
|
+
if (!settings)
|
|
898
|
+
return undefined;
|
|
899
|
+
const swarm = settings["pi-swarm"];
|
|
900
|
+
const val = swarm?.smallModel;
|
|
901
|
+
return typeof val === "string" && val.trim().length > 0
|
|
902
|
+
? val.trim()
|
|
903
|
+
: undefined;
|
|
904
|
+
}
|
|
783
905
|
//# sourceMappingURL=controller.js.map
|