@cuylabs/agent-core 0.8.0 → 0.10.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.
Files changed (127) hide show
  1. package/README.md +33 -17
  2. package/dist/chunk-2O4MCSQS.js +780 -0
  3. package/dist/chunk-2TTOLHBT.js +198 -0
  4. package/dist/chunk-5FMSGQVX.js +281 -0
  5. package/dist/chunk-5NVVNXPQ.js +288 -0
  6. package/dist/{chunk-CAA7FHIH.js → chunk-6HZBHFOL.js} +3 -103
  7. package/dist/chunk-CJI7PVS2.js +58 -0
  8. package/dist/{chunk-N6HWIEEA.js → chunk-CMYN2RCB.js} +278 -61
  9. package/dist/chunk-FII65CN7.js +117 -0
  10. package/dist/{chunk-IVUJDISU.js → chunk-GFTW23FV.js} +5 -14
  11. package/dist/chunk-I6PKJ7XQ.js +292 -0
  12. package/dist/{chunk-BDBZ3SLK.js → chunk-ICZ66572.js} +48 -4
  13. package/dist/chunk-KYLPMBHD.js +316 -0
  14. package/dist/chunk-MXAP4UG6.js +2956 -0
  15. package/dist/{chunk-RZITT45F.js → chunk-N3VX7FEE.js} +39 -6
  16. package/dist/{chunk-YSLSEQ6B.js → chunk-NDZWXCBZ.js} +218 -95
  17. package/dist/{chunk-P6YF7USR.js → chunk-Q742PSH3.js} +23 -38
  18. package/dist/chunk-QAL3OMI3.js +943 -0
  19. package/dist/{chunk-RFEKJKTO.js → chunk-RN6WZEUF.js} +330 -280
  20. package/dist/{chunk-ZXAKHMWH.js → chunk-ROTGCYDW.js} +22 -84
  21. package/dist/chunk-SPBFQXOT.js +0 -0
  22. package/dist/{chunk-LRHOS4ZN.js → chunk-SPILYYDF.js} +3 -2
  23. package/dist/chunk-SSFBF3US.js +602 -0
  24. package/dist/chunk-SZ2XBPTW.js +8 -0
  25. package/dist/chunk-T4UIX5D7.js +115 -0
  26. package/dist/chunk-TIHPYVAJ.js +102 -0
  27. package/dist/{chunk-YUUJK53A.js → chunk-TOTDGK3P.js} +1 -1
  28. package/dist/chunk-V4RFNEET.js +563 -0
  29. package/dist/chunk-VOUEJSW6.js +0 -0
  30. package/dist/{chunk-4BDA7DQY.js → chunk-WBPOZ7CL.js} +673 -273
  31. package/dist/chunk-X4VN4GIJ.js +185 -0
  32. package/dist/dispatch/index.d.ts +93 -0
  33. package/dist/dispatch/index.js +37 -0
  34. package/dist/events/index.d.ts +93 -0
  35. package/dist/events/index.js +6 -0
  36. package/dist/{runtime → execution}/index.d.ts +120 -34
  37. package/dist/{runtime → execution}/index.js +18 -13
  38. package/dist/index-BCqEGzBj.d.ts +251 -0
  39. package/dist/index.d.ts +490 -122
  40. package/dist/index.js +2104 -615
  41. package/dist/{errors → inference/errors}/index.d.ts +2 -2
  42. package/dist/{errors → inference/errors}/index.js +1 -1
  43. package/dist/inference/index.d.ts +16 -23
  44. package/dist/inference/index.js +45 -16
  45. package/dist/instance-BqV2D5pc.d.ts +5723 -0
  46. package/dist/logger/index.d.ts +50 -0
  47. package/dist/logger/index.js +11 -0
  48. package/dist/mcp/index.d.ts +5 -9
  49. package/dist/mcp/index.js +2 -3
  50. package/dist/middleware/index.d.ts +10 -149
  51. package/dist/middleware/index.js +11 -3
  52. package/dist/model-messages-B4nK9D1-.d.ts +13 -0
  53. package/dist/models/index.d.ts +23 -18
  54. package/dist/models/index.js +48 -11
  55. package/dist/models/reasoning/index.d.ts +4 -0
  56. package/dist/{reasoning → models/reasoning}/index.js +3 -3
  57. package/dist/plugin/index.d.ts +458 -0
  58. package/dist/plugin/index.js +32 -0
  59. package/dist/profiles/index.d.ts +55 -0
  60. package/dist/profiles/index.js +30 -0
  61. package/dist/prompt/index.d.ts +8 -12
  62. package/dist/prompt/index.js +3 -2
  63. package/dist/safety/index.d.ts +109 -14
  64. package/dist/safety/index.js +59 -3
  65. package/dist/sandbox/index.d.ts +81 -0
  66. package/dist/sandbox/index.js +1 -0
  67. package/dist/skill/index.d.ts +10 -8
  68. package/dist/skill/index.js +3 -3
  69. package/dist/storage/index.d.ts +12 -4
  70. package/dist/storage/index.js +1 -1
  71. package/dist/subagents/index.d.ts +177 -0
  72. package/dist/subagents/index.js +78 -0
  73. package/dist/team/index.d.ts +544 -0
  74. package/dist/team/index.js +41 -0
  75. package/dist/tool/host/index.d.ts +41 -0
  76. package/dist/tool/host/index.js +10 -0
  77. package/dist/tool/index.d.ts +125 -21
  78. package/dist/tool/index.js +20 -13
  79. package/dist/{types-VQgymC1N.d.ts → types-Bj_J8u_W.d.ts} +44 -64
  80. package/dist/{types-CHiPh8U2.d.ts → types-C_LCeYNg.d.ts} +7 -7
  81. package/dist/types-RSCv7nQ4.d.ts +59 -0
  82. package/package.json +58 -53
  83. package/dist/builder-UpOWQMW3.d.ts +0 -34
  84. package/dist/chunk-7MUFEN4K.js +0 -559
  85. package/dist/chunk-7VKQ4WPB.js +0 -73
  86. package/dist/chunk-BFM2YHNM.js +0 -222
  87. package/dist/chunk-DWYX7ASF.js +0 -26
  88. package/dist/chunk-KUVSERLJ.js +0 -50
  89. package/dist/chunk-N7P4PN3O.js +0 -84
  90. package/dist/chunk-SDSBEQXG.js +0 -157
  91. package/dist/chunk-SQU2AJHO.js +0 -305
  92. package/dist/chunk-VBWWUHWI.js +0 -724
  93. package/dist/chunk-VEKUXUVF.js +0 -41
  94. package/dist/chunk-VNQBHPCT.js +0 -398
  95. package/dist/chunk-WWYYNWEW.js +0 -259
  96. package/dist/context/index.d.ts +0 -259
  97. package/dist/context/index.js +0 -26
  98. package/dist/events-CE72w8W4.d.ts +0 -149
  99. package/dist/host/index.d.ts +0 -45
  100. package/dist/host/index.js +0 -8
  101. package/dist/index-CWSchSql.d.ts +0 -1058
  102. package/dist/messages-BYWGn8TY.d.ts +0 -110
  103. package/dist/presets/index.d.ts +0 -53
  104. package/dist/presets/index.js +0 -28
  105. package/dist/reasoning/index.d.ts +0 -116
  106. package/dist/registry-DwYqsQkX.d.ts +0 -164
  107. package/dist/runner-e2YRcUoX.d.ts +0 -786
  108. package/dist/scope/index.d.ts +0 -10
  109. package/dist/scope/index.js +0 -14
  110. package/dist/session-manager-B_CWGTsl.d.ts +0 -274
  111. package/dist/signal/index.d.ts +0 -28
  112. package/dist/signal/index.js +0 -6
  113. package/dist/sub-agent/index.d.ts +0 -23
  114. package/dist/sub-agent/index.js +0 -15
  115. package/dist/tool-BHbyUAy3.d.ts +0 -150
  116. package/dist/tool-DLXAR9Ce.d.ts +0 -145
  117. package/dist/tracker-DClqYqTj.d.ts +0 -96
  118. package/dist/tracking/index.d.ts +0 -111
  119. package/dist/tracking/index.js +0 -20
  120. package/dist/types-BfNpU8NS.d.ts +0 -270
  121. package/dist/types-BnpEOYV-.d.ts +0 -50
  122. package/dist/types-CQL-SvTn.d.ts +0 -29
  123. package/dist/types-CWm-7rvB.d.ts +0 -55
  124. package/dist/types-KKDrdU9Y.d.ts +0 -325
  125. package/dist/types-QA4WhEfz.d.ts +0 -138
  126. package/dist/types-QKHHQLLq.d.ts +0 -336
  127. package/dist/types-YuWV4ag7.d.ts +0 -72
