lynkr 9.9.1 → 9.11.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 (93) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +11 -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/public/dashboard.html +29 -0
  8. package/scripts/validate-difficulty-classifier.js +27 -6
  9. package/src/api/providers-handler.js +0 -1
  10. package/src/api/router.js +309 -160
  11. package/src/clients/databricks.js +215 -71
  12. package/src/clients/gpt-utils.js +8 -1
  13. package/src/clients/ollama-utils.js +19 -10
  14. package/src/clients/prompt-cache-injection.js +93 -30
  15. package/src/config/index.js +82 -43
  16. package/src/context/compression.js +27 -8
  17. package/src/context/distill.js +2 -1
  18. package/src/dashboard/api.js +5 -1
  19. package/src/memory/distiller.js +350 -0
  20. package/src/memory/extractor.js +0 -8
  21. package/src/memory/format.js +17 -2
  22. package/src/memory/index.js +10 -0
  23. package/src/memory/retriever.js +7 -2
  24. package/src/memory/skills-cache.js +180 -0
  25. package/src/memory/tencentdb-launcher.js +459 -0
  26. package/src/memory/wiki.js +171 -0
  27. package/src/orchestrator/index.js +202 -1237
  28. package/src/orchestrator/passthrough-stream.js +382 -0
  29. package/src/orchestrator/sse-transformer.js +408 -0
  30. package/src/prompts/system.js +20 -6
  31. package/src/routing/affinity-store.js +85 -4
  32. package/src/routing/agentic-detector.js +9 -1
  33. package/src/routing/cache-economics.js +123 -0
  34. package/src/routing/cache-switch-cost.js +194 -0
  35. package/src/routing/calibration.js +8 -1
  36. package/src/routing/complexity-analyzer.js +7 -1
  37. package/src/routing/context-compressor.js +170 -0
  38. package/src/routing/difficulty-classifier.js +161 -17
  39. package/src/routing/index.js +196 -56
  40. package/src/routing/intent-score.js +20 -2
  41. package/src/routing/model-registry.js +35 -10
  42. package/src/routing/session-affinity.js +68 -1
  43. package/src/routing/side-channel-detector.js +103 -0
  44. package/src/routing/telemetry.js +139 -2
  45. package/src/routing/verifier.js +41 -2
  46. package/src/server.js +36 -49
  47. package/src/tools/code-graph.js +6 -4
  48. package/src/agents/context-manager.js +0 -236
  49. package/src/agents/decomposition/dispatcher.js +0 -185
  50. package/src/agents/decomposition/gate.js +0 -136
  51. package/src/agents/decomposition/index.js +0 -183
  52. package/src/agents/decomposition/model-call.js +0 -75
  53. package/src/agents/decomposition/planner.js +0 -223
  54. package/src/agents/decomposition/synthesizer.js +0 -89
  55. package/src/agents/decomposition/telemetry.js +0 -55
  56. package/src/agents/definitions/loader.js +0 -653
  57. package/src/agents/executor.js +0 -457
  58. package/src/agents/index.js +0 -165
  59. package/src/agents/parallel-coordinator.js +0 -68
  60. package/src/agents/reflector.js +0 -331
  61. package/src/agents/skillbook.js +0 -331
  62. package/src/agents/store.js +0 -259
  63. package/src/edits/index.js +0 -171
  64. package/src/indexer/babel-parser.js +0 -213
  65. package/src/indexer/index.js +0 -1629
  66. package/src/indexer/navigation/index.js +0 -32
  67. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  68. package/src/indexer/parser.js +0 -443
  69. package/src/tasks/store.js +0 -349
  70. package/src/tests/coverage.js +0 -173
  71. package/src/tests/index.js +0 -171
  72. package/src/tests/store.js +0 -213
  73. package/src/tools/agent-task.js +0 -145
  74. package/src/tools/code-mode.js +0 -304
  75. package/src/tools/decompose.js +0 -91
  76. package/src/tools/edits.js +0 -94
  77. package/src/tools/execution.js +0 -171
  78. package/src/tools/git.js +0 -1346
  79. package/src/tools/index.js +0 -306
  80. package/src/tools/indexer.js +0 -360
  81. package/src/tools/lazy-loader.js +0 -366
  82. package/src/tools/mcp-remote.js +0 -88
  83. package/src/tools/mcp.js +0 -116
  84. package/src/tools/process.js +0 -167
  85. package/src/tools/smart-selection.js +0 -180
  86. package/src/tools/stubs.js +0 -55
  87. package/src/tools/tasks.js +0 -260
  88. package/src/tools/tests.js +0 -132
  89. package/src/tools/tinyfish.js +0 -358
  90. package/src/tools/truncate.js +0 -106
  91. package/src/tools/web-client.js +0 -71
  92. package/src/tools/web.js +0 -415
  93. 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
- };