@kernel.chat/kbot 3.51.0 → 3.52.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.
- package/README.md +43 -9
- package/dist/agent-protocol.test.d.ts +2 -0
- package/dist/agent-protocol.test.d.ts.map +1 -0
- package/dist/agent-protocol.test.js +730 -0
- package/dist/agent-protocol.test.js.map +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +34 -10
- package/dist/agent.js.map +1 -1
- package/dist/auth.js +3 -3
- package/dist/auth.js.map +1 -1
- package/dist/bench.d.ts +64 -0
- package/dist/bench.d.ts.map +1 -0
- package/dist/bench.js +973 -0
- package/dist/bench.js.map +1 -0
- package/dist/cli.js +144 -29
- package/dist/cli.js.map +1 -1
- package/dist/cloud-agent.d.ts +77 -0
- package/dist/cloud-agent.d.ts.map +1 -0
- package/dist/cloud-agent.js +743 -0
- package/dist/cloud-agent.js.map +1 -0
- package/dist/context.test.d.ts +2 -0
- package/dist/context.test.d.ts.map +1 -0
- package/dist/context.test.js +561 -0
- package/dist/context.test.js.map +1 -0
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +4 -1
- package/dist/evolution.js.map +1 -1
- package/dist/github-release.d.ts +61 -0
- package/dist/github-release.d.ts.map +1 -0
- package/dist/github-release.js +451 -0
- package/dist/github-release.js.map +1 -0
- package/dist/graph-memory.test.d.ts +2 -0
- package/dist/graph-memory.test.d.ts.map +1 -0
- package/dist/graph-memory.test.js +946 -0
- package/dist/graph-memory.test.js.map +1 -0
- package/dist/init-science.d.ts +43 -0
- package/dist/init-science.d.ts.map +1 -0
- package/dist/init-science.js +477 -0
- package/dist/init-science.js.map +1 -0
- package/dist/lab.d.ts +45 -0
- package/dist/lab.d.ts.map +1 -0
- package/dist/lab.js +1020 -0
- package/dist/lab.js.map +1 -0
- package/dist/lsp-deep.d.ts +101 -0
- package/dist/lsp-deep.d.ts.map +1 -0
- package/dist/lsp-deep.js +689 -0
- package/dist/lsp-deep.js.map +1 -0
- package/dist/memory.test.d.ts +2 -0
- package/dist/memory.test.d.ts.map +1 -0
- package/dist/memory.test.js +369 -0
- package/dist/memory.test.js.map +1 -0
- package/dist/multi-session.d.ts +164 -0
- package/dist/multi-session.d.ts.map +1 -0
- package/dist/multi-session.js +885 -0
- package/dist/multi-session.js.map +1 -0
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +5 -2
- package/dist/self-eval.js.map +1 -1
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +0 -1
- package/dist/streaming.js.map +1 -1
- package/dist/teach.d.ts +136 -0
- package/dist/teach.d.ts.map +1 -0
- package/dist/teach.js +915 -0
- package/dist/teach.js.map +1 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/browser-agent.js +2 -2
- package/dist/tools/browser-agent.js.map +1 -1
- package/dist/tools/forge.d.ts.map +1 -1
- package/dist/tools/forge.js +15 -26
- package/dist/tools/forge.js.map +1 -1
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +10 -7
- package/dist/tools/git.js.map +1 -1
- package/dist/voice-realtime.d.ts +54 -0
- package/dist/voice-realtime.d.ts.map +1 -0
- package/dist/voice-realtime.js +805 -0
- package/dist/voice-realtime.js.map +1 -0
- package/package.json +10 -3
package/dist/lsp-deep.js
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
// kbot Deep LSP Integration
|
|
2
|
+
//
|
|
3
|
+
// Wires language servers deeper into the agent loop:
|
|
4
|
+
// 1. Auto-attach to the right LSP server for the project
|
|
5
|
+
// 2. Proactive diagnostics monitoring injected into agent context
|
|
6
|
+
// 3. Type-aware file context enrichment (hover/type info)
|
|
7
|
+
// 4. Smart reference checking before modifying symbols
|
|
8
|
+
// 5. Auto-fix suggestions for common diagnostic codes
|
|
9
|
+
//
|
|
10
|
+
// Unlike lsp-bridge.ts (one-shot diagnostics), this module maintains
|
|
11
|
+
// a persistent LSP session for continuous intelligence.
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
14
|
+
import { join, extname, resolve, relative } from 'node:path';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
const LSP_SERVERS = {
|
|
17
|
+
typescript: {
|
|
18
|
+
command: ['typescript-language-server', '--stdio'],
|
|
19
|
+
languageIds: ['typescript', 'typescriptreact', 'javascript', 'javascriptreact'],
|
|
20
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
21
|
+
detectBinary: 'typescript-language-server',
|
|
22
|
+
},
|
|
23
|
+
python: {
|
|
24
|
+
command: ['pyright-langserver', '--stdio'],
|
|
25
|
+
languageIds: ['python'],
|
|
26
|
+
extensions: ['.py'],
|
|
27
|
+
detectBinary: 'pyright-langserver',
|
|
28
|
+
},
|
|
29
|
+
'python-pylsp': {
|
|
30
|
+
command: ['pylsp'],
|
|
31
|
+
languageIds: ['python'],
|
|
32
|
+
extensions: ['.py'],
|
|
33
|
+
detectBinary: 'pylsp',
|
|
34
|
+
},
|
|
35
|
+
rust: {
|
|
36
|
+
command: ['rust-analyzer'],
|
|
37
|
+
languageIds: ['rust'],
|
|
38
|
+
extensions: ['.rs'],
|
|
39
|
+
detectBinary: 'rust-analyzer',
|
|
40
|
+
},
|
|
41
|
+
go: {
|
|
42
|
+
command: ['gopls', 'serve'],
|
|
43
|
+
languageIds: ['go', 'gomod'],
|
|
44
|
+
extensions: ['.go'],
|
|
45
|
+
detectBinary: 'gopls',
|
|
46
|
+
},
|
|
47
|
+
clangd: {
|
|
48
|
+
command: ['clangd'],
|
|
49
|
+
languageIds: ['c', 'cpp', 'objc', 'objcpp'],
|
|
50
|
+
extensions: ['.c', '.cpp', '.cc', '.cxx', '.h', '.hpp'],
|
|
51
|
+
detectBinary: 'clangd',
|
|
52
|
+
},
|
|
53
|
+
java: {
|
|
54
|
+
command: ['jdtls'],
|
|
55
|
+
languageIds: ['java'],
|
|
56
|
+
extensions: ['.java'],
|
|
57
|
+
detectBinary: 'jdtls',
|
|
58
|
+
},
|
|
59
|
+
lua: {
|
|
60
|
+
command: ['lua-language-server'],
|
|
61
|
+
languageIds: ['lua'],
|
|
62
|
+
extensions: ['.lua'],
|
|
63
|
+
detectBinary: 'lua-language-server',
|
|
64
|
+
},
|
|
65
|
+
ruby: {
|
|
66
|
+
command: ['solargraph', 'stdio'],
|
|
67
|
+
languageIds: ['ruby'],
|
|
68
|
+
extensions: ['.rb'],
|
|
69
|
+
detectBinary: 'solargraph',
|
|
70
|
+
},
|
|
71
|
+
zig: {
|
|
72
|
+
command: ['zls'],
|
|
73
|
+
languageIds: ['zig'],
|
|
74
|
+
extensions: ['.zig'],
|
|
75
|
+
detectBinary: 'zls',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
// ── Internal State ──
|
|
79
|
+
let activeServer = null;
|
|
80
|
+
let activeConfig = null;
|
|
81
|
+
let activeProjectDir = null;
|
|
82
|
+
let messageId = 0;
|
|
83
|
+
let buffer = '';
|
|
84
|
+
let initialized = false;
|
|
85
|
+
let fileWatcher = null;
|
|
86
|
+
const context = {
|
|
87
|
+
diagnostics: [],
|
|
88
|
+
symbols: [],
|
|
89
|
+
references: new Map(),
|
|
90
|
+
typeInfo: new Map(),
|
|
91
|
+
serverCapabilities: {},
|
|
92
|
+
};
|
|
93
|
+
// Pending request callbacks
|
|
94
|
+
const pendingRequests = new Map();
|
|
95
|
+
function encodeMessage(msg) {
|
|
96
|
+
const body = JSON.stringify(msg);
|
|
97
|
+
return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
|
|
98
|
+
}
|
|
99
|
+
function parseMessages(data) {
|
|
100
|
+
const messages = [];
|
|
101
|
+
let remaining = data;
|
|
102
|
+
while (remaining.length > 0) {
|
|
103
|
+
const headerEnd = remaining.indexOf('\r\n\r\n');
|
|
104
|
+
if (headerEnd === -1)
|
|
105
|
+
break;
|
|
106
|
+
const header = remaining.slice(0, headerEnd);
|
|
107
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
108
|
+
if (!lengthMatch)
|
|
109
|
+
break;
|
|
110
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
111
|
+
const bodyStart = headerEnd + 4;
|
|
112
|
+
const body = remaining.slice(bodyStart, bodyStart + contentLength);
|
|
113
|
+
if (body.length < contentLength)
|
|
114
|
+
break;
|
|
115
|
+
try {
|
|
116
|
+
messages.push(JSON.parse(body));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
if (process.env.KBOT_DEBUG) {
|
|
120
|
+
process.stderr.write('[lsp-deep] malformed JSON-RPC message\n');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
remaining = remaining.slice(bodyStart + contentLength);
|
|
124
|
+
}
|
|
125
|
+
return { messages, remaining };
|
|
126
|
+
}
|
|
127
|
+
function mapSeverity(severity) {
|
|
128
|
+
switch (severity) {
|
|
129
|
+
case 1: return 'error';
|
|
130
|
+
case 2: return 'warning';
|
|
131
|
+
case 3: return 'info';
|
|
132
|
+
case 4: return 'hint';
|
|
133
|
+
default: return 'warning';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const SYMBOL_KIND_MAP = {
|
|
137
|
+
1: 'file', 2: 'module', 3: 'namespace', 4: 'package', 5: 'class',
|
|
138
|
+
6: 'method', 7: 'property', 8: 'field', 9: 'constructor', 10: 'enum',
|
|
139
|
+
11: 'interface', 12: 'function', 13: 'variable', 14: 'constant', 15: 'string',
|
|
140
|
+
16: 'number', 17: 'boolean', 18: 'array', 19: 'object', 20: 'key',
|
|
141
|
+
21: 'null', 22: 'enum_member', 23: 'struct', 24: 'event', 25: 'operator',
|
|
142
|
+
26: 'type_parameter',
|
|
143
|
+
};
|
|
144
|
+
function getLanguageId(filePath) {
|
|
145
|
+
const ext = extname(filePath).toLowerCase();
|
|
146
|
+
const map = {
|
|
147
|
+
'.ts': 'typescript', '.tsx': 'typescriptreact',
|
|
148
|
+
'.js': 'javascript', '.jsx': 'javascriptreact',
|
|
149
|
+
'.py': 'python', '.rs': 'rust', '.go': 'go',
|
|
150
|
+
'.c': 'c', '.cpp': 'cpp', '.cc': 'cpp', '.h': 'c', '.hpp': 'cpp',
|
|
151
|
+
'.java': 'java', '.rb': 'ruby', '.lua': 'lua', '.zig': 'zig',
|
|
152
|
+
};
|
|
153
|
+
return map[ext] || 'plaintext';
|
|
154
|
+
}
|
|
155
|
+
// ── LSP Communication ──
|
|
156
|
+
function sendRequest(method, params, timeoutMs = 10000) {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
if (!activeServer?.stdin) {
|
|
159
|
+
reject(new Error('LSP server not running'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const id = ++messageId;
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
pendingRequests.delete(id);
|
|
165
|
+
reject(new Error(`LSP request ${method} timed out after ${timeoutMs}ms`));
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
pendingRequests.set(id, { resolve, reject, timer });
|
|
168
|
+
activeServer.stdin.write(encodeMessage({
|
|
169
|
+
jsonrpc: '2.0',
|
|
170
|
+
id,
|
|
171
|
+
method,
|
|
172
|
+
params,
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function sendNotification(method, params) {
|
|
177
|
+
if (!activeServer?.stdin)
|
|
178
|
+
return;
|
|
179
|
+
activeServer.stdin.write(encodeMessage({
|
|
180
|
+
jsonrpc: '2.0',
|
|
181
|
+
method,
|
|
182
|
+
params,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
function handleMessage(msg) {
|
|
186
|
+
// Response to a request
|
|
187
|
+
if (msg.id !== undefined && pendingRequests.has(msg.id)) {
|
|
188
|
+
const pending = pendingRequests.get(msg.id);
|
|
189
|
+
pendingRequests.delete(msg.id);
|
|
190
|
+
clearTimeout(pending.timer);
|
|
191
|
+
if (msg.error) {
|
|
192
|
+
pending.reject(new Error(msg.error.message));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
pending.resolve(msg.result);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Notification from server
|
|
200
|
+
if (msg.method === 'textDocument/publishDiagnostics') {
|
|
201
|
+
handleDiagnostics(msg.params);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function handleDiagnostics(params) {
|
|
205
|
+
const file = params.uri.replace('file://', '');
|
|
206
|
+
// Remove old diagnostics for this file
|
|
207
|
+
context.diagnostics = context.diagnostics.filter(d => d.file !== file);
|
|
208
|
+
// Add new diagnostics
|
|
209
|
+
for (const d of params.diagnostics) {
|
|
210
|
+
context.diagnostics.push({
|
|
211
|
+
file,
|
|
212
|
+
line: d.range.start.line + 1,
|
|
213
|
+
column: d.range.start.character + 1,
|
|
214
|
+
severity: mapSeverity(d.severity),
|
|
215
|
+
message: d.message,
|
|
216
|
+
source: d.source || 'lsp',
|
|
217
|
+
code: d.code !== undefined ? String(d.code) : undefined,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// ── Project Detection ──
|
|
222
|
+
function detectProjectServer(projectDir) {
|
|
223
|
+
// Check for project markers and pick the right server
|
|
224
|
+
const markers = [
|
|
225
|
+
{ file: 'tsconfig.json', server: 'typescript' },
|
|
226
|
+
{ file: 'jsconfig.json', server: 'typescript' },
|
|
227
|
+
{ file: 'package.json', server: 'typescript' },
|
|
228
|
+
{ file: 'pyproject.toml', server: 'python' },
|
|
229
|
+
{ file: 'setup.py', server: 'python' },
|
|
230
|
+
{ file: 'requirements.txt', server: 'python' },
|
|
231
|
+
{ file: 'Cargo.toml', server: 'rust' },
|
|
232
|
+
{ file: 'go.mod', server: 'go' },
|
|
233
|
+
{ file: 'CMakeLists.txt', server: 'clangd' },
|
|
234
|
+
{ file: 'Makefile', server: 'clangd' },
|
|
235
|
+
{ file: 'pom.xml', server: 'java' },
|
|
236
|
+
{ file: 'build.gradle', server: 'java' },
|
|
237
|
+
{ file: 'Gemfile', server: 'ruby' },
|
|
238
|
+
{ file: 'build.zig', server: 'zig' },
|
|
239
|
+
];
|
|
240
|
+
for (const marker of markers) {
|
|
241
|
+
if (existsSync(join(projectDir, marker.file))) {
|
|
242
|
+
const config = LSP_SERVERS[marker.server];
|
|
243
|
+
if (config && isBinaryAvailable(config.detectBinary)) {
|
|
244
|
+
return config;
|
|
245
|
+
}
|
|
246
|
+
// If primary not available, try fallback (e.g., pylsp for python)
|
|
247
|
+
if (marker.server === 'python') {
|
|
248
|
+
const fallback = LSP_SERVERS['python-pylsp'];
|
|
249
|
+
if (fallback && isBinaryAvailable(fallback.detectBinary)) {
|
|
250
|
+
return fallback;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Fallback: scan file extensions
|
|
256
|
+
try {
|
|
257
|
+
const entries = readdirSync(projectDir).slice(0, 100);
|
|
258
|
+
const extCounts = {};
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
const ext = extname(entry).toLowerCase();
|
|
261
|
+
if (ext)
|
|
262
|
+
extCounts[ext] = (extCounts[ext] || 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
// Find the most common extension that has an LSP server
|
|
265
|
+
const sorted = Object.entries(extCounts).sort((a, b) => b[1] - a[1]);
|
|
266
|
+
for (const [ext] of sorted) {
|
|
267
|
+
for (const config of Object.values(LSP_SERVERS)) {
|
|
268
|
+
if (config.extensions.includes(ext) && isBinaryAvailable(config.detectBinary)) {
|
|
269
|
+
return config;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch { /* ignore */ }
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
function isBinaryAvailable(binary) {
|
|
278
|
+
try {
|
|
279
|
+
execSync(`which ${binary}`, { stdio: 'ignore', timeout: 3000 });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// ── Public API ──
|
|
287
|
+
/**
|
|
288
|
+
* Attach to the appropriate language server for the project.
|
|
289
|
+
*
|
|
290
|
+
* Detects the project type, spawns the right LSP server, initializes
|
|
291
|
+
* the protocol, and begins monitoring diagnostics. Returns the live
|
|
292
|
+
* LSPContext that updates as the server reports new information.
|
|
293
|
+
*/
|
|
294
|
+
export async function attachLSP(projectDir) {
|
|
295
|
+
const absDir = resolve(projectDir);
|
|
296
|
+
// Detach existing session if any
|
|
297
|
+
if (activeServer) {
|
|
298
|
+
detachLSP();
|
|
299
|
+
}
|
|
300
|
+
const serverConfig = detectProjectServer(absDir);
|
|
301
|
+
if (!serverConfig) {
|
|
302
|
+
if (process.env.KBOT_DEBUG) {
|
|
303
|
+
process.stderr.write('[lsp-deep] no suitable language server found\n');
|
|
304
|
+
}
|
|
305
|
+
return context;
|
|
306
|
+
}
|
|
307
|
+
activeConfig = serverConfig;
|
|
308
|
+
activeProjectDir = absDir;
|
|
309
|
+
messageId = 0;
|
|
310
|
+
buffer = '';
|
|
311
|
+
initialized = false;
|
|
312
|
+
context.diagnostics = [];
|
|
313
|
+
context.symbols = [];
|
|
314
|
+
context.references.clear();
|
|
315
|
+
context.typeInfo.clear();
|
|
316
|
+
context.serverCapabilities = {};
|
|
317
|
+
// Spawn the server
|
|
318
|
+
try {
|
|
319
|
+
activeServer = spawn(serverConfig.command[0], serverConfig.command.slice(1), {
|
|
320
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
321
|
+
cwd: absDir,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
if (process.env.KBOT_DEBUG) {
|
|
326
|
+
process.stderr.write(`[lsp-deep] spawn failed: ${err.message}\n`);
|
|
327
|
+
}
|
|
328
|
+
return context;
|
|
329
|
+
}
|
|
330
|
+
activeServer.on('error', (err) => {
|
|
331
|
+
if (process.env.KBOT_DEBUG) {
|
|
332
|
+
process.stderr.write(`[lsp-deep] server error: ${err.message}\n`);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
activeServer.on('exit', (code) => {
|
|
336
|
+
if (process.env.KBOT_DEBUG) {
|
|
337
|
+
process.stderr.write(`[lsp-deep] server exited with code ${code}\n`);
|
|
338
|
+
}
|
|
339
|
+
activeServer = null;
|
|
340
|
+
initialized = false;
|
|
341
|
+
});
|
|
342
|
+
// Handle incoming messages
|
|
343
|
+
activeServer.stdout?.on('data', (chunk) => {
|
|
344
|
+
buffer += chunk.toString();
|
|
345
|
+
const { messages, remaining } = parseMessages(buffer);
|
|
346
|
+
buffer = remaining;
|
|
347
|
+
for (const msg of messages) {
|
|
348
|
+
handleMessage(msg);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
// Initialize the LSP protocol
|
|
352
|
+
const rootUri = `file://${absDir}`;
|
|
353
|
+
try {
|
|
354
|
+
const initResult = await sendRequest('initialize', {
|
|
355
|
+
processId: process.pid,
|
|
356
|
+
capabilities: {
|
|
357
|
+
textDocument: {
|
|
358
|
+
publishDiagnostics: { relatedInformation: true, codeDescriptionSupport: true },
|
|
359
|
+
hover: { contentFormat: ['plaintext', 'markdown'] },
|
|
360
|
+
completion: { completionItem: { snippetSupport: false } },
|
|
361
|
+
definition: { linkSupport: false },
|
|
362
|
+
references: {},
|
|
363
|
+
documentSymbol: { hierarchicalDocumentSymbolSupport: true },
|
|
364
|
+
},
|
|
365
|
+
workspace: {
|
|
366
|
+
workspaceFolders: true,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
rootUri,
|
|
370
|
+
workspaceFolders: [{ uri: rootUri, name: 'workspace' }],
|
|
371
|
+
}, 15000);
|
|
372
|
+
// Parse server capabilities
|
|
373
|
+
if (initResult && typeof initResult === 'object') {
|
|
374
|
+
const caps = initResult.capabilities || {};
|
|
375
|
+
context.serverCapabilities = {
|
|
376
|
+
hover: !!caps.hoverProvider,
|
|
377
|
+
definition: !!caps.definitionProvider,
|
|
378
|
+
references: !!caps.referencesProvider,
|
|
379
|
+
documentSymbol: !!caps.documentSymbolProvider,
|
|
380
|
+
completion: !!caps.completionProvider,
|
|
381
|
+
rename: !!caps.renameProvider,
|
|
382
|
+
codeAction: !!caps.codeActionProvider,
|
|
383
|
+
formatting: !!caps.documentFormattingProvider,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
// Send initialized notification
|
|
387
|
+
sendNotification('initialized', {});
|
|
388
|
+
initialized = true;
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
if (process.env.KBOT_DEBUG) {
|
|
392
|
+
process.stderr.write(`[lsp-deep] initialize failed: ${err.message}\n`);
|
|
393
|
+
}
|
|
394
|
+
detachLSP();
|
|
395
|
+
return context;
|
|
396
|
+
}
|
|
397
|
+
return context;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get all current diagnostics, optionally filtered to errors/warnings only.
|
|
401
|
+
*
|
|
402
|
+
* Returns a snapshot of diagnostics from the LSP server. Use this
|
|
403
|
+
* to inject context into the agent prompt like:
|
|
404
|
+
* "There are 3 TypeScript errors in src/auth.ts"
|
|
405
|
+
*/
|
|
406
|
+
export function getProactiveDiagnostics() {
|
|
407
|
+
return [...context.diagnostics];
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Enrich file content with type information from the LSP.
|
|
411
|
+
*
|
|
412
|
+
* Adds inline type annotations as comments for the agent to understand
|
|
413
|
+
* the code better. Only annotates function signatures and variable
|
|
414
|
+
* declarations to avoid noise.
|
|
415
|
+
*/
|
|
416
|
+
export function enrichFileContext(file, content) {
|
|
417
|
+
if (!initialized || !context.serverCapabilities.hover) {
|
|
418
|
+
return content;
|
|
419
|
+
}
|
|
420
|
+
const absPath = resolve(file);
|
|
421
|
+
const fileUri = `file://${absPath}`;
|
|
422
|
+
// Open the file in the LSP server
|
|
423
|
+
sendNotification('textDocument/didOpen', {
|
|
424
|
+
textDocument: {
|
|
425
|
+
uri: fileUri,
|
|
426
|
+
languageId: getLanguageId(absPath),
|
|
427
|
+
version: 1,
|
|
428
|
+
text: content,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
// For synchronous enrichment, return the content with cached type info
|
|
432
|
+
const lines = content.split('\n');
|
|
433
|
+
const enrichedLines = [];
|
|
434
|
+
for (let i = 0; i < lines.length; i++) {
|
|
435
|
+
const line = lines[i];
|
|
436
|
+
enrichedLines.push(line);
|
|
437
|
+
// Check if we have cached type info for this line
|
|
438
|
+
const key = `${absPath}:${i + 1}`;
|
|
439
|
+
const typeHint = context.typeInfo.get(key);
|
|
440
|
+
if (typeHint) {
|
|
441
|
+
enrichedLines.push(` // [type] ${typeHint}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Close the file
|
|
445
|
+
sendNotification('textDocument/didClose', {
|
|
446
|
+
textDocument: { uri: fileUri },
|
|
447
|
+
});
|
|
448
|
+
return enrichedLines.join('\n');
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get all references to a symbol at a given position.
|
|
452
|
+
*
|
|
453
|
+
* Use this before modifying a function/variable to understand the
|
|
454
|
+
* blast radius of the change.
|
|
455
|
+
*/
|
|
456
|
+
export function getReferencesForSymbol(file, line, col) {
|
|
457
|
+
const cacheKey = `${file}:${line}:${col}`;
|
|
458
|
+
// Return cached references if available
|
|
459
|
+
if (context.references.has(cacheKey)) {
|
|
460
|
+
return context.references.get(cacheKey);
|
|
461
|
+
}
|
|
462
|
+
if (!initialized || !context.serverCapabilities.references) {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
const absPath = resolve(file);
|
|
466
|
+
const fileUri = `file://${absPath}`;
|
|
467
|
+
// Fire the request — result will be cached when it arrives
|
|
468
|
+
sendRequest('textDocument/references', {
|
|
469
|
+
textDocument: { uri: fileUri },
|
|
470
|
+
position: { line: line - 1, character: col - 1 },
|
|
471
|
+
context: { includeDeclaration: true },
|
|
472
|
+
}, 8000).then((result) => {
|
|
473
|
+
if (Array.isArray(result)) {
|
|
474
|
+
const locations = result.map((ref) => ({
|
|
475
|
+
file: ref.uri.replace('file://', ''),
|
|
476
|
+
line: ref.range.start.line + 1,
|
|
477
|
+
column: ref.range.start.character + 1,
|
|
478
|
+
}));
|
|
479
|
+
context.references.set(cacheKey, locations);
|
|
480
|
+
}
|
|
481
|
+
}).catch(() => {
|
|
482
|
+
// Silently ignore reference lookup failures
|
|
483
|
+
});
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Suggest a fix strategy for a diagnostic.
|
|
488
|
+
*
|
|
489
|
+
* Maps common diagnostic codes to actionable fix descriptions that
|
|
490
|
+
* the agent can use to auto-correct code.
|
|
491
|
+
*/
|
|
492
|
+
export function suggestFix(diagnostic) {
|
|
493
|
+
const { code, message, source } = diagnostic;
|
|
494
|
+
// TypeScript common errors
|
|
495
|
+
if (source === 'ts' || source === 'typescript') {
|
|
496
|
+
const tsFixMap = {
|
|
497
|
+
'2304': `Import or declare the identifier: ${extractIdentifier(message)}`,
|
|
498
|
+
'2305': `The module does not export this member. Check the export name or use a different import.`,
|
|
499
|
+
'2307': `Module not found. Install the package or fix the import path.`,
|
|
500
|
+
'2322': `Type mismatch. Cast the value, update the type annotation, or fix the assigned value.`,
|
|
501
|
+
'2339': `Property does not exist on type. Add the property to the interface or use a type assertion.`,
|
|
502
|
+
'2345': `Argument type mismatch. Convert the argument or update the function signature.`,
|
|
503
|
+
'2551': `Property name typo. Did you mean a similarly-named property?`,
|
|
504
|
+
'2554': `Wrong number of arguments. Check the function signature.`,
|
|
505
|
+
'2571': `Object is of type 'unknown'. Add a type guard or type assertion.`,
|
|
506
|
+
'2741': `Missing property in object literal. Add the required property.`,
|
|
507
|
+
'6133': `Declared but never used. Remove the unused declaration or prefix with underscore.`,
|
|
508
|
+
'7006': `Parameter implicitly has 'any' type. Add an explicit type annotation.`,
|
|
509
|
+
'7016': `Could not find declaration file. Install @types/ package or add a .d.ts declaration.`,
|
|
510
|
+
'7031': `Binding element implicitly has 'any' type. Add type annotation to destructured parameter.`,
|
|
511
|
+
};
|
|
512
|
+
if (code && tsFixMap[code])
|
|
513
|
+
return tsFixMap[code];
|
|
514
|
+
}
|
|
515
|
+
// Python / Pyright common errors
|
|
516
|
+
if (source === 'pyright' || source === 'Pyright' || source === 'pylsp') {
|
|
517
|
+
if (message.includes('is not defined')) {
|
|
518
|
+
return `Import the symbol or define it before use.`;
|
|
519
|
+
}
|
|
520
|
+
if (message.includes('could not be resolved')) {
|
|
521
|
+
return `Install the package with pip/conda or fix the import path.`;
|
|
522
|
+
}
|
|
523
|
+
if (message.includes('is not assignable to type')) {
|
|
524
|
+
return `Type mismatch. Fix the assigned value or update the type hint.`;
|
|
525
|
+
}
|
|
526
|
+
if (message.includes('has no attribute')) {
|
|
527
|
+
return `Attribute does not exist. Check the object type or add the attribute.`;
|
|
528
|
+
}
|
|
529
|
+
if (message.includes('Missing return statement')) {
|
|
530
|
+
return `Add a return statement to the function or update the return type to None.`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Rust-analyzer common errors
|
|
534
|
+
if (source === 'rust-analyzer' || source === 'rustc') {
|
|
535
|
+
if (code === 'E0308')
|
|
536
|
+
return `Mismatched types. Convert with .into(), as, or fix the expression.`;
|
|
537
|
+
if (code === 'E0425')
|
|
538
|
+
return `Cannot find value. Import it or fix the identifier name.`;
|
|
539
|
+
if (code === 'E0433')
|
|
540
|
+
return `Failed to resolve path. Add a use statement or fix the module path.`;
|
|
541
|
+
if (code === 'E0382')
|
|
542
|
+
return `Value moved. Clone the value, borrow instead, or restructure ownership.`;
|
|
543
|
+
if (code === 'E0502')
|
|
544
|
+
return `Cannot borrow as mutable. Restructure to avoid overlapping borrows.`;
|
|
545
|
+
if (code === 'E0599')
|
|
546
|
+
return `No method found. Import the trait or check the type.`;
|
|
547
|
+
}
|
|
548
|
+
// Go (gopls) common errors
|
|
549
|
+
if (source === 'compiler' || source === 'gopls') {
|
|
550
|
+
if (message.includes('undefined:')) {
|
|
551
|
+
return `Identifier not defined. Import the package or declare the symbol.`;
|
|
552
|
+
}
|
|
553
|
+
if (message.includes('cannot use')) {
|
|
554
|
+
return `Type mismatch. Convert the value or fix the type.`;
|
|
555
|
+
}
|
|
556
|
+
if (message.includes('imported and not used')) {
|
|
557
|
+
return `Remove the unused import.`;
|
|
558
|
+
}
|
|
559
|
+
if (message.includes('declared and not used')) {
|
|
560
|
+
return `Use the variable or replace it with _.`;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Generic fallbacks based on message patterns
|
|
564
|
+
if (/not found|cannot find|could not resolve|undefined/i.test(message)) {
|
|
565
|
+
return `Symbol or module not found. Check imports, spelling, and installed dependencies.`;
|
|
566
|
+
}
|
|
567
|
+
if (/type.*mismatch|not assignable|incompatible/i.test(message)) {
|
|
568
|
+
return `Type mismatch. Align the types between the source and target.`;
|
|
569
|
+
}
|
|
570
|
+
if (/unused|never read|not used/i.test(message)) {
|
|
571
|
+
return `Unused declaration. Remove it or prefix with underscore to suppress.`;
|
|
572
|
+
}
|
|
573
|
+
if (/missing|required/i.test(message)) {
|
|
574
|
+
return `Required element missing. Add the missing property, argument, or statement.`;
|
|
575
|
+
}
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
/** Extract an identifier name from a diagnostic message */
|
|
579
|
+
function extractIdentifier(message) {
|
|
580
|
+
const match = message.match(/'([^']+)'/);
|
|
581
|
+
return match ? match[1] : '(unknown)';
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Detach from the language server and clean up resources.
|
|
585
|
+
*/
|
|
586
|
+
export function detachLSP() {
|
|
587
|
+
// Cancel all pending requests
|
|
588
|
+
for (const [id, pending] of pendingRequests) {
|
|
589
|
+
clearTimeout(pending.timer);
|
|
590
|
+
pending.reject(new Error('LSP detached'));
|
|
591
|
+
pendingRequests.delete(id);
|
|
592
|
+
}
|
|
593
|
+
// Stop file watcher
|
|
594
|
+
if (fileWatcher) {
|
|
595
|
+
fileWatcher.close();
|
|
596
|
+
fileWatcher = null;
|
|
597
|
+
}
|
|
598
|
+
// Shut down server gracefully
|
|
599
|
+
if (activeServer) {
|
|
600
|
+
try {
|
|
601
|
+
activeServer.stdin?.write(encodeMessage({
|
|
602
|
+
jsonrpc: '2.0',
|
|
603
|
+
id: ++messageId,
|
|
604
|
+
method: 'shutdown',
|
|
605
|
+
params: null,
|
|
606
|
+
}));
|
|
607
|
+
const server = activeServer;
|
|
608
|
+
setTimeout(() => {
|
|
609
|
+
try {
|
|
610
|
+
server.stdin?.write(encodeMessage({
|
|
611
|
+
jsonrpc: '2.0',
|
|
612
|
+
method: 'exit',
|
|
613
|
+
params: null,
|
|
614
|
+
}));
|
|
615
|
+
server.kill();
|
|
616
|
+
}
|
|
617
|
+
catch { /* already dead */ }
|
|
618
|
+
}, 500);
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
activeServer.kill();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
activeServer = null;
|
|
625
|
+
activeConfig = null;
|
|
626
|
+
activeProjectDir = null;
|
|
627
|
+
initialized = false;
|
|
628
|
+
buffer = '';
|
|
629
|
+
// Clear context
|
|
630
|
+
context.diagnostics = [];
|
|
631
|
+
context.symbols = [];
|
|
632
|
+
context.references.clear();
|
|
633
|
+
context.typeInfo.clear();
|
|
634
|
+
context.serverCapabilities = {};
|
|
635
|
+
}
|
|
636
|
+
// ── Formatting Helpers ──
|
|
637
|
+
/**
|
|
638
|
+
* Format diagnostics as a concise summary for agent context injection.
|
|
639
|
+
*
|
|
640
|
+
* Example output:
|
|
641
|
+
* "3 errors, 2 warnings in workspace:
|
|
642
|
+
* ERROR src/auth.ts:42:5 — Property 'token' does not exist on type 'User'
|
|
643
|
+
* ERROR src/auth.ts:58:12 — Cannot find name 'jwt'
|
|
644
|
+
* ERROR src/server.ts:15:1 — Module '"./db"' has no exported member 'connect'"
|
|
645
|
+
*/
|
|
646
|
+
export function formatDiagnosticsSummary(diagnostics) {
|
|
647
|
+
const diags = diagnostics || context.diagnostics;
|
|
648
|
+
if (diags.length === 0)
|
|
649
|
+
return 'No LSP diagnostics.';
|
|
650
|
+
const errors = diags.filter(d => d.severity === 'error');
|
|
651
|
+
const warnings = diags.filter(d => d.severity === 'warning');
|
|
652
|
+
const infos = diags.filter(d => d.severity === 'info' || d.severity === 'hint');
|
|
653
|
+
const parts = [];
|
|
654
|
+
if (errors.length > 0)
|
|
655
|
+
parts.push(`${errors.length} error${errors.length === 1 ? '' : 's'}`);
|
|
656
|
+
if (warnings.length > 0)
|
|
657
|
+
parts.push(`${warnings.length} warning${warnings.length === 1 ? '' : 's'}`);
|
|
658
|
+
if (infos.length > 0)
|
|
659
|
+
parts.push(`${infos.length} info`);
|
|
660
|
+
const lines = [`${parts.join(', ')} in workspace:`];
|
|
661
|
+
// Show errors first (limit to 10 to avoid noise)
|
|
662
|
+
const shownDiags = [...errors, ...warnings, ...infos].slice(0, 10);
|
|
663
|
+
for (const d of shownDiags) {
|
|
664
|
+
const tag = d.severity.toUpperCase();
|
|
665
|
+
const relFile = activeProjectDir ? relative(activeProjectDir, d.file) : d.file;
|
|
666
|
+
lines.push(` ${tag} ${relFile}:${d.line}:${d.column} — ${d.message}`);
|
|
667
|
+
}
|
|
668
|
+
if (diags.length > 10) {
|
|
669
|
+
lines.push(` ... and ${diags.length - 10} more`);
|
|
670
|
+
}
|
|
671
|
+
return lines.join('\n');
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Format reference locations for display.
|
|
675
|
+
*/
|
|
676
|
+
export function formatReferences(locations) {
|
|
677
|
+
if (locations.length === 0)
|
|
678
|
+
return 'No references found.';
|
|
679
|
+
const lines = [`${locations.length} reference${locations.length === 1 ? '' : 's'}:`];
|
|
680
|
+
for (const loc of locations.slice(0, 20)) {
|
|
681
|
+
const relFile = activeProjectDir ? relative(activeProjectDir, loc.file) : loc.file;
|
|
682
|
+
lines.push(` ${relFile}:${loc.line}:${loc.column}`);
|
|
683
|
+
}
|
|
684
|
+
if (locations.length > 20) {
|
|
685
|
+
lines.push(` ... and ${locations.length - 20} more`);
|
|
686
|
+
}
|
|
687
|
+
return lines.join('\n');
|
|
688
|
+
}
|
|
689
|
+
//# sourceMappingURL=lsp-deep.js.map
|