@@ -0,0 +1,2956 @@
1
+ import {
2
+ sleep
3
+ } from "./chunk-SZ2XBPTW.js";
4
+ import {
5
+ Tool
6
+ } from "./chunk-Q742PSH3.js";
7
+ import {
8
+ extractApprovalPatterns,
9
+ matchApprovalPattern,
10
+ resolveCapability
11
+ } from "./chunk-FII65CN7.js";
12
+
13
+ // src/team/types.ts
14
+ var TERMINAL_STATUSES = [
15
+ "completed",
16
+ "aborted",
17
+ "failed",
18
+ "cancelled"
19
+ ];
20
+
21
+ // src/team/task-board.ts
22
+ var TASK_CONFLICT_MAX_RETRIES = 4;
23
+ var TaskConflictError = class extends Error {
24
+ constructor(message, taskId) {
25
+ super(message);
26
+ this.taskId = taskId;
27
+ this.name = "TaskConflictError";
28
+ }
29
+ };
30
+ function clone(value) {
31
+ return structuredClone(value);
32
+ }
33
+ function now() {
34
+ return (/* @__PURE__ */ new Date()).toISOString();
35
+ }
36
+ function requireNonEmpty(value, label) {
37
+ const trimmed = value.trim();
38
+ if (!trimmed) throw new Error(`${label} must not be empty`);
39
+ return trimmed;
40
+ }
41
+ function normalizeDeps(taskId, deps) {
42
+ if (!deps || deps.length === 0) return void 0;
43
+ const unique = [...new Set(deps.map((d) => requireNonEmpty(d, "dependency ID")))];
44
+ if (unique.includes(taskId)) throw new Error(`Task "${taskId}" cannot depend on itself`);
45
+ return unique;
46
+ }
47
+ function isTerminal(status) {
48
+ return status === "completed" || status === "aborted" || status === "failed" || status === "cancelled";
49
+ }
50
+ function isDependencyFailure(status) {
51
+ return status === "failed" || status === "cancelled";
52
+ }
53
+ function resolveDependencyStatus(task, lookup) {
54
+ const deps = task.dependsOn ?? [];
55
+ if (deps.length === 0) {
56
+ return {
57
+ status: task.status === "blocked" ? "pending" : task.status,
58
+ failedDeps: [],
59
+ blockedBy: []
60
+ };
61
+ }
62
+ const failedDeps = [];
63
+ const blockedBy = [];
64
+ for (const depId of deps) {
65
+ const dep = lookup.get(depId);
66
+ if (!dep) {
67
+ blockedBy.push(depId);
68
+ continue;
69
+ }
70
+ if (dep.status === "completed") continue;
71
+ if (isDependencyFailure(dep.status)) {
72
+ failedDeps.push(depId);
73
+ continue;
74
+ }
75
+ blockedBy.push(depId);
76
+ }
77
+ if (failedDeps.length > 0) {
78
+ return { status: "cancelled", failedDeps, blockedBy };
79
+ }
80
+ if (blockedBy.length > 0) {
81
+ return { status: "blocked", failedDeps, blockedBy };
82
+ }
83
+ return {
84
+ status: task.status === "blocked" ? "pending" : task.status,
85
+ failedDeps: [],
86
+ blockedBy: []
87
+ };
88
+ }
89
+ var InMemoryTaskBoardStore = class {
90
+ members = /* @__PURE__ */ new Map();
91
+ tasks = /* @__PURE__ */ new Map();
92
+ async listMembers() {
93
+ return [...this.members.values()].map(clone).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
94
+ }
95
+ async getMember(memberId) {
96
+ const m = this.members.get(memberId);
97
+ return m ? clone(m) : void 0;
98
+ }
99
+ async putMember(member) {
100
+ const copy = clone(member);
101
+ this.members.set(copy.id, copy);
102
+ return clone(copy);
103
+ }
104
+ async listTasks(filter) {
105
+ let tasks = [...this.tasks.values()];
106
+ if (filter?.memberId) {
107
+ tasks = tasks.filter((t) => t.memberId === filter.memberId);
108
+ }
109
+ if (filter?.status) {
110
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
111
+ tasks = tasks.filter((t) => statuses.includes(t.status));
112
+ }
113
+ const sorted = tasks.map(clone).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
114
+ if (filter?.limit !== void 0) {
115
+ return sorted.slice(0, Math.max(0, Math.floor(filter.limit)));
116
+ }
117
+ return sorted;
118
+ }
119
+ async getTask(taskId) {
120
+ const t = this.tasks.get(taskId);
121
+ return t ? clone(t) : void 0;
122
+ }
123
+ async putTask(task) {
124
+ const copy = clone(task);
125
+ this.tasks.set(copy.id, copy);
126
+ return clone(copy);
127
+ }
128
+ };
129
+ var TaskBoard = class {
130
+ constructor(store) {
131
+ this.store = store;
132
+ }
133
+ async start() {
134
+ await this.store.start?.();
135
+ }
136
+ async stop() {
137
+ await this.store.stop?.();
138
+ }
139
+ // ── Read ───────────────────────────────────────────────────────────
140
+ async listMembers() {
141
+ return this.store.listMembers();
142
+ }
143
+ async getMember(id) {
144
+ return this.store.getMember(id);
145
+ }
146
+ async listTasks(filter) {
147
+ return this.store.listTasks(filter);
148
+ }
149
+ async getTask(id) {
150
+ return this.store.getTask(id);
151
+ }
152
+ async snapshot() {
153
+ const [members, tasks] = await Promise.all([
154
+ this.store.listMembers(),
155
+ this.store.listTasks()
156
+ ]);
157
+ return { members, tasks };
158
+ }
159
+ // ── Member mutations ───────────────────────────────────────────────
160
+ async putMember(member) {
161
+ return this.retryOnConflict(() => this.store.putMember(member));
162
+ }
163
+ // ── Task lifecycle ─────────────────────────────────────────────────
164
+ /**
165
+ * Create a new task. Its initial status is computed from dependencies:
166
+ * `"pending"` if all deps are met, `"blocked"` otherwise, or
167
+ * `"cancelled"` if a dependency already failed.
168
+ */
169
+ async createTask(input) {
170
+ return this.retryOnConflict(async () => {
171
+ const id = requireNonEmpty(input.id, "task ID");
172
+ const existing = await this.store.getTask(id);
173
+ if (existing) throw new Error(`Task already exists: ${id}`);
174
+ const ts = now();
175
+ const task = {
176
+ id,
177
+ memberId: input.memberId?.trim() || void 0,
178
+ title: requireNonEmpty(input.title, "title"),
179
+ prompt: requireNonEmpty(input.prompt, "prompt"),
180
+ status: "pending",
181
+ dependsOn: normalizeDeps(id, input.dependsOn),
182
+ createdAt: ts,
183
+ updatedAt: ts
184
+ };
185
+ const allTasks = await this.store.listTasks();
186
+ const lookup = new Map(allTasks.map((t) => [t.id, t]));
187
+ const resolved = resolveDependencyStatus(task, lookup);
188
+ const initial = {
189
+ ...task,
190
+ status: resolved.status,
191
+ error: resolved.failedDeps.length > 0 ? `Dependency failed: ${resolved.failedDeps.join(", ")}` : void 0
192
+ };
193
+ const stored = await this.store.putTask(initial);
194
+ return {
195
+ task: stored,
196
+ transitions: [{ task: stored, reason: "created" }]
197
+ };
198
+ });
199
+ }
200
+ /**
201
+ * Claim a pending task for a member. Moves status to `"claimed"`.
202
+ */
203
+ async claimTask(taskId, memberId) {
204
+ return this.retryOnConflict(async () => {
205
+ const id = requireNonEmpty(memberId, "member ID");
206
+ const task = await this.requireTask(taskId);
207
+ if (task.status !== "pending") {
208
+ throw new Error(`Task "${taskId}" is not claimable (status: ${task.status})`);
209
+ }
210
+ if (task.memberId && task.memberId !== id) {
211
+ throw new Error(`Task "${taskId}" is assigned to ${task.memberId}, not ${id}`);
212
+ }
213
+ const ts = now();
214
+ return this.store.putTask({
215
+ ...task,
216
+ memberId: id,
217
+ status: "claimed",
218
+ claimedAt: ts,
219
+ updatedAt: ts
220
+ });
221
+ });
222
+ }
223
+ /**
224
+ * Claim the next available pending task for a member.
225
+ * Prefers tasks already assigned to this member, then unassigned tasks.
226
+ */
227
+ async claimNextTask(memberId) {
228
+ const id = requireNonEmpty(memberId, "member ID");
229
+ const pending = await this.store.listTasks({ status: "pending" });
230
+ const next = pending.find((t) => t.memberId === id) ?? pending.find((t) => !t.memberId);
231
+ if (!next) return void 0;
232
+ return this.claimTask(next.id, id);
233
+ }
234
+ /**
235
+ * Mark a claimed task as running with a run reference.
236
+ */
237
+ async startTask(taskId, runId) {
238
+ return this.retryOnConflict(async () => {
239
+ const task = await this.requireTask(taskId);
240
+ if (task.status !== "claimed") {
241
+ throw new Error(`Task "${taskId}" must be claimed before starting (status: ${task.status})`);
242
+ }
243
+ return this.store.putTask({
244
+ ...task,
245
+ status: "running",
246
+ runId,
247
+ updatedAt: now()
248
+ });
249
+ });
250
+ }
251
+ /**
252
+ * Complete a running task with a result.
253
+ * Cascades: blocked dependents may become pending or cancelled.
254
+ */
255
+ async completeTask(taskId, result) {
256
+ return this.retryOnConflict(async () => {
257
+ const task = await this.requireTask(taskId);
258
+ const completed = {
259
+ ...task,
260
+ status: "completed",
261
+ result,
262
+ error: void 0,
263
+ updatedAt: now()
264
+ };
265
+ const stored = await this.store.putTask(completed);
266
+ const cascaded = await this.refreshDependents();
267
+ return {
268
+ task: stored,
269
+ transitions: [{ task: stored, reason: "completed", previous: task.status }, ...cascaded]
270
+ };
271
+ });
272
+ }
273
+ /**
274
+ * Fail a running task.
275
+ * Cascades: blocked dependents may be cancelled.
276
+ */
277
+ async failTask(taskId, error) {
278
+ return this.retryOnConflict(async () => {
279
+ const task = await this.requireTask(taskId);
280
+ const failed = {
281
+ ...task,
282
+ status: "failed",
283
+ result: void 0,
284
+ error: requireNonEmpty(error, "error"),
285
+ updatedAt: now()
286
+ };
287
+ const stored = await this.store.putTask(failed);
288
+ const cascaded = await this.refreshDependents();
289
+ return {
290
+ task: stored,
291
+ transitions: [{ task: stored, reason: "failed", previous: task.status }, ...cascaded]
292
+ };
293
+ });
294
+ }
295
+ /**
296
+ * Abort a claimed or running task without cascading dependency failure.
297
+ *
298
+ * Downstream tasks remain blocked so the host can retry or replace the
299
+ * aborted prerequisite explicitly.
300
+ */
301
+ async abortTask(taskId, reason) {
302
+ return this.retryOnConflict(async () => {
303
+ const task = await this.requireTask(taskId);
304
+ if (isTerminal(task.status)) {
305
+ throw new Error(`Task "${taskId}" is already terminal (status: ${task.status})`);
306
+ }
307
+ const aborted = {
308
+ ...task,
309
+ status: "aborted",
310
+ result: void 0,
311
+ error: reason?.trim() || task.error,
312
+ updatedAt: now()
313
+ };
314
+ const stored = await this.store.putTask(aborted);
315
+ return {
316
+ task: stored,
317
+ transitions: [{ task: stored, reason: "aborted", previous: task.status }]
318
+ };
319
+ });
320
+ }
321
+ /**
322
+ * Cancel a task (at any non-terminal status).
323
+ * Cascades: blocked dependents may be cancelled.
324
+ */
325
+ async cancelTask(taskId, reason) {
326
+ return this.retryOnConflict(async () => {
327
+ const task = await this.requireTask(taskId);
328
+ if (isTerminal(task.status)) {
329
+ throw new Error(`Task "${taskId}" is already terminal (status: ${task.status})`);
330
+ }
331
+ const cancelled = {
332
+ ...task,
333
+ status: "cancelled",
334
+ result: void 0,
335
+ error: reason?.trim() || task.error,
336
+ updatedAt: now()
337
+ };
338
+ const stored = await this.store.putTask(cancelled);
339
+ const cascaded = await this.refreshDependents();
340
+ return {
341
+ task: stored,
342
+ transitions: [{ task: stored, reason: "cancelled", previous: task.status }, ...cascaded]
343
+ };
344
+ });
345
+ }
346
+ /**
347
+ * Re-evaluate all blocked tasks against current dependency state.
348
+ * Called automatically after complete/fail/cancel, but can also
349
+ * be called manually for batch operations.
350
+ */
351
+ async refreshDependents() {
352
+ return this.retryOnConflict(async () => {
353
+ const allTasks = await this.store.listTasks();
354
+ const lookup = new Map(allTasks.map((t) => [t.id, t]));
355
+ const transitions = [];
356
+ let changed = true;
357
+ while (changed) {
358
+ changed = false;
359
+ for (const task of lookup.values()) {
360
+ if (task.status !== "blocked") continue;
361
+ const resolved = resolveDependencyStatus(task, lookup);
362
+ if (resolved.status === task.status) continue;
363
+ const updated = {
364
+ ...task,
365
+ status: resolved.status,
366
+ updatedAt: now(),
367
+ error: resolved.failedDeps.length > 0 ? `Dependency failed: ${resolved.failedDeps.join(", ")}` : void 0
368
+ };
369
+ const stored = await this.store.putTask(updated);
370
+ lookup.set(stored.id, stored);
371
+ changed = true;
372
+ transitions.push({
373
+ task: stored,
374
+ reason: resolved.failedDeps.length > 0 ? "blocked-dependency-failed" : "unblocked",
375
+ previous: task.status
376
+ });
377
+ }
378
+ }
379
+ return transitions;
380
+ });
381
+ }
382
+ // ── Internal ───────────────────────────────────────────────────────
383
+ async requireTask(taskId) {
384
+ const task = await this.store.getTask(taskId);
385
+ if (!task) throw new Error(`Unknown task: ${taskId}`);
386
+ return task;
387
+ }
388
+ async retryOnConflict(op) {
389
+ for (let attempt = 0; ; attempt += 1) {
390
+ try {
391
+ return await op();
392
+ } catch (error) {
393
+ if (error instanceof TaskConflictError && attempt + 1 < TASK_CONFLICT_MAX_RETRIES) {
394
+ continue;
395
+ }
396
+ throw error;
397
+ }
398
+ }
399
+ }
400
+ };
401
+
402
+ // src/team/mailbox.ts
403
+ import { randomUUID } from "crypto";
404
+ function clone2(value) {
405
+ return structuredClone(value);
406
+ }
407
+ function requireNonEmpty2(value, label) {
408
+ const trimmed = value.trim();
409
+ if (!trimmed) throw new Error(`${label} must not be empty`);
410
+ return trimmed;
411
+ }
412
+ function matchesKind(msg, kind) {
413
+ if (!kind) return true;
414
+ const list = Array.isArray(kind) ? kind : [kind];
415
+ return list.includes(msg.kind);
416
+ }
417
+ var InMemoryMailboxStore = class {
418
+ messages = [];
419
+ async append(message) {
420
+ const copy = clone2(message);
421
+ this.messages.push(copy);
422
+ return clone2(copy);
423
+ }
424
+ async list(filter) {
425
+ let msgs = this.messages;
426
+ if (filter?.teamId) msgs = msgs.filter((m) => m.teamId === filter.teamId);
427
+ if (filter?.from) msgs = msgs.filter((m) => m.from === filter.from);
428
+ if (filter?.to) msgs = msgs.filter((m) => m.to === filter.to);
429
+ if (filter?.taskId) msgs = msgs.filter((m) => m.taskId === filter.taskId);
430
+ if (filter?.kind) msgs = msgs.filter((m) => matchesKind(m, filter.kind));
431
+ const sorted = msgs.map(clone2).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
432
+ if (filter?.limit !== void 0) {
433
+ const n = Math.max(0, Math.floor(filter.limit));
434
+ return sorted.slice(Math.max(0, sorted.length - n));
435
+ }
436
+ return sorted;
437
+ }
438
+ };
439
+ var Mailbox = class {
440
+ store;
441
+ subscribers = /* @__PURE__ */ new Set();
442
+ createId;
443
+ constructor(options) {
444
+ this.store = options?.store ?? new InMemoryMailboxStore();
445
+ this.createId = options?.createId ?? (() => randomUUID());
446
+ }
447
+ async start() {
448
+ await this.store.start?.();
449
+ }
450
+ async stop() {
451
+ await this.store.stop?.();
452
+ }
453
+ /**
454
+ * Send a message. Notifies subscribers after persistence.
455
+ */
456
+ async send(input) {
457
+ const to = input.to?.trim() || void 0;
458
+ const message = {
459
+ id: input.id?.trim() || this.createId(),
460
+ teamId: requireNonEmpty2(input.teamId, "teamId"),
461
+ from: requireNonEmpty2(input.from, "from"),
462
+ to,
463
+ kind: input.kind ?? (to ? "direct" : "broadcast"),
464
+ body: requireNonEmpty2(input.body, "body"),
465
+ payload: input.payload ? clone2(input.payload) : void 0,
466
+ taskId: input.taskId?.trim() || void 0,
467
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
468
+ metadata: input.metadata ? { ...input.metadata } : void 0
469
+ };
470
+ const stored = await this.store.append(message);
471
+ if (this.subscribers.size > 0) {
472
+ const delivered = clone2(stored);
473
+ await Promise.allSettled(
474
+ [...this.subscribers].map((sub) => sub(clone2(delivered)))
475
+ );
476
+ }
477
+ return clone2(stored);
478
+ }
479
+ /**
480
+ * List messages matching the filter.
481
+ */
482
+ async list(filter) {
483
+ return this.store.list(filter);
484
+ }
485
+ /**
486
+ * Subscribe to new messages. Returns an unsubscribe function.
487
+ */
488
+ subscribe(subscriber) {
489
+ this.subscribers.add(subscriber);
490
+ return () => {
491
+ this.subscribers.delete(subscriber);
492
+ };
493
+ }
494
+ };
495
+
496
+ // src/team/coordinator/coordinator.ts
497
+ import { randomUUID as randomUUID2 } from "crypto";
498
+
499
+ // src/team/permissions.ts
500
+ function matchesRule(rule, memberId, toolName, args, ctx) {
501
+ if (!matchApprovalPattern(rule.tool, toolName)) return false;
502
+ if (rule.members && !rule.members.includes(memberId)) return false;
503
+ if (!rule.pattern) return true;
504
+ const patterns = ctx.permissionPatterns ? ctx.permissionPatterns(args) : extractApprovalPatterns(toolName, args);
505
+ return patterns.some((pattern) => matchApprovalPattern(rule.pattern, pattern));
506
+ }
507
+ function teamPermissionPolicy(config) {
508
+ const rules = config.rules ?? [];
509
+ return {
510
+ name: `team-permission:${config.memberId}`,
511
+ async beforeToolCall(tool, args, ctx) {
512
+ if (resolveCapability(ctx.toolCapabilities?.readOnly, args, false)) {
513
+ return { action: "allow" };
514
+ }
515
+ for (const rule of rules) {
516
+ if (matchesRule(rule, config.memberId, tool, args, ctx)) {
517
+ if (rule.action === "deny") {
518
+ return {
519
+ action: "deny",
520
+ reason: rule.reason ?? `Denied by team policy for "${config.memberId}"`
521
+ };
522
+ }
523
+ return { action: "allow" };
524
+ }
525
+ }
526
+ if (config.forwardApproval) {
527
+ return config.forwardApproval(config.memberId, tool, args, ctx);
528
+ }
529
+ return { action: "allow" };
530
+ }
531
+ };
532
+ }
533
+
534
+ // src/team/execution.ts
535
+ function isTerminalStatus(status) {
536
+ return status === "completed" || status === "aborted" || status === "failed" || status === "cancelled";
537
+ }
538
+ function isQueuedMessageForMember(message, memberId) {
539
+ if (message.kind === "system") {
540
+ return false;
541
+ }
542
+ if (message.kind === "broadcast") {
543
+ return true;
544
+ }
545
+ return message.to === memberId;
546
+ }
547
+ async function collectPendingMessages(ctx, runtime) {
548
+ const messages = await ctx.mailbox.list({
549
+ teamId: ctx.teamId,
550
+ kind: ["direct", "broadcast"]
551
+ });
552
+ const relevant = messages.filter((message) => isQueuedMessageForMember(message, runtime.member.id));
553
+ if (!runtime.lastDeliveredMessageAt || !runtime.lastDeliveredMessageId) {
554
+ return relevant;
555
+ }
556
+ const pending = [];
557
+ let cursorReached = false;
558
+ for (const message of relevant) {
559
+ if (cursorReached) {
560
+ pending.push(message);
561
+ continue;
562
+ }
563
+ if (message.createdAt < runtime.lastDeliveredMessageAt) {
564
+ continue;
565
+ }
566
+ if (message.createdAt > runtime.lastDeliveredMessageAt) {
567
+ cursorReached = true;
568
+ pending.push(message);
569
+ continue;
570
+ }
571
+ if (message.id === runtime.lastDeliveredMessageId) {
572
+ cursorReached = true;
573
+ }
574
+ }
575
+ return pending;
576
+ }
577
+ async function claimAndExecute(ctx, runtime) {
578
+ if (runtime.member.status !== "idle") return void 0;
579
+ const claimed = await ctx.taskBoard.claimNextTask(runtime.member.id);
580
+ if (!claimed) return void 0;
581
+ await ctx.updateMemberStatus(runtime.member.id, "busy", claimed.id);
582
+ await ctx.emitTransitions([{ task: claimed, reason: "claimed", previous: "pending" }]);
583
+ const started = await ctx.taskBoard.startTask(claimed.id, ctx.createId());
584
+ await ctx.emit({
585
+ type: "team-task-transition",
586
+ teamId: ctx.teamId,
587
+ taskId: started.id,
588
+ previous: claimed.status,
589
+ current: "running",
590
+ reason: "started"
591
+ });
592
+ const pendingMessages = await collectPendingMessages(ctx, runtime);
593
+ if (pendingMessages.length > 0) {
594
+ const latest = pendingMessages[pendingMessages.length - 1];
595
+ runtime.lastDeliveredMessageAt = latest.createdAt;
596
+ runtime.lastDeliveredMessageId = latest.id;
597
+ }
598
+ const prompt = buildTaskPrompt(runtime.role, started, pendingMessages);
599
+ runtime.taskAbort = new AbortController();
600
+ runtime.taskStartedAt = Date.now();
601
+ runtime.activeRun = executeTask(ctx, runtime, started, prompt).catch((err) => {
602
+ ctx.log?.(`[${runtime.member.id}] executeTask unhandled error: ${err instanceof Error ? err.message : String(err)}`);
603
+ });
604
+ return started;
605
+ }
606
+ async function prepareTaskForExternalExecution(ctx, runtime, taskId, runId) {
607
+ if (ctx.taskDispatchMode !== "external") {
608
+ throw new Error('External task preparation requires taskDispatchMode="external"');
609
+ }
610
+ if (runtime.member.status !== "idle") return void 0;
611
+ const task = await ctx.taskBoard.getTask(taskId);
612
+ if (!task || task.memberId !== runtime.member.id || task.status !== "pending") {
613
+ return void 0;
614
+ }
615
+ const claimed = await ctx.taskBoard.claimTask(taskId, runtime.member.id);
616
+ await ctx.updateMemberStatus(runtime.member.id, "busy", claimed.id);
617
+ await ctx.emitTransitions([{ task: claimed, reason: "claimed", previous: "pending" }]);
618
+ const started = await ctx.taskBoard.startTask(claimed.id, runId);
619
+ await ctx.emit({
620
+ type: "team-task-transition",
621
+ teamId: ctx.teamId,
622
+ taskId: started.id,
623
+ previous: claimed.status,
624
+ current: "running",
625
+ reason: "started"
626
+ });
627
+ const pendingMessages = await collectPendingMessages(ctx, runtime);
628
+ if (pendingMessages.length > 0) {
629
+ const latest = pendingMessages[pendingMessages.length - 1];
630
+ runtime.lastDeliveredMessageAt = latest.createdAt;
631
+ runtime.lastDeliveredMessageId = latest.id;
632
+ }
633
+ runtime.taskStartedAt = Date.now();
634
+ return {
635
+ task: started,
636
+ memberId: runtime.member.id,
637
+ memberRole: runtime.role.name,
638
+ sessionId: runtime.member.sessionId,
639
+ prompt: buildTaskPrompt(runtime.role, started, pendingMessages)
640
+ };
641
+ }
642
+ async function executeTask(ctx, runtime, task, prompt) {
643
+ try {
644
+ const result = ctx.taskExecutor ? await ctx.taskExecutor({
645
+ runtime,
646
+ task,
647
+ prompt,
648
+ parentSessionId: ctx.leadSessionId,
649
+ abort: runtime.taskAbort?.signal
650
+ }) : await runtime.agent.run({
651
+ parentSessionId: ctx.leadSessionId,
652
+ message: prompt,
653
+ title: task.title,
654
+ abort: runtime.taskAbort?.signal
655
+ });
656
+ const taskResult = {
657
+ response: result.response,
658
+ usage: result.usage,
659
+ toolCalls: result.toolCalls
660
+ };
661
+ await completePreparedTask(ctx, runtime, task.id, taskResult);
662
+ } catch (err) {
663
+ const error = err instanceof Error ? err.message : String(err);
664
+ const wasAborted = runtime.taskAbort?.signal.aborted;
665
+ try {
666
+ if (wasAborted) {
667
+ await abortPreparedTask(
668
+ ctx,
669
+ runtime,
670
+ task.id,
671
+ String(runtime.taskAbort?.signal.reason ?? error)
672
+ );
673
+ return;
674
+ }
675
+ await failPreparedTask(ctx, runtime, task.id, error);
676
+ } catch (inner) {
677
+ ctx.log?.(`[${runtime.member.id}] task finalization error: ${inner instanceof Error ? inner.message : String(inner)}`);
678
+ }
679
+ } finally {
680
+ try {
681
+ await finalizePreparedTask(ctx, runtime, true);
682
+ } catch (cleanupErr) {
683
+ ctx.log?.(`[${runtime.member.id}] finalizePreparedTask error: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`);
684
+ runtime.activeRun = void 0;
685
+ runtime.taskAbort = void 0;
686
+ runtime.taskStartedAt = void 0;
687
+ try {
688
+ await ctx.updateMemberStatus(runtime.member.id, "idle", void 0);
689
+ } catch {
690
+ runtime.member.status = "idle";
691
+ }
692
+ }
693
+ }
694
+ }
695
+ async function completePreparedTask(ctx, runtime, taskId, taskResult) {
696
+ const latest = await beginTaskFinalization(ctx, runtime, taskId);
697
+ if (!latest) return void 0;
698
+ runtime.stats.tasksCompleted += 1;
699
+ runtime.stats.totalToolCalls += taskResult.toolCalls?.length ?? 0;
700
+ if (taskResult.usage) {
701
+ runtime.stats.totalTokens = {
702
+ inputTokens: (runtime.stats.totalTokens.inputTokens ?? 0) + (taskResult.usage.inputTokens ?? 0),
703
+ outputTokens: (runtime.stats.totalTokens.outputTokens ?? 0) + (taskResult.usage.outputTokens ?? 0),
704
+ totalTokens: (runtime.stats.totalTokens.totalTokens ?? 0) + (taskResult.usage.totalTokens ?? 0)
705
+ };
706
+ }
707
+ const { task, transitions } = await ctx.taskBoard.completeTask(taskId, taskResult);
708
+ await ctx.emit({
709
+ type: "team-task-completed",
710
+ teamId: ctx.teamId,
711
+ taskId,
712
+ memberId: runtime.member.id,
713
+ result: taskResult
714
+ });
715
+ await ctx.emitTransitions(transitions);
716
+ return { task, transitions };
717
+ }
718
+ async function failPreparedTask(ctx, runtime, taskId, error) {
719
+ const latest = await beginTaskFinalization(ctx, runtime, taskId);
720
+ if (!latest) return void 0;
721
+ runtime.stats.tasksFailed += 1;
722
+ const { task, transitions } = await ctx.taskBoard.failTask(taskId, error);
723
+ await ctx.emit({
724
+ type: "team-task-failed",
725
+ teamId: ctx.teamId,
726
+ taskId,
727
+ memberId: runtime.member.id,
728
+ error
729
+ });
730
+ await ctx.emitTransitions(transitions);
731
+ return { task, transitions };
732
+ }
733
+ async function abortPreparedTask(ctx, runtime, taskId, reason) {
734
+ const latest = await beginTaskFinalization(ctx, runtime, taskId);
735
+ if (!latest) return void 0;
736
+ const { task, transitions } = await ctx.taskBoard.abortTask(taskId, reason);
737
+ await ctx.emit({
738
+ type: "team-task-aborted",
739
+ teamId: ctx.teamId,
740
+ taskId,
741
+ memberId: runtime.member.id,
742
+ reason
743
+ });
744
+ await ctx.emitTransitions(transitions);
745
+ return { task, transitions };
746
+ }
747
+ function buildTaskPrompt(role, task, pendingMessages = []) {
748
+ const queuedMessages = pendingMessages.length > 0 ? "\n\nTeam messages to consider before you start:\n" + pendingMessages.map((message) => `- [${message.kind}] ${message.from}: ${message.body}`).join("\n") : "";
749
+ return `You are the "${role.name}" specialist.
750
+
751
+ Role: ${role.description}
752
+
753
+ Task: ${task.title}
754
+
755
+ ` + task.prompt + queuedMessages;
756
+ }
757
+ async function beginTaskFinalization(ctx, runtime, taskId) {
758
+ const duration = runtime.taskStartedAt ? Date.now() - runtime.taskStartedAt : 0;
759
+ runtime.stats.totalDurationMs += duration;
760
+ runtime.stats.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
761
+ const latest = await ctx.taskBoard.getTask(taskId);
762
+ if (!latest || isTerminalStatus(latest.status)) {
763
+ return void 0;
764
+ }
765
+ return latest;
766
+ }
767
+ async function finalizePreparedTask(ctx, runtime, clearAbortController) {
768
+ runtime.activeRun = void 0;
769
+ if (clearAbortController) {
770
+ runtime.taskAbort = void 0;
771
+ }
772
+ runtime.taskStartedAt = void 0;
773
+ if (runtime.stopRequested || runtime.member.status === "shutting-down") {
774
+ await ctx.updateMemberStatus(runtime.member.id, "offline", void 0);
775
+ return;
776
+ }
777
+ await ctx.updateMemberStatus(runtime.member.id, "idle", void 0);
778
+ if (ctx.taskDispatchMode === "manual") {
779
+ await ctx.dispatchReady();
780
+ }
781
+ }
782
+
783
+ // src/team/work-loop.ts
784
+ async function runWorkLoop(ctx, runtime) {
785
+ while (!runtime.stopRequested && ctx.started) {
786
+ if (runtime.member.status === "idle") {
787
+ if (ctx.beforeIteration) {
788
+ try {
789
+ const skip = await ctx.beforeIteration(
790
+ runtime.member.id,
791
+ runtime.agent,
792
+ runtime.stats
793
+ );
794
+ if (skip === false) {
795
+ await sleep(ctx.pollIntervalMs);
796
+ continue;
797
+ }
798
+ } catch (err) {
799
+ ctx.log?.(`[${runtime.member.id}] beforeIteration error: ${err instanceof Error ? err.message : String(err)}`);
800
+ await sleep(ctx.pollIntervalMs);
801
+ continue;
802
+ }
803
+ }
804
+ const task = await tryClaimAndExecute(ctx, runtime);
805
+ if (task) {
806
+ if (runtime.activeRun) {
807
+ try {
808
+ await runtime.activeRun;
809
+ } catch (err) {
810
+ ctx.log?.(`[${runtime.member.id}] work-loop: task error \u2014 ${err instanceof Error ? err.message : String(err)}`);
811
+ }
812
+ }
813
+ continue;
814
+ }
815
+ }
816
+ if (runtime.member.status === "busy" && runtime.activeRun) {
817
+ try {
818
+ await runtime.activeRun;
819
+ } catch (err) {
820
+ ctx.log?.(`[${runtime.member.id}] work-loop: active run error \u2014 ${err instanceof Error ? err.message : String(err)}`);
821
+ }
822
+ continue;
823
+ }
824
+ await sleep(ctx.pollIntervalMs);
825
+ }
826
+ if (runtime.member.status !== "offline") {
827
+ await ctx.updateMemberStatus(runtime.member.id, "offline", void 0);
828
+ }
829
+ }
830
+ async function tryClaimAndExecute(ctx, runtime) {
831
+ return claimAndExecute(ctx, runtime);
832
+ }
833
+
834
+ // src/team/shutdown.ts
835
+ async function requestShutdown(ctx, memberId, reason, timeoutMs) {
836
+ const runtime = ctx.members.get(memberId);
837
+ if (!runtime) {
838
+ throw new Error(
839
+ `Unknown member: "${memberId}". Registered: ${[...ctx.members.keys()].join(", ")}`
840
+ );
841
+ }
842
+ if (runtime.member.status === "offline") return true;
843
+ if (runtime.member.status === "shutting-down") {
844
+ return waitForOffline(runtime, timeoutMs);
845
+ }
846
+ const requestId = ctx.createId();
847
+ runtime.pendingShutdownId = requestId;
848
+ runtime.stopRequested = true;
849
+ await ctx.emit({
850
+ type: "team-shutdown-requested",
851
+ teamId: ctx.teamId,
852
+ memberId,
853
+ requestId,
854
+ reason
855
+ });
856
+ await ctx.mailbox.send({
857
+ teamId: ctx.teamId,
858
+ from: "coordinator",
859
+ to: memberId,
860
+ kind: "system",
861
+ body: reason ?? "Shutdown requested by coordinator.",
862
+ payload: {
863
+ kind: "shutdown-request",
864
+ requestId,
865
+ reason
866
+ },
867
+ metadata: { payloadKind: "shutdown-request", requestId }
868
+ });
869
+ if (runtime.member.status === "idle") {
870
+ await ctx.updateMemberStatus(memberId, "offline", void 0);
871
+ await emitShutdownResolved(ctx, memberId, requestId, true);
872
+ runtime.pendingShutdownId = void 0;
873
+ return true;
874
+ }
875
+ await ctx.updateMemberStatus(memberId, "shutting-down", runtime.member.activeTaskId);
876
+ const accepted = await waitForOffline(runtime, timeoutMs);
877
+ await emitShutdownResolved(ctx, memberId, requestId, accepted);
878
+ runtime.pendingShutdownId = void 0;
879
+ return accepted;
880
+ }
881
+ async function shutdownAll(ctx, reason, timeoutMs) {
882
+ const ids = [...ctx.members.keys()].filter((id) => {
883
+ const rt = ctx.members.get(id);
884
+ return rt && rt.member.status !== "offline";
885
+ });
886
+ await Promise.all(
887
+ ids.map((id) => requestShutdown(ctx, id, reason, timeoutMs))
888
+ );
889
+ }
890
+ async function waitForOffline(runtime, timeoutMs) {
891
+ const deadline = timeoutMs !== void 0 ? Date.now() + timeoutMs : void 0;
892
+ while (runtime.member.status !== "offline") {
893
+ if (deadline && Date.now() >= deadline) return false;
894
+ await sleep(100);
895
+ }
896
+ return true;
897
+ }
898
+ async function emitShutdownResolved(ctx, memberId, requestId, accepted) {
899
+ await ctx.emit({
900
+ type: "team-shutdown-resolved",
901
+ teamId: ctx.teamId,
902
+ memberId,
903
+ requestId,
904
+ accepted
905
+ });
906
+ }
907
+
908
+ // src/team/coordinator/turn.ts
909
+ import { z } from "zod";
910
+
911
+ // src/team/coordinator/policy.ts
912
+ var coordinatorToolDescriptions = {
913
+ assignTask(memberList) {
914
+ return `Assign a task to a teammate. The teammate receives ONLY the prompt you provide \u2014 include all necessary context, file paths, and instructions. Available teammates: ${memberList}`;
915
+ },
916
+ sendMessage: "Send guidance to a teammate. If they're working, the message is injected immediately. If they're idle, it's queued for their next task.",
917
+ abortTask: "Abort a running task without cascading to blocked dependents. The teammate returns to idle and can be assigned new work. Use this when a task is taking the wrong approach or should be restarted."
918
+ };
919
+ function buildCoordinatorSystemPrompt(members) {
920
+ const roster = members.map(
921
+ (member) => `- **${member.id}** (${member.role}): ${member.description ?? "General purpose agent"}`
922
+ ).join("\n");
923
+ return "You are the team coordinator. Your job is to break down tasks, delegate work to your teammates, synthesize their results, and communicate the final answer to the user.\n\n## Your Teammates\n\n" + roster + "\n\n## How It Works\n\n1. You receive the user's request.\n2. Decide what work to delegate. Use `assignTask` to give each teammate a focused, self-contained task.\n3. After teammates finish, you'll receive their results as [Task Notification] messages.\n4. Teammates may also send you [Worker Report] messages with mid-task findings or blockers.\n5. Based on results and reports, you may assign follow-up tasks, send guidance via `sendMessage`, or produce your final answer.\n\n## Guidelines\n\n- **Answer directly** when the question is simple and doesn't need tool use.\n- **Delegate** when work requires reading files, writing code, running tests, or exploration.\n- **After assigning tasks, briefly state what you assigned and end your response.** Do not wait for, predict, or fabricate task results \u2014 they will arrive as [Task Notification] messages.\n- **Write self-contained prompts.** Each teammate sees ONLY their task prompt. Include specific file paths, line numbers, and all context they need. Never say 'based on the research' \u2014 include the actual findings.\n- **Parallelize** independent tasks. Assign multiple teammates in one turn when their work doesn't depend on each other.\n- **Use dependencies** when tasks must run in order. Pass `dependsOn: [taskId]` to block a task until its prerequisites complete. Use the task IDs returned by previous assignTask calls.\n- **Serialize** dependent tasks. Wait for research results before assigning implementation work, or use `dependsOn` to chain them.\n- **Synthesize before acting.** When research results come back, read and understand them before directing follow-up work.\n- **Do not repeat completed work.** If a teammate has already completed a task and their result answers the need, synthesize it into your response instead of assigning the same task again.\n- **Only reassign after completion when there is a concrete gap.** If the result is missing, failed, clearly incomplete, or you need a different follow-up phase, explain that gap and assign a narrower next task.\n- **Teammates retain context.** If you assign the same teammate a second task, they remember their prior work. Use this for research-then-implement flows on the same teammate.\n- **On failure:** Review the error. If context is useful, assign the same teammate again. If the approach was wrong, assign a different teammate or try a new strategy.\n\n## Workflow Phases\n\nFor complex tasks, follow this pattern:\n1. **Research** \u2014 Explore the codebase, read files, understand the problem\n2. **Synthesize** \u2014 Read research results and form a concrete plan with specific files and changes\n3. **Implement** \u2014 Direct implementation with exact instructions including file paths and code\n4. **Verify** \u2014 Run tests, check the output, confirm the changes work\n\nWhen you have received all results and have nothing more to delegate, respond with your final comprehensive answer to the user.";
924
+ }
925
+ function formatCoordinatorTaskNotifications(notifications) {
926
+ if (notifications.length === 0) return "";
927
+ return notifications.map((notification) => {
928
+ let resultText = `Status: ${notification.status}`;
929
+ if (notification.status === "completed" && notification.result?.response) {
930
+ resultText = notification.result.response;
931
+ } else if (notification.status === "failed") {
932
+ resultText = `Error: ${notification.error ?? "Unknown error"}`;
933
+ } else if (notification.status === "aborted") {
934
+ resultText = `Aborted${notification.error ? `: ${notification.error}` : ""}`;
935
+ } else if (notification.error) {
936
+ resultText = notification.error;
937
+ }
938
+ return `[Task Notification]
939
+ Task: ${notification.taskId} (${notification.memberId})
940
+ Title: ${notification.title}
941
+ Status: ${notification.status}
942
+ Result:
943
+ ${resultText}`;
944
+ }).join("\n\n---\n\n");
945
+ }
946
+ function formatCoordinatorWorkerReports(reports) {
947
+ if (reports.length === 0) return "";
948
+ return reports.map(
949
+ (report) => `[Worker Report]
950
+ From: ${report.memberId}
951
+ ${report.taskId ? `Task: ${report.taskId}
952
+ ` : ""}Message:
953
+ ${report.message}`
954
+ ).join("\n\n---\n\n");
955
+ }
956
+ function formatCoordinatorRoundMessage(notifications, reports) {
957
+ const parts = [];
958
+ const taskText = formatCoordinatorTaskNotifications(notifications);
959
+ const reportText = formatCoordinatorWorkerReports(reports);
960
+ if (taskText) parts.push(taskText);
961
+ if (reportText) parts.push(reportText);
962
+ return parts.join("\n\n---\n\n");
963
+ }
964
+
965
+ // src/team/coordinator/turn.ts
966
+ var ASK_COORDINATOR_TIMEOUT_MS = 9e4;
967
+ function createAskCoordinatorTool(deps) {
968
+ const { memberId, teamId, runtime, mailbox } = deps;
969
+ return Tool.define("askCoordinator", {
970
+ description: "Ask the coordinator a question and wait for a response before continuing. Use this ONLY when you genuinely need guidance to proceed \u2014 for example, you've hit an ambiguity, discovered something that changes the task scope, or need clarification on requirements. Do NOT use this for progress updates; just continue working and report results when you finish.",
971
+ capabilities: { readOnly: true },
972
+ parameters: z.object({
973
+ question: z.string().describe(
974
+ "Your question for the coordinator \u2014 be specific about what you need to know"
975
+ )
976
+ }),
977
+ execute: async (params) => {
978
+ await mailbox.send({
979
+ teamId,
980
+ from: memberId,
981
+ to: "coordinator",
982
+ kind: "direct",
983
+ body: params.question,
984
+ payload: { kind: "worker-report" },
985
+ metadata: { payloadKind: "worker-report" }
986
+ });
987
+ const response = await new Promise((resolve) => {
988
+ const timeout = setTimeout(() => {
989
+ runtime.pendingAskResolve = void 0;
990
+ resolve("");
991
+ }, ASK_COORDINATOR_TIMEOUT_MS);
992
+ runtime.pendingAskResolve = (msg) => {
993
+ clearTimeout(timeout);
994
+ runtime.pendingAskResolve = void 0;
995
+ resolve(msg);
996
+ };
997
+ });
998
+ if (!response) {
999
+ return {
1000
+ title: "No response from coordinator",
1001
+ output: "The coordinator hasn't responded yet. Use your best judgment and continue working on the task.",
1002
+ metadata: { truncated: false }
1003
+ };
1004
+ }
1005
+ return {
1006
+ title: "Coordinator response",
1007
+ output: response,
1008
+ metadata: { truncated: false }
1009
+ };
1010
+ }
1011
+ });
1012
+ }
1013
+ function isAbortError(err) {
1014
+ if (err instanceof DOMException && err.name === "AbortError") return true;
1015
+ if (err instanceof Error && err.name === "AbortError") return true;
1016
+ if (err instanceof Error && err.message?.includes("aborted")) return true;
1017
+ return false;
1018
+ }
1019
+ async function* raceAbort(source, signal) {
1020
+ if (signal.aborted) return;
1021
+ const aborted = new Promise((resolve) => {
1022
+ signal.addEventListener(
1023
+ "abort",
1024
+ () => resolve({ done: true, value: void 0 }),
1025
+ { once: true }
1026
+ );
1027
+ });
1028
+ try {
1029
+ while (true) {
1030
+ const result = await Promise.race([source.next(), aborted]);
1031
+ if (result.done) return;
1032
+ yield result.value;
1033
+ }
1034
+ } finally {
1035
+ const cleanup = source.return(void 0);
1036
+ await Promise.race([
1037
+ cleanup,
1038
+ new Promise((resolve) => setTimeout(resolve, 5e3))
1039
+ ]).catch(() => {
1040
+ });
1041
+ }
1042
+ }
1043
+ function formatNotification(notification) {
1044
+ switch (notification.kind) {
1045
+ case "worker-report":
1046
+ return `[Worker Report]
1047
+ From: ${notification.memberId}
1048
+ ${notification.taskId ? `Task: ${notification.taskId}
1049
+ ` : ""}Message:
1050
+ ${notification.body}`;
1051
+ case "task-result":
1052
+ return `[Task Notification]
1053
+ Task: ${notification.taskId ?? "unknown"} (${notification.memberId})
1054
+ Title: ${notification.title}
1055
+ Status: ${notification.status ?? "unknown"}
1056
+ ${notification.body}`;
1057
+ default:
1058
+ return `[Coordinator Notification]
1059
+ From: ${notification.memberId}
1060
+ Kind: ${notification.kind}
1061
+ ${notification.body}`;
1062
+ }
1063
+ }
1064
+ function formatCoordinatorInboxBatch(items) {
1065
+ return items.map(
1066
+ (item) => item.kind === "user-message" ? item.body : formatNotification(item.notification ?? {
1067
+ id: item.id,
1068
+ teamId: "",
1069
+ memberId: "unknown",
1070
+ kind: "worker-report",
1071
+ priority: "immediate",
1072
+ title: "Notification",
1073
+ body: item.body,
1074
+ createdAt: item.createdAt
1075
+ })
1076
+ ).join("\n\n---\n\n");
1077
+ }
1078
+ function createCoordinatorTools(coordinator) {
1079
+ const memberIds = [...coordinator.members.keys()];
1080
+ const memberList = memberIds.join(", ");
1081
+ const assignTask = Tool.define("assignTask", {
1082
+ description: coordinatorToolDescriptions.assignTask(memberList),
1083
+ parameters: z.object({
1084
+ memberId: z.string().describe("Teammate ID to assign the task to"),
1085
+ title: z.string().describe("Short task title (2-6 words)"),
1086
+ prompt: z.string().describe(
1087
+ "Self-contained task prompt with all context the teammate needs"
1088
+ ),
1089
+ dependsOn: z.array(z.string()).optional().describe(
1090
+ "Task IDs this task must wait for before starting."
1091
+ )
1092
+ }),
1093
+ capabilities: { readOnly: true },
1094
+ execute: async (params) => {
1095
+ const state = coordinator.getActiveCoordinatorTurnState();
1096
+ const task = await coordinator.queue({
1097
+ memberId: params.memberId,
1098
+ title: params.title,
1099
+ prompt: params.prompt,
1100
+ dependsOn: params.dependsOn
1101
+ });
1102
+ state.actions.assigned.push({
1103
+ taskId: task.id,
1104
+ memberId: params.memberId,
1105
+ title: params.title
1106
+ });
1107
+ return {
1108
+ title: `Assigned to ${params.memberId}`,
1109
+ output: `Task ${task.id} assigned to ${params.memberId}: "${params.title}"`,
1110
+ metadata: {}
1111
+ };
1112
+ }
1113
+ });
1114
+ const sendMessage = Tool.define("sendMessage", {
1115
+ description: coordinatorToolDescriptions.sendMessage,
1116
+ parameters: z.object({
1117
+ memberId: z.string().describe("Teammate ID to message"),
1118
+ message: z.string().describe("Guidance or instructions to send")
1119
+ }),
1120
+ capabilities: { readOnly: true },
1121
+ execute: async (params) => {
1122
+ const state = coordinator.getActiveCoordinatorTurnState();
1123
+ await coordinator.guide(params.memberId, params.message);
1124
+ state.actions.sentMessages.push({ memberId: params.memberId });
1125
+ return {
1126
+ title: `Messaged ${params.memberId}`,
1127
+ output: `Message sent to ${params.memberId}`,
1128
+ metadata: {}
1129
+ };
1130
+ }
1131
+ });
1132
+ const abortTask = Tool.define("abortTask", {
1133
+ description: coordinatorToolDescriptions.abortTask,
1134
+ parameters: z.object({
1135
+ taskId: z.string().describe("ID of the task to abort"),
1136
+ reason: z.string().optional().describe("Why the task is being aborted")
1137
+ }),
1138
+ capabilities: { readOnly: true },
1139
+ execute: async (params) => {
1140
+ const state = coordinator.getActiveCoordinatorTurnState();
1141
+ await coordinator.abort(params.taskId, params.reason);
1142
+ state.actions.abortedTaskIds.push(params.taskId);
1143
+ return {
1144
+ title: "Task aborted",
1145
+ output: `Task ${params.taskId} aborted${params.reason ? `: ${params.reason}` : ""}`,
1146
+ metadata: {}
1147
+ };
1148
+ }
1149
+ });
1150
+ return [assignTask, sendMessage, abortTask];
1151
+ }
1152
+ function createCoordinatorAgent(coordinator) {
1153
+ return coordinator.lead.fork({
1154
+ name: "coordinator",
1155
+ systemPrompt: buildCoordinatorSystemPrompt(
1156
+ [...coordinator.members.values()].map((runtime) => ({
1157
+ id: runtime.member.id,
1158
+ role: runtime.role.name,
1159
+ description: runtime.role.description
1160
+ }))
1161
+ ),
1162
+ tools: createCoordinatorTools(coordinator),
1163
+ reasoningLevel: "off",
1164
+ maxSteps: 1
1165
+ });
1166
+ }
1167
+ async function runCoordinatorTurn(coordinator, agent, sessionId, message, options) {
1168
+ const log = options?.onLog ?? (() => {
1169
+ });
1170
+ const onEvent = options?.onEvent;
1171
+ const onStatus = options?.onStatus;
1172
+ const roundAbort = new AbortController();
1173
+ const state = coordinator.beginCoordinatorTurnState();
1174
+ let response = "";
1175
+ let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1176
+ const toolCalls = [];
1177
+ let hasDelegated = false;
1178
+ let inactivityTimer = null;
1179
+ const INACTIVITY_MS_NORMAL = 9e4;
1180
+ const INACTIVITY_MS_DELEGATED = 15e3;
1181
+ const clearTimers = () => {
1182
+ if (inactivityTimer) clearTimeout(inactivityTimer);
1183
+ inactivityTimer = null;
1184
+ };
1185
+ const resetInactivityTimer = () => {
1186
+ if (inactivityTimer) clearTimeout(inactivityTimer);
1187
+ const ms = hasDelegated ? INACTIVITY_MS_DELEGATED : INACTIVITY_MS_NORMAL;
1188
+ inactivityTimer = setTimeout(() => {
1189
+ log(`Aborting coordinator stream after ${ms / 1e3}s inactivity (delegated=${hasDelegated})`);
1190
+ roundAbort.abort();
1191
+ }, ms);
1192
+ };
1193
+ coordinator.attachCoordinatorTurn(agent, sessionId);
1194
+ coordinator.setCoordinatorTurnOpen(true);
1195
+ resetInactivityTimer();
1196
+ log(`runCoordinatorTurn: start (msg=${message.length}ch)`);
1197
+ try {
1198
+ onStatus?.({
1199
+ phase: "running",
1200
+ message: "Coordinator processing inbox...",
1201
+ pendingInboxCount: coordinator.getCoordinatorInboxSize(),
1202
+ activeTaskCount: await coordinator.getActiveTaskCount()
1203
+ });
1204
+ for await (const event of raceAbort(agent.chat(sessionId, message, { abort: roundAbort.signal }), roundAbort.signal)) {
1205
+ resetInactivityTimer();
1206
+ if (event.type !== "text-delta" && event.type !== "reasoning-delta") {
1207
+ const detail = event.type === "tool-start" ? ` ${event.toolName}` : event.type === "tool-result" ? ` ${event.toolName}` : event.type === "error" ? ` ${event.error?.message?.slice(0, 80)}` : event.type === "status" ? ` ${event.status}` : "";
1208
+ log(`stream: ${event.type}${detail}`);
1209
+ }
1210
+ const isMaxStepsNoise = event.type === "status" && event.status === "error" && hasDelegated || event.type === "error" && event.error?.message?.includes("Maximum steps");
1211
+ const isAbortNoise = roundAbort.signal.aborted && (event.type === "status" && event.status === "error" || event.type === "error" && isAbortError(event.error));
1212
+ if (onEvent && !isMaxStepsNoise && !isAbortNoise) {
1213
+ onEvent(event);
1214
+ }
1215
+ if (event.type === "tool-start") {
1216
+ const isCoordinatorTool = ["assignTask", "sendMessage", "abortTask"].includes(event.toolName);
1217
+ if (isCoordinatorTool) {
1218
+ hasDelegated = true;
1219
+ resetInactivityTimer();
1220
+ }
1221
+ }
1222
+ switch (event.type) {
1223
+ case "text-delta":
1224
+ response += event.text;
1225
+ break;
1226
+ case "tool-result":
1227
+ toolCalls.push({ name: event.toolName, result: event.result });
1228
+ break;
1229
+ case "step-finish":
1230
+ if (event.usage) usage = event.usage;
1231
+ break;
1232
+ case "complete":
1233
+ if (event.usage) usage = event.usage;
1234
+ if (event.output !== void 0) response = event.output;
1235
+ break;
1236
+ case "error":
1237
+ if (event.error?.message?.includes("Maximum steps")) {
1238
+ break;
1239
+ }
1240
+ if (isAbortError(event.error) && roundAbort.signal.aborted) {
1241
+ break;
1242
+ }
1243
+ throw event.error;
1244
+ }
1245
+ }
1246
+ } catch (error) {
1247
+ if (!(isAbortError(error) && roundAbort.signal.aborted)) {
1248
+ log(`runCoordinatorTurn: error \u2014 ${error instanceof Error ? error.message : String(error)}`);
1249
+ throw error;
1250
+ }
1251
+ log(`runCoordinatorTurn: aborted (expected)`);
1252
+ } finally {
1253
+ clearTimers();
1254
+ coordinator.setCoordinatorTurnOpen(false);
1255
+ if (roundAbort.signal.aborted) {
1256
+ agent.clearSessionLock(sessionId);
1257
+ }
1258
+ coordinator.endCoordinatorTurnState();
1259
+ }
1260
+ log(`runCoordinatorTurn: done \u2014 delegated=${hasDelegated} response=${response.length}ch assigned=${state.actions.assigned.length} msgs=${state.actions.sentMessages.length}`);
1261
+ return {
1262
+ response,
1263
+ usage,
1264
+ toolCalls,
1265
+ delegated: hasDelegated,
1266
+ actions: {
1267
+ assigned: [...state.actions.assigned],
1268
+ sentMessages: [...state.actions.sentMessages],
1269
+ abortedTaskIds: [...state.actions.abortedTaskIds]
1270
+ }
1271
+ };
1272
+ }
1273
+
1274
+ // src/team/notifications.ts
1275
+ function formatTaskBody(task, fallbackStatus, fallbackDetail) {
1276
+ const title = task?.title || "Untitled task";
1277
+ const detail = task?.status === "completed" && task.result?.response ? task.result.response : task?.status === "failed" ? task.error ?? fallbackDetail : task?.status === "aborted" ? task.error ?? fallbackDetail : task?.status === "cancelled" ? task.error ?? fallbackDetail : fallbackDetail;
1278
+ return `Title: ${title}
1279
+ Status: ${task?.status ?? fallbackStatus}
1280
+ Result:
1281
+ ${detail}`;
1282
+ }
1283
+ function buildWorkerReportNotification(event) {
1284
+ if (event.message.payload?.kind !== "worker-report") {
1285
+ return null;
1286
+ }
1287
+ return {
1288
+ type: "team-notification",
1289
+ teamId: event.teamId,
1290
+ notification: {
1291
+ id: `worker-report:${event.message.id}`,
1292
+ teamId: event.teamId,
1293
+ memberId: event.message.from,
1294
+ kind: "worker-report",
1295
+ priority: "immediate",
1296
+ title: "Worker report",
1297
+ body: event.message.body,
1298
+ createdAt: event.message.createdAt,
1299
+ taskId: event.message.taskId
1300
+ }
1301
+ };
1302
+ }
1303
+ function buildPermissionForwardedNotification(event) {
1304
+ return {
1305
+ type: "team-notification",
1306
+ teamId: event.teamId,
1307
+ notification: {
1308
+ id: `permission-forwarded:${event.requestId}`,
1309
+ teamId: event.teamId,
1310
+ memberId: event.memberId,
1311
+ kind: "permission-forwarded",
1312
+ priority: "immediate",
1313
+ title: `Permission request \xB7 ${event.tool}`,
1314
+ body: `Requested approval for "${event.tool}".`,
1315
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1316
+ }
1317
+ };
1318
+ }
1319
+ function buildPermissionResolvedNotification(event) {
1320
+ return {
1321
+ type: "team-notification",
1322
+ teamId: event.teamId,
1323
+ notification: {
1324
+ id: `permission-resolved:${event.requestId}:${event.decision}`,
1325
+ teamId: event.teamId,
1326
+ memberId: event.memberId,
1327
+ kind: "permission-resolved",
1328
+ priority: "immediate",
1329
+ title: `Permission ${event.decision}`,
1330
+ body: event.decision === "allow" ? "Coordinator approved the pending tool request." : "Coordinator denied the pending tool request.",
1331
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1332
+ }
1333
+ };
1334
+ }
1335
+ function buildShutdownRequestedNotification(event) {
1336
+ return {
1337
+ type: "team-notification",
1338
+ teamId: event.teamId,
1339
+ notification: {
1340
+ id: `shutdown-requested:${event.requestId}`,
1341
+ teamId: event.teamId,
1342
+ memberId: event.memberId,
1343
+ kind: "shutdown-requested",
1344
+ priority: "immediate",
1345
+ title: "Shutdown requested",
1346
+ body: event.reason ? `Shutdown requested. Reason: ${event.reason}` : "Shutdown requested.",
1347
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1348
+ }
1349
+ };
1350
+ }
1351
+ function buildShutdownResolvedNotification(event) {
1352
+ return {
1353
+ type: "team-notification",
1354
+ teamId: event.teamId,
1355
+ notification: {
1356
+ id: `shutdown-resolved:${event.requestId}:${event.accepted ? "accepted" : "rejected"}`,
1357
+ teamId: event.teamId,
1358
+ memberId: event.memberId,
1359
+ kind: "shutdown-resolved",
1360
+ priority: "immediate",
1361
+ title: event.accepted ? "Shutdown accepted" : "Shutdown deferred",
1362
+ body: event.accepted ? "Member accepted shutdown and went offline." : "Member did not complete shutdown within the requested window.",
1363
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1364
+ }
1365
+ };
1366
+ }
1367
+ async function buildTaskResultNotification(event, resolveTask) {
1368
+ const task = await resolveTask(event.taskId);
1369
+ const detail = event.type === "team-task-completed" ? event.result.response : event.type === "team-task-failed" ? event.error : event.reason ?? "Aborted";
1370
+ const status = event.type === "team-task-completed" ? "completed" : event.type === "team-task-failed" ? "failed" : "aborted";
1371
+ const priority = status === "completed" ? "deferred" : "immediate";
1372
+ const memberId = event.memberId ?? task?.memberId ?? "worker";
1373
+ return {
1374
+ type: "team-notification",
1375
+ teamId: event.teamId,
1376
+ notification: {
1377
+ id: `task-result:${event.taskId}:${status}:${task?.updatedAt ?? "unknown"}`,
1378
+ teamId: event.teamId,
1379
+ memberId,
1380
+ kind: "task-result",
1381
+ priority,
1382
+ title: task?.title ?? "Task result",
1383
+ body: formatTaskBody(task, status, detail),
1384
+ createdAt: task?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1385
+ taskId: event.taskId,
1386
+ status
1387
+ }
1388
+ };
1389
+ }
1390
+ async function buildCoordinatorNotificationEvent(event, resolveTask) {
1391
+ switch (event.type) {
1392
+ case "team-task-completed":
1393
+ case "team-task-failed":
1394
+ case "team-task-aborted":
1395
+ return buildTaskResultNotification(event, resolveTask);
1396
+ case "team-message":
1397
+ return buildWorkerReportNotification(event);
1398
+ case "team-permission-forwarded":
1399
+ return buildPermissionForwardedNotification(event);
1400
+ case "team-permission-resolved":
1401
+ return buildPermissionResolvedNotification(event);
1402
+ case "team-shutdown-requested":
1403
+ return buildShutdownRequestedNotification(event);
1404
+ case "team-shutdown-resolved":
1405
+ return buildShutdownResolvedNotification(event);
1406
+ default:
1407
+ return null;
1408
+ }
1409
+ }
1410
+
1411
+ // src/team/coordinator/inbox.ts
1412
+ var MAX_CONSECUTIVE_TURNS = 20;
1413
+ var MAX_AUTONOMOUS_TURNS = 30;
1414
+ function enqueueInboxItem(ctx, item) {
1415
+ ctx.coordinatorInbox.push(item);
1416
+ ctx.publishCoordinatorStatus({
1417
+ pendingInboxCount: ctx.coordinatorInbox.length
1418
+ });
1419
+ }
1420
+ function enqueueNotification(ctx, notification) {
1421
+ if (notification.kind !== "worker-report" && notification.kind !== "task-result" && notification.kind !== "shutdown-requested" && notification.kind !== "shutdown-resolved") {
1422
+ return;
1423
+ }
1424
+ enqueueInboxItem(ctx, {
1425
+ id: notification.id,
1426
+ kind: "notification",
1427
+ createdAt: notification.createdAt,
1428
+ body: notification.body,
1429
+ notification
1430
+ });
1431
+ }
1432
+ function scheduleProcessing(ctx) {
1433
+ if (!ctx.started) {
1434
+ return;
1435
+ }
1436
+ if (ctx.coordinatorProcessing) {
1437
+ ctx.coordinatorReschedule = true;
1438
+ return;
1439
+ }
1440
+ ctx.coordinatorProcessing = true;
1441
+ void processInbox(ctx).catch((err) => {
1442
+ const msg = err instanceof Error ? err.message : String(err);
1443
+ ctx.log(`processInbox: unhandled error \u2014 ${msg}`);
1444
+ ctx.coordinatorProcessing = false;
1445
+ ctx.publishCoordinatorStatus({
1446
+ phase: "error",
1447
+ message: msg,
1448
+ activeTaskCount: 0
1449
+ });
1450
+ ctx.publishCoordinatorEvent({
1451
+ type: "error",
1452
+ error: err instanceof Error ? err : new Error(msg)
1453
+ });
1454
+ });
1455
+ }
1456
+ async function processInbox(ctx) {
1457
+ try {
1458
+ const agent = ctx.ensureCoordinatorAgent();
1459
+ const sessionId = ctx.getCoordinatorSessionId();
1460
+ let consecutiveTurns = 0;
1461
+ ctx.log(`processInbox: starting drain (inbox=${ctx.coordinatorInbox.length}, processing=${ctx.coordinatorProcessing})`);
1462
+ while (ctx.started) {
1463
+ if (consecutiveTurns >= MAX_CONSECUTIVE_TURNS) {
1464
+ ctx.log(`Inbox processor yielding after ${consecutiveTurns} consecutive turns`);
1465
+ break;
1466
+ }
1467
+ const batch = ctx.coordinatorInbox.splice(0);
1468
+ ctx.publishCoordinatorStatus({
1469
+ pendingInboxCount: ctx.coordinatorInbox.length
1470
+ });
1471
+ if (batch.length === 0) {
1472
+ const activeTaskCount = await ctx.getActiveTaskCount();
1473
+ const nextPhase = activeTaskCount > 0 ? "waiting" : "ready";
1474
+ ctx.log(`processInbox: empty batch \u2192 phase=${nextPhase} (activeTasks=${activeTaskCount})`);
1475
+ ctx.publishCoordinatorStatus({
1476
+ phase: nextPhase,
1477
+ message: activeTaskCount > 0 ? `Waiting on ${activeTaskCount} task${activeTaskCount === 1 ? "" : "s"}` : "Ready",
1478
+ activeTaskCount
1479
+ });
1480
+ break;
1481
+ }
1482
+ ctx.log(`processInbox: batch of ${batch.length} items (${batch.map((i) => i.kind).join(", ")})`);
1483
+ const hasUser = batch.some((item) => item.kind === "user-message");
1484
+ const hasNotification = batch.some((item) => item.kind === "notification");
1485
+ let turnBatch;
1486
+ if (hasUser && hasNotification) {
1487
+ turnBatch = batch.filter((item) => item.kind === "user-message");
1488
+ const deferred = batch.filter((item) => item.kind !== "user-message");
1489
+ ctx.coordinatorInbox.unshift(...deferred);
1490
+ ctx.publishCoordinatorStatus({
1491
+ pendingInboxCount: ctx.coordinatorInbox.length
1492
+ });
1493
+ } else {
1494
+ turnBatch = batch;
1495
+ }
1496
+ if (hasUser) {
1497
+ ctx.autonomousTurnCount = 0;
1498
+ } else {
1499
+ ctx.autonomousTurnCount += 1;
1500
+ if (ctx.autonomousTurnCount > MAX_AUTONOMOUS_TURNS) {
1501
+ ctx.log(
1502
+ `Coordinator yielding after ${ctx.autonomousTurnCount} autonomous turns \u2014 waiting for user guidance`
1503
+ );
1504
+ ctx.coordinatorInbox.unshift(...turnBatch);
1505
+ const activeTaskCount = await ctx.getActiveTaskCount();
1506
+ ctx.publishCoordinatorStatus({
1507
+ phase: "ready",
1508
+ message: "Autonomous turn limit reached \u2014 send a message to continue",
1509
+ activeTaskCount,
1510
+ pendingInboxCount: ctx.coordinatorInbox.length
1511
+ });
1512
+ break;
1513
+ }
1514
+ }
1515
+ const firstUser = turnBatch.find((item) => item.kind === "user-message");
1516
+ const activeTaskCountBeforeTurn = await ctx.getActiveTaskCount();
1517
+ ctx.publishCoordinatorStatus({
1518
+ phase: "running",
1519
+ message: firstUser ? "Coordinator processing user guidance..." : "Coordinator processing team updates...",
1520
+ activeTaskCount: activeTaskCountBeforeTurn,
1521
+ pendingInboxCount: ctx.coordinatorInbox.length
1522
+ });
1523
+ const message = formatCoordinatorInboxBatch(turnBatch);
1524
+ ctx.log(`processInbox: running coordinator turn #${consecutiveTurns + 1} (autonomousTurns=${ctx.autonomousTurnCount})`);
1525
+ let turnAssignedCount = 0;
1526
+ let turnResponseLength = 0;
1527
+ try {
1528
+ const turn = await runCoordinatorTurn(
1529
+ // The turn runner expects the full TeamCoordinator for turn
1530
+ // state management. The InboxContext is a superset at runtime
1531
+ // since the coordinator implements it.
1532
+ ctx,
1533
+ agent,
1534
+ sessionId,
1535
+ message,
1536
+ {
1537
+ onEvent: (event) => ctx.publishCoordinatorEvent(event),
1538
+ onStatus: (status) => ctx.publishCoordinatorStatus(status),
1539
+ onLog: (msg) => ctx.log(msg)
1540
+ }
1541
+ );
1542
+ const completionEntries = [];
1543
+ for (const item of turnBatch) {
1544
+ const taskId = item.notification?.kind === "task-result" ? item.notification.taskId : void 0;
1545
+ if (!taskId) {
1546
+ continue;
1547
+ }
1548
+ const task = await ctx.getTask(taskId);
1549
+ if (!task) {
1550
+ continue;
1551
+ }
1552
+ completionEntries.push({
1553
+ task,
1554
+ result: task.result
1555
+ });
1556
+ }
1557
+ const reportEntries = turnBatch.filter((item) => item.notification?.kind === "worker-report").map((item) => ({
1558
+ messageId: item.notification?.id ?? item.id,
1559
+ memberId: item.notification?.memberId ?? "worker",
1560
+ taskId: item.notification?.taskId,
1561
+ createdAt: item.notification?.createdAt ?? item.createdAt,
1562
+ message: item.notification?.body ?? item.body
1563
+ }));
1564
+ ctx.coordinatorRoundNumber += 1;
1565
+ await ctx.emit({
1566
+ type: "team-coordinator-round",
1567
+ teamId: ctx.teamId,
1568
+ roundNumber: ctx.coordinatorRoundNumber,
1569
+ assigned: turn.actions.assigned,
1570
+ messaged: turn.actions.sentMessages,
1571
+ stopped: turn.actions.abortedTaskIds,
1572
+ completions: completionEntries,
1573
+ reports: reportEntries,
1574
+ response: turn.response,
1575
+ usage: turn.usage
1576
+ });
1577
+ ctx.publishCoordinatorEvent({
1578
+ type: "complete",
1579
+ output: turn.response,
1580
+ usage: turn.usage
1581
+ });
1582
+ turnAssignedCount = turn.actions.assigned.length;
1583
+ turnResponseLength = turn.response.length;
1584
+ consecutiveTurns += 1;
1585
+ } catch (error) {
1586
+ const messageText = error instanceof Error ? error.message : String(error);
1587
+ ctx.log(`processInbox: turn error \u2014 ${messageText}`);
1588
+ ctx.publishCoordinatorStatus({
1589
+ phase: "error",
1590
+ message: messageText,
1591
+ activeTaskCount: await ctx.getActiveTaskCount()
1592
+ });
1593
+ ctx.publishCoordinatorEvent({
1594
+ type: "error",
1595
+ error: error instanceof Error ? error : new Error(messageText)
1596
+ });
1597
+ break;
1598
+ }
1599
+ const activeTaskCountAfterTurn = await ctx.getActiveTaskCount();
1600
+ const afterPhase = activeTaskCountAfterTurn > 0 ? "waiting" : "ready";
1601
+ ctx.log(`processInbox: turn done \u2014 assigned=${turnAssignedCount} response=${turnResponseLength > 0 ? turnResponseLength + "ch" : "(empty)"} \u2192 phase=${afterPhase} (activeTasks=${activeTaskCountAfterTurn}, inbox=${ctx.coordinatorInbox.length})`);
1602
+ ctx.publishCoordinatorStatus({
1603
+ phase: afterPhase,
1604
+ message: activeTaskCountAfterTurn > 0 ? `Waiting on ${activeTaskCountAfterTurn} task${activeTaskCountAfterTurn === 1 ? "" : "s"}` : "Ready",
1605
+ activeTaskCount: activeTaskCountAfterTurn,
1606
+ pendingInboxCount: ctx.coordinatorInbox.length
1607
+ });
1608
+ if (ctx.coordinatorInbox.length === 0 && activeTaskCountAfterTurn > 0) {
1609
+ ctx.log(`processInbox: yielding \u2014 waiting on ${activeTaskCountAfterTurn} task(s)`);
1610
+ break;
1611
+ }
1612
+ }
1613
+ } finally {
1614
+ ctx.coordinatorProcessing = false;
1615
+ if (ctx.coordinatorReschedule || ctx.coordinatorInbox.length > 0) {
1616
+ ctx.log(`processInbox: rescheduling (reschedule=${ctx.coordinatorReschedule}, inbox=${ctx.coordinatorInbox.length})`);
1617
+ ctx.coordinatorReschedule = false;
1618
+ ctx.scheduleCoordinatorProcessing();
1619
+ } else {
1620
+ ctx.log(`processInbox: idle \u2014 resolving waiters`);
1621
+ ctx.resolveCoordinatorIdleWaiters();
1622
+ }
1623
+ }
1624
+ }
1625
+
1626
+ // src/team/coordinator/planning.ts
1627
+ async function planTasks(coordinator, prompt, memberIds) {
1628
+ const targets = memberIds ? memberIds.map((id) => {
1629
+ const rt = coordinator.members.get(id);
1630
+ if (!rt) {
1631
+ throw new Error(
1632
+ `Unknown member: "${id}". Registered: ${[...coordinator.members.keys()].join(", ")}`
1633
+ );
1634
+ }
1635
+ return rt;
1636
+ }) : [...coordinator.members.values()];
1637
+ if (targets.length === 0) {
1638
+ throw new Error("No members available for planning");
1639
+ }
1640
+ const memberDescriptions = targets.map(
1641
+ (rt) => `- ${rt.member.id} (${rt.role.name}): ${rt.role.description ?? "General purpose"}`
1642
+ ).join("\n");
1643
+ const planningPrompt = "You are the team lead. Decompose the following task into targeted sub-tasks for your teammates.\n\nAvailable teammates:\n" + memberDescriptions + "\n\nUser request:\n" + prompt + `
1644
+
1645
+ Create a focused, self-contained task prompt for each relevant teammate. Each prompt must include all necessary context \u2014 teammates cannot see the original request or each other's tasks.
1646
+
1647
+ Respond with ONLY a JSON array (no markdown fences, no explanation):
1648
+ [
1649
+ { "memberId": "id", "title": "short title", "prompt": "detailed self-contained prompt" }
1650
+ ]
1651
+
1652
+ You may omit teammates that are not needed for this task.`;
1653
+ const result = await coordinator.lead.send(coordinator.leadSessionId, planningPrompt);
1654
+ const tasks = parsePlanResponse(result.response, prompt, targets);
1655
+ await coordinator.emit({
1656
+ type: "team-plan-created",
1657
+ teamId: coordinator.teamId,
1658
+ tasks
1659
+ });
1660
+ return { tasks, usage: result.usage };
1661
+ }
1662
+ async function planAndExecuteTasks(coordinator, prompt, memberIds) {
1663
+ const teamPlan = await planTasks(coordinator, prompt, memberIds);
1664
+ const planIndexToTaskId = /* @__PURE__ */ new Map();
1665
+ const tasks = [];
1666
+ for (let i = 0; i < teamPlan.tasks.length; i++) {
1667
+ const planned = teamPlan.tasks[i];
1668
+ let resolvedDeps;
1669
+ if (planned.dependsOn && planned.dependsOn.length > 0) {
1670
+ resolvedDeps = [];
1671
+ for (const dep of planned.dependsOn) {
1672
+ const idx = Number(dep);
1673
+ if (!Number.isNaN(idx) && planIndexToTaskId.has(idx)) {
1674
+ resolvedDeps.push(planIndexToTaskId.get(idx));
1675
+ continue;
1676
+ }
1677
+ const match = tasks.find(
1678
+ (t) => t.title === dep || t.memberId === dep || t.id === dep
1679
+ );
1680
+ if (match) {
1681
+ resolvedDeps.push(match.id);
1682
+ }
1683
+ }
1684
+ if (resolvedDeps.length === 0) resolvedDeps = void 0;
1685
+ }
1686
+ const task = await coordinator.queue({
1687
+ memberId: planned.memberId,
1688
+ title: planned.title,
1689
+ prompt: planned.prompt,
1690
+ dependsOn: resolvedDeps
1691
+ });
1692
+ tasks.push(task);
1693
+ planIndexToTaskId.set(i, task.id);
1694
+ }
1695
+ return { plan: teamPlan, tasks };
1696
+ }
1697
+ function parsePlanResponse(response, originalPrompt, targets) {
1698
+ const validIds = new Set(targets.map((t) => t.member.id));
1699
+ let jsonStr = response.trim();
1700
+ const codeBlockMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
1701
+ if (codeBlockMatch) {
1702
+ jsonStr = codeBlockMatch[1].trim();
1703
+ }
1704
+ const arrayMatch = jsonStr.match(/\[[\s\S]*\]/);
1705
+ if (arrayMatch) {
1706
+ jsonStr = arrayMatch[0];
1707
+ }
1708
+ let parsed;
1709
+ try {
1710
+ parsed = JSON.parse(jsonStr);
1711
+ } catch {
1712
+ return targets.map((rt) => ({
1713
+ memberId: rt.member.id,
1714
+ title: `task:${rt.role.name}`,
1715
+ prompt: originalPrompt
1716
+ }));
1717
+ }
1718
+ if (!Array.isArray(parsed) || parsed.length === 0) {
1719
+ return targets.map((rt) => ({
1720
+ memberId: rt.member.id,
1721
+ title: `task:${rt.role.name}`,
1722
+ prompt: originalPrompt
1723
+ }));
1724
+ }
1725
+ const tasks = [];
1726
+ for (const item of parsed) {
1727
+ if (typeof item !== "object" || item === null) continue;
1728
+ const obj = item;
1729
+ const memberId = obj.memberId;
1730
+ const prompt = obj.prompt;
1731
+ if (typeof memberId !== "string" || typeof prompt !== "string") continue;
1732
+ if (!validIds.has(memberId)) continue;
1733
+ tasks.push({
1734
+ memberId,
1735
+ title: typeof obj.title === "string" ? obj.title : `task:${memberId}`,
1736
+ prompt,
1737
+ dependsOn: Array.isArray(obj.dependsOn) ? obj.dependsOn.filter((d) => typeof d === "string") : void 0
1738
+ });
1739
+ }
1740
+ if (tasks.length === 0) {
1741
+ return targets.map((rt) => ({
1742
+ memberId: rt.member.id,
1743
+ title: `task:${rt.role.name}`,
1744
+ prompt: originalPrompt
1745
+ }));
1746
+ }
1747
+ return tasks;
1748
+ }
1749
+
1750
+ // src/team/coordinator/synthesis.ts
1751
+ async function synthesizeResults(coordinator, originalPrompt, taskIds) {
1752
+ const allTasks = await coordinator.taskBoard.listTasks();
1753
+ const terminal = (taskIds ? allTasks.filter((t) => taskIds.includes(t.id)) : allTasks).filter(
1754
+ (t) => t.status === "completed" || t.status === "aborted" || t.status === "failed" || t.status === "cancelled"
1755
+ );
1756
+ if (terminal.length === 0) {
1757
+ throw new Error("No completed tasks to synthesize");
1758
+ }
1759
+ const sections = terminal.map((task) => {
1760
+ const runtime = coordinator.members.get(task.memberId ?? "");
1761
+ const name = runtime?.role.name ?? task.memberId ?? "unassigned";
1762
+ if (task.status === "completed" && task.result) {
1763
+ return `## ${name}
1764
+ ${task.result.response}`;
1765
+ }
1766
+ return `## ${name}
1767
+ Status: ${task.status}
1768
+ ${task.error ?? "No result."}`;
1769
+ });
1770
+ const synthesisPrompt = `You are the team lead. Synthesize the findings from your teammates into one clear, comprehensive answer.
1771
+
1772
+ Original task:
1773
+ ${originalPrompt}
1774
+
1775
+ Teammate results:
1776
+
1777
+ ` + sections.join("\n\n");
1778
+ const result = await coordinator.lead.send(coordinator.leadSessionId, synthesisPrompt);
1779
+ await coordinator.emit({
1780
+ type: "team-synthesis",
1781
+ teamId: coordinator.teamId,
1782
+ response: result.response
1783
+ });
1784
+ return result;
1785
+ }
1786
+
1787
+ // src/team/coordinator/coordinator.ts
1788
+ var TeamCoordinator = class {
1789
+ teamId;
1790
+ /** @internal */
1791
+ lead;
1792
+ /** @internal */
1793
+ leadSessionId;
1794
+ /** @internal */
1795
+ createId;
1796
+ roles = /* @__PURE__ */ new Map();
1797
+ /** @internal */
1798
+ members = /* @__PURE__ */ new Map();
1799
+ /** @internal */
1800
+ taskBoard;
1801
+ /** @internal */
1802
+ mailbox;
1803
+ onEvent;
1804
+ eventListeners = /* @__PURE__ */ new Set();
1805
+ signal;
1806
+ /** @internal */
1807
+ workLoopEnabled;
1808
+ /** @internal */
1809
+ taskDispatchMode;
1810
+ /** @internal */
1811
+ pollIntervalMs;
1812
+ /** @internal */
1813
+ onPermissionRequest;
1814
+ /** @internal */
1815
+ beforeIteration;
1816
+ /** @internal */
1817
+ sharedPermissions;
1818
+ /** @internal */
1819
+ taskExecutor;
1820
+ externalTaskControl;
1821
+ /** @internal */
1822
+ started = false;
1823
+ onLog;
1824
+ logger;
1825
+ activeCoordinatorTurn;
1826
+ mailboxUnsubscribe;
1827
+ coordinatorAgent;
1828
+ coordinatorSessionId;
1829
+ /** @internal */
1830
+ coordinatorInbox = [];
1831
+ /** @internal */
1832
+ coordinatorProcessing = false;
1833
+ /** @internal */
1834
+ coordinatorReschedule = false;
1835
+ coordinatorStatus = {
1836
+ phase: "ready",
1837
+ message: "Ready",
1838
+ pendingInboxCount: 0,
1839
+ activeTaskCount: 0
1840
+ };
1841
+ coordinatorEventListeners = /* @__PURE__ */ new Set();
1842
+ coordinatorStatusListeners = /* @__PURE__ */ new Set();
1843
+ coordinatorIdleResolvers = [];
1844
+ /** @internal */
1845
+ coordinatorRoundNumber = 0;
1846
+ coordinatorRunSequence = 0;
1847
+ coordinatorTurnState = null;
1848
+ /** @internal Structured log output — writes to both logger and onLog when provided. */
1849
+ log(msg) {
1850
+ const tagged = `[team] ${msg}`;
1851
+ this.logger?.info(tagged);
1852
+ this.onLog?.(tagged);
1853
+ }
1854
+ async dispatchEvent(event) {
1855
+ if (this.signal) {
1856
+ try {
1857
+ this.signal.emit(event);
1858
+ } catch {
1859
+ }
1860
+ }
1861
+ for (const listener of this.eventListeners) {
1862
+ try {
1863
+ await listener(event);
1864
+ } catch {
1865
+ }
1866
+ }
1867
+ if (this.onEvent) {
1868
+ try {
1869
+ await this.onEvent(event);
1870
+ } catch {
1871
+ }
1872
+ }
1873
+ }
1874
+ subscribeTeamEvents(listener) {
1875
+ this.eventListeners.add(listener);
1876
+ return () => {
1877
+ this.eventListeners.delete(listener);
1878
+ };
1879
+ }
1880
+ /** @internal */
1881
+ publishCoordinatorEvent(event) {
1882
+ for (const listener of this.coordinatorEventListeners) {
1883
+ try {
1884
+ listener(event);
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ }
1889
+ /** @internal */
1890
+ publishCoordinatorStatus(status) {
1891
+ this.coordinatorStatus = {
1892
+ ...this.coordinatorStatus,
1893
+ ...status,
1894
+ pendingInboxCount: status.pendingInboxCount ?? this.coordinatorInbox.length
1895
+ };
1896
+ for (const listener of this.coordinatorStatusListeners) {
1897
+ try {
1898
+ listener({ ...this.coordinatorStatus });
1899
+ } catch {
1900
+ }
1901
+ }
1902
+ }
1903
+ /** @internal */
1904
+ resolveCoordinatorIdleWaiters() {
1905
+ if (this.coordinatorProcessing) {
1906
+ return;
1907
+ }
1908
+ if (this.coordinatorInbox.length > 0) {
1909
+ return;
1910
+ }
1911
+ if (this.coordinatorStatus.phase !== "ready" && this.coordinatorStatus.phase !== "error") {
1912
+ return;
1913
+ }
1914
+ const resolvers = this.coordinatorIdleResolvers.splice(0);
1915
+ for (const resolve of resolvers) {
1916
+ resolve();
1917
+ }
1918
+ }
1919
+ constructor(config) {
1920
+ this.lead = config.lead;
1921
+ this.leadSessionId = config.leadSessionId;
1922
+ this.teamId = config.teamId?.trim() || `team-${randomUUID2().slice(0, 8)}`;
1923
+ this.createId = config.createId ?? (() => randomUUID2());
1924
+ this.onEvent = config.onEvent;
1925
+ this.signal = config.signal;
1926
+ this.taskDispatchMode = config.taskDispatchMode ?? (config.workLoopEnabled ?? false ? "workloop" : "manual");
1927
+ this.workLoopEnabled = this.taskDispatchMode === "workloop";
1928
+ this.pollIntervalMs = config.pollIntervalMs ?? 500;
1929
+ this.onPermissionRequest = config.onPermissionRequest;
1930
+ this.beforeIteration = config.beforeIteration;
1931
+ this.sharedPermissions = config.sharedPermissions;
1932
+ this.taskExecutor = config.taskExecutor;
1933
+ this.externalTaskControl = config.externalTaskControl;
1934
+ this.onLog = config.onLog;
1935
+ this.logger = config.logger?.child("team");
1936
+ this.taskBoard = new TaskBoard(
1937
+ config.taskBoardStore ?? new InMemoryTaskBoardStore()
1938
+ );
1939
+ this.mailbox = new Mailbox({
1940
+ store: config.mailboxStore ?? new InMemoryMailboxStore(),
1941
+ createId: this.createId
1942
+ });
1943
+ for (const role of config.roles) {
1944
+ this.roles.set(role.name, { ...role });
1945
+ }
1946
+ }
1947
+ // ── Lifecycle ──────────────────────────────────────────────────────
1948
+ async start() {
1949
+ if (this.started) return;
1950
+ await this.taskBoard.start();
1951
+ try {
1952
+ await this.mailbox.start();
1953
+ } catch (error) {
1954
+ await this.taskBoard.stop().catch(() => {
1955
+ });
1956
+ throw error;
1957
+ }
1958
+ this.started = true;
1959
+ this.mailboxUnsubscribe = this.mailbox.subscribe((message) => {
1960
+ if (message.teamId !== this.teamId) {
1961
+ return;
1962
+ }
1963
+ if (message.to !== "coordinator") {
1964
+ return;
1965
+ }
1966
+ if (message.payload?.kind !== "worker-report") {
1967
+ return;
1968
+ }
1969
+ void this.emit({
1970
+ type: "team-message",
1971
+ teamId: this.teamId,
1972
+ message
1973
+ });
1974
+ });
1975
+ this.log(`Started (${this.members.size} member(s), ${this.roles.size} role(s))`);
1976
+ this.publishCoordinatorStatus({
1977
+ phase: "ready",
1978
+ message: "Ready",
1979
+ pendingInboxCount: 0,
1980
+ activeTaskCount: 0
1981
+ });
1982
+ if (this.workLoopEnabled) {
1983
+ for (const runtime of this.members.values()) {
1984
+ if (!runtime.workLoop) {
1985
+ runtime.stopRequested = false;
1986
+ runtime.workLoop = runWorkLoop(this, runtime).catch((err) => {
1987
+ this.log(`[${runtime.member.id}] work-loop crashed: ${err instanceof Error ? err.message : String(err)}`);
1988
+ });
1989
+ }
1990
+ }
1991
+ }
1992
+ await this.emit({
1993
+ type: "team-started",
1994
+ teamId: this.teamId,
1995
+ memberCount: this.members.size
1996
+ });
1997
+ }
1998
+ async stop() {
1999
+ if (!this.started) return;
2000
+ this.started = false;
2001
+ this.log("Stopping...");
2002
+ this.mailboxUnsubscribe?.();
2003
+ this.mailboxUnsubscribe = void 0;
2004
+ this.detachCoordinatorTurn();
2005
+ this.coordinatorInbox = [];
2006
+ this.coordinatorReschedule = false;
2007
+ this.coordinatorProcessing = false;
2008
+ this.autonomousTurnCount = 0;
2009
+ this.endCoordinatorTurnState();
2010
+ for (const runtime of this.members.values()) {
2011
+ runtime.stopRequested = true;
2012
+ }
2013
+ const promises = [];
2014
+ for (const m of this.members.values()) {
2015
+ if (m.workLoop) promises.push(m.workLoop);
2016
+ if (m.activeRun) promises.push(m.activeRun);
2017
+ }
2018
+ await Promise.allSettled(promises);
2019
+ for (const runtime of this.members.values()) {
2020
+ runtime.workLoop = void 0;
2021
+ }
2022
+ let firstError;
2023
+ try {
2024
+ await this.mailbox.stop();
2025
+ } catch (error) {
2026
+ firstError = error;
2027
+ }
2028
+ try {
2029
+ await this.taskBoard.stop();
2030
+ } catch (error) {
2031
+ if (!firstError) {
2032
+ firstError = error;
2033
+ }
2034
+ }
2035
+ await this.emit({
2036
+ type: "team-stopped",
2037
+ teamId: this.teamId
2038
+ });
2039
+ this.publishCoordinatorStatus({
2040
+ phase: "ready",
2041
+ message: "Stopped",
2042
+ pendingInboxCount: 0,
2043
+ activeTaskCount: 0
2044
+ });
2045
+ this.resolveCoordinatorIdleWaiters();
2046
+ if (firstError) {
2047
+ throw firstError;
2048
+ }
2049
+ }
2050
+ // ── Member registration ────────────────────────────────────────────
2051
+ /**
2052
+ * Register a team member from a role.
2053
+ * Forks the lead agent with the role's specialization.
2054
+ */
2055
+ async register(roleName, memberId = roleName) {
2056
+ const id = memberId.trim();
2057
+ if (!id) throw new Error("Member ID must not be empty");
2058
+ const existing = this.members.get(id);
2059
+ if (existing) return { ...existing.member };
2060
+ const role = this.roles.get(roleName);
2061
+ if (!role) {
2062
+ throw new Error(
2063
+ `Unknown role: "${roleName}". Available: ${[...this.roles.keys()].join(", ")}`
2064
+ );
2065
+ }
2066
+ const permissionRules = (this.sharedPermissions ?? []).map((rule) => ({
2067
+ tool: rule.tool,
2068
+ pattern: rule.pattern,
2069
+ action: "allow"
2070
+ }));
2071
+ const inheritedMiddleware = [...this.lead.getMiddlewareRunner().getMiddleware()];
2072
+ const inheritedApprovalMiddleware = inheritedMiddleware.filter((mw) => mw.name === "approval");
2073
+ const needsTeamPermissionPolicy = permissionRules.length > 0 || inheritedApprovalMiddleware.length > 0 || Boolean(this.onPermissionRequest);
2074
+ const memberMiddleware = needsTeamPermissionPolicy ? [
2075
+ ...inheritedMiddleware.filter(
2076
+ (mw) => mw.name !== "approval" && !mw.name.startsWith("team-permission:")
2077
+ ),
2078
+ ...role.additionalMiddleware ?? [],
2079
+ teamPermissionPolicy({
2080
+ memberId: id,
2081
+ rules: permissionRules,
2082
+ forwardApproval: async (_memberId, tool, args, ctx) => {
2083
+ if (this.onPermissionRequest) {
2084
+ return this.createPermissionForwarder(id)(_memberId, tool, args, ctx);
2085
+ }
2086
+ let currentArgs = args;
2087
+ for (const approval of inheritedApprovalMiddleware) {
2088
+ if (!approval.beforeToolCall) continue;
2089
+ const decision = await approval.beforeToolCall(tool, currentArgs, ctx);
2090
+ if (decision.action === "deny") {
2091
+ return decision;
2092
+ }
2093
+ if (decision.args !== void 0) {
2094
+ currentArgs = decision.args;
2095
+ }
2096
+ }
2097
+ return currentArgs === args ? { action: "allow" } : { action: "allow", args: currentArgs };
2098
+ }
2099
+ })
2100
+ ] : void 0;
2101
+ const agent = this.lead.fork({
2102
+ name: id,
2103
+ profile: role.profile,
2104
+ systemPrompt: role.systemPrompt,
2105
+ model: role.model,
2106
+ maxSteps: role.maxSteps,
2107
+ ...memberMiddleware ? { middleware: memberMiddleware } : { additionalMiddleware: role.additionalMiddleware }
2108
+ });
2109
+ for (const tool of role.additionalTools ?? []) {
2110
+ agent.addTool(tool);
2111
+ }
2112
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
2113
+ const sessionId = `team:${this.teamId}:${id}`;
2114
+ const member = {
2115
+ id,
2116
+ role: role.name,
2117
+ description: role.description,
2118
+ sessionId,
2119
+ status: "idle",
2120
+ createdAt: ts,
2121
+ updatedAt: ts
2122
+ };
2123
+ await this.taskBoard.putMember(member);
2124
+ const stats = {
2125
+ tasksCompleted: 0,
2126
+ tasksFailed: 0,
2127
+ totalTokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
2128
+ totalToolCalls: 0,
2129
+ totalDurationMs: 0,
2130
+ lastActivityAt: ts
2131
+ };
2132
+ this.members.set(id, {
2133
+ member,
2134
+ role,
2135
+ agent,
2136
+ stopRequested: false,
2137
+ stats
2138
+ });
2139
+ this.log(`Registered member @${id} (role: ${role.name}${role.description ? `, ${role.description}` : ""})`);
2140
+ await this.emit({
2141
+ type: "team-member-registered",
2142
+ teamId: this.teamId,
2143
+ member: { ...member }
2144
+ });
2145
+ const runtime = this.members.get(id);
2146
+ agent.addTool(createAskCoordinatorTool({
2147
+ memberId: id,
2148
+ teamId: this.teamId,
2149
+ runtime,
2150
+ mailbox: this.mailbox
2151
+ }));
2152
+ if (this.taskDispatchMode === "workloop" && this.started) {
2153
+ runtime.workLoop = runWorkLoop(this, runtime).catch((err) => {
2154
+ this.log(`[${id}] work-loop crashed: ${err instanceof Error ? err.message : String(err)}`);
2155
+ });
2156
+ }
2157
+ return { ...member };
2158
+ }
2159
+ /**
2160
+ * Register all roles (or a subset) as team members.
2161
+ */
2162
+ async registerAll(roleNames) {
2163
+ const names = roleNames?.length ? roleNames : [...this.roles.keys()];
2164
+ return Promise.all(names.map((name) => this.register(name)));
2165
+ }
2166
+ /**
2167
+ * List available role names.
2168
+ */
2169
+ listRoles() {
2170
+ return [...this.roles.keys()];
2171
+ }
2172
+ // ── Task management ────────────────────────────────────────────────
2173
+ /**
2174
+ * Queue a task. If dependencies are met and a member is idle,
2175
+ * the task is dispatched immediately.
2176
+ */
2177
+ async queue(input) {
2178
+ if (input.memberId) {
2179
+ this.requireMember(input.memberId);
2180
+ }
2181
+ const { task, transitions } = await this.taskBoard.createTask({
2182
+ id: this.createId(),
2183
+ memberId: input.memberId,
2184
+ title: input.title,
2185
+ prompt: input.prompt,
2186
+ dependsOn: input.dependsOn
2187
+ });
2188
+ this.log(`Queued task "${input.title}" \u2192 ${input.memberId ?? "unassigned"}${input.dependsOn?.length ? ` (depends on ${input.dependsOn.length} task(s))` : ""}`);
2189
+ await this.emitTransitions(transitions);
2190
+ if (this.taskDispatchMode === "manual") {
2191
+ await this.dispatchReady();
2192
+ }
2193
+ return await this.taskBoard.getTask(task.id) ?? task;
2194
+ }
2195
+ /**
2196
+ * Dispatch all pending tasks to idle members.
2197
+ * Called automatically after queue/complete/fail/cancel.
2198
+ */
2199
+ async dispatchReady() {
2200
+ if (this.taskDispatchMode === "external") {
2201
+ return [];
2202
+ }
2203
+ const dispatched = [];
2204
+ const members = await this.taskBoard.listMembers();
2205
+ for (const member of members) {
2206
+ if (member.status !== "idle") continue;
2207
+ const runtime = this.members.get(member.id);
2208
+ if (!runtime) continue;
2209
+ const task = await claimAndExecute(this, runtime);
2210
+ if (task) dispatched.push(task);
2211
+ }
2212
+ return dispatched;
2213
+ }
2214
+ /**
2215
+ * Wait for a specific task to reach a terminal state.
2216
+ */
2217
+ async waitForTask(taskId, timeoutMs) {
2218
+ const deadline = timeoutMs !== void 0 ? Date.now() + timeoutMs : void 0;
2219
+ while (true) {
2220
+ const task = await this.taskBoard.getTask(taskId);
2221
+ if (!task) throw new Error(`Unknown task: ${taskId}`);
2222
+ if (task.status === "completed" || task.status === "aborted" || task.status === "failed" || task.status === "cancelled") {
2223
+ return { task, result: task.result };
2224
+ }
2225
+ if (deadline && Date.now() >= deadline) {
2226
+ throw new Error(`Timed out waiting for task "${taskId}"`);
2227
+ }
2228
+ await sleep(100);
2229
+ }
2230
+ }
2231
+ /**
2232
+ * Wait for all tasks to reach terminal states.
2233
+ */
2234
+ async waitForAll(timeoutMs) {
2235
+ const deadline = timeoutMs !== void 0 ? Date.now() + timeoutMs : void 0;
2236
+ const tasks = await this.taskBoard.listTasks();
2237
+ const results = [];
2238
+ for (const task of tasks) {
2239
+ const remaining = deadline ? Math.max(1, deadline - Date.now()) : void 0;
2240
+ results.push(await this.waitForTask(task.id, remaining));
2241
+ }
2242
+ return results;
2243
+ }
2244
+ /**
2245
+ * Cancel a task. If it's running, the task's AbortController fires.
2246
+ * Cascades to dependent tasks.
2247
+ */
2248
+ async cancel(taskId, reason) {
2249
+ const { task, transitions } = await this.taskBoard.cancelTask(taskId, reason);
2250
+ await this.emitTransitions(transitions);
2251
+ if (task.memberId) {
2252
+ const runtime = this.members.get(task.memberId);
2253
+ if (runtime?.taskAbort && runtime.member.activeTaskId === taskId) {
2254
+ runtime.taskAbort.abort(reason ?? "Task cancelled");
2255
+ } else if (runtime && runtime.member.activeTaskId === taskId && !runtime.activeRun) {
2256
+ await this.finalizeExternallyTerminatedTask(runtime);
2257
+ }
2258
+ }
2259
+ if (this.taskDispatchMode === "manual") {
2260
+ await this.dispatchReady();
2261
+ }
2262
+ return task;
2263
+ }
2264
+ /**
2265
+ * Abort a running task. The member returns to idle and can pick up
2266
+ * new work. Unlike `cancel()`, abort preserves downstream tasks by
2267
+ * leaving them blocked on the aborted prerequisite.
2268
+ */
2269
+ async abort(taskId, reason) {
2270
+ const task = await this.taskBoard.getTask(taskId);
2271
+ if (!task) throw new Error(`Unknown task: ${taskId}`);
2272
+ if (task.status !== "running" && task.status !== "claimed") {
2273
+ throw new Error(`Cannot abort task in "${task.status}" state`);
2274
+ }
2275
+ const { task: aborted, transitions } = await this.taskBoard.abortTask(taskId, reason);
2276
+ await this.emitTransitions(transitions);
2277
+ await this.emit({
2278
+ type: "team-task-aborted",
2279
+ teamId: this.teamId,
2280
+ taskId,
2281
+ memberId: aborted.memberId ?? "unassigned",
2282
+ reason
2283
+ });
2284
+ if (aborted.memberId) {
2285
+ const runtime = this.members.get(aborted.memberId);
2286
+ if (runtime?.taskAbort) {
2287
+ runtime.taskAbort.abort(reason ?? "Aborted by coordinator");
2288
+ } else if (runtime && this.externalTaskControl?.abortTask) {
2289
+ void this.externalTaskControl.abortTask({
2290
+ task: aborted,
2291
+ runtime,
2292
+ reason
2293
+ }).catch((err) => {
2294
+ this.log(`External abortTask failed for task ${taskId}: ${err instanceof Error ? err.message : String(err)}`);
2295
+ });
2296
+ } else if (runtime && runtime.member.activeTaskId === taskId && !runtime.activeRun) {
2297
+ await this.finalizeExternallyTerminatedTask(runtime);
2298
+ }
2299
+ }
2300
+ }
2301
+ // ── Communication ──────────────────────────────────────────────────
2302
+ /**
2303
+ * Send a guidance message to a running member via intervention.
2304
+ * If the member is idle, queues a direct-message task instead.
2305
+ */
2306
+ async guide(memberId, message) {
2307
+ const runtime = this.requireMember(memberId);
2308
+ const body = message.trim();
2309
+ if (!body) throw new Error("Message must not be empty");
2310
+ const record = await this.mailbox.send({
2311
+ teamId: this.teamId,
2312
+ from: "coordinator",
2313
+ to: memberId,
2314
+ kind: "direct",
2315
+ body,
2316
+ payload: { kind: "guidance", taskId: runtime.member.activeTaskId },
2317
+ taskId: runtime.member.activeTaskId,
2318
+ metadata: { payloadKind: "guidance" }
2319
+ });
2320
+ await this.emit({
2321
+ type: "team-message",
2322
+ teamId: this.teamId,
2323
+ message: record
2324
+ });
2325
+ if (runtime.member.status === "busy" && runtime.member.activeTaskId) {
2326
+ runtime.lastDeliveredMessageAt = record.createdAt;
2327
+ runtime.lastDeliveredMessageId = record.id;
2328
+ if (runtime.pendingAskResolve) {
2329
+ runtime.pendingAskResolve(body);
2330
+ this.log(`Ask response \u2192 @${memberId}: "${body.length > 80 ? body.slice(0, 80) + "\u2026" : body}"`);
2331
+ return;
2332
+ }
2333
+ if (runtime.taskAbort || this.taskDispatchMode !== "external") {
2334
+ runtime.agent.intervene(body);
2335
+ } else if (this.externalTaskControl?.guideTask) {
2336
+ const activeTask = await this.taskBoard.getTask(runtime.member.activeTaskId);
2337
+ if (activeTask) {
2338
+ void this.externalTaskControl.guideTask({
2339
+ task: activeTask,
2340
+ runtime,
2341
+ message: body
2342
+ }).catch((err) => {
2343
+ this.log(`External guideTask failed for @${memberId}: ${err instanceof Error ? err.message : String(err)}`);
2344
+ });
2345
+ }
2346
+ }
2347
+ this.log(`Intervention \u2192 @${memberId}: "${body.length > 80 ? body.slice(0, 80) + "\u2026" : body}"`);
2348
+ return;
2349
+ }
2350
+ if (this.taskDispatchMode === "manual") {
2351
+ runtime.lastDeliveredMessageAt = record.createdAt;
2352
+ runtime.lastDeliveredMessageId = record.id;
2353
+ await this.queue({
2354
+ memberId,
2355
+ title: "guidance",
2356
+ prompt: body
2357
+ });
2358
+ }
2359
+ }
2360
+ /**
2361
+ * Broadcast a message to all members. Active members receive it
2362
+ * as an intervention; idle members see it on their next task.
2363
+ */
2364
+ async broadcast(message) {
2365
+ const body = message.trim();
2366
+ if (!body) throw new Error("Message must not be empty");
2367
+ const record = await this.mailbox.send({
2368
+ teamId: this.teamId,
2369
+ from: "coordinator",
2370
+ kind: "broadcast",
2371
+ body,
2372
+ payload: { kind: "guidance" },
2373
+ metadata: { payloadKind: "guidance" }
2374
+ });
2375
+ await this.emit({
2376
+ type: "team-message",
2377
+ teamId: this.teamId,
2378
+ message: record
2379
+ });
2380
+ const recipients = [];
2381
+ for (const runtime of this.members.values()) {
2382
+ if (runtime.member.status === "busy") {
2383
+ runtime.lastDeliveredMessageAt = record.createdAt;
2384
+ runtime.lastDeliveredMessageId = record.id;
2385
+ if (runtime.taskAbort || this.taskDispatchMode !== "external") {
2386
+ runtime.agent.intervene(body);
2387
+ } else if (this.externalTaskControl?.guideTask && runtime.member.activeTaskId) {
2388
+ const activeTask = await this.taskBoard.getTask(runtime.member.activeTaskId);
2389
+ if (activeTask) {
2390
+ void this.externalTaskControl.guideTask({
2391
+ task: activeTask,
2392
+ runtime,
2393
+ message: body
2394
+ }).catch((err) => {
2395
+ this.log(`External guideTask (broadcast) failed for @${runtime.member.id}: ${err instanceof Error ? err.message : String(err)}`);
2396
+ });
2397
+ }
2398
+ }
2399
+ recipients.push(runtime.member.id);
2400
+ }
2401
+ }
2402
+ this.log(`Broadcast: "${body.length > 80 ? body.slice(0, 80) + "\u2026" : body}" \u2192 ${recipients.length > 0 ? recipients.join(", ") : "no active members"}`);
2403
+ return record;
2404
+ }
2405
+ // ── Shutdown protocol ──────────────────────────────────────────────
2406
+ /**
2407
+ * Request graceful shutdown of a specific member.
2408
+ *
2409
+ * - If member is idle, auto-accepts and goes offline immediately
2410
+ * - If member is busy, marks as shutting-down and waits for current
2411
+ * task to finish before going offline
2412
+ * - Returns true if shutdown succeeded within the timeout
2413
+ */
2414
+ async requestShutdown(memberId, reason, timeoutMs) {
2415
+ return requestShutdown(this, memberId, reason, timeoutMs);
2416
+ }
2417
+ /**
2418
+ * Gracefully shut down all members.
2419
+ */
2420
+ async shutdown(reason, timeoutMs) {
2421
+ return shutdownAll(this, reason, timeoutMs);
2422
+ }
2423
+ // ── Permission forwarding ──────────────────────────────────────────
2424
+ /**
2425
+ * Handle a permission request forwarded from a team member.
2426
+ * Called by the `teamPermissionPolicy` middleware when it has
2427
+ * a `forwardApproval` callback pointing at the coordinator.
2428
+ */
2429
+ async handlePermissionRequest(memberId, tool, args) {
2430
+ const requestId = this.createId();
2431
+ await this.emit({
2432
+ type: "team-permission-forwarded",
2433
+ teamId: this.teamId,
2434
+ memberId,
2435
+ requestId,
2436
+ tool
2437
+ });
2438
+ let result;
2439
+ if (this.onPermissionRequest) {
2440
+ result = await this.onPermissionRequest(memberId, tool, args);
2441
+ } else {
2442
+ result = { decision: "allow" };
2443
+ }
2444
+ await this.emit({
2445
+ type: "team-permission-resolved",
2446
+ teamId: this.teamId,
2447
+ memberId,
2448
+ requestId,
2449
+ decision: result.decision
2450
+ });
2451
+ return result;
2452
+ }
2453
+ /**
2454
+ * Create a `forwardApproval` callback for `teamPermissionPolicy`
2455
+ * that routes through this coordinator's permission handler.
2456
+ */
2457
+ createPermissionForwarder(memberId) {
2458
+ return async (_memberId, tool, args, _ctx) => {
2459
+ const result = await this.handlePermissionRequest(memberId, tool, args);
2460
+ return { action: result.decision, reason: result.reason };
2461
+ };
2462
+ }
2463
+ /** @internal */
2464
+ beginCoordinatorTurnState() {
2465
+ this.coordinatorTurnState = {
2466
+ actions: {
2467
+ assigned: [],
2468
+ sentMessages: [],
2469
+ abortedTaskIds: []
2470
+ }
2471
+ };
2472
+ return this.coordinatorTurnState;
2473
+ }
2474
+ /** @internal */
2475
+ getActiveCoordinatorTurnState() {
2476
+ if (!this.coordinatorTurnState) {
2477
+ throw new Error("No active coordinator turn state");
2478
+ }
2479
+ return this.coordinatorTurnState;
2480
+ }
2481
+ /** @internal */
2482
+ endCoordinatorTurnState() {
2483
+ this.coordinatorTurnState = null;
2484
+ }
2485
+ /** @internal */
2486
+ getCoordinatorInboxSize() {
2487
+ return this.coordinatorInbox.length;
2488
+ }
2489
+ /** @internal */
2490
+ async getActiveTaskCount() {
2491
+ const tasks = await this.taskBoard.listTasks();
2492
+ return tasks.filter(
2493
+ (task) => task.status !== "completed" && task.status !== "aborted" && task.status !== "failed" && task.status !== "cancelled"
2494
+ ).length;
2495
+ }
2496
+ /** @internal */
2497
+ ensureCoordinatorAgent() {
2498
+ if (!this.coordinatorAgent) {
2499
+ this.coordinatorAgent = createCoordinatorAgent(this);
2500
+ this.coordinatorSessionId = `team:${this.teamId}:coordinator`;
2501
+ this.attachCoordinatorTurn(this.coordinatorAgent, this.coordinatorSessionId);
2502
+ }
2503
+ return this.coordinatorAgent;
2504
+ }
2505
+ /** @internal */
2506
+ getCoordinatorSessionId() {
2507
+ return this.coordinatorSessionId;
2508
+ }
2509
+ resetCoordinatorRunSession() {
2510
+ const agent = this.ensureCoordinatorAgent();
2511
+ this.detachCoordinatorTurn();
2512
+ this.coordinatorInbox = [];
2513
+ this.coordinatorReschedule = false;
2514
+ this.coordinatorProcessing = false;
2515
+ this.endCoordinatorTurnState();
2516
+ this.coordinatorRoundNumber = 0;
2517
+ this.coordinatorRunSequence += 1;
2518
+ this.coordinatorSessionId = `team:${this.teamId}:coordinator:run-${this.coordinatorRunSequence}`;
2519
+ this.attachCoordinatorTurn(agent, this.coordinatorSessionId);
2520
+ this.publishCoordinatorStatus({
2521
+ phase: "ready",
2522
+ message: "Ready",
2523
+ pendingInboxCount: 0,
2524
+ activeTaskCount: 0
2525
+ });
2526
+ }
2527
+ enqueueCoordinatorInboxItem(item) {
2528
+ enqueueInboxItem(this, item);
2529
+ }
2530
+ enqueueCoordinatorNotification(notification) {
2531
+ enqueueNotification(this, notification);
2532
+ }
2533
+ /** @internal */
2534
+ scheduleCoordinatorProcessing() {
2535
+ scheduleProcessing(this);
2536
+ }
2537
+ /** @internal */
2538
+ autonomousTurnCount = 0;
2539
+ async waitForCoordinatorIdle() {
2540
+ if (!this.coordinatorProcessing && this.coordinatorInbox.length === 0 && (this.coordinatorStatus.phase === "ready" || this.coordinatorStatus.phase === "error")) {
2541
+ return;
2542
+ }
2543
+ await new Promise((resolve) => {
2544
+ this.coordinatorIdleResolvers.push(resolve);
2545
+ });
2546
+ }
2547
+ submitToCoordinator(message) {
2548
+ const body = message.trim();
2549
+ if (!body) {
2550
+ throw new Error("Coordinator message must not be empty");
2551
+ }
2552
+ const id = this.createId();
2553
+ this.log(`submitToCoordinator: id=${id} body="${body.length > 80 ? body.slice(0, 80) + "\u2026" : body}" (processing=${this.coordinatorProcessing}, inbox=${this.coordinatorInbox.length})`);
2554
+ this.enqueueCoordinatorInboxItem({
2555
+ id,
2556
+ kind: "user-message",
2557
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2558
+ body
2559
+ });
2560
+ this.scheduleCoordinatorProcessing();
2561
+ return id;
2562
+ }
2563
+ subscribeCoordinatorEvents(listener) {
2564
+ this.coordinatorEventListeners.add(listener);
2565
+ return () => {
2566
+ this.coordinatorEventListeners.delete(listener);
2567
+ };
2568
+ }
2569
+ subscribeCoordinatorStatus(listener) {
2570
+ this.coordinatorStatusListeners.add(listener);
2571
+ listener({ ...this.coordinatorStatus });
2572
+ return () => {
2573
+ this.coordinatorStatusListeners.delete(listener);
2574
+ };
2575
+ }
2576
+ getCoordinatorStatus() {
2577
+ return { ...this.coordinatorStatus };
2578
+ }
2579
+ /** @internal */
2580
+ attachCoordinatorTurn(agent, sessionId) {
2581
+ this.activeCoordinatorTurn = { agent, sessionId, turnOpen: false };
2582
+ }
2583
+ /** @internal */
2584
+ setCoordinatorTurnOpen(open) {
2585
+ if (!this.activeCoordinatorTurn) {
2586
+ return;
2587
+ }
2588
+ this.activeCoordinatorTurn.turnOpen = open;
2589
+ }
2590
+ /** @internal */
2591
+ detachCoordinatorTurn(agent) {
2592
+ if (!this.activeCoordinatorTurn) {
2593
+ return;
2594
+ }
2595
+ if (agent && this.activeCoordinatorTurn.agent !== agent) {
2596
+ return;
2597
+ }
2598
+ this.activeCoordinatorTurn = void 0;
2599
+ }
2600
+ // ── Coordinator loop ──────────────────────────────────────────────
2601
+ /**
2602
+ * Run the coordinator in an iterative loop.
2603
+ *
2604
+ * The lead agent is forked with delegation tools and a coordinator
2605
+ * system prompt. It reasons about the request, assigns tasks to
2606
+ * teammates, receives results, and continues until all work is done.
2607
+ *
2608
+ * This replaces the plan → fan-out → synthesize pipeline with a
2609
+ * dynamic multi-round conversation where the coordinator decides
2610
+ * what to delegate, how to react to results, and when it's done.
2611
+ *
2612
+ * Members must be registered before calling this method.
2613
+ *
2614
+ * @param prompt - The user's request
2615
+ * @param options - Loop configuration (max rounds, timeouts, progress callback)
2616
+ */
2617
+ async run(prompt, options) {
2618
+ this.resetCoordinatorRunSession();
2619
+ const rounds = [];
2620
+ let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
2621
+ let lastResponse = "";
2622
+ const unsubEvents = this.subscribeCoordinatorEvents((event) => {
2623
+ options?.onCoordinatorEvent?.(event);
2624
+ if (event.type === "step-finish" && event.usage) {
2625
+ totalUsage = {
2626
+ inputTokens: (totalUsage.inputTokens ?? 0) + (event.usage.inputTokens ?? 0),
2627
+ outputTokens: (totalUsage.outputTokens ?? 0) + (event.usage.outputTokens ?? 0),
2628
+ totalTokens: (totalUsage.totalTokens ?? 0) + (event.usage.totalTokens ?? 0)
2629
+ };
2630
+ }
2631
+ if (event.type === "complete" && typeof event.output === "string") {
2632
+ lastResponse = event.output;
2633
+ }
2634
+ });
2635
+ const unsubStatus = this.subscribeCoordinatorStatus((status) => {
2636
+ const mappedPhase = status.phase === "running" ? "thinking" : status.phase === "waiting" ? "waiting" : "done";
2637
+ options?.onStatus?.({
2638
+ phase: mappedPhase,
2639
+ round: this.coordinatorRoundNumber,
2640
+ message: status.message
2641
+ });
2642
+ });
2643
+ const unsubTeam = this.subscribeTeamEvents((event) => {
2644
+ if (event.type !== "team-coordinator-round") {
2645
+ return;
2646
+ }
2647
+ const round = {
2648
+ number: event.roundNumber,
2649
+ assigned: [...event.assigned],
2650
+ messaged: [...event.messaged],
2651
+ stopped: [...event.stopped],
2652
+ completions: [...event.completions],
2653
+ reports: [...event.reports],
2654
+ response: event.response,
2655
+ usage: event.usage
2656
+ };
2657
+ rounds.push(round);
2658
+ void options?.onRound?.(round);
2659
+ });
2660
+ try {
2661
+ this.submitToCoordinator(prompt);
2662
+ await this.waitForCoordinatorIdle();
2663
+ return { response: lastResponse, rounds, usage: totalUsage };
2664
+ } finally {
2665
+ unsubEvents();
2666
+ unsubStatus();
2667
+ unsubTeam();
2668
+ }
2669
+ }
2670
+ // ── Planning ────────────────────────────────────────────────────────
2671
+ async plan(prompt, memberIds) {
2672
+ return planTasks(this, prompt, memberIds);
2673
+ }
2674
+ async planAndExecute(prompt, memberIds) {
2675
+ return planAndExecuteTasks(this, prompt, memberIds);
2676
+ }
2677
+ // ── Synthesis ──────────────────────────────────────────────────────
2678
+ /**
2679
+ * Use the lead agent to synthesize all completed task results
2680
+ * into a single coherent response.
2681
+ *
2682
+ * @param originalPrompt - The original task/prompt
2683
+ * @param taskIds - Optional subset of task IDs to synthesize (defaults to all terminal tasks)
2684
+ */
2685
+ async synthesize(originalPrompt, taskIds) {
2686
+ return synthesizeResults(this, originalPrompt, taskIds);
2687
+ }
2688
+ // ── Queries ────────────────────────────────────────────────────────
2689
+ async snapshot() {
2690
+ const [board, messages] = await Promise.all([
2691
+ this.taskBoard.snapshot(),
2692
+ this.mailbox.list({ teamId: this.teamId })
2693
+ ]);
2694
+ return { ...board, messages };
2695
+ }
2696
+ async listMembers() {
2697
+ return this.taskBoard.listMembers();
2698
+ }
2699
+ async listTasks(filter) {
2700
+ return this.taskBoard.listTasks(filter);
2701
+ }
2702
+ async listMessages(filter) {
2703
+ return this.mailbox.list({ ...filter, teamId: this.teamId });
2704
+ }
2705
+ getMemberSessionId(memberId) {
2706
+ return this.requireMember(memberId).member.sessionId;
2707
+ }
2708
+ getMemberRole(memberId) {
2709
+ return this.requireMember(memberId).role.name;
2710
+ }
2711
+ async prepareTaskForExternalExecution(taskId, runId) {
2712
+ const task = await this.taskBoard.getTask(taskId);
2713
+ if (!task?.memberId) {
2714
+ return void 0;
2715
+ }
2716
+ const runtime = this.members.get(task.memberId);
2717
+ if (!runtime) {
2718
+ return void 0;
2719
+ }
2720
+ return prepareTaskForExternalExecution(this, runtime, taskId, runId);
2721
+ }
2722
+ async completeExternalTask(taskId, result) {
2723
+ const task = await this.taskBoard.getTask(taskId);
2724
+ if (!task?.memberId) {
2725
+ return void 0;
2726
+ }
2727
+ const runtime = this.members.get(task.memberId);
2728
+ if (!runtime) {
2729
+ return void 0;
2730
+ }
2731
+ try {
2732
+ return await completePreparedTask(this, runtime, taskId, result);
2733
+ } finally {
2734
+ await this.finalizeExternallyTerminatedTask(runtime);
2735
+ }
2736
+ }
2737
+ async failExternalTask(taskId, error) {
2738
+ const task = await this.taskBoard.getTask(taskId);
2739
+ if (!task?.memberId) {
2740
+ return void 0;
2741
+ }
2742
+ const runtime = this.members.get(task.memberId);
2743
+ if (!runtime) {
2744
+ return void 0;
2745
+ }
2746
+ try {
2747
+ return await failPreparedTask(this, runtime, taskId, error);
2748
+ } finally {
2749
+ await this.finalizeExternallyTerminatedTask(runtime);
2750
+ }
2751
+ }
2752
+ async abortExternalTask(taskId, reason) {
2753
+ const task = await this.taskBoard.getTask(taskId);
2754
+ if (!task?.memberId) {
2755
+ return void 0;
2756
+ }
2757
+ const runtime = this.members.get(task.memberId);
2758
+ if (!runtime) {
2759
+ return void 0;
2760
+ }
2761
+ try {
2762
+ return await abortPreparedTask(this, runtime, taskId, reason);
2763
+ } finally {
2764
+ await this.finalizeExternallyTerminatedTask(runtime);
2765
+ }
2766
+ }
2767
+ async getTask(taskId) {
2768
+ return this.taskBoard.getTask(taskId);
2769
+ }
2770
+ /**
2771
+ * Get aggregate stats for a member.
2772
+ * Returns undefined if the member is not registered.
2773
+ */
2774
+ getMemberStats(memberId) {
2775
+ return this.members.get(memberId)?.stats;
2776
+ }
2777
+ /**
2778
+ * Subscribe to a member's agent events (text-delta, tool-start, etc.).
2779
+ * Returns an unsubscribe function. Throws if the member is not registered.
2780
+ */
2781
+ onMemberEvent(memberId, handler) {
2782
+ const runtime = this.members.get(memberId);
2783
+ if (!runtime) {
2784
+ throw new Error(`Member "${memberId}" is not registered`);
2785
+ }
2786
+ return runtime.agent.signal.onAny(handler);
2787
+ }
2788
+ /**
2789
+ * Serialize the coordinator state for persistence / resume.
2790
+ * Returns snapshot + member stats, enough to reconstruct state.
2791
+ */
2792
+ async toJSON() {
2793
+ const snap = await this.snapshot();
2794
+ const memberStats = {};
2795
+ for (const [id, runtime] of this.members) {
2796
+ memberStats[id] = { ...runtime.stats };
2797
+ }
2798
+ return { teamId: this.teamId, snapshot: snap, memberStats };
2799
+ }
2800
+ // ── Internal: member state (CoordinatorCtx) ────────────────────────
2801
+ /** @internal */
2802
+ async updateMemberStatus(memberId, status, activeTaskId) {
2803
+ const runtime = this.members.get(memberId);
2804
+ if (!runtime) return;
2805
+ const previous = runtime.member.status;
2806
+ const updated = {
2807
+ ...runtime.member,
2808
+ status,
2809
+ activeTaskId,
2810
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2811
+ };
2812
+ await this.taskBoard.putMember(updated);
2813
+ runtime.member = updated;
2814
+ if (previous !== status) {
2815
+ await this.emit({
2816
+ type: "team-member-status",
2817
+ teamId: this.teamId,
2818
+ memberId,
2819
+ previous,
2820
+ current: status,
2821
+ activeTaskId
2822
+ });
2823
+ }
2824
+ }
2825
+ requireMember(memberId) {
2826
+ const runtime = this.members.get(memberId);
2827
+ if (!runtime) {
2828
+ throw new Error(
2829
+ `Unknown member: "${memberId}". Registered: ${[...this.members.keys()].join(", ")}`
2830
+ );
2831
+ }
2832
+ return runtime;
2833
+ }
2834
+ // ── Internal: events (CoordinatorCtx) ──────────────────────────────
2835
+ /** @internal */
2836
+ async emit(event) {
2837
+ await this.dispatchEvent(event);
2838
+ if (event.type === "team-notification") {
2839
+ this.log(`emit: team-notification kind=${event.notification.kind} member=${event.notification.memberId}`);
2840
+ this.enqueueCoordinatorNotification(event.notification);
2841
+ this.scheduleCoordinatorProcessing();
2842
+ return;
2843
+ }
2844
+ try {
2845
+ const notificationEvent = await buildCoordinatorNotificationEvent(
2846
+ event,
2847
+ (taskId) => this.taskBoard.getTask(taskId)
2848
+ );
2849
+ if (notificationEvent) {
2850
+ this.log(`emit: ${event.type} \u2192 notification kind=${notificationEvent.notification.kind} member=${notificationEvent.notification.memberId}`);
2851
+ await this.dispatchEvent(notificationEvent);
2852
+ this.enqueueCoordinatorNotification(notificationEvent.notification);
2853
+ this.scheduleCoordinatorProcessing();
2854
+ }
2855
+ } catch {
2856
+ }
2857
+ }
2858
+ /** @internal */
2859
+ async emitTransitions(transitions) {
2860
+ for (const t of transitions) {
2861
+ if (t.reason === "created") {
2862
+ await this.emit({
2863
+ type: "team-task-created",
2864
+ teamId: this.teamId,
2865
+ task: t.task
2866
+ });
2867
+ }
2868
+ if (t.previous !== void 0) {
2869
+ this.log(`Task "${t.task.title}" (${t.task.memberId ?? "?"}): ${t.previous} \u2192 ${t.task.status}${t.reason ? ` (${t.reason})` : ""}`);
2870
+ await this.emit({
2871
+ type: "team-task-transition",
2872
+ teamId: this.teamId,
2873
+ taskId: t.task.id,
2874
+ previous: t.previous,
2875
+ current: t.task.status,
2876
+ reason: t.reason
2877
+ });
2878
+ }
2879
+ }
2880
+ }
2881
+ async finalizeExternallyTerminatedTask(runtime) {
2882
+ if (runtime.stopRequested || runtime.member.status === "shutting-down") {
2883
+ await this.updateMemberStatus(runtime.member.id, "offline", void 0);
2884
+ return;
2885
+ }
2886
+ await this.updateMemberStatus(runtime.member.id, "idle", void 0);
2887
+ if (this.taskDispatchMode === "manual") {
2888
+ await this.dispatchReady();
2889
+ }
2890
+ }
2891
+ };
2892
+ function createTeamCoordinator(config) {
2893
+ return new TeamCoordinator(config);
2894
+ }
2895
+
2896
+ // src/team/coordinator/round-engine.ts
2897
+ function addTokenUsage(total, usage) {
2898
+ return {
2899
+ inputTokens: (total.inputTokens ?? 0) + (usage?.inputTokens ?? 0),
2900
+ outputTokens: (total.outputTokens ?? 0) + (usage?.outputTokens ?? 0),
2901
+ totalTokens: (total.totalTokens ?? 0) + (usage?.totalTokens ?? 0)
2902
+ };
2903
+ }
2904
+ function evaluateCoordinatorRoundTransition(input) {
2905
+ if (input.reasonAssignmentsCount === 0 && input.activeTaskCount === 0 && input.terminalAfterEvent && input.reports.length === 0) {
2906
+ return {
2907
+ kind: "done",
2908
+ finalResponse: input.reasonResponse
2909
+ };
2910
+ }
2911
+ if (input.notifications.length === 0 && input.reports.length === 0 && input.activeTaskCount === 0) {
2912
+ if (!input.terminalAfterEvent) {
2913
+ return {
2914
+ kind: "continue",
2915
+ nextMessage: input.blockedMessage
2916
+ };
2917
+ }
2918
+ return {
2919
+ kind: "done",
2920
+ finalResponse: input.reasonAssignmentsCount === 0 && input.terminalAfterEvent ? input.reasonResponse : input.roundResponse
2921
+ };
2922
+ }
2923
+ if (input.notifications.length === 0 && input.reports.length === 0 && input.activeTaskCount > 0) {
2924
+ return {
2925
+ kind: "wait",
2926
+ nextMessage: ""
2927
+ };
2928
+ }
2929
+ return {
2930
+ kind: "continue",
2931
+ nextMessage: formatCoordinatorRoundMessage(
2932
+ input.notifications,
2933
+ input.reports
2934
+ )
2935
+ };
2936
+ }
2937
+
2938
+ export {
2939
+ TERMINAL_STATUSES,
2940
+ TaskConflictError,
2941
+ InMemoryTaskBoardStore,
2942
+ TaskBoard,
2943
+ InMemoryMailboxStore,
2944
+ Mailbox,
2945
+ teamPermissionPolicy,
2946
+ coordinatorToolDescriptions,
2947
+ buildCoordinatorSystemPrompt,
2948
+ formatCoordinatorTaskNotifications,
2949
+ formatCoordinatorWorkerReports,
2950
+ formatCoordinatorRoundMessage,
2951
+ buildCoordinatorNotificationEvent,
2952
+ TeamCoordinator,
2953
+ createTeamCoordinator,
2954
+ addTokenUsage,
2955
+ evaluateCoordinatorRoundTransition
2956
+ };