lynkr 9.9.1 → 9.10.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.
Files changed (67) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +4 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +2 -2
  7. package/scripts/validate-difficulty-classifier.js +27 -6
  8. package/src/api/providers-handler.js +0 -1
  9. package/src/api/router.js +275 -160
  10. package/src/clients/databricks.js +95 -6
  11. package/src/config/index.js +3 -43
  12. package/src/orchestrator/index.js +117 -1235
  13. package/src/orchestrator/passthrough-stream.js +382 -0
  14. package/src/orchestrator/sse-transformer.js +408 -0
  15. package/src/routing/affinity-store.js +17 -3
  16. package/src/routing/difficulty-classifier.js +52 -10
  17. package/src/routing/index.js +35 -3
  18. package/src/routing/intent-score.js +20 -2
  19. package/src/routing/session-affinity.js +8 -1
  20. package/src/routing/side-channel-detector.js +103 -0
  21. package/src/server.js +0 -46
  22. package/src/agents/context-manager.js +0 -236
  23. package/src/agents/decomposition/dispatcher.js +0 -185
  24. package/src/agents/decomposition/gate.js +0 -136
  25. package/src/agents/decomposition/index.js +0 -183
  26. package/src/agents/decomposition/model-call.js +0 -75
  27. package/src/agents/decomposition/planner.js +0 -223
  28. package/src/agents/decomposition/synthesizer.js +0 -89
  29. package/src/agents/decomposition/telemetry.js +0 -55
  30. package/src/agents/definitions/loader.js +0 -653
  31. package/src/agents/executor.js +0 -457
  32. package/src/agents/index.js +0 -165
  33. package/src/agents/parallel-coordinator.js +0 -68
  34. package/src/agents/reflector.js +0 -331
  35. package/src/agents/skillbook.js +0 -331
  36. package/src/agents/store.js +0 -259
  37. package/src/edits/index.js +0 -171
  38. package/src/indexer/babel-parser.js +0 -213
  39. package/src/indexer/index.js +0 -1629
  40. package/src/indexer/navigation/index.js +0 -32
  41. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  42. package/src/indexer/parser.js +0 -443
  43. package/src/tasks/store.js +0 -349
  44. package/src/tests/coverage.js +0 -173
  45. package/src/tests/index.js +0 -171
  46. package/src/tests/store.js +0 -213
  47. package/src/tools/agent-task.js +0 -145
  48. package/src/tools/code-mode.js +0 -304
  49. package/src/tools/decompose.js +0 -91
  50. package/src/tools/edits.js +0 -94
  51. package/src/tools/execution.js +0 -171
  52. package/src/tools/git.js +0 -1346
  53. package/src/tools/index.js +0 -306
  54. package/src/tools/indexer.js +0 -360
  55. package/src/tools/lazy-loader.js +0 -366
  56. package/src/tools/mcp-remote.js +0 -88
  57. package/src/tools/mcp.js +0 -116
  58. package/src/tools/process.js +0 -167
  59. package/src/tools/smart-selection.js +0 -180
  60. package/src/tools/stubs.js +0 -55
  61. package/src/tools/tasks.js +0 -260
  62. package/src/tools/tests.js +0 -132
  63. package/src/tools/tinyfish.js +0 -358
  64. package/src/tools/truncate.js +0 -106
  65. package/src/tools/web-client.js +0 -71
  66. package/src/tools/web.js +0 -415
  67. package/src/tools/workspace.js +0 -204
