@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,673 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SharedContextManager - Shared context across team agents
|
|
3
|
+
*
|
|
4
|
+
* Provides a lightweight, token-budgeted context layer that's injected
|
|
5
|
+
* into all agent system prompts. Enables cross-agent knowledge sharing.
|
|
6
|
+
*
|
|
7
|
+
* Token Budget: ~4000 tokens (2% of 200K context)
|
|
8
|
+
*/
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// =============================================================================
|
|
12
|
+
const DEFAULT_TOKEN_BUDGET = 4000;
|
|
13
|
+
const MAX_DECISIONS = 10;
|
|
14
|
+
const MAX_DECISION_LENGTH = 100;
|
|
15
|
+
const MAX_MEMORY_SUMMARY_LENGTH = 2000;
|
|
16
|
+
const MAX_ACTIVITY_ITEMS = 5;
|
|
17
|
+
/** Simple token estimation (chars/4) — consumers can provide better estimators */
|
|
18
|
+
function estimateTokens(text) {
|
|
19
|
+
return Math.ceil(text.length / 4);
|
|
20
|
+
}
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// SharedContextManager
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Manages shared context across all team agents
|
|
26
|
+
*/
|
|
27
|
+
export class SharedContextManager {
|
|
28
|
+
project;
|
|
29
|
+
team;
|
|
30
|
+
teamRoster = []; // Full roster for team awareness
|
|
31
|
+
recentActivity = []; // Activity feed for team awareness
|
|
32
|
+
decisions = [];
|
|
33
|
+
artifactIndex = [];
|
|
34
|
+
tokenBudget;
|
|
35
|
+
updatedAt = new Date();
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.project = {
|
|
38
|
+
id: options?.project?.id ?? null,
|
|
39
|
+
name: options?.project?.name ?? 'Unknown Project',
|
|
40
|
+
path: options?.project?.path ?? '.',
|
|
41
|
+
memorySummary: options?.project?.memorySummary ?? '',
|
|
42
|
+
};
|
|
43
|
+
this.team = {
|
|
44
|
+
agents: ['default'],
|
|
45
|
+
activeAgent: 'default',
|
|
46
|
+
};
|
|
47
|
+
this.tokenBudget = {
|
|
48
|
+
max: options?.maxTokens ?? DEFAULT_TOKEN_BUDGET,
|
|
49
|
+
current: 0,
|
|
50
|
+
};
|
|
51
|
+
this.updateTokenCount();
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Project Management
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Update project information
|
|
58
|
+
*/
|
|
59
|
+
setProject(project) {
|
|
60
|
+
if (project.id !== undefined)
|
|
61
|
+
this.project.id = project.id;
|
|
62
|
+
if (project.name !== undefined)
|
|
63
|
+
this.project.name = project.name;
|
|
64
|
+
if (project.path !== undefined)
|
|
65
|
+
this.project.path = project.path;
|
|
66
|
+
if (project.memorySummary !== undefined) {
|
|
67
|
+
// Truncate if too long
|
|
68
|
+
this.project.memorySummary = project.memorySummary.slice(0, MAX_MEMORY_SUMMARY_LENGTH);
|
|
69
|
+
}
|
|
70
|
+
this.touch();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get project information
|
|
74
|
+
*/
|
|
75
|
+
getProject() {
|
|
76
|
+
return { ...this.project };
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Team Management
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* Update team roster
|
|
83
|
+
*/
|
|
84
|
+
setTeam(agents, activeAgent) {
|
|
85
|
+
this.team.agents = [...agents];
|
|
86
|
+
if (activeAgent && agents.includes(activeAgent)) {
|
|
87
|
+
this.team.activeAgent = activeAgent;
|
|
88
|
+
}
|
|
89
|
+
this.touch();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set active agent
|
|
93
|
+
*/
|
|
94
|
+
setActiveAgent(agentId) {
|
|
95
|
+
if (this.team.agents.includes(agentId)) {
|
|
96
|
+
this.team.activeAgent = agentId;
|
|
97
|
+
this.touch();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Add an agent to the team
|
|
102
|
+
*/
|
|
103
|
+
addAgent(agentId) {
|
|
104
|
+
if (!this.team.agents.includes(agentId)) {
|
|
105
|
+
this.team.agents.push(agentId);
|
|
106
|
+
this.touch();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Remove an agent from the team
|
|
111
|
+
*/
|
|
112
|
+
removeAgent(agentId) {
|
|
113
|
+
const index = this.team.agents.indexOf(agentId);
|
|
114
|
+
if (index !== -1) {
|
|
115
|
+
this.team.agents.splice(index, 1);
|
|
116
|
+
// If active agent was removed, switch to default
|
|
117
|
+
if (this.team.activeAgent === agentId) {
|
|
118
|
+
this.team.activeAgent = 'default';
|
|
119
|
+
}
|
|
120
|
+
this.touch();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get team roster
|
|
125
|
+
*/
|
|
126
|
+
getTeam() {
|
|
127
|
+
return { ...this.team, agents: [...this.team.agents] };
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Team Roster (for Team Awareness)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
/**
|
|
133
|
+
* Update the full team roster with agent details
|
|
134
|
+
* Called when team composition changes
|
|
135
|
+
*/
|
|
136
|
+
updateTeamRoster(entries) {
|
|
137
|
+
this.teamRoster = entries.map((entry) => ({ ...entry }));
|
|
138
|
+
this.touch();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set active agent in the roster
|
|
142
|
+
* Called on agent switch
|
|
143
|
+
*/
|
|
144
|
+
setRosterActiveAgent(agentId) {
|
|
145
|
+
for (const entry of this.teamRoster) {
|
|
146
|
+
entry.isActive = entry.id === agentId;
|
|
147
|
+
}
|
|
148
|
+
// Also update the simple team info
|
|
149
|
+
if (this.team.agents.includes(agentId)) {
|
|
150
|
+
this.team.activeAgent = agentId;
|
|
151
|
+
}
|
|
152
|
+
this.touch();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get the full team roster
|
|
156
|
+
*/
|
|
157
|
+
getTeamRoster() {
|
|
158
|
+
return this.teamRoster.map((entry) => ({ ...entry }));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if team roster is available (more than just default agent)
|
|
162
|
+
*/
|
|
163
|
+
hasTeamRoster() {
|
|
164
|
+
return this.teamRoster.length > 1;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Update task counts for an agent
|
|
168
|
+
* Called by REPL when todo list or work items change
|
|
169
|
+
*/
|
|
170
|
+
updateAgentTaskCounts(agentId, todoCount, workItemCount) {
|
|
171
|
+
const entry = this.teamRoster.find((e) => e.id === agentId);
|
|
172
|
+
if (entry) {
|
|
173
|
+
entry.todoCount = todoCount;
|
|
174
|
+
entry.workItemCount = workItemCount;
|
|
175
|
+
this.touch();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Update task counts for all agents
|
|
180
|
+
* Called with a map of agentId -> { todoCount, workItemCount }
|
|
181
|
+
*/
|
|
182
|
+
updateAllAgentTaskCounts(counts) {
|
|
183
|
+
for (const entry of this.teamRoster) {
|
|
184
|
+
if (entry.id in counts) {
|
|
185
|
+
const agentCounts = counts[entry.id];
|
|
186
|
+
entry.todoCount = agentCounts.todoCount;
|
|
187
|
+
entry.workItemCount = agentCounts.workItemCount;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
entry.todoCount = 0;
|
|
191
|
+
entry.workItemCount = 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
this.touch();
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Activity Feed (for Team Awareness)
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Record a team activity
|
|
201
|
+
* Called when significant actions occur (artifact created, task completed, etc.)
|
|
202
|
+
*/
|
|
203
|
+
recordActivity(activity) {
|
|
204
|
+
const newActivity = {
|
|
205
|
+
...activity,
|
|
206
|
+
timestamp: new Date(),
|
|
207
|
+
};
|
|
208
|
+
this.recentActivity.unshift(newActivity);
|
|
209
|
+
// Keep only MAX_ACTIVITY_ITEMS
|
|
210
|
+
if (this.recentActivity.length > MAX_ACTIVITY_ITEMS) {
|
|
211
|
+
this.recentActivity = this.recentActivity.slice(0, MAX_ACTIVITY_ITEMS);
|
|
212
|
+
}
|
|
213
|
+
this.touch();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get recent activity
|
|
217
|
+
*/
|
|
218
|
+
getRecentActivity() {
|
|
219
|
+
return this.recentActivity.map((a) => ({ ...a }));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if there is recent activity to display
|
|
223
|
+
*/
|
|
224
|
+
hasRecentActivity() {
|
|
225
|
+
return this.recentActivity.length > 0;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear activity feed
|
|
229
|
+
*/
|
|
230
|
+
clearActivity() {
|
|
231
|
+
this.recentActivity = [];
|
|
232
|
+
this.touch();
|
|
233
|
+
}
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Decision Management
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
/**
|
|
238
|
+
* Add a decision to the shared context
|
|
239
|
+
*/
|
|
240
|
+
addDecision(decision) {
|
|
241
|
+
const id = `dec-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`;
|
|
242
|
+
// Truncate summary if too long
|
|
243
|
+
const summary = decision.summary.slice(0, MAX_DECISION_LENGTH);
|
|
244
|
+
const newDecision = {
|
|
245
|
+
id,
|
|
246
|
+
agent: decision.agent,
|
|
247
|
+
summary,
|
|
248
|
+
reasoning: decision.reasoning,
|
|
249
|
+
timestamp: new Date(),
|
|
250
|
+
};
|
|
251
|
+
this.decisions.unshift(newDecision);
|
|
252
|
+
// Keep only MAX_DECISIONS
|
|
253
|
+
if (this.decisions.length > MAX_DECISIONS) {
|
|
254
|
+
this.decisions = this.decisions.slice(0, MAX_DECISIONS);
|
|
255
|
+
}
|
|
256
|
+
this.touch();
|
|
257
|
+
return id;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Update/supersede a decision
|
|
261
|
+
*/
|
|
262
|
+
supersededDecision(oldId, newDecision) {
|
|
263
|
+
// Mark old decision as superseded
|
|
264
|
+
const old = this.decisions.find((d) => d.id === oldId);
|
|
265
|
+
const newId = this.addDecision(newDecision);
|
|
266
|
+
if (old) {
|
|
267
|
+
old.supersededBy = newId;
|
|
268
|
+
}
|
|
269
|
+
return newId;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get all decisions (active only by default)
|
|
273
|
+
*/
|
|
274
|
+
getDecisions(includeSuperseded = false) {
|
|
275
|
+
if (includeSuperseded) {
|
|
276
|
+
return [...this.decisions];
|
|
277
|
+
}
|
|
278
|
+
return this.decisions.filter((d) => !d.supersededBy);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Clear all decisions
|
|
282
|
+
*/
|
|
283
|
+
clearDecisions() {
|
|
284
|
+
this.decisions = [];
|
|
285
|
+
this.touch();
|
|
286
|
+
}
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Artifact Index Management
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
/**
|
|
291
|
+
* Update the artifact index (called by ArtifactStore)
|
|
292
|
+
*/
|
|
293
|
+
setArtifactIndex(index) {
|
|
294
|
+
this.artifactIndex = [...index];
|
|
295
|
+
this.touch();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Add an artifact to the index
|
|
299
|
+
*/
|
|
300
|
+
addArtifactToIndex(artifact) {
|
|
301
|
+
// Remove existing with same ID
|
|
302
|
+
this.artifactIndex = this.artifactIndex.filter((a) => a.id !== artifact.id);
|
|
303
|
+
this.artifactIndex.unshift(artifact);
|
|
304
|
+
this.touch();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Remove an artifact from the index
|
|
308
|
+
*/
|
|
309
|
+
removeArtifactFromIndex(artifactId) {
|
|
310
|
+
this.artifactIndex = this.artifactIndex.filter((a) => a.id !== artifactId);
|
|
311
|
+
this.touch();
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get artifact index
|
|
315
|
+
*/
|
|
316
|
+
getArtifactIndex() {
|
|
317
|
+
return [...this.artifactIndex];
|
|
318
|
+
}
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Token Budget Management
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
/**
|
|
323
|
+
* Update token count based on current content
|
|
324
|
+
*/
|
|
325
|
+
updateTokenCount() {
|
|
326
|
+
let tokens = 0;
|
|
327
|
+
// Project info
|
|
328
|
+
tokens += estimateTokens(this.project.name);
|
|
329
|
+
tokens += estimateTokens(this.project.path);
|
|
330
|
+
tokens += estimateTokens(this.project.memorySummary);
|
|
331
|
+
// Team info
|
|
332
|
+
tokens += estimateTokens(this.team.agents.join(', '));
|
|
333
|
+
// Decisions
|
|
334
|
+
for (const decision of this.decisions) {
|
|
335
|
+
tokens += estimateTokens(decision.summary);
|
|
336
|
+
if (decision.reasoning) {
|
|
337
|
+
tokens += estimateTokens(decision.reasoning);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Artifact index
|
|
341
|
+
for (const artifact of this.artifactIndex) {
|
|
342
|
+
tokens += estimateTokens(artifact.name);
|
|
343
|
+
tokens += estimateTokens(artifact.summary);
|
|
344
|
+
}
|
|
345
|
+
this.tokenBudget.current = tokens;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if adding content would exceed budget
|
|
349
|
+
*/
|
|
350
|
+
wouldExceedBudget(additionalTokens) {
|
|
351
|
+
return this.tokenBudget.current + additionalTokens > this.tokenBudget.max;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get token budget status
|
|
355
|
+
*/
|
|
356
|
+
getTokenBudget() {
|
|
357
|
+
return { ...this.tokenBudget };
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get utilization percentage
|
|
361
|
+
*/
|
|
362
|
+
getUtilization() {
|
|
363
|
+
return Math.round((this.tokenBudget.current / this.tokenBudget.max) * 100);
|
|
364
|
+
}
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// Context Formatting
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
/**
|
|
369
|
+
* Format shared context for injection into system prompt.
|
|
370
|
+
* @param options.excludeRoster - If true, omit the team roster (it will be injected via anchor instead)
|
|
371
|
+
*/
|
|
372
|
+
format(options) {
|
|
373
|
+
const lines = [];
|
|
374
|
+
lines.push('## SHARED TEAM CONTEXT');
|
|
375
|
+
lines.push('');
|
|
376
|
+
// Project info
|
|
377
|
+
lines.push(`**Project:** ${this.project.name}`);
|
|
378
|
+
lines.push(`**Path:** ${this.project.path}`);
|
|
379
|
+
if (this.project.memorySummary) {
|
|
380
|
+
lines.push(`**Summary:** ${this.project.memorySummary}`);
|
|
381
|
+
}
|
|
382
|
+
lines.push('');
|
|
383
|
+
// Team roster — skip if excluded (will be injected via anchor for live updates)
|
|
384
|
+
if (!options?.excludeRoster) {
|
|
385
|
+
if (this.hasTeamRoster()) {
|
|
386
|
+
lines.push(this.formatTeamRoster());
|
|
387
|
+
lines.push('');
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// Fallback to simple team info
|
|
391
|
+
lines.push(`**Team:** ${this.team.agents.map((a) => `$${a}`).join(', ')}`);
|
|
392
|
+
lines.push(`**Active:** $${this.team.activeAgent}`);
|
|
393
|
+
lines.push('');
|
|
394
|
+
// Team collaboration instructions (only if multiple agents)
|
|
395
|
+
if (this.team.agents.length > 1) {
|
|
396
|
+
lines.push('**Team Collaboration:**');
|
|
397
|
+
lines.push('- You are part of a multi-agent team. Each agent has a specialized role.');
|
|
398
|
+
lines.push('- If a question or task seems better suited for another team member, suggest asking them.');
|
|
399
|
+
lines.push('- Example: "This is more of an architecture question - you might want to ask $arch about this."');
|
|
400
|
+
lines.push('- Reference artifacts created by other agents when relevant using $mention syntax.');
|
|
401
|
+
lines.push('');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Recent team activity (if any)
|
|
406
|
+
if (this.hasRecentActivity()) {
|
|
407
|
+
lines.push(this.formatRecentActivity());
|
|
408
|
+
lines.push('');
|
|
409
|
+
}
|
|
410
|
+
// Decisions
|
|
411
|
+
const activeDecisions = this.getDecisions(false);
|
|
412
|
+
if (activeDecisions.length > 0) {
|
|
413
|
+
lines.push('**Recent Decisions:**');
|
|
414
|
+
for (const decision of activeDecisions.slice(0, 5)) {
|
|
415
|
+
lines.push(`- [$${decision.agent}] ${decision.summary}`);
|
|
416
|
+
}
|
|
417
|
+
lines.push('');
|
|
418
|
+
}
|
|
419
|
+
// Artifacts
|
|
420
|
+
if (this.artifactIndex.length > 0) {
|
|
421
|
+
lines.push('**Available Artifacts:**');
|
|
422
|
+
for (const artifact of this.artifactIndex.slice(0, 10)) {
|
|
423
|
+
lines.push(`- "${artifact.name}" by $${artifact.agent} (${artifact.type})`);
|
|
424
|
+
}
|
|
425
|
+
lines.push('');
|
|
426
|
+
}
|
|
427
|
+
return lines.join('\n');
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Format team roster as a table for team awareness
|
|
431
|
+
*/
|
|
432
|
+
formatTeamRoster() {
|
|
433
|
+
const lines = [];
|
|
434
|
+
// Find the active agent for personalized guidance
|
|
435
|
+
const activeEntry = this.teamRoster.find((e) => e.isActive);
|
|
436
|
+
// Check if any agent has task counts to show
|
|
437
|
+
const showTaskCounts = this.teamRoster.some((e) => (e.todoCount ?? 0) > 0 || (e.workItemCount ?? 0) > 0);
|
|
438
|
+
lines.push('### Your Team');
|
|
439
|
+
lines.push('');
|
|
440
|
+
if (showTaskCounts) {
|
|
441
|
+
lines.push('| Agent | Role | Tasks | Expertise |');
|
|
442
|
+
lines.push('|-------|------|-------|-----------|');
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
lines.push('| Agent | Role | Expertise |');
|
|
446
|
+
lines.push('|-------|------|-----------|');
|
|
447
|
+
}
|
|
448
|
+
for (const entry of this.teamRoster) {
|
|
449
|
+
const marker = entry.isActive ? '→ ' : ' ';
|
|
450
|
+
const youMarker = entry.isActive ? ' ← YOU' : '';
|
|
451
|
+
// Show first 3 expertise keywords to keep it concise
|
|
452
|
+
const expertise = entry.expertise.slice(0, 3).join(', ');
|
|
453
|
+
if (showTaskCounts) {
|
|
454
|
+
// Format task counts
|
|
455
|
+
const todoCount = entry.todoCount ?? 0;
|
|
456
|
+
const workItemCount = entry.workItemCount ?? 0;
|
|
457
|
+
const taskParts = [];
|
|
458
|
+
if (todoCount > 0)
|
|
459
|
+
taskParts.push(`${String(todoCount)} todo${todoCount > 1 ? 's' : ''}`);
|
|
460
|
+
if (workItemCount > 0)
|
|
461
|
+
taskParts.push(`${String(workItemCount)} item${workItemCount > 1 ? 's' : ''}`);
|
|
462
|
+
const taskStr = taskParts.length > 0 ? taskParts.join(', ') : '-';
|
|
463
|
+
lines.push(`| ${marker}$${entry.id} ${entry.mascot} | ${entry.displayName} | ${taskStr} | ${expertise}${youMarker} |`);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
lines.push(`| ${marker}$${entry.id} ${entry.mascot} | ${entry.displayName} | ${expertise}${youMarker} |`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
lines.push('');
|
|
470
|
+
// Handoff guidance - dynamic based on active agent and team
|
|
471
|
+
if (activeEntry) {
|
|
472
|
+
const yourExpertise = activeEntry.expertise.slice(0, 3).join(', ');
|
|
473
|
+
lines.push(`**Your expertise:** ${yourExpertise}`);
|
|
474
|
+
lines.push('');
|
|
475
|
+
}
|
|
476
|
+
lines.push('**Collaboration Guidelines:**');
|
|
477
|
+
lines.push('- If a task falls outside your expertise, suggest the appropriate specialist');
|
|
478
|
+
lines.push('- Example: "This would be better handled by $arch for architecture decisions"');
|
|
479
|
+
lines.push("- Do NOT attempt tasks far outside your expertise - it's better to suggest a handoff");
|
|
480
|
+
// Add specific handoff suggestions based on teammates
|
|
481
|
+
const handoffSuggestions = this.buildHandoffSuggestions(activeEntry?.id);
|
|
482
|
+
if (handoffSuggestions.length > 0) {
|
|
483
|
+
lines.push('');
|
|
484
|
+
lines.push('**When to involve teammates:**');
|
|
485
|
+
for (const suggestion of handoffSuggestions) {
|
|
486
|
+
lines.push(`- ${suggestion}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return lines.join('\n');
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Build handoff suggestions based on team composition
|
|
493
|
+
*/
|
|
494
|
+
buildHandoffSuggestions(activeAgentId) {
|
|
495
|
+
const suggestions = [];
|
|
496
|
+
for (const entry of this.teamRoster) {
|
|
497
|
+
// Skip the active agent
|
|
498
|
+
if (entry.id === activeAgentId)
|
|
499
|
+
continue;
|
|
500
|
+
// Build suggestion based on role
|
|
501
|
+
const role = entry.role;
|
|
502
|
+
switch (role) {
|
|
503
|
+
case 'arch':
|
|
504
|
+
suggestions.push(`Architecture/design questions → "Let me get $${entry.id}'s input on the design"`);
|
|
505
|
+
break;
|
|
506
|
+
case 'pm':
|
|
507
|
+
suggestions.push(`Planning/prioritization → "$${entry.id} can help with task breakdown"`);
|
|
508
|
+
break;
|
|
509
|
+
case 'qa':
|
|
510
|
+
suggestions.push(`Testing/quality concerns → "$${entry.id} should review the test strategy"`);
|
|
511
|
+
break;
|
|
512
|
+
case 'dev':
|
|
513
|
+
suggestions.push(`Implementation details → "$${entry.id} can handle the coding"`);
|
|
514
|
+
break;
|
|
515
|
+
case 'ops':
|
|
516
|
+
suggestions.push(`Deployment/infrastructure → "$${entry.id} can advise on CI/CD"`);
|
|
517
|
+
break;
|
|
518
|
+
case 'docs':
|
|
519
|
+
suggestions.push(`Documentation needs → "$${entry.id} can write the user docs"`);
|
|
520
|
+
break;
|
|
521
|
+
case 'ba':
|
|
522
|
+
suggestions.push(`Requirements clarity → "$${entry.id} can help clarify the business need"`);
|
|
523
|
+
break;
|
|
524
|
+
default:
|
|
525
|
+
// For default or custom agents, use their display name
|
|
526
|
+
if (entry.expertise.length > 0) {
|
|
527
|
+
suggestions.push(`${entry.expertise[0]} → "$${entry.id} specializes in this"`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Limit to 5 suggestions to keep it concise
|
|
532
|
+
return suggestions.slice(0, 5);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Format recent activity feed for team awareness
|
|
536
|
+
*/
|
|
537
|
+
formatRecentActivity() {
|
|
538
|
+
const lines = [];
|
|
539
|
+
lines.push('### Recent Team Activity');
|
|
540
|
+
lines.push('');
|
|
541
|
+
for (const activity of this.recentActivity) {
|
|
542
|
+
const ago = this.formatTimeAgo(activity.timestamp);
|
|
543
|
+
lines.push(`- [${ago}] $${activity.agentId} ${activity.summary}`);
|
|
544
|
+
}
|
|
545
|
+
return lines.join('\n');
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Format a timestamp as "Xm ago", "Xh ago", etc.
|
|
549
|
+
*/
|
|
550
|
+
formatTimeAgo(timestamp) {
|
|
551
|
+
const now = Date.now();
|
|
552
|
+
const diff = now - timestamp.getTime();
|
|
553
|
+
const minutes = Math.floor(diff / 60000);
|
|
554
|
+
const hours = Math.floor(diff / 3600000);
|
|
555
|
+
if (minutes < 1) {
|
|
556
|
+
return 'just now';
|
|
557
|
+
}
|
|
558
|
+
else if (minutes < 60) {
|
|
559
|
+
return `${String(minutes)}m ago`;
|
|
560
|
+
}
|
|
561
|
+
else if (hours < 24) {
|
|
562
|
+
return `${String(hours)}h ago`;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const days = Math.floor(hours / 24);
|
|
566
|
+
return `${String(days)}d ago`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Format a compact version (for token-constrained situations)
|
|
571
|
+
*/
|
|
572
|
+
formatCompact() {
|
|
573
|
+
const parts = [];
|
|
574
|
+
parts.push(`Project: ${this.project.name}`);
|
|
575
|
+
parts.push(`Team: ${this.team.agents.map((a) => `$${a}`).join(', ')}`);
|
|
576
|
+
const activeDecisions = this.getDecisions(false);
|
|
577
|
+
if (activeDecisions.length > 0) {
|
|
578
|
+
parts.push(`Decisions: ${String(activeDecisions.length)}`);
|
|
579
|
+
}
|
|
580
|
+
if (this.artifactIndex.length > 0) {
|
|
581
|
+
parts.push(`Artifacts: ${this.artifactIndex.map((a) => `"${a.name}"`).join(', ')}`);
|
|
582
|
+
}
|
|
583
|
+
return parts.join(' | ');
|
|
584
|
+
}
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
// Persistence
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
/**
|
|
589
|
+
* Mark as updated
|
|
590
|
+
*/
|
|
591
|
+
touch() {
|
|
592
|
+
this.updatedAt = new Date();
|
|
593
|
+
this.updateTokenCount();
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Serialize for persistence
|
|
597
|
+
*/
|
|
598
|
+
serialize() {
|
|
599
|
+
return {
|
|
600
|
+
version: 1,
|
|
601
|
+
project: {
|
|
602
|
+
id: this.project.id,
|
|
603
|
+
name: this.project.name,
|
|
604
|
+
path: this.project.path,
|
|
605
|
+
memorySummary: this.project.memorySummary,
|
|
606
|
+
},
|
|
607
|
+
decisions: this.decisions.map((d) => ({
|
|
608
|
+
id: d.id,
|
|
609
|
+
agent: d.agent,
|
|
610
|
+
summary: d.summary,
|
|
611
|
+
reasoning: d.reasoning,
|
|
612
|
+
timestamp: d.timestamp.toISOString(),
|
|
613
|
+
supersededBy: d.supersededBy,
|
|
614
|
+
})),
|
|
615
|
+
artifactIndex: this.artifactIndex.map((a) => ({
|
|
616
|
+
id: a.id,
|
|
617
|
+
name: a.name,
|
|
618
|
+
agent: a.agent,
|
|
619
|
+
type: a.type,
|
|
620
|
+
summary: a.summary,
|
|
621
|
+
updatedAt: a.updatedAt.toISOString(),
|
|
622
|
+
})),
|
|
623
|
+
updatedAt: this.updatedAt.toISOString(),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Restore from serialized data
|
|
628
|
+
*/
|
|
629
|
+
static fromSerialized(data) {
|
|
630
|
+
const manager = new SharedContextManager({
|
|
631
|
+
project: {
|
|
632
|
+
id: data.project.id,
|
|
633
|
+
name: data.project.name,
|
|
634
|
+
path: data.project.path,
|
|
635
|
+
memorySummary: data.project.memorySummary,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
// Restore decisions
|
|
639
|
+
manager.decisions = data.decisions.map((d) => ({
|
|
640
|
+
id: d.id,
|
|
641
|
+
agent: d.agent,
|
|
642
|
+
summary: d.summary,
|
|
643
|
+
reasoning: d.reasoning,
|
|
644
|
+
timestamp: new Date(d.timestamp),
|
|
645
|
+
supersededBy: d.supersededBy,
|
|
646
|
+
}));
|
|
647
|
+
// Restore artifact index
|
|
648
|
+
manager.artifactIndex = data.artifactIndex.map((a) => ({
|
|
649
|
+
id: a.id,
|
|
650
|
+
name: a.name,
|
|
651
|
+
agent: a.agent,
|
|
652
|
+
type: a.type,
|
|
653
|
+
summary: a.summary,
|
|
654
|
+
updatedAt: new Date(a.updatedAt),
|
|
655
|
+
}));
|
|
656
|
+
manager.updatedAt = new Date(data.updatedAt);
|
|
657
|
+
manager.updateTokenCount();
|
|
658
|
+
return manager;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get the full context object
|
|
662
|
+
*/
|
|
663
|
+
getContext() {
|
|
664
|
+
return {
|
|
665
|
+
project: this.getProject(),
|
|
666
|
+
team: this.getTeam(),
|
|
667
|
+
decisions: this.getDecisions(),
|
|
668
|
+
artifactIndex: this.getArtifactIndex(),
|
|
669
|
+
tokenBudget: this.getTokenBudget(),
|
|
670
|
+
updatedAt: this.updatedAt,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
}
|