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,306 +0,0 @@
1
- const logger = require("../logger");
2
- const { truncateToolOutput } = require("./truncate");
3
-
4
- const registry = new Map();
5
- const registryLowercase = new Map();
6
-
7
- // Lazy loader reference (set after module load to avoid circular deps)
8
- let lazyLoader = null;
9
-
10
- /**
11
- * Set the lazy loader module (called from server.js)
12
- * @param {Object} loader - The lazy-loader module
13
- */
14
- function setLazyLoader(loader) {
15
- lazyLoader = loader;
16
- }
17
-
18
- const TOOL_ALIASES = {
19
- bash: "shell",
20
- shell: "shell",
21
- sh: "shell",
22
- terminal: "shell",
23
- grep: "workspace_search",
24
- search: "workspace_search",
25
- find: "workspace_search",
26
- websearch: "web_search",
27
- web_search: "web_search",
28
- "Web Search": "web_search",
29
- WebSearch: "web_search",
30
- web_fetch: "web_fetch",
31
- webfetch: "web_fetch",
32
- web_agent: "web_agent",
33
- webagent: "web_agent",
34
- WebAgent: "web_agent",
35
- tinyfish: "web_agent",
36
- task: "Task",
37
- write: "fs_write",
38
- filewrite: "fs_write",
39
- read: "fs_read",
40
- fileread: "fs_read",
41
- patch: "edit_patch",
42
- edit: "edit_patch",
43
- list: "workspace_list",
44
- ls: "workspace_list",
45
- dir: "workspace_list",
46
- summary: "project_summary",
47
- projectsummary: "project_summary",
48
- overview: "project_summary",
49
- reindex: "workspace_index_rebuild",
50
- index: "workspace_index_rebuild",
51
- scan: "workspace_index_rebuild",
52
- history: "workspace_edit_history",
53
- edits: "workspace_edit_history",
54
- undo: "workspace_edit_revert",
55
- revert: "workspace_edit_revert",
56
- diff: "workspace_diff",
57
- diffsummary: "workspace_diff_summary",
58
- summarizediff: "workspace_diff_summary",
59
- diffsum: "workspace_diff_summary",
60
- symbol: "workspace_symbol_search",
61
- symbols: "workspace_symbol_search",
62
- findsymbol: "workspace_symbol_search",
63
- symbolsearch: "workspace_symbol_search",
64
- references: "workspace_symbol_references",
65
- ref: "workspace_symbol_references",
66
- findreferences: "workspace_symbol_references",
67
- usages: "workspace_symbol_references",
68
- status: "workspace_git_status",
69
- stage: "workspace_git_stage",
70
- unstage: "workspace_git_unstage",
71
- commit: "workspace_git_commit",
72
- push: "workspace_git_push",
73
- pull: "workspace_git_pull",
74
- branches: "workspace_git_branches",
75
- checkout: "workspace_git_checkout",
76
- branch: "workspace_git_checkout",
77
- stash: "workspace_git_stash",
78
- review: "workspace_diff_review",
79
- releasenotes: "workspace_release_notes",
80
- diffstat: "workspace_diff_by_commit",
81
- merge: "workspace_git_merge",
82
- rebase: "workspace_git_rebase",
83
- conflicts: "workspace_git_conflicts",
84
- patchplan: "workspace_git_patch_plan",
85
- changelog: "workspace_changelog_generate",
86
- prtemplate: "workspace_pr_template_generate",
87
- sandbox: "workspace_sandbox_sessions",
88
- mcpsessions: "workspace_sandbox_sessions",
89
- mcpservers: "workspace_mcp_servers",
90
- tests: "workspace_test_summary",
91
- testrun: "workspace_test_run",
92
- runtests: "workspace_test_run",
93
- testsummary: "workspace_test_summary",
94
- testhistory: "workspace_test_history",
95
- };
96
-
97
- function coerceString(value) {
98
- if (value === undefined || value === null) return "";
99
- if (typeof value === "string") return value;
100
- try {
101
- return JSON.stringify(value, null, 2);
102
- } catch {
103
- return String(value);
104
- }
105
- }
106
-
107
- function normalizeHandlerResult(result) {
108
- if (typeof result === "string") {
109
- return {
110
- ok: true,
111
- status: 200,
112
- content: result,
113
- metadata: {},
114
- };
115
- }
116
- if (result === undefined || result === null) {
117
- return {
118
- ok: true,
119
- status: 200,
120
- content: "",
121
- metadata: {},
122
- };
123
- }
124
- const ok = result.ok ?? true;
125
- const status = result.status ?? (ok ? 200 : 500);
126
- const content = coerceString(result.content ?? result.output ?? result.data ?? "");
127
- const metadata = result.metadata ?? {};
128
- return { ok, status, content, metadata };
129
- }
130
-
131
- function parseArguments(call) {
132
- const raw = call?.function?.arguments;
133
- if (typeof raw !== "string" || raw.trim().length === 0) return {};
134
- try {
135
- return JSON.parse(raw);
136
- } catch (err) {
137
- logger.warn({ err }, "Failed to parse tool arguments");
138
- return {};
139
- }
140
- }
141
-
142
- function normaliseToolCall(call) {
143
- const name = call?.function?.name ?? call?.name;
144
- const id = call?.id ?? `${name ?? "tool"}_${Date.now()}`;
145
- return {
146
- id,
147
- name,
148
- arguments: parseArguments(call),
149
- raw: call,
150
- };
151
- }
152
-
153
- function registerTool(name, handler, options = {}) {
154
- if (!name || typeof name !== "string") {
155
- throw new Error("Tool name must be a non-empty string.");
156
- }
157
- if (typeof handler !== "function") {
158
- throw new Error(`Tool "${name}" must be registered with a function handler.`);
159
- }
160
- registry.set(name, { handler, options });
161
- registryLowercase.set(name.toLowerCase(), { handler, options, original: name });
162
- logger.debug({ tool: name }, "Tool registered");
163
- }
164
-
165
- function hasTool(name) {
166
- return registry.has(name);
167
- }
168
-
169
- function getTool(name) {
170
- logger.debug({ tool: name }, "Getting tool");
171
- if (!name) return undefined;
172
- const direct = registry.get(name);
173
- if (direct) return direct;
174
- const lower = registryLowercase.get(name.toLowerCase());
175
- if (lower) return registry.get(lower.original);
176
- const aliasTarget = TOOL_ALIASES[name.toLowerCase()];
177
- if (aliasTarget) {
178
- const aliasEntry = registry.get(aliasTarget);
179
- if (aliasEntry) return aliasEntry;
180
- }
181
- return undefined;
182
- }
183
-
184
- function listTools() {
185
- return Array.from(registry.keys());
186
- }
187
-
188
- async function executeToolCall(call, context = {}) {
189
- const normalisedCall = normaliseToolCall(call);
190
- let registered = registry.get(normalisedCall.name);
191
- if (!registered) {
192
- const aliasTarget = TOOL_ALIASES[normalisedCall.name.toLowerCase()];
193
- if (aliasTarget) {
194
- registered = registry.get(aliasTarget);
195
- if (registered) {
196
- normalisedCall.name = aliasTarget;
197
- }
198
- }
199
- }
200
- if (!registered) {
201
- const lowerEntry = registryLowercase.get(normalisedCall.name.toLowerCase());
202
- if (lowerEntry) {
203
- registered = registry.get(lowerEntry.original);
204
- normalisedCall.name = lowerEntry.original;
205
- }
206
- }
207
-
208
- // Lazy loading: If tool not found, try to load its category
209
- if (!registered && lazyLoader) {
210
- const loaded = lazyLoader.loadCategoryForTool(normalisedCall.name);
211
- if (loaded) {
212
- // Retry lookup after loading
213
- registered = registry.get(normalisedCall.name);
214
- if (!registered) {
215
- const aliasTarget = TOOL_ALIASES[normalisedCall.name.toLowerCase()];
216
- if (aliasTarget) {
217
- registered = registry.get(aliasTarget);
218
- if (registered) normalisedCall.name = aliasTarget;
219
- }
220
- }
221
- if (!registered) {
222
- const lowerEntry = registryLowercase.get(normalisedCall.name.toLowerCase());
223
- if (lowerEntry) {
224
- registered = registry.get(lowerEntry.original);
225
- normalisedCall.name = lowerEntry.original;
226
- }
227
- }
228
- }
229
- }
230
-
231
- if (!registered) {
232
- const content = coerceString({
233
- error: "tool_not_registered",
234
- tool: normalisedCall.name,
235
- input: normalisedCall.arguments,
236
- });
237
- return {
238
- id: normalisedCall.id,
239
- name: normalisedCall.name,
240
- arguments: normalisedCall.arguments,
241
- ok: false,
242
- status: 404,
243
- content,
244
- metadata: { registered: false },
245
- };
246
- }
247
-
248
- try {
249
- const result = await registered.handler(
250
- {
251
- id: normalisedCall.id,
252
- name: normalisedCall.name,
253
- args: normalisedCall.arguments,
254
- raw: normalisedCall.raw,
255
- },
256
- context,
257
- );
258
- const formatted = normalizeHandlerResult(result);
259
-
260
- const truncatedContent = truncateToolOutput(normalisedCall.name, formatted.content);
261
-
262
- return {
263
- id: normalisedCall.id,
264
- name: normalisedCall.name,
265
- arguments: normalisedCall.arguments,
266
- ...formatted,
267
- content: truncatedContent,
268
- metadata: {
269
- ...(formatted.metadata ?? {}),
270
- registered: true,
271
- truncated: truncatedContent !== formatted.content,
272
- originalLength: formatted.content?.length,
273
- truncatedLength: truncatedContent?.length,
274
- },
275
- };
276
- } catch (err) {
277
- logger.error({ err, tool: normalisedCall.name }, "Tool execution failed");
278
- return {
279
- id: normalisedCall.id,
280
- name: normalisedCall.name,
281
- arguments: normalisedCall.arguments,
282
- ok: false,
283
- status: 500,
284
- content: coerceString({
285
- error: "tool_execution_failed",
286
- tool: normalisedCall.name,
287
- message: err.message,
288
- }),
289
- metadata: {
290
- registered: true,
291
- error: true,
292
- },
293
- error: err,
294
- };
295
- }
296
- }
297
-
298
- module.exports = {
299
- registerTool,
300
- hasTool,
301
- getTool,
302
- listTools,
303
- executeToolCall,
304
- setLazyLoader,
305
- TOOL_ALIASES,
306
- };
@@ -1,360 +0,0 @@
1
- const {
2
- listWorkspaceFiles,
3
- searchWorkspace,
4
- rebuildWorkspaceIndex,
5
- getProjectSummary,
6
- searchSymbols,
7
- searchSymbolReferences,
8
- findDefinitionNearLocation,
9
- listDefinitionsBySymbol,
10
- } = require("../indexer");
11
- const { registerTool } = require(".");
12
- const { getTaskSummary } = require("../tasks/store");
13
- const { getTestSummary } = require("../tests");
14
-
15
- function registerWorkspaceListTool() {
16
- registerTool(
17
- "workspace_list",
18
- async ({ args = {} }) => {
19
- const patterns =
20
- typeof args.patterns === "string"
21
- ? [args.patterns]
22
- : Array.isArray(args.patterns)
23
- ? args.patterns
24
- : undefined;
25
- const ignore =
26
- typeof args.ignore === "string"
27
- ? [args.ignore]
28
- : Array.isArray(args.ignore)
29
- ? args.ignore
30
- : undefined;
31
- const limit = Number.isInteger(args.limit) ? args.limit : undefined;
32
- const withStats = args.with_stats === true || args.include_stats === true;
33
- const includeDirectories = args.include_directories === true;
34
-
35
- const entries = await listWorkspaceFiles({
36
- patterns,
37
- ignore,
38
- limit,
39
- withStats,
40
- includeDirectories,
41
- });
42
-
43
- return {
44
- ok: true,
45
- status: 200,
46
- content: JSON.stringify({ entries }, null, 2),
47
- metadata: {
48
- total: entries.length,
49
- },
50
- };
51
- },
52
- { category: "indexing" },
53
- );
54
- }
55
-
56
- function registerWorkspaceSearchTool() {
57
- registerTool(
58
- "workspace_search",
59
- async ({ args = {} }) => {
60
- const query = args.query ?? args.term ?? args.pattern;
61
- const regex = args.regex === true || args.is_regex === true;
62
- const limit = Number.isInteger(args.limit) ? args.limit : undefined;
63
- const ignore =
64
- typeof args.ignore === "string"
65
- ? [args.ignore]
66
- : Array.isArray(args.ignore)
67
- ? args.ignore
68
- : undefined;
69
-
70
- const result = await searchWorkspace({
71
- query,
72
- regex,
73
- limit,
74
- ignore,
75
- });
76
-
77
- return {
78
- ok: true,
79
- status: 200,
80
- content: JSON.stringify(result, null, 2),
81
- metadata: {
82
- total: result.matches.length,
83
- engine: result.engine,
84
- },
85
- };
86
- },
87
- { category: "indexing" },
88
- );
89
- }
90
-
91
- function registerWorkspaceRebuildTool() {
92
- registerTool(
93
- "workspace_index_rebuild",
94
- async ({ args = {} }) => {
95
- const patterns =
96
- typeof args.patterns === "string"
97
- ? [args.patterns]
98
- : Array.isArray(args.patterns)
99
- ? args.patterns
100
- : undefined;
101
- const ignore =
102
- typeof args.ignore === "string"
103
- ? [args.ignore]
104
- : Array.isArray(args.ignore)
105
- ? args.ignore
106
- : undefined;
107
-
108
- const summary = await rebuildWorkspaceIndex({
109
- patterns,
110
- ignore,
111
- });
112
-
113
- return {
114
- ok: true,
115
- status: 200,
116
- content: JSON.stringify(summary, null, 2),
117
- metadata: {
118
- fileCount: summary.fileCount,
119
- languages: summary.languages,
120
- frameworks: summary.frameworks,
121
- },
122
- };
123
- },
124
- { category: "indexing" },
125
- );
126
- }
127
-
128
- function registerProjectSummaryTool() {
129
- registerTool(
130
- "project_summary",
131
- async ({ args = {} }) => {
132
- let summary = getProjectSummary();
133
- if (args.rebuild === true || args.refresh === true) {
134
- summary = await rebuildWorkspaceIndex({});
135
- }
136
-
137
- const tasksLimitRaw =
138
- args.tasks_limit ?? args.tasksLimit ?? args.tasks_sample ?? args.tasksSample;
139
- const tasks = getTaskSummary({
140
- limit:
141
- tasksLimitRaw === undefined || tasksLimitRaw === null
142
- ? undefined
143
- : Number.isFinite(Number(tasksLimitRaw))
144
- ? Number(tasksLimitRaw)
145
- : undefined,
146
- });
147
-
148
- const tests = getTestSummary({
149
- includeRecent: args.tests_recent === true || args.testsRecent === true,
150
- recentLimit:
151
- args.tests_recent_limit ?? args.testsRecentLimit ?? args.tests_recent_sample ?? args.testsRecentSample,
152
- });
153
-
154
- const payload = {
155
- ...summary,
156
- tasks,
157
- tests,
158
- };
159
-
160
- return {
161
- ok: true,
162
- status: 200,
163
- content: JSON.stringify(payload, null, 2),
164
- metadata: {
165
- fileCount: summary.fileCount,
166
- languages: summary.languages,
167
- frameworks: summary.frameworks,
168
- tasks: tasks.total,
169
- tests: tests.totalRuns ?? 0,
170
- },
171
- };
172
- },
173
- { category: "indexing" },
174
- );
175
- }
176
-
177
- function registerSymbolSearchTool() {
178
- registerTool(
179
- "workspace_symbol_search",
180
- async ({ args = {} }) => {
181
- const query = args.query ?? args.name ?? args.symbol;
182
- const limit =
183
- typeof args.limit === "number" && args.limit > 0 ? Math.min(args.limit, 100) : 50;
184
- const language =
185
- typeof args.language === "string" ? args.language.toLowerCase() : undefined;
186
- const file =
187
- typeof args.path === "string"
188
- ? args.path
189
- : typeof args.file === "string"
190
- ? args.file
191
- : undefined;
192
-
193
- const results = searchSymbols({
194
- query,
195
- language,
196
- limit,
197
- filePath: file,
198
- });
199
-
200
- return {
201
- ok: true,
202
- status: 200,
203
- content: JSON.stringify(
204
- {
205
- query,
206
- language,
207
- file,
208
- results,
209
- },
210
- null,
211
- 2,
212
- ),
213
- metadata: {
214
- total: results.length,
215
- },
216
- };
217
- },
218
- { category: "indexing" },
219
- );
220
- }
221
-
222
- function registerSymbolReferencesTool() {
223
- registerTool(
224
- "workspace_symbol_references",
225
- async ({ args = {} }) => {
226
- const symbol = args.symbol ?? args.name ?? args.query;
227
- const limit =
228
- typeof args.limit === "number" && args.limit > 0 ? Math.min(args.limit, 100) : 50;
229
- const file =
230
- typeof args.path === "string"
231
- ? args.path
232
- : typeof args.file === "string"
233
- ? args.file
234
- : undefined;
235
-
236
- const results = searchSymbolReferences({
237
- symbol,
238
- filePath: file,
239
- limit,
240
- });
241
-
242
- return {
243
- ok: true,
244
- status: 200,
245
- content: JSON.stringify(
246
- {
247
- symbol,
248
- file,
249
- results,
250
- },
251
- null,
252
- 2,
253
- ),
254
- metadata: {
255
- total: results.length,
256
- },
257
- };
258
- },
259
- { category: "indexing" },
260
- );
261
- }
262
-
263
- function registerGotoDefinitionTool() {
264
- registerTool(
265
- "workspace_goto_definition",
266
- async ({ args = {} }) => {
267
- const filePath =
268
- typeof args.file === "string"
269
- ? args.file
270
- : typeof args.path === "string"
271
- ? args.path
272
- : null;
273
- const line =
274
- typeof args.line === "number"
275
- ? args.line
276
- : typeof args.row === "number"
277
- ? args.row
278
- : null;
279
- const column =
280
- typeof args.column === "number"
281
- ? args.column
282
- : typeof args.col === "number"
283
- ? args.col
284
- : null;
285
- const symbolName =
286
- typeof args.symbol === "string"
287
- ? args.symbol
288
- : typeof args.name === "string"
289
- ? args.name
290
- : null;
291
-
292
- if (filePath && line) {
293
- const rows = findDefinitionNearLocation({ filePath, line, column });
294
- return {
295
- ok: true,
296
- status: 200,
297
- content: JSON.stringify(
298
- {
299
- filePath,
300
- line,
301
- column: column ?? null,
302
- results: rows.map((row) => ({
303
- symbol: row.name,
304
- kind: row.kind,
305
- definition: {
306
- filePath: row.definition_path,
307
- line: row.definition_line,
308
- column: row.definition_column,
309
- },
310
- reference: {
311
- filePath: row.reference_path,
312
- line: row.reference_line,
313
- column: row.reference_column,
314
- snippet: row.snippet ?? null,
315
- },
316
- })),
317
- },
318
- null,
319
- 2,
320
- ),
321
- };
322
- }
323
-
324
- if (symbolName) {
325
- const rows = listDefinitionsBySymbol({ name: symbolName });
326
- return {
327
- ok: true,
328
- status: 200,
329
- content: JSON.stringify(
330
- {
331
- symbol: symbolName,
332
- definitions: rows,
333
- },
334
- null,
335
- 2,
336
- ),
337
- };
338
- }
339
-
340
- throw new Error(
341
- "workspace_goto_definition requires either (file,line[,column]) or symbol name.",
342
- );
343
- },
344
- { category: "indexing" },
345
- );
346
- }
347
-
348
- function registerIndexerTools() {
349
- registerWorkspaceListTool();
350
- registerWorkspaceSearchTool();
351
- registerWorkspaceRebuildTool();
352
- registerProjectSummaryTool();
353
- registerSymbolSearchTool();
354
- registerSymbolReferencesTool();
355
- registerGotoDefinitionTool();
356
- }
357
-
358
- module.exports = {
359
- registerIndexerTools,
360
- };