@xagent-ai/cli 1.3.6 → 1.4.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 (69) hide show
  1. package/README.md +9 -0
  2. package/README_CN.md +9 -0
  3. package/dist/cli.js +26 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/mcp.d.ts +8 -1
  6. package/dist/mcp.d.ts.map +1 -1
  7. package/dist/mcp.js +53 -20
  8. package/dist/mcp.js.map +1 -1
  9. package/dist/sdk-output-adapter.d.ts +79 -0
  10. package/dist/sdk-output-adapter.d.ts.map +1 -1
  11. package/dist/sdk-output-adapter.js +118 -0
  12. package/dist/sdk-output-adapter.js.map +1 -1
  13. package/dist/session.d.ts +88 -1
  14. package/dist/session.d.ts.map +1 -1
  15. package/dist/session.js +351 -5
  16. package/dist/session.js.map +1 -1
  17. package/dist/slash-commands.d.ts.map +1 -1
  18. package/dist/slash-commands.js +3 -5
  19. package/dist/slash-commands.js.map +1 -1
  20. package/dist/smart-approval.d.ts.map +1 -1
  21. package/dist/smart-approval.js +1 -0
  22. package/dist/smart-approval.js.map +1 -1
  23. package/dist/system-prompt-generator.d.ts +15 -1
  24. package/dist/system-prompt-generator.d.ts.map +1 -1
  25. package/dist/system-prompt-generator.js +36 -27
  26. package/dist/system-prompt-generator.js.map +1 -1
  27. package/dist/team-manager/index.d.ts +6 -0
  28. package/dist/team-manager/index.d.ts.map +1 -0
  29. package/dist/team-manager/index.js +6 -0
  30. package/dist/team-manager/index.js.map +1 -0
  31. package/dist/team-manager/message-broker.d.ts +128 -0
  32. package/dist/team-manager/message-broker.d.ts.map +1 -0
  33. package/dist/team-manager/message-broker.js +638 -0
  34. package/dist/team-manager/message-broker.js.map +1 -0
  35. package/dist/team-manager/team-coordinator.d.ts +45 -0
  36. package/dist/team-manager/team-coordinator.d.ts.map +1 -0
  37. package/dist/team-manager/team-coordinator.js +887 -0
  38. package/dist/team-manager/team-coordinator.js.map +1 -0
  39. package/dist/team-manager/team-store.d.ts +49 -0
  40. package/dist/team-manager/team-store.d.ts.map +1 -0
  41. package/dist/team-manager/team-store.js +436 -0
  42. package/dist/team-manager/team-store.js.map +1 -0
  43. package/dist/team-manager/teammate-spawner.d.ts +86 -0
  44. package/dist/team-manager/teammate-spawner.d.ts.map +1 -0
  45. package/dist/team-manager/teammate-spawner.js +605 -0
  46. package/dist/team-manager/teammate-spawner.js.map +1 -0
  47. package/dist/team-manager/types.d.ts +164 -0
  48. package/dist/team-manager/types.d.ts.map +1 -0
  49. package/dist/team-manager/types.js +27 -0
  50. package/dist/team-manager/types.js.map +1 -0
  51. package/dist/tools.d.ts +41 -1
  52. package/dist/tools.d.ts.map +1 -1
  53. package/dist/tools.js +288 -32
  54. package/dist/tools.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/cli.ts +20 -0
  57. package/src/mcp.ts +64 -25
  58. package/src/sdk-output-adapter.ts +177 -0
  59. package/src/session.ts +423 -15
  60. package/src/slash-commands.ts +3 -7
  61. package/src/smart-approval.ts +1 -0
  62. package/src/system-prompt-generator.ts +59 -26
  63. package/src/team-manager/index.ts +5 -0
  64. package/src/team-manager/message-broker.ts +751 -0
  65. package/src/team-manager/team-coordinator.ts +1117 -0
  66. package/src/team-manager/team-store.ts +558 -0
  67. package/src/team-manager/teammate-spawner.ts +800 -0
  68. package/src/team-manager/types.ts +206 -0
  69. package/src/tools.ts +316 -33
