@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/src/session.ts 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';
@@ -93,11 +92,33 @@ export class InteractiveSession {
93
92
  private heartbeatTimeout: NodeJS.Timeout | null = null;
94
93
  private heartbeatTimeoutMs: number = 300000; // 5 minutes timeout for long AI responses
95
94
  private lastActivityTime: number = Date.now();
95
+ private teammateMessageQueue: ChatMessage[] = [];
96
96
 
97
97
  // SDK response handling for approvals and questions
98
98
  private approvalPromises: Map<string, { resolve: (approved: boolean) => void; reject: (err: Error) => void }> = new Map();
99
99
  private questionPromises: Map<string, { resolve: (answers: string[]) => void; reject: (err: Error) => void }> = new Map();
100
100
 
101
+ // Team mode properties
102
+ private isTeamMode: boolean = false;
103
+ private teamId: string | null = null;
104
+ private memberId: string | null = null;
105
+ private memberName: string | null = null;
106
+ private memberRole: string | null = null;
107
+ private leadId: string | null = null;
108
+ private teamStore: any = null;
109
+ private messageClient: any = null;
110
+ private spawnPrompt: string | null = null;
111
+ private brokerPort: number | null = null;
112
+ private initialTaskId: string | null = null;
113
+
114
+ // Operation lock for preventing concurrent operations
115
+ private _isOperationInProgress: boolean = false;
116
+ private _shutdownResolver: ((value: void) => void) | null = null;
117
+
118
+ // Queue processing lock to prevent race conditions
119
+ private isProcessingQueue: boolean = false;
120
+ private queueProcessingPromise: Promise<void> | null = null;
121
+
101
122
  constructor(indentLevel: number = 0) {
102
123
  this.rl = readline.createInterface({
103
124
  input: process.stdin,
@@ -113,6 +134,59 @@ export class InteractiveSession {
113
134
  this.sessionManager = getSessionManager(process.cwd());
114
135
  this.slashCommandHandler = new SlashCommandHandler();
115
136
 
137
+ // Check if running in Team Mode
138
+ if (process.env.XAGENT_TEAM_MODE === 'true') {
139
+ this.isTeamMode = true;
140
+ this.teamId = process.env.XAGENT_TEAM_ID || null;
141
+ this.memberId = process.env.XAGENT_MEMBER_ID || null;
142
+ this.memberName = process.env.XAGENT_MEMBER_NAME || null;
143
+ this.memberRole = 'teammate'; // Role is now determined by tool response, not env var
144
+ this.leadId = process.env.XAGENT_LEAD_ID || null;
145
+ this.spawnPrompt = process.env.XAGENT_SPAWN_PROMPT || null;
146
+ this.brokerPort = process.env.XAGENT_BROKER_PORT ? parseInt(process.env.XAGENT_BROKER_PORT, 10) : null;
147
+ this.initialTaskId = process.env.XAGENT_INITIAL_TASK_ID || null;
148
+
149
+ // Show team role info in welcome message (compact format)
150
+ console.log(colors.textMuted(`[Team] ${this.memberName} (${this.memberRole})`));
151
+
152
+ // Import and initialize team components
153
+ import('./team-manager/index.js').then(async ({ getTeamStore, MessageClient, setTeammateClient }) => {
154
+ this.teamStore = getTeamStore();
155
+
156
+ // Connect to message broker if port is available
157
+ if (this.brokerPort && this.teamId && this.memberId) {
158
+ this.messageClient = new MessageClient(
159
+ this.teamId,
160
+ this.memberId,
161
+ this.brokerPort
162
+ );
163
+
164
+ // Save to global singleton for TeamCoordinator to use
165
+ setTeammateClient(this.messageClient);
166
+
167
+ this.messageClient.on('message', (msg: any) => {
168
+ this.handleTeamMessage(msg);
169
+ });
170
+
171
+ this.messageClient.on('connected', () => {
172
+ // Silently connected - no output needed
173
+ });
174
+
175
+ this.messageClient.on('disconnected', () => {
176
+ // Silently disconnected - no output needed
177
+ });
178
+
179
+ try {
180
+ await this.messageClient.connect();
181
+ } catch (err) {
182
+ console.error('[Team] Failed to connect to message broker:', err);
183
+ }
184
+ }
185
+ }).catch(() => {
186
+ // Ignore import errors
187
+ });
188
+ }
189
+
116
190
  // Register /clear callback, clear local conversation when clearing dialogue
117
191
  this.slashCommandHandler.setClearCallback(() => {
118
192
  this.conversation = [];
@@ -224,6 +298,233 @@ export class InteractiveSession {
224
298
  await toolRegistry.setSdkMode(true, adapter);
225
299
  }
226
300
 
301
+ /**
302
+ * Handle incoming team message from socket.
303
+ * Messages are queued and must be explicitly processed by the agent.
304
+ */
305
+ private handleTeamMessage(msg: any): void {
306
+ if (!msg || !msg.content) return;
307
+
308
+ let teamMessage: ChatMessage;
309
+
310
+ if (msg.type === 'task_update') {
311
+ let taskInfo: string;
312
+ try {
313
+ const content = typeof msg.content === 'string' ? JSON.parse(msg.content) : msg.content;
314
+ taskInfo = `Task ${content.action}: ${content.title || content.taskId}`;
315
+ if (content.assignee) {
316
+ taskInfo += ` (assignee: ${content.assignee})`;
317
+ }
318
+ if (content.result) {
319
+ taskInfo += ` - Result: ${content.result.substring(0, 100)}...`;
320
+ }
321
+ } catch {
322
+ taskInfo = `Task update: ${msg.content}`;
323
+ }
324
+
325
+ teamMessage = {
326
+ role: 'user',
327
+ content: `<system-notification type="task_update">${taskInfo}</system-notification>`,
328
+ timestamp: msg.timestamp || Date.now()
329
+ };
330
+ } else {
331
+ teamMessage = {
332
+ role: 'user',
333
+ content: `<teammate-message from="${msg.fromMemberId}" type="${msg.type}">${msg.content}</teammate-message>`,
334
+ timestamp: msg.timestamp || Date.now()
335
+ };
336
+ }
337
+
338
+ this.teammateMessageQueue.push(teamMessage);
339
+
340
+ // Only start processing if not already processing
341
+ if (!this.isProcessingQueue && !this._isOperationInProgress) {
342
+ this.startQueueProcessing();
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Start queue processing with proper locking.
348
+ */
349
+ private startQueueProcessing(): void {
350
+ // Prevent multiple queue processing coroutines
351
+ if (this.isProcessingQueue) {
352
+ return;
353
+ }
354
+
355
+ this.isProcessingQueue = true;
356
+ this.queueProcessingPromise = this.processMessageQueue();
357
+
358
+ // Handle completion and errors
359
+ this.queueProcessingPromise
360
+ .catch((error) => {
361
+ console.error('[Team] Queue processing error:', error);
362
+ })
363
+ .finally(() => {
364
+ this.isProcessingQueue = false;
365
+ this.queueProcessingPromise = null;
366
+ });
367
+ }
368
+
369
+ /**
370
+ * Process queued teammate messages one by one.
371
+ * This method should only be called from startQueueProcessing.
372
+ */
373
+ private async processMessageQueue(): Promise<void> {
374
+ while (this.teammateMessageQueue.length > 0) {
375
+ // Check if operation is in progress (e.g., user is typing or another operation)
376
+ if (this._isOperationInProgress) {
377
+ // Wait a bit and retry
378
+ await new Promise(resolve => setTimeout(resolve, 100));
379
+ continue;
380
+ }
381
+
382
+ const message = this.teammateMessageQueue.shift()!;
383
+ this.conversation.push(message);
384
+
385
+ try {
386
+ if (this.remoteAIClient) {
387
+ await this.generateRemoteResponse(0);
388
+ } else {
389
+ await this.generateResponse(0);
390
+ }
391
+ } catch (error) {
392
+ console.error('[Team] Failed to process teammate message:', error);
393
+ // Continue processing remaining messages even if one fails
394
+ }
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Get team mode status.
400
+ */
401
+ getIsTeamMode(): boolean {
402
+ return this.isTeamMode;
403
+ }
404
+
405
+ /**
406
+ * Get team ID.
407
+ */
408
+ getTeamId(): string | null {
409
+ return this.teamId;
410
+ }
411
+
412
+ /**
413
+ * Get member ID.
414
+ */
415
+ getMemberId(): string | null {
416
+ return this.memberId;
417
+ }
418
+
419
+ /**
420
+ * Get member name.
421
+ */
422
+ getMemberName(): string | null {
423
+ return this.memberName;
424
+ }
425
+
426
+ /**
427
+ * Get lead ID.
428
+ */
429
+ getLeadId(): string | null {
430
+ return this.leadId;
431
+ }
432
+
433
+ /**
434
+ * Get spawn prompt (for team mode).
435
+ */
436
+ getSpawnPrompt(): string | null {
437
+ return this.spawnPrompt;
438
+ }
439
+
440
+ /**
441
+ * Get teammate message queue info.
442
+ */
443
+ getTeammateMessageQueueInfo(): { length: number } {
444
+ return {
445
+ length: this.teammateMessageQueue.length,
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Get and clear all teammate messages from queue.
451
+ * Returns all pending messages and clears the queue.
452
+ */
453
+ popTeammateMessages(): ChatMessage[] {
454
+ const messages = [...this.teammateMessageQueue];
455
+ this.teammateMessageQueue = [];
456
+ return messages;
457
+ }
458
+
459
+ /**
460
+ * Connect to team message broker (for lead agent after creating a team).
461
+ * This allows the lead agent to receive real-time messages from teammates.
462
+ */
463
+ async connectToTeamBroker(teamId: string, memberId: string, brokerPort: number): Promise<void> {
464
+ // Skip if already connected
465
+ if (this.messageClient) {
466
+ return;
467
+ }
468
+
469
+ this.isTeamMode = true;
470
+ this.teamId = teamId;
471
+ this.memberId = memberId;
472
+ this.memberRole = 'lead';
473
+ this.brokerPort = brokerPort;
474
+
475
+ try {
476
+ const { getTeamStore, MessageClient } = await import('./team-manager/index.js');
477
+ this.teamStore = getTeamStore();
478
+
479
+ this.messageClient = new MessageClient(teamId, memberId, brokerPort);
480
+
481
+ this.messageClient.on('message', (msg: any) => {
482
+ this.handleTeamMessage(msg);
483
+ });
484
+
485
+ this.messageClient.on('connected', () => {
486
+ // Silently connected - no output needed
487
+ });
488
+
489
+ this.messageClient.on('disconnected', () => {
490
+ // Silently disconnected - no output needed
491
+ });
492
+
493
+ await this.messageClient.connect();
494
+ } catch (err) {
495
+ console.error(colors.error('[Team] Failed to connect to message broker:'), err);
496
+ throw err; // Re-throw to let caller handle it
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Cleanup team mode resources.
502
+ */
503
+ async cleanupTeamMode(): Promise<void> {
504
+ // Clear teammate's persistent MessageClient if exists
505
+ const { clearTeammateClient } = await import('./team-manager/index.js');
506
+ clearTeammateClient();
507
+
508
+ if (this.messageClient) {
509
+ await this.messageClient.disconnect();
510
+ this.messageClient = null;
511
+ }
512
+
513
+ if (this.teamStore && this.teamId && this.memberId) {
514
+ try {
515
+ await this.teamStore.updateMember(this.teamId, this.memberId, {
516
+ status: 'shutdown',
517
+ lastActivity: Date.now()
518
+ });
519
+ } catch {
520
+ // Ignore cleanup errors
521
+ }
522
+ }
523
+
524
+ // Reset team mode flag to restore thinking spinner
525
+ this.isTeamMode = false;
526
+ }
527
+
227
528
  /**
228
529
  * Get SDK mode status.
229
530
  */
@@ -268,11 +569,24 @@ export class InteractiveSession {
268
569
  await skillInvoker.reload();
269
570
 
270
571
  const toolRegistry = getToolRegistry();
572
+
573
+ // Build team context if running in team mode
574
+ const teamContext = this.isTeamMode ? {
575
+ teamId: this.teamId!,
576
+ memberId: this.memberId!,
577
+ memberName: this.memberName!,
578
+ memberRole: this.memberRole!,
579
+ leadId: this.leadId || undefined,
580
+ brokerPort: this.brokerPort || undefined,
581
+ initialTaskId: this.initialTaskId || undefined,
582
+ } : undefined;
583
+
271
584
  const promptGenerator = new SystemPromptGenerator(
272
585
  toolRegistry,
273
586
  this.executionMode,
274
587
  undefined,
275
- this.mcpManager
588
+ this.mcpManager,
589
+ teamContext
276
590
  );
277
591
 
278
592
  // Use the current agent's original system prompt as base
@@ -409,7 +723,7 @@ export class InteractiveSession {
409
723
  let _escCleanup: (() => void) | undefined;
410
724
  if (process.stdin.isTTY) {
411
725
  _escCleanup = setupEscKeyHandler(() => {
412
- if ((this as any)._isOperationInProgress) {
726
+ if (this._isOperationInProgress) {
413
727
  // An operation is running, let it be cancelled
414
728
  this.cancellationManager.cancel();
415
729
  }
@@ -418,16 +732,42 @@ export class InteractiveSession {
418
732
  }
419
733
 
420
734
  // Track if an operation is in progress
421
- (this as any)._isOperationInProgress = false;
735
+ this._isOperationInProgress = false;
422
736
 
423
737
  this.promptLoop();
424
738
 
425
739
  // Keep the promise pending until shutdown
426
740
  return new Promise((resolve) => {
427
- (this as any)._shutdownResolver = resolve;
741
+ this._shutdownResolver = resolve;
428
742
  });
429
743
  }
430
744
 
745
+ private async sendInitialPrompt(prompt: string): Promise<void> {
746
+ console.log(colors.textMuted(`\n📋 Initial prompt: ${prompt}\n`));
747
+
748
+ const userMessage: ChatMessage = {
749
+ role: 'user',
750
+ content: prompt,
751
+ timestamp: Date.now(),
752
+ };
753
+
754
+ this.conversation.push(userMessage);
755
+ await this.conversationManager.addMessage(userMessage);
756
+
757
+ const thinkingConfig = this.configManager.getThinkingConfig();
758
+ let thinkingTokens = 0;
759
+ if (thinkingConfig.enabled) {
760
+ const thinkingMode = detectThinkingKeywords(prompt);
761
+ thinkingTokens = getThinkingTokens(thinkingMode);
762
+ }
763
+
764
+ if (this.remoteAIClient) {
765
+ await this.generateRemoteResponse(thinkingTokens);
766
+ } else {
767
+ await this.generateResponse(thinkingTokens);
768
+ }
769
+ }
770
+
431
771
  private async initialize(): Promise<void> {
432
772
  logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
433
773
 
@@ -957,6 +1297,20 @@ export class InteractiveSession {
957
1297
  return;
958
1298
  }
959
1299
 
1300
+ // Check teammate message queue when agent becomes idle
1301
+ if (this.teammateMessageQueue.length > 0) {
1302
+ await this.processMessageQueue();
1303
+ }
1304
+
1305
+ // If running in team mode with initial prompt, send it immediately and skip user input
1306
+ if (this.spawnPrompt) {
1307
+ const prompt = this.spawnPrompt;
1308
+ this.spawnPrompt = null;
1309
+ await this.sendInitialPrompt(prompt);
1310
+ this.promptLoop();
1311
+ return;
1312
+ }
1313
+
960
1314
  // In SDK mode, use a different input loop
961
1315
  if (this.isSdkMode) {
962
1316
  await this.sdkPromptLoop();
@@ -1410,7 +1764,6 @@ export class InteractiveSession {
1410
1764
  public async processUserMessage(message: string, _agent?: AgentConfig): Promise<void> {
1411
1765
  const inputs = await parseInput(message);
1412
1766
  const textInput = inputs.find((i) => i.type === 'text');
1413
- const fileInputs = inputs.filter((i) => i.type === 'file');
1414
1767
  const commandInput = inputs.find((i) => i.type === 'command');
1415
1768
 
1416
1769
  if (commandInput) {
@@ -1418,25 +1771,7 @@ export class InteractiveSession {
1418
1771
  return;
1419
1772
  }
1420
1773
 
1421
- let userContent = textInput?.content || '';
1422
-
1423
- if (fileInputs.length > 0) {
1424
- const toolRegistry = getToolRegistry();
1425
- for (const fileInput of fileInputs) {
1426
- try {
1427
- const content = await toolRegistry.execute(
1428
- 'Read',
1429
- { filePath: fileInput.content },
1430
- this.executionMode
1431
- );
1432
- userContent += `\n\n--- File: ${fileInput.content} ---\n${content}`;
1433
- } catch (error: any) {
1434
- console.log(
1435
- chalk.yellow(`Warning: Failed to read file ${fileInput.content}: ${error.message}`)
1436
- );
1437
- }
1438
- }
1439
- }
1774
+ const userContent = textInput?.content || '';
1440
1775
 
1441
1776
  // Record input to session manager
1442
1777
  const sessionInput = {
@@ -1579,7 +1914,25 @@ export class InteractiveSession {
1579
1914
 
1580
1915
  const toolRegistry = getToolRegistry();
1581
1916
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
1582
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
1917
+
1918
+ // Build team context if running in team mode
1919
+ const teamContext = this.isTeamMode ? {
1920
+ teamId: this.teamId!,
1921
+ memberId: this.memberId!,
1922
+ memberName: this.memberName!,
1923
+ memberRole: this.memberRole!,
1924
+ leadId: this.leadId || undefined,
1925
+ brokerPort: this.brokerPort || undefined,
1926
+ initialTaskId: this.initialTaskId || undefined,
1927
+ } : undefined;
1928
+
1929
+ const systemPromptGenerator = new SystemPromptGenerator(
1930
+ toolRegistry,
1931
+ this.executionMode,
1932
+ undefined,
1933
+ undefined,
1934
+ teamContext
1935
+ );
1583
1936
  const enhancedSystemPrompt =
1584
1937
  await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1585
1938
 
@@ -1802,15 +2155,15 @@ export class InteractiveSession {
1802
2155
  }
1803
2156
 
1804
2157
  // Mark that an operation is in progress
1805
- (this as any)._isOperationInProgress = true;
2158
+ this._isOperationInProgress = true;
1806
2159
 
1807
2160
  const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
1808
2161
  const icon = colors.primary(icons.brain);
1809
2162
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1810
2163
  let frameIndex = 0;
1811
2164
 
1812
- // SDK 模式下不显示 spinner
1813
- const showThinkingSpinner = !this.isSdkMode;
2165
+ // SDK 模式和 Team 模式下不显示 spinner (team 模式下 teammate 输出会与 spinner 混淆)
2166
+ const showThinkingSpinner = !this.isSdkMode && !this.isTeamMode;
1814
2167
  let spinnerInterval: NodeJS.Timeout | null = null;
1815
2168
 
1816
2169
  // Custom spinner: only icon rotates, text stays static
@@ -1846,11 +2199,24 @@ export class InteractiveSession {
1846
2199
  : toolDefinitions;
1847
2200
 
1848
2201
  const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
2202
+
2203
+ // Build team context if running in team mode
2204
+ const teamContext = this.isTeamMode ? {
2205
+ teamId: this.teamId!,
2206
+ memberId: this.memberId!,
2207
+ memberName: this.memberName!,
2208
+ memberRole: this.memberRole!,
2209
+ leadId: this.leadId || undefined,
2210
+ brokerPort: this.brokerPort || undefined,
2211
+ initialTaskId: this.initialTaskId || undefined,
2212
+ } : undefined;
2213
+
1849
2214
  const systemPromptGenerator = new SystemPromptGenerator(
1850
2215
  toolRegistry,
1851
2216
  this.executionMode,
1852
2217
  undefined,
1853
- this.mcpManager
2218
+ this.mcpManager,
2219
+ teamContext
1854
2220
  );
1855
2221
  const enhancedSystemPrompt =
1856
2222
  await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
@@ -1927,13 +2293,13 @@ export class InteractiveSession {
1927
2293
  }
1928
2294
 
1929
2295
  // Operation completed successfully, clear the flag
1930
- (this as any)._isOperationInProgress = false;
2296
+ this._isOperationInProgress = false;
1931
2297
  } catch (error: any) {
1932
2298
  if (spinnerInterval) clearInterval(spinnerInterval);
1933
2299
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
1934
2300
 
1935
2301
  // Clear the operation flag
1936
- (this as any)._isOperationInProgress = false;
2302
+ this._isOperationInProgress = false;
1937
2303
 
1938
2304
  // Signal request completion to SDK
1939
2305
  if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
@@ -2009,7 +2375,7 @@ export class InteractiveSession {
2009
2375
  }
2010
2376
  } catch (error: any) {
2011
2377
  // Clear the operation flag
2012
- (this as any)._isOperationInProgress = false;
2378
+ this._isOperationInProgress = false;
2013
2379
 
2014
2380
  if (error.message === 'Operation cancelled by user') {
2015
2381
  // Notify backend to cancel the task
@@ -2098,7 +2464,7 @@ export class InteractiveSession {
2098
2464
  onComplete?: () => Promise<void>
2099
2465
  ): Promise<void> {
2100
2466
  // Mark that tool execution is in progress
2101
- (this as any)._isOperationInProgress = true;
2467
+ this._isOperationInProgress = true;
2102
2468
 
2103
2469
  const toolRegistry = getToolRegistry();
2104
2470
  const showToolDetails = this.configManager.get('showToolDetails') || false;
@@ -2177,7 +2543,7 @@ export class InteractiveSession {
2177
2543
  // 清理 conversation 中未完成的 tool_call
2178
2544
  this.cleanupIncompleteToolCalls();
2179
2545
 
2180
- (this as any)._isOperationInProgress = false;
2546
+ this._isOperationInProgress = false;
2181
2547
  return;
2182
2548
  }
2183
2549
 
@@ -2450,7 +2816,7 @@ export class InteractiveSession {
2450
2816
  // If GUI agent was cancelled by user, don't continue generating response
2451
2817
  // This avoids wasting API calls and tokens on cancelled tasks
2452
2818
  if (guiSubagentCancelled) {
2453
- (this as any)._isOperationInProgress = false;
2819
+ this._isOperationInProgress = false;
2454
2820
  return;
2455
2821
  }
2456
2822
 
@@ -2934,3 +3300,25 @@ export function setSingletonSession(session: InteractiveSession): void {
2934
3300
  export function getSingletonSession(): InteractiveSession | null {
2935
3301
  return singletonSession;
2936
3302
  }
3303
+
3304
+ /**
3305
+ * Get teammate message queue info from the singleton session.
3306
+ * Returns queue length.
3307
+ */
3308
+ export function getTeammateMessageQueueInfo(): { length: number } | null {
3309
+ if (!singletonSession) {
3310
+ return null;
3311
+ }
3312
+ return singletonSession.getTeammateMessageQueueInfo();
3313
+ }
3314
+
3315
+ /**
3316
+ * Get and clear all teammate messages from the singleton session.
3317
+ * Returns all pending messages and clears the queue.
3318
+ */
3319
+ export function popTeammateMessages(): ChatMessage[] | null {
3320
+ if (!singletonSession) {
3321
+ return null;
3322
+ }
3323
+ return singletonSession.popTeammateMessages();
3324
+ }
@@ -189,11 +189,8 @@ export class SlashCommandHandler {
189
189
  console.log(colors.accent('Shortcuts'));
190
190
  console.log(colors.border(separator));
191
191
  console.log('');
192
- console.log(
193
- colors.textDim(` ${colors.accent('!')} - ${colors.textMuted('Enter bash mode')}`)
194
- );
192
+ console.log(colors.textDim(` ${colors.accent('!')} - ${colors.textMuted('Enter bash mode')}`));
195
193
  console.log(colors.textDim(` ${colors.accent('/')} - ${colors.textMuted('Commands')}`));
196
- console.log(colors.textDim(` ${colors.accent('@')} - ${colors.textMuted('File paths')}`));
197
194
  console.log('');
198
195
 
199
196
  // Basic Commands
@@ -2132,54 +2129,18 @@ export class SlashCommandHandler {
2132
2129
 
2133
2130
  export async function parseInput(input: string): Promise<InputType[]> {
2134
2131
  const inputs: InputType[] = [];
2135
- let remaining = input;
2136
2132
 
2137
- // Match @ followed by any non-whitespace sequence
2138
- const fileRefRegex = /@([^\s]+)/g;
2139
- let match;
2140
- while ((match = fileRefRegex.exec(remaining)) !== null) {
2141
- const filePath = match[1];
2142
- const beforeMatch = remaining.substring(0, match.index);
2143
- const afterMatch = remaining.substring(match.index + match[0].length);
2144
-
2145
- if (beforeMatch.trim()) {
2146
- inputs.push({ type: 'text', content: beforeMatch.trim() });
2147
- }
2148
-
2149
- // Only treat as file reference if the file actually exists
2150
- const exists = await fileExists(filePath);
2151
- if (exists) {
2152
- inputs.push({ type: 'file', content: filePath });
2133
+ if (input.trim()) {
2134
+ if (input.trim().startsWith('!')) {
2135
+ inputs.push({ type: 'command', content: input.trim().slice(1).trim() });
2153
2136
  } else {
2154
- // Not a file, treat as regular text (preserving the @ symbol)
2155
- inputs.push({ type: 'text', content: '@' + filePath });
2156
- }
2157
- remaining = afterMatch;
2158
- }
2159
-
2160
- if (remaining.trim()) {
2161
- if (remaining.startsWith('!')) {
2162
- inputs.push({ type: 'command', content: remaining.slice(1).trim() });
2163
- } else {
2164
- inputs.push({ type: 'text', content: remaining.trim() });
2137
+ inputs.push({ type: 'text', content: input.trim() });
2165
2138
  }
2166
2139
  }
2167
2140
 
2168
2141
  return inputs;
2169
2142
  }
2170
2143
 
2171
- // Helper function to check if a file path exists
2172
- async function fileExists(filePath: string): Promise<boolean> {
2173
- try {
2174
- // Resolve to absolute path
2175
- const absolutePath = path.resolve(filePath);
2176
- await fs.access(absolutePath);
2177
- return true;
2178
- } catch {
2179
- return false;
2180
- }
2181
- }
2182
-
2183
2144
  export function detectImageInput(input: string): boolean {
2184
2145
  return input.includes('[Pasted image') || input.includes('<image');
2185
2146
  }
@@ -82,6 +82,7 @@ export class WhitelistChecker {
82
82
  'xml_escape',
83
83
  'InvokeSkill',
84
84
  'task',
85
+ 'Team',
85
86
  ]);
86
87
 
87
88
  /**