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.
- package/CITATIONS.bib +6 -0
- package/DEPLOYMENT.md +1001 -0
- package/README.md +215 -71
- package/docs/index.md +55 -2
- package/monitor-agents.sh +31 -0
- package/package.json +7 -3
- package/src/agents/context-manager.js +220 -0
- package/src/agents/definitions/loader.js +563 -0
- package/src/agents/executor.js +412 -0
- package/src/agents/index.js +157 -0
- package/src/agents/parallel-coordinator.js +68 -0
- package/src/agents/reflector.js +321 -0
- package/src/agents/skillbook.js +331 -0
- package/src/agents/store.js +244 -0
- package/src/api/router.js +55 -0
- package/src/clients/databricks.js +214 -17
- package/src/clients/routing.js +15 -7
- package/src/clients/standard-tools.js +341 -0
- package/src/config/index.js +41 -5
- package/src/orchestrator/index.js +254 -37
- package/src/server.js +2 -0
- package/src/tools/agent-task.js +96 -0
- package/test/azure-openai-config.test.js +203 -0
- package/test/azure-openai-error-resilience.test.js +238 -0
- package/test/azure-openai-format-conversion.test.js +354 -0
- package/test/azure-openai-integration.test.js +281 -0
- package/test/azure-openai-routing.test.js +148 -0
- package/test/azure-openai-streaming.test.js +171 -0
- package/test/format-conversion.test.js +578 -0
- package/test/hybrid-routing-integration.test.js +18 -11
- package/test/openrouter-error-resilience.test.js +418 -0
- package/test/passthrough-mode.test.js +385 -0
- package/test/routing.test.js +9 -3
- package/test/web-tools.test.js +3 -0
- package/test-agents-simple.js +43 -0
- package/test-cli-connection.sh +33 -0
- package/test-learning-unit.js +126 -0
- package/test-learning.js +112 -0
- package/test-parallel-agents.sh +124 -0
- package/test-parallel-direct.js +155 -0
- 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
|
+
});
|
package/test/routing.test.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
package/test/web-tools.test.js
CHANGED
|
@@ -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();
|