lynkr 1.0.0 → 2.0.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 (41) hide show
  1. package/CITATIONS.bib +6 -0
  2. package/DEPLOYMENT.md +1001 -0
  3. package/README.md +215 -71
  4. package/docs/index.md +55 -2
  5. package/monitor-agents.sh +31 -0
  6. package/package.json +7 -3
  7. package/src/agents/context-manager.js +220 -0
  8. package/src/agents/definitions/loader.js +563 -0
  9. package/src/agents/executor.js +412 -0
  10. package/src/agents/index.js +157 -0
  11. package/src/agents/parallel-coordinator.js +68 -0
  12. package/src/agents/reflector.js +321 -0
  13. package/src/agents/skillbook.js +331 -0
  14. package/src/agents/store.js +244 -0
  15. package/src/api/router.js +55 -0
  16. package/src/clients/databricks.js +214 -17
  17. package/src/clients/routing.js +15 -7
  18. package/src/clients/standard-tools.js +341 -0
  19. package/src/config/index.js +41 -5
  20. package/src/orchestrator/index.js +254 -37
  21. package/src/server.js +2 -0
  22. package/src/tools/agent-task.js +96 -0
  23. package/test/azure-openai-config.test.js +203 -0
  24. package/test/azure-openai-error-resilience.test.js +238 -0
  25. package/test/azure-openai-format-conversion.test.js +354 -0
  26. package/test/azure-openai-integration.test.js +281 -0
  27. package/test/azure-openai-routing.test.js +148 -0
  28. package/test/azure-openai-streaming.test.js +171 -0
  29. package/test/format-conversion.test.js +578 -0
  30. package/test/hybrid-routing-integration.test.js +18 -11
  31. package/test/openrouter-error-resilience.test.js +418 -0
  32. package/test/passthrough-mode.test.js +385 -0
  33. package/test/routing.test.js +9 -3
  34. package/test/web-tools.test.js +3 -0
  35. package/test-agents-simple.js +43 -0
  36. package/test-cli-connection.sh +33 -0
  37. package/test-learning-unit.js +126 -0
  38. package/test-learning.js +112 -0
  39. package/test-parallel-agents.sh +124 -0
  40. package/test-parallel-direct.js +155 -0
  41. package/test-subagents.sh +117 -0
