lynkr 9.9.0 → 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 (73) hide show
  1. package/README.md +92 -23
  2. package/bin/cli.js +13 -7
  3. package/bin/lynkr-init.js +34 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +3 -3
  7. package/scripts/build-eval-set.js +256 -0
  8. package/scripts/mine-difficulty-anchors.js +288 -0
  9. package/scripts/validate-difficulty-classifier.js +144 -0
  10. package/scripts/validate-intent-anchors.js +186 -0
  11. package/src/api/providers-handler.js +0 -1
  12. package/src/api/router.js +292 -160
  13. package/src/clients/databricks.js +95 -6
  14. package/src/config/index.js +3 -43
  15. package/src/context/tool-result-compressor.js +883 -40
  16. package/src/orchestrator/index.js +117 -1235
  17. package/src/orchestrator/passthrough-stream.js +382 -0
  18. package/src/orchestrator/sse-transformer.js +408 -0
  19. package/src/routing/affinity-store.js +17 -3
  20. package/src/routing/classifier-setup.js +207 -0
  21. package/src/routing/complexity-analyzer.js +40 -4
  22. package/src/routing/difficulty-classifier.js +261 -0
  23. package/src/routing/index.js +70 -7
  24. package/src/routing/intent-score.js +132 -12
  25. package/src/routing/session-affinity.js +8 -1
  26. package/src/routing/side-channel-detector.js +103 -0
  27. package/src/server.js +20 -46
  28. package/src/agents/context-manager.js +0 -236
  29. package/src/agents/decomposition/dispatcher.js +0 -185
  30. package/src/agents/decomposition/gate.js +0 -136
  31. package/src/agents/decomposition/index.js +0 -183
  32. package/src/agents/decomposition/model-call.js +0 -75
  33. package/src/agents/decomposition/planner.js +0 -223
  34. package/src/agents/decomposition/synthesizer.js +0 -89
  35. package/src/agents/decomposition/telemetry.js +0 -55
  36. package/src/agents/definitions/loader.js +0 -653
  37. package/src/agents/executor.js +0 -457
  38. package/src/agents/index.js +0 -165
  39. package/src/agents/parallel-coordinator.js +0 -68
  40. package/src/agents/reflector.js +0 -331
  41. package/src/agents/skillbook.js +0 -331
  42. package/src/agents/store.js +0 -259
  43. package/src/edits/index.js +0 -171
  44. package/src/indexer/babel-parser.js +0 -213
  45. package/src/indexer/index.js +0 -1629
  46. package/src/indexer/navigation/index.js +0 -32
  47. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  48. package/src/indexer/parser.js +0 -443
  49. package/src/tasks/store.js +0 -349
  50. package/src/tests/coverage.js +0 -173
  51. package/src/tests/index.js +0 -171
  52. package/src/tests/store.js +0 -213
  53. package/src/tools/agent-task.js +0 -145
  54. package/src/tools/code-mode.js +0 -304
  55. package/src/tools/decompose.js +0 -91
  56. package/src/tools/edits.js +0 -94
  57. package/src/tools/execution.js +0 -171
  58. package/src/tools/git.js +0 -1346
  59. package/src/tools/index.js +0 -306
  60. package/src/tools/indexer.js +0 -360
  61. package/src/tools/lazy-loader.js +0 -366
  62. package/src/tools/mcp-remote.js +0 -88
  63. package/src/tools/mcp.js +0 -116
  64. package/src/tools/process.js +0 -167
  65. package/src/tools/smart-selection.js +0 -180
  66. package/src/tools/stubs.js +0 -55
  67. package/src/tools/tasks.js +0 -260
  68. package/src/tools/tests.js +0 -132
  69. package/src/tools/tinyfish.js +0 -358
  70. package/src/tools/truncate.js +0 -106
  71. package/src/tools/web-client.js +0 -71
  72. package/src/tools/web.js +0 -415
  73. package/src/tools/workspace.js +0 -204
