@compilr-dev/sdk 0.1.27 → 0.2.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/dist/index.d.ts +6 -2
- package/dist/index.js +27 -1
- package/dist/meta-tools/registry.js +4 -2
- package/dist/team/activity.d.ts +21 -0
- package/dist/team/activity.js +34 -0
- package/dist/team/agent-selection.d.ts +53 -0
- package/dist/team/agent-selection.js +88 -0
- package/dist/team/artifacts.d.ts +175 -0
- package/dist/team/artifacts.js +279 -0
- package/dist/team/collision-utils.d.ts +16 -0
- package/dist/team/collision-utils.js +28 -0
- package/dist/team/context-resolver.d.ts +97 -0
- package/dist/team/context-resolver.js +322 -0
- package/dist/team/custom-agents.d.ts +68 -0
- package/dist/team/custom-agents.js +150 -0
- package/dist/team/delegation-tracker.d.ts +147 -0
- package/dist/team/delegation-tracker.js +215 -0
- package/dist/team/index.d.ts +34 -0
- package/dist/team/index.js +30 -0
- package/dist/team/interfaces.d.ts +36 -0
- package/dist/team/interfaces.js +7 -0
- package/dist/team/mention-parser.d.ts +64 -0
- package/dist/team/mention-parser.js +138 -0
- package/dist/team/shared-context.d.ts +293 -0
- package/dist/team/shared-context.js +673 -0
- package/dist/team/skill-requirements.d.ts +66 -0
- package/dist/team/skill-requirements.js +178 -0
- package/dist/team/task-assignment.d.ts +69 -0
- package/dist/team/task-assignment.js +123 -0
- package/dist/team/task-suggestion.d.ts +31 -0
- package/dist/team/task-suggestion.js +84 -0
- package/dist/team/team-agent.d.ts +201 -0
- package/dist/team/team-agent.js +492 -0
- package/dist/team/team.d.ts +297 -0
- package/dist/team/team.js +615 -0
- package/dist/team/tool-config.d.ts +110 -0
- package/dist/team/tool-config.js +739 -0
- package/dist/team/types.d.ts +211 -0
- package/dist/team/types.js +638 -0
- package/package.json +1 -1
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentTeam - Multi-agent orchestration
|
|
3
|
+
*
|
|
4
|
+
* Manages a team of persistent agents:
|
|
5
|
+
* - Agent switching ($name prefix)
|
|
6
|
+
* - Isolated contexts per agent
|
|
7
|
+
* - Team-level persistence
|
|
8
|
+
* - Event notifications
|
|
9
|
+
*/
|
|
10
|
+
import { TeamAgent } from './team-agent.js';
|
|
11
|
+
import { ROLE_EXPERTISE } from './types.js';
|
|
12
|
+
import { SharedContextManager } from './shared-context.js';
|
|
13
|
+
import { ArtifactStore } from './artifacts.js';
|
|
14
|
+
import { resolveAgentIdCollision } from './collision-utils.js';
|
|
15
|
+
/**
|
|
16
|
+
* AgentTeam orchestrates multiple persistent agents
|
|
17
|
+
*/
|
|
18
|
+
export class AgentTeam {
|
|
19
|
+
/**
|
|
20
|
+
* Team name
|
|
21
|
+
*/
|
|
22
|
+
name;
|
|
23
|
+
/**
|
|
24
|
+
* Map of agent ID to TeamAgent
|
|
25
|
+
*/
|
|
26
|
+
agents = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Currently active agent ID
|
|
29
|
+
*/
|
|
30
|
+
_activeAgentId = 'default';
|
|
31
|
+
/**
|
|
32
|
+
* Coordinator agent ID (optional)
|
|
33
|
+
*/
|
|
34
|
+
_coordinatorId;
|
|
35
|
+
/**
|
|
36
|
+
* Factory for creating agents
|
|
37
|
+
*/
|
|
38
|
+
agentFactory;
|
|
39
|
+
/**
|
|
40
|
+
* Event handler
|
|
41
|
+
*/
|
|
42
|
+
onEvent;
|
|
43
|
+
/**
|
|
44
|
+
* Shared context manager for cross-agent knowledge sharing
|
|
45
|
+
*/
|
|
46
|
+
_sharedContext;
|
|
47
|
+
/**
|
|
48
|
+
* Artifact store for team artifacts
|
|
49
|
+
*/
|
|
50
|
+
_artifactStore;
|
|
51
|
+
/**
|
|
52
|
+
* Optional session registry for collision detection
|
|
53
|
+
*/
|
|
54
|
+
_sessionRegistry;
|
|
55
|
+
/**
|
|
56
|
+
* Track handoff source: targetAgentId → sourceAgentId
|
|
57
|
+
* Used for one-hop prevention (agents that were handed a task
|
|
58
|
+
* cannot re-hand it off, except back to the coordinator)
|
|
59
|
+
*/
|
|
60
|
+
_handedFrom = new Map();
|
|
61
|
+
/**
|
|
62
|
+
* Creation timestamp
|
|
63
|
+
*/
|
|
64
|
+
createdAt = new Date();
|
|
65
|
+
/**
|
|
66
|
+
* Last updated timestamp
|
|
67
|
+
*/
|
|
68
|
+
updatedAt = new Date();
|
|
69
|
+
constructor(config) {
|
|
70
|
+
this.name = config.name ?? 'Team';
|
|
71
|
+
this.agentFactory = config.agentFactory;
|
|
72
|
+
this.onEvent = config.onEvent;
|
|
73
|
+
this._sessionRegistry = config.sessionRegistry;
|
|
74
|
+
// Initialize shared context (use provided or create new)
|
|
75
|
+
this._sharedContext = config.sharedContext ?? new SharedContextManager();
|
|
76
|
+
// Initialize artifact store (use provided or create in-memory)
|
|
77
|
+
this._artifactStore = config.artifactStore ?? new ArtifactStore();
|
|
78
|
+
// Create default agent
|
|
79
|
+
const defaultAgent = TeamAgent.fromRole('default', 'default');
|
|
80
|
+
this.agents.set('default', defaultAgent);
|
|
81
|
+
// Add any initial agents
|
|
82
|
+
if (config.initialAgents) {
|
|
83
|
+
for (const agentConfig of config.initialAgents) {
|
|
84
|
+
if (agentConfig.id !== 'default') {
|
|
85
|
+
this.agents.set(agentConfig.id, new TeamAgent(agentConfig));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Update shared context with team roster (simple list)
|
|
90
|
+
this._sharedContext.setTeam(Array.from(this.agents.keys()), this._activeAgentId);
|
|
91
|
+
// Build full team roster for team awareness
|
|
92
|
+
this.updateTeamRoster();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the currently active agent ID
|
|
96
|
+
*/
|
|
97
|
+
get activeAgentId() {
|
|
98
|
+
return this._activeAgentId;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the coordinator agent ID (if set)
|
|
102
|
+
*/
|
|
103
|
+
get coordinatorId() {
|
|
104
|
+
return this._coordinatorId;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the shared context manager
|
|
108
|
+
*/
|
|
109
|
+
get sharedContext() {
|
|
110
|
+
return this._sharedContext;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the artifact store
|
|
114
|
+
*/
|
|
115
|
+
get artifactStore() {
|
|
116
|
+
return this._artifactStore;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the currently active TeamAgent
|
|
120
|
+
*/
|
|
121
|
+
getActive() {
|
|
122
|
+
const agent = this.agents.get(this._activeAgentId);
|
|
123
|
+
if (!agent) {
|
|
124
|
+
// Fallback to default
|
|
125
|
+
const defaultAgent = this.agents.get('default');
|
|
126
|
+
if (!defaultAgent) {
|
|
127
|
+
throw new Error('Default agent not found - this should never happen');
|
|
128
|
+
}
|
|
129
|
+
return defaultAgent;
|
|
130
|
+
}
|
|
131
|
+
return agent;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get the currently active Agent instance
|
|
135
|
+
* Returns null if not initialized
|
|
136
|
+
*/
|
|
137
|
+
getActiveAgent() {
|
|
138
|
+
return this.getActive().agent;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get a specific TeamAgent by ID
|
|
142
|
+
*/
|
|
143
|
+
get(id) {
|
|
144
|
+
return this.agents.get(id);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Set the agent instance for the default TeamAgent
|
|
148
|
+
* Used when the agent is created before the team
|
|
149
|
+
*/
|
|
150
|
+
setDefaultAgent(agent) {
|
|
151
|
+
const defaultTeamAgent = this.agents.get('default');
|
|
152
|
+
if (defaultTeamAgent) {
|
|
153
|
+
defaultTeamAgent.setAgent(agent);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if an agent exists in the team
|
|
158
|
+
*/
|
|
159
|
+
has(id) {
|
|
160
|
+
return this.agents.has(id);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get all agent IDs
|
|
164
|
+
*/
|
|
165
|
+
getAgentIds() {
|
|
166
|
+
return Array.from(this.agents.keys());
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get all TeamAgents
|
|
170
|
+
*/
|
|
171
|
+
getAll() {
|
|
172
|
+
return Array.from(this.agents.values());
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get team size (number of agents)
|
|
176
|
+
*/
|
|
177
|
+
get size() {
|
|
178
|
+
return this.agents.size;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Add a new agent to the team
|
|
182
|
+
*/
|
|
183
|
+
async addAgent(config) {
|
|
184
|
+
if (this.agents.has(config.id)) {
|
|
185
|
+
throw new Error(`Agent '${config.id}' already exists in team`);
|
|
186
|
+
}
|
|
187
|
+
const teamAgent = new TeamAgent(config);
|
|
188
|
+
this.agents.set(config.id, teamAgent);
|
|
189
|
+
// Initialize the agent with shared context
|
|
190
|
+
await teamAgent.initialize(this.agentFactory, this._sharedContext);
|
|
191
|
+
// Update shared context roster
|
|
192
|
+
this._sharedContext.addAgent(config.id);
|
|
193
|
+
// Update full team roster for team awareness
|
|
194
|
+
this.updateTeamRoster();
|
|
195
|
+
this.updatedAt = new Date();
|
|
196
|
+
this.emit({ type: 'agent:added', agentId: config.id });
|
|
197
|
+
return teamAgent;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Add an agent from a predefined role
|
|
201
|
+
* @param role - The predefined role
|
|
202
|
+
* @param id - Optional custom ID (defaults to role name)
|
|
203
|
+
* @param modelTier - Optional model tier override (defaults to role's default tier)
|
|
204
|
+
*/
|
|
205
|
+
async addAgentFromRole(role, id, modelTier) {
|
|
206
|
+
let agentId = id ?? role;
|
|
207
|
+
// Cross-terminal collision check (if registry provided)
|
|
208
|
+
if (this._sessionRegistry) {
|
|
209
|
+
const globalIds = this._sessionRegistry.getGlobalAgentIds();
|
|
210
|
+
const localIds = Array.from(this.agents.keys());
|
|
211
|
+
const resolved = resolveAgentIdCollision(agentId, localIds, globalIds);
|
|
212
|
+
if (resolved.wasCollision) {
|
|
213
|
+
const originalId = agentId;
|
|
214
|
+
agentId = resolved.id;
|
|
215
|
+
this.emit({ type: 'agent:collision', originalId, agentId: resolved.id });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (this.agents.has(agentId)) {
|
|
219
|
+
throw new Error(`Agent '${agentId}' already exists in team`);
|
|
220
|
+
}
|
|
221
|
+
const teamAgent = TeamAgent.fromRole(role, agentId, modelTier);
|
|
222
|
+
this.agents.set(agentId, teamAgent);
|
|
223
|
+
// Initialize the agent with shared context
|
|
224
|
+
await teamAgent.initialize(this.agentFactory, this._sharedContext);
|
|
225
|
+
// Update shared context roster
|
|
226
|
+
this._sharedContext.addAgent(agentId);
|
|
227
|
+
// Update full team roster for team awareness
|
|
228
|
+
this.updateTeamRoster();
|
|
229
|
+
this.updatedAt = new Date();
|
|
230
|
+
this.emit({ type: 'agent:added', agentId });
|
|
231
|
+
return teamAgent;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Add a custom agent from a CustomAgentDefinition
|
|
235
|
+
*/
|
|
236
|
+
async addCustomAgent(def) {
|
|
237
|
+
let agentId = def.id;
|
|
238
|
+
// Cross-terminal collision check (if registry provided)
|
|
239
|
+
if (this._sessionRegistry) {
|
|
240
|
+
const globalIds = this._sessionRegistry.getGlobalAgentIds();
|
|
241
|
+
const localIds = Array.from(this.agents.keys());
|
|
242
|
+
const resolved = resolveAgentIdCollision(agentId, localIds, globalIds);
|
|
243
|
+
if (resolved.wasCollision) {
|
|
244
|
+
const originalId = agentId;
|
|
245
|
+
agentId = resolved.id;
|
|
246
|
+
def = { ...def, id: agentId };
|
|
247
|
+
this.emit({ type: 'agent:collision', originalId, agentId: resolved.id });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (this.agents.has(agentId)) {
|
|
251
|
+
throw new Error(`Agent '${agentId}' already exists in team`);
|
|
252
|
+
}
|
|
253
|
+
const teamAgent = TeamAgent.fromCustomDefinition(def);
|
|
254
|
+
this.agents.set(agentId, teamAgent);
|
|
255
|
+
// Initialize the agent with shared context
|
|
256
|
+
await teamAgent.initialize(this.agentFactory, this._sharedContext);
|
|
257
|
+
// Update shared context roster
|
|
258
|
+
this._sharedContext.addAgent(def.id);
|
|
259
|
+
// Update full team roster for team awareness
|
|
260
|
+
this.updateTeamRoster();
|
|
261
|
+
this.updatedAt = new Date();
|
|
262
|
+
this.emit({ type: 'agent:added', agentId: def.id });
|
|
263
|
+
return teamAgent;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Remove an agent from the team
|
|
267
|
+
*/
|
|
268
|
+
removeAgent(id) {
|
|
269
|
+
if (id === 'default') {
|
|
270
|
+
throw new Error('Cannot remove the default agent');
|
|
271
|
+
}
|
|
272
|
+
if (!this.agents.has(id)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
// If removing active agent, switch to default
|
|
276
|
+
if (this._activeAgentId === id) {
|
|
277
|
+
this._activeAgentId = 'default';
|
|
278
|
+
this._sharedContext.setActiveAgent('default');
|
|
279
|
+
}
|
|
280
|
+
// If removing coordinator, clear coordinator
|
|
281
|
+
if (this._coordinatorId === id) {
|
|
282
|
+
this._coordinatorId = undefined;
|
|
283
|
+
}
|
|
284
|
+
this.agents.delete(id);
|
|
285
|
+
// Update shared context roster
|
|
286
|
+
this._sharedContext.removeAgent(id);
|
|
287
|
+
// Update full team roster for team awareness
|
|
288
|
+
this.updateTeamRoster();
|
|
289
|
+
this.updatedAt = new Date();
|
|
290
|
+
this.emit({ type: 'agent:removed', agentId: id });
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Clear conversation history for all agents in the team
|
|
295
|
+
* Used by /reset command to start fresh
|
|
296
|
+
*/
|
|
297
|
+
clearAllHistories() {
|
|
298
|
+
for (const agent of this.agents.values()) {
|
|
299
|
+
agent.clearHistory();
|
|
300
|
+
}
|
|
301
|
+
// Clear shared context activity feed
|
|
302
|
+
this._sharedContext.clearActivity();
|
|
303
|
+
this.updatedAt = new Date();
|
|
304
|
+
this.emit({ type: 'team:reset' });
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Callback for checking in-progress tasks when switching agents
|
|
308
|
+
* Set by the consumer to provide task counts for warnings
|
|
309
|
+
*/
|
|
310
|
+
_getInProgressTasksCallback;
|
|
311
|
+
/**
|
|
312
|
+
* Set the callback for getting in-progress tasks
|
|
313
|
+
* Used by consumer to integrate with todo list and work items
|
|
314
|
+
*/
|
|
315
|
+
setInProgressTasksCallback(callback) {
|
|
316
|
+
this._getInProgressTasksCallback = callback;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Switch to a different agent
|
|
320
|
+
*/
|
|
321
|
+
async switchTo(id) {
|
|
322
|
+
const agent = this.agents.get(id);
|
|
323
|
+
if (!agent) {
|
|
324
|
+
throw new Error(`Agent '${id}' not found in team`);
|
|
325
|
+
}
|
|
326
|
+
// Initialize if needed
|
|
327
|
+
if (!agent.isInitialized) {
|
|
328
|
+
await agent.initialize(this.agentFactory, this._sharedContext);
|
|
329
|
+
}
|
|
330
|
+
const previousId = this._activeAgentId;
|
|
331
|
+
// Check for in-progress tasks owned by the outgoing agent
|
|
332
|
+
if (previousId !== id && this._getInProgressTasksCallback) {
|
|
333
|
+
const inProgressTasks = this._getInProgressTasksCallback(previousId);
|
|
334
|
+
const hasTodos = inProgressTasks.todos.length > 0;
|
|
335
|
+
const hasWorkItems = inProgressTasks.workItems.length > 0;
|
|
336
|
+
if (hasTodos || hasWorkItems) {
|
|
337
|
+
this.emit({
|
|
338
|
+
type: 'agent:switch_warning',
|
|
339
|
+
agentId: id,
|
|
340
|
+
previousAgentId: previousId,
|
|
341
|
+
message: `Agent "${previousId}" has in-progress tasks: ${hasTodos ? `${String(inProgressTasks.todos.length)} todo(s)` : ''}${hasTodos && hasWorkItems ? ', ' : ''}${hasWorkItems ? `${String(inProgressTasks.workItems.length)} work item(s)` : ''}`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
this._activeAgentId = id;
|
|
346
|
+
this.updatedAt = new Date();
|
|
347
|
+
// Update shared context active agent
|
|
348
|
+
this._sharedContext.setActiveAgent(id);
|
|
349
|
+
// Update roster active agent for team awareness
|
|
350
|
+
this._sharedContext.setRosterActiveAgent(id);
|
|
351
|
+
this.emit({
|
|
352
|
+
type: 'agent:switched',
|
|
353
|
+
agentId: id,
|
|
354
|
+
previousAgentId: previousId,
|
|
355
|
+
});
|
|
356
|
+
return agent;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set the coordinator agent
|
|
360
|
+
*/
|
|
361
|
+
setCoordinator(id) {
|
|
362
|
+
if (id !== undefined && !this.agents.has(id)) {
|
|
363
|
+
throw new Error(`Agent '${id}' not found in team`);
|
|
364
|
+
}
|
|
365
|
+
this._coordinatorId = id;
|
|
366
|
+
this.updatedAt = new Date();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check if an agent is the coordinator
|
|
370
|
+
* The default agent is considered coordinator if no explicit coordinator is set.
|
|
371
|
+
*/
|
|
372
|
+
isCoordinator(id) {
|
|
373
|
+
if (this._coordinatorId !== undefined) {
|
|
374
|
+
return this._coordinatorId === id;
|
|
375
|
+
}
|
|
376
|
+
// If no coordinator explicitly set, default agent is the coordinator
|
|
377
|
+
return id === 'default';
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check if the active agent is the coordinator
|
|
381
|
+
* The default agent is considered coordinator if no explicit coordinator is set.
|
|
382
|
+
*/
|
|
383
|
+
isActiveCoordinator() {
|
|
384
|
+
if (this._coordinatorId !== undefined) {
|
|
385
|
+
return this._coordinatorId === this._activeAgentId;
|
|
386
|
+
}
|
|
387
|
+
// If no coordinator explicitly set, default agent is the coordinator
|
|
388
|
+
return this._activeAgentId === 'default';
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Change an agent's model tier
|
|
392
|
+
* This updates the tier and reinitializes the agent with the new model.
|
|
393
|
+
* Conversation history is preserved.
|
|
394
|
+
*
|
|
395
|
+
* @param id - The agent ID
|
|
396
|
+
* @param newTier - The new model tier
|
|
397
|
+
* @returns The updated TeamAgent
|
|
398
|
+
*/
|
|
399
|
+
async changeAgentTier(id, newTier) {
|
|
400
|
+
const agent = this.agents.get(id);
|
|
401
|
+
if (!agent) {
|
|
402
|
+
throw new Error(`Agent '${id}' not found in team`);
|
|
403
|
+
}
|
|
404
|
+
// Set the new tier (this preserves state and clears the agent instance)
|
|
405
|
+
agent.setModelTier(newTier);
|
|
406
|
+
// Reinitialize with the new tier
|
|
407
|
+
await agent.initialize(this.agentFactory, this._sharedContext);
|
|
408
|
+
this.updatedAt = new Date();
|
|
409
|
+
this.emit({ type: 'agent:switched', agentId: id, previousAgentId: id });
|
|
410
|
+
return agent;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Initialize all agents (lazy initialization)
|
|
414
|
+
*/
|
|
415
|
+
async initializeAll() {
|
|
416
|
+
for (const agent of this.agents.values()) {
|
|
417
|
+
if (!agent.isInitialized) {
|
|
418
|
+
await agent.initialize(this.agentFactory, this._sharedContext);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Initialize only the active agent
|
|
424
|
+
*/
|
|
425
|
+
async initializeActive() {
|
|
426
|
+
const teamAgent = this.getActive();
|
|
427
|
+
if (!teamAgent.isInitialized) {
|
|
428
|
+
await teamAgent.initialize(this.agentFactory, this._sharedContext);
|
|
429
|
+
}
|
|
430
|
+
const agent = teamAgent.agent;
|
|
431
|
+
if (!agent) {
|
|
432
|
+
throw new Error('Agent not initialized after initialize() call');
|
|
433
|
+
}
|
|
434
|
+
return agent;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Parse a message for $agent prefix and switch if needed
|
|
438
|
+
* Returns the message without the prefix and whether a switch occurred
|
|
439
|
+
*/
|
|
440
|
+
async parseAndSwitch(message) {
|
|
441
|
+
const match = message.match(/^\$(\w+)\s*(.*)/s);
|
|
442
|
+
if (!match) {
|
|
443
|
+
return { message, switched: false, agentId: this._activeAgentId };
|
|
444
|
+
}
|
|
445
|
+
const [, agentId, remainingMessage] = match;
|
|
446
|
+
// Check if agent exists
|
|
447
|
+
if (!this.agents.has(agentId)) {
|
|
448
|
+
// Return original message - let the agent handle invalid prefix
|
|
449
|
+
return { message, switched: false, agentId: this._activeAgentId };
|
|
450
|
+
}
|
|
451
|
+
// Switch to the agent
|
|
452
|
+
await this.switchTo(agentId);
|
|
453
|
+
return {
|
|
454
|
+
message: remainingMessage.trim() || message, // Keep original if no remaining message
|
|
455
|
+
switched: true,
|
|
456
|
+
agentId,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get autocomplete suggestions for $agent prefix
|
|
461
|
+
*/
|
|
462
|
+
getAgentSuggestions(prefix) {
|
|
463
|
+
const results = [];
|
|
464
|
+
const searchPrefix = prefix.toLowerCase();
|
|
465
|
+
for (const [id, agent] of this.agents) {
|
|
466
|
+
if (id.toLowerCase().startsWith(searchPrefix)) {
|
|
467
|
+
results.push({
|
|
468
|
+
id,
|
|
469
|
+
label: agent.getFullLabel(),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return results;
|
|
474
|
+
}
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// Handoff Tracking (One-Hop Prevention)
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
/**
|
|
479
|
+
* Record that sourceAgent handed off to targetAgent.
|
|
480
|
+
* Used for one-hop prevention.
|
|
481
|
+
*/
|
|
482
|
+
recordHandoff(targetAgentId, sourceAgentId) {
|
|
483
|
+
this._handedFrom.set(targetAgentId, sourceAgentId);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Check if an agent was handed a task (i.e., is a handoff target).
|
|
487
|
+
* If true, this agent cannot re-hand off (except back to coordinator).
|
|
488
|
+
*/
|
|
489
|
+
wasHandedTo(agentId) {
|
|
490
|
+
return this._handedFrom.has(agentId);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Get the source agent that handed off to the given agent.
|
|
494
|
+
*/
|
|
495
|
+
getHandoffSource(agentId) {
|
|
496
|
+
return this._handedFrom.get(agentId);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Clear handoff tracking for an agent (after it completes its task).
|
|
500
|
+
*/
|
|
501
|
+
clearHandoffRecord(agentId) {
|
|
502
|
+
this._handedFrom.delete(agentId);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Serialize the team for persistence
|
|
506
|
+
*/
|
|
507
|
+
serialize() {
|
|
508
|
+
const agents = [];
|
|
509
|
+
for (const agent of this.agents.values()) {
|
|
510
|
+
agents.push(agent.serialize());
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
metadata: {
|
|
514
|
+
name: this.name,
|
|
515
|
+
activeAgentId: this._activeAgentId,
|
|
516
|
+
coordinatorId: this._coordinatorId,
|
|
517
|
+
agentIds: Array.from(this.agents.keys()),
|
|
518
|
+
createdAt: this.createdAt.toISOString(),
|
|
519
|
+
updatedAt: this.updatedAt.toISOString(),
|
|
520
|
+
},
|
|
521
|
+
agents,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Restore team state from serialized data
|
|
526
|
+
*/
|
|
527
|
+
static restore(data, agentFactory, onEvent, options) {
|
|
528
|
+
const team = new AgentTeam({
|
|
529
|
+
name: data.metadata.name,
|
|
530
|
+
agentFactory,
|
|
531
|
+
onEvent,
|
|
532
|
+
sharedContext: options?.sharedContext,
|
|
533
|
+
artifactStore: options?.artifactStore,
|
|
534
|
+
sessionRegistry: options?.sessionRegistry,
|
|
535
|
+
});
|
|
536
|
+
// Clear default agent if not in serialized data
|
|
537
|
+
if (!data.metadata.agentIds.includes('default')) {
|
|
538
|
+
team.agents.delete('default');
|
|
539
|
+
}
|
|
540
|
+
// Restore agents
|
|
541
|
+
for (const agentData of data.agents) {
|
|
542
|
+
const teamAgent = TeamAgent.fromSerialized(agentData);
|
|
543
|
+
team.agents.set(agentData.id, teamAgent);
|
|
544
|
+
}
|
|
545
|
+
// Restore metadata
|
|
546
|
+
team._activeAgentId = data.metadata.activeAgentId;
|
|
547
|
+
team._coordinatorId = data.metadata.coordinatorId;
|
|
548
|
+
// Update shared context with restored team roster (simple list)
|
|
549
|
+
team._sharedContext.setTeam(Array.from(team.agents.keys()), team._activeAgentId);
|
|
550
|
+
// Build full team roster for team awareness
|
|
551
|
+
team.updateTeamRoster();
|
|
552
|
+
team.emit({ type: 'team:restored' });
|
|
553
|
+
return team;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Emit a team event
|
|
557
|
+
*/
|
|
558
|
+
emit(event) {
|
|
559
|
+
this.onEvent?.(event);
|
|
560
|
+
}
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
// Team Awareness - Roster Management
|
|
563
|
+
// ---------------------------------------------------------------------------
|
|
564
|
+
/**
|
|
565
|
+
* Build and update the team roster in shared context
|
|
566
|
+
* Called when team composition or active agent changes
|
|
567
|
+
*/
|
|
568
|
+
updateTeamRoster() {
|
|
569
|
+
const roster = this.buildTeamRoster();
|
|
570
|
+
this._sharedContext.updateTeamRoster(roster);
|
|
571
|
+
// Push updated roster to all initialized agents via anchors.
|
|
572
|
+
// Anchors are re-injected on every LLM call, so agents always see the latest roster.
|
|
573
|
+
if (this._sharedContext.hasTeamRoster()) {
|
|
574
|
+
const rosterContent = this._sharedContext.formatTeamRoster();
|
|
575
|
+
for (const [, teamAgent] of this.agents) {
|
|
576
|
+
const agent = teamAgent.agent;
|
|
577
|
+
if (agent && agent.hasAnchors()) {
|
|
578
|
+
agent.addAnchor({
|
|
579
|
+
id: 'team-roster',
|
|
580
|
+
content: rosterContent,
|
|
581
|
+
priority: 'info',
|
|
582
|
+
scope: 'session',
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Build the team roster from current agents
|
|
590
|
+
*/
|
|
591
|
+
buildTeamRoster() {
|
|
592
|
+
const roster = [];
|
|
593
|
+
for (const [id, agent] of this.agents) {
|
|
594
|
+
const role = agent.role;
|
|
595
|
+
// For custom agents, derive expertise from their specialty/description
|
|
596
|
+
// since ROLE_EXPERTISE['custom'] is empty
|
|
597
|
+
let expertise = ROLE_EXPERTISE[role];
|
|
598
|
+
if (role === 'custom' && expertise.length === 0 && agent.description) {
|
|
599
|
+
expertise = agent.description
|
|
600
|
+
.split(',')
|
|
601
|
+
.map((s) => s.trim())
|
|
602
|
+
.filter(Boolean);
|
|
603
|
+
}
|
|
604
|
+
roster.push({
|
|
605
|
+
id,
|
|
606
|
+
displayName: agent.displayName,
|
|
607
|
+
mascot: agent.mascot,
|
|
608
|
+
role,
|
|
609
|
+
expertise,
|
|
610
|
+
isActive: id === this._activeAgentId,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return roster;
|
|
614
|
+
}
|
|
615
|
+
}
|