@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
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import { TeamStore, getTeamStore } from './team-store.js';
|
|
2
|
+
import { TeammateSpawner, getTeammateSpawner } from './teammate-spawner.js';
|
|
3
|
+
import { MessageBroker, MessageClient, getMessageBroker, removeMessageBroker, getTeammateClient } from './message-broker.js';
|
|
4
|
+
import {
|
|
5
|
+
TeamToolParams,
|
|
6
|
+
TeamMember,
|
|
7
|
+
TeamTask,
|
|
8
|
+
MessageDeliveryInfo,
|
|
9
|
+
LEAD_PERMISSIONS,
|
|
10
|
+
TEAMMATE_PERMISSIONS,
|
|
11
|
+
MemberPermissions,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
import { colors, icons } from '../theme.js';
|
|
14
|
+
|
|
15
|
+
export class TeamCoordinator {
|
|
16
|
+
private store: TeamStore;
|
|
17
|
+
private spawner: TeammateSpawner;
|
|
18
|
+
private brokers: Map<string, MessageBroker> = new Map();
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.store = getTeamStore();
|
|
22
|
+
this.spawner = getTeammateSpawner();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async getBroker(teamId: string): Promise<MessageBroker> {
|
|
26
|
+
if (!this.brokers.has(teamId)) {
|
|
27
|
+
const broker = getMessageBroker(teamId);
|
|
28
|
+
if (!broker.isConnected()) {
|
|
29
|
+
await broker.start();
|
|
30
|
+
}
|
|
31
|
+
this.brokers.set(teamId, broker);
|
|
32
|
+
}
|
|
33
|
+
return this.brokers.get(teamId)!;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async broadcastTaskUpdate(
|
|
37
|
+
teamId: string,
|
|
38
|
+
fromMemberId: string,
|
|
39
|
+
taskId: string,
|
|
40
|
+
action: 'created' | 'claimed' | 'completed' | 'released' | 'deleted',
|
|
41
|
+
taskInfo?: { title: string; assignee?: string; result?: string }
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const envBrokerPort = process.env.XAGENT_BROKER_PORT;
|
|
44
|
+
const content = JSON.stringify({
|
|
45
|
+
taskId,
|
|
46
|
+
action,
|
|
47
|
+
...taskInfo,
|
|
48
|
+
timestamp: Date.now()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Teammate processes use MessageClient to broadcast
|
|
52
|
+
if (envBrokerPort && fromMemberId === process.env.XAGENT_MEMBER_ID) {
|
|
53
|
+
await this.broadcastAsTeammate(teamId, fromMemberId, parseInt(envBrokerPort, 10), content, 'task_update');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Lead processes use broker directly
|
|
58
|
+
try {
|
|
59
|
+
const broker = await this.getBroker(teamId);
|
|
60
|
+
broker.sendMessage(fromMemberId, 'broadcast', content, 'task_update');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.warn('[Team] Failed to broadcast task update:', error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Broadcast message as teammate using persistent MessageClient
|
|
68
|
+
*/
|
|
69
|
+
private async broadcastAsTeammate(
|
|
70
|
+
teamId: string,
|
|
71
|
+
memberId: string,
|
|
72
|
+
brokerPort: number,
|
|
73
|
+
content: string,
|
|
74
|
+
type: string
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
// Try to use the persistent client first
|
|
77
|
+
const persistentClient = getTeammateClient();
|
|
78
|
+
|
|
79
|
+
if (persistentClient && persistentClient.isConnected()) {
|
|
80
|
+
if (type === 'task_update') {
|
|
81
|
+
persistentClient.sendTaskUpdate('', '', content);
|
|
82
|
+
} else {
|
|
83
|
+
persistentClient.broadcast(content);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Fallback: create temporary connection
|
|
89
|
+
return this.broadcastWithTempClient(teamId, memberId, brokerPort, content, type);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fallback: broadcast with temporary MessageClient connection
|
|
94
|
+
*/
|
|
95
|
+
private async broadcastWithTempClient(
|
|
96
|
+
teamId: string,
|
|
97
|
+
memberId: string,
|
|
98
|
+
brokerPort: number,
|
|
99
|
+
content: string,
|
|
100
|
+
type: string
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
const client = new MessageClient(teamId, memberId, brokerPort);
|
|
104
|
+
let resolved = false;
|
|
105
|
+
|
|
106
|
+
const cleanup = () => {
|
|
107
|
+
if (!resolved) {
|
|
108
|
+
resolved = true;
|
|
109
|
+
client.disconnect().catch(() => {});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Timeout after 5 seconds
|
|
114
|
+
const timeout = setTimeout(() => {
|
|
115
|
+
cleanup();
|
|
116
|
+
resolve(); // Resolve anyway, broadcast is fire-and-forget
|
|
117
|
+
}, 5000);
|
|
118
|
+
|
|
119
|
+
client.on('connected', () => {
|
|
120
|
+
if (type === 'task_update') {
|
|
121
|
+
client.sendTaskUpdate('', '', content);
|
|
122
|
+
} else {
|
|
123
|
+
client.broadcast(content);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Small delay before closing
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
cleanup();
|
|
130
|
+
resolve();
|
|
131
|
+
}, 300);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
client.on('error', () => {
|
|
135
|
+
clearTimeout(timeout);
|
|
136
|
+
cleanup();
|
|
137
|
+
resolve(); // Resolve anyway, broadcast is fire-and-forget
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
client.on('disconnected', () => {
|
|
141
|
+
if (!resolved) {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
cleanup();
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
client.connect().catch(() => {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
cleanup();
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private checkPermission(
|
|
157
|
+
memberPermissions: MemberPermissions,
|
|
158
|
+
action: string
|
|
159
|
+
): boolean {
|
|
160
|
+
const permissionMap: Record<string, keyof MemberPermissions> = {
|
|
161
|
+
createTask: 'canCreateTask',
|
|
162
|
+
assignTask: 'canAssignTask',
|
|
163
|
+
claimTask: 'canClaimTask',
|
|
164
|
+
completeTask: 'canCompleteTask',
|
|
165
|
+
deleteTask: 'canDeleteTask',
|
|
166
|
+
messageAll: 'canMessageAll',
|
|
167
|
+
messageDirect: 'canMessageDirect',
|
|
168
|
+
shutdownTeam: 'canShutdownTeam',
|
|
169
|
+
shutdownMember: 'canShutdownMember',
|
|
170
|
+
inviteMembers: 'canInviteMembers',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const permissionKey = permissionMap[action];
|
|
174
|
+
if (!permissionKey) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return memberPermissions[permissionKey];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async execute(
|
|
181
|
+
params: TeamToolParams
|
|
182
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
183
|
+
const memberId = process.env.XAGENT_MEMBER_ID;
|
|
184
|
+
// Role determined by action type:
|
|
185
|
+
// - create: caller is lead
|
|
186
|
+
// - other actions: if XAGENT_MEMBER_ID is not set, caller is lead
|
|
187
|
+
const isTeamLead = params.team_action === 'create' || !memberId;
|
|
188
|
+
|
|
189
|
+
const permissions = isTeamLead ? LEAD_PERMISSIONS : TEAMMATE_PERMISSIONS;
|
|
190
|
+
|
|
191
|
+
switch (params.team_action) {
|
|
192
|
+
case 'create':
|
|
193
|
+
return this.createTeam(params);
|
|
194
|
+
case 'spawn':
|
|
195
|
+
if (!this.checkPermission(permissions, 'inviteMembers')) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
message: 'Permission denied: Only team lead can invite new members',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return this.spawnTeammate(params);
|
|
202
|
+
case 'message': {
|
|
203
|
+
const canMessage = params.message?.to_member_id === 'broadcast'
|
|
204
|
+
? this.checkPermission(permissions, 'messageAll')
|
|
205
|
+
: this.checkPermission(permissions, 'messageDirect');
|
|
206
|
+
if (!canMessage) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
message: 'Permission denied: You cannot send broadcast messages',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return this.sendTeamMessage(params);
|
|
213
|
+
}
|
|
214
|
+
case 'task_create':
|
|
215
|
+
if (!this.checkPermission(permissions, 'createTask')) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
message: 'Permission denied: You cannot create tasks',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return this.createTeamTask(params, memberId);
|
|
222
|
+
case 'task_update':
|
|
223
|
+
return this.updateTeamTask(params, memberId, permissions);
|
|
224
|
+
case 'task_delete':
|
|
225
|
+
if (!this.checkPermission(permissions, 'deleteTask')) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
message: 'Permission denied: Only lead can delete tasks',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return this.deleteTeamTask(params);
|
|
232
|
+
case 'task_list':
|
|
233
|
+
return this.listTeamTasks(params);
|
|
234
|
+
case 'shutdown':
|
|
235
|
+
if (!params.team_id) {
|
|
236
|
+
return { success: false, message: 'team_id is required for shutdown' };
|
|
237
|
+
}
|
|
238
|
+
{
|
|
239
|
+
const team = await this.store.getTeam(params.team_id);
|
|
240
|
+
if (!team) {
|
|
241
|
+
return { success: false, message: `Team ${params.team_id} not found` };
|
|
242
|
+
}
|
|
243
|
+
const actualMemberId = memberId || team.leadMemberId;
|
|
244
|
+
|
|
245
|
+
// Prevent shutting down self - use cleanup to shutdown entire team
|
|
246
|
+
if (params.member_id === 'self' || params.member_id === actualMemberId) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
message: 'Cannot shutdown yourself.',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Prevent shutting down lead - lead should only be removed via cleanup
|
|
254
|
+
if (params.member_id === team.leadMemberId) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
message: 'Cannot shutdown team lead.',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (!params.member_id) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
message: 'member_id is required for shutdown',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (!this.checkPermission(permissions, 'shutdownMember')) {
|
|
268
|
+
return {
|
|
269
|
+
success: false,
|
|
270
|
+
message: 'Permission denied: Only team lead can shutdown other members',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return this.shutdownTeammate(params, params.member_id);
|
|
274
|
+
case 'cleanup':
|
|
275
|
+
if (!this.checkPermission(permissions, 'shutdownTeam')) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
message: 'Permission denied: Only team lead can cleanup the team',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return this.cleanupTeam(params);
|
|
282
|
+
case 'list_teams':
|
|
283
|
+
return this.listTeams();
|
|
284
|
+
case 'get_status':
|
|
285
|
+
return this.getTeamStatus(params);
|
|
286
|
+
default:
|
|
287
|
+
throw new Error(`Unknown team action: ${params.team_action}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async createTeam(
|
|
292
|
+
params: TeamToolParams
|
|
293
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
294
|
+
if (!params.team_name) {
|
|
295
|
+
return { success: false, message: 'team_name is required' };
|
|
296
|
+
}
|
|
297
|
+
if (!params.teammates || params.teammates.length === 0) {
|
|
298
|
+
return { success: false, message: 'teammates is required' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 检查每个 teammate 配置
|
|
302
|
+
for (let i = 0; i < params.teammates.length; i++) {
|
|
303
|
+
const t = params.teammates[i];
|
|
304
|
+
if (!t.name) {
|
|
305
|
+
return { success: false, message: `teammates[${i}].name is required` };
|
|
306
|
+
}
|
|
307
|
+
if (!t.role) {
|
|
308
|
+
return { success: false, message: `teammates[${i}].role is required` };
|
|
309
|
+
}
|
|
310
|
+
if (!t.prompt) {
|
|
311
|
+
return { success: false, message: `teammates[${i}].prompt is required` };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const team = await this.store.createTeam(
|
|
316
|
+
params.team_name || 'unnamed-team',
|
|
317
|
+
process.env.XAGENT_SESSION_ID || 'lead',
|
|
318
|
+
process.cwd()
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const displayMode = 'auto';
|
|
322
|
+
|
|
323
|
+
const broker = await this.getBroker(team.teamId);
|
|
324
|
+
const brokerPort = broker.getPort();
|
|
325
|
+
|
|
326
|
+
console.log(
|
|
327
|
+
colors.primaryBright(`\n${icons.rocket} Team "${team.teamName}" created`)
|
|
328
|
+
);
|
|
329
|
+
console.log(colors.textMuted(` ${icons.arrow} Broker: port ${brokerPort}`));
|
|
330
|
+
|
|
331
|
+
const spawnedMembers: TeamMember[] = [];
|
|
332
|
+
const initialTasks: { taskId: string; title: string; assignee: string }[] = [];
|
|
333
|
+
|
|
334
|
+
const leadMember = team.members[0];
|
|
335
|
+
const leadMemberId = leadMember?.memberId;
|
|
336
|
+
|
|
337
|
+
// Spawn teammates with lead ID
|
|
338
|
+
if (params.teammates && params.teammates.length > 0) {
|
|
339
|
+
console.log(colors.text(` ${icons.bullet} Members:`));
|
|
340
|
+
|
|
341
|
+
for (const teammateConfig of params.teammates) {
|
|
342
|
+
// Create initial task BEFORE spawning, so we can pass task ID to teammate
|
|
343
|
+
const task = await this.store.createTask(team.teamId, {
|
|
344
|
+
title: `Initial task for ${teammateConfig.name}`,
|
|
345
|
+
description: teammateConfig.prompt,
|
|
346
|
+
priority: 'high',
|
|
347
|
+
}, 'lead');
|
|
348
|
+
|
|
349
|
+
initialTasks.push({
|
|
350
|
+
taskId: task.taskId,
|
|
351
|
+
title: task.title,
|
|
352
|
+
assignee: '', // Will be set after spawn
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Spawn teammate with initial task ID and lead ID
|
|
356
|
+
const member = await this.spawner.spawnTeammate(
|
|
357
|
+
team.teamId,
|
|
358
|
+
teammateConfig,
|
|
359
|
+
team.workDir,
|
|
360
|
+
displayMode,
|
|
361
|
+
brokerPort,
|
|
362
|
+
task.taskId, // Pass initial task ID
|
|
363
|
+
leadMemberId // Pass lead member ID
|
|
364
|
+
);
|
|
365
|
+
spawnedMembers.push(member);
|
|
366
|
+
const displayName = member.name || member.memberId.slice(0, 8);
|
|
367
|
+
const statusIcon = member.status === 'active' ? '🟢' : '🟡';
|
|
368
|
+
|
|
369
|
+
console.log(
|
|
370
|
+
` ${statusIcon} ${colors.primary(displayName)} ${colors.textMuted(`(${member.memberRole || member.role})`)}`
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Mark task as in_progress and assign to the teammate
|
|
374
|
+
await this.store.updateTask(team.teamId, task.taskId, {
|
|
375
|
+
status: 'in_progress',
|
|
376
|
+
assignee: member.memberId,
|
|
377
|
+
}, task.version);
|
|
378
|
+
|
|
379
|
+
// Update initialTasks with assignee
|
|
380
|
+
initialTasks[initialTasks.length - 1].assignee = member.memberId;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
success: true,
|
|
386
|
+
message: `Team "${team.teamName}" created successfully with ${initialTasks.length} initial tasks`,
|
|
387
|
+
result: {
|
|
388
|
+
team_id: team.teamId,
|
|
389
|
+
team_name: team.teamName,
|
|
390
|
+
display_mode: displayMode,
|
|
391
|
+
lead_id: leadMemberId,
|
|
392
|
+
your_role: 'lead',
|
|
393
|
+
your_member_id: leadMemberId,
|
|
394
|
+
broker_port: brokerPort,
|
|
395
|
+
is_team_lead: true,
|
|
396
|
+
members: spawnedMembers.map((m) => ({
|
|
397
|
+
id: m.memberId,
|
|
398
|
+
name: m.name,
|
|
399
|
+
role: m.memberRole || m.role,
|
|
400
|
+
display_mode: m.displayMode,
|
|
401
|
+
})),
|
|
402
|
+
initial_tasks: initialTasks,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private async spawnTeammate(
|
|
408
|
+
params: TeamToolParams
|
|
409
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
410
|
+
if (!params.team_id) {
|
|
411
|
+
return { success: false, message: 'team_id is required' };
|
|
412
|
+
}
|
|
413
|
+
if (!params.teammates || params.teammates.length === 0) {
|
|
414
|
+
return { success: false, message: 'teammates[0] is required' };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 只检查第一个 teammate
|
|
418
|
+
const t = params.teammates[0];
|
|
419
|
+
if (!t.name) {
|
|
420
|
+
return { success: false, message: 'teammates[0].name is required' };
|
|
421
|
+
}
|
|
422
|
+
if (!t.role) {
|
|
423
|
+
return { success: false, message: 'teammates[0].role is required' };
|
|
424
|
+
}
|
|
425
|
+
if (!t.prompt) {
|
|
426
|
+
return { success: false, message: 'teammates[0].prompt is required' };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const team = await this.store.getTeam(params.team_id);
|
|
430
|
+
if (!team) {
|
|
431
|
+
return { success: false, message: `Team ${params.team_id} not found` };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const displayMode = 'auto';
|
|
435
|
+
const broker = await this.getBroker(params.team_id);
|
|
436
|
+
const brokerPort = broker.getPort();
|
|
437
|
+
const leadMemberId = team.leadMemberId;
|
|
438
|
+
|
|
439
|
+
// 只 spawn 一个 teammate
|
|
440
|
+
const teammateConfig = params.teammates[0];
|
|
441
|
+
const member = await this.spawner.spawnTeammate(
|
|
442
|
+
params.team_id,
|
|
443
|
+
teammateConfig,
|
|
444
|
+
team.workDir,
|
|
445
|
+
displayMode,
|
|
446
|
+
brokerPort,
|
|
447
|
+
undefined, // No initial task ID for dynamic spawn
|
|
448
|
+
leadMemberId
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
const displayName = member.name || member.memberId.slice(0, 8);
|
|
452
|
+
const statusIcon = member.status === 'active' ? '🟢' : '🟡';
|
|
453
|
+
console.log(
|
|
454
|
+
` ${statusIcon} ${colors.success('Spawned:')} ${colors.primary(displayName)} ${colors.textMuted(`(${member.memberRole || member.role})`)}`
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
success: true,
|
|
459
|
+
message: `Spawned teammate: ${displayName}`,
|
|
460
|
+
result: {
|
|
461
|
+
team_id: params.team_id,
|
|
462
|
+
member_id: member.memberId,
|
|
463
|
+
name: member.name,
|
|
464
|
+
role: member.memberRole || member.role,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private async sendTeamMessage(
|
|
470
|
+
params: TeamToolParams
|
|
471
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
472
|
+
if (!params.team_id) {
|
|
473
|
+
throw new Error('team_id is required');
|
|
474
|
+
}
|
|
475
|
+
if (!params.message) {
|
|
476
|
+
throw new Error('message is required');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const team = await this.store.getTeam(params.team_id);
|
|
480
|
+
if (!team) {
|
|
481
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Determine fromMemberId: use env var for teammates, or team.leadMemberId for lead
|
|
485
|
+
const envMemberId = process.env.XAGENT_MEMBER_ID;
|
|
486
|
+
const envBrokerPort = process.env.XAGENT_BROKER_PORT;
|
|
487
|
+
const fromMemberId = envMemberId || team.leadMemberId;
|
|
488
|
+
|
|
489
|
+
// Resolve target: convert 'lead' to actual leadMemberId for direct messages
|
|
490
|
+
let targetMemberId = params.message.to_member_id || 'broadcast';
|
|
491
|
+
if (targetMemberId === 'lead') {
|
|
492
|
+
targetMemberId = team.leadMemberId;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Check if this is a teammate process (has broker port env var)
|
|
496
|
+
// Teammate should use MessageClient to connect to lead's broker
|
|
497
|
+
if (envBrokerPort && envMemberId) {
|
|
498
|
+
return this.sendMessageAsTeammate(
|
|
499
|
+
params.team_id,
|
|
500
|
+
envMemberId,
|
|
501
|
+
parseInt(envBrokerPort, 10),
|
|
502
|
+
targetMemberId,
|
|
503
|
+
params.message.content
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Lead process: use broker directly
|
|
508
|
+
const broker = await this.getBroker(params.team_id);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const { message, deliveryInfo } = await broker.sendMessageWithAck(
|
|
512
|
+
fromMemberId,
|
|
513
|
+
targetMemberId,
|
|
514
|
+
params.message.content
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const isBroadcast =
|
|
518
|
+
params.message.to_member_id === 'broadcast' || !params.message.to_member_id;
|
|
519
|
+
const ackCount = Array.isArray(deliveryInfo)
|
|
520
|
+
? deliveryInfo.filter((d) => d.status === 'acknowledged').length
|
|
521
|
+
: deliveryInfo.status === 'acknowledged'
|
|
522
|
+
? 1
|
|
523
|
+
: 0;
|
|
524
|
+
const totalCount = Array.isArray(deliveryInfo) ? deliveryInfo.length : 1;
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
success: true,
|
|
528
|
+
message: isBroadcast
|
|
529
|
+
? `Message broadcasted and acknowledged by ${ackCount}/${totalCount} members`
|
|
530
|
+
: `Message delivered and acknowledged`,
|
|
531
|
+
result: {
|
|
532
|
+
message_id: message.messageId,
|
|
533
|
+
delivered_to: message.toMemberId,
|
|
534
|
+
delivery_status: Array.isArray(deliveryInfo)
|
|
535
|
+
? deliveryInfo.map((d) => ({
|
|
536
|
+
member_id: d.acknowledgedBy?.[0],
|
|
537
|
+
status: d.status,
|
|
538
|
+
}))
|
|
539
|
+
: { status: deliveryInfo.status, acknowledged_at: deliveryInfo.acknowledgedAt },
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
} catch (error) {
|
|
543
|
+
return {
|
|
544
|
+
success: false,
|
|
545
|
+
message: `Failed to send message: ${error instanceof Error ? error.message : String(error)}`,
|
|
546
|
+
result: undefined,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Send message as teammate using persistent MessageClient
|
|
553
|
+
*/
|
|
554
|
+
private async sendMessageAsTeammate(
|
|
555
|
+
teamId: string,
|
|
556
|
+
memberId: string,
|
|
557
|
+
brokerPort: number,
|
|
558
|
+
targetMemberId: string,
|
|
559
|
+
content: string
|
|
560
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
561
|
+
// Try to use the persistent client first
|
|
562
|
+
const persistentClient = getTeammateClient();
|
|
563
|
+
|
|
564
|
+
if (persistentClient && persistentClient.isConnected()) {
|
|
565
|
+
// Send message using appropriate method
|
|
566
|
+
if (targetMemberId === 'broadcast') {
|
|
567
|
+
persistentClient.broadcast(content);
|
|
568
|
+
} else {
|
|
569
|
+
persistentClient.sendDirect(targetMemberId, content);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
success: true,
|
|
576
|
+
message: `Message sent to ${targetMemberId}`,
|
|
577
|
+
result: {
|
|
578
|
+
message_id: messageId,
|
|
579
|
+
delivered_to: targetMemberId,
|
|
580
|
+
delivery_status: { status: 'sent' },
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Fallback: create temporary connection if persistent client not available
|
|
586
|
+
return this.sendMessageWithTempClient(teamId, memberId, brokerPort, targetMemberId, content);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Fallback: send message with temporary MessageClient connection
|
|
591
|
+
*/
|
|
592
|
+
private async sendMessageWithTempClient(
|
|
593
|
+
teamId: string,
|
|
594
|
+
memberId: string,
|
|
595
|
+
brokerPort: number,
|
|
596
|
+
targetMemberId: string,
|
|
597
|
+
content: string
|
|
598
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
599
|
+
return new Promise((resolve) => {
|
|
600
|
+
const client = new MessageClient(teamId, memberId, brokerPort);
|
|
601
|
+
let resolved = false;
|
|
602
|
+
|
|
603
|
+
const cleanup = () => {
|
|
604
|
+
if (!resolved) {
|
|
605
|
+
resolved = true;
|
|
606
|
+
client.disconnect().catch(() => {});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// Timeout after 10 seconds
|
|
611
|
+
const timeout = setTimeout(() => {
|
|
612
|
+
cleanup();
|
|
613
|
+
resolve({
|
|
614
|
+
success: false,
|
|
615
|
+
message: 'Failed to send message: timeout',
|
|
616
|
+
result: undefined,
|
|
617
|
+
});
|
|
618
|
+
}, 10000);
|
|
619
|
+
|
|
620
|
+
client.on('connected', () => {
|
|
621
|
+
// Send message using appropriate method
|
|
622
|
+
if (targetMemberId === 'broadcast') {
|
|
623
|
+
client.broadcast(content);
|
|
624
|
+
} else {
|
|
625
|
+
client.sendDirect(targetMemberId, content);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
629
|
+
|
|
630
|
+
// Give a small delay for the message to be sent before closing
|
|
631
|
+
setTimeout(() => {
|
|
632
|
+
clearTimeout(timeout);
|
|
633
|
+
cleanup();
|
|
634
|
+
resolve({
|
|
635
|
+
success: true,
|
|
636
|
+
message: `Message sent to ${targetMemberId}`,
|
|
637
|
+
result: {
|
|
638
|
+
message_id: messageId,
|
|
639
|
+
delivered_to: targetMemberId,
|
|
640
|
+
delivery_status: { status: 'sent' },
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
}, 500);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
client.on('error', (err: Error) => {
|
|
647
|
+
clearTimeout(timeout);
|
|
648
|
+
cleanup();
|
|
649
|
+
resolve({
|
|
650
|
+
success: false,
|
|
651
|
+
message: `Failed to send message: ${err.message}`,
|
|
652
|
+
result: undefined,
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
client.on('disconnected', () => {
|
|
657
|
+
if (!resolved) {
|
|
658
|
+
clearTimeout(timeout);
|
|
659
|
+
cleanup();
|
|
660
|
+
resolve({
|
|
661
|
+
success: false,
|
|
662
|
+
message: 'Failed to send message: disconnected from broker',
|
|
663
|
+
result: undefined,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
client.connect().catch((err) => {
|
|
669
|
+
clearTimeout(timeout);
|
|
670
|
+
cleanup();
|
|
671
|
+
resolve({
|
|
672
|
+
success: false,
|
|
673
|
+
message: `Failed to connect to broker: ${err.message}`,
|
|
674
|
+
result: undefined,
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async getMessageDeliveryInfo(
|
|
681
|
+
teamId: string,
|
|
682
|
+
messageId: string
|
|
683
|
+
): Promise<MessageDeliveryInfo | undefined> {
|
|
684
|
+
const broker = this.brokers.get(teamId);
|
|
685
|
+
if (!broker) {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
return broker.getDeliveryInfo(messageId);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private async createTeamTask(
|
|
692
|
+
params: TeamToolParams,
|
|
693
|
+
createdBy: string | undefined
|
|
694
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
695
|
+
if (!params.team_id) {
|
|
696
|
+
throw new Error('team_id is required');
|
|
697
|
+
}
|
|
698
|
+
if (!params.task_config) {
|
|
699
|
+
throw new Error('task_config is required');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Get team to resolve leadMemberId if createdBy is not provided
|
|
703
|
+
const team = await this.store.getTeam(params.team_id);
|
|
704
|
+
if (!team) {
|
|
705
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
706
|
+
}
|
|
707
|
+
const actualCreatedBy = createdBy || team.leadMemberId;
|
|
708
|
+
|
|
709
|
+
const task = await this.store.createTask(params.team_id, params.task_config, actualCreatedBy);
|
|
710
|
+
|
|
711
|
+
console.log(colors.success(`✓ Task created: ${task.title} (${task.taskId})`));
|
|
712
|
+
|
|
713
|
+
await this.broadcastTaskUpdate(params.team_id!, actualCreatedBy, task.taskId, 'created', {
|
|
714
|
+
title: task.title
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
success: true,
|
|
719
|
+
message: `Task "${task.title}" created successfully`,
|
|
720
|
+
result: {
|
|
721
|
+
task_id: task.taskId,
|
|
722
|
+
title: task.title,
|
|
723
|
+
description: task.description,
|
|
724
|
+
status: task.status,
|
|
725
|
+
priority: task.priority,
|
|
726
|
+
assignee: task.assignee,
|
|
727
|
+
dependencies: task.dependencies,
|
|
728
|
+
created_at: task.createdAt,
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private async updateTeamTask(
|
|
734
|
+
params: TeamToolParams,
|
|
735
|
+
memberId: string | undefined,
|
|
736
|
+
permissions: MemberPermissions
|
|
737
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
738
|
+
if (!params.team_id) {
|
|
739
|
+
throw new Error('team_id is required');
|
|
740
|
+
}
|
|
741
|
+
if (!params.task_update) {
|
|
742
|
+
throw new Error('task_update is required');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Get team to resolve leadMemberId if memberId is not provided
|
|
746
|
+
const team = await this.store.getTeam(params.team_id);
|
|
747
|
+
if (!team) {
|
|
748
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
749
|
+
}
|
|
750
|
+
const actualMemberId = memberId || team.leadMemberId;
|
|
751
|
+
|
|
752
|
+
const { task_id, action, result } = params.task_update;
|
|
753
|
+
|
|
754
|
+
if (action === 'claim') {
|
|
755
|
+
if (!this.checkPermission(permissions, 'claimTask')) {
|
|
756
|
+
return {
|
|
757
|
+
success: false,
|
|
758
|
+
message: 'Permission denied: You cannot claim tasks',
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const claimedTask = await this.store.claimTask(params.team_id, task_id, actualMemberId);
|
|
764
|
+
if (!claimedTask) {
|
|
765
|
+
return {
|
|
766
|
+
success: false,
|
|
767
|
+
message: `Task ${task_id} not found`,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'claimed', {
|
|
772
|
+
title: claimedTask.title,
|
|
773
|
+
assignee: claimedTask.assignee
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
return {
|
|
777
|
+
success: true,
|
|
778
|
+
message: `Task ${task_id} claimed successfully`,
|
|
779
|
+
result: {
|
|
780
|
+
task_id: claimedTask.taskId,
|
|
781
|
+
status: claimedTask.status,
|
|
782
|
+
assignee: claimedTask.assignee,
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
} catch (error) {
|
|
786
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
787
|
+
return {
|
|
788
|
+
success: false,
|
|
789
|
+
message: errorMessage,
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (action === 'complete') {
|
|
795
|
+
if (!this.checkPermission(permissions, 'completeTask')) {
|
|
796
|
+
return {
|
|
797
|
+
success: false,
|
|
798
|
+
message: 'Permission denied: You cannot complete tasks',
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const existingTask = await this.store.getTask(params.team_id, task_id);
|
|
803
|
+
if (!existingTask) {
|
|
804
|
+
return { success: false, message: `Task ${task_id} not found` };
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const task = await this.store.updateTask(params.team_id, task_id, {
|
|
808
|
+
status: 'completed',
|
|
809
|
+
result,
|
|
810
|
+
}, existingTask.version);
|
|
811
|
+
if (!task) {
|
|
812
|
+
return { success: false, message: `Task ${task_id} update failed` };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'completed', {
|
|
816
|
+
title: task.title,
|
|
817
|
+
result: task.result
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
return {
|
|
821
|
+
success: true,
|
|
822
|
+
message: `Task ${task_id} completed`,
|
|
823
|
+
result: {
|
|
824
|
+
task_id: task.taskId,
|
|
825
|
+
status: task.status,
|
|
826
|
+
result: task.result,
|
|
827
|
+
},
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (action === 'release') {
|
|
832
|
+
const existingTask = await this.store.getTask(params.team_id, task_id);
|
|
833
|
+
if (!existingTask) {
|
|
834
|
+
return { success: false, message: `Task ${task_id} not found` };
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const task = await this.store.updateTask(params.team_id, task_id, {
|
|
838
|
+
status: 'pending',
|
|
839
|
+
assignee: undefined,
|
|
840
|
+
}, existingTask.version);
|
|
841
|
+
if (!task) {
|
|
842
|
+
return { success: false, message: `Task ${task_id} update failed` };
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
await this.broadcastTaskUpdate(params.team_id, actualMemberId, task_id, 'released', {
|
|
846
|
+
title: task.title
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
success: true,
|
|
851
|
+
message: `Task ${task_id} released back to pool`,
|
|
852
|
+
result: {
|
|
853
|
+
task_id: task.taskId,
|
|
854
|
+
status: task.status,
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
throw new Error(`Unknown task action: ${action}`);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private async deleteTeamTask(
|
|
863
|
+
params: TeamToolParams
|
|
864
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
865
|
+
if (!params.team_id) {
|
|
866
|
+
throw new Error('team_id is required');
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const team = await this.store.getTeam(params.team_id);
|
|
870
|
+
if (!team) {
|
|
871
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const memberId = process.env.XAGENT_MEMBER_ID || team.leadMemberId;
|
|
875
|
+
const taskId = params.task_update?.task_id;
|
|
876
|
+
if (!taskId) {
|
|
877
|
+
throw new Error('task_id is required for deletion');
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const existingTask = await this.store.getTask(params.team_id, taskId);
|
|
881
|
+
if (!existingTask) {
|
|
882
|
+
return { success: false, message: `Task ${taskId} not found` };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const deleted = await this.store.deleteTask(params.team_id, taskId, existingTask.version);
|
|
886
|
+
if (!deleted) {
|
|
887
|
+
return { success: false, message: `Task ${taskId} was modified by another member` };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
console.log(colors.success(`✓ Task ${taskId} deleted`));
|
|
891
|
+
|
|
892
|
+
await this.broadcastTaskUpdate(params.team_id, memberId, taskId, 'deleted', {
|
|
893
|
+
title: existingTask.title
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
success: true,
|
|
898
|
+
message: `Task ${taskId} deleted`,
|
|
899
|
+
result: { task_id: taskId },
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
private async listTeamTasks(
|
|
904
|
+
params: TeamToolParams
|
|
905
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
906
|
+
if (!params.team_id) {
|
|
907
|
+
throw new Error('team_id is required');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const team = await this.store.getTeam(params.team_id);
|
|
911
|
+
if (!team) {
|
|
912
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const filter = params.task_filter || 'all';
|
|
916
|
+
let tasks: TeamTask[];
|
|
917
|
+
|
|
918
|
+
switch (filter) {
|
|
919
|
+
case 'pending':
|
|
920
|
+
tasks = (await this.store.getTasks(params.team_id)).filter(
|
|
921
|
+
(t) => t.status === 'pending'
|
|
922
|
+
);
|
|
923
|
+
break;
|
|
924
|
+
case 'available':
|
|
925
|
+
tasks = await this.store.getAvailableTasks(params.team_id);
|
|
926
|
+
break;
|
|
927
|
+
case 'in_progress':
|
|
928
|
+
tasks = (await this.store.getTasks(params.team_id)).filter(
|
|
929
|
+
(t) => t.status === 'in_progress'
|
|
930
|
+
);
|
|
931
|
+
break;
|
|
932
|
+
case 'completed':
|
|
933
|
+
tasks = (await this.store.getTasks(params.team_id)).filter(
|
|
934
|
+
(t) => t.status === 'completed'
|
|
935
|
+
);
|
|
936
|
+
break;
|
|
937
|
+
default:
|
|
938
|
+
tasks = await this.store.getTasks(params.team_id);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return {
|
|
942
|
+
success: true,
|
|
943
|
+
message: `Found ${tasks.length} tasks (filter: ${filter})`,
|
|
944
|
+
result: {
|
|
945
|
+
team_id: params.team_id,
|
|
946
|
+
filter,
|
|
947
|
+
total_count: tasks.length,
|
|
948
|
+
tasks: tasks.map((t) => ({
|
|
949
|
+
task_id: t.taskId,
|
|
950
|
+
title: t.title,
|
|
951
|
+
description: t.description,
|
|
952
|
+
status: t.status,
|
|
953
|
+
priority: t.priority,
|
|
954
|
+
assignee: t.assignee,
|
|
955
|
+
dependencies: t.dependencies,
|
|
956
|
+
created_at: t.createdAt,
|
|
957
|
+
updated_at: t.updatedAt,
|
|
958
|
+
result: t.result,
|
|
959
|
+
})),
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
private async shutdownTeammate(
|
|
965
|
+
params: TeamToolParams,
|
|
966
|
+
memberId: string
|
|
967
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
968
|
+
if (!params.team_id) {
|
|
969
|
+
throw new Error('team_id is required');
|
|
970
|
+
}
|
|
971
|
+
if (!memberId) {
|
|
972
|
+
throw new Error('member_id is required');
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const result = await this.spawner.shutdownTeammate(params.team_id, memberId);
|
|
976
|
+
|
|
977
|
+
if (result.success) {
|
|
978
|
+
console.log(colors.textMuted(`${icons.check} Teammate ${memberId.slice(0, 8)} shut down${result.reason ? `: ${result.reason}` : ''}`));
|
|
979
|
+
} else {
|
|
980
|
+
console.log(colors.error(`${icons.cross} Failed to shutdown ${memberId.slice(0, 8)}: ${result.reason}`));
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return {
|
|
984
|
+
success: result.success,
|
|
985
|
+
message: result.success
|
|
986
|
+
? `Teammate ${memberId} shut down${result.reason ? `: ${result.reason}` : ''}`
|
|
987
|
+
: `Failed to shutdown ${memberId}: ${result.reason}`,
|
|
988
|
+
result: { member_id: memberId, reason: result.reason },
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
private async cleanupTeam(
|
|
993
|
+
params: TeamToolParams
|
|
994
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
995
|
+
if (!params.team_id) {
|
|
996
|
+
throw new Error('team_id is required');
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const team = await this.store.getTeam(params.team_id);
|
|
1000
|
+
if (!team) {
|
|
1001
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Auto-shutdown all active teammates (role !== 'lead')
|
|
1005
|
+
const activeTeammates = team.members.filter(
|
|
1006
|
+
(m) => m.status === 'active' && m.role !== 'lead'
|
|
1007
|
+
);
|
|
1008
|
+
const shutdownResults: { memberId: string; success: boolean; reason?: string }[] = [];
|
|
1009
|
+
|
|
1010
|
+
for (const teammate of activeTeammates) {
|
|
1011
|
+
const result = await this.spawner.shutdownTeammate(params.team_id, teammate.memberId);
|
|
1012
|
+
shutdownResults.push({
|
|
1013
|
+
memberId: teammate.memberId,
|
|
1014
|
+
success: result.success,
|
|
1015
|
+
reason: result.reason,
|
|
1016
|
+
});
|
|
1017
|
+
const memberName = teammate.name || teammate.memberId.slice(0, 8);
|
|
1018
|
+
if (result.success) {
|
|
1019
|
+
console.log(colors.textMuted(` ${icons.check} ${memberName}`));
|
|
1020
|
+
} else {
|
|
1021
|
+
console.log(colors.error(` ${icons.cross} ${memberName}: ${result.reason}`));
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Stop the message broker
|
|
1026
|
+
const broker = this.brokers.get(params.team_id);
|
|
1027
|
+
if (broker) {
|
|
1028
|
+
await broker.stop();
|
|
1029
|
+
this.brokers.delete(params.team_id);
|
|
1030
|
+
removeMessageBroker(params.team_id);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
await this.store.deleteTeam(params.team_id);
|
|
1034
|
+
|
|
1035
|
+
console.log(colors.success(`\n${icons.check} Team cleaned up (${shutdownResults.filter(r => r.success).length}/${activeTeammates.length} teammates shutdown)`));
|
|
1036
|
+
|
|
1037
|
+
return {
|
|
1038
|
+
success: true,
|
|
1039
|
+
message: `Team ${params.team_id} cleaned up (${shutdownResults.filter(r => r.success).length}/${activeTeammates.length} teammates auto-shutdown)`,
|
|
1040
|
+
result: {
|
|
1041
|
+
team_id: params.team_id,
|
|
1042
|
+
auto_shutdown: shutdownResults,
|
|
1043
|
+
},
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
private async listTeams(): Promise<{ success: boolean; message: string; result?: any }> {
|
|
1048
|
+
const teams = await this.store.listTeams();
|
|
1049
|
+
|
|
1050
|
+
return {
|
|
1051
|
+
success: true,
|
|
1052
|
+
message: `Found ${teams.length} team(s)`,
|
|
1053
|
+
result: {
|
|
1054
|
+
total_count: teams.length,
|
|
1055
|
+
teams: teams.map((t) => ({
|
|
1056
|
+
team_id: t.teamId,
|
|
1057
|
+
team_name: t.teamName,
|
|
1058
|
+
member_count: t.members.length,
|
|
1059
|
+
status: t.status,
|
|
1060
|
+
created_at: t.createdAt,
|
|
1061
|
+
work_dir: t.workDir,
|
|
1062
|
+
})),
|
|
1063
|
+
},
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private async getTeamStatus(
|
|
1068
|
+
params: TeamToolParams
|
|
1069
|
+
): Promise<{ success: boolean; message: string; result?: any }> {
|
|
1070
|
+
if (!params.team_id) {
|
|
1071
|
+
throw new Error('team_id is required');
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const status = await this.store.getTeamStatus(params.team_id);
|
|
1075
|
+
|
|
1076
|
+
if (!status.team) {
|
|
1077
|
+
throw new Error(`Team ${params.team_id} not found`);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const envMemberId = process.env.XAGENT_MEMBER_ID;
|
|
1081
|
+
const isTeamLead = !envMemberId;
|
|
1082
|
+
const yourRole = isTeamLead ? 'lead' : 'teammate';
|
|
1083
|
+
const yourMemberId = envMemberId || status.team.leadMemberId;
|
|
1084
|
+
|
|
1085
|
+
return {
|
|
1086
|
+
success: true,
|
|
1087
|
+
message: `Team status retrieved`,
|
|
1088
|
+
result: {
|
|
1089
|
+
team_id: params.team_id,
|
|
1090
|
+
team_name: status.team.teamName,
|
|
1091
|
+
status: status.team.status,
|
|
1092
|
+
your_role: yourRole,
|
|
1093
|
+
your_member_id: yourMemberId,
|
|
1094
|
+
member_count: status.memberCount,
|
|
1095
|
+
members: status.team.members.map((m) => ({
|
|
1096
|
+
id: m.memberId,
|
|
1097
|
+
name: m.name,
|
|
1098
|
+
role: m.memberRole || m.role,
|
|
1099
|
+
status: m.status,
|
|
1100
|
+
display_mode: m.displayMode,
|
|
1101
|
+
})),
|
|
1102
|
+
active_task_count: status.activeTaskCount,
|
|
1103
|
+
completed_task_count: status.completedTaskCount,
|
|
1104
|
+
created_at: status.team.createdAt,
|
|
1105
|
+
},
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
let teamCoordinatorInstance: TeamCoordinator | null = null;
|
|
1111
|
+
|
|
1112
|
+
export function getTeamCoordinator(): TeamCoordinator {
|
|
1113
|
+
if (!teamCoordinatorInstance) {
|
|
1114
|
+
teamCoordinatorInstance = new TeamCoordinator();
|
|
1115
|
+
}
|
|
1116
|
+
return teamCoordinatorInstance;
|
|
1117
|
+
}
|