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,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
- };