micro-models-agent 0.41.2 → 0.43.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/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +19 -9
- package/dist/cli/completer.js +37 -36
- package/dist/cli/index.js +2 -2
- package/dist/cli/main.js +23 -48
- package/dist/cli/repl-commands.js +12 -40
- package/dist/cli/repl.js +87 -217
- package/dist/cli/security-commands.js +7 -5
- package/dist/cli/setup.js +26 -8
- package/dist/config/config.js +5 -52
- package/dist/config/defaults.js +5 -29
- package/dist/config/experts.js +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/security.js +10 -3
- package/dist/core/agent-moe.js +10 -2
- package/dist/core/agent.js +82 -273
- package/dist/core/bootstrap.js +13 -80
- package/dist/core/index.js +2 -2
- package/dist/core/prompt-builder.js +2 -23
- package/dist/core/session-logger.js +4 -46
- package/dist/i18n/en.json +2 -75
- package/dist/i18n/ru.json +1 -74
- package/dist/index.js +1 -1
- package/dist/llm/image-utils.js +5 -4
- package/dist/llm/index.js +4 -4
- package/dist/llm/model-loader.js +6 -6
- package/dist/llm/openai-compat.js +34 -40
- package/dist/llm/orchestrator.js +29 -33
- package/dist/llm/response.js +9 -9
- package/dist/logger/app-logger.js +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/main.js +648 -183
- package/dist/migration/backup.js +13 -13
- package/dist/migration/detect.js +11 -11
- package/dist/migration/index.js +2 -2
- package/dist/modules/browser/actions.js +4 -34
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +6 -6
- package/dist/modules/browser/index.js +5 -7
- package/dist/modules/browser/module.js +7 -8
- package/dist/modules/browser/session.js +84 -87
- package/dist/modules/browser/snapshot.js +58 -92
- package/dist/modules/browser/types.js +1 -4
- package/dist/modules/certification/cli.js +4 -2
- package/dist/modules/certification/fact-checker.js +3 -1
- package/dist/modules/certification/loader.js +9 -3
- package/dist/modules/certification/runner.js +4 -1
- package/dist/modules/context/index.js +1 -1
- package/dist/modules/context/manager.js +86 -160
- package/dist/modules/execution/auditor.js +25 -177
- package/dist/modules/execution/module.js +544 -201
- package/dist/modules/execution/moe-executor.js +0 -25
- package/dist/modules/execution/plan-store.js +3 -1
- package/dist/modules/execution/plan-validator.js +10 -10
- package/dist/modules/execution/planner.js +1 -6
- package/dist/modules/execution/stuck-detector.js +10 -173
- package/dist/modules/execution/verifier.js +42 -86
- package/dist/modules/hallucination/confidence.js +1 -8
- package/dist/modules/hallucination/detector.js +5 -2
- package/dist/modules/hallucination/factual.js +64 -3
- package/dist/modules/hallucination/index.js +1 -1
- package/dist/modules/hallucination/js-identifiers.js +0 -190
- package/dist/modules/hallucination/llm-judge.js +3 -1
- package/dist/modules/indexer/cache.js +7 -9
- package/dist/modules/indexer/index.js +3 -3
- package/dist/modules/indexer/module.js +42 -95
- package/dist/modules/indexer/walker.js +17 -17
- package/dist/modules/lsp/client.js +31 -74
- package/dist/modules/lsp/config.js +33 -87
- package/dist/modules/lsp/index.js +3 -3
- package/dist/modules/lsp/module.js +21 -185
- package/dist/modules/mcp/module.js +6 -2
- package/dist/modules/memory/index.js +1 -1
- package/dist/modules/memory/module.js +23 -71
- package/dist/modules/memory/search.js +9 -11
- package/dist/modules/memory/store.js +13 -13
- package/dist/modules/pipelines/engine.js +10 -10
- package/dist/modules/pipelines/index.js +3 -3
- package/dist/modules/pipelines/parser.js +14 -17
- package/dist/modules/pipelines/template.js +1 -1
- package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
- package/dist/modules/plugins/builtin/notify.js +2 -3
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/loader.js +17 -59
- package/dist/modules/plugins/manager.js +17 -73
- package/dist/modules/processes/index.js +1 -1
- package/dist/modules/processes/registry.js +46 -135
- package/dist/modules/registry.js +2 -4
- package/dist/modules/security/audit-notifier.js +39 -39
- package/dist/modules/security/command-validator.js +8 -2
- package/dist/modules/security/data-sanitizer.js +9 -1
- package/dist/modules/security/encryption.js +56 -58
- package/dist/modules/security/network-validator.js +9 -1
- package/dist/modules/security/path-validator.js +3 -1
- package/dist/modules/security/security-policies.js +19 -3
- package/dist/modules/security/session-encryption.js +1 -1
- package/dist/modules/security/session-isolation.js +8 -8
- package/dist/modules/session/index.js +3 -3
- package/dist/modules/session/module.js +5 -5
- package/dist/modules/session/store.js +9 -3
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/checker.js +6 -70
- package/dist/modules/updater/index.js +1 -2
- package/dist/modules/user-profile/compressor.js +2 -2
- package/dist/modules/user-profile/index.js +1 -1
- package/dist/modules/user-profile/profile.js +9 -9
- package/dist/tools/attach-image.js +1 -1
- package/dist/tools/bash.js +19 -178
- package/dist/tools/browser.js +29 -46
- package/dist/tools/executor.js +5 -4
- package/dist/tools/file-info.js +12 -13
- package/dist/tools/filter-tools.js +2 -9
- package/dist/tools/glob-tool.js +11 -11
- package/dist/tools/grep-tool.js +3 -1
- package/dist/tools/index.js +2 -13
- package/dist/tools/list-dir.js +17 -18
- package/dist/tools/load-skill.js +3 -1
- package/dist/tools/path-utils.js +4 -4
- package/dist/tools/pipeline-run.js +25 -25
- package/dist/tools/process-kill.js +11 -11
- package/dist/tools/process-list.js +22 -20
- package/dist/tools/process-log.js +18 -22
- package/dist/tools/question.js +3 -1
- package/dist/tools/read-file.js +2 -10
- package/dist/tools/recall.js +37 -44
- package/dist/tools/registry.js +4 -15
- package/dist/tools/remember.js +29 -29
- package/dist/tools/scope-check.js +9 -9
- package/dist/tools/subagent.js +9 -54
- package/dist/tools/user-input.js +1 -1
- package/dist/tools/web-browse.js +3 -3
- package/dist/tools/web-fetch.js +3 -3
- package/dist/tools/web-search.js +3 -3
- package/dist/tools/write-file.js +3 -1
- package/dist/ui/box.js +5 -1
- package/dist/ui/index.js +6 -6
- package/dist/ui/md-formatter.js +33 -33
- package/dist/ui/output.js +5 -5
- package/dist/ui/renderer.js +10 -15
- package/dist/ui/table.js +1 -1
- package/package.json +48 -48
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/run-result.js +0 -22
- package/dist/core/version.js +0 -24
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -162
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/execution/audit-runners.js +0 -152
- package/dist/modules/execution/execution-plugin.js +0 -272
- package/dist/modules/execution/plan-tool.js +0 -508
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/probe.js +0 -76
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -141
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/updater/module.js +0 -116
- package/dist/tools/chunk-query.js +0 -99
- package/dist/tools/download-file.js +0 -116
- package/dist/tools/enable-tools.js +0 -58
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/ui/line-editor.js +0 -703
- package/dist/ui/line-math.js +0 -69
- package/dist/ui/plan-view.js +0 -103
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, statSync, existsSync, watch } from
|
|
2
|
-
import { join, relative, extname } from
|
|
1
|
+
import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
|
|
2
|
+
import { join, relative, extname } from 'path';
|
|
3
3
|
const LANGUAGES = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
'.ts': 'typescript',
|
|
5
|
+
'.tsx': 'typescript-react',
|
|
6
|
+
'.js': 'javascript',
|
|
7
|
+
'.jsx': 'javascript-react',
|
|
8
|
+
'.json': 'json',
|
|
9
|
+
'.md': 'markdown',
|
|
10
|
+
'.yaml': 'yaml',
|
|
11
|
+
'.yml': 'yaml',
|
|
12
|
+
'.py': 'python',
|
|
13
|
+
'.rs': 'rust',
|
|
14
|
+
'.go': 'go',
|
|
15
15
|
};
|
|
16
|
-
const IGNORE_DIRS = new Set([
|
|
16
|
+
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.mma', 'coverage']);
|
|
17
17
|
export class Indexer {
|
|
18
18
|
baseDir;
|
|
19
19
|
MAX_FILES = 1000;
|
|
@@ -63,7 +63,7 @@ export class Indexer {
|
|
|
63
63
|
const ext = extname(entry).toLowerCase();
|
|
64
64
|
const language = LANGUAGES[ext];
|
|
65
65
|
if (language) {
|
|
66
|
-
const content = readFileSync(fullPath,
|
|
66
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
67
67
|
const exports = this.extractExports(content, language);
|
|
68
68
|
files.push({ path: relPath, language, exports, size: stat.size });
|
|
69
69
|
totalSize += stat.size;
|
|
@@ -76,7 +76,7 @@ export class Indexer {
|
|
|
76
76
|
return { files, totalSize };
|
|
77
77
|
}
|
|
78
78
|
extractExports(content, language) {
|
|
79
|
-
if (language ===
|
|
79
|
+
if (language === 'typescript' || language === 'javascript') {
|
|
80
80
|
const exports = [];
|
|
81
81
|
const exportRegex = /export\s+(?:const|function|class|interface|type|enum|default\s+(?:class|function))\s+(\w+)/g;
|
|
82
82
|
let match;
|
|
@@ -95,7 +95,7 @@ export class Indexer {
|
|
|
95
95
|
const langs = Object.entries(byLang)
|
|
96
96
|
.sort((a, b) => b[1] - a[1])
|
|
97
97
|
.map(([lang, count]) => `${lang}:${count}`)
|
|
98
|
-
.join(
|
|
98
|
+
.join(', ');
|
|
99
99
|
return `[Index: ${result.files.length} files, ${(result.totalSize / 1024).toFixed(1)}KB (${langs})]`;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { spawn, execSync } from "child_process";
|
|
2
2
|
import { resolve } from "path";
|
|
3
|
-
import { platform } from "os";
|
|
4
|
-
import { resolveSpawnCommand, buildWinCommandLine } from "./command";
|
|
5
|
-
import { killTree } from "../processes/registry";
|
|
6
3
|
const DEFAULT_TIMEOUT = 15000;
|
|
7
4
|
export class LspClient {
|
|
8
5
|
process = null;
|
|
@@ -14,10 +11,18 @@ export class LspClient {
|
|
|
14
11
|
diagnosticsResolve = null;
|
|
15
12
|
diagnosticsTimer = null;
|
|
16
13
|
initialized = false;
|
|
17
|
-
async checkFile(filePath, baseDir, config
|
|
14
|
+
async checkFile(filePath, baseDir, config) {
|
|
18
15
|
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
19
16
|
try {
|
|
20
|
-
await this.
|
|
17
|
+
await this.startServer(config, baseDir);
|
|
18
|
+
const rootUri = this.pathToUri(resolve(baseDir));
|
|
19
|
+
const initResult = await this.sendRequest("initialize", {
|
|
20
|
+
processId: process.pid,
|
|
21
|
+
rootUri,
|
|
22
|
+
workspaceFolders: [{ uri: rootUri, name: "workspace" }],
|
|
23
|
+
capabilities: { textDocument: { publishDiagnostics: {} } },
|
|
24
|
+
}, timeout);
|
|
25
|
+
this.initialized = true;
|
|
21
26
|
this.sendNotification("initialized", {});
|
|
22
27
|
const uri = this.pathToUri(resolve(filePath));
|
|
23
28
|
const fs = await import("fs");
|
|
@@ -26,8 +31,10 @@ export class LspClient {
|
|
|
26
31
|
this.diagnosticsResolve = resolve;
|
|
27
32
|
this.diagnostics = [];
|
|
28
33
|
this.diagnosticsTimer = setTimeout(() => {
|
|
29
|
-
this.diagnosticsResolve
|
|
30
|
-
|
|
34
|
+
if (this.diagnosticsResolve) {
|
|
35
|
+
this.diagnosticsResolve([]);
|
|
36
|
+
this.diagnosticsResolve = null;
|
|
37
|
+
}
|
|
31
38
|
}, timeout);
|
|
32
39
|
});
|
|
33
40
|
this.sendNotification("textDocument/didOpen", {
|
|
@@ -40,51 +47,14 @@ export class LspClient {
|
|
|
40
47
|
});
|
|
41
48
|
return await diagPromise;
|
|
42
49
|
}
|
|
43
|
-
catch
|
|
44
|
-
|
|
45
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
await this.shutdown();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Lightweight "does the server actually start" probe: spawn + initialize +
|
|
53
|
-
* shutdown. Used by the REPL banner to surface a dead server (npx
|
|
54
|
-
* auto-install failure, wrong package name, missing binary) up front instead
|
|
55
|
-
* of the first write_file timing out.
|
|
56
|
-
*/
|
|
57
|
-
async probe(config, projectRoot, timeout) {
|
|
58
|
-
try {
|
|
59
|
-
await this.initialize(config, projectRoot, timeout);
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
60
52
|
}
|
|
61
53
|
finally {
|
|
62
54
|
await this.shutdown();
|
|
63
55
|
}
|
|
64
56
|
}
|
|
65
|
-
async
|
|
66
|
-
await this.startServer(config, projectRoot);
|
|
67
|
-
const rootUri = this.pathToUri(projectRoot);
|
|
68
|
-
const initParams = {
|
|
69
|
-
processId: process.pid,
|
|
70
|
-
rootUri,
|
|
71
|
-
workspaceFolders: [{ uri: rootUri, name: "workspace" }],
|
|
72
|
-
capabilities: { textDocument: { publishDiagnostics: {} } },
|
|
73
|
-
};
|
|
74
|
-
try {
|
|
75
|
-
await this.sendRequest("initialize", initParams, timeout);
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
// npx auto-install can blow past the initialize timeout on first run — retry once.
|
|
79
|
-
if (!(e instanceof Error) || !e.message.includes("initialize"))
|
|
80
|
-
throw e;
|
|
81
|
-
await this.shutdown();
|
|
82
|
-
await this.startServer(config, projectRoot);
|
|
83
|
-
await this.sendRequest("initialize", initParams, timeout);
|
|
84
|
-
}
|
|
85
|
-
this.initialized = true;
|
|
86
|
-
}
|
|
87
|
-
async startServer(config, projectRoot) {
|
|
57
|
+
async startServer(config, baseDir) {
|
|
88
58
|
if (config.autoInstall === false) {
|
|
89
59
|
try {
|
|
90
60
|
execSync(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
|
|
@@ -95,21 +65,11 @@ export class LspClient {
|
|
|
95
65
|
}
|
|
96
66
|
return new Promise((resolve, reject) => {
|
|
97
67
|
const args = config.args ?? [];
|
|
98
|
-
const
|
|
99
|
-
let spawnCommand = resolveSpawnCommand(config.command);
|
|
100
|
-
let spawnArgs = args;
|
|
101
|
-
const spawnOpts = {
|
|
68
|
+
const proc = spawn(config.command, args, {
|
|
102
69
|
stdio: ["pipe", "pipe", "pipe"],
|
|
103
70
|
env: { ...process.env, ...config.env },
|
|
104
|
-
cwd:
|
|
105
|
-
};
|
|
106
|
-
if (isWin) {
|
|
107
|
-
// A `.cmd` shim can't be spawned directly under Node (`spawn npx EINVAL`) — run via cmd.exe.
|
|
108
|
-
spawnCommand = buildWinCommandLine(spawnCommand, args);
|
|
109
|
-
spawnArgs = [];
|
|
110
|
-
spawnOpts.shell = true;
|
|
111
|
-
}
|
|
112
|
-
const proc = spawn(spawnCommand, spawnArgs, spawnOpts);
|
|
71
|
+
cwd: baseDir,
|
|
72
|
+
});
|
|
113
73
|
proc.on("error", reject);
|
|
114
74
|
proc.stdout.on("data", (chunk) => {
|
|
115
75
|
this.handleData(chunk);
|
|
@@ -120,8 +80,9 @@ export class LspClient {
|
|
|
120
80
|
});
|
|
121
81
|
this.process = proc;
|
|
122
82
|
setTimeout(() => {
|
|
123
|
-
if (!this.initialized && this.process)
|
|
83
|
+
if (!this.initialized && this.process) {
|
|
124
84
|
reject(new Error("LSP server start timeout"));
|
|
85
|
+
}
|
|
125
86
|
}, config.timeout ?? 10000);
|
|
126
87
|
});
|
|
127
88
|
}
|
|
@@ -172,8 +133,10 @@ export class LspClient {
|
|
|
172
133
|
clearTimeout(this.diagnosticsTimer);
|
|
173
134
|
this.diagnosticsTimer = null;
|
|
174
135
|
}
|
|
175
|
-
this.diagnosticsResolve
|
|
176
|
-
|
|
136
|
+
if (this.diagnosticsResolve) {
|
|
137
|
+
this.diagnosticsResolve(this.diagnostics);
|
|
138
|
+
this.diagnosticsResolve = null;
|
|
139
|
+
}
|
|
177
140
|
}
|
|
178
141
|
return;
|
|
179
142
|
}
|
|
@@ -229,18 +192,13 @@ export class LspClient {
|
|
|
229
192
|
}
|
|
230
193
|
finally {
|
|
231
194
|
if (this.process) {
|
|
232
|
-
// The direct child on win32 is the cmd.exe wrapper; killing it alone
|
|
233
|
-
// orphans the npx → node → tsserver tree (observed as leaked node
|
|
234
|
-
// processes after every check). Tree-kill so the whole chain dies.
|
|
235
195
|
try {
|
|
236
|
-
|
|
196
|
+
this.process.kill();
|
|
237
197
|
}
|
|
238
198
|
catch { }
|
|
239
199
|
this.process = null;
|
|
240
200
|
}
|
|
241
201
|
this.initialized = false;
|
|
242
|
-
this.buffer = "";
|
|
243
|
-
this.contentLength = -1;
|
|
244
202
|
if (this.diagnosticsTimer) {
|
|
245
203
|
clearTimeout(this.diagnosticsTimer);
|
|
246
204
|
this.diagnosticsTimer = null;
|
|
@@ -249,19 +207,18 @@ export class LspClient {
|
|
|
249
207
|
}
|
|
250
208
|
pathToUri(filePath) {
|
|
251
209
|
const normalized = filePath.replace(/\\/g, "/");
|
|
252
|
-
|
|
210
|
+
if (/^[a-zA-Z]:/.test(normalized)) {
|
|
211
|
+
return `file:///${normalized}`;
|
|
212
|
+
}
|
|
213
|
+
return `file://${normalized}`;
|
|
253
214
|
}
|
|
254
215
|
languageFromPath(filePath) {
|
|
255
216
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
256
217
|
const map = {
|
|
257
218
|
ts: "typescript",
|
|
258
219
|
tsx: "typescriptreact",
|
|
259
|
-
mts: "typescript",
|
|
260
|
-
cts: "typescript",
|
|
261
220
|
js: "javascript",
|
|
262
221
|
jsx: "javascriptreact",
|
|
263
|
-
mjs: "javascript",
|
|
264
|
-
cjs: "javascript",
|
|
265
222
|
py: "python",
|
|
266
223
|
go: "go",
|
|
267
224
|
rs: "rust",
|
|
@@ -10,125 +10,71 @@ export const DEFAULT_LSP_CONFIG = {
|
|
|
10
10
|
timeout: 15000,
|
|
11
11
|
servers: {
|
|
12
12
|
typescript: {
|
|
13
|
-
command:
|
|
14
|
-
args: [
|
|
15
|
-
|
|
16
|
-
"--package",
|
|
17
|
-
"typescript-language-server",
|
|
18
|
-
"--package",
|
|
19
|
-
"typescript@5",
|
|
20
|
-
"typescript-language-server",
|
|
21
|
-
"--stdio",
|
|
22
|
-
],
|
|
23
|
-
timeout: 30000,
|
|
13
|
+
command: 'npx',
|
|
14
|
+
args: ['typescript-language-server', '--stdio'],
|
|
15
|
+
timeout: 20000,
|
|
24
16
|
autoInstall: true,
|
|
25
|
-
workspaceMarkers: ["package.json", "tsconfig.json"],
|
|
26
17
|
},
|
|
27
18
|
javascript: {
|
|
28
|
-
command:
|
|
29
|
-
args: [
|
|
30
|
-
|
|
31
|
-
"--package",
|
|
32
|
-
"typescript-language-server",
|
|
33
|
-
"--package",
|
|
34
|
-
"typescript@5",
|
|
35
|
-
"typescript-language-server",
|
|
36
|
-
"--stdio",
|
|
37
|
-
],
|
|
38
|
-
timeout: 30000,
|
|
19
|
+
command: 'npx',
|
|
20
|
+
args: ['typescript-language-server', '--stdio'],
|
|
21
|
+
timeout: 15000,
|
|
39
22
|
autoInstall: true,
|
|
40
|
-
workspaceMarkers: ["package.json", "tsconfig.json"],
|
|
41
23
|
},
|
|
42
24
|
python: {
|
|
43
|
-
command:
|
|
44
|
-
args: [
|
|
25
|
+
command: 'npx',
|
|
26
|
+
args: ['pyright', '--stdio'],
|
|
45
27
|
timeout: 15000,
|
|
46
28
|
autoInstall: true,
|
|
47
|
-
workspaceMarkers: ["pyproject.toml", "setup.py", "requirements.txt"],
|
|
48
29
|
},
|
|
49
30
|
rust: {
|
|
50
|
-
command:
|
|
31
|
+
command: 'rust-analyzer',
|
|
51
32
|
timeout: 15000,
|
|
52
33
|
autoInstall: false,
|
|
53
|
-
workspaceMarkers: ["Cargo.toml"],
|
|
54
34
|
},
|
|
55
35
|
go: {
|
|
56
|
-
command:
|
|
36
|
+
command: 'gopls',
|
|
57
37
|
timeout: 15000,
|
|
58
38
|
autoInstall: false,
|
|
59
|
-
workspaceMarkers: ["go.mod"],
|
|
60
39
|
},
|
|
61
40
|
json: {
|
|
62
|
-
command:
|
|
63
|
-
args: [
|
|
64
|
-
|
|
65
|
-
"--package",
|
|
66
|
-
"vscode-langservers-extracted",
|
|
67
|
-
"vscode-json-language-server",
|
|
68
|
-
"--stdio",
|
|
69
|
-
],
|
|
70
|
-
timeout: 30000,
|
|
41
|
+
command: 'npx',
|
|
42
|
+
args: ['vscode-json-languageserver', '--stdio'],
|
|
43
|
+
timeout: 10000,
|
|
71
44
|
autoInstall: true,
|
|
72
|
-
workspaceMarkers: ["package.json"],
|
|
73
45
|
},
|
|
74
46
|
html: {
|
|
75
|
-
command:
|
|
76
|
-
args: [
|
|
77
|
-
|
|
78
|
-
"--package",
|
|
79
|
-
"vscode-langservers-extracted",
|
|
80
|
-
"--package",
|
|
81
|
-
"typescript@5",
|
|
82
|
-
"vscode-html-language-server",
|
|
83
|
-
"--stdio",
|
|
84
|
-
],
|
|
85
|
-
timeout: 60000,
|
|
47
|
+
command: 'npx',
|
|
48
|
+
args: ['vscode-html-languageserver', '--stdio'],
|
|
49
|
+
timeout: 10000,
|
|
86
50
|
autoInstall: true,
|
|
87
|
-
workspaceMarkers: ["package.json"],
|
|
88
51
|
},
|
|
89
52
|
css: {
|
|
90
|
-
command:
|
|
91
|
-
args: [
|
|
92
|
-
|
|
93
|
-
"--package",
|
|
94
|
-
"vscode-langservers-extracted",
|
|
95
|
-
"--package",
|
|
96
|
-
"typescript@5",
|
|
97
|
-
"vscode-css-language-server",
|
|
98
|
-
"--stdio",
|
|
99
|
-
],
|
|
100
|
-
timeout: 60000,
|
|
53
|
+
command: 'npx',
|
|
54
|
+
args: ['vscode-css-languageserver', '--stdio'],
|
|
55
|
+
timeout: 10000,
|
|
101
56
|
autoInstall: true,
|
|
102
|
-
workspaceMarkers: ["package.json"],
|
|
103
57
|
},
|
|
104
58
|
},
|
|
105
59
|
};
|
|
106
60
|
const LANGUAGE_MAP = {
|
|
107
|
-
ts:
|
|
108
|
-
tsx:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
json: "json",
|
|
119
|
-
html: "html",
|
|
120
|
-
css: "css",
|
|
121
|
-
scss: "css",
|
|
61
|
+
ts: 'typescript',
|
|
62
|
+
tsx: 'typescript',
|
|
63
|
+
js: 'javascript',
|
|
64
|
+
jsx: 'javascript',
|
|
65
|
+
py: 'python',
|
|
66
|
+
rs: 'rust',
|
|
67
|
+
go: 'go',
|
|
68
|
+
json: 'json',
|
|
69
|
+
html: 'html',
|
|
70
|
+
css: 'css',
|
|
71
|
+
scss: 'css',
|
|
122
72
|
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
73
|
+
export function getServerForFile(filePath, config) {
|
|
74
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
126
75
|
if (!ext)
|
|
127
76
|
return null;
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
export function getServerForFile(filePath, config) {
|
|
131
|
-
const lang = getLanguageForFile(filePath);
|
|
77
|
+
const lang = LANGUAGE_MAP[ext];
|
|
132
78
|
if (!lang)
|
|
133
79
|
return null;
|
|
134
80
|
return config.servers[lang] ?? null;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { LspModule } from
|
|
2
|
-
export { LspClient } from
|
|
3
|
-
export { getServerForFile, DEFAULT_LSP_CONFIG } from
|
|
1
|
+
export { LspModule } from './module';
|
|
2
|
+
export { LspClient } from './client';
|
|
3
|
+
export { getServerForFile, DEFAULT_LSP_CONFIG } from './config';
|
|
@@ -1,57 +1,33 @@
|
|
|
1
|
-
import { existsSync } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import { LspClient } from
|
|
4
|
-
import { getServerForFile, DEFAULT_LSP_CONFIG } from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Consecutive LSP failures before a server is disabled for the session. A dead
|
|
13
|
-
* server (not installed, npx auto-install failed, path missing) otherwise
|
|
14
|
-
* spams `[LSP check failed]` on EVERY write, teaching the model to ignore the
|
|
15
|
-
* signal entirely (observed: ses_mst2r0r5 had LSP dead all session and the
|
|
16
|
-
* model never reacted). After the threshold we say so once and stop the noise
|
|
17
|
-
* for that server — a CSS timeout must not take TypeScript checks down too.
|
|
18
|
-
*/
|
|
19
|
-
const MAX_CONSECUTIVE_LSP_FAILURES = 3;
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { LspClient } from './client';
|
|
4
|
+
import { getServerForFile, DEFAULT_LSP_CONFIG } from './config';
|
|
5
|
+
const severityLabels = {
|
|
6
|
+
1: 'error',
|
|
7
|
+
2: 'warning',
|
|
8
|
+
3: 'info',
|
|
9
|
+
4: 'hint',
|
|
10
|
+
};
|
|
20
11
|
export class LspModule {
|
|
21
|
-
name =
|
|
12
|
+
name = 'lsp';
|
|
22
13
|
config;
|
|
23
|
-
client;
|
|
24
|
-
|
|
25
|
-
disabledServers = new Set();
|
|
26
|
-
constructor(config, client) {
|
|
14
|
+
client = new LspClient();
|
|
15
|
+
constructor(config) {
|
|
27
16
|
this.config = { ...DEFAULT_LSP_CONFIG, ...config };
|
|
28
|
-
this.client = client ?? new LspClient();
|
|
29
|
-
}
|
|
30
|
-
/** Test hook: has the module disabled any LSP server for the session? */
|
|
31
|
-
isLspDisabled() {
|
|
32
|
-
return this.disabledServers.size > 0;
|
|
33
|
-
}
|
|
34
|
-
/** Test hook: is the given server (config-derived key) disabled? */
|
|
35
|
-
isServerDisabled(key) {
|
|
36
|
-
return this.disabledServers.has(key);
|
|
37
|
-
}
|
|
38
|
-
/** Stable server identity derived from the config. */
|
|
39
|
-
serverKey(server) {
|
|
40
|
-
return `${server.command}:${(server.args ?? []).join(" ")}`;
|
|
41
17
|
}
|
|
42
18
|
getPlugin() {
|
|
43
19
|
const self = this;
|
|
44
20
|
return {
|
|
45
|
-
name:
|
|
21
|
+
name: 'lsp-check',
|
|
46
22
|
priority: 15,
|
|
47
23
|
onAfterTool: async (_ctx, call, result) => {
|
|
48
24
|
if (!self.config.enabled)
|
|
49
25
|
return;
|
|
50
|
-
if (call.name !==
|
|
26
|
+
if (call.name !== 'write_file' && call.name !== 'edit_file')
|
|
51
27
|
return;
|
|
52
28
|
if (!result.success)
|
|
53
29
|
return;
|
|
54
|
-
const filePath = String(call.arguments.path ??
|
|
30
|
+
const filePath = String(call.arguments.path ?? '');
|
|
55
31
|
if (!filePath)
|
|
56
32
|
return;
|
|
57
33
|
const fullPath = resolve(_ctx.baseDir, filePath);
|
|
@@ -60,13 +36,8 @@ export class LspModule {
|
|
|
60
36
|
const serverConfig = getServerForFile(fullPath, self.config);
|
|
61
37
|
if (!serverConfig)
|
|
62
38
|
return;
|
|
63
|
-
const key = self.serverKey(serverConfig);
|
|
64
|
-
if (self.disabledServers.has(key))
|
|
65
|
-
return;
|
|
66
|
-
const projectRoot = findProjectRoot(fullPath, _ctx.baseDir, serverConfig.workspaceMarkers ?? []);
|
|
67
39
|
try {
|
|
68
|
-
const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig
|
|
69
|
-
self.failuresByServer.set(key, 0);
|
|
40
|
+
const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig);
|
|
70
41
|
const errors = diagnostics.filter((d) => d.severity === 1);
|
|
71
42
|
const warnings = diagnostics.filter((d) => d.severity === 2);
|
|
72
43
|
if (errors.length > 0) {
|
|
@@ -78,155 +49,20 @@ export class LspModule {
|
|
|
78
49
|
result.output += `\n\n[LSP warnings]:\n${items}`;
|
|
79
50
|
}
|
|
80
51
|
}
|
|
81
|
-
catch
|
|
82
|
-
// Surface failures instead of silently returning [] — a dead LSP
|
|
83
|
-
// previously produced ZERO diagnostics across whole sessions and the
|
|
84
|
-
// model only found type errors reactively via `bun run build`.
|
|
85
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
86
|
-
const failures = (self.failuresByServer.get(key) ?? 0) + 1;
|
|
87
|
-
self.failuresByServer.set(key, failures);
|
|
88
|
-
try {
|
|
89
|
-
_ctx.logger?.warn(`LSP check failed for ${filePath}: ${msg}`);
|
|
90
|
-
}
|
|
91
|
-
catch { }
|
|
92
|
-
if (failures >= MAX_CONSECUTIVE_LSP_FAILURES) {
|
|
93
|
-
// One loud, actionable message, then stop the per-write noise for
|
|
94
|
-
// the rest of the session. The model must not learn to ignore LSP.
|
|
95
|
-
self.disabledServers.add(key);
|
|
96
|
-
result.output += `\n\n[LSP disabled: ${t("lsp.unavailable")}]`;
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
result.output += `\n\n[LSP check failed]: ${msg}`;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
52
|
+
catch { }
|
|
102
53
|
},
|
|
103
54
|
};
|
|
104
55
|
}
|
|
105
|
-
/**
|
|
106
|
-
* On-demand diagnostics tool. LSP checks only ran after write/edit (the
|
|
107
|
-
* plugin above), so a read-only "check this project for errors" task got
|
|
108
|
-
* ZERO compiler feedback — the model scanned files with read_file and never
|
|
109
|
-
* learned whether the code compiled (observed: ses_msvuao0h — a "проверь на
|
|
110
|
-
* ошибки" task ran no build, no lsp, and declared success). `lsp_check`
|
|
111
|
-
* gives the model a way to ask for static diagnostics on demand.
|
|
112
|
-
*/
|
|
113
|
-
getToolDefinitions() {
|
|
114
|
-
const self = this;
|
|
115
|
-
return [
|
|
116
|
-
{
|
|
117
|
-
name: "lsp_check",
|
|
118
|
-
description: 'Run static checks (LSP diagnostics) on a file or directory. For "check for errors" tasks: pass a file to check it, or a directory to check its source files (capped at 15). Returns [LSP errors] / [LSP warnings] for files with a configured LSP server.',
|
|
119
|
-
tags: ["code", "check"],
|
|
120
|
-
alwaysOn: true,
|
|
121
|
-
boundedOutput: true,
|
|
122
|
-
timeoutMs: 90000,
|
|
123
|
-
parameters: {
|
|
124
|
-
type: "object",
|
|
125
|
-
properties: {
|
|
126
|
-
path: {
|
|
127
|
-
type: "string",
|
|
128
|
-
description: "File or directory to check",
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
required: ["path"],
|
|
132
|
-
},
|
|
133
|
-
handler: async (ctx, args) => self.runCheck(ctx, args),
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
async runCheck(ctx, args) {
|
|
138
|
-
if (!this.config.enabled) {
|
|
139
|
-
return { success: false, output: t("lsp.check_disabled") };
|
|
140
|
-
}
|
|
141
|
-
const path = String(args.path ?? "");
|
|
142
|
-
if (!path)
|
|
143
|
-
return { success: false, output: t("lsp.check_no_path") };
|
|
144
|
-
const resolved = safeResolvePath(ctx.baseDir, path);
|
|
145
|
-
const securityPaths = ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths;
|
|
146
|
-
const scopeCheck = isPathInScope(ctx.baseDir, resolved, ctx.scope, securityPaths);
|
|
147
|
-
if (!scopeCheck.allowed) {
|
|
148
|
-
return {
|
|
149
|
-
success: false,
|
|
150
|
-
output: t("file.path_not_allowed", {
|
|
151
|
-
path: `${path} — ${scopeCheck.reason}`,
|
|
152
|
-
}),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
if (!existsSync(resolved)) {
|
|
156
|
-
return { success: false, output: t("lsp.check_notfound", { path }) };
|
|
157
|
-
}
|
|
158
|
-
const files = await collectCheckFiles(resolved, this.config);
|
|
159
|
-
const errors = [];
|
|
160
|
-
const warnings = [];
|
|
161
|
-
const notes = [];
|
|
162
|
-
let checked = 0;
|
|
163
|
-
for (const file of files) {
|
|
164
|
-
const serverConfig = getServerForFile(file, this.config);
|
|
165
|
-
if (!serverConfig)
|
|
166
|
-
continue;
|
|
167
|
-
const key = this.serverKey(serverConfig);
|
|
168
|
-
if (this.disabledServers.has(key))
|
|
169
|
-
continue;
|
|
170
|
-
const projectRoot = findProjectRoot(file, ctx.baseDir, serverConfig.workspaceMarkers ?? []);
|
|
171
|
-
try {
|
|
172
|
-
const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
|
|
173
|
-
this.failuresByServer.set(key, 0);
|
|
174
|
-
checked++;
|
|
175
|
-
for (const d of diags) {
|
|
176
|
-
if (d.severity === 1)
|
|
177
|
-
errors.push({ file, diag: d });
|
|
178
|
-
else if (d.severity === 2)
|
|
179
|
-
warnings.push({ file, diag: d });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
catch (e) {
|
|
183
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
184
|
-
const failures = (this.failuresByServer.get(key) ?? 0) + 1;
|
|
185
|
-
this.failuresByServer.set(key, failures);
|
|
186
|
-
try {
|
|
187
|
-
ctx.logger?.warn(`LSP check failed for ${file}: ${msg}`);
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
190
|
-
if (failures >= MAX_CONSECUTIVE_LSP_FAILURES) {
|
|
191
|
-
this.disabledServers.add(key);
|
|
192
|
-
notes.push(`[LSP disabled: ${t("lsp.unavailable")}]`);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
notes.push(`[LSP check failed for ${file}: ${msg}]`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const lines = [];
|
|
200
|
-
if (errors.length > 0) {
|
|
201
|
-
lines.push(`[LSP errors] (${errors.length}):`);
|
|
202
|
-
lines.push(formatCheckDiagnostics(errors, ctx.baseDir));
|
|
203
|
-
}
|
|
204
|
-
if (warnings.length > 0) {
|
|
205
|
-
lines.push(`[LSP warnings] (${warnings.length}):`);
|
|
206
|
-
lines.push(formatCheckDiagnostics(warnings, ctx.baseDir));
|
|
207
|
-
}
|
|
208
|
-
if (checked > 0 && errors.length === 0 && warnings.length === 0) {
|
|
209
|
-
lines.push(t("lsp.check_clean", { count: String(checked) }));
|
|
210
|
-
}
|
|
211
|
-
lines.push(...notes);
|
|
212
|
-
if (lines.length === 0) {
|
|
213
|
-
return {
|
|
214
|
-
success: true,
|
|
215
|
-
output: t("lsp.check_clean", { count: String(checked) }),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
return { success: errors.length === 0, output: lines.join("\n") };
|
|
219
|
-
}
|
|
220
56
|
}
|
|
221
57
|
function formatDiagnostics(diagnostics, filePath) {
|
|
222
|
-
const filename = filePath.replace(/\\/g,
|
|
58
|
+
const filename = filePath.replace(/\\/g, '/').split('/').pop() ?? filePath;
|
|
223
59
|
return diagnostics
|
|
224
60
|
.slice(0, 5)
|
|
225
61
|
.map((d) => {
|
|
226
62
|
const line = d.range.start.line + 1;
|
|
227
63
|
const col = d.range.start.character + 1;
|
|
228
|
-
const sev = severityLabels[d.severity] ??
|
|
64
|
+
const sev = severityLabels[d.severity] ?? 'unknown';
|
|
229
65
|
return ` ${filename}(${line},${col}): ${sev}: ${d.message}`;
|
|
230
66
|
})
|
|
231
|
-
.join(
|
|
67
|
+
.join('\n');
|
|
232
68
|
}
|