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