@compilr-dev/cli 0.6.1 → 0.6.3
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/CHANGELOG.md +104 -0
- package/README.md +12 -0
- package/dist/commands-v2/handlers/core.js +2 -2
- package/dist/commands-v2/handlers/index.d.ts +1 -0
- package/dist/commands-v2/handlers/index.js +5 -2
- package/dist/commands-v2/handlers/perf.d.ts +9 -0
- package/dist/commands-v2/handlers/perf.js +66 -0
- package/dist/commands-v2/handlers/project.js +2 -3
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +14 -8
- package/dist/repl-v2.js +5 -1
- package/dist/session/project-session-manager.js +1 -1
- package/dist/slash-autocomplete.js +18 -16
- package/dist/tabbed-menu.js +8 -7
- package/dist/ui/base/overlay-base.js +2 -1
- package/dist/ui/overlay/impl/artifact-detail-overlay-v2.js +12 -7
- package/dist/ui/overlay/impl/config-overlay-v2.js +2 -2
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +12 -8
- package/dist/ui/overlay/impl/pending-overlay-v2.js +4 -1
- package/dist/ui/overlay-manager.js +7 -6
- package/dist/ui/terminal-render-item.js +2 -1
- package/dist/ui/terminal-renderer.js +1 -2
- package/dist/ui/terminal-ui.js +6 -4
- package/dist/ui/terminal.d.ts +9 -0
- package/dist/ui/terminal.js +28 -15
- package/dist/utils/update-checker.d.ts +6 -1
- package/dist/utils/update-checker.js +16 -1
- package/package.json +5 -4
- package/dist/.tsbuildinfo.app +0 -1
- package/dist/.tsbuildinfo.data +0 -1
- package/dist/.tsbuildinfo.domain +0 -1
- package/dist/.tsbuildinfo.foundation +0 -1
- package/dist/guide/guide-content.d.ts +0 -23
- package/dist/guide/guide-content.js +0 -196
- package/dist/multi-agent/activity.d.ts +0 -21
- package/dist/multi-agent/activity.js +0 -34
- package/dist/multi-agent/agent-selection.d.ts +0 -55
- package/dist/multi-agent/agent-selection.js +0 -90
- package/dist/multi-agent/artifacts.d.ts +0 -197
- package/dist/multi-agent/artifacts.js +0 -379
- package/dist/multi-agent/collision-utils.d.ts +0 -16
- package/dist/multi-agent/collision-utils.js +0 -28
- package/dist/multi-agent/context-resolver.d.ts +0 -97
- package/dist/multi-agent/context-resolver.js +0 -316
- package/dist/multi-agent/mention-parser.d.ts +0 -64
- package/dist/multi-agent/mention-parser.js +0 -146
- package/dist/multi-agent/shared-context.d.ts +0 -293
- package/dist/multi-agent/shared-context.js +0 -671
- package/dist/multi-agent/skill-requirements.d.ts +0 -66
- package/dist/multi-agent/skill-requirements.js +0 -178
- package/dist/multi-agent/task-assignment.d.ts +0 -69
- package/dist/multi-agent/task-assignment.js +0 -123
- package/dist/multi-agent/task-suggestion.d.ts +0 -31
- package/dist/multi-agent/task-suggestion.js +0 -72
- package/dist/multi-agent/team-agent.d.ts +0 -201
- package/dist/multi-agent/team-agent.js +0 -488
- package/dist/multi-agent/team.d.ts +0 -286
- package/dist/multi-agent/team.js +0 -610
- package/dist/multi-agent/tool-config.d.ts +0 -110
- package/dist/multi-agent/tool-config.js +0 -661
- package/dist/multi-agent/types.d.ts +0 -211
- package/dist/multi-agent/types.js +0 -617
- package/dist/tools/guide-tool.d.ts +0 -12
- package/dist/tools/guide-tool.js +0 -59
|
@@ -1,316 +0,0 @@
|
|
|
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
|
-
// Token counting via tiktoken
|
|
20
|
-
import { estimateTokens } from '../utils/token-tracker.js';
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// ContextResolver
|
|
23
|
-
// =============================================================================
|
|
24
|
-
/**
|
|
25
|
-
* Resolve mentions to actual content
|
|
26
|
-
*/
|
|
27
|
-
export class ContextResolver {
|
|
28
|
-
team;
|
|
29
|
-
artifactStore;
|
|
30
|
-
constructor(team, artifactStore) {
|
|
31
|
-
this.team = team;
|
|
32
|
-
this.artifactStore = artifactStore;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve multiple mentions
|
|
36
|
-
*/
|
|
37
|
-
resolveAll(mentions, options = {}) {
|
|
38
|
-
const maxTotal = options.maxTotalTokens ?? DEFAULT_MAX_TOTAL_TOKENS;
|
|
39
|
-
const perMention = options.maxTokensPerMention ?? DEFAULT_MAX_TOKENS_PER_MENTION;
|
|
40
|
-
// Calculate budget per mention
|
|
41
|
-
const budgetPerMention = Math.min(perMention, Math.floor(maxTotal / mentions.length));
|
|
42
|
-
const results = new Map();
|
|
43
|
-
let totalTokens = 0;
|
|
44
|
-
for (const mention of mentions) {
|
|
45
|
-
// Skip if we're out of budget
|
|
46
|
-
if (totalTokens >= maxTotal) {
|
|
47
|
-
results.set(mention.agentId, {
|
|
48
|
-
mention,
|
|
49
|
-
resolved: false,
|
|
50
|
-
source: 'not_found',
|
|
51
|
-
sourceName: 'budget exceeded',
|
|
52
|
-
content: '',
|
|
53
|
-
tokenCount: 0,
|
|
54
|
-
});
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const remainingBudget = Math.min(budgetPerMention, maxTotal - totalTokens);
|
|
58
|
-
const resolved = this.resolveMention(mention, {
|
|
59
|
-
...options,
|
|
60
|
-
maxTokensPerMention: remainingBudget,
|
|
61
|
-
});
|
|
62
|
-
results.set(mention.agentId, resolved);
|
|
63
|
-
totalTokens += resolved.tokenCount;
|
|
64
|
-
}
|
|
65
|
-
return results;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Resolve a single mention
|
|
69
|
-
*/
|
|
70
|
-
resolveMention(mention, options = {}) {
|
|
71
|
-
const maxTokens = options.maxTokensPerMention ?? DEFAULT_MAX_TOKENS_PER_MENTION;
|
|
72
|
-
// 1. Try to find matching artifact
|
|
73
|
-
const artifactResult = this.resolveFromArtifacts(mention, maxTokens, options);
|
|
74
|
-
if (artifactResult) {
|
|
75
|
-
return artifactResult;
|
|
76
|
-
}
|
|
77
|
-
// 2. Try to extract from conversation history
|
|
78
|
-
const historyResult = this.resolveFromHistory(mention, maxTokens, options);
|
|
79
|
-
if (historyResult) {
|
|
80
|
-
return historyResult;
|
|
81
|
-
}
|
|
82
|
-
// 3. Not found
|
|
83
|
-
return {
|
|
84
|
-
mention,
|
|
85
|
-
resolved: false,
|
|
86
|
-
source: 'not_found',
|
|
87
|
-
sourceName: 'no relevant content found',
|
|
88
|
-
content: '',
|
|
89
|
-
tokenCount: 0,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resolve from artifacts
|
|
94
|
-
*/
|
|
95
|
-
resolveFromArtifacts(mention, maxTokens, options) {
|
|
96
|
-
const { agentId, context } = mention;
|
|
97
|
-
// Get artifacts by this agent
|
|
98
|
-
const agentArtifacts = this.artifactStore.listByAgent(agentId);
|
|
99
|
-
if (agentArtifacts.length === 0) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
// Sort artifacts by most recent first
|
|
103
|
-
const sortedArtifacts = agentArtifacts
|
|
104
|
-
.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
105
|
-
// Find matching artifact based on context hint
|
|
106
|
-
let matchedArtifact;
|
|
107
|
-
if (context) {
|
|
108
|
-
// Search by name or content
|
|
109
|
-
const searchResults = this.artifactStore.search(context);
|
|
110
|
-
const searchMatch = searchResults.find(a => a.agent === agentId);
|
|
111
|
-
// Use search match if found, otherwise fall back to most recent
|
|
112
|
-
matchedArtifact = searchMatch ?? sortedArtifacts[0];
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// No context hint - use most recent artifact
|
|
116
|
-
matchedArtifact = sortedArtifacts[0];
|
|
117
|
-
}
|
|
118
|
-
// Format artifact content
|
|
119
|
-
let content;
|
|
120
|
-
let tokenCount;
|
|
121
|
-
if (options.includeFullArtifact) {
|
|
122
|
-
content = this.formatArtifactFull(matchedArtifact);
|
|
123
|
-
tokenCount = estimateTokens(content);
|
|
124
|
-
// If over budget, fall back to summary
|
|
125
|
-
if (tokenCount > maxTokens) {
|
|
126
|
-
content = this.formatArtifactSummary(matchedArtifact);
|
|
127
|
-
tokenCount = estimateTokens(content);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
// Start with summary, include full if within budget
|
|
132
|
-
const summary = this.formatArtifactSummary(matchedArtifact);
|
|
133
|
-
const full = this.formatArtifactFull(matchedArtifact);
|
|
134
|
-
if (estimateTokens(full) <= maxTokens) {
|
|
135
|
-
content = full;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
content = summary;
|
|
139
|
-
}
|
|
140
|
-
tokenCount = estimateTokens(content);
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
mention,
|
|
144
|
-
resolved: true,
|
|
145
|
-
source: 'artifact',
|
|
146
|
-
sourceName: matchedArtifact.name,
|
|
147
|
-
content,
|
|
148
|
-
tokenCount,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Resolve from conversation history
|
|
153
|
-
*/
|
|
154
|
-
resolveFromHistory(mention, maxTokens, options) {
|
|
155
|
-
const { agentId, context } = mention;
|
|
156
|
-
const maxMessages = options.maxHistoryMessages ?? DEFAULT_MAX_HISTORY_MESSAGES;
|
|
157
|
-
// Get the team agent
|
|
158
|
-
const teamAgent = this.team.get(agentId);
|
|
159
|
-
if (!teamAgent) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
// Get the underlying agent
|
|
163
|
-
const agent = teamAgent.agent;
|
|
164
|
-
if (!agent) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
// Get recent messages from the agent's history
|
|
168
|
-
const state = agent.serialize();
|
|
169
|
-
const messages = state.messages;
|
|
170
|
-
if (messages.length === 0) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
// Get assistant messages (agent's responses)
|
|
174
|
-
const assistantMessages = messages
|
|
175
|
-
.filter((m) => m.role === 'assistant')
|
|
176
|
-
.slice(-maxMessages);
|
|
177
|
-
if (assistantMessages.length === 0) {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
// Extract relevant content
|
|
181
|
-
let relevantContent;
|
|
182
|
-
if (context) {
|
|
183
|
-
// Search for context in messages
|
|
184
|
-
relevantContent = this.extractRelevantContent(assistantMessages, context, maxTokens);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
// Use most recent message
|
|
188
|
-
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
189
|
-
relevantContent = this.formatMessage(lastMessage);
|
|
190
|
-
}
|
|
191
|
-
if (!relevantContent) {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
// Truncate if needed
|
|
195
|
-
const tokenCount = estimateTokens(relevantContent);
|
|
196
|
-
if (tokenCount > maxTokens) {
|
|
197
|
-
relevantContent = this.truncateToTokens(relevantContent, maxTokens);
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
mention,
|
|
201
|
-
resolved: true,
|
|
202
|
-
source: 'history',
|
|
203
|
-
sourceName: 'recent conversation',
|
|
204
|
-
content: relevantContent,
|
|
205
|
-
tokenCount: estimateTokens(relevantContent),
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Extract relevant content from messages based on context hint
|
|
210
|
-
*/
|
|
211
|
-
extractRelevantContent(messages, context, maxTokens) {
|
|
212
|
-
const keywords = context.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
213
|
-
// Score messages by relevance
|
|
214
|
-
const scored = messages.map(msg => {
|
|
215
|
-
const content = this.formatMessage(msg);
|
|
216
|
-
const contentLower = content.toLowerCase();
|
|
217
|
-
let score = 0;
|
|
218
|
-
for (const keyword of keywords) {
|
|
219
|
-
if (contentLower.includes(keyword)) {
|
|
220
|
-
score += 1;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return { content, score };
|
|
224
|
-
});
|
|
225
|
-
// Sort by score (highest first)
|
|
226
|
-
scored.sort((a, b) => b.score - a.score);
|
|
227
|
-
// Take best matches within token budget
|
|
228
|
-
const results = [];
|
|
229
|
-
let totalTokens = 0;
|
|
230
|
-
for (const { content, score } of scored) {
|
|
231
|
-
if (score === 0)
|
|
232
|
-
continue;
|
|
233
|
-
const tokens = estimateTokens(content);
|
|
234
|
-
if (totalTokens + tokens > maxTokens) {
|
|
235
|
-
// Try to include truncated version
|
|
236
|
-
const remaining = maxTokens - totalTokens;
|
|
237
|
-
if (remaining > 100) {
|
|
238
|
-
results.push(this.truncateToTokens(content, remaining));
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
results.push(content);
|
|
243
|
-
totalTokens += tokens;
|
|
244
|
-
}
|
|
245
|
-
return results.join('\n\n---\n\n');
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Format a message for injection
|
|
249
|
-
*/
|
|
250
|
-
formatMessage(message) {
|
|
251
|
-
if (typeof message.content === 'string') {
|
|
252
|
-
return message.content;
|
|
253
|
-
}
|
|
254
|
-
// Handle array content (Claude format)
|
|
255
|
-
if (Array.isArray(message.content)) {
|
|
256
|
-
const blocks = message.content;
|
|
257
|
-
return blocks
|
|
258
|
-
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
259
|
-
.map(block => block.text)
|
|
260
|
-
.join('\n');
|
|
261
|
-
}
|
|
262
|
-
return String(message.content);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Format artifact with full content
|
|
266
|
-
*/
|
|
267
|
-
formatArtifactFull(artifact) {
|
|
268
|
-
return [
|
|
269
|
-
`**${artifact.name}** (${artifact.type})`,
|
|
270
|
-
`By: $${artifact.agent} | Updated: ${artifact.updatedAt.toLocaleDateString()}`,
|
|
271
|
-
'',
|
|
272
|
-
artifact.content,
|
|
273
|
-
].join('\n');
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Format artifact with summary only
|
|
277
|
-
*/
|
|
278
|
-
formatArtifactSummary(artifact) {
|
|
279
|
-
return [
|
|
280
|
-
`**${artifact.name}** (${artifact.type})`,
|
|
281
|
-
`By: $${artifact.agent} | Updated: ${artifact.updatedAt.toLocaleDateString()}`,
|
|
282
|
-
'',
|
|
283
|
-
`Summary: ${artifact.summary}`,
|
|
284
|
-
'',
|
|
285
|
-
`[Full content available via: /artifact view "${artifact.name}"]`,
|
|
286
|
-
].join('\n');
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Truncate content to approximate token count
|
|
290
|
-
*/
|
|
291
|
-
truncateToTokens(content, maxTokens) {
|
|
292
|
-
const maxChars = maxTokens * 4;
|
|
293
|
-
if (content.length <= maxChars) {
|
|
294
|
-
return content;
|
|
295
|
-
}
|
|
296
|
-
// Truncate at word boundary
|
|
297
|
-
const truncated = content.slice(0, maxChars);
|
|
298
|
-
const lastSpace = truncated.lastIndexOf(' ');
|
|
299
|
-
if (lastSpace > maxChars * 0.8) {
|
|
300
|
-
return truncated.slice(0, lastSpace) + '...';
|
|
301
|
-
}
|
|
302
|
-
return truncated + '...';
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Build a context map from resolved mentions
|
|
307
|
-
*/
|
|
308
|
-
export function buildContextMap(resolved) {
|
|
309
|
-
const contextMap = new Map();
|
|
310
|
-
for (const [agentId, resolution] of resolved) {
|
|
311
|
-
if (resolution.resolved && resolution.content) {
|
|
312
|
-
contextMap.set(agentId, resolution.content);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return contextMap;
|
|
316
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MentionParser - Parse $agent references in user input
|
|
3
|
-
*
|
|
4
|
-
* Two types of $agent usage:
|
|
5
|
-
* 1. Switch: "$arch message" at start of input → switch to arch, send message
|
|
6
|
-
* 2. Reference: "what did $arch propose" mid-sentence → inject arch's context
|
|
7
|
-
*
|
|
8
|
-
* Detection rule: $name at START of trimmed input = switch. Otherwise = reference.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* A parsed mention in the input
|
|
12
|
-
*/
|
|
13
|
-
export interface ParsedMention {
|
|
14
|
-
/** Agent ID without $ prefix */
|
|
15
|
-
agentId: string;
|
|
16
|
-
/** Position in original input */
|
|
17
|
-
startIndex: number;
|
|
18
|
-
endIndex: number;
|
|
19
|
-
/** The full match including $ prefix */
|
|
20
|
-
raw: string;
|
|
21
|
-
/** Optional query/context after the agent name */
|
|
22
|
-
context?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Result of parsing user input for mentions
|
|
26
|
-
*/
|
|
27
|
-
export interface ParsedInput {
|
|
28
|
-
/** Original input text */
|
|
29
|
-
original: string;
|
|
30
|
-
/** Whether the input starts with a switch ($agent at beginning) */
|
|
31
|
-
hasSwitch: boolean;
|
|
32
|
-
/** The switch target agent (if hasSwitch is true) */
|
|
33
|
-
switchTarget?: string;
|
|
34
|
-
/** Message to send after switching (if hasSwitch is true) */
|
|
35
|
-
messageAfterSwitch?: string;
|
|
36
|
-
/** All reference mentions (mid-sentence $agent) */
|
|
37
|
-
references: ParsedMention[];
|
|
38
|
-
/** Input with mentions replaced by placeholders for context injection */
|
|
39
|
-
normalizedInput: string;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Parse user input for $agent mentions
|
|
43
|
-
*
|
|
44
|
-
* @param input - User input string
|
|
45
|
-
* @param validAgents - Set of valid agent IDs (for validation)
|
|
46
|
-
* @returns Parsed input with mentions identified
|
|
47
|
-
*/
|
|
48
|
-
export declare function parseInputForMentions(input: string, validAgents?: Set<string>): ParsedInput;
|
|
49
|
-
/**
|
|
50
|
-
* Get unique agent IDs from references
|
|
51
|
-
*/
|
|
52
|
-
export declare function getReferencedAgents(parsed: ParsedInput): string[];
|
|
53
|
-
/**
|
|
54
|
-
* Check if input contains any references (not just switch)
|
|
55
|
-
*/
|
|
56
|
-
export declare function hasReferences(parsed: ParsedInput): boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Build a message with context injected for mentions
|
|
59
|
-
*
|
|
60
|
-
* @param parsed - Parsed input
|
|
61
|
-
* @param contextMap - Map of agentId → resolved context string
|
|
62
|
-
* @returns Message with context prepended or injected
|
|
63
|
-
*/
|
|
64
|
-
export declare function buildMessageWithContext(parsed: ParsedInput, contextMap: Map<string, string>): string;
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MentionParser - Parse $agent references in user input
|
|
3
|
-
*
|
|
4
|
-
* Two types of $agent usage:
|
|
5
|
-
* 1. Switch: "$arch message" at start of input → switch to arch, send message
|
|
6
|
-
* 2. Reference: "what did $arch propose" mid-sentence → inject arch's context
|
|
7
|
-
*
|
|
8
|
-
* Detection rule: $name at START of trimmed input = switch. Otherwise = reference.
|
|
9
|
-
*/
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Constants
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Match $agentname pattern - alphanumeric and underscore/hyphen
|
|
14
|
-
const MENTION_PATTERN = /\$([a-zA-Z][a-zA-Z0-9_-]*)/g;
|
|
15
|
-
// Match $agent at start of input (for switch detection)
|
|
16
|
-
const SWITCH_PATTERN = /^\$([a-zA-Z][a-zA-Z0-9_-]*)\s*/;
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Parser
|
|
19
|
-
// =============================================================================
|
|
20
|
-
/**
|
|
21
|
-
* Parse user input for $agent mentions
|
|
22
|
-
*
|
|
23
|
-
* @param input - User input string
|
|
24
|
-
* @param validAgents - Set of valid agent IDs (for validation)
|
|
25
|
-
* @returns Parsed input with mentions identified
|
|
26
|
-
*/
|
|
27
|
-
export function parseInputForMentions(input, validAgents) {
|
|
28
|
-
const trimmed = input.trim();
|
|
29
|
-
// Check for switch pattern at start
|
|
30
|
-
const switchMatch = trimmed.match(SWITCH_PATTERN);
|
|
31
|
-
let hasSwitch = false;
|
|
32
|
-
let switchTarget;
|
|
33
|
-
let messageAfterSwitch;
|
|
34
|
-
let searchStart = 0;
|
|
35
|
-
if (switchMatch) {
|
|
36
|
-
const agentId = switchMatch[1].toLowerCase();
|
|
37
|
-
// Only treat as switch if it's a valid agent
|
|
38
|
-
if (!validAgents || validAgents.has(agentId)) {
|
|
39
|
-
hasSwitch = true;
|
|
40
|
-
switchTarget = agentId;
|
|
41
|
-
messageAfterSwitch = trimmed.slice(switchMatch[0].length).trim();
|
|
42
|
-
searchStart = switchMatch[0].length; // Skip switch when looking for references
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// Find all reference mentions (excluding the switch)
|
|
46
|
-
const references = [];
|
|
47
|
-
MENTION_PATTERN.lastIndex = searchStart;
|
|
48
|
-
let match;
|
|
49
|
-
while ((match = MENTION_PATTERN.exec(trimmed)) !== null) {
|
|
50
|
-
const agentId = match[1].toLowerCase();
|
|
51
|
-
// Skip if not a valid agent (if validation set provided)
|
|
52
|
-
if (validAgents && !validAgents.has(agentId)) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
// Skip if this is the switch mention
|
|
56
|
-
if (hasSwitch && match.index === 0) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
references.push({
|
|
60
|
-
agentId,
|
|
61
|
-
startIndex: match.index,
|
|
62
|
-
endIndex: match.index + match[0].length,
|
|
63
|
-
raw: match[0],
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
// Extract context hints for each reference
|
|
67
|
-
for (const ref of references) {
|
|
68
|
-
ref.context = extractContextHint(trimmed, ref.endIndex);
|
|
69
|
-
}
|
|
70
|
-
// Build normalized input (with mention placeholders)
|
|
71
|
-
const normalizedInput = hasSwitch ? messageAfterSwitch ?? '' : trimmed;
|
|
72
|
-
// For normalized input, we keep mentions as-is but could replace with markers
|
|
73
|
-
// This is useful for the resolver to know where to inject context
|
|
74
|
-
return {
|
|
75
|
-
original: input,
|
|
76
|
-
hasSwitch,
|
|
77
|
-
switchTarget,
|
|
78
|
-
messageAfterSwitch,
|
|
79
|
-
references,
|
|
80
|
-
normalizedInput,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Extract a context hint following a mention
|
|
85
|
-
*
|
|
86
|
-
* Examples:
|
|
87
|
-
* - "what did $arch propose for the API" → "propose for the API"
|
|
88
|
-
* - "review $qa's feedback on the PR" → "'s feedback on the PR"
|
|
89
|
-
* - "based on $arch" → undefined
|
|
90
|
-
*/
|
|
91
|
-
function extractContextHint(text, afterIndex) {
|
|
92
|
-
const remaining = text.slice(afterIndex);
|
|
93
|
-
// Common patterns that provide context:
|
|
94
|
-
// - "'s <noun>" (possessive)
|
|
95
|
-
// - " <verb> <object>" (action)
|
|
96
|
-
// - " about <topic>"
|
|
97
|
-
// - " for <topic>"
|
|
98
|
-
// - " on <topic>"
|
|
99
|
-
// Get up to end of sentence or 50 chars
|
|
100
|
-
const endMatch = remaining.match(/^[^.!?\n]{0,50}/);
|
|
101
|
-
if (!endMatch || endMatch[0].trim().length < 3) {
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
return endMatch[0].trim();
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Get unique agent IDs from references
|
|
108
|
-
*/
|
|
109
|
-
export function getReferencedAgents(parsed) {
|
|
110
|
-
const agents = new Set();
|
|
111
|
-
for (const ref of parsed.references) {
|
|
112
|
-
agents.add(ref.agentId);
|
|
113
|
-
}
|
|
114
|
-
return Array.from(agents);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Check if input contains any references (not just switch)
|
|
118
|
-
*/
|
|
119
|
-
export function hasReferences(parsed) {
|
|
120
|
-
return parsed.references.length > 0;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Build a message with context injected for mentions
|
|
124
|
-
*
|
|
125
|
-
* @param parsed - Parsed input
|
|
126
|
-
* @param contextMap - Map of agentId → resolved context string
|
|
127
|
-
* @returns Message with context prepended or injected
|
|
128
|
-
*/
|
|
129
|
-
export function buildMessageWithContext(parsed, contextMap) {
|
|
130
|
-
if (parsed.references.length === 0) {
|
|
131
|
-
return parsed.normalizedInput;
|
|
132
|
-
}
|
|
133
|
-
// Collect context blocks
|
|
134
|
-
const contextBlocks = [];
|
|
135
|
-
for (const ref of parsed.references) {
|
|
136
|
-
const context = contextMap.get(ref.agentId);
|
|
137
|
-
if (context) {
|
|
138
|
-
contextBlocks.push(`[Context from $${ref.agentId}:\n${context}\n]`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
if (contextBlocks.length === 0) {
|
|
142
|
-
return parsed.normalizedInput;
|
|
143
|
-
}
|
|
144
|
-
// Prepend context blocks to the message
|
|
145
|
-
return `${contextBlocks.join('\n\n')}\n\n${parsed.normalizedInput}`;
|
|
146
|
-
}
|