@gjczone/pi-swarm 0.5.0 → 0.7.1

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.
Files changed (68) hide show
  1. package/README.md +14 -21
  2. package/dist/index.d.ts +2 -12
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -18
  5. package/dist/index.js.map +1 -1
  6. package/dist/shared/controller.d.ts +10 -1
  7. package/dist/shared/controller.d.ts.map +1 -1
  8. package/dist/shared/controller.js +48 -12
  9. package/dist/shared/controller.js.map +1 -1
  10. package/dist/shared/render.d.ts +1 -1
  11. package/dist/shared/render.js +1 -1
  12. package/dist/shared/spawner.js +54 -8
  13. package/dist/shared/spawner.js.map +1 -1
  14. package/dist/shared/types.d.ts +10 -0
  15. package/dist/shared/types.d.ts.map +1 -1
  16. package/dist/shared/worktree.d.ts +2 -1
  17. package/dist/shared/worktree.d.ts.map +1 -1
  18. package/dist/shared/worktree.js +10 -4
  19. package/dist/shared/worktree.js.map +1 -1
  20. package/dist/state/recovery.d.ts.map +1 -1
  21. package/dist/state/recovery.js +25 -4
  22. package/dist/state/recovery.js.map +1 -1
  23. package/dist/swarm/command.d.ts +4 -5
  24. package/dist/swarm/command.d.ts.map +1 -1
  25. package/dist/swarm/command.js +26 -74
  26. package/dist/swarm/command.js.map +1 -1
  27. package/dist/swarm/mode.d.ts +1 -1
  28. package/dist/swarm/mode.js +2 -2
  29. package/dist/swarm/mode.js.map +1 -1
  30. package/dist/swarm/tool.d.ts +4 -4
  31. package/dist/swarm/tool.d.ts.map +1 -1
  32. package/dist/swarm/tool.js +112 -167
  33. package/dist/swarm/tool.js.map +1 -1
  34. package/dist/team/command.d.ts +2 -4
  35. package/dist/team/command.d.ts.map +1 -1
  36. package/dist/team/command.js +5 -13
  37. package/dist/team/command.js.map +1 -1
  38. package/dist/team/mailbox.d.ts +7 -2
  39. package/dist/team/mailbox.d.ts.map +1 -1
  40. package/dist/team/mailbox.js +121 -32
  41. package/dist/team/mailbox.js.map +1 -1
  42. package/dist/tui/progress.d.ts +35 -47
  43. package/dist/tui/progress.d.ts.map +1 -1
  44. package/dist/tui/progress.js +245 -489
  45. package/dist/tui/progress.js.map +1 -1
  46. package/dist/tui/swarm-markers.d.ts +1 -1
  47. package/dist/tui/swarm-markers.js +1 -1
  48. package/package.json +13 -2
  49. package/dist/team/supervisor.d.ts +0 -171
  50. package/dist/team/supervisor.d.ts.map +0 -1
  51. package/dist/team/supervisor.js +0 -685
  52. package/dist/team/supervisor.js.map +0 -1
  53. package/dist/team/task-graph.d.ts +0 -64
  54. package/dist/team/task-graph.d.ts.map +0 -1
  55. package/dist/team/task-graph.js +0 -216
  56. package/dist/team/task-graph.js.map +0 -1
  57. package/dist/team/tool.d.ts +0 -11
  58. package/dist/team/tool.d.ts.map +0 -1
  59. package/dist/team/tool.js +0 -491
  60. package/dist/team/tool.js.map +0 -1
  61. package/dist/tui/permission-prompt.d.ts +0 -26
  62. package/dist/tui/permission-prompt.d.ts.map +0 -1
  63. package/dist/tui/permission-prompt.js +0 -98
  64. package/dist/tui/permission-prompt.js.map +0 -1
  65. package/dist/tui/team-dashboard.d.ts +0 -81
  66. package/dist/tui/team-dashboard.d.ts.map +0 -1
  67. package/dist/tui/team-dashboard.js +0 -657
  68. package/dist/tui/team-dashboard.js.map +0 -1
