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.
Files changed (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +19 -9
  3. package/dist/cli/completer.js +37 -36
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +23 -48
  6. package/dist/cli/repl-commands.js +12 -40
  7. package/dist/cli/repl.js +87 -217
  8. package/dist/cli/security-commands.js +7 -5
  9. package/dist/cli/setup.js +26 -8
  10. package/dist/config/config.js +5 -52
  11. package/dist/config/defaults.js +5 -29
  12. package/dist/config/experts.js +1 -1
  13. package/dist/config/index.js +3 -3
  14. package/dist/config/security.js +10 -3
  15. package/dist/core/agent-moe.js +10 -2
  16. package/dist/core/agent.js +82 -273
  17. package/dist/core/bootstrap.js +13 -80
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/prompt-builder.js +2 -23
  20. package/dist/core/session-logger.js +4 -46
  21. package/dist/i18n/en.json +2 -75
  22. package/dist/i18n/ru.json +1 -74
  23. package/dist/index.js +1 -1
  24. package/dist/llm/image-utils.js +5 -4
  25. package/dist/llm/index.js +4 -4
  26. package/dist/llm/model-loader.js +6 -6
  27. package/dist/llm/openai-compat.js +34 -40
  28. package/dist/llm/orchestrator.js +29 -33
  29. package/dist/llm/response.js +9 -9
  30. package/dist/logger/app-logger.js +1 -1
  31. package/dist/logger/index.js +1 -1
  32. package/dist/main.js +648 -183
  33. package/dist/migration/backup.js +13 -13
  34. package/dist/migration/detect.js +11 -11
  35. package/dist/migration/index.js +2 -2
  36. package/dist/modules/browser/actions.js +4 -34
  37. package/dist/modules/browser/bridge-server.mjs +202 -202
  38. package/dist/modules/browser/cookie-store.js +6 -6
  39. package/dist/modules/browser/index.js +5 -7
  40. package/dist/modules/browser/module.js +7 -8
  41. package/dist/modules/browser/session.js +84 -87
  42. package/dist/modules/browser/snapshot.js +58 -92
  43. package/dist/modules/browser/types.js +1 -4
  44. package/dist/modules/certification/cli.js +4 -2
  45. package/dist/modules/certification/fact-checker.js +3 -1
  46. package/dist/modules/certification/loader.js +9 -3
  47. package/dist/modules/certification/runner.js +4 -1
  48. package/dist/modules/context/index.js +1 -1
  49. package/dist/modules/context/manager.js +86 -160
  50. package/dist/modules/execution/auditor.js +25 -177
  51. package/dist/modules/execution/module.js +544 -201
  52. package/dist/modules/execution/moe-executor.js +0 -25
  53. package/dist/modules/execution/plan-store.js +3 -1
  54. package/dist/modules/execution/plan-validator.js +10 -10
  55. package/dist/modules/execution/planner.js +1 -6
  56. package/dist/modules/execution/stuck-detector.js +10 -173
  57. package/dist/modules/execution/verifier.js +42 -86
  58. package/dist/modules/hallucination/confidence.js +1 -8
  59. package/dist/modules/hallucination/detector.js +5 -2
  60. package/dist/modules/hallucination/factual.js +64 -3
  61. package/dist/modules/hallucination/index.js +1 -1
  62. package/dist/modules/hallucination/js-identifiers.js +0 -190
  63. package/dist/modules/hallucination/llm-judge.js +3 -1
  64. package/dist/modules/indexer/cache.js +7 -9
  65. package/dist/modules/indexer/index.js +3 -3
  66. package/dist/modules/indexer/module.js +42 -95
  67. package/dist/modules/indexer/walker.js +17 -17
  68. package/dist/modules/lsp/client.js +31 -74
  69. package/dist/modules/lsp/config.js +33 -87
  70. package/dist/modules/lsp/index.js +3 -3
  71. package/dist/modules/lsp/module.js +21 -185
  72. package/dist/modules/mcp/module.js +6 -2
  73. package/dist/modules/memory/index.js +1 -1
  74. package/dist/modules/memory/module.js +23 -71
  75. package/dist/modules/memory/search.js +9 -11
  76. package/dist/modules/memory/store.js +13 -13
  77. package/dist/modules/pipelines/engine.js +10 -10
  78. package/dist/modules/pipelines/index.js +3 -3
  79. package/dist/modules/pipelines/parser.js +14 -17
  80. package/dist/modules/pipelines/template.js +1 -1
  81. package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
  82. package/dist/modules/plugins/builtin/notify.js +2 -3
  83. package/dist/modules/plugins/index.js +1 -1
  84. package/dist/modules/plugins/loader.js +17 -59
  85. package/dist/modules/plugins/manager.js +17 -73
  86. package/dist/modules/processes/index.js +1 -1
  87. package/dist/modules/processes/registry.js +46 -135
  88. package/dist/modules/registry.js +2 -4
  89. package/dist/modules/security/audit-notifier.js +39 -39
  90. package/dist/modules/security/command-validator.js +8 -2
  91. package/dist/modules/security/data-sanitizer.js +9 -1
  92. package/dist/modules/security/encryption.js +56 -58
  93. package/dist/modules/security/network-validator.js +9 -1
  94. package/dist/modules/security/path-validator.js +3 -1
  95. package/dist/modules/security/security-policies.js +19 -3
  96. package/dist/modules/security/session-encryption.js +1 -1
  97. package/dist/modules/security/session-isolation.js +8 -8
  98. package/dist/modules/session/index.js +3 -3
  99. package/dist/modules/session/module.js +5 -5
  100. package/dist/modules/session/store.js +9 -3
  101. package/dist/modules/skills/module.js +2 -1
  102. package/dist/modules/updater/checker.js +6 -70
  103. package/dist/modules/updater/index.js +1 -2
  104. package/dist/modules/user-profile/compressor.js +2 -2
  105. package/dist/modules/user-profile/index.js +1 -1
  106. package/dist/modules/user-profile/profile.js +9 -9
  107. package/dist/tools/attach-image.js +1 -1
  108. package/dist/tools/bash.js +19 -178
  109. package/dist/tools/browser.js +29 -46
  110. package/dist/tools/executor.js +5 -4
  111. package/dist/tools/file-info.js +12 -13
  112. package/dist/tools/filter-tools.js +2 -9
  113. package/dist/tools/glob-tool.js +11 -11
  114. package/dist/tools/grep-tool.js +3 -1
  115. package/dist/tools/index.js +2 -13
  116. package/dist/tools/list-dir.js +17 -18
  117. package/dist/tools/load-skill.js +3 -1
  118. package/dist/tools/path-utils.js +4 -4
  119. package/dist/tools/pipeline-run.js +25 -25
  120. package/dist/tools/process-kill.js +11 -11
  121. package/dist/tools/process-list.js +22 -20
  122. package/dist/tools/process-log.js +18 -22
  123. package/dist/tools/question.js +3 -1
  124. package/dist/tools/read-file.js +2 -10
  125. package/dist/tools/recall.js +37 -44
  126. package/dist/tools/registry.js +4 -15
  127. package/dist/tools/remember.js +29 -29
  128. package/dist/tools/scope-check.js +9 -9
  129. package/dist/tools/subagent.js +9 -54
  130. package/dist/tools/user-input.js +1 -1
  131. package/dist/tools/web-browse.js +3 -3
  132. package/dist/tools/web-fetch.js +3 -3
  133. package/dist/tools/web-search.js +3 -3
  134. package/dist/tools/write-file.js +3 -1
  135. package/dist/ui/box.js +5 -1
  136. package/dist/ui/index.js +6 -6
  137. package/dist/ui/md-formatter.js +33 -33
  138. package/dist/ui/output.js +5 -5
  139. package/dist/ui/renderer.js +10 -15
  140. package/dist/ui/table.js +1 -1
  141. package/package.json +48 -48
  142. package/dist/cli/plugin-commands.js +0 -36
  143. package/dist/cli/run-result.js +0 -22
  144. package/dist/core/version.js +0 -24
  145. package/dist/modules/artifacts/store.js +0 -61
  146. package/dist/modules/browser/bridge-client.js +0 -199
  147. package/dist/modules/browser/bridge-path.js +0 -10
  148. package/dist/modules/browser/driver.js +0 -136
  149. package/dist/modules/context/chunk-query.js +0 -100
  150. package/dist/modules/context/fact-extractor.js +0 -162
  151. package/dist/modules/context/history.js +0 -15
  152. package/dist/modules/execution/audit-runners.js +0 -152
  153. package/dist/modules/execution/execution-plugin.js +0 -272
  154. package/dist/modules/execution/plan-tool.js +0 -508
  155. package/dist/modules/execution/windows-commands.js +0 -41
  156. package/dist/modules/indexer/project-profile.js +0 -183
  157. package/dist/modules/lsp/check-tool.js +0 -58
  158. package/dist/modules/lsp/command.js +0 -60
  159. package/dist/modules/lsp/probe.js +0 -76
  160. package/dist/modules/lsp/project-root.js +0 -32
  161. package/dist/modules/lsp/startup-check.js +0 -141
  162. package/dist/modules/processes/detect.js +0 -34
  163. package/dist/modules/skills/matcher.js +0 -27
  164. package/dist/modules/updater/module.js +0 -116
  165. package/dist/tools/chunk-query.js +0 -99
  166. package/dist/tools/download-file.js +0 -116
  167. package/dist/tools/enable-tools.js +0 -58
  168. package/dist/tools/hidden-tools-block.js +0 -37
  169. package/dist/ui/line-editor.js +0 -703
  170. package/dist/ui/line-math.js +0 -69
  171. package/dist/ui/plan-view.js +0 -103
@@ -1,19 +1,19 @@
1
- import { readdirSync, readFileSync, statSync, existsSync, watch } from "fs";
2
- import { join, relative, extname } from "path";
1
+ import { readdirSync, readFileSync, statSync, existsSync, watch } from 'fs';
2
+ import { join, relative, extname } from 'path';
3
3
  const LANGUAGES = {
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",
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(["node_modules", ".git", "dist", "build", ".mma", "coverage"]);
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, "utf-8");
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 === "typescript" || language === "javascript") {
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, projectRoot = baseDir) {
14
+ async checkFile(filePath, baseDir, config) {
18
15
  const timeout = config.timeout ?? DEFAULT_TIMEOUT;
19
16
  try {
20
- await this.initialize(config, projectRoot, timeout);
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
- this.diagnosticsResolve = null;
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 (e) {
44
- // Re-throw so the LspModule can surface failures instead of hiding them.
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 initialize(config, projectRoot, timeout) {
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 isWin = platform() === "win32";
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: projectRoot,
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?.(this.diagnostics);
176
- this.diagnosticsResolve = null;
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
- killTree(this.process);
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
- return /^[a-zA-Z]:/.test(normalized) ? `file:///${normalized}` : `file://${normalized}`;
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: "npx",
14
- args: [
15
- "--yes",
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: "npx",
29
- args: [
30
- "--yes",
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: "npx",
44
- args: ["pyright", "--stdio"],
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: "rust-analyzer",
31
+ command: 'rust-analyzer',
51
32
  timeout: 15000,
52
33
  autoInstall: false,
53
- workspaceMarkers: ["Cargo.toml"],
54
34
  },
55
35
  go: {
56
- command: "gopls",
36
+ command: 'gopls',
57
37
  timeout: 15000,
58
38
  autoInstall: false,
59
- workspaceMarkers: ["go.mod"],
60
39
  },
61
40
  json: {
62
- command: "npx",
63
- args: [
64
- "--yes",
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: "npx",
76
- args: [
77
- "--yes",
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: "npx",
91
- args: [
92
- "--yes",
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: "typescript",
108
- tsx: "typescript",
109
- mts: "typescript",
110
- cts: "typescript",
111
- js: "javascript",
112
- jsx: "javascript",
113
- mjs: "javascript",
114
- cjs: "javascript",
115
- py: "python",
116
- rs: "rust",
117
- go: "go",
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
- /** Language id for a file extension (via LANGUAGE_MAP), or null when unsupported. */
124
- export function getLanguageForFile(filePath) {
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
- return LANGUAGE_MAP[ext] ?? null;
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 "./module";
2
- export { LspClient } from "./client";
3
- export { getServerForFile, DEFAULT_LSP_CONFIG } from "./config";
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 "fs";
2
- import { resolve } from "path";
3
- import { LspClient } from "./client";
4
- import { getServerForFile, DEFAULT_LSP_CONFIG } from "./config";
5
- import { findProjectRoot } from "./project-root";
6
- import { isPathInScope } from "../security/path-validator";
7
- import { DEFAULT_SECURITY_CONFIG } from "../../config/security";
8
- import { safeResolvePath } from "../../tools/path-utils";
9
- import { t } from "../../i18n/index";
10
- import { severityLabels, collectCheckFiles, formatCheckDiagnostics } from "./check-tool";
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 = "lsp";
12
+ name = 'lsp';
22
13
  config;
23
- client;
24
- failuresByServer = new Map();
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: "lsp-check",
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 !== "write_file" && call.name !== "edit_file")
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, projectRoot);
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 (e) {
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, "/").split("/").pop() ?? filePath;
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] ?? "unknown";
64
+ const sev = severityLabels[d.severity] ?? 'unknown';
229
65
  return ` ${filename}(${line},${col}): ${sev}: ${d.message}`;
230
66
  })
231
- .join("\n");
67
+ .join('\n');
232
68
  }