@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/src/session.ts CHANGED
@@ -93,11 +93,33 @@ export class InteractiveSession {
93
93
  private heartbeatTimeout: NodeJS.Timeout | null = null;
94
94
  private heartbeatTimeoutMs: number = 300000; // 5 minutes timeout for long AI responses
95
95
  private lastActivityTime: number = Date.now();
96
+ private teammateMessageQueue: ChatMessage[] = [];
96
97
 
97
98
  // SDK response handling for approvals and questions
98
99
  private approvalPromises: Map<string, { resolve: (approved: boolean) => void; reject: (err: Error) => void }> = new Map();
99
100
  private questionPromises: Map<string, { resolve: (answers: string[]) => void; reject: (err: Error) => void }> = new Map();
100
101
 
102
+ // Team mode properties
103
+ private isTeamMode: boolean = false;
104
+ private teamId: string | null = null;
105
+ private memberId: string | null = null;
106
+ private memberName: string | null = null;
107
+ private memberRole: string | null = null;
108
+ private leadId: string | null = null;
109
+ private teamStore: any = null;
110
+ private messageClient: any = null;
111
+ private spawnPrompt: string | null = null;
112
+ private brokerPort: number | null = null;
113
+ private initialTaskId: string | null = null;
114
+
115
+ // Operation lock for preventing concurrent operations
116
+ private _isOperationInProgress: boolean = false;
117
+ private _shutdownResolver: ((value: void) => void) | null = null;
118
+
119
+ // Queue processing lock to prevent race conditions
120
+ private isProcessingQueue: boolean = false;
121
+ private queueProcessingPromise: Promise<void> | null = null;
122
+
101
123
  constructor(indentLevel: number = 0) {
102
124
  this.rl = readline.createInterface({
103
125
  input: process.stdin,
@@ -113,6 +135,59 @@ export class InteractiveSession {
113
135
  this.sessionManager = getSessionManager(process.cwd());
114
136
  this.slashCommandHandler = new SlashCommandHandler();
115
137
 
138
+ // Check if running in Team Mode
139
+ if (process.env.XAGENT_TEAM_MODE === 'true') {
140
+ this.isTeamMode = true;
141
+ this.teamId = process.env.XAGENT_TEAM_ID || null;
142
+ this.memberId = process.env.XAGENT_MEMBER_ID || null;
143
+ this.memberName = process.env.XAGENT_MEMBER_NAME || null;
144
+ this.memberRole = 'teammate'; // Role is now determined by tool response, not env var
145
+ this.leadId = process.env.XAGENT_LEAD_ID || null;
146
+ this.spawnPrompt = process.env.XAGENT_SPAWN_PROMPT || null;
147
+ this.brokerPort = process.env.XAGENT_BROKER_PORT ? parseInt(process.env.XAGENT_BROKER_PORT, 10) : null;
148
+ this.initialTaskId = process.env.XAGENT_INITIAL_TASK_ID || null;
149
+
150
+ // Show team role info in welcome message (compact format)
151
+ console.log(colors.textMuted(`[Team] ${this.memberName} (${this.memberRole})`));
152
+
153
+ // Import and initialize team components
154
+ import('./team-manager/index.js').then(async ({ getTeamStore, MessageClient, setTeammateClient }) => {
155
+ this.teamStore = getTeamStore();
156
+
157
+ // Connect to message broker if port is available
158
+ if (this.brokerPort && this.teamId && this.memberId) {
159
+ this.messageClient = new MessageClient(
160
+ this.teamId,
161
+ this.memberId,
162
+ this.brokerPort
163
+ );
164
+
165
+ // Save to global singleton for TeamCoordinator to use
166
+ setTeammateClient(this.messageClient);
167
+
168
+ this.messageClient.on('message', (msg: any) => {
169
+ this.handleTeamMessage(msg);
170
+ });
171
+
172
+ this.messageClient.on('connected', () => {
173
+ // Silently connected - no output needed
174
+ });
175
+
176
+ this.messageClient.on('disconnected', () => {
177
+ // Silently disconnected - no output needed
178
+ });
179
+
180
+ try {
181
+ await this.messageClient.connect();
182
+ } catch (err) {
183
+ console.error('[Team] Failed to connect to message broker:', err);
184
+ }
185
+ }
186
+ }).catch(() => {
187
+ // Ignore import errors
188
+ });
189
+ }
190
+
116
191
  // Register /clear callback, clear local conversation when clearing dialogue
117
192
  this.slashCommandHandler.setClearCallback(() => {
118
193
  this.conversation = [];
@@ -224,6 +299,233 @@ export class InteractiveSession {
224
299
  await toolRegistry.setSdkMode(true, adapter);
225
300
  }
226
301
 
302
+ /**
303
+ * Handle incoming team message from socket.
304
+ * Messages are queued and must be explicitly processed by the agent.
305
+ */
306
+ private handleTeamMessage(msg: any): void {
307
+ if (!msg || !msg.content) return;
308
+
309
+ let teamMessage: ChatMessage;
310
+
311
+ if (msg.type === 'task_update') {
312
+ let taskInfo: string;
313
+ try {
314
+ const content = typeof msg.content === 'string' ? JSON.parse(msg.content) : msg.content;
315
+ taskInfo = `Task ${content.action}: ${content.title || content.taskId}`;
316
+ if (content.assignee) {
317
+ taskInfo += ` (assignee: ${content.assignee})`;
318
+ }
319
+ if (content.result) {
320
+ taskInfo += ` - Result: ${content.result.substring(0, 100)}...`;
321
+ }
322
+ } catch {
323
+ taskInfo = `Task update: ${msg.content}`;
324
+ }
325
+
326
+ teamMessage = {
327
+ role: 'user',
328
+ content: `<system-notification type="task_update">${taskInfo}</system-notification>`,
329
+ timestamp: msg.timestamp || Date.now()
330
+ };
331
+ } else {
332
+ teamMessage = {
333
+ role: 'user',
334
+ content: `<teammate-message from="${msg.fromMemberId}" type="${msg.type}">${msg.content}</teammate-message>`,
335
+ timestamp: msg.timestamp || Date.now()
336
+ };
337
+ }
338
+
339
+ this.teammateMessageQueue.push(teamMessage);
340
+
341
+ // Only start processing if not already processing
342
+ if (!this.isProcessingQueue && !this._isOperationInProgress) {
343
+ this.startQueueProcessing();
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Start queue processing with proper locking.
349
+ */
350
+ private startQueueProcessing(): void {
351
+ // Prevent multiple queue processing coroutines
352
+ if (this.isProcessingQueue) {
353
+ return;
354
+ }
355
+
356
+ this.isProcessingQueue = true;
357
+ this.queueProcessingPromise = this.processMessageQueue();
358
+
359
+ // Handle completion and errors
360
+ this.queueProcessingPromise
361
+ .catch((error) => {
362
+ console.error('[Team] Queue processing error:', error);
363
+ })
364
+ .finally(() => {
365
+ this.isProcessingQueue = false;
366
+ this.queueProcessingPromise = null;
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Process queued teammate messages one by one.
372
+ * This method should only be called from startQueueProcessing.
373
+ */
374
+ private async processMessageQueue(): Promise<void> {
375
+ while (this.teammateMessageQueue.length > 0) {
376
+ // Check if operation is in progress (e.g., user is typing or another operation)
377
+ if (this._isOperationInProgress) {
378
+ // Wait a bit and retry
379
+ await new Promise(resolve => setTimeout(resolve, 100));
380
+ continue;
381
+ }
382
+
383
+ const message = this.teammateMessageQueue.shift()!;
384
+ this.conversation.push(message);
385
+
386
+ try {
387
+ if (this.remoteAIClient) {
388
+ await this.generateRemoteResponse(0);
389
+ } else {
390
+ await this.generateResponse(0);
391
+ }
392
+ } catch (error) {
393
+ console.error('[Team] Failed to process teammate message:', error);
394
+ // Continue processing remaining messages even if one fails
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Get team mode status.
401
+ */
402
+ getIsTeamMode(): boolean {
403
+ return this.isTeamMode;
404
+ }
405
+
406
+ /**
407
+ * Get team ID.
408
+ */
409
+ getTeamId(): string | null {
410
+ return this.teamId;
411
+ }
412
+
413
+ /**
414
+ * Get member ID.
415
+ */
416
+ getMemberId(): string | null {
417
+ return this.memberId;
418
+ }
419
+
420
+ /**
421
+ * Get member name.
422
+ */
423
+ getMemberName(): string | null {
424
+ return this.memberName;
425
+ }
426
+
427
+ /**
428
+ * Get lead ID.
429
+ */
430
+ getLeadId(): string | null {
431
+ return this.leadId;
432
+ }
433
+
434
+ /**
435
+ * Get spawn prompt (for team mode).
436
+ */
437
+ getSpawnPrompt(): string | null {
438
+ return this.spawnPrompt;
439
+ }
440
+
441
+ /**
442
+ * Get teammate message queue info.
443
+ */
444
+ getTeammateMessageQueueInfo(): { length: number } {
445
+ return {
446
+ length: this.teammateMessageQueue.length,
447
+ };
448
+ }
449
+
450
+ /**
451
+ * Get and clear all teammate messages from queue.
452
+ * Returns all pending messages and clears the queue.
453
+ */
454
+ popTeammateMessages(): ChatMessage[] {
455
+ const messages = [...this.teammateMessageQueue];
456
+ this.teammateMessageQueue = [];
457
+ return messages;
458
+ }
459
+
460
+ /**
461
+ * Connect to team message broker (for lead agent after creating a team).
462
+ * This allows the lead agent to receive real-time messages from teammates.
463
+ */
464
+ async connectToTeamBroker(teamId: string, memberId: string, brokerPort: number): Promise<void> {
465
+ // Skip if already connected
466
+ if (this.messageClient) {
467
+ return;
468
+ }
469
+
470
+ this.isTeamMode = true;
471
+ this.teamId = teamId;
472
+ this.memberId = memberId;
473
+ this.memberRole = 'lead';
474
+ this.brokerPort = brokerPort;
475
+
476
+ try {
477
+ const { getTeamStore, MessageClient } = await import('./team-manager/index.js');
478
+ this.teamStore = getTeamStore();
479
+
480
+ this.messageClient = new MessageClient(teamId, memberId, brokerPort);
481
+
482
+ this.messageClient.on('message', (msg: any) => {
483
+ this.handleTeamMessage(msg);
484
+ });
485
+
486
+ this.messageClient.on('connected', () => {
487
+ // Silently connected - no output needed
488
+ });
489
+
490
+ this.messageClient.on('disconnected', () => {
491
+ // Silently disconnected - no output needed
492
+ });
493
+
494
+ await this.messageClient.connect();
495
+ } catch (err) {
496
+ console.error(colors.error('[Team] Failed to connect to message broker:'), err);
497
+ throw err; // Re-throw to let caller handle it
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Cleanup team mode resources.
503
+ */
504
+ async cleanupTeamMode(): Promise<void> {
505
+ // Clear teammate's persistent MessageClient if exists
506
+ const { clearTeammateClient } = await import('./team-manager/index.js');
507
+ clearTeammateClient();
508
+
509
+ if (this.messageClient) {
510
+ await this.messageClient.disconnect();
511
+ this.messageClient = null;
512
+ }
513
+
514
+ if (this.teamStore && this.teamId && this.memberId) {
515
+ try {
516
+ await this.teamStore.updateMember(this.teamId, this.memberId, {
517
+ status: 'shutdown',
518
+ lastActivity: Date.now()
519
+ });
520
+ } catch {
521
+ // Ignore cleanup errors
522
+ }
523
+ }
524
+
525
+ // Reset team mode flag to restore thinking spinner
526
+ this.isTeamMode = false;
527
+ }
528
+
227
529
  /**
228
530
  * Get SDK mode status.
229
531
  */
@@ -268,11 +570,24 @@ export class InteractiveSession {
268
570
  await skillInvoker.reload();
269
571
 
270
572
  const toolRegistry = getToolRegistry();
573
+
574
+ // Build team context if running in team mode
575
+ const teamContext = this.isTeamMode ? {
576
+ teamId: this.teamId!,
577
+ memberId: this.memberId!,
578
+ memberName: this.memberName!,
579
+ memberRole: this.memberRole!,
580
+ leadId: this.leadId || undefined,
581
+ brokerPort: this.brokerPort || undefined,
582
+ initialTaskId: this.initialTaskId || undefined,
583
+ } : undefined;
584
+
271
585
  const promptGenerator = new SystemPromptGenerator(
272
586
  toolRegistry,
273
587
  this.executionMode,
274
588
  undefined,
275
- this.mcpManager
589
+ this.mcpManager,
590
+ teamContext
276
591
  );
277
592
 
278
593
  // Use the current agent's original system prompt as base
@@ -409,7 +724,7 @@ export class InteractiveSession {
409
724
  let _escCleanup: (() => void) | undefined;
410
725
  if (process.stdin.isTTY) {
411
726
  _escCleanup = setupEscKeyHandler(() => {
412
- if ((this as any)._isOperationInProgress) {
727
+ if (this._isOperationInProgress) {
413
728
  // An operation is running, let it be cancelled
414
729
  this.cancellationManager.cancel();
415
730
  }
@@ -418,16 +733,42 @@ export class InteractiveSession {
418
733
  }
419
734
 
420
735
  // Track if an operation is in progress
421
- (this as any)._isOperationInProgress = false;
736
+ this._isOperationInProgress = false;
422
737
 
423
738
  this.promptLoop();
424
739
 
425
740
  // Keep the promise pending until shutdown
426
741
  return new Promise((resolve) => {
427
- (this as any)._shutdownResolver = resolve;
742
+ this._shutdownResolver = resolve;
428
743
  });
429
744
  }
430
745
 
746
+ private async sendInitialPrompt(prompt: string): Promise<void> {
747
+ console.log(colors.textMuted(`\n📋 Initial prompt: ${prompt}\n`));
748
+
749
+ const userMessage: ChatMessage = {
750
+ role: 'user',
751
+ content: prompt,
752
+ timestamp: Date.now(),
753
+ };
754
+
755
+ this.conversation.push(userMessage);
756
+ await this.conversationManager.addMessage(userMessage);
757
+
758
+ const thinkingConfig = this.configManager.getThinkingConfig();
759
+ let thinkingTokens = 0;
760
+ if (thinkingConfig.enabled) {
761
+ const thinkingMode = detectThinkingKeywords(prompt);
762
+ thinkingTokens = getThinkingTokens(thinkingMode);
763
+ }
764
+
765
+ if (this.remoteAIClient) {
766
+ await this.generateRemoteResponse(thinkingTokens);
767
+ } else {
768
+ await this.generateResponse(thinkingTokens);
769
+ }
770
+ }
771
+
431
772
  private async initialize(): Promise<void> {
432
773
  logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
433
774
 
@@ -957,6 +1298,20 @@ export class InteractiveSession {
957
1298
  return;
958
1299
  }
959
1300
 
1301
+ // Check teammate message queue when agent becomes idle
1302
+ if (this.teammateMessageQueue.length > 0) {
1303
+ await this.processMessageQueue();
1304
+ }
1305
+
1306
+ // If running in team mode with initial prompt, send it immediately and skip user input
1307
+ if (this.spawnPrompt) {
1308
+ const prompt = this.spawnPrompt;
1309
+ this.spawnPrompt = null;
1310
+ await this.sendInitialPrompt(prompt);
1311
+ this.promptLoop();
1312
+ return;
1313
+ }
1314
+
960
1315
  // In SDK mode, use a different input loop
961
1316
  if (this.isSdkMode) {
962
1317
  await this.sdkPromptLoop();
@@ -1579,7 +1934,25 @@ export class InteractiveSession {
1579
1934
 
1580
1935
  const toolRegistry = getToolRegistry();
1581
1936
  const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
1582
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
1937
+
1938
+ // Build team context if running in team mode
1939
+ const teamContext = this.isTeamMode ? {
1940
+ teamId: this.teamId!,
1941
+ memberId: this.memberId!,
1942
+ memberName: this.memberName!,
1943
+ memberRole: this.memberRole!,
1944
+ leadId: this.leadId || undefined,
1945
+ brokerPort: this.brokerPort || undefined,
1946
+ initialTaskId: this.initialTaskId || undefined,
1947
+ } : undefined;
1948
+
1949
+ const systemPromptGenerator = new SystemPromptGenerator(
1950
+ toolRegistry,
1951
+ this.executionMode,
1952
+ undefined,
1953
+ undefined,
1954
+ teamContext
1955
+ );
1583
1956
  const enhancedSystemPrompt =
1584
1957
  await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
1585
1958
 
@@ -1802,15 +2175,15 @@ export class InteractiveSession {
1802
2175
  }
1803
2176
 
1804
2177
  // Mark that an operation is in progress
1805
- (this as any)._isOperationInProgress = true;
2178
+ this._isOperationInProgress = true;
1806
2179
 
1807
2180
  const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
1808
2181
  const icon = colors.primary(icons.brain);
1809
2182
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1810
2183
  let frameIndex = 0;
1811
2184
 
1812
- // SDK 模式下不显示 spinner
1813
- const showThinkingSpinner = !this.isSdkMode;
2185
+ // SDK 模式和 Team 模式下不显示 spinner (team 模式下 teammate 输出会与 spinner 混淆)
2186
+ const showThinkingSpinner = !this.isSdkMode && !this.isTeamMode;
1814
2187
  let spinnerInterval: NodeJS.Timeout | null = null;
1815
2188
 
1816
2189
  // Custom spinner: only icon rotates, text stays static
@@ -1846,11 +2219,24 @@ export class InteractiveSession {
1846
2219
  : toolDefinitions;
1847
2220
 
1848
2221
  const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
2222
+
2223
+ // Build team context if running in team mode
2224
+ const teamContext = this.isTeamMode ? {
2225
+ teamId: this.teamId!,
2226
+ memberId: this.memberId!,
2227
+ memberName: this.memberName!,
2228
+ memberRole: this.memberRole!,
2229
+ leadId: this.leadId || undefined,
2230
+ brokerPort: this.brokerPort || undefined,
2231
+ initialTaskId: this.initialTaskId || undefined,
2232
+ } : undefined;
2233
+
1849
2234
  const systemPromptGenerator = new SystemPromptGenerator(
1850
2235
  toolRegistry,
1851
2236
  this.executionMode,
1852
2237
  undefined,
1853
- this.mcpManager
2238
+ this.mcpManager,
2239
+ teamContext
1854
2240
  );
1855
2241
  const enhancedSystemPrompt =
1856
2242
  await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
@@ -1927,13 +2313,13 @@ export class InteractiveSession {
1927
2313
  }
1928
2314
 
1929
2315
  // Operation completed successfully, clear the flag
1930
- (this as any)._isOperationInProgress = false;
2316
+ this._isOperationInProgress = false;
1931
2317
  } catch (error: any) {
1932
2318
  if (spinnerInterval) clearInterval(spinnerInterval);
1933
2319
  process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
1934
2320
 
1935
2321
  // Clear the operation flag
1936
- (this as any)._isOperationInProgress = false;
2322
+ this._isOperationInProgress = false;
1937
2323
 
1938
2324
  // Signal request completion to SDK
1939
2325
  if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
@@ -2009,7 +2395,7 @@ export class InteractiveSession {
2009
2395
  }
2010
2396
  } catch (error: any) {
2011
2397
  // Clear the operation flag
2012
- (this as any)._isOperationInProgress = false;
2398
+ this._isOperationInProgress = false;
2013
2399
 
2014
2400
  if (error.message === 'Operation cancelled by user') {
2015
2401
  // Notify backend to cancel the task
@@ -2098,7 +2484,7 @@ export class InteractiveSession {
2098
2484
  onComplete?: () => Promise<void>
2099
2485
  ): Promise<void> {
2100
2486
  // Mark that tool execution is in progress
2101
- (this as any)._isOperationInProgress = true;
2487
+ this._isOperationInProgress = true;
2102
2488
 
2103
2489
  const toolRegistry = getToolRegistry();
2104
2490
  const showToolDetails = this.configManager.get('showToolDetails') || false;
@@ -2177,7 +2563,7 @@ export class InteractiveSession {
2177
2563
  // 清理 conversation 中未完成的 tool_call
2178
2564
  this.cleanupIncompleteToolCalls();
2179
2565
 
2180
- (this as any)._isOperationInProgress = false;
2566
+ this._isOperationInProgress = false;
2181
2567
  return;
2182
2568
  }
2183
2569
 
@@ -2450,7 +2836,7 @@ export class InteractiveSession {
2450
2836
  // If GUI agent was cancelled by user, don't continue generating response
2451
2837
  // This avoids wasting API calls and tokens on cancelled tasks
2452
2838
  if (guiSubagentCancelled) {
2453
- (this as any)._isOperationInProgress = false;
2839
+ this._isOperationInProgress = false;
2454
2840
  return;
2455
2841
  }
2456
2842
 
@@ -2934,3 +3320,25 @@ export function setSingletonSession(session: InteractiveSession): void {
2934
3320
  export function getSingletonSession(): InteractiveSession | null {
2935
3321
  return singletonSession;
2936
3322
  }
3323
+
3324
+ /**
3325
+ * Get teammate message queue info from the singleton session.
3326
+ * Returns queue length.
3327
+ */
3328
+ export function getTeammateMessageQueueInfo(): { length: number } | null {
3329
+ if (!singletonSession) {
3330
+ return null;
3331
+ }
3332
+ return singletonSession.getTeammateMessageQueueInfo();
3333
+ }
3334
+
3335
+ /**
3336
+ * Get and clear all teammate messages from the singleton session.
3337
+ * Returns all pending messages and clears the queue.
3338
+ */
3339
+ export function popTeammateMessages(): ChatMessage[] | null {
3340
+ if (!singletonSession) {
3341
+ return null;
3342
+ }
3343
+ return singletonSession.popTeammateMessages();
3344
+ }
@@ -758,13 +758,9 @@ export class SlashCommandHandler {
758
758
  this.configManager.set(configKey, selectedModel);
759
759
  this.configManager.save('global');
760
760
 
761
- // Clear conversation history to avoid tool call ID conflicts between providers
762
- if (this.onClearCallback) {
763
- this.onClearCallback();
764
- console.log(
765
- chalk.cyan(' Conversation cleared to avoid tool call ID conflicts between models.')
766
- );
767
- }
761
+ // Note: In remote mode, we don't need to clear conversation history because
762
+ // all requests go to the same remote server - only the model parameter changes.
763
+ // Tool call IDs are managed by the remote server, not affected by model changes.
768
764
 
769
765
  // Notify InteractiveSession to update aiClient config
770
766
  if (this.onConfigUpdate) {
@@ -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
  /**