micro-models-agent 0.28.17 → 0.29.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/dist/cli/commands.js +3 -116
- package/dist/cli/main.js +8 -35
- package/dist/cli/repl.js +611 -110
- package/dist/cli/setup.js +12 -32
- package/dist/config/config.js +30 -46
- package/dist/config/defaults.js +1 -10
- package/dist/config/security.js +8 -15
- package/dist/core/agent-moe.js +12 -24
- package/dist/core/agent.js +47 -281
- package/dist/core/bootstrap.js +36 -52
- package/dist/core/session-logger.js +2 -35
- package/dist/i18n/en.json +15 -79
- package/dist/i18n/index.js +9 -12
- package/dist/i18n/ru.json +15 -79
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +10 -39
- package/dist/logger/app-logger.js +16 -83
- package/dist/main.js +624 -243
- package/dist/modules/browser/session.js +60 -108
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/manager.js +10 -119
- package/dist/modules/execution/auditor.js +39 -33
- package/dist/modules/execution/index.js +6 -8
- package/dist/modules/execution/module.js +32 -474
- package/dist/modules/execution/moe-executor.js +40 -97
- package/dist/modules/execution/planner.js +13 -63
- package/dist/modules/execution/stuck-detector.js +39 -252
- package/dist/modules/execution/tracker.js +7 -21
- package/dist/modules/execution/verifier.js +17 -46
- package/dist/modules/hallucination/confidence.js +2 -7
- package/dist/modules/hallucination/consistency.js +42 -8
- package/dist/modules/hallucination/detector.js +21 -26
- package/dist/modules/hallucination/factual.js +150 -170
- package/dist/modules/hallucination/index.js +4 -5
- package/dist/modules/index.js +5 -5
- package/dist/modules/mcp/client.js +2 -8
- package/dist/modules/memory/store.js +0 -4
- package/dist/modules/plugins/builtin/lint-on-write.js +38 -143
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +2 -1
- package/dist/modules/processes/registry.js +35 -125
- package/dist/modules/processes/runner.js +110 -9
- package/dist/modules/security/audit-log.js +10 -30
- package/dist/modules/security/command-validator.js +16 -42
- package/dist/modules/security/content-scanner.js +8 -9
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +10 -64
- package/dist/modules/security/security-policies.js +67 -221
- package/dist/modules/security/session-encryption.js +25 -42
- package/dist/modules/session/manager.js +10 -15
- package/dist/modules/session/store.js +8 -62
- package/dist/modules/skills/index.js +3 -2
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +23 -10
- package/dist/tools/bash.js +90 -287
- package/dist/tools/create-dir.js +1 -0
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/edit-file.js +8 -10
- package/dist/tools/executor.js +7 -57
- package/dist/tools/grep-tool.js +29 -51
- package/dist/tools/index.js +40 -55
- package/dist/tools/load-skill.js +18 -14
- package/dist/tools/move-file.js +2 -3
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +5 -15
- package/dist/tools/search-history.js +22 -42
- package/dist/tools/subagent.js +12 -21
- package/dist/tools/web-browse.js +25 -54
- package/dist/tools/web-fetch.js +34 -60
- package/dist/tools/web-search.js +20 -39
- package/dist/tools/write-file.js +10 -13
- package/dist/ui/diff.js +16 -9
- package/dist/ui/renderer.js +6 -69
- package/package.json +1 -1
- package/dist/cli/repl-commands.js +0 -633
- package/dist/core/workspace.js +0 -76
- package/dist/logger/file-log.js +0 -151
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
const JUDGE_TIMEOUT_MS = 15000;
|
|
3
|
-
const MAX_DECISIONS_SHOWN = 6;
|
|
4
|
-
const MAX_RESPONSE_CHARS = 2000;
|
|
5
|
-
/**
|
|
6
|
-
* LLM-as-judge consistency check. Asks a model whether the agent's latest
|
|
7
|
-
* response contradicts any of its earlier decisions — replacing the fragile
|
|
8
|
-
* word-matching heuristic that only caught exact substring reversals.
|
|
9
|
-
*
|
|
10
|
-
* Fast path: when no decisions are tracked, returns pass without any LLM call.
|
|
11
|
-
* Any judge failure (timeout, provider error, unparseable verdict) degrades to
|
|
12
|
-
* pass so the agent loop is never blocked by the judge.
|
|
13
|
-
*/
|
|
14
|
-
export class LLMJudge {
|
|
15
|
-
provider;
|
|
16
|
-
constructor(provider) {
|
|
17
|
-
this.provider = provider;
|
|
18
|
-
}
|
|
19
|
-
async validate(response, consistency) {
|
|
20
|
-
const decisions = consistency.getDecisions();
|
|
21
|
-
if (decisions.length === 0) {
|
|
22
|
-
return { status: "pass" };
|
|
23
|
-
}
|
|
24
|
-
const shown = decisions.slice(-MAX_DECISIONS_SHOWN);
|
|
25
|
-
const decisionLines = shown
|
|
26
|
-
.map((d, i) => `${i + 1}. [${d.location || "agent"}] "${d.decision}"`)
|
|
27
|
-
.join("\n");
|
|
28
|
-
const system = `You are a consistency checker for an AI coding agent. The agent previously made these decisions:
|
|
29
|
-
|
|
30
|
-
${decisionLines}
|
|
31
|
-
|
|
32
|
-
Decide whether the agent's NEW response contradicts any of these decisions. A contradiction means reversing or abandoning a previously stated approach (e.g. switching to a different framework, changing a chosen strategy, or undoing a decision that was explicitly made).
|
|
33
|
-
|
|
34
|
-
Reply with ONLY valid JSON, no other text:
|
|
35
|
-
- {"contradicts": false}
|
|
36
|
-
- {"contradicts": true, "reason": "short explanation"}`;
|
|
37
|
-
const user = `Agent's new response:\n\n${response.slice(0, MAX_RESPONSE_CHARS)}`;
|
|
38
|
-
try {
|
|
39
|
-
const verdictText = await this.askJudge([
|
|
40
|
-
{ role: "system", content: system },
|
|
41
|
-
{ role: "user", content: user },
|
|
42
|
-
]);
|
|
43
|
-
const verdict = this.parseVerdict(verdictText);
|
|
44
|
-
if (verdict.contradicts) {
|
|
45
|
-
const reason = verdict.reason?.trim()
|
|
46
|
-
? ` — ${verdict.reason.trim()}`
|
|
47
|
-
: "";
|
|
48
|
-
return {
|
|
49
|
-
status: "warn",
|
|
50
|
-
reason: t("hall.contradiction_llm", { reason }),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return { status: "pass" };
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return { status: "pass" };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async askJudge(messages) {
|
|
60
|
-
let text = "";
|
|
61
|
-
let timer;
|
|
62
|
-
const consume = (async () => {
|
|
63
|
-
for await (const chunk of this.provider.chat(messages, [])) {
|
|
64
|
-
if (chunk.type === "text" && chunk.content) {
|
|
65
|
-
text += chunk.content;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return text;
|
|
69
|
-
})();
|
|
70
|
-
const timeout = new Promise((_, reject) => {
|
|
71
|
-
timer = setTimeout(() => reject(new Error("LLM judge timed out")), JUDGE_TIMEOUT_MS);
|
|
72
|
-
});
|
|
73
|
-
try {
|
|
74
|
-
return await Promise.race([consume, timeout]);
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
if (timer)
|
|
78
|
-
clearTimeout(timer);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
parseVerdict(text) {
|
|
82
|
-
const block = text.match(/\{[\s\S]*\}/);
|
|
83
|
-
if (block) {
|
|
84
|
-
try {
|
|
85
|
-
const obj = JSON.parse(block[0]);
|
|
86
|
-
if (typeof obj.contradicts === "boolean") {
|
|
87
|
-
return {
|
|
88
|
-
contradicts: obj.contradicts,
|
|
89
|
-
reason: typeof obj.reason === "string" ? obj.reason : undefined,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// fall through to lenient parsing
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const lower = text.toLowerCase();
|
|
98
|
-
if (lower.includes('"contradicts": true') || /^yes\b/.test(lower.trim())) {
|
|
99
|
-
return { contradicts: true, reason: text.slice(0, 200) };
|
|
100
|
-
}
|
|
101
|
-
return { contradicts: false };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { spawn, execSync } from "child_process";
|
|
2
|
-
import { resolve } from "path";
|
|
3
|
-
const DEFAULT_TIMEOUT = 15000;
|
|
4
|
-
export class LspClient {
|
|
5
|
-
process = null;
|
|
6
|
-
requestId = 0;
|
|
7
|
-
pending = new Map();
|
|
8
|
-
buffer = "";
|
|
9
|
-
contentLength = -1;
|
|
10
|
-
diagnostics = [];
|
|
11
|
-
diagnosticsResolve = null;
|
|
12
|
-
diagnosticsTimer = null;
|
|
13
|
-
initialized = false;
|
|
14
|
-
async checkFile(filePath, baseDir, config) {
|
|
15
|
-
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
16
|
-
try {
|
|
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;
|
|
26
|
-
this.sendNotification("initialized", {});
|
|
27
|
-
const uri = this.pathToUri(resolve(filePath));
|
|
28
|
-
const fs = await import("fs");
|
|
29
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
30
|
-
const diagPromise = new Promise((resolve) => {
|
|
31
|
-
this.diagnosticsResolve = resolve;
|
|
32
|
-
this.diagnostics = [];
|
|
33
|
-
this.diagnosticsTimer = setTimeout(() => {
|
|
34
|
-
if (this.diagnosticsResolve) {
|
|
35
|
-
this.diagnosticsResolve([]);
|
|
36
|
-
this.diagnosticsResolve = null;
|
|
37
|
-
}
|
|
38
|
-
}, timeout);
|
|
39
|
-
});
|
|
40
|
-
this.sendNotification("textDocument/didOpen", {
|
|
41
|
-
textDocument: {
|
|
42
|
-
uri,
|
|
43
|
-
languageId: this.languageFromPath(filePath),
|
|
44
|
-
version: 1,
|
|
45
|
-
text: content,
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
return await diagPromise;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
await this.shutdown();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
async startServer(config, baseDir) {
|
|
58
|
-
if (config.autoInstall === false) {
|
|
59
|
-
try {
|
|
60
|
-
execSync(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
throw new Error(`${config.command} not found in PATH`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const args = config.args ?? [];
|
|
68
|
-
const proc = spawn(config.command, args, {
|
|
69
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
-
env: { ...process.env, ...config.env },
|
|
71
|
-
cwd: baseDir,
|
|
72
|
-
});
|
|
73
|
-
proc.on("error", reject);
|
|
74
|
-
proc.stdout.on("data", (chunk) => {
|
|
75
|
-
this.handleData(chunk);
|
|
76
|
-
});
|
|
77
|
-
proc.stderr.on("data", () => { });
|
|
78
|
-
proc.once("spawn", () => {
|
|
79
|
-
resolve();
|
|
80
|
-
});
|
|
81
|
-
this.process = proc;
|
|
82
|
-
setTimeout(() => {
|
|
83
|
-
if (!this.initialized && this.process) {
|
|
84
|
-
reject(new Error("LSP server start timeout"));
|
|
85
|
-
}
|
|
86
|
-
}, config.timeout ?? 10000);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
handleData(chunk) {
|
|
90
|
-
this.buffer += chunk.toString();
|
|
91
|
-
this.parseMessages();
|
|
92
|
-
}
|
|
93
|
-
parseMessages() {
|
|
94
|
-
while (true) {
|
|
95
|
-
if (this.contentLength === -1) {
|
|
96
|
-
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
97
|
-
if (headerEnd === -1)
|
|
98
|
-
return;
|
|
99
|
-
const header = this.buffer.slice(0, headerEnd);
|
|
100
|
-
const match = header.match(/Content-Length: (\d+)/i);
|
|
101
|
-
if (!match) {
|
|
102
|
-
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
103
|
-
this.contentLength = -1;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
this.contentLength = parseInt(match[1], 10);
|
|
107
|
-
// Guard against malicious/buggy servers sending absurd Content-Length
|
|
108
|
-
if (this.contentLength > 10 * 1024 * 1024) {
|
|
109
|
-
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
110
|
-
this.contentLength = -1;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
this.buffer = this.buffer.slice(headerEnd + 4);
|
|
114
|
-
}
|
|
115
|
-
if (this.buffer.length < this.contentLength)
|
|
116
|
-
return;
|
|
117
|
-
const body = this.buffer.slice(0, this.contentLength);
|
|
118
|
-
this.buffer = this.buffer.slice(this.contentLength);
|
|
119
|
-
this.contentLength = -1;
|
|
120
|
-
try {
|
|
121
|
-
const msg = JSON.parse(body);
|
|
122
|
-
this.handleMessage(msg);
|
|
123
|
-
}
|
|
124
|
-
catch { }
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
handleMessage(msg) {
|
|
128
|
-
if (msg.method === "textDocument/publishDiagnostics") {
|
|
129
|
-
const params = msg.params;
|
|
130
|
-
if (params?.diagnostics) {
|
|
131
|
-
this.diagnostics = params.diagnostics;
|
|
132
|
-
if (this.diagnosticsTimer) {
|
|
133
|
-
clearTimeout(this.diagnosticsTimer);
|
|
134
|
-
this.diagnosticsTimer = null;
|
|
135
|
-
}
|
|
136
|
-
if (this.diagnosticsResolve) {
|
|
137
|
-
this.diagnosticsResolve(this.diagnostics);
|
|
138
|
-
this.diagnosticsResolve = null;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (msg.id !== undefined && msg.id !== null) {
|
|
144
|
-
const pending = this.pending.get(msg.id);
|
|
145
|
-
if (pending) {
|
|
146
|
-
this.pending.delete(msg.id);
|
|
147
|
-
if (msg.error) {
|
|
148
|
-
pending.reject(new Error(String(msg.error.message ?? "LSP error")));
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
pending.resolve(msg.result);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
sendRequest(method, params, timeout) {
|
|
157
|
-
return new Promise((resolve, reject) => {
|
|
158
|
-
const id = ++this.requestId;
|
|
159
|
-
this.pending.set(id, { resolve, reject });
|
|
160
|
-
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
161
|
-
this.write(message);
|
|
162
|
-
setTimeout(() => {
|
|
163
|
-
if (this.pending.has(id)) {
|
|
164
|
-
this.pending.delete(id);
|
|
165
|
-
reject(new Error(`LSP request timeout: ${method}`));
|
|
166
|
-
}
|
|
167
|
-
}, timeout);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
sendNotification(method, params) {
|
|
171
|
-
const message = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
172
|
-
this.write(message);
|
|
173
|
-
}
|
|
174
|
-
write(message) {
|
|
175
|
-
if (!this.process?.stdin?.writable)
|
|
176
|
-
return;
|
|
177
|
-
const header = `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`;
|
|
178
|
-
try {
|
|
179
|
-
this.process.stdin.write(header + message);
|
|
180
|
-
}
|
|
181
|
-
catch { }
|
|
182
|
-
}
|
|
183
|
-
async shutdown() {
|
|
184
|
-
try {
|
|
185
|
-
if (this.process && this.initialized && this.process.stdin?.writable) {
|
|
186
|
-
this.sendRequest("shutdown", null, 3000).catch(() => { });
|
|
187
|
-
this.sendNotification("exit", null);
|
|
188
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
}
|
|
193
|
-
finally {
|
|
194
|
-
if (this.process) {
|
|
195
|
-
try {
|
|
196
|
-
this.process.kill();
|
|
197
|
-
}
|
|
198
|
-
catch { }
|
|
199
|
-
this.process = null;
|
|
200
|
-
}
|
|
201
|
-
this.initialized = false;
|
|
202
|
-
if (this.diagnosticsTimer) {
|
|
203
|
-
clearTimeout(this.diagnosticsTimer);
|
|
204
|
-
this.diagnosticsTimer = null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
pathToUri(filePath) {
|
|
209
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
210
|
-
if (/^[a-zA-Z]:/.test(normalized)) {
|
|
211
|
-
return `file:///${normalized}`;
|
|
212
|
-
}
|
|
213
|
-
return `file://${normalized}`;
|
|
214
|
-
}
|
|
215
|
-
languageFromPath(filePath) {
|
|
216
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
217
|
-
const map = {
|
|
218
|
-
ts: "typescript",
|
|
219
|
-
tsx: "typescriptreact",
|
|
220
|
-
js: "javascript",
|
|
221
|
-
jsx: "javascriptreact",
|
|
222
|
-
py: "python",
|
|
223
|
-
go: "go",
|
|
224
|
-
rs: "rust",
|
|
225
|
-
json: "json",
|
|
226
|
-
html: "html",
|
|
227
|
-
css: "css",
|
|
228
|
-
scss: "scss",
|
|
229
|
-
md: "markdown",
|
|
230
|
-
yaml: "yaml",
|
|
231
|
-
yml: "yaml",
|
|
232
|
-
};
|
|
233
|
-
return map[ext ?? ""] ?? "plaintext";
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LSP server defaults.
|
|
3
|
-
*
|
|
4
|
-
* Strategy: prefer `npx` (auto-downloads) over globally installed binaries.
|
|
5
|
-
* Servers using system tools (rust-analyzer, gopls) have `autoInstall: false`
|
|
6
|
-
* and are skipped silently when not found.
|
|
7
|
-
*/
|
|
8
|
-
export const DEFAULT_LSP_CONFIG = {
|
|
9
|
-
enabled: true,
|
|
10
|
-
timeout: 15000,
|
|
11
|
-
servers: {
|
|
12
|
-
typescript: {
|
|
13
|
-
command: 'npx',
|
|
14
|
-
args: ['typescript-language-server', '--stdio'],
|
|
15
|
-
timeout: 20000,
|
|
16
|
-
autoInstall: true,
|
|
17
|
-
},
|
|
18
|
-
javascript: {
|
|
19
|
-
command: 'npx',
|
|
20
|
-
args: ['typescript-language-server', '--stdio'],
|
|
21
|
-
timeout: 15000,
|
|
22
|
-
autoInstall: true,
|
|
23
|
-
},
|
|
24
|
-
python: {
|
|
25
|
-
command: 'npx',
|
|
26
|
-
args: ['pyright', '--stdio'],
|
|
27
|
-
timeout: 15000,
|
|
28
|
-
autoInstall: true,
|
|
29
|
-
},
|
|
30
|
-
rust: {
|
|
31
|
-
command: 'rust-analyzer',
|
|
32
|
-
timeout: 15000,
|
|
33
|
-
autoInstall: false,
|
|
34
|
-
},
|
|
35
|
-
go: {
|
|
36
|
-
command: 'gopls',
|
|
37
|
-
timeout: 15000,
|
|
38
|
-
autoInstall: false,
|
|
39
|
-
},
|
|
40
|
-
json: {
|
|
41
|
-
command: 'npx',
|
|
42
|
-
args: ['vscode-json-languageserver', '--stdio'],
|
|
43
|
-
timeout: 10000,
|
|
44
|
-
autoInstall: true,
|
|
45
|
-
},
|
|
46
|
-
html: {
|
|
47
|
-
command: 'npx',
|
|
48
|
-
args: ['vscode-html-languageserver', '--stdio'],
|
|
49
|
-
timeout: 10000,
|
|
50
|
-
autoInstall: true,
|
|
51
|
-
},
|
|
52
|
-
css: {
|
|
53
|
-
command: 'npx',
|
|
54
|
-
args: ['vscode-css-languageserver', '--stdio'],
|
|
55
|
-
timeout: 10000,
|
|
56
|
-
autoInstall: true,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
const LANGUAGE_MAP = {
|
|
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',
|
|
72
|
-
};
|
|
73
|
-
export function getServerForFile(filePath, config) {
|
|
74
|
-
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
75
|
-
if (!ext)
|
|
76
|
-
return null;
|
|
77
|
-
const lang = LANGUAGE_MAP[ext];
|
|
78
|
-
if (!lang)
|
|
79
|
-
return null;
|
|
80
|
-
return config.servers[lang] ?? null;
|
|
81
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
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
|
-
};
|
|
11
|
-
export class LspModule {
|
|
12
|
-
name = 'lsp';
|
|
13
|
-
config;
|
|
14
|
-
client = new LspClient();
|
|
15
|
-
constructor(config) {
|
|
16
|
-
this.config = { ...DEFAULT_LSP_CONFIG, ...config };
|
|
17
|
-
}
|
|
18
|
-
getPlugin() {
|
|
19
|
-
const self = this;
|
|
20
|
-
return {
|
|
21
|
-
name: 'lsp-check',
|
|
22
|
-
priority: 15,
|
|
23
|
-
onAfterTool: async (_ctx, call, result) => {
|
|
24
|
-
if (!self.config.enabled)
|
|
25
|
-
return;
|
|
26
|
-
if (call.name !== 'write_file' && call.name !== 'edit_file')
|
|
27
|
-
return;
|
|
28
|
-
if (!result.success)
|
|
29
|
-
return;
|
|
30
|
-
const filePath = String(call.arguments.path ?? '');
|
|
31
|
-
if (!filePath)
|
|
32
|
-
return;
|
|
33
|
-
const fullPath = resolve(_ctx.baseDir, filePath);
|
|
34
|
-
if (!existsSync(fullPath))
|
|
35
|
-
return;
|
|
36
|
-
const serverConfig = getServerForFile(fullPath, self.config);
|
|
37
|
-
if (!serverConfig)
|
|
38
|
-
return;
|
|
39
|
-
try {
|
|
40
|
-
const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig);
|
|
41
|
-
const errors = diagnostics.filter((d) => d.severity === 1);
|
|
42
|
-
const warnings = diagnostics.filter((d) => d.severity === 2);
|
|
43
|
-
if (errors.length > 0) {
|
|
44
|
-
const items = formatDiagnostics(errors, fullPath);
|
|
45
|
-
result.output += `\n\n[LSP errors]:\n${items}`;
|
|
46
|
-
}
|
|
47
|
-
if (warnings.length > 0) {
|
|
48
|
-
const items = formatDiagnostics(warnings, fullPath);
|
|
49
|
-
result.output += `\n\n[LSP warnings]:\n${items}`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch { }
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function formatDiagnostics(diagnostics, filePath) {
|
|
58
|
-
const filename = filePath.replace(/\\/g, '/').split('/').pop() ?? filePath;
|
|
59
|
-
return diagnostics
|
|
60
|
-
.slice(0, 5)
|
|
61
|
-
.map((d) => {
|
|
62
|
-
const line = d.range.start.line + 1;
|
|
63
|
-
const col = d.range.start.character + 1;
|
|
64
|
-
const sev = severityLabels[d.severity] ?? 'unknown';
|
|
65
|
-
return ` ${filename}(${line},${col}): ${sev}: ${d.message}`;
|
|
66
|
-
})
|
|
67
|
-
.join('\n');
|
|
68
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|