@@ -1,685 +0,0 @@
1
- /**
2
- * team/supervisor — team supervisor agent.
3
- *
4
- * The supervisor decomposes a high-level goal into phases, assigns
5
- * each phase to a role agent, monitors progress via the mailbox,
6
- * and synthesizes the final result.
7
- */
8
- import { SMALL_MODEL_ROLES } from "../shared/types.js";
9
- import { escapeXmlAttr, escapeXmlBody } from "../shared/xml.js";
10
- import { TaskGraph, DEFAULT_TEAM_PHASES, } from "./task-graph.js";
11
- import { resolveMailboxPaths, ensureMailbox, sendMessage, readTaskInbox, ackTaskMessages, updateDeliveryState, countOutboxMessages, } from "./mailbox.js";
12
- /**
13
- * Get the mailbox root path for a given run.
14
- * Exposed so tool.ts can pass it to the controller for real-time mailbox access.
15
- */
16
- export function getMailboxRoot(swarmRoot, runId) {
17
- return resolveMailboxPaths(swarmRoot, runId).root;
18
- }
19
- // ---------------------------------------------------------------------------
20
- // Supervisor
21
- // ---------------------------------------------------------------------------
22
- export class TeamSupervisor {
23
- config;
24
- state;
25
- mailboxPaths;
26
- onProgress;
27
- constructor(config) {
28
- this.config = config;
29
- this.onProgress = config.onProgress;
30
- this.mailboxPaths = resolveMailboxPaths(config.swarmRoot, config.runId);
31
- const phases = config.phases ?? DEFAULT_TEAM_PHASES;
32
- this.state = {
33
- runId: config.runId,
34
- goal: config.goal,
35
- status: "running",
36
- taskGraph: new TaskGraph([...phases]),
37
- agentIds: new Map(),
38
- startedAt: Date.now(),
39
- };
40
- ensureMailbox(this.mailboxPaths);
41
- // Emit initial state (all queued)
42
- this.emitProgress();
43
- }
44
- // -------------------------------------------------------------------
45
- // Phase management
46
- // -------------------------------------------------------------------
47
- /**
48
- * Get ALL phases that are ready to execute (dependencies satisfied,
49
- * not yet started or assigned). Returns in definition order.
50
- *
51
- * 业务说明:返回所有依赖已满足、尚未开始的阶段,而非仅第一个。
52
- * 这使得独立阶段可以并行执行。
53
- */
54
- getAllReadyPhases() {
55
- const ready = [];
56
- for (const name of this.state.taskGraph.getPhaseNames()) {
57
- const phase = this.state.taskGraph.getPhase(name);
58
- if (!phase || phase.status !== "queued")
59
- continue;
60
- // Check dependencies
61
- const deps = phase.phase.dependsOn ?? [];
62
- const depsSatisfied = deps.every((dep) => {
63
- const depState = this.state.taskGraph.getPhase(dep);
64
- return depState?.status === "completed";
65
- });
66
- if (depsSatisfied) {
67
- ready.push(phase);
68
- }
69
- }
70
- return ready;
71
- }
72
- /**
73
- * Start ALL currently ready phases and return their execution descriptors.
74
- * Returns an empty array when no phases are ready.
75
- *
76
- * 业务说明:一次性启动所有就绪阶段,支持并行执行。
77
- * 调用者应并发启动所有返回的阶段。
78
- */
79
- startReadyPhases() {
80
- const readyPhases = this.getAllReadyPhases();
81
- const results = [];
82
- for (const phase of readyPhases) {
83
- const result = this.state.taskGraph.startPhase(phase.phase.name);
84
- if (!result.ok)
85
- continue;
86
- // Gather dependency results to include in assignment
87
- const deps = phase.phase.dependsOn ?? [];
88
- const depResults = {};
89
- for (const dep of deps) {
90
- const depState = this.state.taskGraph.getPhase(dep);
91
- if (depState?.result) {
92
- depResults[dep] = depState.result;
93
- }
94
- }
95
- // Send task assignment message to the role's mailbox
96
- const assignmentMessage = {
97
- messageId: this.generateMessageId(),
98
- runId: this.state.runId,
99
- timestamp: new Date().toISOString(),
100
- from: "supervisor",
101
- to: phase.phase.role,
102
- type: "task_assignment",
103
- payload: {
104
- phase: phase.phase.name,
105
- goal: this.config.goal,
106
- dependsOn: deps,
107
- dependencyResults: depResults,
108
- },
109
- };
110
- sendMessage(this.mailboxPaths, assignmentMessage);
111
- updateDeliveryState(this.mailboxPaths, assignmentMessage.messageId, "delivered");
112
- const prompt = this.buildPhasePrompt(phase);
113
- results.push({
114
- phase,
115
- role: phase.phase.role,
116
- prompt,
117
- });
118
- }
119
- if (results.length > 0) {
120
- this.emitProgress();
121
- }
122
- return results;
123
- }
124
- /**
125
- * Parse agent output for mailbox_message blocks and separate them from the result.
126
- */
127
- parseAgentMessages(output) {
128
- const messages = [];
129
- const messageRegex = /<mailbox_message\s+to="([^"]+)">([\s\S]*?)<\/mailbox_message>/g;
130
- let match;
131
- while ((match = messageRegex.exec(output)) !== null) {
132
- messages.push({
133
- to: match[1].trim(),
134
- content: match[2].trim(),
135
- });
136
- }
137
- const result = output.replace(messageRegex, "").trim();
138
- return { result, messages };
139
- }
140
- /**
141
- * Mark a phase as completed with its raw output.
142
- * Parses messages from output, delivers them, and sends task_result broadcast.
143
- */
144
- completePhase(name, rawOutput, usage) {
145
- const phase = this.state.taskGraph.getPhase(name);
146
- if (!phase) {
147
- return { result: rawOutput, deliveredMessages: 0 };
148
- }
149
- const { result, messages } = this.parseAgentMessages(rawOutput);
150
- this.state.taskGraph.completePhase(name, result, usage);
151
- // Deliver parsed handoff messages
152
- let deliveredCount = 0;
153
- for (const msg of messages) {
154
- try {
155
- const handoffMessage = {
156
- messageId: this.generateMessageId(),
157
- runId: this.state.runId,
158
- timestamp: new Date().toISOString(),
159
- from: name,
160
- to: msg.to,
161
- type: "handoff",
162
- payload: { content: msg.content },
163
- };
164
- sendMessage(this.mailboxPaths, handoffMessage);
165
- updateDeliveryState(this.mailboxPaths, handoffMessage.messageId, "delivered");
166
- deliveredCount++;
167
- }
168
- catch {
169
- // Skip invalid messages
170
- }
171
- }
172
- // Broadcast task result
173
- const resultMessage = {
174
- messageId: this.generateMessageId(),
175
- runId: this.state.runId,
176
- timestamp: new Date().toISOString(),
177
- from: name,
178
- to: "broadcast",
179
- type: "task_result",
180
- payload: { phase: name, result },
181
- };
182
- sendMessage(this.mailboxPaths, resultMessage);
183
- updateDeliveryState(this.mailboxPaths, resultMessage.messageId, "broadcast");
184
- this.emitProgress();
185
- return { result, deliveredMessages: deliveredCount };
186
- }
187
- generateMessageId() {
188
- return `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
189
- }
190
- /**
191
- * Mark a phase as failed.
192
- */
193
- failPhase(name, error) {
194
- this.state.taskGraph.failPhase(name, error);
195
- // BFS: skip all phases that depend (directly or transitively) on a failed/skipped phase
196
- const queue = [name];
197
- const visited = new Set();
198
- while (queue.length > 0) {
199
- const current = queue.shift();
200
- if (visited.has(current))
201
- continue;
202
- visited.add(current);
203
- for (const otherName of this.state.taskGraph.getPhaseNames()) {
204
- const other = this.state.taskGraph.getPhase(otherName);
205
- if (!other || other.status !== "queued")
206
- continue;
207
- const deps = other.phase.dependsOn ?? [];
208
- if (deps.includes(current)) {
209
- this.state.taskGraph.skipPhase(otherName);
210
- queue.push(otherName);
211
- }
212
- }
213
- }
214
- this.emitProgress();
215
- }
216
- /** Assign an agent to a phase. */
217
- assignAgent(phaseName, agentId) {
218
- this.state.agentIds.set(phaseName, agentId);
219
- this.state.taskGraph.assignAgent(phaseName, agentId);
220
- }
221
- /**
222
- * Update token usage for a running phase (for real-time progress display).
223
- *
224
- * 业务说明:实时更新运行中阶段的 token 使用量,供 TUI 仪表盘显示。
225
- * 由 controller 的 onUsage 回调在每次 usage 更新时调用。
226
- */
227
- updatePhaseUsage(phaseName, usage) {
228
- const phase = this.state.taskGraph.getPhase(phaseName);
229
- if (phase && phase.status === "running") {
230
- phase.usage = { ...usage };
231
- this.emitProgress();
232
- }
233
- }
234
- /**
235
- * Handle a real-time message sent by an agent during execution (not just at completion).
236
- * Delivers the message to the recipient's inbox immediately, enabling true live communication.
237
- *
238
- * 业务说明:处理 agent 运行期间实时发送的消息(而不是仅在完成时)。
239
- * 消息立即投递到接收方的 inbox 文件,正在运行的 agent 可以读取到。
240
- * 这实现了真正的实时 agent 间通信,无需等待阶段完成。
241
- */
242
- handleRealtimeMessage(fromRole, message) {
243
- try {
244
- // Override from field to use the actual sender role
245
- const deliveryMessage = {
246
- ...message,
247
- from: fromRole,
248
- };
249
- sendMessage(this.mailboxPaths, deliveryMessage);
250
- updateDeliveryState(this.mailboxPaths, deliveryMessage.messageId, message.to === "broadcast" ? "broadcast" : "delivered");
251
- // Update TUI to reflect new message count
252
- this.emitProgress();
253
- }
254
- catch {
255
- // Best effort real-time delivery; failures are non-fatal
256
- }
257
- }
258
- /**
259
- * Get the mailbox root path for passing to spawner/controller.
260
- */
261
- getMailboxPath() {
262
- return this.mailboxPaths.root;
263
- }
264
- /**
265
- * Resolve the model and tools configuration for a given phase.
266
- *
267
- * Resolution order (highest priority first):
268
- * 1. Phase-level explicit model override (phase.model)
269
- * 2. Phase-level modelTier (small => smallModel if configured)
270
- * 3. Role-level model override from roles config
271
- * 4. Auto-routing by role name: roles in SMALL_MODEL_ROLES => smallModel
272
- * 5. Default model (undefined, inherits parent)
273
- */
274
- getPhaseExecutionConfig(phaseName) {
275
- const phase = this.state.taskGraph.getPhase(phaseName);
276
- const role = phase?.phase.role;
277
- const roleConfig = role
278
- ? this.config.roles?.find((r) => r.role === role)
279
- : undefined;
280
- let model;
281
- let tools;
282
- // Role-level tools whitelist
283
- if (roleConfig?.tools) {
284
- tools = [...roleConfig.tools];
285
- }
286
- // Phase-level explicit model (highest priority)
287
- if (phase?.phase.model) {
288
- model = phase.phase.model;
289
- }
290
- // Phase-level modelTier override
291
- else if (phase?.phase.modelTier === "small" && this.config.smallModel) {
292
- model = this.config.smallModel;
293
- }
294
- else if (phase?.phase.modelTier === "default") {
295
- model = undefined; // explicit default
296
- }
297
- // Role-level model override
298
- else if (roleConfig?.model) {
299
- model = roleConfig.model;
300
- }
301
- // Auto-routing: explorer (and other SMALL_MODEL_ROLES) => smallModel
302
- else if (role && SMALL_MODEL_ROLES.has(role) && this.config.smallModel) {
303
- model = this.config.smallModel;
304
- }
305
- // Phase-level tools override (overrides role config)
306
- if (phase?.phase.tools) {
307
- tools = [...phase.phase.tools];
308
- }
309
- return {
310
- model,
311
- tools,
312
- cwd: this.config.cwd,
313
- };
314
- }
315
- // -------------------------------------------------------------------
316
- // Completion
317
- // -------------------------------------------------------------------
318
- // -------------------------------------------------------------------
319
- // Progress emission
320
- // -------------------------------------------------------------------
321
- /**
322
- * Build and emit a TeamProgressSnapshot for the TUI dashboard.
323
- *
324
- * Reads current phase statuses from the task graph, counts mailbox
325
- * messages, and calls the optional onProgress callback with the
326
- * snapshot.
327
- */
328
- emitProgress() {
329
- if (!this.onProgress)
330
- return;
331
- try {
332
- const allPhases = this.state.taskGraph.getAllPhases();
333
- const phases = allPhases.map((p) => ({
334
- name: p.phase.name,
335
- role: p.phase.role,
336
- status: p.status,
337
- error: p.error,
338
- usage: p.usage,
339
- }));
340
- const completed = phases.filter((p) => p.status === "completed").length;
341
- const failed = phases.filter((p) => p.status === "failed").length;
342
- // Find current running phase(s)
343
- const runningPhases = phases.filter((p) => p.status === "running");
344
- const runningPhase = runningPhases[0];
345
- let mailboxCount = 0;
346
- try {
347
- mailboxCount = countOutboxMessages(this.mailboxPaths);
348
- }
349
- catch {
350
- // Ignore mailbox read errors
351
- }
352
- const totalUsage = this.state.taskGraph.getTotalUsage();
353
- // Build dependency edges from phase definitions
354
- const allPhaseDefs = this.state.taskGraph.getAllPhases();
355
- const dependencyEdges = [];
356
- for (const p of allPhaseDefs) {
357
- const deps = p.phase.dependsOn ?? [];
358
- for (const dep of deps) {
359
- dependencyEdges.push({ from: dep, to: p.phase.name });
360
- }
361
- }
362
- const snapshot = {
363
- title: this.config.goal,
364
- goal: this.config.goal,
365
- status: this.state.status,
366
- totalPhases: phases.length,
367
- completedPhases: completed,
368
- failedPhases: failed,
369
- currentPhase: runningPhases.length > 0
370
- ? runningPhases.map((p) => p.name).join(", ")
371
- : runningPhase?.name,
372
- currentRole: runningPhases.length > 0
373
- ? runningPhases.map((p) => p.role).join(", ")
374
- : runningPhase?.role,
375
- phases,
376
- mailboxCount,
377
- startedAt: this.state.startedAt,
378
- totalUsage,
379
- dependencyEdges,
380
- };
381
- this.onProgress(snapshot);
382
- }
383
- catch {
384
- // Best effort — dashboard failure must not break the run
385
- }
386
- }
387
- /** Check if the entire run is complete. */
388
- isComplete() {
389
- return this.state.taskGraph.isComplete();
390
- }
391
- /** Finalize the run with an overall status. */
392
- finalize() {
393
- this.state.status = this.state.taskGraph.overallStatus();
394
- this.state.completedAt = Date.now();
395
- this.emitProgress();
396
- }
397
- /**
398
- * Synthesize the final team result from all completed phases.
399
- * Max characters of phase output to include inline; rest is truncated.
400
- */
401
- static MAX_PHASE_OUTPUT_CHARS = 50000;
402
- synthesizeResult() {
403
- const allPhases = this.state.taskGraph.getAllPhases();
404
- const durationMs = (this.state.completedAt ?? Date.now()) - this.state.startedAt;
405
- const lines = [
406
- "<swarm_team_result>",
407
- `<summary>${this.buildSummary()}</summary>`,
408
- `<total_duration_ms>${durationMs}</total_duration_ms>`,
409
- ];
410
- // Per-phase results
411
- for (const phase of allPhases) {
412
- const name = phase.phase.name;
413
- const role = phase.phase.role;
414
- const status = phase.status;
415
- const result = phase.result ?? "";
416
- const error = phase.error ?? "";
417
- const agentId = phase.agentId ?? "";
418
- const duration = phase.startedAt && phase.completedAt
419
- ? phase.completedAt - phase.startedAt
420
- : undefined;
421
- const attrs = [
422
- `name="${escapeXmlAttr(name)}"`,
423
- `role="${escapeXmlAttr(role)}"`,
424
- `outcome="${escapeXmlAttr(status)}"`,
425
- ];
426
- if (agentId)
427
- attrs.push(`agent_id="${escapeXmlAttr(agentId)}"`);
428
- if (duration !== undefined)
429
- attrs.push(`duration_ms="${String(duration)}"`);
430
- lines.push(`<phase ${attrs.join(" ")}>`);
431
- if (status === "completed") {
432
- if (result.trim()) {
433
- const truncated = this.truncateForOutput(result);
434
- lines.push(escapeXmlBody(truncated));
435
- }
436
- else {
437
- lines.push("(agent returned no text output; see per-agent output.log for full session transcript)");
438
- }
439
- }
440
- else if (status === "failed" && error) {
441
- lines.push(`<error>${escapeXmlBody(error)}</error>`);
442
- }
443
- else if (status === "skipped") {
444
- lines.push("(phase skipped due to failed dependency)");
445
- }
446
- lines.push(`</phase>`);
447
- }
448
- // Supervisor synthesis — a consolidated summary across all phases
449
- lines.push("<supervisor_synthesis>");
450
- lines.push(escapeXmlBody(this.buildSynthesis(allPhases)));
451
- lines.push("</supervisor_synthesis>");
452
- lines.push("</swarm_team_result>");
453
- return lines.join("\n");
454
- }
455
- /**
456
- * Truncate a phase result to MAX_PHASE_OUTPUT_CHARS with a note.
457
- */
458
- truncateForOutput(text) {
459
- if (text.length <= TeamSupervisor.MAX_PHASE_OUTPUT_CHARS) {
460
- return text;
461
- }
462
- const truncated = text.slice(0, TeamSupervisor.MAX_PHASE_OUTPUT_CHARS);
463
- return (truncated +
464
- `\n\n... [output truncated: ${text.length - TeamSupervisor.MAX_PHASE_OUTPUT_CHARS} additional characters omitted. See output.log for full content.]`);
465
- }
466
- /**
467
- * Build a consolidated synthesis across all completed phases.
468
- *
469
- * Extracts the first non-empty line or heading from each phase to create
470
- * an executive summary, then lists all phases with their key outcomes.
471
- */
472
- buildSynthesis(phases) {
473
- const sections = [];
474
- const completedPhases = phases.filter((p) => p.status === "completed");
475
- const failedPhases = phases.filter((p) => p.status === "failed");
476
- sections.push(`### Team Run: ${this.config.goal.slice(0, 200)}`);
477
- sections.push("");
478
- if (failedPhases.length > 0) {
479
- sections.push(`### Errors`);
480
- for (const p of failedPhases) {
481
- sections.push(`- **${p.phase.name}** (${p.phase.role}): ${p.error ?? "unknown error"}`);
482
- }
483
- sections.push("");
484
- }
485
- sections.push(`### Phase Outcomes`);
486
- for (const p of phases) {
487
- const status = p.status;
488
- const bullet = status === "completed"
489
- ? "DONE"
490
- : status === "failed"
491
- ? "FAIL"
492
- : status === "skipped"
493
- ? "SKIP"
494
- : "??";
495
- const firstLine = this.extractFirstMeaningfulLine(p.result ?? "");
496
- const suffix = firstLine ? ` — ${firstLine}` : "";
497
- sections.push(`- [${bullet}] **${p.phase.name}** (${p.phase.role})${suffix}`);
498
- }
499
- if (completedPhases.length > 0) {
500
- sections.push("");
501
- sections.push(`### Key Deliverables`);
502
- for (const p of completedPhases) {
503
- const excerpt = this.extractExcerpt(p.result ?? "");
504
- if (excerpt) {
505
- sections.push(`#### ${p.phase.name} (${p.phase.role})`);
506
- sections.push(excerpt);
507
- sections.push("");
508
- }
509
- }
510
- }
511
- return sections.join("\n");
512
- }
513
- /**
514
- * Extract the first meaningful line (not empty, not a mailbox tag).
515
- */
516
- extractFirstMeaningfulLine(text) {
517
- const lines = text.split("\n");
518
- for (const line of lines) {
519
- const trimmed = line.trim();
520
- if (!trimmed)
521
- continue;
522
- if (trimmed.startsWith("<mailbox_message"))
523
- continue;
524
- if (trimmed.startsWith("</mailbox_message"))
525
- continue;
526
- // Strip leading markdown heading markers
527
- const cleaned = trimmed.replace(/^#+\s*/, "");
528
- return cleaned.slice(0, 120);
529
- }
530
- return "";
531
- }
532
- /**
533
- * Extract a short excerpt (first ~400 chars of meaningful content) from a phase result.
534
- */
535
- extractExcerpt(text) {
536
- const lines = text.split("\n");
537
- const meaningful = [];
538
- let total = 0;
539
- for (const line of lines) {
540
- const trimmed = line.trim();
541
- if (!trimmed)
542
- continue;
543
- if (trimmed.startsWith("<mailbox_message"))
544
- continue;
545
- if (trimmed.startsWith("</mailbox_message"))
546
- continue;
547
- meaningful.push(line);
548
- total += line.length;
549
- if (total > 400)
550
- break;
551
- }
552
- return meaningful.join("\n").slice(0, 500);
553
- }
554
- // -------------------------------------------------------------------
555
- // Helpers
556
- // -------------------------------------------------------------------
557
- buildPhasePrompt(phase) {
558
- const goal = this.config.goal;
559
- const role = phase.phase.role;
560
- const name = phase.phase.name;
561
- const deps = phase.phase.dependsOn ?? [];
562
- // Gather context from completed dependency phases
563
- let contextBlock = "";
564
- if (deps.length > 0) {
565
- const depSections = deps
566
- .map((dep) => {
567
- const depState = this.state.taskGraph.getPhase(dep);
568
- if (!depState)
569
- return null;
570
- const status = depState.status;
571
- if (status === "failed") {
572
- return `### ${dep} (${depState.phase.role}) — FAILED\nError: ${depState.error ?? "unknown"}`;
573
- }
574
- if (status === "skipped") {
575
- return `### ${dep} (${depState.phase.role}) — SKIPPED`;
576
- }
577
- if (depState.result) {
578
- return `### ${dep} (${depState.phase.role})\n${depState.result}`;
579
- }
580
- return null;
581
- })
582
- .filter(Boolean)
583
- .join("\n\n");
584
- if (depSections) {
585
- contextBlock = `\n\n## Previous Phase Results\n\n${depSections}\n`;
586
- }
587
- }
588
- // Read messages addressed to this role
589
- let messagesBlock = "";
590
- const ackIds = [];
591
- try {
592
- const roleMessages = readTaskInbox(this.mailboxPaths, role);
593
- if (roleMessages.length > 0) {
594
- const messageParts = [];
595
- for (const m of roleMessages) {
596
- ackIds.push(m.messageId);
597
- if (m.type === "task_assignment") {
598
- const depList = Array.isArray(m.payload.dependsOn)
599
- ? m.payload.dependsOn.join(", ")
600
- : "(none)";
601
- let depResultsText = "";
602
- const depResults = m.payload.dependencyResults;
603
- if (depResults && Object.keys(depResults).length > 0) {
604
- depResultsText = Object.entries(depResults)
605
- .map(([depName, depResult]) => `#### ${depName} result:\n${depResult}`)
606
- .join("\n\n");
607
- }
608
- messageParts.push(`### Task Assignment from supervisor (${m.timestamp})\n` +
609
- `Phase: ${m.payload.phase ?? name}\n` +
610
- `Dependencies: ${depList}\n` +
611
- (depResultsText ? `\n${depResultsText}\n` : ""));
612
- }
613
- else if (m.type === "handoff") {
614
- const content = String(m.payload.content ?? "");
615
- messageParts.push(`### Message from ${m.from} (${m.timestamp})\n${content}`);
616
- }
617
- else if (m.type === "task_result") {
618
- const resultText = String(m.payload.result ?? "");
619
- const fromPhase = String(m.payload.phase ?? m.from);
620
- messageParts.push(`### Completed: ${fromPhase} (${m.timestamp})\n${resultText}`);
621
- }
622
- }
623
- if (messageParts.length > 0) {
624
- messagesBlock = `\n## Messages\n\n${messageParts.join("\n\n")}\n`;
625
- }
626
- }
627
- }
628
- catch {
629
- // Ignore mailbox read errors
630
- }
631
- // Acknowledge messages that were included in the prompt
632
- if (ackIds.length > 0) {
633
- try {
634
- ackTaskMessages(this.mailboxPaths, role, ackIds);
635
- }
636
- catch {
637
- // Best effort acknowledgment
638
- }
639
- }
640
- // Get role-specific system prompt if configured
641
- const roleConfig = this.config.roles?.find((r) => r.role === role);
642
- const roleSystemPrompt = roleConfig?.systemPrompt;
643
- return [
644
- roleSystemPrompt
645
- ? roleSystemPrompt
646
- : `You are the ${role} agent on a software engineering team.`,
647
- "",
648
- `## Overall Goal`,
649
- "",
650
- goal,
651
- "",
652
- `## Your Current Phase: ${name}`,
653
- "",
654
- contextBlock,
655
- messagesBlock,
656
- `## Communication Protocol`,
657
- "",
658
- `You have TWO ways to communicate with other team members:`,
659
- "",
660
- `1. **In your final output** (delivered after you complete):`,
661
- ` Wrap messages in: <mailbox_message to="role_name">content</mailbox_message>`,
662
- "",
663
- `2. **Real-time during your work** (delivered immediately):`,
664
- ` Append a JSON line to your outbox file (see "Real-time Mailbox Communication" section below).`,
665
- ` Check your inbox file periodically for new messages from teammates.`,
666
- "",
667
- `You can address messages to: explorer, planner, coder, reviewer, tester, fixer, or broadcast (all agents).`,
668
- "",
669
- `Your final output (outside of mailbox_message tags) is your phase result.`,
670
- `Complete the ${name} phase and write your result.`,
671
- ]
672
- .filter(Boolean)
673
- .join("\n");
674
- }
675
- buildSummary() {
676
- const all = this.state.taskGraph.getAllPhases();
677
- const completed = all.filter((p) => p.status === "completed").length;
678
- const failed = all.filter((p) => p.status === "failed").length;
679
- const skipped = all.filter((p) => p.status === "skipped").length;
680
- const total = all.length;
681
- return (`Phases completed: ${completed}/${total}. ` +
682
- `Succeeded: ${completed}, Failed: ${failed}, Skipped: ${skipped}.`);
683
- }
684
- }
685
- //# sourceMappingURL=supervisor.js.map