@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,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextResolver - Resolve $agent mentions to actual content
|
|
3
|
+
*
|
|
4
|
+
* Resolution strategy:
|
|
5
|
+
* 1. Check for explicit artifact reference in context hint
|
|
6
|
+
* 2. Search artifacts by name/content matching context hint
|
|
7
|
+
* 3. Fall back to recent conversation history
|
|
8
|
+
*
|
|
9
|
+
* Token budgets:
|
|
10
|
+
* - Single mention: 2000 tokens max
|
|
11
|
+
* - Multiple mentions: 3000 tokens total, split among mentions
|
|
12
|
+
*/
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// =============================================================================
|
|
16
|
+
const DEFAULT_MAX_TOKENS_PER_MENTION = 2000;
|
|
17
|
+
const DEFAULT_MAX_TOTAL_TOKENS = 3000;
|
|
18
|
+
const DEFAULT_MAX_HISTORY_MESSAGES = 10;
|
|
19
|
+
/**
|
|
20
|
+
* Simple token estimation: ~4 chars per token
|
|
21
|
+
*/
|
|
22
|
+
function estimateTokens(text) {
|
|
23
|
+
return Math.ceil(text.length / 4);
|
|
24
|
+
}
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// ContextResolver
|
|
27
|
+
// =============================================================================
|
|
28
|
+
/**
|
|
29
|
+
* Resolve mentions to actual content
|
|
30
|
+
*/
|
|
31
|
+
export class ContextResolver {
|
|
32
|
+
team;
|
|
33
|
+
artifactStore;
|
|
34
|
+
constructor(team, artifactStore) {
|
|
35
|
+
this.team = team;
|
|
36
|
+
this.artifactStore = artifactStore;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve multiple mentions
|
|
40
|
+
*/
|
|
41
|
+
resolveAll(mentions, options = {}) {
|
|
42
|
+
const maxTotal = options.maxTotalTokens ?? DEFAULT_MAX_TOTAL_TOKENS;
|
|
43
|
+
const perMention = options.maxTokensPerMention ?? DEFAULT_MAX_TOKENS_PER_MENTION;
|
|
44
|
+
// Calculate budget per mention
|
|
45
|
+
const budgetPerMention = Math.min(perMention, Math.floor(maxTotal / mentions.length));
|
|
46
|
+
const results = new Map();
|
|
47
|
+
let totalTokens = 0;
|
|
48
|
+
for (const mention of mentions) {
|
|
49
|
+
// Skip if we're out of budget
|
|
50
|
+
if (totalTokens >= maxTotal) {
|
|
51
|
+
results.set(mention.agentId, {
|
|
52
|
+
mention,
|
|
53
|
+
resolved: false,
|
|
54
|
+
source: 'not_found',
|
|
55
|
+
sourceName: 'budget exceeded',
|
|
56
|
+
content: '',
|
|
57
|
+
tokenCount: 0,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const remainingBudget = Math.min(budgetPerMention, maxTotal - totalTokens);
|
|
62
|
+
const resolved = this.resolveMention(mention, {
|
|
63
|
+
...options,
|
|
64
|
+
maxTokensPerMention: remainingBudget,
|
|
65
|
+
});
|
|
66
|
+
results.set(mention.agentId, resolved);
|
|
67
|
+
totalTokens += resolved.tokenCount;
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a single mention
|
|
73
|
+
*/
|
|
74
|
+
resolveMention(mention, options = {}) {
|
|
75
|
+
const maxTokens = options.maxTokensPerMention ?? DEFAULT_MAX_TOKENS_PER_MENTION;
|
|
76
|
+
// 1. Try to find matching artifact
|
|
77
|
+
const artifactResult = this.resolveFromArtifacts(mention, maxTokens, options);
|
|
78
|
+
if (artifactResult) {
|
|
79
|
+
return artifactResult;
|
|
80
|
+
}
|
|
81
|
+
// 2. Try to extract from conversation history
|
|
82
|
+
const historyResult = this.resolveFromHistory(mention, maxTokens, options);
|
|
83
|
+
if (historyResult) {
|
|
84
|
+
return historyResult;
|
|
85
|
+
}
|
|
86
|
+
// 3. Not found
|
|
87
|
+
return {
|
|
88
|
+
mention,
|
|
89
|
+
resolved: false,
|
|
90
|
+
source: 'not_found',
|
|
91
|
+
sourceName: 'no relevant content found',
|
|
92
|
+
content: '',
|
|
93
|
+
tokenCount: 0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve from artifacts
|
|
98
|
+
*/
|
|
99
|
+
resolveFromArtifacts(mention, maxTokens, options) {
|
|
100
|
+
const { agentId, context } = mention;
|
|
101
|
+
// Get artifacts by this agent
|
|
102
|
+
const agentArtifacts = this.artifactStore.listByAgent(agentId);
|
|
103
|
+
if (agentArtifacts.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// Sort artifacts by most recent first
|
|
107
|
+
const sortedArtifacts = agentArtifacts.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
108
|
+
// Find matching artifact based on context hint
|
|
109
|
+
let matchedArtifact;
|
|
110
|
+
if (context) {
|
|
111
|
+
// Search by name or content
|
|
112
|
+
const searchResults = this.artifactStore.search(context);
|
|
113
|
+
const searchMatch = searchResults.find((a) => a.agent === agentId);
|
|
114
|
+
// Use search match if found, otherwise fall back to most recent
|
|
115
|
+
matchedArtifact = searchMatch ?? sortedArtifacts[0];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// No context hint - use most recent artifact
|
|
119
|
+
matchedArtifact = sortedArtifacts[0];
|
|
120
|
+
}
|
|
121
|
+
// Format artifact content
|
|
122
|
+
let content;
|
|
123
|
+
let tokenCount;
|
|
124
|
+
if (options.includeFullArtifact) {
|
|
125
|
+
content = this.formatArtifactFull(matchedArtifact);
|
|
126
|
+
tokenCount = estimateTokens(content);
|
|
127
|
+
// If over budget, fall back to summary
|
|
128
|
+
if (tokenCount > maxTokens) {
|
|
129
|
+
content = this.formatArtifactSummary(matchedArtifact);
|
|
130
|
+
tokenCount = estimateTokens(content);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Start with summary, include full if within budget
|
|
135
|
+
const summary = this.formatArtifactSummary(matchedArtifact);
|
|
136
|
+
const full = this.formatArtifactFull(matchedArtifact);
|
|
137
|
+
if (estimateTokens(full) <= maxTokens) {
|
|
138
|
+
content = full;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
content = summary;
|
|
142
|
+
}
|
|
143
|
+
tokenCount = estimateTokens(content);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
mention,
|
|
147
|
+
resolved: true,
|
|
148
|
+
source: 'artifact',
|
|
149
|
+
sourceName: matchedArtifact.name,
|
|
150
|
+
content,
|
|
151
|
+
tokenCount,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resolve from conversation history
|
|
156
|
+
*/
|
|
157
|
+
resolveFromHistory(mention, maxTokens, options) {
|
|
158
|
+
const { agentId, context } = mention;
|
|
159
|
+
const maxMessages = options.maxHistoryMessages ?? DEFAULT_MAX_HISTORY_MESSAGES;
|
|
160
|
+
// Get the team agent
|
|
161
|
+
const teamAgent = this.team.get(agentId);
|
|
162
|
+
if (!teamAgent) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
// Get the underlying agent
|
|
166
|
+
const agent = teamAgent.agent;
|
|
167
|
+
if (!agent) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
// Get recent messages from the agent's history
|
|
171
|
+
const state = agent.serialize();
|
|
172
|
+
const messages = state.messages;
|
|
173
|
+
if (messages.length === 0) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// Get assistant messages (agent's responses)
|
|
177
|
+
const assistantMessages = messages
|
|
178
|
+
.filter((m) => m.role === 'assistant')
|
|
179
|
+
.slice(-maxMessages);
|
|
180
|
+
if (assistantMessages.length === 0) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// Extract relevant content
|
|
184
|
+
let relevantContent;
|
|
185
|
+
if (context) {
|
|
186
|
+
// Search for context in messages
|
|
187
|
+
relevantContent = this.extractRelevantContent(assistantMessages, context, maxTokens);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Use most recent message
|
|
191
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
192
|
+
relevantContent = this.formatMessage(lastMessage);
|
|
193
|
+
}
|
|
194
|
+
if (!relevantContent) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// Truncate if needed
|
|
198
|
+
const tokenCount = estimateTokens(relevantContent);
|
|
199
|
+
if (tokenCount > maxTokens) {
|
|
200
|
+
relevantContent = this.truncateToTokens(relevantContent, maxTokens);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
mention,
|
|
204
|
+
resolved: true,
|
|
205
|
+
source: 'history',
|
|
206
|
+
sourceName: 'recent conversation',
|
|
207
|
+
content: relevantContent,
|
|
208
|
+
tokenCount: estimateTokens(relevantContent),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Extract relevant content from messages based on context hint
|
|
213
|
+
*/
|
|
214
|
+
extractRelevantContent(messages, context, maxTokens) {
|
|
215
|
+
const keywords = context
|
|
216
|
+
.toLowerCase()
|
|
217
|
+
.split(/\s+/)
|
|
218
|
+
.filter((w) => w.length > 2);
|
|
219
|
+
// Score messages by relevance
|
|
220
|
+
const scored = messages.map((msg) => {
|
|
221
|
+
const content = this.formatMessage(msg);
|
|
222
|
+
const contentLower = content.toLowerCase();
|
|
223
|
+
let score = 0;
|
|
224
|
+
for (const keyword of keywords) {
|
|
225
|
+
if (contentLower.includes(keyword)) {
|
|
226
|
+
score += 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { content, score };
|
|
230
|
+
});
|
|
231
|
+
// Sort by score (highest first)
|
|
232
|
+
scored.sort((a, b) => b.score - a.score);
|
|
233
|
+
// Take best matches within token budget
|
|
234
|
+
const results = [];
|
|
235
|
+
let totalTokens = 0;
|
|
236
|
+
for (const { content, score } of scored) {
|
|
237
|
+
if (score === 0)
|
|
238
|
+
continue;
|
|
239
|
+
const tokens = estimateTokens(content);
|
|
240
|
+
if (totalTokens + tokens > maxTokens) {
|
|
241
|
+
// Try to include truncated version
|
|
242
|
+
const remaining = maxTokens - totalTokens;
|
|
243
|
+
if (remaining > 100) {
|
|
244
|
+
results.push(this.truncateToTokens(content, remaining));
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
results.push(content);
|
|
249
|
+
totalTokens += tokens;
|
|
250
|
+
}
|
|
251
|
+
return results.join('\n\n---\n\n');
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Format a message for injection
|
|
255
|
+
*/
|
|
256
|
+
formatMessage(message) {
|
|
257
|
+
if (typeof message.content === 'string') {
|
|
258
|
+
return message.content;
|
|
259
|
+
}
|
|
260
|
+
// Handle array content (Claude format)
|
|
261
|
+
if (Array.isArray(message.content)) {
|
|
262
|
+
const blocks = message.content;
|
|
263
|
+
return blocks
|
|
264
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
265
|
+
.map((block) => block.text)
|
|
266
|
+
.join('\n');
|
|
267
|
+
}
|
|
268
|
+
return String(message.content);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Format artifact with full content
|
|
272
|
+
*/
|
|
273
|
+
formatArtifactFull(artifact) {
|
|
274
|
+
return [
|
|
275
|
+
`**${artifact.name}** (${artifact.type})`,
|
|
276
|
+
`By: $${artifact.agent} | Updated: ${artifact.updatedAt.toLocaleDateString()}`,
|
|
277
|
+
'',
|
|
278
|
+
artifact.content,
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Format artifact with summary only
|
|
283
|
+
*/
|
|
284
|
+
formatArtifactSummary(artifact) {
|
|
285
|
+
return [
|
|
286
|
+
`**${artifact.name}** (${artifact.type})`,
|
|
287
|
+
`By: $${artifact.agent} | Updated: ${artifact.updatedAt.toLocaleDateString()}`,
|
|
288
|
+
'',
|
|
289
|
+
`Summary: ${artifact.summary}`,
|
|
290
|
+
'',
|
|
291
|
+
`[Full content available via artifact store]`,
|
|
292
|
+
].join('\n');
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Truncate content to approximate token count
|
|
296
|
+
*/
|
|
297
|
+
truncateToTokens(content, maxTokens) {
|
|
298
|
+
const maxChars = maxTokens * 4;
|
|
299
|
+
if (content.length <= maxChars) {
|
|
300
|
+
return content;
|
|
301
|
+
}
|
|
302
|
+
// Truncate at word boundary
|
|
303
|
+
const truncated = content.slice(0, maxChars);
|
|
304
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
305
|
+
if (lastSpace > maxChars * 0.8) {
|
|
306
|
+
return truncated.slice(0, lastSpace) + '...';
|
|
307
|
+
}
|
|
308
|
+
return truncated + '...';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Build a context map from resolved mentions
|
|
313
|
+
*/
|
|
314
|
+
export function buildContextMap(resolved) {
|
|
315
|
+
const contextMap = new Map();
|
|
316
|
+
for (const [agentId, resolution] of resolved) {
|
|
317
|
+
if (resolution.resolved && resolution.content) {
|
|
318
|
+
contextMap.set(agentId, resolution.content);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return contextMap;
|
|
322
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Agent Definitions
|
|
3
|
+
*
|
|
4
|
+
* Enables users to create custom specialized agents beyond predefined templates.
|
|
5
|
+
* Custom agents have user-defined names, specialties, and personalities.
|
|
6
|
+
*
|
|
7
|
+
* This SDK version contains only types and pure functions.
|
|
8
|
+
* Consumers handle their own persistence (load/save).
|
|
9
|
+
*/
|
|
10
|
+
import { type ToolConfig, type ToolProfile } from './tool-config.js';
|
|
11
|
+
import type { ModelTier } from '../models/index.js';
|
|
12
|
+
export interface CustomAgentDefinition {
|
|
13
|
+
id: string;
|
|
14
|
+
displayName: string;
|
|
15
|
+
specialty: string;
|
|
16
|
+
personality?: string;
|
|
17
|
+
mascot: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
toolConfig?: ToolConfig;
|
|
20
|
+
enabledSkills?: string[];
|
|
21
|
+
modelTier?: ModelTier;
|
|
22
|
+
}
|
|
23
|
+
export type { ToolConfig, ToolProfile };
|
|
24
|
+
/**
|
|
25
|
+
* Mascots available for custom agents.
|
|
26
|
+
* These are distinct from predefined role mascots.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CUSTOM_MASCOTS: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Assign a mascot from the available pool.
|
|
31
|
+
* Avoids mascots already in use by other custom agents.
|
|
32
|
+
*/
|
|
33
|
+
export declare function assignMascot(existingAgents: CustomAgentDefinition[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Generate system prompt for a custom agent.
|
|
36
|
+
* Uses a template-based approach (no LLM call).
|
|
37
|
+
* Includes tool awareness if the agent has tool restrictions.
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateCustomAgentSystemPrompt(agent: CustomAgentDefinition): string;
|
|
40
|
+
/**
|
|
41
|
+
* Get the tool filter (list of allowed tools) for a custom agent.
|
|
42
|
+
* Returns undefined for full access (no filtering).
|
|
43
|
+
*/
|
|
44
|
+
export declare function getCustomAgentToolFilter(agent: CustomAgentDefinition): string[] | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Get the profile display name for a custom agent.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCustomAgentProfileLabel(agent: CustomAgentDefinition): string;
|
|
49
|
+
/**
|
|
50
|
+
* Validate agent ID format.
|
|
51
|
+
* Must be lowercase letters, numbers, and underscores.
|
|
52
|
+
* Must start with a letter.
|
|
53
|
+
*/
|
|
54
|
+
export declare function validateAgentId(id: string): {
|
|
55
|
+
valid: boolean;
|
|
56
|
+
error?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Check if agent ID is already in use.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isAgentIdTaken(id: string, existingCustomAgents: CustomAgentDefinition[], teamAgentIds: string[], predefinedRoleIds: string[]): {
|
|
62
|
+
taken: boolean;
|
|
63
|
+
reason?: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Create a new CustomAgentDefinition with auto-assigned mascot.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createCustomAgentDefinition(id: string, displayName: string, specialty: string, personality: string | undefined, existingAgents: CustomAgentDefinition[], toolConfig?: ToolConfig, enabledSkills?: string[], modelTier?: ModelTier): CustomAgentDefinition;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Agent Definitions
|
|
3
|
+
*
|
|
4
|
+
* Enables users to create custom specialized agents beyond predefined templates.
|
|
5
|
+
* Custom agents have user-defined names, specialties, and personalities.
|
|
6
|
+
*
|
|
7
|
+
* This SDK version contains only types and pure functions.
|
|
8
|
+
* Consumers handle their own persistence (load/save).
|
|
9
|
+
*/
|
|
10
|
+
import { createDefaultToolConfig, getToolsForProfile, generateToolAwarenessPrompt, PROFILE_INFO, } from './tool-config.js';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Mascot Pool for Custom Agents
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Mascots available for custom agents.
|
|
16
|
+
* These are distinct from predefined role mascots.
|
|
17
|
+
*/
|
|
18
|
+
export const CUSTOM_MASCOTS = [
|
|
19
|
+
'[⊡_⊡]',
|
|
20
|
+
'[⊞_⊞]',
|
|
21
|
+
'[⊟_⊟]',
|
|
22
|
+
'[⊠_⊠]',
|
|
23
|
+
'[⋈_⋈]',
|
|
24
|
+
'[⋐_⋐]',
|
|
25
|
+
'[⋑_⋑]',
|
|
26
|
+
'[⋒_⋒]',
|
|
27
|
+
'[◌_◌]',
|
|
28
|
+
'[◍_◍]',
|
|
29
|
+
'[●_●]',
|
|
30
|
+
'[◐_◐]',
|
|
31
|
+
'[◑_◑]',
|
|
32
|
+
'[◒_◒]',
|
|
33
|
+
'[◓_◓]',
|
|
34
|
+
'[◔_◔]',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Assign a mascot from the available pool.
|
|
38
|
+
* Avoids mascots already in use by other custom agents.
|
|
39
|
+
*/
|
|
40
|
+
export function assignMascot(existingAgents) {
|
|
41
|
+
const usedMascots = new Set(existingAgents.map((a) => a.mascot));
|
|
42
|
+
const available = CUSTOM_MASCOTS.filter((m) => !usedMascots.has(m));
|
|
43
|
+
if (available.length > 0) {
|
|
44
|
+
return available[0];
|
|
45
|
+
}
|
|
46
|
+
// Fall back to random selection if all are used
|
|
47
|
+
return CUSTOM_MASCOTS[Math.floor(Math.random() * CUSTOM_MASCOTS.length)];
|
|
48
|
+
}
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// System Prompt Generation
|
|
51
|
+
// =============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Generate system prompt for a custom agent.
|
|
54
|
+
* Uses a template-based approach (no LLM call).
|
|
55
|
+
* Includes tool awareness if the agent has tool restrictions.
|
|
56
|
+
*/
|
|
57
|
+
export function generateCustomAgentSystemPrompt(agent) {
|
|
58
|
+
const lines = [`You are a ${agent.displayName} specialized in ${agent.specialty}.`];
|
|
59
|
+
if (agent.personality) {
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push(`Your approach: ${agent.personality}`);
|
|
62
|
+
}
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('Focus on your area of expertise. When questions fall outside your specialty, suggest which team member might be better suited to help.');
|
|
65
|
+
// Add tool awareness if agent has tool restrictions
|
|
66
|
+
const toolConfig = agent.toolConfig ?? createDefaultToolConfig();
|
|
67
|
+
if (toolConfig.profile !== 'full') {
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push('---');
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push(generateToolAwarenessPrompt(toolConfig));
|
|
72
|
+
}
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the tool filter (list of allowed tools) for a custom agent.
|
|
77
|
+
* Returns undefined for full access (no filtering).
|
|
78
|
+
*/
|
|
79
|
+
export function getCustomAgentToolFilter(agent) {
|
|
80
|
+
const toolConfig = agent.toolConfig ?? createDefaultToolConfig();
|
|
81
|
+
return getToolsForProfile(toolConfig.profile, toolConfig.customGroups);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the profile display name for a custom agent.
|
|
85
|
+
*/
|
|
86
|
+
export function getCustomAgentProfileLabel(agent) {
|
|
87
|
+
const toolConfig = agent.toolConfig ?? createDefaultToolConfig();
|
|
88
|
+
const info = PROFILE_INFO[toolConfig.profile];
|
|
89
|
+
return info.label;
|
|
90
|
+
}
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Validation
|
|
93
|
+
// =============================================================================
|
|
94
|
+
/**
|
|
95
|
+
* Validate agent ID format.
|
|
96
|
+
* Must be lowercase letters, numbers, and underscores.
|
|
97
|
+
* Must start with a letter.
|
|
98
|
+
*/
|
|
99
|
+
export function validateAgentId(id) {
|
|
100
|
+
if (!id || id.trim() === '') {
|
|
101
|
+
return { valid: false, error: 'Agent ID is required' };
|
|
102
|
+
}
|
|
103
|
+
if (id.length > 20) {
|
|
104
|
+
return { valid: false, error: 'Agent ID must be 20 characters or less' };
|
|
105
|
+
}
|
|
106
|
+
if (!/^[a-z][a-z0-9_]*$/.test(id)) {
|
|
107
|
+
return {
|
|
108
|
+
valid: false,
|
|
109
|
+
error: 'Must be lowercase letters, numbers, underscore (start with letter)',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { valid: true };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if agent ID is already in use.
|
|
116
|
+
*/
|
|
117
|
+
export function isAgentIdTaken(id, existingCustomAgents, teamAgentIds, predefinedRoleIds) {
|
|
118
|
+
// Check predefined roles
|
|
119
|
+
if (predefinedRoleIds.includes(id)) {
|
|
120
|
+
return { taken: true, reason: `"${id}" is a predefined role` };
|
|
121
|
+
}
|
|
122
|
+
// Check team agents
|
|
123
|
+
if (teamAgentIds.includes(id)) {
|
|
124
|
+
return { taken: true, reason: `"${id}" is already in your team` };
|
|
125
|
+
}
|
|
126
|
+
// Check custom agents
|
|
127
|
+
if (existingCustomAgents.some((a) => a.id === id)) {
|
|
128
|
+
return { taken: true, reason: `"${id}" already exists as a custom agent` };
|
|
129
|
+
}
|
|
130
|
+
return { taken: false };
|
|
131
|
+
}
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Factory
|
|
134
|
+
// =============================================================================
|
|
135
|
+
/**
|
|
136
|
+
* Create a new CustomAgentDefinition with auto-assigned mascot.
|
|
137
|
+
*/
|
|
138
|
+
export function createCustomAgentDefinition(id, displayName, specialty, personality, existingAgents, toolConfig, enabledSkills, modelTier) {
|
|
139
|
+
return {
|
|
140
|
+
id,
|
|
141
|
+
displayName,
|
|
142
|
+
specialty,
|
|
143
|
+
personality: personality || undefined,
|
|
144
|
+
mascot: assignMascot(existingAgents),
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
toolConfig: toolConfig ?? createDefaultToolConfig(),
|
|
147
|
+
enabledSkills: enabledSkills ?? [], // Empty = all skills
|
|
148
|
+
modelTier: modelTier ?? 'balanced', // Default tier
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Tracker (Coordinator Mode)
|
|
3
|
+
*
|
|
4
|
+
* Tracks active delegations from the coordinator to background specialists.
|
|
5
|
+
* Maintains a completion event queue so the coordinator can be notified
|
|
6
|
+
* when specialists finish their work.
|
|
7
|
+
*/
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
export type DelegationStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
10
|
+
export interface Delegation {
|
|
11
|
+
/** Unique delegation ID (del_<uuid>) */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Agent that initiated the delegation (always 'default' for now) */
|
|
14
|
+
coordinatorId: string;
|
|
15
|
+
/** Target specialist agent ID (e.g., 'arch', 'dev', 'qa') */
|
|
16
|
+
targetAgentId: string;
|
|
17
|
+
/** Task description sent to the specialist */
|
|
18
|
+
task: string;
|
|
19
|
+
/** What the specialist is expected to produce */
|
|
20
|
+
expectedOutput?: string;
|
|
21
|
+
/** Associated todo index (if any) */
|
|
22
|
+
todoIndex?: number;
|
|
23
|
+
/** Current status */
|
|
24
|
+
status: DelegationStatus;
|
|
25
|
+
/** Result (set on completion or failure) */
|
|
26
|
+
result?: DelegationResult;
|
|
27
|
+
/** When the delegation was created */
|
|
28
|
+
createdAt: Date;
|
|
29
|
+
/** When the delegation completed */
|
|
30
|
+
completedAt?: Date;
|
|
31
|
+
}
|
|
32
|
+
export interface DelegationResult {
|
|
33
|
+
/** Whether the task completed successfully */
|
|
34
|
+
success: boolean;
|
|
35
|
+
/** Brief outcome summary */
|
|
36
|
+
summary: string;
|
|
37
|
+
/** Artifact IDs created by the specialist */
|
|
38
|
+
artifactIds: string[];
|
|
39
|
+
/** Error message (if failed) */
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CompletionEvent {
|
|
43
|
+
/** The delegation ID */
|
|
44
|
+
delegationId: string;
|
|
45
|
+
/** The specialist that completed the work */
|
|
46
|
+
agentId: string;
|
|
47
|
+
/** Completion status */
|
|
48
|
+
status: 'completed' | 'failed';
|
|
49
|
+
/** Result details */
|
|
50
|
+
result: DelegationResult;
|
|
51
|
+
/** When the event was generated */
|
|
52
|
+
timestamp: Date;
|
|
53
|
+
}
|
|
54
|
+
export interface CreateDelegationOptions {
|
|
55
|
+
/** Agent that initiated the delegation */
|
|
56
|
+
coordinatorId: string;
|
|
57
|
+
/** Target specialist agent ID */
|
|
58
|
+
targetAgentId: string;
|
|
59
|
+
/** Task description */
|
|
60
|
+
task: string;
|
|
61
|
+
/** Expected output description */
|
|
62
|
+
expectedOutput?: string;
|
|
63
|
+
/** Associated todo index */
|
|
64
|
+
todoIndex?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface DelegationStats {
|
|
67
|
+
total: number;
|
|
68
|
+
pending: number;
|
|
69
|
+
running: number;
|
|
70
|
+
completed: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
cancelled: number;
|
|
73
|
+
}
|
|
74
|
+
export interface DelegationTrackerEvents {
|
|
75
|
+
/** Emitted when a new delegation is created */
|
|
76
|
+
'delegation-created': (delegation: Delegation) => void;
|
|
77
|
+
/** Emitted when a delegation completes successfully */
|
|
78
|
+
'delegation-completed': (event: CompletionEvent) => void;
|
|
79
|
+
/** Emitted when a delegation fails */
|
|
80
|
+
'delegation-failed': (event: CompletionEvent) => void;
|
|
81
|
+
/** Emitted when delegation counts change */
|
|
82
|
+
'count-changed': (stats: DelegationStats) => void;
|
|
83
|
+
}
|
|
84
|
+
export declare class DelegationTracker extends EventEmitter {
|
|
85
|
+
private readonly delegations;
|
|
86
|
+
private readonly completionQueue;
|
|
87
|
+
/**
|
|
88
|
+
* Create a new delegation.
|
|
89
|
+
*/
|
|
90
|
+
create(options: CreateDelegationOptions): Delegation;
|
|
91
|
+
/**
|
|
92
|
+
* Update a delegation's status.
|
|
93
|
+
*/
|
|
94
|
+
updateStatus(id: string, status: DelegationStatus): void;
|
|
95
|
+
/**
|
|
96
|
+
* Mark a delegation as completed with a result.
|
|
97
|
+
*/
|
|
98
|
+
complete(id: string, result: DelegationResult): void;
|
|
99
|
+
/**
|
|
100
|
+
* Mark a delegation as failed.
|
|
101
|
+
*/
|
|
102
|
+
fail(id: string, error: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Mark a delegation as cancelled.
|
|
105
|
+
*/
|
|
106
|
+
cancel(id: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Cancel all active delegations for a specific agent.
|
|
109
|
+
*/
|
|
110
|
+
cancelAllForAgent(agentId: string): number;
|
|
111
|
+
/**
|
|
112
|
+
* Get a delegation by ID.
|
|
113
|
+
*/
|
|
114
|
+
getDelegation(id: string): Delegation | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Get all delegations targeting a specific agent.
|
|
117
|
+
*/
|
|
118
|
+
getByAgent(agentId: string): Delegation[];
|
|
119
|
+
/**
|
|
120
|
+
* Get all active (pending or running) delegations.
|
|
121
|
+
*/
|
|
122
|
+
getActive(): Delegation[];
|
|
123
|
+
/**
|
|
124
|
+
* Get all delegations.
|
|
125
|
+
*/
|
|
126
|
+
getAll(): Delegation[];
|
|
127
|
+
/**
|
|
128
|
+
* Check if there are pending completion events.
|
|
129
|
+
*/
|
|
130
|
+
hasCompletionEvents(): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Drain all completion events (removes them from queue).
|
|
133
|
+
*/
|
|
134
|
+
drainCompletionEvents(): CompletionEvent[];
|
|
135
|
+
/**
|
|
136
|
+
* Peek at completion events without removing them.
|
|
137
|
+
*/
|
|
138
|
+
peekCompletionEvents(): CompletionEvent[];
|
|
139
|
+
/**
|
|
140
|
+
* Get delegation statistics.
|
|
141
|
+
*/
|
|
142
|
+
getStats(): DelegationStats;
|
|
143
|
+
/**
|
|
144
|
+
* Clear all delegations and completion events.
|
|
145
|
+
*/
|
|
146
|
+
clear(): void;
|
|
147
|
+
}
|