@@ -0,0 +1,385 @@
1
+ const assert = require("assert");
2
+ const { describe, it, beforeEach, afterEach, mock } = require("node:test");
3
+
4
+ describe("Passthrough Mode (Client-Side Tool Execution)", () => {
5
+ let originalEnv;
6
+ let config;
7
+ let orchestrator;
8
+
9
+ beforeEach(() => {
10
+ // Store original environment
11
+ originalEnv = { ...process.env };
12
+
13
+ // Ensure clean state for TOOL_EXECUTION_MODE
14
+ delete process.env.TOOL_EXECUTION_MODE;
15
+
16
+ // Set MODEL_PROVIDER to databricks for tests (not azure-openai from .env)
17
+ process.env.MODEL_PROVIDER = "databricks";
18
+ process.env.DATABRICKS_API_KEY = "test-key";
19
+ process.env.DATABRICKS_API_BASE = "http://test.com";
20
+
21
+ // Clear module cache
22
+ delete require.cache[require.resolve("../src/config")];
23
+ delete require.cache[require.resolve("../src/orchestrator/index")];
24
+ });
25
+
26
+ afterEach(() => {
27
+ // Restore environment
28
+ process.env = originalEnv;
29
+ });
30
+
31
+ describe("Configuration", () => {
32
+ it("should accept 'client' mode", () => {
33
+ process.env.TOOL_EXECUTION_MODE = "client";
34
+ process.env.DATABRICKS_API_KEY = "test-key";
35
+ process.env.DATABRICKS_API_BASE = "http://test.com";
36
+ config = require("../src/config");
37
+
38
+ assert.strictEqual(config.toolExecutionMode, "client");
39
+ });
40
+
41
+ it("should accept 'passthrough' mode (alias for client)", () => {
42
+ process.env.TOOL_EXECUTION_MODE = "passthrough";
43
+ process.env.DATABRICKS_API_KEY = "test-key";
44
+ process.env.DATABRICKS_API_BASE = "http://test.com";
45
+ config = require("../src/config");
46
+
47
+ assert.strictEqual(config.toolExecutionMode, "passthrough");
48
+ });
49
+
50
+ it("should accept 'server' mode explicitly", () => {
51
+ process.env.TOOL_EXECUTION_MODE = "server";
52
+ process.env.DATABRICKS_API_KEY = "test-key";
53
+ process.env.DATABRICKS_API_BASE = "http://test.com";
54
+ config = require("../src/config");
55
+
56
+ assert.strictEqual(config.toolExecutionMode, "server");
57
+ });
58
+ });
59
+
60
+ describe("Response Format in Passthrough Mode", () => {
61
+ it("should return Anthropic-formatted response with tool_use blocks", () => {
62
+ // Mock response from provider with tool calls
63
+ const mockProviderResponse = {
64
+ ok: true,
65
+ status: 200,
66
+ json: {
67
+ choices: [
68
+ {
69
+ message: {
70
+ role: "assistant",
71
+ content: "I'll create that file for you.",
72
+ tool_calls: [
73
+ {
74
+ id: "call_123",
75
+ type: "function",
76
+ function: {
77
+ name: "Write",
78
+ arguments: JSON.stringify({
79
+ file_path: "/tmp/test.txt",
80
+ content: "Hello World"
81
+ })
82
+ }
83
+ }
84
+ ]
85
+ },
86
+ finish_reason: "tool_calls"
87
+ }
88
+ ],
89
+ model: "openai/gpt-4o-mini",
90
+ usage: {
91
+ prompt_tokens: 10,
92
+ completion_tokens: 20,
93
+ total_tokens: 30
94
+ }
95
+ }
96
+ };
97
+
98
+ // Expected Anthropic format
99
+ const expectedContent = [
100
+ {
101
+ type: "text",
102
+ text: "I'll create that file for you."
103
+ },
104
+ {
105
+ type: "tool_use",
106
+ id: "call_123",
107
+ name: "Write",
108
+ input: {
109
+ file_path: "/tmp/test.txt",
110
+ content: "Hello World"
111
+ }
112
+ }
113
+ ];
114
+
115
+ // Test content conversion
116
+ const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
117
+ const anthropicResponse = convertOpenRouterResponseToAnthropic(
118
+ mockProviderResponse.json,
119
+ "claude-sonnet-4-5"
120
+ );
121
+
122
+ assert.strictEqual(anthropicResponse.role, "assistant");
123
+ assert.strictEqual(anthropicResponse.stop_reason, "tool_use");
124
+ assert.strictEqual(Array.isArray(anthropicResponse.content), true);
125
+ assert.strictEqual(anthropicResponse.content.length, 2);
126
+ assert.strictEqual(anthropicResponse.content[0].type, "text");
127
+ assert.strictEqual(anthropicResponse.content[1].type, "tool_use");
128
+ assert.strictEqual(anthropicResponse.content[1].name, "Write");
129
+ assert.deepStrictEqual(anthropicResponse.content[1].input, {
130
+ file_path: "/tmp/test.txt",
131
+ content: "Hello World"
132
+ });
133
+ });
134
+
135
+ it("should handle multiple tool calls in one response", () => {
136
+ const mockProviderResponse = {
137
+ choices: [
138
+ {
139
+ message: {
140
+ role: "assistant",
141
+ content: "I'll read the file and then write it.",
142
+ tool_calls: [
143
+ {
144
+ id: "call_1",
145
+ type: "function",
146
+ function: {
147
+ name: "Read",
148
+ arguments: JSON.stringify({ file_path: "/tmp/input.txt" })
149
+ }
150
+ },
151
+ {
152
+ id: "call_2",
153
+ type: "function",
154
+ function: {
155
+ name: "Write",
156
+ arguments: JSON.stringify({
157
+ file_path: "/tmp/output.txt",
158
+ content: "Modified"
159
+ })
160
+ }
161
+ }
162
+ ]
163
+ },
164
+ finish_reason: "tool_calls"
165
+ }
166
+ ],
167
+ model: "openai/gpt-4o-mini",
168
+ usage: { prompt_tokens: 10, completion_tokens: 30, total_tokens: 40 }
169
+ };
170
+
171
+ const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
172
+ const anthropicResponse = convertOpenRouterResponseToAnthropic(
173
+ mockProviderResponse,
174
+ "claude-sonnet-4-5"
175
+ );
176
+
177
+ assert.strictEqual(anthropicResponse.content.length, 3); // 1 text + 2 tool_use
178
+ assert.strictEqual(anthropicResponse.content[1].type, "tool_use");
179
+ assert.strictEqual(anthropicResponse.content[1].name, "Read");
180
+ assert.strictEqual(anthropicResponse.content[2].type, "tool_use");
181
+ assert.strictEqual(anthropicResponse.content[2].name, "Write");
182
+ });
183
+
184
+ it("should handle tool calls without text content", () => {
185
+ const mockProviderResponse = {
186
+ choices: [
187
+ {
188
+ message: {
189
+ role: "assistant",
190
+ content: null,
191
+ tool_calls: [
192
+ {
193
+ id: "call_1",
194
+ type: "function",
195
+ function: {
196
+ name: "Read",
197
+ arguments: JSON.stringify({ file_path: "/tmp/test.txt" })
198
+ }
199
+ }
200
+ ]
201
+ },
202
+ finish_reason: "tool_calls"
203
+ }
204
+ ],
205
+ model: "openai/gpt-4o-mini",
206
+ usage: { prompt_tokens: 10, completion_tokens: 15, total_tokens: 25 }
207
+ };
208
+
209
+ const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
210
+ const anthropicResponse = convertOpenRouterResponseToAnthropic(
211
+ mockProviderResponse,
212
+ "claude-sonnet-4-5"
213
+ );
214
+
215
+ // Should only have tool_use block, no text block
216
+ assert.strictEqual(anthropicResponse.content.length, 1);
217
+ assert.strictEqual(anthropicResponse.content[0].type, "tool_use");
218
+ });
219
+ });
220
+
221
+ describe("Tool Result Processing", () => {
222
+ it("should accept tool_result blocks from CLI in next request", () => {
223
+ // Simulate a conversation with tool results coming back from CLI
224
+ const messagesWithToolResults = [
225
+ {
226
+ role: "user",
227
+ content: "Create a file /tmp/test.txt"
228
+ },
229
+ {
230
+ role: "assistant",
231
+ content: [
232
+ { type: "text", text: "I'll create that file." },
233
+ {
234
+ type: "tool_use",
235
+ id: "toolu_123",
236
+ name: "Write",
237
+ input: {
238
+ file_path: "/tmp/test.txt",
239
+ content: "Hello"
240
+ }
241
+ }
242
+ ]
243
+ },
244
+ {
245
+ role: "user",
246
+ content: [
247
+ {
248
+ type: "tool_result",
249
+ tool_use_id: "toolu_123",
250
+ content: "File created successfully"
251
+ }
252
+ ]
253
+ }
254
+ ];
255
+
256
+ // Verify structure
257
+ assert.strictEqual(messagesWithToolResults.length, 3);
258
+ assert.strictEqual(messagesWithToolResults[2].role, "user");
259
+ assert.strictEqual(Array.isArray(messagesWithToolResults[2].content), true);
260
+ assert.strictEqual(messagesWithToolResults[2].content[0].type, "tool_result");
261
+ assert.strictEqual(messagesWithToolResults[2].content[0].tool_use_id, "toolu_123");
262
+ });
263
+
264
+ it("should convert tool_result blocks to OpenRouter format", () => {
265
+ // Must include assistant message with tool_use first
266
+ const anthropicMessages = [
267
+ {
268
+ role: "assistant",
269
+ content: [
270
+ {
271
+ type: "tool_use",
272
+ id: "toolu_123",
273
+ name: "Write",
274
+ input: { file_path: "/tmp/test.txt", content: "Hello" }
275
+ }
276
+ ]
277
+ },
278
+ {
279
+ role: "user",
280
+ content: [
281
+ {
282
+ type: "tool_result",
283
+ tool_use_id: "toolu_123",
284
+ content: "File created successfully"
285
+ }
286
+ ]
287
+ }
288
+ ];
289
+
290
+ const { convertAnthropicMessagesToOpenRouter } = require("../src/clients/openrouter-utils");
291
+ const openRouterMessages = convertAnthropicMessagesToOpenRouter(anthropicMessages);
292
+
293
+ // Should convert to tool role message for OpenRouter
294
+ assert.strictEqual(openRouterMessages.length >= 2, true);
295
+ // OpenRouter expects tool results as separate tool messages
296
+ const toolMessage = openRouterMessages.find(m => m.role === "tool");
297
+ assert.ok(toolMessage, "Tool message should be present");
298
+ });
299
+ });
300
+
301
+ describe("Stop Reason Handling", () => {
302
+ it("should set stop_reason to 'tool_use' when tools are present", () => {
303
+ const mockResponse = {
304
+ choices: [
305
+ {
306
+ message: {
307
+ role: "assistant",
308
+ content: "Using tool",
309
+ tool_calls: [
310
+ {
311
+ id: "call_1",
312
+ function: {
313
+ name: "Read",
314
+ arguments: "{}"
315
+ }
316
+ }
317
+ ]
318
+ },
319
+ finish_reason: "tool_calls"
320
+ }
321
+ ],
322
+ model: "test-model",
323
+ usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 }
324
+ };
325
+
326
+ const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
327
+ const result = convertOpenRouterResponseToAnthropic(mockResponse, "claude-sonnet-4-5");
328
+
329
+ assert.strictEqual(result.stop_reason, "tool_use");
330
+ });
331
+
332
+ it("should set stop_reason to 'end_turn' when no tools", () => {
333
+ const mockResponse = {
334
+ choices: [
335
+ {
336
+ message: {
337
+ role: "assistant",
338
+ content: "Simple response"
339
+ },
340
+ finish_reason: "stop"
341
+ }
342
+ ],
343
+ model: "test-model",
344
+ usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 }
345
+ };
346
+
347
+ const { convertOpenRouterResponseToAnthropic } = require("../src/clients/openrouter-utils");
348
+ const result = convertOpenRouterResponseToAnthropic(mockResponse, "claude-sonnet-4-5");
349
+
350
+ assert.strictEqual(result.stop_reason, "end_turn");
351
+ });
352
+ });
353
+
354
+ describe("Session Storage Format", () => {
355
+ it("should store tool_use blocks in Anthropic format for session", () => {
356
+ // Session content should always be in Anthropic format
357
+ // regardless of provider
358
+ const sessionContent = [
359
+ {
360
+ type: "text",
361
+ text: "I'll help with that."
362
+ },
363
+ {
364
+ type: "tool_use",
365
+ id: "toolu_abc",
366
+ name: "Write",
367
+ input: {
368
+ file_path: "/tmp/test.txt",
369
+ content: "data"
370
+ }
371
+ }
372
+ ];
373
+
374
+ // Verify all blocks have correct structure
375
+ sessionContent.forEach(block => {
376
+ assert.strictEqual(typeof block.type, "string");
377
+ if (block.type === "tool_use") {
378
+ assert.strictEqual(typeof block.id, "string");
379
+ assert.strictEqual(typeof block.name, "string");
380
+ assert.strictEqual(typeof block.input, "object");
381
+ }
382
+ });
383
+ });
384
+ });
385
+ });
@@ -78,7 +78,9 @@ describe("Routing Logic", () => {
78
78
  process.env.PREFER_OLLAMA = "true";
79
79
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
80
80
  process.env.OLLAMA_MAX_TOOLS_FOR_ROUTING = "3";
81
- process.env.OLLAMA_FALLBACK_PROVIDER = "databricks";
81
+ process.env.OPENROUTER_MAX_TOOLS_FOR_ROUTING = "3"; // Set same as ollama to skip openrouter tier
82
+ process.env.FALLBACK_PROVIDER = "databricks";
83
+ process.env.FALLBACK_ENABLED = "true"; // Ensure fallback is enabled
82
84
  process.env.DATABRICKS_API_KEY = "test-key";
83
85
  process.env.DATABRICKS_API_BASE = "http://test.com";
84
86
 
@@ -105,6 +107,7 @@ describe("Routing Logic", () => {
105
107
  process.env.PREFER_OLLAMA = "true";
106
108
  process.env.OLLAMA_MODEL = "llama3:latest"; // Non-tool-capable model
107
109
  process.env.OLLAMA_FALLBACK_PROVIDER = "databricks";
110
+ process.env.FALLBACK_ENABLED = "true"; // Ensure fallback is enabled
108
111
  process.env.DATABRICKS_API_KEY = "test-key";
109
112
  process.env.DATABRICKS_API_BASE = "http://test.com";
110
113
 
@@ -153,6 +156,9 @@ describe("Routing Logic", () => {
153
156
  process.env.MODEL_PROVIDER = "ollama";
154
157
  process.env.PREFER_OLLAMA = "true";
155
158
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
159
+ // Override .env file which sets FALLBACK_ENABLED=false
160
+ // Test default behavior when not set to "false"
161
+ process.env.FALLBACK_ENABLED = "true";
156
162
 
157
163
  config = require("../src/config");
158
164
  routing = require("../src/clients/routing");
@@ -164,7 +170,7 @@ describe("Routing Logic", () => {
164
170
  process.env.MODEL_PROVIDER = "ollama";
165
171
  process.env.PREFER_OLLAMA = "true";
166
172
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
167
- process.env.OLLAMA_FALLBACK_ENABLED = "false";
173
+ process.env.FALLBACK_ENABLED = "false";
168
174
 
169
175
  config = require("../src/config");
170
176
  routing = require("../src/clients/routing");
@@ -191,7 +197,7 @@ describe("Routing Logic", () => {
191
197
  process.env.MODEL_PROVIDER = "ollama";
192
198
  process.env.PREFER_OLLAMA = "true";
193
199
  process.env.OLLAMA_MODEL = "qwen2.5-coder:latest";
194
- process.env.OLLAMA_FALLBACK_PROVIDER = "azure-anthropic";
200
+ process.env.FALLBACK_PROVIDER = "azure-anthropic";
195
201
  process.env.AZURE_ANTHROPIC_ENDPOINT = "http://test.com";
196
202
  process.env.AZURE_ANTHROPIC_API_KEY = "test-key";
197
203
 
@@ -3,6 +3,9 @@ const assert = require("node:assert");
3
3
  const http = require("http");
4
4
 
5
5
  // Mock configuration
6
+ process.env.MODEL_PROVIDER = "databricks"; // Override any .env settings
7
+ process.env.DATABRICKS_API_KEY = "test-key";
8
+ process.env.DATABRICKS_API_BASE = "http://test.com";
6
9
  process.env.WEB_SEARCH_ENDPOINT = "http://localhost:9999/search";
7
10
  process.env.WEB_SEARCH_TIMEOUT_MS = "5000";
8
11
  process.env.WEB_FETCH_BODY_PREVIEW_MAX = "1000";
@@ -0,0 +1,43 @@
1
+ // Simple test to verify agent system is working
2
+ // Run: AGENTS_ENABLED=true node test-agents-simple.js
3
+
4
+ const config = require('./src/config');
5
+ const { listAgents, getAgentStats } = require('./src/agents');
6
+
7
+ console.log('🧪 Testing Agent System\n');
8
+
9
+ // Test 1: Check configuration
10
+ console.log('1. Configuration:');
11
+ console.log(` Agents enabled: ${config.agents?.enabled}`);
12
+ console.log(` Max concurrent: ${config.agents?.maxConcurrent}`);
13
+ console.log(` Default model: ${config.agents?.defaultModel}`);
14
+ console.log(` Max steps: ${config.agents?.maxSteps}`);
15
+ console.log(` Timeout: ${config.agents?.timeout}ms`);
16
+
17
+ // Test 2: List available agents
18
+ console.log('\n2. Available Agents:');
19
+ const agents = listAgents();
20
+ agents.forEach(agent => {
21
+ console.log(` - ${agent.name} (${agent.model})`);
22
+ console.log(` ${agent.description.substring(0, 80)}...`);
23
+ console.log(` Tools: ${agent.allowedTools.length === 0 ? 'ALL' : agent.allowedTools.join(', ')}`);
24
+ console.log(` Max steps: ${agent.maxSteps}`);
25
+ });
26
+
27
+ // Test 3: Check stats
28
+ console.log('\n3. Execution Stats:');
29
+ const stats = getAgentStats();
30
+ if (stats.length === 0) {
31
+ console.log(' No executions yet');
32
+ } else {
33
+ stats.forEach(stat => {
34
+ console.log(` - ${stat.agent_type}:`);
35
+ console.log(` Total: ${stat.total_executions}, Completed: ${stat.completed}, Failed: ${stat.failed}`);
36
+ });
37
+ }
38
+
39
+ console.log('\n✅ Agent system is operational!\n');
40
+ console.log('Next steps:');
41
+ console.log('1. Start server: AGENTS_ENABLED=true npm start');
42
+ console.log('2. Test with curl (see README)');
43
+ console.log('3. Or run: ./test-subagents.sh\n');
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+
3
+ echo "🔍 Claude Code CLI Connection Test"
4
+ echo "===================================="
5
+ echo ""
6
+
7
+ echo "1. Environment Variables:"
8
+ env | grep -i anthropic | sed 's/^/ /'
9
+
10
+ echo ""
11
+ echo "2. Testing ngrok endpoint:"
12
+ curl -s https://ae619b9fdba2.ngrok-free.app/health -H "ngrok-skip-browser-warning: true" | jq .
13
+
14
+ echo ""
15
+ echo "3. Testing direct localhost:"
16
+ curl -s http://localhost:8080/health | jq .
17
+
18
+ echo ""
19
+ echo "4. Checking Claude config file:"
20
+ if [ -f ~/.config/claude/config.json ]; then
21
+ echo " Config exists:"
22
+ cat ~/.config/claude/config.json | jq .api 2>/dev/null || cat ~/.config/claude/config.json
23
+ else
24
+ echo " No config file found"
25
+ fi
26
+
27
+ echo ""
28
+ echo "5. Last 5 requests in server log:"
29
+ tail -100 /tmp/lynkr.log | grep "Request started" | tail -5 | sed 's/^/ /'
30
+
31
+ echo ""
32
+ echo "======================================"
33
+ echo "Please share this output!"
@@ -0,0 +1,126 @@
1
+ const Skillbook = require('./src/agents/skillbook');
2
+ const Reflector = require('./src/agents/reflector');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ console.log('========================================');
7
+ console.log('Learning System Unit Test');
8
+ console.log('========================================\n');
9
+
10
+ async function testLearningSystem() {
11
+ try {
12
+ // Test 1: Create and save skillbook
13
+ console.log('Test 1: Creating skillbook...');
14
+ const skillbook = new Skillbook('Explore');
15
+
16
+ // Add a test skill
17
+ skillbook.addSkill({
18
+ pattern: "Search task",
19
+ action: "Use tools: Glob, Grep, Read",
20
+ reasoning: "Successfully completed search task using these tools",
21
+ tools: ["Glob", "Grep", "Read"],
22
+ confidence: 0.75
23
+ });
24
+
25
+ console.log(`✓ Added 1 skill`);
26
+ console.log(` Total skills: ${skillbook.skills.size}\n`);
27
+
28
+ // Test 2: Save skillbook
29
+ console.log('Test 2: Saving skillbook...');
30
+ const saved = await skillbook.save();
31
+ console.log(`✓ Skillbook saved: ${saved}\n`);
32
+
33
+ // Test 3: Load skillbook
34
+ console.log('Test 3: Loading skillbook...');
35
+ const loaded = await Skillbook.load('Explore');
36
+ console.log(`✓ Loaded skillbook`);
37
+ console.log(` Skills: ${loaded.skills.size}\n`);
38
+
39
+ // Test 4: Get top skills
40
+ console.log('Test 4: Getting top skills...');
41
+ const topSkills = loaded.getTopSkills(3);
42
+ console.log(`✓ Top skills: ${topSkills.length}`);
43
+ topSkills.forEach((skill, i) => {
44
+ console.log(` ${i + 1}. ${skill.pattern} (confidence: ${Math.round(skill.confidence * 100)}%)`);
45
+ });
46
+ console.log('');
47
+
48
+ // Test 5: Format for prompt
49
+ console.log('Test 5: Formatting for prompt...');
50
+ const promptSection = loaded.formatForPrompt();
51
+ console.log(`✓ Generated prompt section (${promptSection.length} chars):`);
52
+ console.log(promptSection.substring(0, 200) + '...\n');
53
+
54
+ // Test 6: Test Reflector
55
+ console.log('Test 6: Testing Reflector...');
56
+ const mockContext = {
57
+ agentName: 'Test',
58
+ taskPrompt: 'Find all JavaScript files in src directory',
59
+ steps: 3,
60
+ maxSteps: 10,
61
+ inputTokens: 500,
62
+ outputTokens: 300,
63
+ transcript: [
64
+ {
65
+ type: 'tool_call',
66
+ toolName: 'Glob',
67
+ timestamp: Date.now() - 2000
68
+ },
69
+ {
70
+ type: 'tool_call',
71
+ toolName: 'Grep',
72
+ timestamp: Date.now() - 1000
73
+ },
74
+ {
75
+ type: 'tool_call',
76
+ toolName: 'Read',
77
+ timestamp: Date.now()
78
+ }
79
+ ]
80
+ };
81
+
82
+ const patterns = Reflector.reflect(mockContext, true);
83
+ console.log(`✓ Reflector extracted ${patterns.length} patterns:`);
84
+ patterns.forEach((p, i) => {
85
+ console.log(` ${i + 1}. ${p.pattern}`);
86
+ console.log(` Action: ${p.action}`);
87
+ console.log(` Confidence: ${Math.round(p.confidence * 100)}%`);
88
+ });
89
+ console.log('');
90
+
91
+ // Test 7: Add reflected patterns to skillbook
92
+ console.log('Test 7: Adding reflected patterns...');
93
+ const testSkillbook = new Skillbook('Test');
94
+ for (const pattern of patterns) {
95
+ testSkillbook.addSkill(pattern);
96
+ }
97
+ console.log(`✓ Added ${patterns.length} patterns`);
98
+ console.log(` Total skills: ${testSkillbook.skills.size}\n`);
99
+
100
+ // Test 8: Save test skillbook
101
+ console.log('Test 8: Saving test skillbook...');
102
+ await testSkillbook.save();
103
+ console.log(`✓ Test skillbook saved\n`);
104
+
105
+ // Test 9: List all skillbooks
106
+ console.log('Test 9: Listing all skillbooks...');
107
+ const skillbooksDir = path.join(process.cwd(), 'data', 'skillbooks');
108
+ const files = fs.readdirSync(skillbooksDir);
109
+ console.log(`✓ Found ${files.length} skillbook(s):`);
110
+ files.forEach(file => {
111
+ console.log(` - ${file}`);
112
+ });
113
+ console.log('');
114
+
115
+ console.log('========================================');
116
+ console.log('✅ All Tests Passed!');
117
+ console.log('========================================\n');
118
+
119
+ } catch (error) {
120
+ console.error('❌ Test failed:', error.message);
121
+ console.error(error.stack);
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ testLearningSystem();