@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.
- package/README.md +9 -0
- package/README_CN.md +9 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -1
- package/dist/mcp.d.ts +8 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +53 -20
- package/dist/mcp.js.map +1 -1
- package/dist/sdk-output-adapter.d.ts +79 -0
- package/dist/sdk-output-adapter.d.ts.map +1 -1
- package/dist/sdk-output-adapter.js +118 -0
- package/dist/sdk-output-adapter.js.map +1 -1
- package/dist/session.d.ts +88 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +351 -5
- package/dist/session.js.map +1 -1
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +3 -5
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +1 -0
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.d.ts +15 -1
- package/dist/system-prompt-generator.d.ts.map +1 -1
- package/dist/system-prompt-generator.js +36 -27
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/team-manager/index.d.ts +6 -0
- package/dist/team-manager/index.d.ts.map +1 -0
- package/dist/team-manager/index.js +6 -0
- package/dist/team-manager/index.js.map +1 -0
- package/dist/team-manager/message-broker.d.ts +128 -0
- package/dist/team-manager/message-broker.d.ts.map +1 -0
- package/dist/team-manager/message-broker.js +638 -0
- package/dist/team-manager/message-broker.js.map +1 -0
- package/dist/team-manager/team-coordinator.d.ts +45 -0
- package/dist/team-manager/team-coordinator.d.ts.map +1 -0
- package/dist/team-manager/team-coordinator.js +887 -0
- package/dist/team-manager/team-coordinator.js.map +1 -0
- package/dist/team-manager/team-store.d.ts +49 -0
- package/dist/team-manager/team-store.d.ts.map +1 -0
- package/dist/team-manager/team-store.js +436 -0
- package/dist/team-manager/team-store.js.map +1 -0
- package/dist/team-manager/teammate-spawner.d.ts +86 -0
- package/dist/team-manager/teammate-spawner.d.ts.map +1 -0
- package/dist/team-manager/teammate-spawner.js +605 -0
- package/dist/team-manager/teammate-spawner.js.map +1 -0
- package/dist/team-manager/types.d.ts +164 -0
- package/dist/team-manager/types.d.ts.map +1 -0
- package/dist/team-manager/types.js +27 -0
- package/dist/team-manager/types.js.map +1 -0
- package/dist/tools.d.ts +41 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +288 -32
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +20 -0
- package/src/mcp.ts +64 -25
- package/src/sdk-output-adapter.ts +177 -0
- package/src/session.ts +423 -15
- package/src/slash-commands.ts +3 -7
- package/src/smart-approval.ts +1 -0
- package/src/system-prompt-generator.ts +59 -26
- package/src/team-manager/index.ts +5 -0
- package/src/team-manager/message-broker.ts +751 -0
- package/src/team-manager/team-coordinator.ts +1117 -0
- package/src/team-manager/team-store.ts +558 -0
- package/src/team-manager/teammate-spawner.ts +800 -0
- package/src/team-manager/types.ts +206 -0
- 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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/slash-commands.ts
CHANGED
|
@@ -758,13 +758,9 @@ export class SlashCommandHandler {
|
|
|
758
758
|
this.configManager.set(configKey, selectedModel);
|
|
759
759
|
this.configManager.save('global');
|
|
760
760
|
|
|
761
|
-
//
|
|
762
|
-
|
|
763
|
-
|
|
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) {
|