@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.
- 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 +352 -20
- package/dist/session.js.map +1 -1
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +4 -39
- 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 +424 -36
- package/src/slash-commands.ts +5 -44
- 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
|
@@ -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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/slash-commands.ts
CHANGED
|
@@ -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
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
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
|
-
|
|
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
|
}
|