@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
package/dist/session.js CHANGED
@@ -67,9 +67,28 @@ export class InteractiveSession {
67
67
  heartbeatTimeout = null;
68
68
  heartbeatTimeoutMs = 300000; // 5 minutes timeout for long AI responses
69
69
  lastActivityTime = Date.now();
70
+ teammateMessageQueue = [];
70
71
  // SDK response handling for approvals and questions
71
72
  approvalPromises = new Map();
72
73
  questionPromises = new Map();
74
+ // Team mode properties
75
+ isTeamMode = false;
76
+ teamId = null;
77
+ memberId = null;
78
+ memberName = null;
79
+ memberRole = null;
80
+ leadId = null;
81
+ teamStore = null;
82
+ messageClient = null;
83
+ spawnPrompt = null;
84
+ brokerPort = null;
85
+ initialTaskId = null;
86
+ // Operation lock for preventing concurrent operations
87
+ _isOperationInProgress = false;
88
+ _shutdownResolver = null;
89
+ // Queue processing lock to prevent race conditions
90
+ isProcessingQueue = false;
91
+ queueProcessingPromise = null;
73
92
  constructor(indentLevel = 0) {
74
93
  this.rl = readline.createInterface({
75
94
  input: process.stdin,
@@ -83,6 +102,47 @@ export class InteractiveSession {
83
102
  this.conversationManager = getConversationManager();
84
103
  this.sessionManager = getSessionManager(process.cwd());
85
104
  this.slashCommandHandler = new SlashCommandHandler();
105
+ // Check if running in Team Mode
106
+ if (process.env.XAGENT_TEAM_MODE === 'true') {
107
+ this.isTeamMode = true;
108
+ this.teamId = process.env.XAGENT_TEAM_ID || null;
109
+ this.memberId = process.env.XAGENT_MEMBER_ID || null;
110
+ this.memberName = process.env.XAGENT_MEMBER_NAME || null;
111
+ this.memberRole = 'teammate'; // Role is now determined by tool response, not env var
112
+ this.leadId = process.env.XAGENT_LEAD_ID || null;
113
+ this.spawnPrompt = process.env.XAGENT_SPAWN_PROMPT || null;
114
+ this.brokerPort = process.env.XAGENT_BROKER_PORT ? parseInt(process.env.XAGENT_BROKER_PORT, 10) : null;
115
+ this.initialTaskId = process.env.XAGENT_INITIAL_TASK_ID || null;
116
+ // Show team role info in welcome message (compact format)
117
+ console.log(colors.textMuted(`[Team] ${this.memberName} (${this.memberRole})`));
118
+ // Import and initialize team components
119
+ import('./team-manager/index.js').then(async ({ getTeamStore, MessageClient, setTeammateClient }) => {
120
+ this.teamStore = getTeamStore();
121
+ // Connect to message broker if port is available
122
+ if (this.brokerPort && this.teamId && this.memberId) {
123
+ this.messageClient = new MessageClient(this.teamId, this.memberId, this.brokerPort);
124
+ // Save to global singleton for TeamCoordinator to use
125
+ setTeammateClient(this.messageClient);
126
+ this.messageClient.on('message', (msg) => {
127
+ this.handleTeamMessage(msg);
128
+ });
129
+ this.messageClient.on('connected', () => {
130
+ // Silently connected - no output needed
131
+ });
132
+ this.messageClient.on('disconnected', () => {
133
+ // Silently disconnected - no output needed
134
+ });
135
+ try {
136
+ await this.messageClient.connect();
137
+ }
138
+ catch (err) {
139
+ console.error('[Team] Failed to connect to message broker:', err);
140
+ }
141
+ }
142
+ }).catch(() => {
143
+ // Ignore import errors
144
+ });
145
+ }
86
146
  // Register /clear callback, clear local conversation when clearing dialogue
87
147
  this.slashCommandHandler.setClearCallback(() => {
88
148
  this.conversation = [];
@@ -175,6 +235,208 @@ export class InteractiveSession {
175
235
  const toolRegistry = getToolRegistry();
176
236
  await toolRegistry.setSdkMode(true, adapter);
177
237
  }
238
+ /**
239
+ * Handle incoming team message from socket.
240
+ * Messages are queued and must be explicitly processed by the agent.
241
+ */
242
+ handleTeamMessage(msg) {
243
+ if (!msg || !msg.content)
244
+ return;
245
+ let teamMessage;
246
+ if (msg.type === 'task_update') {
247
+ let taskInfo;
248
+ try {
249
+ const content = typeof msg.content === 'string' ? JSON.parse(msg.content) : msg.content;
250
+ taskInfo = `Task ${content.action}: ${content.title || content.taskId}`;
251
+ if (content.assignee) {
252
+ taskInfo += ` (assignee: ${content.assignee})`;
253
+ }
254
+ if (content.result) {
255
+ taskInfo += ` - Result: ${content.result.substring(0, 100)}...`;
256
+ }
257
+ }
258
+ catch {
259
+ taskInfo = `Task update: ${msg.content}`;
260
+ }
261
+ teamMessage = {
262
+ role: 'user',
263
+ content: `<system-notification type="task_update">${taskInfo}</system-notification>`,
264
+ timestamp: msg.timestamp || Date.now()
265
+ };
266
+ }
267
+ else {
268
+ teamMessage = {
269
+ role: 'user',
270
+ content: `<teammate-message from="${msg.fromMemberId}" type="${msg.type}">${msg.content}</teammate-message>`,
271
+ timestamp: msg.timestamp || Date.now()
272
+ };
273
+ }
274
+ this.teammateMessageQueue.push(teamMessage);
275
+ // Only start processing if not already processing
276
+ if (!this.isProcessingQueue && !this._isOperationInProgress) {
277
+ this.startQueueProcessing();
278
+ }
279
+ }
280
+ /**
281
+ * Start queue processing with proper locking.
282
+ */
283
+ startQueueProcessing() {
284
+ // Prevent multiple queue processing coroutines
285
+ if (this.isProcessingQueue) {
286
+ return;
287
+ }
288
+ this.isProcessingQueue = true;
289
+ this.queueProcessingPromise = this.processMessageQueue();
290
+ // Handle completion and errors
291
+ this.queueProcessingPromise
292
+ .catch((error) => {
293
+ console.error('[Team] Queue processing error:', error);
294
+ })
295
+ .finally(() => {
296
+ this.isProcessingQueue = false;
297
+ this.queueProcessingPromise = null;
298
+ });
299
+ }
300
+ /**
301
+ * Process queued teammate messages one by one.
302
+ * This method should only be called from startQueueProcessing.
303
+ */
304
+ async processMessageQueue() {
305
+ while (this.teammateMessageQueue.length > 0) {
306
+ // Check if operation is in progress (e.g., user is typing or another operation)
307
+ if (this._isOperationInProgress) {
308
+ // Wait a bit and retry
309
+ await new Promise(resolve => setTimeout(resolve, 100));
310
+ continue;
311
+ }
312
+ const message = this.teammateMessageQueue.shift();
313
+ this.conversation.push(message);
314
+ try {
315
+ if (this.remoteAIClient) {
316
+ await this.generateRemoteResponse(0);
317
+ }
318
+ else {
319
+ await this.generateResponse(0);
320
+ }
321
+ }
322
+ catch (error) {
323
+ console.error('[Team] Failed to process teammate message:', error);
324
+ // Continue processing remaining messages even if one fails
325
+ }
326
+ }
327
+ }
328
+ /**
329
+ * Get team mode status.
330
+ */
331
+ getIsTeamMode() {
332
+ return this.isTeamMode;
333
+ }
334
+ /**
335
+ * Get team ID.
336
+ */
337
+ getTeamId() {
338
+ return this.teamId;
339
+ }
340
+ /**
341
+ * Get member ID.
342
+ */
343
+ getMemberId() {
344
+ return this.memberId;
345
+ }
346
+ /**
347
+ * Get member name.
348
+ */
349
+ getMemberName() {
350
+ return this.memberName;
351
+ }
352
+ /**
353
+ * Get lead ID.
354
+ */
355
+ getLeadId() {
356
+ return this.leadId;
357
+ }
358
+ /**
359
+ * Get spawn prompt (for team mode).
360
+ */
361
+ getSpawnPrompt() {
362
+ return this.spawnPrompt;
363
+ }
364
+ /**
365
+ * Get teammate message queue info.
366
+ */
367
+ getTeammateMessageQueueInfo() {
368
+ return {
369
+ length: this.teammateMessageQueue.length,
370
+ };
371
+ }
372
+ /**
373
+ * Get and clear all teammate messages from queue.
374
+ * Returns all pending messages and clears the queue.
375
+ */
376
+ popTeammateMessages() {
377
+ const messages = [...this.teammateMessageQueue];
378
+ this.teammateMessageQueue = [];
379
+ return messages;
380
+ }
381
+ /**
382
+ * Connect to team message broker (for lead agent after creating a team).
383
+ * This allows the lead agent to receive real-time messages from teammates.
384
+ */
385
+ async connectToTeamBroker(teamId, memberId, brokerPort) {
386
+ // Skip if already connected
387
+ if (this.messageClient) {
388
+ return;
389
+ }
390
+ this.isTeamMode = true;
391
+ this.teamId = teamId;
392
+ this.memberId = memberId;
393
+ this.memberRole = 'lead';
394
+ this.brokerPort = brokerPort;
395
+ try {
396
+ const { getTeamStore, MessageClient } = await import('./team-manager/index.js');
397
+ this.teamStore = getTeamStore();
398
+ this.messageClient = new MessageClient(teamId, memberId, brokerPort);
399
+ this.messageClient.on('message', (msg) => {
400
+ this.handleTeamMessage(msg);
401
+ });
402
+ this.messageClient.on('connected', () => {
403
+ // Silently connected - no output needed
404
+ });
405
+ this.messageClient.on('disconnected', () => {
406
+ // Silently disconnected - no output needed
407
+ });
408
+ await this.messageClient.connect();
409
+ }
410
+ catch (err) {
411
+ console.error(colors.error('[Team] Failed to connect to message broker:'), err);
412
+ throw err; // Re-throw to let caller handle it
413
+ }
414
+ }
415
+ /**
416
+ * Cleanup team mode resources.
417
+ */
418
+ async cleanupTeamMode() {
419
+ // Clear teammate's persistent MessageClient if exists
420
+ const { clearTeammateClient } = await import('./team-manager/index.js');
421
+ clearTeammateClient();
422
+ if (this.messageClient) {
423
+ await this.messageClient.disconnect();
424
+ this.messageClient = null;
425
+ }
426
+ if (this.teamStore && this.teamId && this.memberId) {
427
+ try {
428
+ await this.teamStore.updateMember(this.teamId, this.memberId, {
429
+ status: 'shutdown',
430
+ lastActivity: Date.now()
431
+ });
432
+ }
433
+ catch {
434
+ // Ignore cleanup errors
435
+ }
436
+ }
437
+ // Reset team mode flag to restore thinking spinner
438
+ this.isTeamMode = false;
439
+ }
178
440
  /**
179
441
  * Get SDK mode status.
180
442
  */
@@ -211,7 +473,17 @@ export class InteractiveSession {
211
473
  const skillInvoker = (await import('./skill-invoker.js')).getSkillInvoker();
212
474
  await skillInvoker.reload();
213
475
  const toolRegistry = getToolRegistry();
214
- const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
476
+ // Build team context if running in team mode
477
+ const teamContext = this.isTeamMode ? {
478
+ teamId: this.teamId,
479
+ memberId: this.memberId,
480
+ memberName: this.memberName,
481
+ memberRole: this.memberRole,
482
+ leadId: this.leadId || undefined,
483
+ brokerPort: this.brokerPort || undefined,
484
+ initialTaskId: this.initialTaskId || undefined,
485
+ } : undefined;
486
+ const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager, teamContext);
215
487
  // Use the current agent's original system prompt as base
216
488
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
217
489
  const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
@@ -337,6 +609,28 @@ export class InteractiveSession {
337
609
  this._shutdownResolver = resolve;
338
610
  });
339
611
  }
612
+ async sendInitialPrompt(prompt) {
613
+ console.log(colors.textMuted(`\n📋 Initial prompt: ${prompt}\n`));
614
+ const userMessage = {
615
+ role: 'user',
616
+ content: prompt,
617
+ timestamp: Date.now(),
618
+ };
619
+ this.conversation.push(userMessage);
620
+ await this.conversationManager.addMessage(userMessage);
621
+ const thinkingConfig = this.configManager.getThinkingConfig();
622
+ let thinkingTokens = 0;
623
+ if (thinkingConfig.enabled) {
624
+ const thinkingMode = detectThinkingKeywords(prompt);
625
+ thinkingTokens = getThinkingTokens(thinkingMode);
626
+ }
627
+ if (this.remoteAIClient) {
628
+ await this.generateRemoteResponse(thinkingTokens);
629
+ }
630
+ else {
631
+ await this.generateResponse(thinkingTokens);
632
+ }
633
+ }
340
634
  async initialize() {
341
635
  logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
342
636
  try {
@@ -790,6 +1084,18 @@ export class InteractiveSession {
790
1084
  if (this._isShuttingDown) {
791
1085
  return;
792
1086
  }
1087
+ // Check teammate message queue when agent becomes idle
1088
+ if (this.teammateMessageQueue.length > 0) {
1089
+ await this.processMessageQueue();
1090
+ }
1091
+ // If running in team mode with initial prompt, send it immediately and skip user input
1092
+ if (this.spawnPrompt) {
1093
+ const prompt = this.spawnPrompt;
1094
+ this.spawnPrompt = null;
1095
+ await this.sendInitialPrompt(prompt);
1096
+ this.promptLoop();
1097
+ return;
1098
+ }
793
1099
  // In SDK mode, use a different input loop
794
1100
  if (this.isSdkMode) {
795
1101
  await this.sdkPromptLoop();
@@ -1323,7 +1629,17 @@ export class InteractiveSession {
1323
1629
  }
1324
1630
  const toolRegistry = getToolRegistry();
1325
1631
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
1326
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
1632
+ // Build team context if running in team mode
1633
+ const teamContext = this.isTeamMode ? {
1634
+ teamId: this.teamId,
1635
+ memberId: this.memberId,
1636
+ memberName: this.memberName,
1637
+ memberRole: this.memberRole,
1638
+ leadId: this.leadId || undefined,
1639
+ brokerPort: this.brokerPort || undefined,
1640
+ initialTaskId: this.initialTaskId || undefined,
1641
+ } : undefined;
1642
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, undefined, teamContext);
1327
1643
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1328
1644
  const result = await this.contextCompressor.compressContext(this.conversation, enhancedSystemPrompt, compressionConfig);
1329
1645
  if (result.wasCompressed) {
@@ -1487,8 +1803,8 @@ export class InteractiveSession {
1487
1803
  const icon = colors.primary(icons.brain);
1488
1804
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1489
1805
  let frameIndex = 0;
1490
- // SDK 模式下不显示 spinner
1491
- const showThinkingSpinner = !this.isSdkMode;
1806
+ // SDK 模式和 Team 模式下不显示 spinner (team 模式下 teammate 输出会与 spinner 混淆)
1807
+ const showThinkingSpinner = !this.isSdkMode && !this.isTeamMode;
1492
1808
  let spinnerInterval = null;
1493
1809
  // Custom spinner: only icon rotates, text stays static
1494
1810
  if (showThinkingSpinner) {
@@ -1514,7 +1830,17 @@ export class InteractiveSession {
1514
1830
  allowedToolNames.includes(tool.function.name))
1515
1831
  : toolDefinitions;
1516
1832
  const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
1517
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
1833
+ // Build team context if running in team mode
1834
+ const teamContext = this.isTeamMode ? {
1835
+ teamId: this.teamId,
1836
+ memberId: this.memberId,
1837
+ memberName: this.memberName,
1838
+ memberRole: this.memberRole,
1839
+ leadId: this.leadId || undefined,
1840
+ brokerPort: this.brokerPort || undefined,
1841
+ initialTaskId: this.initialTaskId || undefined,
1842
+ } : undefined;
1843
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager, teamContext);
1518
1844
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1519
1845
  const messages = [
1520
1846
  { role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
@@ -2444,4 +2770,24 @@ export function setSingletonSession(session) {
2444
2770
  export function getSingletonSession() {
2445
2771
  return singletonSession;
2446
2772
  }
2773
+ /**
2774
+ * Get teammate message queue info from the singleton session.
2775
+ * Returns queue length.
2776
+ */
2777
+ export function getTeammateMessageQueueInfo() {
2778
+ if (!singletonSession) {
2779
+ return null;
2780
+ }
2781
+ return singletonSession.getTeammateMessageQueueInfo();
2782
+ }
2783
+ /**
2784
+ * Get and clear all teammate messages from the singleton session.
2785
+ * Returns all pending messages and clears the queue.
2786
+ */
2787
+ export function popTeammateMessages() {
2788
+ if (!singletonSession) {
2789
+ return null;
2790
+ }
2791
+ return singletonSession.popTeammateMessages();
2792
+ }
2447
2793
  //# sourceMappingURL=session.js.map