@@ -1,366 +0,0 @@
1
- /**
2
- * Lazy Tool Loader
3
- *
4
- * Loads tool categories on-demand based on prompt content analysis.
5
- * Reduces startup time and memory by only loading tools when needed.
6
- *
7
- * @module tools/lazy-loader
8
- */
9
-
10
- const logger = require('../logger');
11
-
12
- // Track which tool categories have been loaded
13
- const loadedCategories = new Set();
14
-
15
- // Core tools that are always loaded at startup
16
- const CORE_CATEGORIES = ['stubs', 'workspace', 'execution'];
17
-
18
- // Tool categories with their registration functions and keyword triggers
19
- const TOOL_CATEGORIES = {
20
- stubs: {
21
- keywords: [], // Always loaded
22
- loader: () => require('./stubs').registerStubTools,
23
- priority: 0,
24
- },
25
- workspace: {
26
- keywords: ['file', 'read', 'write', 'edit', 'create', 'delete', 'list', 'directory', 'folder', 'path'],
27
- loader: () => require('./workspace').registerWorkspaceTools,
28
- priority: 0,
29
- },
30
- execution: {
31
- keywords: ['run', 'execute', 'shell', 'bash', 'command', 'terminal', 'npm', 'node', 'python', 'script'],
32
- loader: () => require('./execution').registerExecutionTools,
33
- priority: 0,
34
- },
35
- web: {
36
- keywords: ['web', 'search', 'fetch', 'url', 'http', 'https', 'api', 'request', 'internet', 'online', 'browse', 'website'],
37
- loader: () => require('./web').registerWebTools,
38
- priority: 1,
39
- },
40
- indexer: {
41
- keywords: ['index', 'search', 'find', 'symbol', 'reference', 'grep', 'scan', 'codebase'],
42
- loader: () => require('./indexer').registerIndexerTools,
43
- priority: 1,
44
- },
45
- edits: {
46
- keywords: ['edit', 'patch', 'modify', 'change', 'update', 'replace', 'refactor'],
47
- loader: () => require('./edits').registerEditTools,
48
- priority: 1,
49
- },
50
- git: {
51
- keywords: ['git', 'commit', 'push', 'pull', 'branch', 'merge', 'rebase', 'stash', 'checkout', 'clone', 'diff', 'status', 'log', 'remote', 'fetch', 'pr', 'pull request'],
52
- loader: () => require('./git').registerGitTools,
53
- priority: 2,
54
- },
55
- tasks: {
56
- keywords: ['task', 'todo', 'subtask', 'agent', 'spawn', 'background'],
57
- loader: () => require('./tasks').registerTaskTools,
58
- priority: 2,
59
- },
60
- tinyfish: {
61
- keywords: ['tinyfish', 'web_agent', 'automate', 'scrape', 'extract', 'crawl', 'browser'],
62
- loader: () => require('./tinyfish').registerTinyFishTools,
63
- priority: 2,
64
- },
65
- tests: {
66
- keywords: ['test', 'jest', 'mocha', 'pytest', 'unittest', 'spec', 'coverage', 'assert'],
67
- loader: () => require('./tests').registerTestTools,
68
- priority: 2,
69
- },
70
- mcp: {
71
- keywords: ['mcp', 'server', 'sandbox', 'container', 'docker'],
72
- loader: () => require('./mcp').registerMcpTools,
73
- priority: 3,
74
- },
75
- agentTask: {
76
- keywords: ['agent', 'subagent', 'spawn', 'delegate', 'parallel'],
77
- loader: () => require('./agent-task').registerAgentTaskTool,
78
- priority: 2,
79
- },
80
- decompose: {
81
- keywords: ['decompose', 'subtask', 'break down', 'break into', 'split task', 'plan and execute'],
82
- loader: () => require('./decompose').registerDecomposeTool,
83
- priority: 2,
84
- },
85
- 'code-mode': {
86
- keywords: ['mcp', 'execute', 'server', 'tool', 'code mode'],
87
- loader: () => require('./code-mode').registerCodeModeTools,
88
- priority: 3,
89
- },
90
- };
91
-
92
- /**
93
- * Load a specific tool category
94
- * @param {string} category - Category name
95
- * @returns {boolean} - True if loaded, false if already loaded or failed
96
- */
97
- function loadCategory(category) {
98
- if (loadedCategories.has(category)) {
99
- return false;
100
- }
101
-
102
- const config = TOOL_CATEGORIES[category];
103
- if (!config) {
104
- logger.warn({ category }, '[LazyLoader] Unknown tool category');
105
- return false;
106
- }
107
-
108
- try {
109
- const registerFn = config.loader();
110
- if (typeof registerFn === 'function') {
111
- registerFn();
112
- }
113
- loadedCategories.add(category);
114
- logger.debug({ category }, '[LazyLoader] Tool category loaded');
115
- return true;
116
- } catch (err) {
117
- logger.error({ category, error: err.message }, '[LazyLoader] Failed to load tool category');
118
- return false;
119
- }
120
- }
121
-
122
- /**
123
- * Load core tools (called at startup)
124
- */
125
- function loadCoreTools() {
126
- const startTime = Date.now();
127
-
128
- for (const category of CORE_CATEGORIES) {
129
- loadCategory(category);
130
- }
131
-
132
- logger.info({
133
- loadedCategories: Array.from(loadedCategories),
134
- duration: Date.now() - startTime,
135
- }, '[LazyLoader] Core tools loaded');
136
- }
137
-
138
- /**
139
- * Load all tools (for backwards compatibility or when lazy loading is disabled)
140
- */
141
- function loadAllTools() {
142
- const startTime = Date.now();
143
-
144
- for (const category of Object.keys(TOOL_CATEGORIES)) {
145
- loadCategory(category);
146
- }
147
-
148
- logger.info({
149
- loadedCategories: Array.from(loadedCategories),
150
- duration: Date.now() - startTime,
151
- }, '[LazyLoader] All tools loaded');
152
- }
153
-
154
- /**
155
- * Analyze prompt content and determine which tool categories are needed
156
- * @param {string|Array} content - Prompt content (string or messages array)
157
- * @returns {string[]} - List of category names that should be loaded
158
- */
159
- function analyzePromptForTools(content) {
160
- // Extract text from various input formats
161
- let text = '';
162
-
163
- if (typeof content === 'string') {
164
- text = content.toLowerCase();
165
- } else if (Array.isArray(content)) {
166
- // Extract from messages array
167
- text = content
168
- .map(msg => {
169
- if (typeof msg.content === 'string') {
170
- return msg.content;
171
- }
172
- if (Array.isArray(msg.content)) {
173
- return msg.content
174
- .filter(part => part.type === 'text' || part.type === 'input_text')
175
- .map(part => part.text || part.input_text || '')
176
- .join(' ');
177
- }
178
- return '';
179
- })
180
- .join(' ')
181
- .toLowerCase();
182
- }
183
-
184
- if (!text) return [];
185
-
186
- const neededCategories = new Set();
187
-
188
- // Check each category's keywords
189
- for (const [category, config] of Object.entries(TOOL_CATEGORIES)) {
190
- // Skip already loaded categories
191
- if (loadedCategories.has(category)) continue;
192
-
193
- // Check if any keyword matches
194
- for (const keyword of config.keywords) {
195
- if (text.includes(keyword.toLowerCase())) {
196
- neededCategories.add(category);
197
- break;
198
- }
199
- }
200
- }
201
-
202
- // Sort by priority (lower = load first)
203
- return Array.from(neededCategories).sort((a, b) => {
204
- return (TOOL_CATEGORIES[a]?.priority ?? 99) - (TOOL_CATEGORIES[b]?.priority ?? 99);
205
- });
206
- }
207
-
208
- /**
209
- * Ensure tools needed for a prompt are loaded
210
- * @param {string|Array} content - Prompt content
211
- * @returns {{ loaded: string[], alreadyLoaded: string[] }}
212
- */
213
- function ensureToolsForPrompt(content) {
214
- const neededCategories = analyzePromptForTools(content);
215
- const loaded = [];
216
- const alreadyLoaded = [];
217
-
218
- for (const category of neededCategories) {
219
- if (loadedCategories.has(category)) {
220
- alreadyLoaded.push(category);
221
- } else if (loadCategory(category)) {
222
- loaded.push(category);
223
- }
224
- }
225
-
226
- if (loaded.length > 0) {
227
- logger.info({ loaded, triggered: content?.substring?.(0, 100) }, '[LazyLoader] Loaded tools for prompt');
228
- }
229
-
230
- return { loaded, alreadyLoaded };
231
- }
232
-
233
- /**
234
- * Load a tool category by tool name (called when a tool is requested but not found)
235
- * @param {string} toolName - Name of the tool being requested
236
- * @returns {boolean} - True if a category was loaded
237
- */
238
- function loadCategoryForTool(toolName) {
239
- if (!toolName) return false;
240
-
241
- const lowerName = toolName.toLowerCase();
242
-
243
- // Map tool names to categories
244
- const toolToCategory = {
245
- // Git tools
246
- 'workspace_git_status': 'git',
247
- 'workspace_git_stage': 'git',
248
- 'workspace_git_unstage': 'git',
249
- 'workspace_git_commit': 'git',
250
- 'workspace_git_push': 'git',
251
- 'workspace_git_pull': 'git',
252
- 'workspace_git_branches': 'git',
253
- 'workspace_git_checkout': 'git',
254
- 'workspace_git_stash': 'git',
255
- 'workspace_git_merge': 'git',
256
- 'workspace_git_rebase': 'git',
257
- 'workspace_git_conflicts': 'git',
258
- 'workspace_diff': 'git',
259
- 'workspace_diff_review': 'git',
260
- 'workspace_diff_summary': 'git',
261
- 'workspace_diff_by_commit': 'git',
262
- 'workspace_release_notes': 'git',
263
- 'workspace_changelog_generate': 'git',
264
- 'workspace_pr_template_generate': 'git',
265
- 'workspace_git_patch_plan': 'git',
266
-
267
- // Web tools
268
- 'web_search': 'web',
269
- 'web_fetch': 'web',
270
-
271
- // Indexer tools
272
- 'workspace_search': 'indexer',
273
- 'workspace_symbol_search': 'indexer',
274
- 'workspace_symbol_references': 'indexer',
275
- 'workspace_index_rebuild': 'indexer',
276
-
277
- // Edit tools
278
- 'edit_patch': 'edits',
279
-
280
- // Task tools
281
- 'task_create': 'tasks',
282
- 'task_update': 'tasks',
283
- 'task_list': 'tasks',
284
-
285
- // Test tools
286
- 'workspace_test_run': 'tests',
287
- 'workspace_test_summary': 'tests',
288
- 'workspace_test_history': 'tests',
289
-
290
- // MCP tools
291
- 'workspace_sandbox_sessions': 'mcp',
292
- 'workspace_mcp_servers': 'mcp',
293
-
294
- // Code Mode meta-tools
295
- 'mcp_list_tools': 'code-mode',
296
- 'mcp_tool_info': 'code-mode',
297
- 'mcp_tool_docs': 'code-mode',
298
- 'mcp_execute': 'code-mode',
299
-
300
- // Agent task
301
- // TinyFish (web agent)
302
- 'web_agent': 'tinyfish',
303
- 'agent_task': 'agentTask',
304
-
305
- // Task decomposition
306
- 'decomposetask': 'decompose',
307
- };
308
-
309
- // Direct mapping
310
- const category = toolToCategory[lowerName];
311
- if (category && !loadedCategories.has(category)) {
312
- return loadCategory(category);
313
- }
314
-
315
- // Fuzzy matching by prefix
316
- for (const [toolPattern, cat] of Object.entries(toolToCategory)) {
317
- if (lowerName.startsWith(toolPattern.split('_')[0]) && !loadedCategories.has(cat)) {
318
- return loadCategory(cat);
319
- }
320
- }
321
-
322
- return false;
323
- }
324
-
325
- /**
326
- * Get statistics about loaded tools
327
- */
328
- function getLoaderStats() {
329
- const allCategories = Object.keys(TOOL_CATEGORIES);
330
- return {
331
- loaded: Array.from(loadedCategories),
332
- notLoaded: allCategories.filter(c => !loadedCategories.has(c)),
333
- totalCategories: allCategories.length,
334
- loadedCount: loadedCategories.size,
335
- };
336
- }
337
-
338
- /**
339
- * Check if a category is loaded
340
- * @param {string} category
341
- * @returns {boolean}
342
- */
343
- function isCategoryLoaded(category) {
344
- return loadedCategories.has(category);
345
- }
346
-
347
- /**
348
- * Reset loader state (for testing)
349
- */
350
- function resetLoader() {
351
- loadedCategories.clear();
352
- }
353
-
354
- module.exports = {
355
- loadCoreTools,
356
- loadAllTools,
357
- loadCategory,
358
- loadCategoryForTool,
359
- analyzePromptForTools,
360
- ensureToolsForPrompt,
361
- getLoaderStats,
362
- isCategoryLoaded,
363
- resetLoader,
364
- TOOL_CATEGORIES,
365
- CORE_CATEGORIES,
366
- };
@@ -1,88 +0,0 @@
1
- const { registerTool } = require(".");
2
- const { listServers, ensureClient } = require("../mcp");
3
- const config = require("../config");
4
- const logger = require("../logger");
5
-
6
- const REMOTE_TOOL_PREFIX = "mcp";
7
-
8
- function sanitiseName(value) {
9
- return String(value)
10
- .replace(/[^A-Za-z0-9_]/g, "_")
11
- .replace(/_+/g, "_")
12
- .replace(/^_+|_+$/g, "");
13
- }
14
-
15
- async function registerRemoteTools() {
16
- // Code Mode: register 4 meta-tools instead of individual remote tools
17
- if (config.mcp?.codeMode?.enabled) {
18
- const { registerCodeModeTools } = require("./code-mode");
19
- registerCodeModeTools();
20
- return;
21
- }
22
- const servers = listServers();
23
- await Promise.all(
24
- servers.map(async (server) => {
25
- try {
26
- const client = await ensureClient(server.id);
27
- if (!client) return;
28
-
29
- let result;
30
- try {
31
- result = await client.request("tools/list", {});
32
- } catch (err) {
33
- logger.warn(
34
- { err, server: server.id },
35
- "MCP server did not respond to tools/list",
36
- );
37
- return;
38
- }
39
-
40
- const tools = Array.isArray(result?.tools) ? result.tools : [];
41
- tools.forEach((tool) => {
42
- if (!tool || typeof tool !== "object") return;
43
- const remoteName = tool.name ?? tool.method;
44
- if (!remoteName) return;
45
- const localName = `${REMOTE_TOOL_PREFIX}_${sanitiseName(server.id)}_${sanitiseName(remoteName)}`;
46
- const descriptionParts = [];
47
- if (server.name) descriptionParts.push(`[${server.name}]`);
48
- if (tool.description) descriptionParts.push(tool.description);
49
- const description = descriptionParts.join(" ");
50
- const method = tool.method ?? tool.name;
51
-
52
- registerTool(
53
- localName,
54
- async ({ args = {} }) => {
55
- const payload =
56
- typeof args === "object" && args !== null ? args : {};
57
- const response = await client.request(method, payload);
58
- return {
59
- ok: true,
60
- status: 200,
61
- content: JSON.stringify(
62
- {
63
- server: server.id,
64
- tool: remoteName,
65
- result: response,
66
- },
67
- null,
68
- 2,
69
- ),
70
- metadata: {
71
- server: server.id,
72
- tool: remoteName,
73
- },
74
- };
75
- },
76
- { category: "mcp", description },
77
- );
78
- });
79
- } catch (err) {
80
- logger.warn({ err, server: server.id }, "Failed to register MCP remote tools");
81
- }
82
- }),
83
- );
84
- }
85
-
86
- module.exports = {
87
- registerRemoteTools,
88
- };
package/src/tools/mcp.js DELETED
@@ -1,116 +0,0 @@
1
- const { registerTool } = require(".");
2
- const { listServers, loadConfiguredServers, ensureClient } = require("../mcp");
3
- const { listSessions, releaseSession, isSandboxEnabled } = require("../mcp/sandbox");
4
- const { registerRemoteTools } = require("./mcp-remote");
5
- const logger = require("../logger");
6
-
7
- function formatJson(payload) {
8
- return JSON.stringify(payload, null, 2);
9
- }
10
-
11
- function registerMcpTools() {
12
- registerTool(
13
- "workspace_mcp_servers",
14
- async () => {
15
- loadConfiguredServers();
16
- registerRemoteTools().catch((err) => {
17
- logger.warn({ err }, "Failed to refresh MCP remote tools");
18
- });
19
- const servers = listServers().map((server) => ({
20
- id: server.id,
21
- name: server.name,
22
- description: server.description,
23
- command: server.command,
24
- args: server.args,
25
- transport: server.transport,
26
- metadata: server.metadata,
27
- }));
28
- return {
29
- ok: true,
30
- status: 200,
31
- content: formatJson({
32
- sandboxEnabled: isSandboxEnabled(),
33
- servers,
34
- }),
35
- };
36
- },
37
- { category: "mcp" },
38
- );
39
-
40
- registerTool("workspace_sandbox_sessions", async ({ args = {} }) => {
41
- const sessions = listSessions();
42
- if (args.release === true) {
43
- const target = typeof args.session_id === "string" ? args.session_id : null;
44
- if (target) {
45
- releaseSession(target);
46
- } else {
47
- sessions.forEach((session) => releaseSession(session.id));
48
- }
49
- return {
50
- ok: true,
51
- status: 200,
52
- content: formatJson({
53
- released: true,
54
- target: target ?? "all",
55
- }),
56
- };
57
- }
58
- return {
59
- ok: true,
60
- status: 200,
61
- content: formatJson({
62
- sandboxEnabled: isSandboxEnabled(),
63
- sessions,
64
- }),
65
- };
66
- }, { category: "mcp" });
67
-
68
- registerTool(
69
- "workspace_mcp_call",
70
- async ({ args = {} }) => {
71
- const serverId = args.server ?? args.server_id ?? args.serverId;
72
- const method = args.method ?? args.call;
73
- const params =
74
- typeof args.params === "object" && args.params !== null
75
- ? args.params
76
- : {};
77
- if (typeof serverId !== "string" || !serverId.trim()) {
78
- throw new Error("workspace_mcp_call requires a server id.");
79
- }
80
- if (typeof method !== "string" || !method.trim()) {
81
- throw new Error("workspace_mcp_call requires a method name.");
82
- }
83
- const client = await ensureClient(serverId.trim());
84
- if (!client) {
85
- throw new Error(`MCP server "${serverId}" is not available.`);
86
- }
87
- const result = await client.request(method.trim(), params);
88
- return {
89
- ok: true,
90
- status: 200,
91
- content: formatJson({
92
- server: serverId,
93
- method: method.trim(),
94
- result,
95
- }),
96
- metadata: {
97
- server: serverId,
98
- method: method.trim(),
99
- },
100
- };
101
- },
102
- { category: "mcp" },
103
- );
104
-
105
- registerRemoteTools()
106
- .then(() => {
107
- logger.info("Registered MCP remote tools");
108
- })
109
- .catch((err) => {
110
- logger.warn({ err }, "Failed to register MCP remote tools");
111
- });
112
- }
113
-
114
- module.exports = {
115
- registerMcpTools,
116
- };