@@ -0,0 +1,887 @@
1
+ import { getTeamStore } from './team-store.js';
2
+ import { getTeammateSpawner } from './teammate-spawner.js';
3
+ import { MessageClient, getMessageBroker, removeMessageBroker, getTeammateClient } from './message-broker.js';
4
+ import { LEAD_PERMISSIONS, TEAMMATE_PERMISSIONS, } from './types.js';
5
+ import { colors, icons } from '../theme.js';
6
+ export class TeamCoordinator {
7
+ store;
8
+ spawner;
9
+ brokers = new Map();
10
+ constructor() {
11
+ this.store = getTeamStore();
12
+ this.spawner = getTeammateSpawner();
13
+ }
14
+ async getBroker(teamId) {
15
+ if (!this.brokers.has(teamId)) {
16
+ const broker = getMessageBroker(teamId);
17
+ if (!broker.isConnected()) {
18
+ await broker.start();
19
+ }
20
+ this.brokers.set(teamId, broker);
21
+ }
22
+ return this.brokers.get(teamId);
23
+ }
24
+ async broadcastTaskUpdate(teamId, fromMemberId, taskId, action, taskInfo) {
25
+ const envBrokerPort = process.env.XAGENT_BROKER_PORT;
26
+ const content = JSON.stringify({
27
+ taskId,
28
+ action,
29
+ ...taskInfo,
30
+ timestamp: Date.now()
31
+ });
32
+ // Teammate processes use MessageClient to broadcast
33
+ if (envBrokerPort && fromMemberId === process.env.XAGENT_MEMBER_ID) {
34
+ await this.broadcastAsTeammate(teamId, fromMemberId, parseInt(envBrokerPort, 10), content, 'task_update');
35
+ return;
36
+ }
37
+ // Lead processes use broker directly
38
+ try {
39
+ const broker = await this.getBroker(teamId);
40
+ broker.sendMessage(fromMemberId, 'broadcast', content, 'task_update');
41
+ }
42
+ catch (error) {
43
+ console.warn('[Team] Failed to broadcast task update:', error);
44
+ }
45
+ }
46
+ /**
47
+ * Broadcast message as teammate using persistent MessageClient
48
+ */
49
+ async broadcastAsTeammate(teamId, memberId, brokerPort, content, type) {
50
+ // Try to use the persistent client first
51
+ const persistentClient = getTeammateClient();
52
+ if (persistentClient && persistentClient.isConnected()) {
53
+ if (type === 'task_update') {
54
+ persistentClient.sendTaskUpdate('', '', content);
55
+ }
56
+ else {
57
+ persistentClient.broadcast(content);
58
+ }
59
+ return;
60
+ }
61
+ // Fallback: create temporary connection
62
+ return this.broadcastWithTempClient(teamId, memberId, brokerPort, content, type);
63
+ }
64
+ /**
65
+ * Fallback: broadcast with temporary MessageClient connection
66
+ */
67
+ async broadcastWithTempClient(teamId, memberId, brokerPort, content, type) {
68
+ return new Promise((resolve) => {
69
+ const client = new MessageClient(teamId, memberId, brokerPort);
70
+ let resolved = false;
71
+ const cleanup = () => {
72
+ if (!resolved) {
73
+ resolved = true;
74
+ client.disconnect().catch(() => { });
75
+ }
76
+ };
77
+ // Timeout after 5 seconds
78
+ const timeout = setTimeout(() => {
79
+ cleanup();
80
+ resolve(); // Resolve anyway, broadcast is fire-and-forget
81
+ }, 5000);
82
+ client.on('connected', () => {
83
+ if (type === 'task_update') {
84
+ client.sendTaskUpdate('', '', content);
85
+ }
86
+ else {
87
+ client.broadcast(content);
88
+ }
89
+ // Small delay before closing
90
+ setTimeout(() => {
91
+ clearTimeout(timeout);
92
+ cleanup();
93
+ resolve();
94
+ }, 300);
95
+ });
96
+ client.on('error', () => {
97
+ clearTimeout(timeout);
98
+ cleanup();
99
+ resolve(); // Resolve anyway, broadcast is fire-and-forget
100
+ });
101
+ client.on('disconnected', () => {
102
+ if (!resolved) {
103
+ clearTimeout(timeout);
104
+ cleanup();
105
+ resolve();
106
+ }
107
+ });
108
+ client.connect().catch(() => {
109
+ clearTimeout(timeout);
110
+ cleanup();
111
+ resolve();
112
+ });
113
+ });
114
+ }
115
+ checkPermission(memberPermissions, action) {
116
+ const permissionMap = {
117
+ createTask: 'canCreateTask',
118
+ assignTask: 'canAssignTask',
119
+ claimTask: 'canClaimTask',
120
+ completeTask: 'canCompleteTask',
121
+ deleteTask: 'canDeleteTask',
122
+ messageAll: 'canMessageAll',
123
+ messageDirect: 'canMessageDirect',
124
+ shutdownTeam: 'canShutdownTeam',
125
+ shutdownMember: 'canShutdownMember',
126
+ inviteMembers: 'canInviteMembers',
127
+ };
128
+ const permissionKey = permissionMap[action];
129
+ if (!permissionKey) {
130
+ return true;
131
+ }
132
+ return memberPermissions[permissionKey];
133
+ }
134
+ async execute(params) {
135
+ const memberId = process.env.XAGENT_MEMBER_ID;
136
+ // Role determined by action type:
137
+ // - create: caller is lead
138
+ // - other actions: if XAGENT_MEMBER_ID is not set, caller is lead
139
+ const isTeamLead = params.team_action === 'create' || !memberId;
140
+ const permissions = isTeamLead ? LEAD_PERMISSIONS : TEAMMATE_PERMISSIONS;
141
+ switch (params.team_action) {
142
+ case 'create':
143
+ return this.createTeam(params);
144
+ case 'spawn':
145
+ if (!this.checkPermission(permissions, 'inviteMembers')) {
146
+ return {
147
+ success: false,
148
+ message: 'Permission denied: Only team lead can invite new members',
149
+ };
150
+ }
151
+ return this.spawnTeammate(params);
152
+ case 'message': {
153
+ const canMessage = params.message?.to_member_id === 'broadcast'
154
+ ? this.checkPermission(permissions, 'messageAll')
155
+ : this.checkPermission(permissions, 'messageDirect');
156
+ if (!canMessage) {
157
+ return {
158
+ success: false,
159
+ message: 'Permission denied: You cannot send broadcast messages',
160
+ };
161
+ }
162
+ return this.sendTeamMessage(params);
163
+ }
164
+ case 'task_create':
165
+ if (!this.checkPermission(permissions, 'createTask')) {
166
+ return {
167
+ success: false,
168
+ message: 'Permission denied: You cannot create tasks',
169
+ };
170
+ }
171
+ return this.createTeamTask(params, memberId);
172
+ case 'task_update':
173
+ return this.updateTeamTask(params, memberId, permissions);
174
+ case 'task_delete':
175
+ if (!this.checkPermission(permissions, 'deleteTask')) {
176
+ return {
177
+ success: false,
178
+ message: 'Permission denied: Only lead can delete tasks',
179
+ };
180
+ }
181
+ return this.deleteTeamTask(params);
182
+ case 'task_list':
183
+ return this.listTeamTasks(params);
184
+ case 'shutdown':
185
+ if (!params.team_id) {
186
+ return { success: false, message: 'team_id is required for shutdown' };
187
+ }
188
+ {
189
+ const team = await this.store.getTeam(params.team_id);
190
+ if (!team) {
191
+ return { success: false, message: `Team ${params.team_id} not found` };
192
+ }
193
+ const actualMemberId = memberId || team.leadMemberId;
194
+ // Prevent shutting down self - use cleanup to shutdown entire team
195
+ if (params.member_id === 'self' || params.member_id === actualMemberId) {
196
+ return {
197
+ success: false,
198
+ message: 'Cannot shutdown yourself.',
199
+ };
200
+ }
201
+ // Prevent shutting down lead - lead should only be removed via cleanup
202
+ if (params.member_id === team.leadMemberId) {
203
+ return {
204
+ success: false,
205
+ message: 'Cannot shutdown team lead.',
206
+ };
207
+ }
208
+ }
209
+ if (!params.member_id) {
210
+ return {
211
+ success: false,
212
+ message: 'member_id is required for shutdown',
213
+ };
214
+ }
215
+ if (!this.checkPermission(permissions, 'shutdownMember')) {
216
+ return {
217
+ success: false,
218
+ message: 'Permission denied: Only team lead can shutdown other members',
219
+ };
220
+ }
221
+ return this.shutdownTeammate(params, params.member_id);
222
+ case 'cleanup':
223
+ if (!this.checkPermission(permissions, 'shutdownTeam')) {
224
+ return {
225
+ success: false,
226
+ message: 'Permission denied: Only team lead can cleanup the team',
227
+ };
228
+ }
229
+ return this.cleanupTeam(params);
230
+ case 'list_teams':
231
+ return this.listTeams();
232
+ case 'get_status':
233
+ return this.getTeamStatus(params);
234
+ default:
235
+ throw new Error(`Unknown team action: ${params.team_action}`);
236
+ }
237
+ }
238
+ async createTeam(params) {
239
+ if (!params.team_name) {
240
+ return { success: false, message: 'team_name is required' };
241
+ }
242
+ if (!params.teammates || params.teammates.length === 0) {
243
+ return { success: false, message: 'teammates is required' };
244
+ }
245
+ // 检查每个 teammate 配置
246
+ for (let i = 0; i < params.teammates.length; i++) {
247
+ const t = params.teammates[i];
248
+ if (!t.name) {
249
+ return { success: false, message: `teammates[${i}].name is required` };
250
+ }
251
+ if (!t.role) {
252
+ return { success: false, message: `teammates[${i}].role is required` };
253
+ }
254
+ if (!t.prompt) {
255
+ return { success: false, message: `teammates[${i}].prompt is required` };
256
+ }
257
+ }
258
+ const team = await this.store.createTeam(params.team_name || 'unnamed-team', process.env.XAGENT_SESSION_ID || 'lead', process.cwd());
259
+ const displayMode = 'auto';
260
+ const broker = await this.getBroker(team.teamId);
261
+ const brokerPort = broker.getPort();
262
+ console.log(colors.primaryBright(`\n${icons.rocket} Team "${team.teamName}" created`));
263
+ console.log(colors.textMuted(` ${icons.arrow} Broker: port ${brokerPort}`));
264
+ const spawnedMembers = [];
265
+ const initialTasks = [];
266
+ const leadMember = team.members[0];
267
+ const leadMemberId = leadMember?.memberId;
268
+ // Spawn teammates with lead ID
269
+ if (params.teammates && params.teammates.length > 0) {
270
+ console.log(colors.text(` ${icons.bullet} Members:`));
271
+ for (const teammateConfig of params.teammates) {
272
+ // Create initial task BEFORE spawning, so we can pass task ID to teammate
273
+ const task = await this.store.createTask(team.teamId, {
274
+ title: `Initial task for ${teammateConfig.name}`,
275
+ description: teammateConfig.prompt,
276
+ priority: 'high',
277
+ }, 'lead');
278
+ initialTasks.push({
279
+ taskId: task.taskId,
280
+ title: task.title,
281
+ assignee: '', // Will be set after spawn
282
+ });
283
+ // Spawn teammate with initial task ID and lead ID
284
+ const member = await this.spawner.spawnTeammate(team.teamId, teammateConfig, team.workDir, displayMode, brokerPort, task.taskId, // Pass initial task ID
285
+ leadMemberId // Pass lead member ID
286
+ );
287
+ spawnedMembers.push(member);
288
+ const displayName = member.name || member.memberId.slice(0, 8);
289
+ const statusIcon = member.status === 'active' ? '🟢' : '🟡';
290
+ console.log(` ${statusIcon} ${colors.primary(displayName)} ${colors.textMuted(`(${member.memberRole || member.role})`)}`);
291
+ // Mark task as in_progress and assign to the teammate
292
+ await this.store.updateTask(team.teamId, task.taskId, {
293
+ status: 'in_progress',
294
+ assignee: member.memberId,
295
+ }, task.version);
296
+ // Update initialTasks with assignee
297
+ initialTasks[initialTasks.length - 1].assignee = member.memberId;
298
+ }
299
+ }
300
+ return {
301
+ success: true,
302
+ message: `Team "${team.teamName}" created successfully with ${initialTasks.length} initial tasks`,
303
+ result: {
304
+ team_id: team.teamId,
305
+ team_name: team.teamName,
306
+ display_mode: displayMode,
307
+ lead_id: leadMemberId,
308
+ your_role: 'lead',
309
+ your_member_id: leadMemberId,
310
+ broker_port: brokerPort,
311
+ is_team_lead: true,
312
+ members: spawnedMembers.map((m) => ({
313
+ id: m.memberId,
314
+ name: m.name,
315
+ role: m.memberRole || m.role,
316
+ display_mode: m.displayMode,
317
+ })),
318
+ initial_tasks: initialTasks,
319
+ },
320
+ };
321
+ }
322
+ async spawnTeammate(params) {
323
+ if (!params.team_id) {
324
+ return { success: false, message: 'team_id is required' };
325
+ }
326
+ if (!params.teammates || params.teammates.length === 0) {
327
+ return { success: false, message: 'teammates[0] is required' };
328
+ }
329
+ // 只检查第一个 teammate
330
+ const t = params.teammates[0];
331
+ if (!t.name) {
332
+ return { success: false, message: 'teammates[0].name is required' };
333
+ }
334
+ if (!t.role) {
335
+ return { success: false, message: 'teammates[0].role is required' };
336
+ }
337
+ if (!t.prompt) {
338
+ return { success: false, message: 'teammates[0].prompt is required' };
339
+ }
340
+ const team = await this.store.getTeam(params.team_id);
341
+ if (!team) {
342
+ return { success: false, message: `Team ${params.team_id} not found` };
343
+ }
344
+ const displayMode = 'auto';
345
+ const broker = await this.getBroker(params.team_id);
346
+ const brokerPort = broker.getPort();
347
+ const leadMemberId = team.leadMemberId;
348
+ // 只 spawn 一个 teammate
349
+ const teammateConfig = params.teammates[0];
350
+ const member = await this.spawner.spawnTeammate(params.team_id, teammateConfig, team.workDir, displayMode, brokerPort, undefined, // No initial task ID for dynamic spawn
351
+ leadMemberId);
352
+ const displayName = member.name || member.memberId.slice(0, 8);
353
+ const statusIcon = member.status === 'active' ? '🟢' : '🟡';
354
+ console.log(` ${statusIcon} ${colors.success('Spawned:')} ${colors.primary(displayName)} ${colors.textMuted(`(${member.memberRole || member.role})`)}`);
355
+ return {
356
+ success: true,
357
+ message: `Spawned teammate: ${displayName}`,
358
+ result: {
359
+ team_id: params.team_id,
360
+ member_id: member.memberId,
361
+ name: member.name,
362
+ role: member.memberRole || member.role,
363
+ },
364
+ };
365
+ }
366
+ async sendTeamMessage(params) {
367
+ if (!params.team_id) {
368
+ throw new Error('team_id is required');
369
+ }
370
+ if (!params.message) {
371
+ throw new Error('message is required');
372
+ }
373
+ const team = await this.store.getTeam(params.team_id);
374
+ if (!team) {
375
+ throw new Error(`Team ${params.team_id} not found`);
376
+ }
377
+ // Determine fromMemberId: use env var for teammates, or team.leadMemberId for lead
378
+ const envMemberId = process.env.XAGENT_MEMBER_ID;
379
+ const envBrokerPort = process.env.XAGENT_BROKER_PORT;
380
+ const fromMemberId = envMemberId || team.leadMemberId;
381
+ // Resolve target: convert 'lead' to actual leadMemberId for direct messages
382
+ let targetMemberId = params.message.to_member_id || 'broadcast';
383
+ if (targetMemberId === 'lead') {
384
+ targetMemberId = team.leadMemberId;
385
+ }
386
+ // Check if this is a teammate process (has broker port env var)
387
+ // Teammate should use MessageClient to connect to lead's broker
388
+ if (envBrokerPort && envMemberId) {
389
+ return this.sendMessageAsTeammate(params.team_id, envMemberId, parseInt(envBrokerPort, 10), targetMemberId, params.message.content);
390
+ }
391
+ // Lead process: use broker directly
392
+ const broker = await this.getBroker(params.team_id);
393
+ try {
394
+ const { message, deliveryInfo } = await broker.sendMessageWithAck(fromMemberId, targetMemberId, params.message.content);
395
+ const isBroadcast = params.message.to_member_id === 'broadcast' || !params.message.to_member_id;
396
+ const ackCount = Array.isArray(deliveryInfo)
397
+ ? deliveryInfo.filter((d) => d.status === 'acknowledged').length
398
+ : deliveryInfo.status === 'acknowledged'
399
+ ? 1
400
+ : 0;
401
+ const totalCount = Array.isArray(deliveryInfo) ? deliveryInfo.length : 1;
402
+ return {
403
+ success: true,
404
+ message: isBroadcast
405
+ ? `Message broadcasted and acknowledged by ${ackCount}/${totalCount} members`
406
+ : `Message delivered and acknowledged`,
407
+ result: {
408
+ message_id: message.messageId,
409
+ delivered_to: message.toMemberId,
410
+ delivery_status: Array.isArray(deliveryInfo)
411
+ ? deliveryInfo.map((d) => ({
412
+ member_id: d.acknowledgedBy?.[0],
413
+ status: d.status,
414
+ }))
415
+ : { status: deliveryInfo.status, acknowledged_at: deliveryInfo.acknowledgedAt },
416
+ },
417
+ };
418
+ }
419
+ catch (error) {
420
+ return {
421
+ success: false,
422
+ message: `Failed to send message: ${error instanceof Error ? error.message : String(error)}`,
423
+ result: undefined,
424
+ };
425
+ }
426
+ }
427
+ /**
428
+ * Send message as teammate using persistent MessageClient
429
+ */
430
+ async sendMessageAsTeammate(teamId, memberId, brokerPort, targetMemberId, content) {
431
+ // Try to use the persistent client first
432
+ const persistentClient = getTeammateClient();
433
+ if (persistentClient && persistentClient.isConnected()) {
434
+ // Send message using appropriate method
435
+ if (targetMemberId === 'broadcast') {
436
+ persistentClient.broadcast(content);
437
+ }
438
+ else {
439
+ persistentClient.sendDirect(targetMemberId, content);
440
+ }
441
+ const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
442
+ return {
443
+ success: true,
444
+ message: `Message sent to ${targetMemberId}`,
445
+ result: {
446
+ message_id: messageId,
447
+ delivered_to: targetMemberId,
448
+ delivery_status: { status: 'sent' },
449
+ },
450
+ };
451
+ }
452
+ // Fallback: create temporary connection if persistent client not available
453
+ return this.sendMessageWithTempClient(teamId, memberId, brokerPort, targetMemberId, content);
454
+ }
455
+ /**
456
+ * Fallback: send message with temporary MessageClient connection
457
+ */
458
+ async sendMessageWithTempClient(teamId, memberId, brokerPort, targetMemberId, content) {
459
+ return new Promise((resolve) => {
460
+ const client = new MessageClient(teamId, memberId, brokerPort);
461
+ let resolved = false;
462
+ const cleanup = () => {
463
+ if (!resolved) {
464
+ resolved = true;
465
+ client.disconnect().catch(() => { });
466
+ }
467
+ };
468
+ // Timeout after 10 seconds
469
+ const timeout = setTimeout(() => {
470
+ cleanup();
471
+ resolve({
472
+ success: false,
473
+ message: 'Failed to send message: timeout',
474
+ result: undefined,
475
+ });
476
+ }, 10000);
477
+ client.on('connected', () => {
478
+ // Send message using appropriate method
479
+ if (targetMemberId === 'broadcast') {
480
+ client.broadcast(content);
481
+ }
482
+ else {
483
+ client.sendDirect(targetMemberId, content);
484
+ }
485
+ const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
486
+ // Give a small delay for the message to be sent before closing
487
+ setTimeout(() => {
488
+ clearTimeout(timeout);
489
+ cleanup();
490
+ resolve({
491
+ success: true,
492
+ message: `Message sent to ${targetMemberId}`,
493
+ result: {
494
+ message_id: messageId,
495
+ delivered_to: targetMemberId,
496
+ delivery_status: { status: 'sent' },
497
+ },
498
+ });
499
+ }, 500);
500
+ });
501
+ client.on('error', (err) => {
502
+ clearTimeout(timeout);
503
+ cleanup();
504
+ resolve({
505
+ success: false,
506
+ message: `Failed to send message: ${err.message}`,
507
+ result: undefined,
508
+ });
509
+ });
510
+ client.on('disconnected', () => {
511
+ if (!resolved) {
512
+ clearTimeout(timeout);
513
+ cleanup();
514
+ resolve({
515
+ success: false,
516
+ message: 'Failed to send message: disconnected from broker',
517
+ result: undefined,
518
+ });
519
+ }
520
+ });
521
+ client.connect().catch((err) => {
522
+ clearTimeout(timeout);
523
+ cleanup();
524
+ resolve({
525
+ success: false,
526
+ message: `Failed to connect to broker: ${err.message}`,
527
+ result: undefined,
528
+ });
529
+ });
530
+ });
531
+ }
532
+ async getMessageDeliveryInfo(teamId, messageId) {
533
+ const broker = this.brokers.get(teamId);
534
+ if (!broker) {
535
+ return undefined;
536
+ }
537
+ return broker.getDeliveryInfo(messageId);
538
+ }
539
+ async createTeamTask(params, createdBy) {
540
+ if (!params.team_id) {
541
+ throw new Error('team_id is required');
542
+ }
543
+ if (!params.task_config) {
544
+ throw new Error('task_config is required');
545
+ }
546
+ // Get team to resolve leadMemberId if createdBy is not provided
547
+ const team = await this.store.getTeam(params.team_id);
548
+ if (!team) {
549
+ throw new Error(`Team ${params.team_id} not found`);
550
+ }
551
+ const actualCreatedBy = createdBy || team.leadMemberId;
552
+ const task = await this.store.createTask(params.team_id, params.task_config, actualCreatedBy);
553
+ console.log(colors.success(`✓ Task created: ${task.title} (${task.taskId})`));
554
+ await this.broadcastTaskUpdate(params.team_id, actualCreatedBy, task.taskId, 'created', {
555
+ title: task.title
556
+ });
557
+ return {
558
+ success: true,
559
+ message: `Task "${task.title}" created successfully`,
560
+ result: {
561
+ task_id: task.taskId,
562
+ title: task.title,
563
+ description: task.description,
564
+ status: task.status,
565
+ priority: task.priority,
566
+ assignee: task.assignee,
567
+ dependencies: task.dependencies,
568
+ created_at: task.createdAt,
569
+ },
570
+ };
571
+ }
572
+ async updateTeamTask(params, memberId, permissions) {
573
+ if (!params.team_id) {
574
+ throw new Error('team_id is required');
575
+ }
576
+ if (!params.task_update) {
577
+ throw new Error('task_update is required');
578
+ }
579
+ // Get team to resolve leadMemberId if memberId is not provided
580
+ const team = await this.store.getTeam(params.team_id);
581
+ if (!team) {
582
+ throw new Error(`Team ${params.team_id} not found`);
583
+ }
584
+ const actualMemberId = memberId || team.leadMemberId;
585
+ const { task_id, action, result } = params.task_update;
586
+ if (action === 'claim') {
587
+ if (!this.checkPermission(permissions, 'claimTask')) {
588
+ return {
589
+ success: false,
590
+ message: 'Permission denied: You cannot claim tasks',
591
+ };
592
+ }
593
+ try {
594
+ const claimedTask = await this.store.claimTask(params.team_id, task_id, actualMemberId);
595
+ if (!claimedTask) {
596
+ return {
597
+ success: false,
598
+ message: `Task ${task_id} not found`,
599
+ };
600
+ }
601
+ await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'claimed', {
602
+ title: claimedTask.title,
603
+ assignee: claimedTask.assignee
604
+ });
605
+ return {
606
+ success: true,
607
+ message: `Task ${task_id} claimed successfully`,
608
+ result: {
609
+ task_id: claimedTask.taskId,
610
+ status: claimedTask.status,
611
+ assignee: claimedTask.assignee,
612
+ },
613
+ };
614
+ }
615
+ catch (error) {
616
+ const errorMessage = error instanceof Error ? error.message : String(error);
617
+ return {
618
+ success: false,
619
+ message: errorMessage,
620
+ };
621
+ }
622
+ }
623
+ if (action === 'complete') {
624
+ if (!this.checkPermission(permissions, 'completeTask')) {
625
+ return {
626
+ success: false,
627
+ message: 'Permission denied: You cannot complete tasks',
628
+ };
629
+ }
630
+ const existingTask = await this.store.getTask(params.team_id, task_id);
631
+ if (!existingTask) {
632
+ return { success: false, message: `Task ${task_id} not found` };
633
+ }
634
+ const task = await this.store.updateTask(params.team_id, task_id, {
635
+ status: 'completed',
636
+ result,
637
+ }, existingTask.version);
638
+ if (!task) {
639
+ return { success: false, message: `Task ${task_id} update failed` };
640
+ }
641
+ await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'completed', {
642
+ title: task.title,
643
+ result: task.result
644
+ });
645
+ return {
646
+ success: true,
647
+ message: `Task ${task_id} completed`,
648
+ result: {
649
+ task_id: task.taskId,
650
+ status: task.status,
651
+ result: task.result,
652
+ },
653
+ };
654
+ }
655
+ if (action === 'release') {
656
+ const existingTask = await this.store.getTask(params.team_id, task_id);
657
+ if (!existingTask) {
658
+ return { success: false, message: `Task ${task_id} not found` };
659
+ }
660
+ const task = await this.store.updateTask(params.team_id, task_id, {
661
+ status: 'pending',
662
+ assignee: undefined,
663
+ }, existingTask.version);
664
+ if (!task) {
665
+ return { success: false, message: `Task ${task_id} update failed` };
666
+ }
667
+ await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'released', {
668
+ title: task.title
669
+ });
670
+ return {
671
+ success: true,
672
+ message: `Task ${task_id} released back to pool`,
673
+ result: {
674
+ task_id: task.taskId,
675
+ status: task.status,
676
+ },
677
+ };
678
+ }
679
+ throw new Error(`Unknown task action: ${action}`);
680
+ }
681
+ async deleteTeamTask(params) {
682
+ if (!params.team_id) {
683
+ throw new Error('team_id is required');
684
+ }
685
+ const team = await this.store.getTeam(params.team_id);
686
+ if (!team) {
687
+ throw new Error(`Team ${params.team_id} not found`);
688
+ }
689
+ const memberId = process.env.XAGENT_MEMBER_ID || team.leadMemberId;
690
+ const taskId = params.task_update?.task_id;
691
+ if (!taskId) {
692
+ throw new Error('task_id is required for deletion');
693
+ }
694
+ const existingTask = await this.store.getTask(params.team_id, taskId);
695
+ if (!existingTask) {
696
+ return { success: false, message: `Task ${taskId} not found` };
697
+ }
698
+ const deleted = await this.store.deleteTask(params.team_id, taskId, existingTask.version);
699
+ if (!deleted) {
700
+ return { success: false, message: `Task ${taskId} was modified by another member` };
701
+ }
702
+ console.log(colors.success(`✓ Task ${taskId} deleted`));
703
+ await this.broadcastTaskUpdate(params.team_id, memberId, taskId, 'deleted', {
704
+ title: existingTask.title
705
+ });
706
+ return {
707
+ success: true,
708
+ message: `Task ${taskId} deleted`,
709
+ result: { task_id: taskId },
710
+ };
711
+ }
712
+ async listTeamTasks(params) {
713
+ if (!params.team_id) {
714
+ throw new Error('team_id is required');
715
+ }
716
+ const team = await this.store.getTeam(params.team_id);
717
+ if (!team) {
718
+ throw new Error(`Team ${params.team_id} not found`);
719
+ }
720
+ const filter = params.task_filter || 'all';
721
+ let tasks;
722
+ switch (filter) {
723
+ case 'pending':
724
+ tasks = (await this.store.getTasks(params.team_id)).filter((t) => t.status === 'pending');
725
+ break;
726
+ case 'available':
727
+ tasks = await this.store.getAvailableTasks(params.team_id);
728
+ break;
729
+ case 'in_progress':
730
+ tasks = (await this.store.getTasks(params.team_id)).filter((t) => t.status === 'in_progress');
731
+ break;
732
+ case 'completed':
733
+ tasks = (await this.store.getTasks(params.team_id)).filter((t) => t.status === 'completed');
734
+ break;
735
+ default:
736
+ tasks = await this.store.getTasks(params.team_id);
737
+ }
738
+ return {
739
+ success: true,
740
+ message: `Found ${tasks.length} tasks (filter: ${filter})`,
741
+ result: {
742
+ team_id: params.team_id,
743
+ filter,
744
+ total_count: tasks.length,
745
+ tasks: tasks.map((t) => ({
746
+ task_id: t.taskId,
747
+ title: t.title,
748
+ description: t.description,
749
+ status: t.status,
750
+ priority: t.priority,
751
+ assignee: t.assignee,
752
+ dependencies: t.dependencies,
753
+ created_at: t.createdAt,
754
+ updated_at: t.updatedAt,
755
+ result: t.result,
756
+ })),
757
+ },
758
+ };
759
+ }
760
+ async shutdownTeammate(params, memberId) {
761
+ if (!params.team_id) {
762
+ throw new Error('team_id is required');
763
+ }
764
+ if (!memberId) {
765
+ throw new Error('member_id is required');
766
+ }
767
+ const result = await this.spawner.shutdownTeammate(params.team_id, memberId);
768
+ if (result.success) {
769
+ console.log(colors.textMuted(`${icons.check} Teammate ${memberId.slice(0, 8)} shut down${result.reason ? `: ${result.reason}` : ''}`));
770
+ }
771
+ else {
772
+ console.log(colors.error(`${icons.cross} Failed to shutdown ${memberId.slice(0, 8)}: ${result.reason}`));
773
+ }
774
+ return {
775
+ success: result.success,
776
+ message: result.success
777
+ ? `Teammate ${memberId} shut down${result.reason ? `: ${result.reason}` : ''}`
778
+ : `Failed to shutdown ${memberId}: ${result.reason}`,
779
+ result: { member_id: memberId, reason: result.reason },
780
+ };
781
+ }
782
+ async cleanupTeam(params) {
783
+ if (!params.team_id) {
784
+ throw new Error('team_id is required');
785
+ }
786
+ const team = await this.store.getTeam(params.team_id);
787
+ if (!team) {
788
+ throw new Error(`Team ${params.team_id} not found`);
789
+ }
790
+ // Auto-shutdown all active teammates (role !== 'lead')
791
+ const activeTeammates = team.members.filter((m) => m.status === 'active' && m.role !== 'lead');
792
+ const shutdownResults = [];
793
+ for (const teammate of activeTeammates) {
794
+ const result = await this.spawner.shutdownTeammate(params.team_id, teammate.memberId);
795
+ shutdownResults.push({
796
+ memberId: teammate.memberId,
797
+ success: result.success,
798
+ reason: result.reason,
799
+ });
800
+ const memberName = teammate.name || teammate.memberId.slice(0, 8);
801
+ if (result.success) {
802
+ console.log(colors.textMuted(` ${icons.check} ${memberName}`));
803
+ }
804
+ else {
805
+ console.log(colors.error(` ${icons.cross} ${memberName}: ${result.reason}`));
806
+ }
807
+ }
808
+ // Stop the message broker
809
+ const broker = this.brokers.get(params.team_id);
810
+ if (broker) {
811
+ await broker.stop();
812
+ this.brokers.delete(params.team_id);
813
+ removeMessageBroker(params.team_id);
814
+ }
815
+ await this.store.deleteTeam(params.team_id);
816
+ console.log(colors.success(`\n${icons.check} Team cleaned up (${shutdownResults.filter(r => r.success).length}/${activeTeammates.length} teammates shutdown)`));
817
+ return {
818
+ success: true,
819
+ message: `Team ${params.team_id} cleaned up (${shutdownResults.filter(r => r.success).length}/${activeTeammates.length} teammates auto-shutdown)`,
820
+ result: {
821
+ team_id: params.team_id,
822
+ auto_shutdown: shutdownResults,
823
+ },
824
+ };
825
+ }
826
+ async listTeams() {
827
+ const teams = await this.store.listTeams();
828
+ return {
829
+ success: true,
830
+ message: `Found ${teams.length} team(s)`,
831
+ result: {
832
+ total_count: teams.length,
833
+ teams: teams.map((t) => ({
834
+ team_id: t.teamId,
835
+ team_name: t.teamName,
836
+ member_count: t.members.length,
837
+ status: t.status,
838
+ created_at: t.createdAt,
839
+ work_dir: t.workDir,
840
+ })),
841
+ },
842
+ };
843
+ }
844
+ async getTeamStatus(params) {
845
+ if (!params.team_id) {
846
+ throw new Error('team_id is required');
847
+ }
848
+ const status = await this.store.getTeamStatus(params.team_id);
849
+ if (!status.team) {
850
+ throw new Error(`Team ${params.team_id} not found`);
851
+ }
852
+ const envMemberId = process.env.XAGENT_MEMBER_ID;
853
+ const isTeamLead = !envMemberId;
854
+ const yourRole = isTeamLead ? 'lead' : 'teammate';
855
+ const yourMemberId = envMemberId || status.team.leadMemberId;
856
+ return {
857
+ success: true,
858
+ message: `Team status retrieved`,
859
+ result: {
860
+ team_id: params.team_id,
861
+ team_name: status.team.teamName,
862
+ status: status.team.status,
863
+ your_role: yourRole,
864
+ your_member_id: yourMemberId,
865
+ member_count: status.memberCount,
866
+ members: status.team.members.map((m) => ({
867
+ id: m.memberId,
868
+ name: m.name,
869
+ role: m.memberRole || m.role,
870
+ status: m.status,
871
+ display_mode: m.displayMode,
872
+ })),
873
+ active_task_count: status.activeTaskCount,
874
+ completed_task_count: status.completedTaskCount,
875
+ created_at: status.team.createdAt,
876
+ },
877
+ };
878
+ }
879
+ }
880
+ let teamCoordinatorInstance = null;
881
+ export function getTeamCoordinator() {
882
+ if (!teamCoordinatorInstance) {
883
+ teamCoordinatorInstance = new TeamCoordinator();
884
+ }
885
+ return teamCoordinatorInstance;
886
+ }
887
+ //# sourceMappingURL=team-coordinator.js.map