@xagent-ai/cli 1.3.7 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +352 -20
  16. package/dist/session.js.map +1 -1
  17. package/dist/slash-commands.d.ts.map +1 -1
  18. package/dist/slash-commands.js +4 -39
  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 +424 -36
  60. package/src/slash-commands.ts +5 -44
  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
@@ -1,5 +1,4 @@
1
1
  import readline from 'readline';
2
- import chalk from 'chalk';
3
2
  import https from 'https';
4
3
  import axios from 'axios';
5
4
  import crypto from 'crypto';
@@ -67,9 +66,28 @@ export class InteractiveSession {
67
66
  heartbeatTimeout = null;
68
67
  heartbeatTimeoutMs = 300000; // 5 minutes timeout for long AI responses
69
68
  lastActivityTime = Date.now();
69
+ teammateMessageQueue = [];
70
70
  // SDK response handling for approvals and questions
71
71
  approvalPromises = new Map();
72
72
  questionPromises = new Map();
73
+ // Team mode properties
74
+ isTeamMode = false;
75
+ teamId = null;
76
+ memberId = null;
77
+ memberName = null;
78
+ memberRole = null;
79
+ leadId = null;
80
+ teamStore = null;
81
+ messageClient = null;
82
+ spawnPrompt = null;
83
+ brokerPort = null;
84
+ initialTaskId = null;
85
+ // Operation lock for preventing concurrent operations
86
+ _isOperationInProgress = false;
87
+ _shutdownResolver = null;
88
+ // Queue processing lock to prevent race conditions
89
+ isProcessingQueue = false;
90
+ queueProcessingPromise = null;
73
91
  constructor(indentLevel = 0) {
74
92
  this.rl = readline.createInterface({
75
93
  input: process.stdin,
@@ -83,6 +101,47 @@ export class InteractiveSession {
83
101
  this.conversationManager = getConversationManager();
84
102
  this.sessionManager = getSessionManager(process.cwd());
85
103
  this.slashCommandHandler = new SlashCommandHandler();
104
+ // Check if running in Team Mode
105
+ if (process.env.XAGENT_TEAM_MODE === 'true') {
106
+ this.isTeamMode = true;
107
+ this.teamId = process.env.XAGENT_TEAM_ID || null;
108
+ this.memberId = process.env.XAGENT_MEMBER_ID || null;
109
+ this.memberName = process.env.XAGENT_MEMBER_NAME || null;
110
+ this.memberRole = 'teammate'; // Role is now determined by tool response, not env var
111
+ this.leadId = process.env.XAGENT_LEAD_ID || null;
112
+ this.spawnPrompt = process.env.XAGENT_SPAWN_PROMPT || null;
113
+ this.brokerPort = process.env.XAGENT_BROKER_PORT ? parseInt(process.env.XAGENT_BROKER_PORT, 10) : null;
114
+ this.initialTaskId = process.env.XAGENT_INITIAL_TASK_ID || null;
115
+ // Show team role info in welcome message (compact format)
116
+ console.log(colors.textMuted(`[Team] ${this.memberName} (${this.memberRole})`));
117
+ // Import and initialize team components
118
+ import('./team-manager/index.js').then(async ({ getTeamStore, MessageClient, setTeammateClient }) => {
119
+ this.teamStore = getTeamStore();
120
+ // Connect to message broker if port is available
121
+ if (this.brokerPort && this.teamId && this.memberId) {
122
+ this.messageClient = new MessageClient(this.teamId, this.memberId, this.brokerPort);
123
+ // Save to global singleton for TeamCoordinator to use
124
+ setTeammateClient(this.messageClient);
125
+ this.messageClient.on('message', (msg) => {
126
+ this.handleTeamMessage(msg);
127
+ });
128
+ this.messageClient.on('connected', () => {
129
+ // Silently connected - no output needed
130
+ });
131
+ this.messageClient.on('disconnected', () => {
132
+ // Silently disconnected - no output needed
133
+ });
134
+ try {
135
+ await this.messageClient.connect();
136
+ }
137
+ catch (err) {
138
+ console.error('[Team] Failed to connect to message broker:', err);
139
+ }
140
+ }
141
+ }).catch(() => {
142
+ // Ignore import errors
143
+ });
144
+ }
86
145
  // Register /clear callback, clear local conversation when clearing dialogue
87
146
  this.slashCommandHandler.setClearCallback(() => {
88
147
  this.conversation = [];
@@ -175,6 +234,208 @@ export class InteractiveSession {
175
234
  const toolRegistry = getToolRegistry();
176
235
  await toolRegistry.setSdkMode(true, adapter);
177
236
  }
237
+ /**
238
+ * Handle incoming team message from socket.
239
+ * Messages are queued and must be explicitly processed by the agent.
240
+ */
241
+ handleTeamMessage(msg) {
242
+ if (!msg || !msg.content)
243
+ return;
244
+ let teamMessage;
245
+ if (msg.type === 'task_update') {
246
+ let taskInfo;
247
+ try {
248
+ const content = typeof msg.content === 'string' ? JSON.parse(msg.content) : msg.content;
249
+ taskInfo = `Task ${content.action}: ${content.title || content.taskId}`;
250
+ if (content.assignee) {
251
+ taskInfo += ` (assignee: ${content.assignee})`;
252
+ }
253
+ if (content.result) {
254
+ taskInfo += ` - Result: ${content.result.substring(0, 100)}...`;
255
+ }
256
+ }
257
+ catch {
258
+ taskInfo = `Task update: ${msg.content}`;
259
+ }
260
+ teamMessage = {
261
+ role: 'user',
262
+ content: `<system-notification type="task_update">${taskInfo}</system-notification>`,
263
+ timestamp: msg.timestamp || Date.now()
264
+ };
265
+ }
266
+ else {
267
+ teamMessage = {
268
+ role: 'user',
269
+ content: `<teammate-message from="${msg.fromMemberId}" type="${msg.type}">${msg.content}</teammate-message>`,
270
+ timestamp: msg.timestamp || Date.now()
271
+ };
272
+ }
273
+ this.teammateMessageQueue.push(teamMessage);
274
+ // Only start processing if not already processing
275
+ if (!this.isProcessingQueue && !this._isOperationInProgress) {
276
+ this.startQueueProcessing();
277
+ }
278
+ }
279
+ /**
280
+ * Start queue processing with proper locking.
281
+ */
282
+ startQueueProcessing() {
283
+ // Prevent multiple queue processing coroutines
284
+ if (this.isProcessingQueue) {
285
+ return;
286
+ }
287
+ this.isProcessingQueue = true;
288
+ this.queueProcessingPromise = this.processMessageQueue();
289
+ // Handle completion and errors
290
+ this.queueProcessingPromise
291
+ .catch((error) => {
292
+ console.error('[Team] Queue processing error:', error);
293
+ })
294
+ .finally(() => {
295
+ this.isProcessingQueue = false;
296
+ this.queueProcessingPromise = null;
297
+ });
298
+ }
299
+ /**
300
+ * Process queued teammate messages one by one.
301
+ * This method should only be called from startQueueProcessing.
302
+ */
303
+ async processMessageQueue() {
304
+ while (this.teammateMessageQueue.length > 0) {
305
+ // Check if operation is in progress (e.g., user is typing or another operation)
306
+ if (this._isOperationInProgress) {
307
+ // Wait a bit and retry
308
+ await new Promise(resolve => setTimeout(resolve, 100));
309
+ continue;
310
+ }
311
+ const message = this.teammateMessageQueue.shift();
312
+ this.conversation.push(message);
313
+ try {
314
+ if (this.remoteAIClient) {
315
+ await this.generateRemoteResponse(0);
316
+ }
317
+ else {
318
+ await this.generateResponse(0);
319
+ }
320
+ }
321
+ catch (error) {
322
+ console.error('[Team] Failed to process teammate message:', error);
323
+ // Continue processing remaining messages even if one fails
324
+ }
325
+ }
326
+ }
327
+ /**
328
+ * Get team mode status.
329
+ */
330
+ getIsTeamMode() {
331
+ return this.isTeamMode;
332
+ }
333
+ /**
334
+ * Get team ID.
335
+ */
336
+ getTeamId() {
337
+ return this.teamId;
338
+ }
339
+ /**
340
+ * Get member ID.
341
+ */
342
+ getMemberId() {
343
+ return this.memberId;
344
+ }
345
+ /**
346
+ * Get member name.
347
+ */
348
+ getMemberName() {
349
+ return this.memberName;
350
+ }
351
+ /**
352
+ * Get lead ID.
353
+ */
354
+ getLeadId() {
355
+ return this.leadId;
356
+ }
357
+ /**
358
+ * Get spawn prompt (for team mode).
359
+ */
360
+ getSpawnPrompt() {
361
+ return this.spawnPrompt;
362
+ }
363
+ /**
364
+ * Get teammate message queue info.
365
+ */
366
+ getTeammateMessageQueueInfo() {
367
+ return {
368
+ length: this.teammateMessageQueue.length,
369
+ };
370
+ }
371
+ /**
372
+ * Get and clear all teammate messages from queue.
373
+ * Returns all pending messages and clears the queue.
374
+ */
375
+ popTeammateMessages() {
376
+ const messages = [...this.teammateMessageQueue];
377
+ this.teammateMessageQueue = [];
378
+ return messages;
379
+ }
380
+ /**
381
+ * Connect to team message broker (for lead agent after creating a team).
382
+ * This allows the lead agent to receive real-time messages from teammates.
383
+ */
384
+ async connectToTeamBroker(teamId, memberId, brokerPort) {
385
+ // Skip if already connected
386
+ if (this.messageClient) {
387
+ return;
388
+ }
389
+ this.isTeamMode = true;
390
+ this.teamId = teamId;
391
+ this.memberId = memberId;
392
+ this.memberRole = 'lead';
393
+ this.brokerPort = brokerPort;
394
+ try {
395
+ const { getTeamStore, MessageClient } = await import('./team-manager/index.js');
396
+ this.teamStore = getTeamStore();
397
+ this.messageClient = new MessageClient(teamId, memberId, brokerPort);
398
+ this.messageClient.on('message', (msg) => {
399
+ this.handleTeamMessage(msg);
400
+ });
401
+ this.messageClient.on('connected', () => {
402
+ // Silently connected - no output needed
403
+ });
404
+ this.messageClient.on('disconnected', () => {
405
+ // Silently disconnected - no output needed
406
+ });
407
+ await this.messageClient.connect();
408
+ }
409
+ catch (err) {
410
+ console.error(colors.error('[Team] Failed to connect to message broker:'), err);
411
+ throw err; // Re-throw to let caller handle it
412
+ }
413
+ }
414
+ /**
415
+ * Cleanup team mode resources.
416
+ */
417
+ async cleanupTeamMode() {
418
+ // Clear teammate's persistent MessageClient if exists
419
+ const { clearTeammateClient } = await import('./team-manager/index.js');
420
+ clearTeammateClient();
421
+ if (this.messageClient) {
422
+ await this.messageClient.disconnect();
423
+ this.messageClient = null;
424
+ }
425
+ if (this.teamStore && this.teamId && this.memberId) {
426
+ try {
427
+ await this.teamStore.updateMember(this.teamId, this.memberId, {
428
+ status: 'shutdown',
429
+ lastActivity: Date.now()
430
+ });
431
+ }
432
+ catch {
433
+ // Ignore cleanup errors
434
+ }
435
+ }
436
+ // Reset team mode flag to restore thinking spinner
437
+ this.isTeamMode = false;
438
+ }
178
439
  /**
179
440
  * Get SDK mode status.
180
441
  */
@@ -211,7 +472,17 @@ export class InteractiveSession {
211
472
  const skillInvoker = (await import('./skill-invoker.js')).getSkillInvoker();
212
473
  await skillInvoker.reload();
213
474
  const toolRegistry = getToolRegistry();
214
- const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
475
+ // Build team context if running in team mode
476
+ const teamContext = this.isTeamMode ? {
477
+ teamId: this.teamId,
478
+ memberId: this.memberId,
479
+ memberName: this.memberName,
480
+ memberRole: this.memberRole,
481
+ leadId: this.leadId || undefined,
482
+ brokerPort: this.brokerPort || undefined,
483
+ initialTaskId: this.initialTaskId || undefined,
484
+ } : undefined;
485
+ const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager, teamContext);
215
486
  // Use the current agent's original system prompt as base
216
487
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
217
488
  const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
@@ -337,6 +608,28 @@ export class InteractiveSession {
337
608
  this._shutdownResolver = resolve;
338
609
  });
339
610
  }
611
+ async sendInitialPrompt(prompt) {
612
+ console.log(colors.textMuted(`\n📋 Initial prompt: ${prompt}\n`));
613
+ const userMessage = {
614
+ role: 'user',
615
+ content: prompt,
616
+ timestamp: Date.now(),
617
+ };
618
+ this.conversation.push(userMessage);
619
+ await this.conversationManager.addMessage(userMessage);
620
+ const thinkingConfig = this.configManager.getThinkingConfig();
621
+ let thinkingTokens = 0;
622
+ if (thinkingConfig.enabled) {
623
+ const thinkingMode = detectThinkingKeywords(prompt);
624
+ thinkingTokens = getThinkingTokens(thinkingMode);
625
+ }
626
+ if (this.remoteAIClient) {
627
+ await this.generateRemoteResponse(thinkingTokens);
628
+ }
629
+ else {
630
+ await this.generateResponse(thinkingTokens);
631
+ }
632
+ }
340
633
  async initialize() {
341
634
  logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
342
635
  try {
@@ -790,6 +1083,18 @@ export class InteractiveSession {
790
1083
  if (this._isShuttingDown) {
791
1084
  return;
792
1085
  }
1086
+ // Check teammate message queue when agent becomes idle
1087
+ if (this.teammateMessageQueue.length > 0) {
1088
+ await this.processMessageQueue();
1089
+ }
1090
+ // If running in team mode with initial prompt, send it immediately and skip user input
1091
+ if (this.spawnPrompt) {
1092
+ const prompt = this.spawnPrompt;
1093
+ this.spawnPrompt = null;
1094
+ await this.sendInitialPrompt(prompt);
1095
+ this.promptLoop();
1096
+ return;
1097
+ }
793
1098
  // In SDK mode, use a different input loop
794
1099
  if (this.isSdkMode) {
795
1100
  await this.sdkPromptLoop();
@@ -1196,25 +1501,12 @@ export class InteractiveSession {
1196
1501
  async processUserMessage(message, _agent) {
1197
1502
  const inputs = await parseInput(message);
1198
1503
  const textInput = inputs.find((i) => i.type === 'text');
1199
- const fileInputs = inputs.filter((i) => i.type === 'file');
1200
1504
  const commandInput = inputs.find((i) => i.type === 'command');
1201
1505
  if (commandInput) {
1202
1506
  await this.executeShellCommand(commandInput.content);
1203
1507
  return;
1204
1508
  }
1205
- let userContent = textInput?.content || '';
1206
- if (fileInputs.length > 0) {
1207
- const toolRegistry = getToolRegistry();
1208
- for (const fileInput of fileInputs) {
1209
- try {
1210
- const content = await toolRegistry.execute('Read', { filePath: fileInput.content }, this.executionMode);
1211
- userContent += `\n\n--- File: ${fileInput.content} ---\n${content}`;
1212
- }
1213
- catch (error) {
1214
- console.log(chalk.yellow(`Warning: Failed to read file ${fileInput.content}: ${error.message}`));
1215
- }
1216
- }
1217
- }
1509
+ const userContent = textInput?.content || '';
1218
1510
  // Record input to session manager
1219
1511
  const sessionInput = {
1220
1512
  type: 'text',
@@ -1323,7 +1615,17 @@ export class InteractiveSession {
1323
1615
  }
1324
1616
  const toolRegistry = getToolRegistry();
1325
1617
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
1326
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
1618
+ // Build team context if running in team mode
1619
+ const teamContext = this.isTeamMode ? {
1620
+ teamId: this.teamId,
1621
+ memberId: this.memberId,
1622
+ memberName: this.memberName,
1623
+ memberRole: this.memberRole,
1624
+ leadId: this.leadId || undefined,
1625
+ brokerPort: this.brokerPort || undefined,
1626
+ initialTaskId: this.initialTaskId || undefined,
1627
+ } : undefined;
1628
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, undefined, teamContext);
1327
1629
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1328
1630
  const result = await this.contextCompressor.compressContext(this.conversation, enhancedSystemPrompt, compressionConfig);
1329
1631
  if (result.wasCompressed) {
@@ -1487,8 +1789,8 @@ export class InteractiveSession {
1487
1789
  const icon = colors.primary(icons.brain);
1488
1790
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1489
1791
  let frameIndex = 0;
1490
- // SDK 模式下不显示 spinner
1491
- const showThinkingSpinner = !this.isSdkMode;
1792
+ // SDK 模式和 Team 模式下不显示 spinner (team 模式下 teammate 输出会与 spinner 混淆)
1793
+ const showThinkingSpinner = !this.isSdkMode && !this.isTeamMode;
1492
1794
  let spinnerInterval = null;
1493
1795
  // Custom spinner: only icon rotates, text stays static
1494
1796
  if (showThinkingSpinner) {
@@ -1514,7 +1816,17 @@ export class InteractiveSession {
1514
1816
  allowedToolNames.includes(tool.function.name))
1515
1817
  : toolDefinitions;
1516
1818
  const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
1517
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
1819
+ // Build team context if running in team mode
1820
+ const teamContext = this.isTeamMode ? {
1821
+ teamId: this.teamId,
1822
+ memberId: this.memberId,
1823
+ memberName: this.memberName,
1824
+ memberRole: this.memberRole,
1825
+ leadId: this.leadId || undefined,
1826
+ brokerPort: this.brokerPort || undefined,
1827
+ initialTaskId: this.initialTaskId || undefined,
1828
+ } : undefined;
1829
+ const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager, teamContext);
1518
1830
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1519
1831
  const messages = [
1520
1832
  { role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
@@ -2444,4 +2756,24 @@ export function setSingletonSession(session) {
2444
2756
  export function getSingletonSession() {
2445
2757
  return singletonSession;
2446
2758
  }
2759
+ /**
2760
+ * Get teammate message queue info from the singleton session.
2761
+ * Returns queue length.
2762
+ */
2763
+ export function getTeammateMessageQueueInfo() {
2764
+ if (!singletonSession) {
2765
+ return null;
2766
+ }
2767
+ return singletonSession.getTeammateMessageQueueInfo();
2768
+ }
2769
+ /**
2770
+ * Get and clear all teammate messages from the singleton session.
2771
+ * Returns all pending messages and clears the queue.
2772
+ */
2773
+ export function popTeammateMessages() {
2774
+ if (!singletonSession) {
2775
+ return null;
2776
+ }
2777
+ return singletonSession.popTeammateMessages();
2778
+ }
2447
2779
  //# sourceMappingURL=session.js.map