@@ -1,55 +0,0 @@
1
- /**
2
- * Decomposition telemetry + shadow mode (Phase 6).
3
- *
4
- * Appends one JSON line per decomposition decision to
5
- * data/decomposition-decisions.jsonl so the net token effect can be audited.
6
- * Because the research is clear that decomposition can COST more than it saves,
7
- * a shadow mode (TASK_DECOMPOSITION_SHADOW=true) runs the gate + records what it
8
- * WOULD have done without actually decomposing — so savings can be validated on
9
- * real traffic before enabling for real.
10
- */
11
-
12
- const fs = require("fs");
13
- const path = require("path");
14
- const logger = require("../../logger");
15
-
16
- const LOG_PATH = path.join(__dirname, "../../../data/decomposition-decisions.jsonl");
17
-
18
- function estimateTokens(text) {
19
- if (typeof text !== "string") return 0;
20
- return Math.ceil(text.length / 4);
21
- }
22
-
23
- /**
24
- * Rough net-savings estimate.
25
- * monolithic ≈ what one big context would have cost (estimated input tokens).
26
- * decomposed ≈ planning + Σ(subagent in+out) + synthesis.
27
- * Positive `savedTokens` = decomposition was cheaper.
28
- */
29
- function estimateSavings({ monolithicTokens, planUsage, dispatchStats, synthUsage }) {
30
- const decomposed =
31
- (planUsage?.inputTokens || 0) +
32
- (planUsage?.outputTokens || 0) +
33
- (dispatchStats?.inputTokens || 0) +
34
- (dispatchStats?.outputTokens || 0) +
35
- (synthUsage?.inputTokens || 0) +
36
- (synthUsage?.outputTokens || 0);
37
- return {
38
- monolithicTokens: monolithicTokens || 0,
39
- decomposedTokens: decomposed,
40
- savedTokens: (monolithicTokens || 0) - decomposed,
41
- };
42
- }
43
-
44
- function record(entry) {
45
- const line = { timestamp: Date.now(), ...entry };
46
- try {
47
- fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
48
- fs.appendFileSync(LOG_PATH, JSON.stringify(line) + "\n");
49
- } catch (err) {
50
- logger.debug({ err: err.message }, "[Decomposition] Telemetry append failed");
51
- }
52
- return line;
53
- }
54
-
55
- module.exports = { record, estimateSavings, estimateTokens, LOG_PATH };
@@ -1,653 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const yaml = require("js-yaml");
4
- const logger = require("../../logger");
5
- const Skillbook = require("../skillbook");
6
-
7
- // How often to prune low-quality learned skills. 6 hours is frequent enough to
8
- // keep skillbooks tidy on a long-running process but rare enough to be
9
- // negligible overhead. Hardcoded on purpose — not worth a config knob.
10
- const SKILL_PRUNE_INTERVAL_MS = 6 * 60 * 60 * 1000;
11
-
12
- class AgentDefinitionLoader {
13
- constructor() {
14
- this.agents = new Map();
15
- this.skillbooks = new Map(); // agentType → Skillbook
16
- this.initialized = false;
17
- this.pruneTimer = null;
18
-
19
- // Initialize synchronously for compatibility
20
- this.loadBuiltInAgentsSync();
21
- this.loadFilesystemAgents();
22
-
23
- // Load skillbooks asynchronously (non-blocking)
24
- this._loadSkillbooksAsync();
25
- }
26
-
27
- /**
28
- * Async initialization of skillbooks (doesn't block constructor)
29
- */
30
- async _loadSkillbooksAsync() {
31
- try {
32
- // Load skillbooks for all built-in agents
33
- const agentTypes = Array.from(this.agents.keys());
34
-
35
- await Promise.all(
36
- agentTypes.map(async (agentType) => {
37
- const skillbook = await Skillbook.load(agentType);
38
- this.skillbooks.set(agentType, skillbook);
39
-
40
- // Update agent system prompt with learned skills
41
- this._injectSkillsIntoPrompt(agentType);
42
- })
43
- );
44
-
45
- this.initialized = true;
46
-
47
- logger.info({
48
- agentCount: this.agents.size,
49
- skillbooksLoaded: this.skillbooks.size,
50
- totalSkills: Array.from(this.skillbooks.values())
51
- .reduce((sum, sb) => sum + sb.skills.size, 0)
52
- }, "Skillbooks loaded and injected into agents");
53
-
54
- } catch (error) {
55
- logger.error({
56
- error: error.message
57
- }, "Failed to load skillbooks");
58
- }
59
- }
60
-
61
- /**
62
- * Inject learned skills into agent system prompt
63
- */
64
- _injectSkillsIntoPrompt(agentType) {
65
- const agent = this.agents.get(agentType);
66
- const skillbook = this.skillbooks.get(agentType);
67
-
68
- if (!agent || !skillbook) return;
69
-
70
- // Store original prompt if not already stored
71
- if (!agent.originalSystemPrompt) {
72
- agent.originalSystemPrompt = agent.systemPrompt;
73
- }
74
-
75
- // Get learned skills
76
- const skillsSection = skillbook.formatForPrompt();
77
-
78
- // Inject skills into prompt (prepend to agent prompt)
79
- if (skillsSection) {
80
- agent.systemPrompt = agent.originalSystemPrompt + "\n" + skillsSection;
81
- }
82
- }
83
-
84
- /**
85
- * Get skillbook for agent type
86
- */
87
- getSkillbook(agentType) {
88
- return this.skillbooks.get(agentType);
89
- }
90
-
91
- /**
92
- * Replace an agent's skillbook with a freshly-learned instance and re-inject
93
- * its skills into the (in-memory) system prompt. Called by the executor after
94
- * a run persists new skills, so learning takes effect within the running
95
- * process instead of only after a restart. Injection rebuilds from
96
- * originalSystemPrompt, so repeated calls do not duplicate the skills block.
97
- */
98
- setSkillbook(agentType, skillbook) {
99
- if (!agentType || !skillbook) return;
100
- // Match the case-insensitive key an agent is actually stored under.
101
- const key = this.agents.has(agentType)
102
- ? agentType
103
- : Array.from(this.agents.keys()).find(
104
- k => k.toLowerCase() === String(agentType).toLowerCase()
105
- );
106
- if (!key) return;
107
-
108
- this.skillbooks.set(key, skillbook);
109
- this._injectSkillsIntoPrompt(key);
110
- }
111
-
112
- /**
113
- * Reload skillbooks and update prompts (call after learning)
114
- */
115
- async reloadSkillbooks() {
116
- await this._loadSkillbooksAsync();
117
- }
118
-
119
- /**
120
- * Prune low-quality skills from every loaded skillbook. Skillbook.prune()
121
- * only drops skills that have been tried enough times to prove they don't
122
- * help (default: useCount >= 3 && confidence < 0.2), so fresh skills are
123
- * never removed. Persists and re-injects only the skillbooks that changed.
124
- * @returns {Promise<number>} total skills pruned across all agents
125
- */
126
- async pruneSkillbooks() {
127
- let totalPruned = 0;
128
-
129
- for (const [agentType, skillbook] of this.skillbooks.entries()) {
130
- try {
131
- const pruned = skillbook.prune();
132
- if (pruned > 0) {
133
- await skillbook.save();
134
- this._injectSkillsIntoPrompt(agentType);
135
- totalPruned += pruned;
136
- }
137
- } catch (error) {
138
- logger.warn(
139
- { agentType, error: error.message },
140
- "Failed to prune skillbook"
141
- );
142
- }
143
- }
144
-
145
- if (totalPruned > 0) {
146
- logger.info({ totalPruned }, "Pruned low-quality agent skills");
147
- }
148
- return totalPruned;
149
- }
150
-
151
- /**
152
- * Start periodic skillbook pruning. Idempotent; a non-positive interval
153
- * disables pruning. The timer is unref'd so it never keeps the process alive.
154
- */
155
- startSkillPruning(intervalMs = SKILL_PRUNE_INTERVAL_MS) {
156
- if (this.pruneTimer) return; // already running
157
- if (!Number.isInteger(intervalMs) || intervalMs <= 0) {
158
- logger.debug({ intervalMs }, "Skillbook pruning disabled");
159
- return;
160
- }
161
-
162
- this.pruneTimer = setInterval(() => {
163
- this.pruneSkillbooks().catch((error) => {
164
- logger.warn({ error: error.message }, "Skillbook pruning failed");
165
- });
166
- }, intervalMs);
167
- this.pruneTimer.unref();
168
-
169
- logger.info({ intervalMs }, "Skillbook pruning started");
170
- }
171
-
172
- /**
173
- * Stop periodic skillbook pruning (for shutdown / tests).
174
- */
175
- stopSkillPruning() {
176
- if (this.pruneTimer) {
177
- clearInterval(this.pruneTimer);
178
- this.pruneTimer = null;
179
- }
180
- }
181
-
182
- /**
183
- * Load built-in agents (Explore, Plan, General)
184
- */
185
- loadBuiltInAgentsSync() {
186
- // Explore Agent
187
- this.agents.set("Explore", {
188
- name: "Explore",
189
- description: "Fast codebase exploration for finding files, searching code, and understanding architecture. MUST BE USED when user asks 'where is', 'find all', 'how does X work', or needs to search codebase.",
190
- systemPrompt: `You are a fast codebase exploration agent.
191
-
192
- Your role:
193
- - Search codebases efficiently using Glob, Grep, Read
194
- - Find files, functions, patterns
195
- - Answer questions about code location and structure
196
- - Provide concise, actionable findings
197
-
198
- Tools available: Glob, Grep, Read, workspace_search, workspace_symbol_search
199
-
200
- IMPORTANT RULES:
201
- 1. You CANNOT spawn subagents (no Task tool)
202
- 2. Return ONLY a summary of findings (not all intermediate steps)
203
- 3. Be efficient - aim for 5-8 tool calls maximum
204
- 4. Include specific file paths and line numbers in your final answer
205
- 5. When done, provide clear summary starting with "EXPLORATION COMPLETE:"
206
-
207
- Work autonomously. Do not ask questions.`,
208
- allowedTools: [
209
- "Glob",
210
- "Grep",
211
- "Read"
212
- ],
213
- model: "haiku", // Fast, cheap
214
- maxSteps: 25, // Increased for thorough exploration
215
- builtIn: true
216
- });
217
-
218
- // Plan Agent
219
- this.agents.set("Plan", {
220
- name: "Plan",
221
- description: "Design implementation plans for features. MUST BE USED when user asks 'how should I implement', 'plan for adding', 'design approach for', or needs architectural guidance.",
222
- systemPrompt: `You are an implementation planning agent.
223
-
224
- Your role:
225
- - Understand existing codebase architecture
226
- - Design step-by-step implementation plans
227
- - Identify files to modify
228
- - Consider edge cases and testing
229
-
230
- Tools available: All exploration tools
231
-
232
- IMPORTANT RULES:
233
- 1. You CANNOT spawn subagents (no Task tool)
234
- 2. Explore codebase first to understand patterns
235
- 3. Create detailed, numbered implementation steps
236
- 4. Return ONLY the final plan (not exploration details)
237
- 5. When done, provide plan starting with "IMPLEMENTATION PLAN:"
238
-
239
- Maximum 10 exploration steps, then generate plan.
240
- Work autonomously. Make reasonable assumptions.`,
241
- allowedTools: [
242
- "Glob",
243
- "Grep",
244
- "Read"
245
- ],
246
- model: "sonnet", // Needs reasoning
247
- maxSteps: 15,
248
- builtIn: true
249
- });
250
-
251
- // General-Purpose Agent
252
- this.agents.set("general-purpose", {
253
- name: "general-purpose",
254
- description: "Complex multi-step tasks requiring file modifications, refactoring, or implementing features. MUST BE USED for 'refactor', 'implement', 'add feature', 'update all', or complex changes.",
255
- systemPrompt: `You are a general-purpose agent for complex tasks.
256
-
257
- Your role:
258
- - Execute multi-step implementations
259
- - Modify files, refactor code, add features
260
- - Use all available tools to complete tasks
261
- - Handle errors and adapt
262
-
263
- Tools available: ALL TOOLS (Read, Write, Edit, Bash, Glob, Grep, etc.)
264
-
265
- IMPORTANT RULES:
266
- 1. You CANNOT spawn subagents (no Task tool)
267
- 2. Break complex tasks into steps
268
- 3. Execute autonomously
269
- 4. Return ONLY summary of changes (not all tool output)
270
- 5. When done, provide summary starting with "TASK COMPLETE:"
271
-
272
- Maximum 20 steps.
273
- Work autonomously. Complete the task.`,
274
- allowedTools: [], // Empty = all tools allowed
275
- model: "sonnet",
276
- maxSteps: 20,
277
- builtIn: true
278
- });
279
-
280
- // Test Agent
281
- this.agents.set("Test", {
282
- name: "Test",
283
- description: "Write tests, fix test failures, improve test coverage. MUST BE USED for 'write tests for', 'fix failing tests', 'improve coverage', or test-related tasks.",
284
- systemPrompt: `You are a test writing and fixing agent.
285
-
286
- Your role:
287
- - Write comprehensive unit tests
288
- - Fix failing tests
289
- - Improve test coverage
290
- - Use appropriate testing frameworks and patterns
291
-
292
- Tools available: Read, Write, Edit, Bash, Glob, Grep
293
-
294
- IMPORTANT RULES:
295
- 1. You CANNOT spawn subagents (no Task tool)
296
- 2. Explore existing tests to match style and framework
297
- 3. Write tests that cover edge cases
298
- 4. Run tests with Bash to verify they pass
299
- 5. When done, provide summary starting with "TESTS COMPLETE:"
300
-
301
- Best practices:
302
- - Follow existing test patterns in codebase
303
- - Use descriptive test names
304
- - Test edge cases and error conditions
305
- - Mock external dependencies
306
- - Aim for clear, maintainable tests
307
-
308
- Maximum 20 steps.
309
- Work autonomously.`,
310
- allowedTools: [
311
- "Read",
312
- "Write",
313
- "Edit",
314
- "Bash",
315
- "Glob",
316
- "Grep"
317
- ],
318
- model: "sonnet",
319
- maxSteps: 20,
320
- builtIn: true
321
- });
322
-
323
- // Debug Agent
324
- this.agents.set("Debug", {
325
- name: "Debug",
326
- description: "Investigate bugs, analyze logs, find root causes. MUST BE USED for 'debug', 'why is X failing', 'investigate error', or troubleshooting tasks.",
327
- systemPrompt: `You are a debugging agent specialized in finding root causes.
328
-
329
- Your role:
330
- - Investigate bugs systematically
331
- - Analyze error messages and logs
332
- - Trace code execution paths
333
- - Identify root causes
334
- - Suggest fixes
335
-
336
- Tools available: Read, Grep, Bash, Glob
337
-
338
- IMPORTANT RULES:
339
- 1. You CANNOT spawn subagents (no Task tool)
340
- 2. Form hypotheses and test them systematically
341
- 3. Read relevant code and logs
342
- 4. Use Grep to search for error patterns
343
- 5. When done, provide analysis starting with "DEBUG COMPLETE:"
344
-
345
- Debugging approach:
346
- - Understand the error message/symptom
347
- - Locate relevant code
348
- - Trace execution path
349
- - Identify root cause
350
- - Explain findings clearly
351
- - Suggest specific fixes
352
-
353
- Maximum 15 steps.
354
- Work autonomously.`,
355
- allowedTools: [
356
- "Read",
357
- "Grep",
358
- "Bash",
359
- "Glob"
360
- ],
361
- model: "sonnet",
362
- maxSteps: 15,
363
- builtIn: true
364
- });
365
-
366
- // Fix Agent
367
- this.agents.set("Fix", {
368
- name: "Fix",
369
- description: "Fix specific bugs with minimal code changes. MUST BE USED for 'fix bug', 'fix error', 'patch', or targeted bug fixes.",
370
- systemPrompt: `You are a bug fixing agent focused on surgical fixes.
371
-
372
- Your role:
373
- - Fix specific bugs with minimal changes
374
- - Preserve existing behavior
375
- - Make targeted, safe edits
376
- - Verify fixes work
377
-
378
- Tools available: Read, Edit, Bash
379
-
380
- IMPORTANT RULES:
381
- 1. You CANNOT spawn subagents (no Task tool)
382
- 2. Read the buggy code carefully
383
- 3. Make minimal, surgical changes
384
- 4. Verify fix doesn't break other code
385
- 5. When done, provide summary starting with "FIX COMPLETE:"
386
-
387
- Fixing principles:
388
- - Change only what's necessary
389
- - Preserve surrounding code
390
- - Don't refactor while fixing
391
- - Test the fix if possible
392
- - Explain what you changed and why
393
-
394
- Maximum 10 steps.
395
- Work autonomously.`,
396
- allowedTools: [
397
- "Read",
398
- "Edit",
399
- "Bash"
400
- ],
401
- model: "sonnet",
402
- maxSteps: 10,
403
- builtIn: true
404
- });
405
-
406
- // Refactor Agent
407
- this.agents.set("Refactor", {
408
- name: "Refactor",
409
- description: "Code refactoring, improving structure, eliminating duplication. MUST BE USED for 'refactor', 'clean up code', 'remove duplication', 'improve structure', or code quality improvements.",
410
- systemPrompt: `You are a code refactoring agent focused on improving code quality.
411
-
412
- Your role:
413
- - Refactor code to improve structure and readability
414
- - Eliminate code duplication (DRY principle)
415
- - Improve naming and organization
416
- - Preserve existing behavior exactly
417
- - Make targeted, safe improvements
418
-
419
- Tools available: Read, Edit, Grep, Glob
420
-
421
- IMPORTANT RULES:
422
- 1. You CANNOT spawn subagents (no Task tool)
423
- 2. Read code carefully before refactoring
424
- 3. PRESERVE existing behavior - no functional changes
425
- 4. Make incremental, safe changes
426
- 5. When done, provide summary starting with "REFACTOR COMPLETE:"
427
-
428
- Refactoring principles:
429
- - Keep changes minimal and focused
430
- - Maintain test compatibility
431
- - Improve readability without over-engineering
432
- - Extract duplicated code into functions
433
- - Rename variables for clarity
434
- - Organize code logically
435
-
436
- Maximum 15 steps.
437
- Work autonomously.`,
438
- allowedTools: [
439
- "Read",
440
- "Edit",
441
- "Grep",
442
- "Glob"
443
- ],
444
- model: "sonnet",
445
- maxSteps: 15,
446
- builtIn: true
447
- });
448
-
449
- // Documentation Agent
450
- this.agents.set("Documentation", {
451
- name: "Documentation",
452
- description: "Writing and updating documentation, README files, API docs. MUST BE USED for 'write docs', 'update README', 'document API', 'add comments', or documentation tasks.",
453
- systemPrompt: `You are a documentation writing agent.
454
-
455
- Your role:
456
- - Write clear, comprehensive documentation
457
- - Update README files with usage examples
458
- - Document APIs, functions, and modules
459
- - Add helpful code comments where needed
460
- - Create user-friendly guides
461
-
462
- Tools available: Read, Write, Edit, Glob, Grep
463
-
464
- IMPORTANT RULES:
465
- 1. You CANNOT spawn subagents (no Task tool)
466
- 2. Read existing code to understand functionality
467
- 3. Write clear, concise documentation
468
- 4. Include practical examples
469
- 5. When done, provide summary starting with "DOCUMENTATION COMPLETE:"
470
-
471
- Documentation best practices:
472
- - Start with purpose and overview
473
- - Provide clear examples
474
- - Document parameters and return values
475
- - Explain edge cases and limitations
476
- - Use consistent formatting
477
- - Keep language simple and direct
478
-
479
- Maximum 10 steps.
480
- Work autonomously.`,
481
- allowedTools: [
482
- "Read",
483
- "Write",
484
- "Edit",
485
- "Glob",
486
- "Grep"
487
- ],
488
- model: "haiku", // Documentation doesn't need deep reasoning
489
- maxSteps: 10,
490
- builtIn: true
491
- });
492
-
493
- logger.info({ count: this.agents.size }, "Loaded built-in agents");
494
- }
495
-
496
- /**
497
- * Load agents from .claude/agents/*.md files
498
- */
499
- loadFilesystemAgents() {
500
- const agentsDir = path.join(process.cwd(), ".claude", "agents");
501
-
502
- if (!fs.existsSync(agentsDir)) {
503
- logger.debug("No .claude/agents directory found, skipping filesystem agents");
504
- return;
505
- }
506
-
507
- const files = fs.readdirSync(agentsDir).filter(f => f.endsWith(".md"));
508
-
509
- for (const file of files) {
510
- try {
511
- const content = fs.readFileSync(path.join(agentsDir, file), "utf8");
512
- const agent = this.parseAgentFile(content, file);
513
-
514
- if (agent) {
515
- // Programmatic agents take precedence over filesystem
516
- if (!this.agents.has(agent.name) || !this.agents.get(agent.name).builtIn) {
517
- this.agents.set(agent.name, agent);
518
- logger.info({ name: agent.name, file }, "Loaded filesystem agent");
519
- }
520
- }
521
- } catch (error) {
522
- logger.warn({ file, error: error.message }, "Failed to load agent file");
523
- }
524
- }
525
- }
526
-
527
- /**
528
- * Parse agent markdown file with YAML frontmatter
529
- */
530
- parseAgentFile(content, filename) {
531
- const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
532
-
533
- if (!match) {
534
- logger.warn({ filename }, "Agent file missing YAML frontmatter");
535
- return null;
536
- }
537
-
538
- const [, frontmatter, body] = match;
539
- const config = yaml.load(frontmatter);
540
-
541
- return {
542
- name: config.name || path.basename(filename, ".md"),
543
- description: config.description || "",
544
- systemPrompt: body.trim(),
545
- allowedTools: config.tools || [],
546
- model: config.model || "sonnet",
547
- maxSteps: config.maxSteps || 15,
548
- builtIn: false,
549
- source: "filesystem"
550
- };
551
- }
552
-
553
- /**
554
- * Register agent programmatically (takes precedence over filesystem)
555
- */
556
- registerAgent(name, definition) {
557
- this.agents.set(name, {
558
- ...definition,
559
- name,
560
- builtIn: false,
561
- source: "programmatic"
562
- });
563
- logger.info({ name }, "Registered programmatic agent");
564
- }
565
-
566
- /**
567
- * Get agent definition by name
568
- */
569
- getAgent(name) {
570
- // Case-insensitive lookup
571
- const normalized = name.toLowerCase();
572
- for (const [key, value] of this.agents.entries()) {
573
- if (key.toLowerCase() === normalized) {
574
- return value;
575
- }
576
- }
577
- return null;
578
- }
579
-
580
- /**
581
- * Get all agent definitions
582
- */
583
- getAllAgents() {
584
- return Array.from(this.agents.values());
585
- }
586
-
587
- /**
588
- * Find agent by task description (automatic delegation)
589
- */
590
- findAgentForTask(taskDescription) {
591
- const desc = taskDescription.toLowerCase();
592
-
593
- // Score each agent based on description match
594
- let bestMatch = null;
595
- let bestScore = 0;
596
-
597
- for (const agent of this.agents.values()) {
598
- const agentDesc = agent.description.toLowerCase();
599
-
600
- // Extract keywords from agent description
601
- const keywords = this.extractKeywords(agentDesc);
602
-
603
- // Count matches
604
- let score = 0;
605
- for (const keyword of keywords) {
606
- if (desc.includes(keyword)) {
607
- score += keyword.length; // Longer keywords = higher weight
608
- }
609
- }
610
-
611
- if (score > bestScore) {
612
- bestScore = score;
613
- bestMatch = agent;
614
- }
615
- }
616
-
617
- // Require minimum score to avoid false positives
618
- if (bestScore >= 5) {
619
- logger.info({
620
- agent: bestMatch.name,
621
- score: bestScore,
622
- task: taskDescription.slice(0, 50)
623
- }, "Auto-selected agent for task");
624
- return bestMatch;
625
- }
626
-
627
- return null;
628
- }
629
-
630
- /**
631
- * Extract keywords from agent description
632
- */
633
- extractKeywords(description) {
634
- // Extract words in quotes and common phrases
635
- const keywords = [];
636
-
637
- // Words in quotes
638
- const quoted = description.match(/'([^']+)'/g) || [];
639
- keywords.push(...quoted.map(q => q.replace(/'/g, "")));
640
-
641
- // Common action words
642
- const actions = ["find", "search", "implement", "plan", "refactor", "explore"];
643
- for (const action of actions) {
644
- if (description.includes(action)) {
645
- keywords.push(action);
646
- }
647
- }
648
-
649
- return keywords;
650
- }
651
- }
652
-
653
- module.exports = AgentDefinitionLoader;