micro-models-agent 0.26.0 → 0.28.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/main.js +1338 -894
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2229,7 +2229,9 @@ var init_defaults = __esm(() => {
|
|
|
2229
2229
|
navigationTimeout: 15000
|
|
2230
2230
|
},
|
|
2231
2231
|
ui: {
|
|
2232
|
-
spinner: true
|
|
2232
|
+
spinner: true,
|
|
2233
|
+
toolStyle: "inline",
|
|
2234
|
+
toolComments: true
|
|
2233
2235
|
},
|
|
2234
2236
|
mcpServers: {
|
|
2235
2237
|
context7: {
|
|
@@ -2391,7 +2393,7 @@ var init_en = __esm(() => {
|
|
|
2391
2393
|
"tool.interactive_disabled": "Interactive tool is disabled in exit-on-complete mode. Proceed without asking the user.",
|
|
2392
2394
|
"proc.started": `Started background process {id} (PID {pid}).
|
|
2393
2395
|
Command: {command}`,
|
|
2394
|
-
"proc.
|
|
2396
|
+
"proc.promoted_hint": "Command still running after {ms} ms — moved to the background",
|
|
2395
2397
|
"proc.manage_hint": "Check output: process_log id={id}. Stop it: process_kill id={id}. List all: process_list.",
|
|
2396
2398
|
"proc.none": "No background processes running.",
|
|
2397
2399
|
"proc.not_found": "Process not found: {id}",
|
|
@@ -2400,7 +2402,6 @@ Command: {command}`,
|
|
|
2400
2402
|
"proc.list_header": "Background processes",
|
|
2401
2403
|
"proc.log_header": "Process {id} ({status}) output:",
|
|
2402
2404
|
"proc.log_empty": "(no output yet)",
|
|
2403
|
-
"proc.timed_out": "Command timed out after {ms} ms and was killed.",
|
|
2404
2405
|
"proc.hint": "Manage them with {list}, {log}, {kill}.",
|
|
2405
2406
|
"proc.status_running": "running",
|
|
2406
2407
|
"proc.status_exited": "exited",
|
|
@@ -2717,7 +2718,7 @@ Use this knowledge to answer the user's question.`,
|
|
|
2717
2718
|
"hall.repetitive": "Response too repetitive ({pct}% overlap)",
|
|
2718
2719
|
"hall.uncertainty": "Uncertainty markers: {markers}",
|
|
2719
2720
|
"hall.unknown_paths": "Mentioned file paths not found in known files: {paths}",
|
|
2720
|
-
"hall.
|
|
2721
|
+
"hall.contradiction_llm": "Contradicts an earlier decision{reason}",
|
|
2721
2722
|
"hall.uncertainty_prefix": `
|
|
2722
2723
|
|
|
2723
2724
|
[⚠️ Uncertainty] `,
|
|
@@ -2936,7 +2937,7 @@ var init_ru = __esm(() => {
|
|
|
2936
2937
|
"tool.interactive_disabled": "Интерактивный инструмент отключён в режиме exit-on-complete. Продолжай без вопроса пользователю.",
|
|
2937
2938
|
"proc.started": `Фоновый процесс запущен: {id} (PID {pid}).
|
|
2938
2939
|
Команда: {command}`,
|
|
2939
|
-
"proc.
|
|
2940
|
+
"proc.promoted_hint": "Команда всё ещё выполняется через {ms} мс — переведена в фоновый режим",
|
|
2940
2941
|
"proc.manage_hint": "Проверить вывод: process_log id={id}. Остановить: process_kill id={id}. Список всех: process_list.",
|
|
2941
2942
|
"proc.none": "Фоновых процессов нет.",
|
|
2942
2943
|
"proc.not_found": "Процесс не найден: {id}",
|
|
@@ -2945,7 +2946,6 @@ var init_ru = __esm(() => {
|
|
|
2945
2946
|
"proc.list_header": "Фоновые процессы",
|
|
2946
2947
|
"proc.log_header": "Вывод процесса {id} ({status}):",
|
|
2947
2948
|
"proc.log_empty": "(вывода пока нет)",
|
|
2948
|
-
"proc.timed_out": "Команда превысила таймаут {ms} мс и была остановлена.",
|
|
2949
2949
|
"proc.hint": "Управление: {list}, {log}, {kill}.",
|
|
2950
2950
|
"proc.status_running": "работает",
|
|
2951
2951
|
"proc.status_exited": "завершён",
|
|
@@ -3262,7 +3262,7 @@ var init_ru = __esm(() => {
|
|
|
3262
3262
|
"hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
|
|
3263
3263
|
"hall.uncertainty": "Маркеры неопределённости: {markers}",
|
|
3264
3264
|
"hall.unknown_paths": "Упомянутые файлы не найдены: {paths}",
|
|
3265
|
-
"hall.
|
|
3265
|
+
"hall.contradiction_llm": "Противоречит ранее принятому решению{reason}",
|
|
3266
3266
|
"hall.uncertainty_prefix": `
|
|
3267
3267
|
|
|
3268
3268
|
[⚠️ Неопределённость] `,
|
|
@@ -4516,12 +4516,12 @@ class OpenAICompatProvider {
|
|
|
4516
4516
|
};
|
|
4517
4517
|
this.rateLimiter = createRateLimiter(config.rateLimits);
|
|
4518
4518
|
}
|
|
4519
|
-
async* chat(messages, tools) {
|
|
4519
|
+
async* chat(messages, tools, signal) {
|
|
4520
4520
|
if (!this.rateLimiter.canMakeRequest()) {
|
|
4521
4521
|
throw new Error(`Rate limit exceeded: ${this.rateLimiter.getConfig().maxRequestsPerMinute} requests per minute`);
|
|
4522
4522
|
}
|
|
4523
4523
|
this.rateLimiter.recordRequest();
|
|
4524
|
-
const streamResult = this.doStream(messages, tools);
|
|
4524
|
+
const streamResult = this.doStream(messages, tools, signal);
|
|
4525
4525
|
let hasToolCall = false;
|
|
4526
4526
|
let hasText = false;
|
|
4527
4527
|
let reasoningAcc = "";
|
|
@@ -4536,13 +4536,13 @@ class OpenAICompatProvider {
|
|
|
4536
4536
|
yield chunk;
|
|
4537
4537
|
}
|
|
4538
4538
|
if (!hasToolCall && !hasText) {
|
|
4539
|
-
const fallback = await this.doNonStreaming(messages, tools);
|
|
4539
|
+
const fallback = await this.doNonStreaming(messages, tools, signal);
|
|
4540
4540
|
for (const chunk of fallback) {
|
|
4541
4541
|
yield chunk;
|
|
4542
4542
|
}
|
|
4543
4543
|
}
|
|
4544
4544
|
}
|
|
4545
|
-
async* doStream(messages, tools) {
|
|
4545
|
+
async* doStream(messages, tools, signal) {
|
|
4546
4546
|
const body = {
|
|
4547
4547
|
model: this.model,
|
|
4548
4548
|
messages,
|
|
@@ -4569,11 +4569,12 @@ class OpenAICompatProvider {
|
|
|
4569
4569
|
const controller = new AbortController;
|
|
4570
4570
|
const totalTimeoutMs = 120000;
|
|
4571
4571
|
const timeoutId = setTimeout(() => controller.abort(), totalTimeoutMs);
|
|
4572
|
+
const abortSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
4572
4573
|
const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
4573
4574
|
method: "POST",
|
|
4574
4575
|
headers,
|
|
4575
4576
|
body: JSON.stringify(body),
|
|
4576
|
-
signal:
|
|
4577
|
+
signal: abortSignal
|
|
4577
4578
|
});
|
|
4578
4579
|
if (!response.ok) {
|
|
4579
4580
|
clearTimeout(timeoutId);
|
|
@@ -4672,7 +4673,7 @@ class OpenAICompatProvider {
|
|
|
4672
4673
|
reader.releaseLock();
|
|
4673
4674
|
}
|
|
4674
4675
|
}
|
|
4675
|
-
async doNonStreaming(messages, tools) {
|
|
4676
|
+
async doNonStreaming(messages, tools, signal) {
|
|
4676
4677
|
const body = {
|
|
4677
4678
|
model: this.model,
|
|
4678
4679
|
messages,
|
|
@@ -4700,7 +4701,8 @@ class OpenAICompatProvider {
|
|
|
4700
4701
|
const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
4701
4702
|
method: "POST",
|
|
4702
4703
|
headers,
|
|
4703
|
-
body: JSON.stringify(body)
|
|
4704
|
+
body: JSON.stringify(body),
|
|
4705
|
+
signal
|
|
4704
4706
|
});
|
|
4705
4707
|
if (!response.ok) {
|
|
4706
4708
|
const errorText = await response.text();
|
|
@@ -4794,7 +4796,7 @@ class OpenAICompatProvider {
|
|
|
4794
4796
|
if (attempt < maxRetries) {
|
|
4795
4797
|
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
4796
4798
|
const jitter = Math.random() * baseDelay * 0.1;
|
|
4797
|
-
await this.sleep(delay + jitter);
|
|
4799
|
+
await this.sleep(delay + jitter, init.signal ?? undefined);
|
|
4798
4800
|
}
|
|
4799
4801
|
}
|
|
4800
4802
|
throw lastError ?? new Error(t("error.llm_retries"));
|
|
@@ -4802,8 +4804,23 @@ class OpenAICompatProvider {
|
|
|
4802
4804
|
isRetryable(status) {
|
|
4803
4805
|
return status === 429 || status >= 500;
|
|
4804
4806
|
}
|
|
4805
|
-
sleep(ms) {
|
|
4806
|
-
return new Promise((resolve) =>
|
|
4807
|
+
sleep(ms, signal) {
|
|
4808
|
+
return new Promise((resolve, reject) => {
|
|
4809
|
+
if (signal?.aborted) {
|
|
4810
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
4811
|
+
return;
|
|
4812
|
+
}
|
|
4813
|
+
let timer;
|
|
4814
|
+
const onAbort = () => {
|
|
4815
|
+
clearTimeout(timer);
|
|
4816
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
4817
|
+
};
|
|
4818
|
+
timer = setTimeout(() => {
|
|
4819
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4820
|
+
resolve();
|
|
4821
|
+
}, ms);
|
|
4822
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4823
|
+
});
|
|
4807
4824
|
}
|
|
4808
4825
|
}
|
|
4809
4826
|
var init_openai_compat = __esm(() => {
|
|
@@ -4929,8 +4946,12 @@ class ToolRegistry {
|
|
|
4929
4946
|
}
|
|
4930
4947
|
|
|
4931
4948
|
// src/modules/processes/runner.ts
|
|
4932
|
-
|
|
4933
|
-
|
|
4949
|
+
function registerKillable(callId, kill) {
|
|
4950
|
+
activeChildren.set(callId, { kill });
|
|
4951
|
+
}
|
|
4952
|
+
function unregisterKillable(callId) {
|
|
4953
|
+
activeChildren.delete(callId);
|
|
4954
|
+
}
|
|
4934
4955
|
function killByCallId(callId) {
|
|
4935
4956
|
const entry = activeChildren.get(callId);
|
|
4936
4957
|
if (!entry)
|
|
@@ -4941,95 +4962,6 @@ function killByCallId(callId) {
|
|
|
4941
4962
|
} catch {}
|
|
4942
4963
|
return true;
|
|
4943
4964
|
}
|
|
4944
|
-
function killTree(child) {
|
|
4945
|
-
const pid = child.pid;
|
|
4946
|
-
if (!pid)
|
|
4947
|
-
return;
|
|
4948
|
-
if (platform() === "win32") {
|
|
4949
|
-
spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
4950
|
-
windowsHide: true,
|
|
4951
|
-
stdio: "ignore"
|
|
4952
|
-
});
|
|
4953
|
-
return;
|
|
4954
|
-
}
|
|
4955
|
-
try {
|
|
4956
|
-
process.kill(-pid, "SIGTERM");
|
|
4957
|
-
} catch {
|
|
4958
|
-
try {
|
|
4959
|
-
child.kill("SIGKILL");
|
|
4960
|
-
} catch {}
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
function runCommand(command, options = {}) {
|
|
4964
|
-
const {
|
|
4965
|
-
cwd,
|
|
4966
|
-
callId,
|
|
4967
|
-
timeoutMs = 120000,
|
|
4968
|
-
maxBuffer = 10 * 1024 * 1024
|
|
4969
|
-
} = options;
|
|
4970
|
-
return new Promise((resolve) => {
|
|
4971
|
-
let timedOut = false;
|
|
4972
|
-
let stdout = "";
|
|
4973
|
-
let stderr = "";
|
|
4974
|
-
const child = spawn(command, {
|
|
4975
|
-
cwd,
|
|
4976
|
-
shell: true,
|
|
4977
|
-
windowsHide: true,
|
|
4978
|
-
detached: platform() !== "win32",
|
|
4979
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4980
|
-
});
|
|
4981
|
-
const stdoutRef = { value: stdout };
|
|
4982
|
-
const stderrRef = { value: stderr };
|
|
4983
|
-
const decoder = platform() === "win32" ? new TextDecoder("ibm866") : null;
|
|
4984
|
-
const decodeChunk = (chunk) => decoder ? decoder.decode(chunk, { stream: true }) : chunk.toString("utf-8");
|
|
4985
|
-
child.stdout?.on("data", (chunk) => {
|
|
4986
|
-
const text = decodeChunk(chunk);
|
|
4987
|
-
const next = stdoutRef.value.length + text.length;
|
|
4988
|
-
if (next > maxBuffer) {
|
|
4989
|
-
stdoutRef.value = stdoutRef.value.slice(stdoutRef.value.length + text.length - maxBuffer) + text;
|
|
4990
|
-
} else {
|
|
4991
|
-
stdoutRef.value = stdoutRef.value + text;
|
|
4992
|
-
}
|
|
4993
|
-
});
|
|
4994
|
-
child.stderr?.on("data", (chunk) => {
|
|
4995
|
-
const text = decodeChunk(chunk);
|
|
4996
|
-
const next = stderrRef.value.length + text.length;
|
|
4997
|
-
if (next > maxBuffer) {
|
|
4998
|
-
stderrRef.value = stderrRef.value.slice(stderrRef.value.length + text.length - maxBuffer) + text;
|
|
4999
|
-
} else {
|
|
5000
|
-
stderrRef.value = stderrRef.value + text;
|
|
5001
|
-
}
|
|
5002
|
-
});
|
|
5003
|
-
const entry = {
|
|
5004
|
-
kill: () => killTree(child)
|
|
5005
|
-
};
|
|
5006
|
-
if (callId) {
|
|
5007
|
-
activeChildren.set(callId, entry);
|
|
5008
|
-
}
|
|
5009
|
-
const timer = setTimeout(() => {
|
|
5010
|
-
timedOut = true;
|
|
5011
|
-
killTree(child);
|
|
5012
|
-
}, timeoutMs);
|
|
5013
|
-
child.on("error", () => {
|
|
5014
|
-
clearTimeout(timer);
|
|
5015
|
-
if (callId)
|
|
5016
|
-
activeChildren.delete(callId);
|
|
5017
|
-
resolve({
|
|
5018
|
-
stdout: stdoutRef.value,
|
|
5019
|
-
stderr: stderrRef.value,
|
|
5020
|
-
code: null,
|
|
5021
|
-
signal: null,
|
|
5022
|
-
timedOut
|
|
5023
|
-
});
|
|
5024
|
-
});
|
|
5025
|
-
child.on("close", (code, signal) => {
|
|
5026
|
-
clearTimeout(timer);
|
|
5027
|
-
if (callId)
|
|
5028
|
-
activeChildren.delete(callId);
|
|
5029
|
-
resolve({ stdout: stdoutRef.value, stderr: stderrRef.value, code, signal, timedOut });
|
|
5030
|
-
});
|
|
5031
|
-
});
|
|
5032
|
-
}
|
|
5033
4965
|
var activeChildren;
|
|
5034
4966
|
var init_runner = __esm(() => {
|
|
5035
4967
|
activeChildren = new Map;
|
|
@@ -5045,6 +4977,16 @@ class ToolExecutor {
|
|
|
5045
4977
|
this.ctx = ctx;
|
|
5046
4978
|
this.pluginManager = pluginManager;
|
|
5047
4979
|
}
|
|
4980
|
+
async executeByName(name, args, ctx) {
|
|
4981
|
+
const prevCtx = this.ctx;
|
|
4982
|
+
if (ctx)
|
|
4983
|
+
this.ctx = ctx;
|
|
4984
|
+
try {
|
|
4985
|
+
return await this.execute({ id: `redirect_${Date.now()}`, name, arguments: args });
|
|
4986
|
+
} finally {
|
|
4987
|
+
this.ctx = prevCtx;
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
5048
4990
|
async execute(call, signal) {
|
|
5049
4991
|
const tool = this.registry.get(call.name);
|
|
5050
4992
|
if (!tool) {
|
|
@@ -6036,9 +5978,9 @@ function formatLine(line, maxNumWidth) {
|
|
|
6036
5978
|
const num = line.type === "remove" ? line.oldNum : line.newNum;
|
|
6037
5979
|
const numStr = num !== null ? String(num).padStart(maxNumWidth) : " ".repeat(maxNumWidth);
|
|
6038
5980
|
if (line.type === "remove") {
|
|
6039
|
-
return
|
|
5981
|
+
return `${numStr} ${pc.red("-")} ${line.content}`;
|
|
6040
5982
|
} else if (line.type === "add") {
|
|
6041
|
-
return
|
|
5983
|
+
return `${numStr} ${pc.green("+")} ${line.content}`;
|
|
6042
5984
|
} else if (line.content === "...") {
|
|
6043
5985
|
return pc.dim(` ${" ".repeat(maxNumWidth)}...`);
|
|
6044
5986
|
} else {
|
|
@@ -6077,7 +6019,7 @@ function generateNewFileDiff(content) {
|
|
|
6077
6019
|
const diffLines = [];
|
|
6078
6020
|
for (let i = 0;i < lines.length; i++) {
|
|
6079
6021
|
const numStr = String(i + 1).padStart(maxNumWidth);
|
|
6080
|
-
diffLines.push(
|
|
6022
|
+
diffLines.push(`${numStr} ${pc.green("+")} ${lines[i]}`);
|
|
6081
6023
|
}
|
|
6082
6024
|
if (diffLines.length > MAX_DIFF_LINES) {
|
|
6083
6025
|
const truncated = diffLines.slice(0, MAX_DIFF_LINES);
|
|
@@ -6095,7 +6037,7 @@ function generateDeleteDiff(content) {
|
|
|
6095
6037
|
const diffLines = [];
|
|
6096
6038
|
for (let i = 0;i < lines.length; i++) {
|
|
6097
6039
|
const numStr = String(i + 1).padStart(maxNumWidth);
|
|
6098
|
-
diffLines.push(
|
|
6040
|
+
diffLines.push(`${numStr} ${pc.red("-")} ${lines[i]}`);
|
|
6099
6041
|
}
|
|
6100
6042
|
if (diffLines.length > MAX_DIFF_LINES) {
|
|
6101
6043
|
const truncated = diffLines.slice(0, MAX_DIFF_LINES);
|
|
@@ -6810,14 +6752,14 @@ var init_command_validator = __esm(() => {
|
|
|
6810
6752
|
});
|
|
6811
6753
|
|
|
6812
6754
|
// src/modules/processes/registry.ts
|
|
6813
|
-
import { spawn
|
|
6814
|
-
import { platform
|
|
6815
|
-
function
|
|
6755
|
+
import { spawn } from "child_process";
|
|
6756
|
+
import { platform } from "os";
|
|
6757
|
+
function killTree(child) {
|
|
6816
6758
|
const pid = child.pid;
|
|
6817
6759
|
if (!pid)
|
|
6818
6760
|
return;
|
|
6819
|
-
if (
|
|
6820
|
-
|
|
6761
|
+
if (platform() === "win32") {
|
|
6762
|
+
spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
6821
6763
|
windowsHide: true,
|
|
6822
6764
|
stdio: "ignore"
|
|
6823
6765
|
});
|
|
@@ -6835,6 +6777,7 @@ function killTree2(child) {
|
|
|
6835
6777
|
class ProcessRegistry {
|
|
6836
6778
|
procs = new Map;
|
|
6837
6779
|
children = new Map;
|
|
6780
|
+
exitWaiters = new Map;
|
|
6838
6781
|
start(command, cwd, sessionId) {
|
|
6839
6782
|
const id = `proc_${Date.now()}_${++seq}`;
|
|
6840
6783
|
const entry = {
|
|
@@ -6850,46 +6793,76 @@ class ProcessRegistry {
|
|
|
6850
6793
|
};
|
|
6851
6794
|
this.trimOldEntries();
|
|
6852
6795
|
this.procs.set(id, entry);
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6796
|
+
let child;
|
|
6797
|
+
try {
|
|
6798
|
+
child = spawn(command, {
|
|
6799
|
+
cwd,
|
|
6800
|
+
shell: true,
|
|
6801
|
+
windowsHide: true,
|
|
6802
|
+
detached: platform() !== "win32",
|
|
6803
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6804
|
+
});
|
|
6805
|
+
} catch (e) {
|
|
6806
|
+
entry.status = "killed";
|
|
6807
|
+
entry.spawnError = e?.message || String(e);
|
|
6808
|
+
entry.log.push(`[process error] ${entry.spawnError}`);
|
|
6809
|
+
return entry;
|
|
6810
|
+
}
|
|
6860
6811
|
this.children.set(id, child);
|
|
6861
6812
|
entry.pid = child.pid ?? 0;
|
|
6862
|
-
|
|
6813
|
+
const decoder = platform() === "win32" ? new TextDecoder("ibm866") : null;
|
|
6814
|
+
let buf = "";
|
|
6815
|
+
const pushLine = (line) => {
|
|
6816
|
+
if (entry.log.length >= MAX_LOG_LINES) {
|
|
6817
|
+
entry.log.shift();
|
|
6818
|
+
}
|
|
6819
|
+
entry.log.push(line);
|
|
6820
|
+
};
|
|
6863
6821
|
const append = (chunk) => {
|
|
6864
|
-
const
|
|
6865
|
-
|
|
6866
|
-
|
|
6822
|
+
const decoded = decoder ? decoder.decode(chunk, { stream: true }) : chunk.toString();
|
|
6823
|
+
buf += decoded;
|
|
6824
|
+
const lines = buf.split(/\r?\n/);
|
|
6825
|
+
buf = lines.pop() ?? "";
|
|
6867
6826
|
for (const line of lines) {
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6827
|
+
pushLine(line);
|
|
6828
|
+
}
|
|
6829
|
+
};
|
|
6830
|
+
const flush = () => {
|
|
6831
|
+
if (decoder) {
|
|
6832
|
+
buf += decoder.decode();
|
|
6833
|
+
}
|
|
6834
|
+
if (buf) {
|
|
6835
|
+
pushLine(buf);
|
|
6836
|
+
buf = "";
|
|
6872
6837
|
}
|
|
6873
6838
|
};
|
|
6839
|
+
const resolveExit = () => {
|
|
6840
|
+
this.exitWaiters.get(id)?.resolve();
|
|
6841
|
+
};
|
|
6874
6842
|
child.stdout?.on("data", append);
|
|
6875
6843
|
child.stderr?.on("data", append);
|
|
6876
6844
|
child.on("error", (err) => {
|
|
6877
6845
|
if (entry.status === "running") {
|
|
6878
6846
|
entry.status = "killed";
|
|
6879
6847
|
}
|
|
6848
|
+
entry.spawnError = err.message;
|
|
6880
6849
|
append(Buffer.from(`[process error] ${err.message}`));
|
|
6850
|
+
resolveExit();
|
|
6881
6851
|
});
|
|
6882
6852
|
child.on("close", (code) => {
|
|
6853
|
+
flush();
|
|
6883
6854
|
if (entry.status === "running") {
|
|
6884
6855
|
entry.status = "exited";
|
|
6885
6856
|
}
|
|
6886
6857
|
entry.exitCode = code;
|
|
6887
6858
|
this.children.delete(id);
|
|
6888
|
-
|
|
6889
|
-
entry.log.push(partial);
|
|
6890
|
-
partial = "";
|
|
6891
|
-
}
|
|
6859
|
+
resolveExit();
|
|
6892
6860
|
});
|
|
6861
|
+
let resolve10;
|
|
6862
|
+
const promise = new Promise((r) => {
|
|
6863
|
+
resolve10 = r;
|
|
6864
|
+
});
|
|
6865
|
+
this.exitWaiters.set(id, { promise, resolve: resolve10 });
|
|
6893
6866
|
return entry;
|
|
6894
6867
|
}
|
|
6895
6868
|
list(sessionId) {
|
|
@@ -6917,7 +6890,7 @@ class ProcessRegistry {
|
|
|
6917
6890
|
entry.status = "killed";
|
|
6918
6891
|
const child = this.children.get(id);
|
|
6919
6892
|
if (child) {
|
|
6920
|
-
|
|
6893
|
+
killTree(child);
|
|
6921
6894
|
}
|
|
6922
6895
|
}
|
|
6923
6896
|
return true;
|
|
@@ -6929,71 +6902,61 @@ class ProcessRegistry {
|
|
|
6929
6902
|
}
|
|
6930
6903
|
return targets.length;
|
|
6931
6904
|
}
|
|
6905
|
+
waitForExit(id, timeoutMs) {
|
|
6906
|
+
const entry = this.procs.get(id);
|
|
6907
|
+
const waiter = this.exitWaiters.get(id);
|
|
6908
|
+
if (!entry || entry.status !== "running" || !waiter) {
|
|
6909
|
+
return Promise.resolve(true);
|
|
6910
|
+
}
|
|
6911
|
+
return new Promise((resolve10) => {
|
|
6912
|
+
const timer = setTimeout(() => resolve10(false), timeoutMs);
|
|
6913
|
+
waiter.promise.then(() => {
|
|
6914
|
+
clearTimeout(timer);
|
|
6915
|
+
resolve10(true);
|
|
6916
|
+
});
|
|
6917
|
+
});
|
|
6918
|
+
}
|
|
6919
|
+
remove(id) {
|
|
6920
|
+
const entry = this.procs.get(id);
|
|
6921
|
+
if (!entry)
|
|
6922
|
+
return false;
|
|
6923
|
+
if (entry.status === "running") {
|
|
6924
|
+
const child = this.children.get(id);
|
|
6925
|
+
if (child) {
|
|
6926
|
+
killTree(child);
|
|
6927
|
+
}
|
|
6928
|
+
entry.status = "killed";
|
|
6929
|
+
}
|
|
6930
|
+
this.procs.delete(id);
|
|
6931
|
+
this.children.delete(id);
|
|
6932
|
+
this.exitWaiters.delete(id);
|
|
6933
|
+
return true;
|
|
6934
|
+
}
|
|
6932
6935
|
trimOldEntries() {
|
|
6933
6936
|
if (this.procs.size < MAX_KEPT_PROCESSES)
|
|
6934
6937
|
return;
|
|
6935
6938
|
const sorted = Array.from(this.procs.values()).sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
6936
6939
|
const toRemove = sorted.slice(0, sorted.length - MAX_KEPT_PROCESSES + 1);
|
|
6937
6940
|
for (const entry of toRemove) {
|
|
6938
|
-
|
|
6939
|
-
const child = this.children.get(entry.id);
|
|
6940
|
-
if (child) {
|
|
6941
|
-
killTree2(child);
|
|
6942
|
-
}
|
|
6943
|
-
}
|
|
6944
|
-
this.procs.delete(entry.id);
|
|
6945
|
-
this.children.delete(entry.id);
|
|
6941
|
+
this.remove(entry.id);
|
|
6946
6942
|
}
|
|
6947
6943
|
}
|
|
6948
6944
|
}
|
|
6949
|
-
var MAX_LOG_LINES =
|
|
6945
|
+
var MAX_LOG_LINES = 2000, MAX_KEPT_PROCESSES = 20, seq = 0, processRegistry;
|
|
6950
6946
|
var init_registry = __esm(() => {
|
|
6951
6947
|
processRegistry = new ProcessRegistry;
|
|
6952
6948
|
});
|
|
6953
6949
|
|
|
6954
|
-
// src/modules/processes/detect.ts
|
|
6955
|
-
function isLongRunningCommand(command) {
|
|
6956
|
-
return LONG_RUNNING_PATTERNS.some((pattern) => pattern.test(command));
|
|
6957
|
-
}
|
|
6958
|
-
var LONG_RUNNING_PATTERNS;
|
|
6959
|
-
var init_detect2 = __esm(() => {
|
|
6960
|
-
LONG_RUNNING_PATTERNS = [
|
|
6961
|
-
/\b(npm|pnpm|yarn|bun|npx)\s+(run\s+)?(dev|start|serve|preview|watch|server)\b/i,
|
|
6962
|
-
/\b(deno|node)\s+.*\b(run\s+)?(dev|serve|watch|server)\b/i,
|
|
6963
|
-
/(^|\s)--watch\b/i,
|
|
6964
|
-
/(^|\s)-w\b/i,
|
|
6965
|
-
/\bnodemon\b/i,
|
|
6966
|
-
/\btsx watch\b/i,
|
|
6967
|
-
/\b(ts-node|tsc)\s+.*--watch\b/i,
|
|
6968
|
-
/\bvite(?!\s+(build|create))\b/i,
|
|
6969
|
-
/\bwebpack(-dev-server|\s+serve)\b/i,
|
|
6970
|
-
/\bastro dev\b/i,
|
|
6971
|
-
/\bnext (dev|start)\b/i,
|
|
6972
|
-
/\bnuxt (dev|start)\b/i,
|
|
6973
|
-
/\bsvelte-kit (dev|preview)\b/i,
|
|
6974
|
-
/\bgatsby develop\b/i,
|
|
6975
|
-
/\bdocker(-compose)?\s+.*\bup\b/i,
|
|
6976
|
-
/\buvicorn\b|\bgunicorn\b/i,
|
|
6977
|
-
/\bpython\s+-m\s+http\.server\b/i,
|
|
6978
|
-
/\bflask run\b/i,
|
|
6979
|
-
/\bphp artisan serve\b/i,
|
|
6980
|
-
/\brails (server|s)\b/i,
|
|
6981
|
-
/\bvitest watch\b/i,
|
|
6982
|
-
/\bjest --watch\b/i
|
|
6983
|
-
];
|
|
6984
|
-
});
|
|
6985
|
-
|
|
6986
6950
|
// src/modules/processes/index.ts
|
|
6987
6951
|
var init_processes = __esm(() => {
|
|
6988
6952
|
init_runner();
|
|
6989
6953
|
init_registry();
|
|
6990
|
-
init_detect2();
|
|
6991
6954
|
});
|
|
6992
6955
|
|
|
6993
6956
|
// src/tools/bash.ts
|
|
6994
|
-
import { platform as
|
|
6957
|
+
import { platform as platform2 } from "os";
|
|
6995
6958
|
function adaptCommandForWindows(command) {
|
|
6996
|
-
if (
|
|
6959
|
+
if (platform2() !== "win32")
|
|
6997
6960
|
return command;
|
|
6998
6961
|
const trimmed = command.trim();
|
|
6999
6962
|
if (trimmed.startsWith("mkdir -p ")) {
|
|
@@ -7002,9 +6965,45 @@ function adaptCommandForWindows(command) {
|
|
|
7002
6965
|
if (trimmed === "mkdir -p" || trimmed.startsWith("mkdir -p ")) {
|
|
7003
6966
|
return trimmed.replace(/mkdir -p/g, "mkdir");
|
|
7004
6967
|
}
|
|
6968
|
+
const firstWord = trimmed.split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
6969
|
+
const translated = firstWord ? UNIX_TO_WIN_TRANSLATE[firstWord] : undefined;
|
|
6970
|
+
if (translated && !trimmed.includes("|") && !trimmed.includes(">") && !trimmed.includes("&&") && !trimmed.includes(";")) {
|
|
6971
|
+
return trimmed.replace(firstWord, translated);
|
|
6972
|
+
}
|
|
7005
6973
|
return command;
|
|
7006
6974
|
}
|
|
7007
|
-
|
|
6975
|
+
function detectToolCallInBash(command) {
|
|
6976
|
+
const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
|
|
6977
|
+
if (!match)
|
|
6978
|
+
return null;
|
|
6979
|
+
const tool = match[1];
|
|
6980
|
+
const rest = match[2].trim();
|
|
6981
|
+
if (!/^[a-z][a-z0-9_]+$/.test(tool))
|
|
6982
|
+
return null;
|
|
6983
|
+
if (!rest.includes("=") && !rest.startsWith("{"))
|
|
6984
|
+
return null;
|
|
6985
|
+
return { tool, args: rest };
|
|
6986
|
+
}
|
|
6987
|
+
function parseToolArgs(raw) {
|
|
6988
|
+
const trimmed = raw.trim();
|
|
6989
|
+
if (trimmed.startsWith("{")) {
|
|
6990
|
+
try {
|
|
6991
|
+
return JSON.parse(trimmed);
|
|
6992
|
+
} catch {}
|
|
6993
|
+
}
|
|
6994
|
+
const args = {};
|
|
6995
|
+
const tokens = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
|
|
6996
|
+
for (const token of tokens) {
|
|
6997
|
+
const eq = token.indexOf("=");
|
|
6998
|
+
if (eq > 0) {
|
|
6999
|
+
const key = token.slice(0, eq);
|
|
7000
|
+
const value = token.slice(eq + 1);
|
|
7001
|
+
args[key] = value.replace(/^["']|["']$/g, "");
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
return args;
|
|
7005
|
+
}
|
|
7006
|
+
var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, bashGraceMs, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, bashTool;
|
|
7008
7007
|
var init_bash = __esm(() => {
|
|
7009
7008
|
init_command_validator();
|
|
7010
7009
|
init_audit_log();
|
|
@@ -7012,6 +7011,7 @@ var init_bash = __esm(() => {
|
|
|
7012
7011
|
init_security();
|
|
7013
7012
|
init_processes();
|
|
7014
7013
|
init_i18n();
|
|
7014
|
+
bashGraceMs = BASH_GRACE_MS;
|
|
7015
7015
|
UNIX_TO_WIN_HINTS = {
|
|
7016
7016
|
ls: 'Use "dir" or the list_dir tool instead.',
|
|
7017
7017
|
pwd: 'Use "echo %cd%" or the file_info tool instead.',
|
|
@@ -7028,23 +7028,40 @@ var init_bash = __esm(() => {
|
|
|
7028
7028
|
wc: "Use the read_file tool instead.",
|
|
7029
7029
|
diff: "Use the diff tool instead.",
|
|
7030
7030
|
which: 'Use "where" instead.',
|
|
7031
|
-
echo: "echo works on Windows, but avoid pipes (|)."
|
|
7031
|
+
echo: "echo works on Windows, but avoid pipes (|).",
|
|
7032
|
+
"Get-Content": 'The shell is cmd.exe, not PowerShell. Use "type" or the read_file tool instead.',
|
|
7033
|
+
"Select-Object": "The shell is cmd.exe, not PowerShell. Use the read_file tool with offset/limit instead."
|
|
7034
|
+
};
|
|
7035
|
+
UNIX_TO_WIN_TRANSLATE = {
|
|
7036
|
+
ls: "dir",
|
|
7037
|
+
pwd: "echo %cd%",
|
|
7038
|
+
cat: "type",
|
|
7039
|
+
wc: 'find /c /v ""'
|
|
7032
7040
|
};
|
|
7033
7041
|
bashTool = {
|
|
7034
7042
|
name: "bash",
|
|
7035
|
-
description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations.
|
|
7043
|
+
description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).",
|
|
7036
7044
|
tags: ["shell", "code"],
|
|
7037
7045
|
parameters: {
|
|
7038
7046
|
type: "object",
|
|
7039
7047
|
properties: {
|
|
7040
7048
|
command: { type: "string", description: "Shell command to execute" },
|
|
7041
7049
|
workdir: { type: "string", description: "Working directory (default: baseDir)" },
|
|
7042
|
-
background: { type: "boolean", description: "
|
|
7050
|
+
background: { type: "boolean", description: "Return a process id immediately without waiting (default: commands still running after a few seconds are auto-promoted to the background)" }
|
|
7043
7051
|
},
|
|
7044
7052
|
required: ["command"]
|
|
7045
7053
|
},
|
|
7046
7054
|
handler: async (ctx, args) => {
|
|
7047
7055
|
const originalCommand = String(args.command);
|
|
7056
|
+
const toolCall = detectToolCallInBash(originalCommand);
|
|
7057
|
+
if (toolCall && toolCall.tool !== "bash" && ctx.toolExecutor) {
|
|
7058
|
+
const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
|
|
7059
|
+
return {
|
|
7060
|
+
success: redirected.success,
|
|
7061
|
+
output: `[redirected to tool "${toolCall.tool}"]
|
|
7062
|
+
${redirected.output}`
|
|
7063
|
+
};
|
|
7064
|
+
}
|
|
7048
7065
|
const command = adaptCommandForWindows(originalCommand);
|
|
7049
7066
|
const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
|
|
7050
7067
|
const appConfig = ctx.config || {};
|
|
@@ -7062,68 +7079,67 @@ Hint: Use the "workdir" parameter to run commands in a specific directory instea
|
|
|
7062
7079
|
if (securityConfig?.logCommands) {
|
|
7063
7080
|
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, `Working directory: ${workdir}`);
|
|
7064
7081
|
}
|
|
7065
|
-
const
|
|
7066
|
-
if (
|
|
7067
|
-
|
|
7068
|
-
if (securityConfig?.logCommands) {
|
|
7069
|
-
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
|
|
7070
|
-
}
|
|
7071
|
-
const explicit = args.background === true;
|
|
7072
|
-
return {
|
|
7073
|
-
success: true,
|
|
7074
|
-
output: `${t("proc.started", {
|
|
7075
|
-
id: entry.id,
|
|
7076
|
-
pid: entry.pid,
|
|
7077
|
-
command
|
|
7078
|
-
})}${explicit ? "" : `
|
|
7079
|
-
${t("proc.detected_hint")}`}
|
|
7080
|
-
${t("proc.manage_hint", {
|
|
7081
|
-
id: entry.id
|
|
7082
|
-
})}`
|
|
7083
|
-
};
|
|
7082
|
+
const entry = processRegistry.start(command, workdir, ctx.sessionId);
|
|
7083
|
+
if (ctx.activeCallId) {
|
|
7084
|
+
registerKillable(ctx.activeCallId, () => processRegistry.kill(entry.id));
|
|
7084
7085
|
}
|
|
7085
7086
|
try {
|
|
7086
|
-
const
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7087
|
+
const settleMs = args.background === true ? SPAWN_SETTLE_MS : bashGraceMs;
|
|
7088
|
+
const exited = await processRegistry.waitForExit(entry.id, settleMs);
|
|
7089
|
+
if (exited) {
|
|
7090
|
+
if (entry.spawnError) {
|
|
7091
|
+
return {
|
|
7092
|
+
success: false,
|
|
7093
|
+
output: `[process error] ${entry.spawnError}
|
|
7094
|
+
Hint: check the "workdir" path exists and the command is valid for this OS.`
|
|
7095
|
+
};
|
|
7096
|
+
}
|
|
7097
|
+
const code = entry.exitCode;
|
|
7098
|
+
let output2 = entry.log.join(`
|
|
7093
7099
|
`);
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
`
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
const firstWord = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
7104
|
-
const hint = firstWord ? UNIX_TO_WIN_HINTS[firstWord] : undefined;
|
|
7105
|
-
if (hint) {
|
|
7106
|
-
output = `${output}
|
|
7100
|
+
processRegistry.remove(entry.id);
|
|
7101
|
+
if (!output2 && code !== 0) {
|
|
7102
|
+
output2 = `(exit code ${code})`;
|
|
7103
|
+
}
|
|
7104
|
+
if (platform2() === "win32" && code !== 0) {
|
|
7105
|
+
const firstWord = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
7106
|
+
const hint = firstWord ? UNIX_TO_WIN_HINTS[firstWord] : undefined;
|
|
7107
|
+
if (hint) {
|
|
7108
|
+
output2 = `${output2}
|
|
7107
7109
|
|
|
7108
7110
|
Hint: "${firstWord}" may not work on Windows. ${hint}`;
|
|
7111
|
+
}
|
|
7109
7112
|
}
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
const lines = output.split(`
|
|
7113
|
+
if (securityConfig?.logCommands) {
|
|
7114
|
+
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output2.length}`);
|
|
7115
|
+
}
|
|
7116
|
+
const lines = output2.split(`
|
|
7115
7117
|
`);
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
+
if (lines.length > MAX_PREVIEW_LINES) {
|
|
7119
|
+
output2 = lines.slice(0, MAX_PREVIEW_LINES).join(`
|
|
7118
7120
|
`) + `
|
|
7119
7121
|
... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
7122
|
+
}
|
|
7123
|
+
return { success: code === 0, output: output2 };
|
|
7120
7124
|
}
|
|
7121
|
-
|
|
7122
|
-
|
|
7125
|
+
const explicit = args.background === true;
|
|
7126
|
+
const output = `${t("proc.started", {
|
|
7127
|
+
id: entry.id,
|
|
7128
|
+
pid: entry.pid,
|
|
7129
|
+
command
|
|
7130
|
+
})}${explicit ? "" : `
|
|
7131
|
+
${t("proc.promoted_hint", { ms: settleMs })}`}
|
|
7132
|
+
${t("proc.manage_hint", {
|
|
7133
|
+
id: entry.id
|
|
7134
|
+
})}`;
|
|
7123
7135
|
if (securityConfig?.logCommands) {
|
|
7124
|
-
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command),
|
|
7136
|
+
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
|
|
7137
|
+
}
|
|
7138
|
+
return { success: true, output };
|
|
7139
|
+
} finally {
|
|
7140
|
+
if (ctx.activeCallId) {
|
|
7141
|
+
unregisterKillable(ctx.activeCallId);
|
|
7125
7142
|
}
|
|
7126
|
-
return { success: false, output: e.message || String(e) };
|
|
7127
7143
|
}
|
|
7128
7144
|
}
|
|
7129
7145
|
};
|
|
@@ -7423,6 +7439,15 @@ class SessionLogger {
|
|
|
7423
7439
|
iteration
|
|
7424
7440
|
});
|
|
7425
7441
|
}
|
|
7442
|
+
logPlan(event, detail, iteration) {
|
|
7443
|
+
this.session?.appendLog({
|
|
7444
|
+
ts: new Date().toISOString(),
|
|
7445
|
+
type: "plan",
|
|
7446
|
+
content: detail,
|
|
7447
|
+
tool: event,
|
|
7448
|
+
...iteration !== undefined ? { iteration } : {}
|
|
7449
|
+
});
|
|
7450
|
+
}
|
|
7426
7451
|
}
|
|
7427
7452
|
|
|
7428
7453
|
// node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js
|
|
@@ -8548,66 +8573,382 @@ function filterToolsByTags(tools, toolTags) {
|
|
|
8548
8573
|
});
|
|
8549
8574
|
}
|
|
8550
8575
|
|
|
8551
|
-
// src/modules/execution/
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8576
|
+
// src/modules/execution/stuck-detector.ts
|
|
8577
|
+
class StuckDetector {
|
|
8578
|
+
threshold;
|
|
8579
|
+
errorThreshold;
|
|
8580
|
+
iterationsOnCurrentStep = 0;
|
|
8581
|
+
currentStepId = null;
|
|
8582
|
+
toolErrors = new Map;
|
|
8583
|
+
currentStepDescription = "";
|
|
8584
|
+
recentToolCalls = [];
|
|
8585
|
+
maxRecentCalls = 10;
|
|
8586
|
+
repetitionThreshold = 3;
|
|
8587
|
+
consecutiveFailures = 0;
|
|
8588
|
+
lastFailedTool = "";
|
|
8589
|
+
lastErrorOutput = "";
|
|
8590
|
+
escalationCount = 0;
|
|
8591
|
+
escalationThreshold = 3;
|
|
8592
|
+
fileRewriteCount = new Map;
|
|
8593
|
+
fileRewriteThreshold = 3;
|
|
8594
|
+
constructor(threshold = 8, errorThreshold = 3) {
|
|
8595
|
+
this.threshold = threshold;
|
|
8596
|
+
this.errorThreshold = errorThreshold;
|
|
8597
|
+
}
|
|
8598
|
+
recordIteration(stepId) {
|
|
8599
|
+
if (stepId === this.currentStepId) {
|
|
8600
|
+
this.iterationsOnCurrentStep++;
|
|
8601
|
+
} else {
|
|
8602
|
+
this.resetStepState(stepId);
|
|
8573
8603
|
}
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8604
|
+
}
|
|
8605
|
+
recordToolCall(name, args) {
|
|
8606
|
+
const argsKey = JSON.stringify(args);
|
|
8607
|
+
this.recentToolCalls.push({ name, argsKey });
|
|
8608
|
+
if (this.recentToolCalls.length > this.maxRecentCalls) {
|
|
8609
|
+
this.recentToolCalls.shift();
|
|
8577
8610
|
}
|
|
8578
8611
|
}
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
return {
|
|
8586
|
-
subtaskId: subtask.id,
|
|
8587
|
-
success: false,
|
|
8588
|
-
summary: `Unknown expert_tag: ${subtask.expert_tag}`,
|
|
8589
|
-
result: "",
|
|
8590
|
-
error: `No expert config found for tag "${subtask.expert_tag}"`,
|
|
8591
|
-
durationMs: Date.now() - startTime
|
|
8592
|
-
};
|
|
8612
|
+
recordToolError(toolName, output) {
|
|
8613
|
+
this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
|
|
8614
|
+
this.consecutiveFailures++;
|
|
8615
|
+
this.lastFailedTool = toolName;
|
|
8616
|
+
if (output)
|
|
8617
|
+
this.lastErrorOutput = output;
|
|
8593
8618
|
}
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
return {
|
|
8597
|
-
subtaskId: subtask.id,
|
|
8598
|
-
success: false,
|
|
8599
|
-
summary: "Subagent tool not registered",
|
|
8600
|
-
result: "",
|
|
8601
|
-
error: "Subagent tool not found in registry",
|
|
8602
|
-
durationMs: Date.now() - startTime
|
|
8603
|
-
};
|
|
8619
|
+
getLastErrorOutput() {
|
|
8620
|
+
return this.lastErrorOutput;
|
|
8604
8621
|
}
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8622
|
+
getLastFailedTool() {
|
|
8623
|
+
return this.lastFailedTool;
|
|
8624
|
+
}
|
|
8625
|
+
getIterationsOnCurrentStep() {
|
|
8626
|
+
return this.iterationsOnCurrentStep;
|
|
8627
|
+
}
|
|
8628
|
+
recordToolSuccess() {
|
|
8629
|
+
this.consecutiveFailures = 0;
|
|
8630
|
+
this.lastFailedTool = "";
|
|
8631
|
+
this.escalationCount = 0;
|
|
8632
|
+
}
|
|
8633
|
+
setCurrentStep(stepId, description) {
|
|
8634
|
+
if (stepId !== this.currentStepId) {
|
|
8635
|
+
this.resetStepState(stepId);
|
|
8636
|
+
}
|
|
8637
|
+
this.currentStepDescription = description;
|
|
8638
|
+
}
|
|
8639
|
+
isStuck() {
|
|
8640
|
+
return this.iterationsOnCurrentStep > this.threshold;
|
|
8641
|
+
}
|
|
8642
|
+
hasRepetitiveErrors(toolName) {
|
|
8643
|
+
if (toolName) {
|
|
8644
|
+
return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
|
|
8645
|
+
}
|
|
8646
|
+
return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
|
|
8647
|
+
}
|
|
8648
|
+
hasRepetitiveToolCalls() {
|
|
8649
|
+
if (this.recentToolCalls.length < this.repetitionThreshold)
|
|
8650
|
+
return false;
|
|
8651
|
+
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
8652
|
+
let count = 1;
|
|
8653
|
+
for (let i = this.recentToolCalls.length - 2;i >= 0; i--) {
|
|
8654
|
+
if (this.recentToolCalls[i].name === last.name && this.recentToolCalls[i].argsKey === last.argsKey) {
|
|
8655
|
+
count++;
|
|
8656
|
+
} else {
|
|
8657
|
+
break;
|
|
8658
|
+
}
|
|
8659
|
+
}
|
|
8660
|
+
return count >= this.repetitionThreshold;
|
|
8661
|
+
}
|
|
8662
|
+
hasConsecutiveFailures() {
|
|
8663
|
+
return this.consecutiveFailures >= this.errorThreshold;
|
|
8664
|
+
}
|
|
8665
|
+
getConsecutiveFailuresCount() {
|
|
8666
|
+
return this.consecutiveFailures;
|
|
8667
|
+
}
|
|
8668
|
+
recordEscalation() {
|
|
8669
|
+
this.escalationCount++;
|
|
8670
|
+
}
|
|
8671
|
+
shouldEscalate() {
|
|
8672
|
+
return this.escalationCount >= this.escalationThreshold;
|
|
8673
|
+
}
|
|
8674
|
+
resetEscalation() {
|
|
8675
|
+
this.escalationCount = 0;
|
|
8676
|
+
}
|
|
8677
|
+
getEscalationCount() {
|
|
8678
|
+
return this.escalationCount;
|
|
8679
|
+
}
|
|
8680
|
+
getHints() {
|
|
8681
|
+
const hints = [];
|
|
8682
|
+
const desc = this.currentStepDescription.toLowerCase();
|
|
8683
|
+
if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
|
|
8684
|
+
hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
|
|
8685
|
+
}
|
|
8686
|
+
if (desc.includes("test") || desc.includes("spec")) {
|
|
8687
|
+
hints.push("Make sure the source files exist and have real code before running tests.");
|
|
8688
|
+
}
|
|
8689
|
+
if (desc.includes("build") || desc.includes("compile")) {
|
|
8690
|
+
hints.push("Check that all dependencies are installed and source files are not empty.");
|
|
8691
|
+
}
|
|
8692
|
+
if (desc.includes("deploy") || desc.includes("publish")) {
|
|
8693
|
+
hints.push("Verify credentials and network access before deploying.");
|
|
8694
|
+
}
|
|
8695
|
+
if (this.hasRepetitiveToolCalls()) {
|
|
8696
|
+
hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
|
|
8697
|
+
}
|
|
8698
|
+
if (this.hasConsecutiveFailures()) {
|
|
8699
|
+
hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
|
|
8700
|
+
}
|
|
8701
|
+
return hints;
|
|
8702
|
+
}
|
|
8703
|
+
getActionableHints() {
|
|
8704
|
+
const hints = [];
|
|
8705
|
+
const error = this.lastErrorOutput;
|
|
8706
|
+
if (!error)
|
|
8707
|
+
return hints;
|
|
8708
|
+
if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
|
|
8709
|
+
if (/node_modules/.test(error)) {
|
|
8710
|
+
hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
|
|
8714
|
+
hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
|
|
8715
|
+
}
|
|
8716
|
+
if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
|
|
8717
|
+
hints.push("TypeScript type mismatch. Check the type signature of the function/API you are using. If a package lacks type declarations, install @types/<package> or use skipLibCheck.");
|
|
8718
|
+
}
|
|
8719
|
+
if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
|
|
8720
|
+
hints.push("Module not found. Run npm install or check if the import path is correct.");
|
|
8721
|
+
}
|
|
8722
|
+
if (/EACCES|EPERM|permission denied/i.test(error)) {
|
|
8723
|
+
hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
|
|
8724
|
+
}
|
|
8725
|
+
if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
|
|
8726
|
+
hints.push("Network error. Check if the server is running and accessible.");
|
|
8727
|
+
}
|
|
8728
|
+
if (/is not recognized|command not found|not found in path/i.test(error)) {
|
|
8729
|
+
hints.push("Command not found. Check if the tool is installed and in PATH.");
|
|
8730
|
+
}
|
|
8731
|
+
if (this.hasExcessiveRewrites()) {
|
|
8732
|
+
const file = this.getExcessiveRewriteFile();
|
|
8733
|
+
hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
|
|
8734
|
+
}
|
|
8735
|
+
return hints;
|
|
8736
|
+
}
|
|
8737
|
+
getToolAlternative() {
|
|
8738
|
+
const tool = this.lastFailedTool;
|
|
8739
|
+
const error = this.lastErrorOutput;
|
|
8740
|
+
if (!tool)
|
|
8741
|
+
return null;
|
|
8742
|
+
if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
|
|
8743
|
+
const alternatives = {
|
|
8744
|
+
"ts-node": "tsx",
|
|
8745
|
+
jest: "vitest",
|
|
8746
|
+
mocha: "vitest",
|
|
8747
|
+
webpack: "vite",
|
|
8748
|
+
rollup: "vite",
|
|
8749
|
+
parcel: "vite",
|
|
8750
|
+
babel: "tsc",
|
|
8751
|
+
eslint: "biome",
|
|
8752
|
+
prettier: "biome"
|
|
8753
|
+
};
|
|
8754
|
+
for (const [failed, alt] of Object.entries(alternatives)) {
|
|
8755
|
+
if (tool.includes(failed) || error.includes(failed)) {
|
|
8756
|
+
return alt;
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8760
|
+
return null;
|
|
8761
|
+
}
|
|
8762
|
+
recordFileRewrite(filePath) {
|
|
8763
|
+
const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
|
|
8764
|
+
this.fileRewriteCount.set(filePath, count);
|
|
8765
|
+
}
|
|
8766
|
+
getFileRewriteCount(filePath) {
|
|
8767
|
+
return this.fileRewriteCount.get(filePath) || 0;
|
|
8768
|
+
}
|
|
8769
|
+
hasExcessiveRewrites() {
|
|
8770
|
+
return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
|
|
8771
|
+
}
|
|
8772
|
+
getExcessiveRewriteFile() {
|
|
8773
|
+
for (const [file, count] of this.fileRewriteCount) {
|
|
8774
|
+
if (count >= this.fileRewriteThreshold)
|
|
8775
|
+
return file;
|
|
8776
|
+
}
|
|
8777
|
+
return null;
|
|
8778
|
+
}
|
|
8779
|
+
getRepetitiveToolMessage() {
|
|
8780
|
+
if (!this.hasRepetitiveToolCalls())
|
|
8781
|
+
return "";
|
|
8782
|
+
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
8783
|
+
return t("exec.repetitive_tool", {
|
|
8784
|
+
tool: last.name,
|
|
8785
|
+
count: this.repetitionThreshold
|
|
8786
|
+
});
|
|
8787
|
+
}
|
|
8788
|
+
getStuckReason() {
|
|
8789
|
+
if (this.isStuck()) {
|
|
8790
|
+
return t("exec.stuck", {
|
|
8791
|
+
stepId: String(this.currentStepId ?? "?"),
|
|
8792
|
+
description: this.currentStepDescription,
|
|
8793
|
+
iterations: this.iterationsOnCurrentStep
|
|
8794
|
+
});
|
|
8795
|
+
}
|
|
8796
|
+
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
8797
|
+
if (errorTool) {
|
|
8798
|
+
return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
|
|
8799
|
+
}
|
|
8800
|
+
return "";
|
|
8801
|
+
}
|
|
8802
|
+
getRecoveryMessage() {
|
|
8803
|
+
if (this.isStuck()) {
|
|
8804
|
+
return t("exec.stuck_recovery", {
|
|
8805
|
+
iterations: this.iterationsOnCurrentStep,
|
|
8806
|
+
stepId: String(this.currentStepId ?? "?"),
|
|
8807
|
+
description: this.currentStepDescription
|
|
8808
|
+
});
|
|
8809
|
+
}
|
|
8810
|
+
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
8811
|
+
if (errorTool) {
|
|
8812
|
+
return t("exec.tool_errors_recovery", {
|
|
8813
|
+
tool: errorTool[0],
|
|
8814
|
+
count: errorTool[1]
|
|
8815
|
+
});
|
|
8816
|
+
}
|
|
8817
|
+
if (this.hasConsecutiveFailures()) {
|
|
8818
|
+
return t("exec.consecutive_failures_recovery", {
|
|
8819
|
+
count: this.consecutiveFailures
|
|
8820
|
+
});
|
|
8821
|
+
}
|
|
8822
|
+
if (this.hasRepetitiveToolCalls()) {
|
|
8823
|
+
return this.getRepetitiveToolMessage();
|
|
8824
|
+
}
|
|
8825
|
+
return "";
|
|
8826
|
+
}
|
|
8827
|
+
checkOffTrack(toolName, call) {
|
|
8828
|
+
if (!this.currentStepDescription)
|
|
8829
|
+
return null;
|
|
8830
|
+
const toolArgStr = JSON.stringify(call.arguments).toLowerCase();
|
|
8831
|
+
const pathInStep = (this.currentStepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
8832
|
+
const pathInCall = (toolArgStr.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
8833
|
+
const overlaps = pathInCall.some((p) => pathInStep.some((s) => p.includes(s) || s.includes(p)));
|
|
8834
|
+
if (!overlaps && pathInStep.length > 0 && pathInCall.length > 0) {
|
|
8835
|
+
return t("exec.off_track", {
|
|
8836
|
+
stepId: String(this.currentStepId),
|
|
8837
|
+
description: this.currentStepDescription,
|
|
8838
|
+
tool: toolName
|
|
8839
|
+
});
|
|
8840
|
+
}
|
|
8841
|
+
return null;
|
|
8842
|
+
}
|
|
8843
|
+
reset() {
|
|
8844
|
+
this.currentStepId = null;
|
|
8845
|
+
this.iterationsOnCurrentStep = 0;
|
|
8846
|
+
this.toolErrors.clear();
|
|
8847
|
+
this.consecutiveFailures = 0;
|
|
8848
|
+
this.lastFailedTool = "";
|
|
8849
|
+
this.lastErrorOutput = "";
|
|
8850
|
+
this.recentToolCalls = [];
|
|
8851
|
+
this.fileRewriteCount.clear();
|
|
8852
|
+
this.escalationCount = 0;
|
|
8853
|
+
}
|
|
8854
|
+
resetStepState(stepId) {
|
|
8855
|
+
this.currentStepId = stepId;
|
|
8856
|
+
this.iterationsOnCurrentStep = 1;
|
|
8857
|
+
this.toolErrors.clear();
|
|
8858
|
+
this.consecutiveFailures = 0;
|
|
8859
|
+
this.lastFailedTool = "";
|
|
8860
|
+
this.lastErrorOutput = "";
|
|
8861
|
+
this.recentToolCalls = [];
|
|
8862
|
+
this.fileRewriteCount.clear();
|
|
8863
|
+
this.escalationCount = 0;
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8866
|
+
var init_stuck_detector = __esm(() => {
|
|
8867
|
+
init_i18n();
|
|
8868
|
+
});
|
|
8869
|
+
|
|
8870
|
+
// src/modules/skills/error-skill-map.ts
|
|
8871
|
+
function suggestSkill(errorOutput) {
|
|
8872
|
+
for (const { pattern, skill } of ERROR_SKILL_MAP) {
|
|
8873
|
+
if (pattern.test(errorOutput))
|
|
8874
|
+
return skill;
|
|
8875
|
+
}
|
|
8876
|
+
return null;
|
|
8877
|
+
}
|
|
8878
|
+
var ERROR_SKILL_MAP;
|
|
8879
|
+
var init_error_skill_map = __esm(() => {
|
|
8880
|
+
ERROR_SKILL_MAP = [
|
|
8881
|
+
{ pattern: /TS\d{4}:/i, skill: "typescript-expert" },
|
|
8882
|
+
{ pattern: /Cannot find name|No overload matches/i, skill: "typescript-type-expert" },
|
|
8883
|
+
{ pattern: /ts-node|tsx.*error/i, skill: "typescript-expert" },
|
|
8884
|
+
{ pattern: /Module not found|Cannot resolve module/i, skill: "typescript-expert" },
|
|
8885
|
+
{ pattern: /ECONNREFUSED|ETIMEDOUT|fetch failed/i, skill: "devops-expert" },
|
|
8886
|
+
{ pattern: /permission denied|EACCES/i, skill: "linux-server-expert" },
|
|
8887
|
+
{ pattern: /docker|container/i, skill: "docker-expert" },
|
|
8888
|
+
{ pattern: /jest|vitest|test.*fail/i, skill: "vitest-testing-expert" }
|
|
8889
|
+
];
|
|
8890
|
+
});
|
|
8891
|
+
|
|
8892
|
+
// src/modules/execution/moe-executor.ts
|
|
8893
|
+
function isTransientError(error) {
|
|
8894
|
+
return TRANSIENT_ERROR_PATTERNS.some((p) => error.includes(p));
|
|
8895
|
+
}
|
|
8896
|
+
function sleep(ms) {
|
|
8897
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
8898
|
+
}
|
|
8899
|
+
function topologicalSort(subtasks) {
|
|
8900
|
+
const sorted = [];
|
|
8901
|
+
const remaining = new Set(subtasks.map((s) => s.id));
|
|
8902
|
+
const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
|
|
8903
|
+
while (remaining.size > 0) {
|
|
8904
|
+
const ready = [];
|
|
8905
|
+
for (const id of remaining) {
|
|
8906
|
+
const sub = subtaskMap.get(id);
|
|
8907
|
+
const depsSatisfied = (sub.depends_on || []).every((d) => !remaining.has(d));
|
|
8908
|
+
if (depsSatisfied) {
|
|
8909
|
+
ready.push(sub);
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
if (ready.length === 0) {
|
|
8913
|
+
break;
|
|
8914
|
+
}
|
|
8915
|
+
sorted.push(ready);
|
|
8916
|
+
for (const r of ready) {
|
|
8917
|
+
remaining.delete(r.id);
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8920
|
+
return sorted;
|
|
8921
|
+
}
|
|
8922
|
+
async function executeSubtask(subtask, deps, _sharedContext) {
|
|
8923
|
+
const startTime = Date.now();
|
|
8924
|
+
const expertConfig = getExpertConfig(deps.config, subtask.expert_tag);
|
|
8925
|
+
if (!expertConfig) {
|
|
8926
|
+
return {
|
|
8927
|
+
subtaskId: subtask.id,
|
|
8928
|
+
success: false,
|
|
8929
|
+
summary: `Unknown expert_tag: ${subtask.expert_tag}`,
|
|
8930
|
+
result: "",
|
|
8931
|
+
error: `No expert config found for tag "${subtask.expert_tag}"`,
|
|
8932
|
+
durationMs: Date.now() - startTime
|
|
8933
|
+
};
|
|
8934
|
+
}
|
|
8935
|
+
const subagentTool = deps.toolRegistry.get("subagent");
|
|
8936
|
+
if (!subagentTool) {
|
|
8937
|
+
return {
|
|
8938
|
+
subtaskId: subtask.id,
|
|
8939
|
+
success: false,
|
|
8940
|
+
summary: "Subagent tool not registered",
|
|
8941
|
+
result: "",
|
|
8942
|
+
error: "Subagent tool not found in registry",
|
|
8943
|
+
durationMs: Date.now() - startTime
|
|
8944
|
+
};
|
|
8945
|
+
}
|
|
8946
|
+
const allTools = deps.toolRegistry.getAll();
|
|
8947
|
+
const filteredTools = filterToolsByTags(allTools, expertConfig.tool_tags);
|
|
8948
|
+
if (filteredTools.length === 0) {
|
|
8949
|
+
return {
|
|
8950
|
+
subtaskId: subtask.id,
|
|
8951
|
+
success: false,
|
|
8611
8952
|
summary: `No tools available for expert "${subtask.expert_tag}" (tags: ${expertConfig.tool_tags.join(", ")})`,
|
|
8612
8953
|
result: "",
|
|
8613
8954
|
error: "No matching tools",
|
|
@@ -8617,7 +8958,9 @@ async function executeSubtask(subtask, deps, _sharedContext) {
|
|
|
8617
8958
|
const maxAttempts = expertConfig.max_attempts || 3;
|
|
8618
8959
|
let lastError = "";
|
|
8619
8960
|
let lastResult = null;
|
|
8961
|
+
const stuckDetector = new StuckDetector(maxAttempts * 2, maxAttempts);
|
|
8620
8962
|
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
8963
|
+
stuckDetector.recordIteration(1);
|
|
8621
8964
|
try {
|
|
8622
8965
|
const allowedFiles = subtask.allowed_files || [];
|
|
8623
8966
|
const readOnlyFiles = subtask.read_only_files || [];
|
|
@@ -8655,44 +8998,90 @@ ${(subtask.success_criteria || []).map((c) => `- ${c}`).join(`
|
|
|
8655
8998
|
}
|
|
8656
8999
|
lastError = result.output;
|
|
8657
9000
|
lastResult = result;
|
|
9001
|
+
stuckDetector.recordToolError("subagent", result.output);
|
|
9002
|
+
if (attempt < maxAttempts) {
|
|
9003
|
+
const hints2 = stuckDetector.getActionableHints();
|
|
9004
|
+
const recovery = stuckDetector.getRecoveryMessage();
|
|
9005
|
+
if (hints2.length > 0) {
|
|
9006
|
+
deps.logger.debug(`Hint for subtask "${subtask.id}" retry: ${hints2.join("; ")}`);
|
|
9007
|
+
}
|
|
9008
|
+
if (recovery) {
|
|
9009
|
+
deps.logger.debug(`Recovery for subtask "${subtask.id}": ${recovery}`);
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
8658
9012
|
if (isTransientError(lastError) && attempt < maxAttempts) {
|
|
8659
9013
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
|
|
8660
9014
|
deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: transient error`);
|
|
8661
9015
|
await sleep(delay);
|
|
8662
9016
|
continue;
|
|
8663
9017
|
}
|
|
9018
|
+
const hints = stuckDetector.getActionableHints();
|
|
9019
|
+
const suggestedSkill = suggestSkill(lastError);
|
|
9020
|
+
let errorDetail = result.output;
|
|
9021
|
+
if (hints.length > 0) {
|
|
9022
|
+
errorDetail += `
|
|
9023
|
+
|
|
9024
|
+
${t("exec.hints", { hints: hints.map((h) => `- ${h}`).join(`
|
|
9025
|
+
`) })}`;
|
|
9026
|
+
}
|
|
9027
|
+
if (suggestedSkill) {
|
|
9028
|
+
errorDetail += `
|
|
9029
|
+
|
|
9030
|
+
Consider loading the "${suggestedSkill}" skill for expert guidance.`;
|
|
9031
|
+
}
|
|
8664
9032
|
return {
|
|
8665
9033
|
subtaskId: subtask.id,
|
|
8666
9034
|
success: false,
|
|
8667
9035
|
summary: `Failed: ${subtask.id}`,
|
|
8668
9036
|
result: result.output,
|
|
8669
|
-
error:
|
|
9037
|
+
error: errorDetail,
|
|
8670
9038
|
durationMs: Date.now() - startTime
|
|
8671
9039
|
};
|
|
8672
9040
|
} catch (e) {
|
|
8673
9041
|
lastError = e.message;
|
|
9042
|
+
stuckDetector.recordToolError("subagent", e.message);
|
|
8674
9043
|
if (isTransientError(lastError) && attempt < maxAttempts) {
|
|
8675
9044
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
|
|
8676
9045
|
deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: ${lastError}`);
|
|
8677
9046
|
await sleep(delay);
|
|
8678
9047
|
continue;
|
|
8679
9048
|
}
|
|
9049
|
+
const hints = stuckDetector.getActionableHints();
|
|
9050
|
+
const suggestedSkill = suggestSkill(lastError);
|
|
9051
|
+
let errorDetail = e.message;
|
|
9052
|
+
if (hints.length > 0) {
|
|
9053
|
+
errorDetail += `
|
|
9054
|
+
|
|
9055
|
+
${t("exec.hints", { hints: hints.map((h) => `- ${h}`).join(`
|
|
9056
|
+
`) })}`;
|
|
9057
|
+
}
|
|
9058
|
+
if (suggestedSkill) {
|
|
9059
|
+
errorDetail += `
|
|
9060
|
+
|
|
9061
|
+
Consider loading the "${suggestedSkill}" skill for expert guidance.`;
|
|
9062
|
+
}
|
|
8680
9063
|
return {
|
|
8681
9064
|
subtaskId: subtask.id,
|
|
8682
9065
|
success: false,
|
|
8683
9066
|
summary: `Error: ${subtask.id}`,
|
|
8684
9067
|
result: "",
|
|
8685
|
-
error:
|
|
9068
|
+
error: errorDetail,
|
|
8686
9069
|
durationMs: Date.now() - startTime
|
|
8687
9070
|
};
|
|
8688
9071
|
}
|
|
8689
9072
|
}
|
|
9073
|
+
const stuckReason = stuckDetector.getStuckReason();
|
|
9074
|
+
let finalError = lastError;
|
|
9075
|
+
if (stuckReason) {
|
|
9076
|
+
finalError += `
|
|
9077
|
+
${stuckReason}`;
|
|
9078
|
+
}
|
|
8690
9079
|
return {
|
|
8691
9080
|
subtaskId: subtask.id,
|
|
8692
9081
|
success: false,
|
|
8693
9082
|
summary: `Failed after ${maxAttempts} attempts: ${subtask.id}`,
|
|
8694
9083
|
result: "",
|
|
8695
|
-
error:
|
|
9084
|
+
error: finalError,
|
|
8696
9085
|
durationMs: Date.now() - startTime
|
|
8697
9086
|
};
|
|
8698
9087
|
}
|
|
@@ -8751,6 +9140,9 @@ class MoEExecutor {
|
|
|
8751
9140
|
}
|
|
8752
9141
|
var TRANSIENT_ERROR_PATTERNS;
|
|
8753
9142
|
var init_moe_executor = __esm(() => {
|
|
9143
|
+
init_stuck_detector();
|
|
9144
|
+
init_error_skill_map();
|
|
9145
|
+
init_i18n();
|
|
8754
9146
|
TRANSIENT_ERROR_PATTERNS = [
|
|
8755
9147
|
"timeout",
|
|
8756
9148
|
"Timeout",
|
|
@@ -8772,7 +9164,7 @@ var init_moe_executor = __esm(() => {
|
|
|
8772
9164
|
// src/modules/execution/verifier.ts
|
|
8773
9165
|
import { existsSync as existsSync17 } from "fs";
|
|
8774
9166
|
import { resolve as resolve10, extname as extname2 } from "path";
|
|
8775
|
-
import {
|
|
9167
|
+
import { spawn as spawn2 } from "child_process";
|
|
8776
9168
|
|
|
8777
9169
|
class StepVerifier {
|
|
8778
9170
|
baseDir;
|
|
@@ -8789,12 +9181,7 @@ class StepVerifier {
|
|
|
8789
9181
|
}
|
|
8790
9182
|
async runScript(scriptName) {
|
|
8791
9183
|
try {
|
|
8792
|
-
|
|
8793
|
-
cwd: this.baseDir,
|
|
8794
|
-
encoding: "utf-8",
|
|
8795
|
-
timeout: 60000,
|
|
8796
|
-
stdio: "pipe"
|
|
8797
|
-
});
|
|
9184
|
+
await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60000);
|
|
8798
9185
|
return { passed: true, message: t("verify.script_passed", { script: scriptName }) };
|
|
8799
9186
|
} catch (e) {
|
|
8800
9187
|
return { passed: false, message: t("verify.script_failed", { script: scriptName, message: e.message }) };
|
|
@@ -8806,12 +9193,7 @@ class StepVerifier {
|
|
|
8806
9193
|
return { passed: true, message: "No tsconfig.json found — skipping type check" };
|
|
8807
9194
|
}
|
|
8808
9195
|
try {
|
|
8809
|
-
|
|
8810
|
-
cwd: this.baseDir,
|
|
8811
|
-
encoding: "utf-8",
|
|
8812
|
-
timeout: 60000,
|
|
8813
|
-
stdio: "pipe"
|
|
8814
|
-
});
|
|
9196
|
+
await this.runAsync("npx tsc --noEmit", this.baseDir, 60000);
|
|
8815
9197
|
return { passed: true, message: "TypeScript type check passed" };
|
|
8816
9198
|
} catch (e) {
|
|
8817
9199
|
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
@@ -8873,7 +9255,7 @@ class StepVerifier {
|
|
|
8873
9255
|
results.push(result);
|
|
8874
9256
|
if (result.passed) {
|
|
8875
9257
|
const fullPath = resolve10(this.baseDir, filePath);
|
|
8876
|
-
if (!this.validateSyntax(fullPath)) {
|
|
9258
|
+
if (!await this.validateSyntax(fullPath)) {
|
|
8877
9259
|
syntaxValid = false;
|
|
8878
9260
|
results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
|
|
8879
9261
|
}
|
|
@@ -8885,11 +9267,11 @@ class StepVerifier {
|
|
|
8885
9267
|
failed: results.filter((r) => !r.passed)
|
|
8886
9268
|
};
|
|
8887
9269
|
}
|
|
8888
|
-
validateSyntax(filePath) {
|
|
9270
|
+
async validateSyntax(filePath) {
|
|
8889
9271
|
const ext = extname2(filePath);
|
|
8890
9272
|
if (ext === ".ts" || ext === ".tsx") {
|
|
8891
9273
|
try {
|
|
8892
|
-
|
|
9274
|
+
await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 1e4);
|
|
8893
9275
|
return true;
|
|
8894
9276
|
} catch (err) {
|
|
8895
9277
|
if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
|
|
@@ -8904,7 +9286,7 @@ class StepVerifier {
|
|
|
8904
9286
|
}
|
|
8905
9287
|
if (ext === ".js" || ext === ".jsx") {
|
|
8906
9288
|
try {
|
|
8907
|
-
|
|
9289
|
+
await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
|
|
8908
9290
|
return true;
|
|
8909
9291
|
} catch {
|
|
8910
9292
|
return false;
|
|
@@ -8912,6 +9294,44 @@ class StepVerifier {
|
|
|
8912
9294
|
}
|
|
8913
9295
|
return true;
|
|
8914
9296
|
}
|
|
9297
|
+
runAsync(command, cwd, timeoutMs) {
|
|
9298
|
+
return new Promise((resolve11, reject) => {
|
|
9299
|
+
const child = spawn2(command, {
|
|
9300
|
+
cwd,
|
|
9301
|
+
shell: true,
|
|
9302
|
+
windowsHide: true,
|
|
9303
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9304
|
+
});
|
|
9305
|
+
let stdout = "";
|
|
9306
|
+
let stderr = "";
|
|
9307
|
+
child.stdout?.on("data", (d) => {
|
|
9308
|
+
stdout += d.toString();
|
|
9309
|
+
});
|
|
9310
|
+
child.stderr?.on("data", (d) => {
|
|
9311
|
+
stderr += d.toString();
|
|
9312
|
+
});
|
|
9313
|
+
const timer = setTimeout(() => {
|
|
9314
|
+
child.kill();
|
|
9315
|
+
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
9316
|
+
}, timeoutMs);
|
|
9317
|
+
child.on("error", (err) => {
|
|
9318
|
+
clearTimeout(timer);
|
|
9319
|
+
reject(err);
|
|
9320
|
+
});
|
|
9321
|
+
child.on("close", (code) => {
|
|
9322
|
+
clearTimeout(timer);
|
|
9323
|
+
if (code === 0) {
|
|
9324
|
+
resolve11({ stdout, stderr });
|
|
9325
|
+
} else {
|
|
9326
|
+
const err = new Error(`Command failed with exit code ${code}`);
|
|
9327
|
+
err.stdout = stdout;
|
|
9328
|
+
err.stderr = stderr;
|
|
9329
|
+
err.status = code;
|
|
9330
|
+
reject(err);
|
|
9331
|
+
}
|
|
9332
|
+
});
|
|
9333
|
+
});
|
|
9334
|
+
}
|
|
8915
9335
|
}
|
|
8916
9336
|
var init_verifier = __esm(() => {
|
|
8917
9337
|
init_i18n();
|
|
@@ -9146,6 +9566,18 @@ var init_store = __esm(() => {
|
|
|
9146
9566
|
|
|
9147
9567
|
// src/core/agent.ts
|
|
9148
9568
|
import { join as join10 } from "path";
|
|
9569
|
+
function isToolCallJson(text) {
|
|
9570
|
+
const trimmed = text.trim();
|
|
9571
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
9572
|
+
try {
|
|
9573
|
+
JSON.parse(trimmed);
|
|
9574
|
+
return true;
|
|
9575
|
+
} catch {
|
|
9576
|
+
return false;
|
|
9577
|
+
}
|
|
9578
|
+
}
|
|
9579
|
+
return false;
|
|
9580
|
+
}
|
|
9149
9581
|
|
|
9150
9582
|
class Agent {
|
|
9151
9583
|
deps;
|
|
@@ -9240,6 +9672,17 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9240
9672
|
logger,
|
|
9241
9673
|
sessionManager: sessionManager?.getActiveMeta()
|
|
9242
9674
|
});
|
|
9675
|
+
if (config.session?.baselineCheck !== false) {
|
|
9676
|
+
const verifier = new StepVerifier(baseDir);
|
|
9677
|
+
verifier.runTypeCheck().then((tc) => {
|
|
9678
|
+
if (!tc.passed) {
|
|
9679
|
+
logger.warn(`Baseline typecheck has issues: ${tc.message?.slice(0, 500)}`);
|
|
9680
|
+
onMeta?.(pc.yellow(`
|
|
9681
|
+
⚠ Baseline typecheck has issues
|
|
9682
|
+
`));
|
|
9683
|
+
}
|
|
9684
|
+
}).catch(() => {});
|
|
9685
|
+
}
|
|
9243
9686
|
}
|
|
9244
9687
|
contextManager.addMessage({ role: "user", content: input });
|
|
9245
9688
|
slog.logUser(input);
|
|
@@ -9287,7 +9730,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9287
9730
|
logger,
|
|
9288
9731
|
lastUserMessage: input,
|
|
9289
9732
|
contextManager,
|
|
9290
|
-
onMeta
|
|
9733
|
+
onMeta,
|
|
9734
|
+
sessionLog: {
|
|
9735
|
+
plan: (event, detail, iter) => slog.logPlan(event, detail, iter)
|
|
9736
|
+
}
|
|
9291
9737
|
});
|
|
9292
9738
|
if (contextManager.needsCompaction()) {
|
|
9293
9739
|
contextManager.compact();
|
|
@@ -9296,6 +9742,12 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9296
9742
|
}
|
|
9297
9743
|
const currentTokens = contextManager.getEstimatedTokens();
|
|
9298
9744
|
const budget2 = contextManager.getBudget();
|
|
9745
|
+
const quality = contextManager.getQuality();
|
|
9746
|
+
if (quality < QUALITY_TRIGGER_THRESHOLD && contextManager.getCompactionCount() > 0) {
|
|
9747
|
+
contextManager.compact();
|
|
9748
|
+
logger.warn(`Low context quality (${quality}%) — forced compaction`);
|
|
9749
|
+
slog.logCompaction(`quality-triggered compaction (${quality}% < ${QUALITY_TRIGGER_THRESHOLD}%), iteration ${iteration}`, iteration, currentTokens, budget2.history);
|
|
9750
|
+
}
|
|
9299
9751
|
if (currentTokens > budget2.history) {
|
|
9300
9752
|
contextManager.compact();
|
|
9301
9753
|
logger.warn(`Context overflow (${currentTokens} > ${budget2.history}), forced compaction`);
|
|
@@ -9313,7 +9765,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9313
9765
|
const textChunks = [];
|
|
9314
9766
|
this.emitPhase(iteration, "thinking", onPhase);
|
|
9315
9767
|
try {
|
|
9316
|
-
for await (const chunk of llmProvider.chat(history, allTools)) {
|
|
9768
|
+
for await (const chunk of llmProvider.chat(history, allTools, this.abortController?.signal)) {
|
|
9317
9769
|
if (this.shutdownRequested)
|
|
9318
9770
|
break;
|
|
9319
9771
|
if (chunk.type === "text" && chunk.content) {
|
|
@@ -9355,6 +9807,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9355
9807
|
}
|
|
9356
9808
|
}
|
|
9357
9809
|
} catch (err) {
|
|
9810
|
+
if (this.shutdownRequested || err?.name === "AbortError") {
|
|
9811
|
+
logger.info("LLM call aborted (interrupt)");
|
|
9812
|
+
break;
|
|
9813
|
+
}
|
|
9358
9814
|
logger.error(`LLM call failed: ${err.message}`);
|
|
9359
9815
|
slog.logError(err.message);
|
|
9360
9816
|
pluginManager.runOnError({ iteration, logger }, err);
|
|
@@ -9367,7 +9823,12 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9367
9823
|
} finally {
|
|
9368
9824
|
this.emitPhase(iteration, "done", onPhase);
|
|
9369
9825
|
}
|
|
9370
|
-
if (
|
|
9826
|
+
if (this.shutdownRequested) {
|
|
9827
|
+
break;
|
|
9828
|
+
}
|
|
9829
|
+
const toolComments = this.deps.config.ui?.toolComments ?? true;
|
|
9830
|
+
const showText = textChunks.length > 0 && (!sawToolCall || toolComments && !isToolCallJson(textContent));
|
|
9831
|
+
if (showText) {
|
|
9371
9832
|
for (const chunk of textChunks) {
|
|
9372
9833
|
const textOut = pluginManager.runOnText({ iteration, logger }, chunk);
|
|
9373
9834
|
onChunk?.(textOut);
|
|
@@ -9423,6 +9884,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
9423
9884
|
const duration = Date.now() - startTime;
|
|
9424
9885
|
if (!result.success)
|
|
9425
9886
|
anyToolFailed = true;
|
|
9887
|
+
if (result.success && call.arguments.path) {
|
|
9888
|
+
const filePath = String(call.arguments.path);
|
|
9889
|
+
if (call.name === "write_file" || call.name === "edit_file") {
|
|
9890
|
+
hallucinationDetector.getConsistencyCheck().trackCreatedFile(filePath);
|
|
9891
|
+
} else if (call.name === "delete_file") {
|
|
9892
|
+
hallucinationDetector.getConsistencyCheck().trackDeletedFile(filePath);
|
|
9893
|
+
}
|
|
9894
|
+
}
|
|
9426
9895
|
pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
|
|
9427
9896
|
if (result.display) {
|
|
9428
9897
|
onMeta?.(`
|
|
@@ -9507,14 +9976,15 @@ ${taskReminder}</system-summary>`
|
|
|
9507
9976
|
const ctxBar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
|
|
9508
9977
|
const pctColor = ctxPct >= 75 ? pc.yellow : pc.dim;
|
|
9509
9978
|
const compCount = contextManager.getCompactionCount();
|
|
9510
|
-
const
|
|
9511
|
-
const qualityColor =
|
|
9979
|
+
const quality2 = contextManager.getQuality();
|
|
9980
|
+
const qualityColor = quality2 >= 70 ? pc.green : quality2 >= 40 ? pc.yellow : pc.red;
|
|
9512
9981
|
onMeta?.(`
|
|
9513
|
-
${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${
|
|
9982
|
+
${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${quality2}%`)}
|
|
9514
9983
|
`);
|
|
9515
9984
|
continue;
|
|
9516
9985
|
}
|
|
9517
|
-
|
|
9986
|
+
hallucinationDetector.getConfidenceCheck().setPreviousResponse(lastText);
|
|
9987
|
+
const hallucinationResult = await hallucinationDetector.validate(textContent);
|
|
9518
9988
|
if (hallucinationResult.status === "block") {
|
|
9519
9989
|
logger.warn(`Response blocked: ${hallucinationResult.reason}`);
|
|
9520
9990
|
return {
|
|
@@ -9570,6 +10040,14 @@ ${taskReminder}</system-summary>`
|
|
|
9570
10040
|
slog.logAssistant(textContent, reasoningContent, undefined, iteration);
|
|
9571
10041
|
}
|
|
9572
10042
|
lastText = textContent;
|
|
10043
|
+
{
|
|
10044
|
+
const decisionPatterns = [
|
|
10045
|
+
...textContent.matchAll(/(?:plan|decided|decision|решено|план|решение):\s*(.+?)(?:\n|$)/gi)
|
|
10046
|
+
];
|
|
10047
|
+
for (const match of decisionPatterns) {
|
|
10048
|
+
hallucinationDetector.getConsistencyCheck().trackDecision(match[1].trim(), "agent_response");
|
|
10049
|
+
}
|
|
10050
|
+
}
|
|
9573
10051
|
if (!sawToolCall) {
|
|
9574
10052
|
if (this.deps.finalAudit) {
|
|
9575
10053
|
const audit = await this.deps.finalAudit();
|
|
@@ -9677,7 +10155,7 @@ ${taskReminder}</system-summary>`
|
|
|
9677
10155
|
});
|
|
9678
10156
|
}
|
|
9679
10157
|
}
|
|
9680
|
-
var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
|
|
10158
|
+
var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40;
|
|
9681
10159
|
var init_agent = __esm(() => {
|
|
9682
10160
|
init_i18n();
|
|
9683
10161
|
init_colors();
|
|
@@ -9685,6 +10163,7 @@ var init_agent = __esm(() => {
|
|
|
9685
10163
|
init_processes();
|
|
9686
10164
|
init_agent_moe();
|
|
9687
10165
|
init_store();
|
|
10166
|
+
init_verifier();
|
|
9688
10167
|
});
|
|
9689
10168
|
|
|
9690
10169
|
// src/modules/context/manager.ts
|
|
@@ -9990,53 +10469,16 @@ class ConsistencyCheck {
|
|
|
9990
10469
|
getCreatedFiles() {
|
|
9991
10470
|
return Array.from(this.createdFiles);
|
|
9992
10471
|
}
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
return true;
|
|
10002
|
-
if (lower.includes(`replacing ${word} with`))
|
|
10003
|
-
return true;
|
|
10004
|
-
if (lower.includes(`switching to`))
|
|
10005
|
-
return true;
|
|
10006
|
-
if (lower.includes(`changing from ${word}`))
|
|
10007
|
-
return true;
|
|
10008
|
-
if (lower.includes(`abandoning ${word}`))
|
|
10009
|
-
return true;
|
|
10010
|
-
if (lower.includes(`вместо ${word}`))
|
|
10011
|
-
return true;
|
|
10012
|
-
if (lower.includes(`заменяя ${word}`))
|
|
10013
|
-
return true;
|
|
10014
|
-
if (lower.includes(`заменяем ${word}`))
|
|
10015
|
-
return true;
|
|
10016
|
-
if (lower.includes(`переключаемся на`))
|
|
10017
|
-
return true;
|
|
10018
|
-
if (lower.includes(`от ${word} к`))
|
|
10019
|
-
return true;
|
|
10020
|
-
if (lower.includes(`отказываемся от ${word}`))
|
|
10021
|
-
return true;
|
|
10022
|
-
return false;
|
|
10023
|
-
});
|
|
10024
|
-
if (contradicts) {
|
|
10025
|
-
return {
|
|
10026
|
-
status: "warn",
|
|
10027
|
-
reason: t("hall.contradiction", {
|
|
10028
|
-
decision: d.decision,
|
|
10029
|
-
location: d.location
|
|
10030
|
-
})
|
|
10031
|
-
};
|
|
10032
|
-
}
|
|
10033
|
-
}
|
|
10034
|
-
return { status: "pass" };
|
|
10472
|
+
getDeletedFiles() {
|
|
10473
|
+
return Array.from(this.deletedFiles);
|
|
10474
|
+
}
|
|
10475
|
+
getDecisions() {
|
|
10476
|
+
return this.decisions;
|
|
10477
|
+
}
|
|
10478
|
+
hasDecisions() {
|
|
10479
|
+
return this.decisions.length > 0;
|
|
10035
10480
|
}
|
|
10036
10481
|
}
|
|
10037
|
-
var init_consistency = __esm(() => {
|
|
10038
|
-
init_i18n();
|
|
10039
|
-
});
|
|
10040
10482
|
|
|
10041
10483
|
// src/modules/hallucination/confidence.ts
|
|
10042
10484
|
class ConfidenceCheck {
|
|
@@ -10094,13 +10536,191 @@ var init_confidence = __esm(() => {
|
|
|
10094
10536
|
init_i18n();
|
|
10095
10537
|
});
|
|
10096
10538
|
|
|
10539
|
+
// src/modules/hallucination/factual.ts
|
|
10540
|
+
import { existsSync as existsSync20 } from "fs";
|
|
10541
|
+
import { resolve as resolve11, isAbsolute } from "path";
|
|
10542
|
+
function looksLikeFilePath(s) {
|
|
10543
|
+
const lower = s.toLowerCase();
|
|
10544
|
+
if (TECH_NAMES.has(lower))
|
|
10545
|
+
return false;
|
|
10546
|
+
if (!s.includes("/") && !s.includes("\\") && !s.match(/^\w+\.[a-z]{2,4}$/i))
|
|
10547
|
+
return false;
|
|
10548
|
+
const ext = s.split(".").pop()?.toLowerCase() || "";
|
|
10549
|
+
return !NON_FILE_EXTENSIONS.has(ext);
|
|
10550
|
+
}
|
|
10551
|
+
|
|
10552
|
+
class FactualCheck {
|
|
10553
|
+
baseDir;
|
|
10554
|
+
constructor(baseDir) {
|
|
10555
|
+
this.baseDir = baseDir;
|
|
10556
|
+
}
|
|
10557
|
+
validate(response) {
|
|
10558
|
+
const filePaths = this.extractFilePaths(response);
|
|
10559
|
+
if (filePaths.length === 0)
|
|
10560
|
+
return { status: "pass" };
|
|
10561
|
+
const nonExistent = [];
|
|
10562
|
+
for (const fp of filePaths) {
|
|
10563
|
+
const abs = isAbsolute(fp) ? fp : resolve11(this.baseDir, fp);
|
|
10564
|
+
if (!existsSync20(abs)) {
|
|
10565
|
+
nonExistent.push(fp);
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
if (nonExistent.length > 0) {
|
|
10569
|
+
return {
|
|
10570
|
+
status: "warn",
|
|
10571
|
+
reason: t("hall.unknown_paths", {
|
|
10572
|
+
paths: nonExistent.slice(0, 5).join(", ")
|
|
10573
|
+
})
|
|
10574
|
+
};
|
|
10575
|
+
}
|
|
10576
|
+
return { status: "pass" };
|
|
10577
|
+
}
|
|
10578
|
+
extractFilePaths(text) {
|
|
10579
|
+
const pattern = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{2,6}\b/gi;
|
|
10580
|
+
const matches = text.match(pattern) || [];
|
|
10581
|
+
return [...new Set(matches)].filter(looksLikeFilePath);
|
|
10582
|
+
}
|
|
10583
|
+
}
|
|
10584
|
+
var NON_FILE_EXTENSIONS, TECH_NAMES;
|
|
10585
|
+
var init_factual = __esm(() => {
|
|
10586
|
+
init_i18n();
|
|
10587
|
+
NON_FILE_EXTENSIONS = new Set([
|
|
10588
|
+
"com",
|
|
10589
|
+
"org",
|
|
10590
|
+
"net",
|
|
10591
|
+
"gov",
|
|
10592
|
+
"edu",
|
|
10593
|
+
"io",
|
|
10594
|
+
"dev",
|
|
10595
|
+
"app",
|
|
10596
|
+
"co",
|
|
10597
|
+
"ai",
|
|
10598
|
+
"ru",
|
|
10599
|
+
"de",
|
|
10600
|
+
"fr",
|
|
10601
|
+
"uk",
|
|
10602
|
+
"us",
|
|
10603
|
+
"es",
|
|
10604
|
+
"it",
|
|
10605
|
+
"jp",
|
|
10606
|
+
"cn",
|
|
10607
|
+
"br"
|
|
10608
|
+
]);
|
|
10609
|
+
TECH_NAMES = new Set([
|
|
10610
|
+
"node.js",
|
|
10611
|
+
"react.js",
|
|
10612
|
+
"vue.js",
|
|
10613
|
+
"next.js",
|
|
10614
|
+
"angular.js",
|
|
10615
|
+
"express.js",
|
|
10616
|
+
"deno.js",
|
|
10617
|
+
"qwik.js",
|
|
10618
|
+
"svelte.js",
|
|
10619
|
+
"jquery.js"
|
|
10620
|
+
]);
|
|
10621
|
+
});
|
|
10622
|
+
|
|
10623
|
+
// src/modules/hallucination/llm-judge.ts
|
|
10624
|
+
class LLMJudge {
|
|
10625
|
+
provider;
|
|
10626
|
+
constructor(provider) {
|
|
10627
|
+
this.provider = provider;
|
|
10628
|
+
}
|
|
10629
|
+
async validate(response, consistency) {
|
|
10630
|
+
const decisions = consistency.getDecisions();
|
|
10631
|
+
if (decisions.length === 0) {
|
|
10632
|
+
return { status: "pass" };
|
|
10633
|
+
}
|
|
10634
|
+
const shown = decisions.slice(-MAX_DECISIONS_SHOWN);
|
|
10635
|
+
const decisionLines = shown.map((d, i) => `${i + 1}. [${d.location || "agent"}] "${d.decision}"`).join(`
|
|
10636
|
+
`);
|
|
10637
|
+
const system = `You are a consistency checker for an AI coding agent. The agent previously made these decisions:
|
|
10638
|
+
|
|
10639
|
+
${decisionLines}
|
|
10640
|
+
|
|
10641
|
+
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).
|
|
10642
|
+
|
|
10643
|
+
Reply with ONLY valid JSON, no other text:
|
|
10644
|
+
- {"contradicts": false}
|
|
10645
|
+
- {"contradicts": true, "reason": "short explanation"}`;
|
|
10646
|
+
const user = `Agent's new response:
|
|
10647
|
+
|
|
10648
|
+
${response.slice(0, MAX_RESPONSE_CHARS)}`;
|
|
10649
|
+
try {
|
|
10650
|
+
const verdictText = await this.askJudge([
|
|
10651
|
+
{ role: "system", content: system },
|
|
10652
|
+
{ role: "user", content: user }
|
|
10653
|
+
]);
|
|
10654
|
+
const verdict = this.parseVerdict(verdictText);
|
|
10655
|
+
if (verdict.contradicts) {
|
|
10656
|
+
const reason = verdict.reason?.trim() ? ` — ${verdict.reason.trim()}` : "";
|
|
10657
|
+
return {
|
|
10658
|
+
status: "warn",
|
|
10659
|
+
reason: t("hall.contradiction_llm", { reason })
|
|
10660
|
+
};
|
|
10661
|
+
}
|
|
10662
|
+
return { status: "pass" };
|
|
10663
|
+
} catch {
|
|
10664
|
+
return { status: "pass" };
|
|
10665
|
+
}
|
|
10666
|
+
}
|
|
10667
|
+
async askJudge(messages) {
|
|
10668
|
+
let text = "";
|
|
10669
|
+
let timer;
|
|
10670
|
+
const consume = (async () => {
|
|
10671
|
+
for await (const chunk of this.provider.chat(messages, [])) {
|
|
10672
|
+
if (chunk.type === "text" && chunk.content) {
|
|
10673
|
+
text += chunk.content;
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10676
|
+
return text;
|
|
10677
|
+
})();
|
|
10678
|
+
const timeout = new Promise((_, reject) => {
|
|
10679
|
+
timer = setTimeout(() => reject(new Error("LLM judge timed out")), JUDGE_TIMEOUT_MS);
|
|
10680
|
+
});
|
|
10681
|
+
try {
|
|
10682
|
+
return await Promise.race([consume, timeout]);
|
|
10683
|
+
} finally {
|
|
10684
|
+
if (timer)
|
|
10685
|
+
clearTimeout(timer);
|
|
10686
|
+
}
|
|
10687
|
+
}
|
|
10688
|
+
parseVerdict(text) {
|
|
10689
|
+
const block = text.match(/\{[\s\S]*\}/);
|
|
10690
|
+
if (block) {
|
|
10691
|
+
try {
|
|
10692
|
+
const obj = JSON.parse(block[0]);
|
|
10693
|
+
if (typeof obj.contradicts === "boolean") {
|
|
10694
|
+
return {
|
|
10695
|
+
contradicts: obj.contradicts,
|
|
10696
|
+
reason: typeof obj.reason === "string" ? obj.reason : undefined
|
|
10697
|
+
};
|
|
10698
|
+
}
|
|
10699
|
+
} catch {}
|
|
10700
|
+
}
|
|
10701
|
+
const lower = text.toLowerCase();
|
|
10702
|
+
if (lower.includes('"contradicts": true') || /^yes\b/.test(lower.trim())) {
|
|
10703
|
+
return { contradicts: true, reason: text.slice(0, 200) };
|
|
10704
|
+
}
|
|
10705
|
+
return { contradicts: false };
|
|
10706
|
+
}
|
|
10707
|
+
}
|
|
10708
|
+
var JUDGE_TIMEOUT_MS = 15000, MAX_DECISIONS_SHOWN = 6, MAX_RESPONSE_CHARS = 2000;
|
|
10709
|
+
var init_llm_judge = __esm(() => {
|
|
10710
|
+
init_i18n();
|
|
10711
|
+
});
|
|
10712
|
+
|
|
10097
10713
|
// src/modules/hallucination/detector.ts
|
|
10098
10714
|
class HallucinationDetector {
|
|
10099
10715
|
consistency;
|
|
10100
10716
|
confidence;
|
|
10101
|
-
|
|
10717
|
+
factual;
|
|
10718
|
+
judge;
|
|
10719
|
+
constructor(baseDir, llmProvider) {
|
|
10102
10720
|
this.consistency = new ConsistencyCheck;
|
|
10103
10721
|
this.confidence = new ConfidenceCheck;
|
|
10722
|
+
this.factual = baseDir ? new FactualCheck(baseDir) : null;
|
|
10723
|
+
this.judge = llmProvider ? new LLMJudge(llmProvider) : null;
|
|
10104
10724
|
}
|
|
10105
10725
|
getConsistencyCheck() {
|
|
10106
10726
|
return this.consistency;
|
|
@@ -10108,15 +10728,19 @@ class HallucinationDetector {
|
|
|
10108
10728
|
getConfidenceCheck() {
|
|
10109
10729
|
return this.confidence;
|
|
10110
10730
|
}
|
|
10111
|
-
validate(response) {
|
|
10731
|
+
async validate(response) {
|
|
10112
10732
|
const confidenceResult = this.confidence.validate(response);
|
|
10113
10733
|
if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
|
|
10114
10734
|
return confidenceResult;
|
|
10115
10735
|
}
|
|
10116
|
-
const
|
|
10736
|
+
const factualResult = this.factual?.validate(response);
|
|
10737
|
+
if (factualResult && factualResult.status !== "pass") {
|
|
10738
|
+
return factualResult;
|
|
10739
|
+
}
|
|
10740
|
+
const judgeResult = this.judge ? await this.judge.validate(response, this.consistency) : null;
|
|
10117
10741
|
const warnings = [];
|
|
10118
|
-
if (
|
|
10119
|
-
warnings.push(
|
|
10742
|
+
if (judgeResult && judgeResult.status === "warn")
|
|
10743
|
+
warnings.push(judgeResult.reason || "");
|
|
10120
10744
|
if (confidenceResult.status === "warn")
|
|
10121
10745
|
warnings.push(confidenceResult.reason || "");
|
|
10122
10746
|
if (warnings.length > 0) {
|
|
@@ -10126,8 +10750,9 @@ class HallucinationDetector {
|
|
|
10126
10750
|
}
|
|
10127
10751
|
}
|
|
10128
10752
|
var init_detector = __esm(() => {
|
|
10129
|
-
init_consistency();
|
|
10130
10753
|
init_confidence();
|
|
10754
|
+
init_factual();
|
|
10755
|
+
init_llm_judge();
|
|
10131
10756
|
});
|
|
10132
10757
|
|
|
10133
10758
|
// src/modules/plugins/manager.ts
|
|
@@ -10370,7 +10995,7 @@ var init_subagent = __esm(() => {
|
|
|
10370
10995
|
try {
|
|
10371
10996
|
const subContextManager = new ContextManager(ctx.config.contextWindow, ctx.config.contextBudget);
|
|
10372
10997
|
const subPluginManager = new PluginManager;
|
|
10373
|
-
const hallucinationDetector = new HallucinationDetector;
|
|
10998
|
+
const hallucinationDetector = new HallucinationDetector(ctx.baseDir, ctx.llmProvider);
|
|
10374
10999
|
const systemPrompt = {
|
|
10375
11000
|
content: `You are a sub-agent working on a specific task. ${context ? `Context: ${context}` : ""}`,
|
|
10376
11001
|
priority: "critical",
|
|
@@ -10896,7 +11521,7 @@ async function runStep(ctx, step, params, outputs) {
|
|
|
10896
11521
|
toolExecutor: ctx.toolExecutor,
|
|
10897
11522
|
pluginManager: subPluginManager,
|
|
10898
11523
|
contextManager: subContextManager,
|
|
10899
|
-
hallucinationDetector: new HallucinationDetector,
|
|
11524
|
+
hallucinationDetector: new HallucinationDetector(ctx.baseDir, ctx.llmProvider),
|
|
10900
11525
|
logger: ctx.logger,
|
|
10901
11526
|
baseDir: ctx.baseDir,
|
|
10902
11527
|
scope: ctx.scope,
|
|
@@ -11126,7 +11751,7 @@ class MCPClient {
|
|
|
11126
11751
|
}
|
|
11127
11752
|
}
|
|
11128
11753
|
connectStdio() {
|
|
11129
|
-
return new Promise((
|
|
11754
|
+
return new Promise((resolve12, reject) => {
|
|
11130
11755
|
const child = spawn3(this.config.command, this.config.args || [], {
|
|
11131
11756
|
env: { ...process.env, ...this.config.env },
|
|
11132
11757
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11144,7 +11769,7 @@ class MCPClient {
|
|
|
11144
11769
|
child.on("spawn", () => {
|
|
11145
11770
|
this._connected = true;
|
|
11146
11771
|
this.process = child;
|
|
11147
|
-
|
|
11772
|
+
resolve12();
|
|
11148
11773
|
});
|
|
11149
11774
|
});
|
|
11150
11775
|
}
|
|
@@ -11213,7 +11838,7 @@ class MCPClient {
|
|
|
11213
11838
|
if (!this.process) {
|
|
11214
11839
|
throw new Error("Not connected to MCP server");
|
|
11215
11840
|
}
|
|
11216
|
-
return new Promise((
|
|
11841
|
+
return new Promise((resolve12, reject) => {
|
|
11217
11842
|
const request = {
|
|
11218
11843
|
jsonrpc: "2.0",
|
|
11219
11844
|
id: Date.now(),
|
|
@@ -11237,7 +11862,7 @@ class MCPClient {
|
|
|
11237
11862
|
if (response.error) {
|
|
11238
11863
|
reject(new Error(response.error.message));
|
|
11239
11864
|
} else {
|
|
11240
|
-
|
|
11865
|
+
resolve12(response.result?.tools || []);
|
|
11241
11866
|
}
|
|
11242
11867
|
return;
|
|
11243
11868
|
}
|
|
@@ -11319,8 +11944,8 @@ class MCPClient {
|
|
|
11319
11944
|
arguments: args
|
|
11320
11945
|
}
|
|
11321
11946
|
};
|
|
11322
|
-
return new Promise((
|
|
11323
|
-
this.pendingResolve =
|
|
11947
|
+
return new Promise((resolve12, reject) => {
|
|
11948
|
+
this.pendingResolve = resolve12;
|
|
11324
11949
|
this.pendingReject = reject;
|
|
11325
11950
|
const timeout = this.config.timeout || 30000;
|
|
11326
11951
|
const timer = setTimeout(() => {
|
|
@@ -11347,7 +11972,7 @@ class MCPClient {
|
|
|
11347
11972
|
if (result.error) {
|
|
11348
11973
|
reject(new Error(result.error.message));
|
|
11349
11974
|
} else {
|
|
11350
|
-
|
|
11975
|
+
resolve12(result.result || result);
|
|
11351
11976
|
}
|
|
11352
11977
|
}).catch((err) => {
|
|
11353
11978
|
clearTimeout(timer);
|
|
@@ -11361,7 +11986,7 @@ class MCPClient {
|
|
|
11361
11986
|
if (!this.process) {
|
|
11362
11987
|
throw new Error("Not connected to MCP server");
|
|
11363
11988
|
}
|
|
11364
|
-
return new Promise((
|
|
11989
|
+
return new Promise((resolve12, reject) => {
|
|
11365
11990
|
const request = {
|
|
11366
11991
|
jsonrpc: "2.0",
|
|
11367
11992
|
id: Date.now(),
|
|
@@ -11389,7 +12014,7 @@ class MCPClient {
|
|
|
11389
12014
|
if (response.error) {
|
|
11390
12015
|
reject(new Error(response.error.message));
|
|
11391
12016
|
} else {
|
|
11392
|
-
|
|
12017
|
+
resolve12(response.result);
|
|
11393
12018
|
}
|
|
11394
12019
|
return;
|
|
11395
12020
|
}
|
|
@@ -12377,14 +13002,14 @@ async function readClipboardImage() {
|
|
|
12377
13002
|
return readClipboardFallback();
|
|
12378
13003
|
}
|
|
12379
13004
|
async function readClipboardFallback() {
|
|
12380
|
-
const { platform:
|
|
12381
|
-
const { execSync:
|
|
13005
|
+
const { platform: platform3 } = await import("os");
|
|
13006
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
12382
13007
|
const { readFileSync: readFileSync13, unlinkSync: unlinkSync3 } = await import("fs");
|
|
12383
13008
|
const { join: join16 } = await import("path");
|
|
12384
13009
|
const tmpPath = join16(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
|
|
12385
13010
|
try {
|
|
12386
|
-
if (
|
|
12387
|
-
|
|
13011
|
+
if (platform3() === "linux") {
|
|
13012
|
+
execSync2(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
|
|
12388
13013
|
} else {
|
|
12389
13014
|
return null;
|
|
12390
13015
|
}
|
|
@@ -12459,8 +13084,8 @@ var init_image_utils = __esm(() => {
|
|
|
12459
13084
|
});
|
|
12460
13085
|
|
|
12461
13086
|
// src/tools/attach-image.ts
|
|
12462
|
-
import { existsSync as
|
|
12463
|
-
import { resolve as
|
|
13087
|
+
import { existsSync as existsSync22 } from "fs";
|
|
13088
|
+
import { resolve as resolve12 } from "path";
|
|
12464
13089
|
var attachImageTool;
|
|
12465
13090
|
var init_attach_image = __esm(() => {
|
|
12466
13091
|
init_i18n();
|
|
@@ -12501,8 +13126,8 @@ var init_attach_image = __esm(() => {
|
|
|
12501
13126
|
const result = await loadUrlAsDataUrl(source);
|
|
12502
13127
|
dataUrl = result.dataUrl;
|
|
12503
13128
|
} else {
|
|
12504
|
-
const absPath =
|
|
12505
|
-
if (!
|
|
13129
|
+
const absPath = resolve12(ctx.baseDir, source);
|
|
13130
|
+
if (!existsSync22(absPath)) {
|
|
12506
13131
|
return {
|
|
12507
13132
|
success: false,
|
|
12508
13133
|
output: t("image.not_found", { path: source })
|
|
@@ -12658,12 +13283,12 @@ class ModuleRegistry {
|
|
|
12658
13283
|
}
|
|
12659
13284
|
|
|
12660
13285
|
// src/modules/plugins/loader.ts
|
|
12661
|
-
import { readdirSync as readdirSync5, existsSync as
|
|
13286
|
+
import { readdirSync as readdirSync5, existsSync as existsSync23, statSync as statSync4 } from "fs";
|
|
12662
13287
|
import { join as join16 } from "path";
|
|
12663
13288
|
|
|
12664
13289
|
class PluginLoader {
|
|
12665
13290
|
loadFromDir(dirPath, pluginManager, logger) {
|
|
12666
|
-
if (!
|
|
13291
|
+
if (!existsSync23(dirPath))
|
|
12667
13292
|
return;
|
|
12668
13293
|
const entries = readdirSync5(dirPath);
|
|
12669
13294
|
for (const entry of entries) {
|
|
@@ -12690,9 +13315,9 @@ var init_loader = __esm(() => {
|
|
|
12690
13315
|
});
|
|
12691
13316
|
|
|
12692
13317
|
// src/modules/plugins/builtin/lint-on-write.ts
|
|
12693
|
-
import {
|
|
12694
|
-
import { existsSync as
|
|
12695
|
-
import { resolve as
|
|
13318
|
+
import { spawn as spawn4 } from "child_process";
|
|
13319
|
+
import { existsSync as existsSync24, readFileSync as readFileSync13 } from "fs";
|
|
13320
|
+
import { resolve as resolve13, extname as extname4, join as join17 } from "path";
|
|
12696
13321
|
|
|
12697
13322
|
class LintOnWritePlugin {
|
|
12698
13323
|
name = "lint-on-write";
|
|
@@ -12707,11 +13332,11 @@ class LintOnWritePlugin {
|
|
|
12707
13332
|
const path = String(call.arguments.path || "");
|
|
12708
13333
|
if (!path)
|
|
12709
13334
|
return;
|
|
12710
|
-
const fullPath =
|
|
12711
|
-
if (!
|
|
13335
|
+
const fullPath = resolve13(ctx.baseDir, path);
|
|
13336
|
+
if (!existsSync24(fullPath))
|
|
12712
13337
|
return;
|
|
12713
13338
|
const ext = extname4(fullPath);
|
|
12714
|
-
const syntaxError = this.checkSyntax(fullPath, ext, ctx.baseDir);
|
|
13339
|
+
const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
|
|
12715
13340
|
if (syntaxError) {
|
|
12716
13341
|
result.output += `
|
|
12717
13342
|
|
|
@@ -12721,10 +13346,10 @@ class LintOnWritePlugin {
|
|
|
12721
13346
|
await this.runProjectLint(ctx, result);
|
|
12722
13347
|
await this.runProjectTypeCheck(ctx, result);
|
|
12723
13348
|
}
|
|
12724
|
-
checkSyntax(filePath, ext, baseDir) {
|
|
13349
|
+
async checkSyntax(filePath, ext, baseDir) {
|
|
12725
13350
|
if (ext === ".ts" || ext === ".tsx") {
|
|
12726
13351
|
try {
|
|
12727
|
-
|
|
13352
|
+
await runAsync(`npx tsc --noEmit --skipLibCheck "${filePath}"`, baseDir, 1e4);
|
|
12728
13353
|
return null;
|
|
12729
13354
|
} catch (err) {
|
|
12730
13355
|
if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
|
|
@@ -12741,7 +13366,7 @@ class LintOnWritePlugin {
|
|
|
12741
13366
|
}
|
|
12742
13367
|
if (ext === ".js" || ext === ".jsx") {
|
|
12743
13368
|
try {
|
|
12744
|
-
|
|
13369
|
+
await runAsync(`node --check "${filePath}"`, baseDir, 5000);
|
|
12745
13370
|
return null;
|
|
12746
13371
|
} catch (err) {
|
|
12747
13372
|
const stderr = err.stderr?.toString() || "";
|
|
@@ -12755,7 +13380,7 @@ class LintOnWritePlugin {
|
|
|
12755
13380
|
async runProjectLint(ctx, result) {
|
|
12756
13381
|
try {
|
|
12757
13382
|
const packageJsonPath = join17(ctx.baseDir, "package.json");
|
|
12758
|
-
if (!
|
|
13383
|
+
if (!existsSync24(packageJsonPath)) {
|
|
12759
13384
|
return;
|
|
12760
13385
|
}
|
|
12761
13386
|
const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf-8"));
|
|
@@ -12764,7 +13389,7 @@ class LintOnWritePlugin {
|
|
|
12764
13389
|
return;
|
|
12765
13390
|
}
|
|
12766
13391
|
ctx.logger.debug(`Running lint: ${lintScript}`);
|
|
12767
|
-
|
|
13392
|
+
await runAsync(lintScript, ctx.baseDir, 30000);
|
|
12768
13393
|
ctx.logger.debug("Lint passed");
|
|
12769
13394
|
} catch (err) {
|
|
12770
13395
|
ctx.logger.warn(`Lint failed: ${err.message}`);
|
|
@@ -12775,7 +13400,7 @@ class LintOnWritePlugin {
|
|
|
12775
13400
|
}
|
|
12776
13401
|
async runProjectTypeCheck(ctx, result) {
|
|
12777
13402
|
const tsconfigPath = join17(ctx.baseDir, "tsconfig.json");
|
|
12778
|
-
if (!
|
|
13403
|
+
if (!existsSync24(tsconfigPath)) {
|
|
12779
13404
|
return;
|
|
12780
13405
|
}
|
|
12781
13406
|
const now = Date.now();
|
|
@@ -12799,7 +13424,7 @@ class LintOnWritePlugin {
|
|
|
12799
13424
|
}
|
|
12800
13425
|
async runTscCheck(baseDir) {
|
|
12801
13426
|
try {
|
|
12802
|
-
|
|
13427
|
+
await runAsync(`npx tsc --noEmit --skipLibCheck`, baseDir, 30000);
|
|
12803
13428
|
return null;
|
|
12804
13429
|
} catch (err) {
|
|
12805
13430
|
if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
|
|
@@ -12811,9 +13436,47 @@ class LintOnWritePlugin {
|
|
|
12811
13436
|
`).find((line) => line.includes("error TS")) || "TypeScript type error";
|
|
12812
13437
|
return firstError.trim();
|
|
12813
13438
|
}
|
|
12814
|
-
return null;
|
|
12815
|
-
}
|
|
12816
|
-
}
|
|
13439
|
+
return null;
|
|
13440
|
+
}
|
|
13441
|
+
}
|
|
13442
|
+
}
|
|
13443
|
+
function runAsync(command, cwd, timeoutMs) {
|
|
13444
|
+
return new Promise((resolve14, reject) => {
|
|
13445
|
+
const child = spawn4(command, {
|
|
13446
|
+
cwd,
|
|
13447
|
+
shell: true,
|
|
13448
|
+
windowsHide: true,
|
|
13449
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
13450
|
+
});
|
|
13451
|
+
let stdout = "";
|
|
13452
|
+
let stderr = "";
|
|
13453
|
+
child.stdout?.on("data", (d) => {
|
|
13454
|
+
stdout += d.toString();
|
|
13455
|
+
});
|
|
13456
|
+
child.stderr?.on("data", (d) => {
|
|
13457
|
+
stderr += d.toString();
|
|
13458
|
+
});
|
|
13459
|
+
const timer = setTimeout(() => {
|
|
13460
|
+
child.kill();
|
|
13461
|
+
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
13462
|
+
}, timeoutMs);
|
|
13463
|
+
child.on("error", (err) => {
|
|
13464
|
+
clearTimeout(timer);
|
|
13465
|
+
reject(err);
|
|
13466
|
+
});
|
|
13467
|
+
child.on("close", (code) => {
|
|
13468
|
+
clearTimeout(timer);
|
|
13469
|
+
if (code === 0) {
|
|
13470
|
+
resolve14({ stdout, stderr });
|
|
13471
|
+
} else {
|
|
13472
|
+
const err = new Error(`Command failed with exit code ${code}`);
|
|
13473
|
+
err.stdout = stdout;
|
|
13474
|
+
err.stderr = stderr;
|
|
13475
|
+
err.status = code;
|
|
13476
|
+
reject(err);
|
|
13477
|
+
}
|
|
13478
|
+
});
|
|
13479
|
+
});
|
|
12817
13480
|
}
|
|
12818
13481
|
var projectTypeCheckPromise = null, projectTypeCheckTimestamp = 0, TYPE_CHECK_DEBOUNCE_MS = 2000, plugin;
|
|
12819
13482
|
var init_lint_on_write = __esm(() => {
|
|
@@ -12867,372 +13530,97 @@ class PlanCreator {
|
|
|
12867
13530
|
const stepCount = stepDescriptions.length;
|
|
12868
13531
|
return {
|
|
12869
13532
|
id: generatePlanId(),
|
|
12870
|
-
title: `[${stepCount} ст.] ${title}`,
|
|
12871
|
-
steps: stepDescriptions.map((desc, i) => ({
|
|
12872
|
-
id: i + 1,
|
|
12873
|
-
description: desc,
|
|
12874
|
-
status: "pending"
|
|
12875
|
-
})),
|
|
12876
|
-
createdAt: new Date().toISOString(),
|
|
12877
|
-
baseDir
|
|
12878
|
-
};
|
|
12879
|
-
}
|
|
12880
|
-
static toPromptBlock(plan, currentStepIndex) {
|
|
12881
|
-
const date = plan.createdAt.slice(0, 10);
|
|
12882
|
-
const lines = [
|
|
12883
|
-
`[${plan.id}] ${plan.title}`,
|
|
12884
|
-
`Dir: ${plan.baseDir}`,
|
|
12885
|
-
`Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
|
|
12886
|
-
``
|
|
12887
|
-
];
|
|
12888
|
-
for (const step of plan.steps) {
|
|
12889
|
-
const icon = step.status === "done" ? "[x]" : step.status === "in_progress" ? "[*]" : step.status === "failed" ? "[!]" : step.status === "skipped" ? "[-]" : "[ ]";
|
|
12890
|
-
const note = step.note ? ` — ${step.note}` : "";
|
|
12891
|
-
lines.push(`${icon} ${step.id}. ${step.description}${note}`);
|
|
12892
|
-
}
|
|
12893
|
-
return lines.join(`
|
|
12894
|
-
`);
|
|
12895
|
-
}
|
|
12896
|
-
}
|
|
12897
|
-
|
|
12898
|
-
// src/modules/execution/tracker.ts
|
|
12899
|
-
class PlanTracker {
|
|
12900
|
-
plan;
|
|
12901
|
-
currentStepIndex = 0;
|
|
12902
|
-
constructor(plan) {
|
|
12903
|
-
this.plan = plan;
|
|
12904
|
-
}
|
|
12905
|
-
getPlan() {
|
|
12906
|
-
return this.plan;
|
|
12907
|
-
}
|
|
12908
|
-
getCurrentStepIndex() {
|
|
12909
|
-
return this.currentStepIndex;
|
|
12910
|
-
}
|
|
12911
|
-
getCurrentStep() {
|
|
12912
|
-
return this.plan.steps[this.currentStepIndex];
|
|
12913
|
-
}
|
|
12914
|
-
getStep(id) {
|
|
12915
|
-
return this.plan.steps.find((s) => s.id === id);
|
|
12916
|
-
}
|
|
12917
|
-
updateStepStatus(id, status) {
|
|
12918
|
-
const step = this.getStep(id);
|
|
12919
|
-
if (step)
|
|
12920
|
-
step.status = status;
|
|
12921
|
-
}
|
|
12922
|
-
addNote(id, note) {
|
|
12923
|
-
const step = this.getStep(id);
|
|
12924
|
-
if (step)
|
|
12925
|
-
step.note = note;
|
|
12926
|
-
}
|
|
12927
|
-
advance() {
|
|
12928
|
-
if (this.currentStepIndex < this.plan.steps.length - 1) {
|
|
12929
|
-
this.currentStepIndex++;
|
|
12930
|
-
this.plan.steps[this.currentStepIndex].status = "in_progress";
|
|
12931
|
-
return true;
|
|
12932
|
-
}
|
|
12933
|
-
return false;
|
|
12934
|
-
}
|
|
12935
|
-
isComplete() {
|
|
12936
|
-
return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
12937
|
-
}
|
|
12938
|
-
getProgressString() {
|
|
12939
|
-
const done = this.plan.steps.filter((s) => s.status === "done").length;
|
|
12940
|
-
const total = this.plan.steps.length;
|
|
12941
|
-
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
12942
|
-
const barWidth = 10;
|
|
12943
|
-
const filled = Math.round(done / total * barWidth);
|
|
12944
|
-
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
|
|
12945
|
-
return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
|
|
12946
|
-
}
|
|
12947
|
-
toPromptBlock() {
|
|
12948
|
-
return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
|
|
12949
|
-
}
|
|
12950
|
-
}
|
|
12951
|
-
var init_tracker = () => {};
|
|
12952
|
-
|
|
12953
|
-
// src/modules/execution/stuck-detector.ts
|
|
12954
|
-
class StuckDetector {
|
|
12955
|
-
threshold;
|
|
12956
|
-
errorThreshold;
|
|
12957
|
-
iterationsOnCurrentStep = 0;
|
|
12958
|
-
currentStepId = null;
|
|
12959
|
-
toolErrors = new Map;
|
|
12960
|
-
currentStepDescription = "";
|
|
12961
|
-
recentToolCalls = [];
|
|
12962
|
-
maxRecentCalls = 10;
|
|
12963
|
-
repetitionThreshold = 3;
|
|
12964
|
-
consecutiveFailures = 0;
|
|
12965
|
-
lastFailedTool = "";
|
|
12966
|
-
lastErrorOutput = "";
|
|
12967
|
-
escalationCount = 0;
|
|
12968
|
-
escalationThreshold = 3;
|
|
12969
|
-
fileRewriteCount = new Map;
|
|
12970
|
-
fileRewriteThreshold = 3;
|
|
12971
|
-
constructor(threshold = 8, errorThreshold = 3) {
|
|
12972
|
-
this.threshold = threshold;
|
|
12973
|
-
this.errorThreshold = errorThreshold;
|
|
12974
|
-
}
|
|
12975
|
-
recordIteration(stepId) {
|
|
12976
|
-
if (stepId === this.currentStepId) {
|
|
12977
|
-
this.iterationsOnCurrentStep++;
|
|
12978
|
-
} else {
|
|
12979
|
-
this.currentStepId = stepId;
|
|
12980
|
-
this.iterationsOnCurrentStep = 1;
|
|
12981
|
-
}
|
|
12982
|
-
}
|
|
12983
|
-
recordToolCall(name, args) {
|
|
12984
|
-
const argsKey = JSON.stringify(args);
|
|
12985
|
-
this.recentToolCalls.push({ name, argsKey });
|
|
12986
|
-
if (this.recentToolCalls.length > this.maxRecentCalls) {
|
|
12987
|
-
this.recentToolCalls.shift();
|
|
12988
|
-
}
|
|
12989
|
-
}
|
|
12990
|
-
recordToolError(toolName, output) {
|
|
12991
|
-
this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
|
|
12992
|
-
this.consecutiveFailures++;
|
|
12993
|
-
this.lastFailedTool = toolName;
|
|
12994
|
-
if (output)
|
|
12995
|
-
this.lastErrorOutput = output;
|
|
12996
|
-
}
|
|
12997
|
-
getLastErrorOutput() {
|
|
12998
|
-
return this.lastErrorOutput;
|
|
12999
|
-
}
|
|
13000
|
-
getLastFailedTool() {
|
|
13001
|
-
return this.lastFailedTool;
|
|
13002
|
-
}
|
|
13003
|
-
getIterationsOnCurrentStep() {
|
|
13004
|
-
return this.iterationsOnCurrentStep;
|
|
13005
|
-
}
|
|
13006
|
-
recordToolSuccess() {
|
|
13007
|
-
this.consecutiveFailures = 0;
|
|
13008
|
-
this.lastFailedTool = "";
|
|
13009
|
-
this.escalationCount = 0;
|
|
13010
|
-
}
|
|
13011
|
-
setCurrentStep(stepId, description) {
|
|
13012
|
-
if (stepId !== this.currentStepId) {
|
|
13013
|
-
this.currentStepId = stepId;
|
|
13014
|
-
this.iterationsOnCurrentStep = 1;
|
|
13015
|
-
}
|
|
13016
|
-
this.currentStepDescription = description;
|
|
13017
|
-
}
|
|
13018
|
-
isStuck() {
|
|
13019
|
-
return this.iterationsOnCurrentStep > this.threshold;
|
|
13020
|
-
}
|
|
13021
|
-
hasRepetitiveErrors(toolName) {
|
|
13022
|
-
if (toolName) {
|
|
13023
|
-
return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
|
|
13024
|
-
}
|
|
13025
|
-
return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
|
|
13026
|
-
}
|
|
13027
|
-
hasRepetitiveToolCalls() {
|
|
13028
|
-
if (this.recentToolCalls.length < this.repetitionThreshold)
|
|
13029
|
-
return false;
|
|
13030
|
-
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
13031
|
-
let count = 1;
|
|
13032
|
-
for (let i = this.recentToolCalls.length - 2;i >= 0; i--) {
|
|
13033
|
-
if (this.recentToolCalls[i].name === last.name && this.recentToolCalls[i].argsKey === last.argsKey) {
|
|
13034
|
-
count++;
|
|
13035
|
-
} else {
|
|
13036
|
-
break;
|
|
13037
|
-
}
|
|
13038
|
-
}
|
|
13039
|
-
return count >= this.repetitionThreshold;
|
|
13040
|
-
}
|
|
13041
|
-
hasConsecutiveFailures() {
|
|
13042
|
-
return this.consecutiveFailures >= this.errorThreshold;
|
|
13043
|
-
}
|
|
13044
|
-
getConsecutiveFailuresCount() {
|
|
13045
|
-
return this.consecutiveFailures;
|
|
13046
|
-
}
|
|
13047
|
-
recordEscalation() {
|
|
13048
|
-
this.escalationCount++;
|
|
13049
|
-
}
|
|
13050
|
-
shouldEscalate() {
|
|
13051
|
-
return this.escalationCount >= this.escalationThreshold;
|
|
13052
|
-
}
|
|
13053
|
-
resetEscalation() {
|
|
13054
|
-
this.escalationCount = 0;
|
|
13055
|
-
}
|
|
13056
|
-
getEscalationCount() {
|
|
13057
|
-
return this.escalationCount;
|
|
13058
|
-
}
|
|
13059
|
-
getHints() {
|
|
13060
|
-
const hints = [];
|
|
13061
|
-
const desc = this.currentStepDescription.toLowerCase();
|
|
13062
|
-
if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
|
|
13063
|
-
hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
|
|
13064
|
-
}
|
|
13065
|
-
if (desc.includes("test") || desc.includes("spec")) {
|
|
13066
|
-
hints.push("Make sure the source files exist and have real code before running tests.");
|
|
13067
|
-
}
|
|
13068
|
-
if (desc.includes("build") || desc.includes("compile")) {
|
|
13069
|
-
hints.push("Check that all dependencies are installed and source files are not empty.");
|
|
13070
|
-
}
|
|
13071
|
-
if (desc.includes("deploy") || desc.includes("publish")) {
|
|
13072
|
-
hints.push("Verify credentials and network access before deploying.");
|
|
13073
|
-
}
|
|
13074
|
-
if (this.hasRepetitiveToolCalls()) {
|
|
13075
|
-
hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
|
|
13076
|
-
}
|
|
13077
|
-
if (this.hasConsecutiveFailures()) {
|
|
13078
|
-
hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
|
|
13079
|
-
}
|
|
13080
|
-
return hints;
|
|
13081
|
-
}
|
|
13082
|
-
getActionableHints() {
|
|
13083
|
-
const hints = [];
|
|
13084
|
-
const error = this.lastErrorOutput;
|
|
13085
|
-
if (!error)
|
|
13086
|
-
return hints;
|
|
13087
|
-
if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
|
|
13088
|
-
if (/node_modules/.test(error)) {
|
|
13089
|
-
hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
|
|
13090
|
-
}
|
|
13091
|
-
}
|
|
13092
|
-
if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
|
|
13093
|
-
hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
|
|
13094
|
-
}
|
|
13095
|
-
if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
|
|
13096
|
-
hints.push("TypeScript type mismatch. Check the type signature of the function/API you are using. If a package lacks type declarations, install @types/<package> or use skipLibCheck.");
|
|
13097
|
-
}
|
|
13098
|
-
if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
|
|
13099
|
-
hints.push("Module not found. Run npm install or check if the import path is correct.");
|
|
13100
|
-
}
|
|
13101
|
-
if (/EACCES|EPERM|permission denied/i.test(error)) {
|
|
13102
|
-
hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
|
|
13103
|
-
}
|
|
13104
|
-
if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
|
|
13105
|
-
hints.push("Network error. Check if the server is running and accessible.");
|
|
13106
|
-
}
|
|
13107
|
-
if (/is not recognized|command not found|not found in path/i.test(error)) {
|
|
13108
|
-
hints.push("Command not found. Check if the tool is installed and in PATH.");
|
|
13109
|
-
}
|
|
13110
|
-
if (this.hasExcessiveRewrites()) {
|
|
13111
|
-
const file = this.getExcessiveRewriteFile();
|
|
13112
|
-
hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
|
|
13113
|
-
}
|
|
13114
|
-
return hints;
|
|
13533
|
+
title: `[${stepCount} ст.] ${title}`,
|
|
13534
|
+
steps: stepDescriptions.map((desc, i) => ({
|
|
13535
|
+
id: i + 1,
|
|
13536
|
+
description: desc,
|
|
13537
|
+
status: "pending"
|
|
13538
|
+
})),
|
|
13539
|
+
createdAt: new Date().toISOString(),
|
|
13540
|
+
baseDir
|
|
13541
|
+
};
|
|
13115
13542
|
}
|
|
13116
|
-
|
|
13117
|
-
const
|
|
13118
|
-
const
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
parcel: "vite",
|
|
13129
|
-
babel: "tsc",
|
|
13130
|
-
eslint: "biome",
|
|
13131
|
-
prettier: "biome"
|
|
13132
|
-
};
|
|
13133
|
-
for (const [failed, alt] of Object.entries(alternatives)) {
|
|
13134
|
-
if (tool.includes(failed) || error.includes(failed)) {
|
|
13135
|
-
return alt;
|
|
13136
|
-
}
|
|
13137
|
-
}
|
|
13543
|
+
static toPromptBlock(plan, currentStepIndex) {
|
|
13544
|
+
const date = plan.createdAt.slice(0, 10);
|
|
13545
|
+
const lines = [
|
|
13546
|
+
`[${plan.id}] ${plan.title}`,
|
|
13547
|
+
`Dir: ${plan.baseDir}`,
|
|
13548
|
+
`Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
|
|
13549
|
+
``
|
|
13550
|
+
];
|
|
13551
|
+
for (const step of plan.steps) {
|
|
13552
|
+
const icon = step.status === "done" ? "[x]" : step.status === "in_progress" ? "[*]" : step.status === "failed" ? "[!]" : step.status === "skipped" ? "[-]" : "[ ]";
|
|
13553
|
+
const note = step.note ? ` — ${step.note}` : "";
|
|
13554
|
+
lines.push(`${icon} ${step.id}. ${step.description}${note}`);
|
|
13138
13555
|
}
|
|
13139
|
-
return
|
|
13556
|
+
return lines.join(`
|
|
13557
|
+
`);
|
|
13140
13558
|
}
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13559
|
+
}
|
|
13560
|
+
|
|
13561
|
+
// src/modules/execution/tracker.ts
|
|
13562
|
+
class PlanTracker {
|
|
13563
|
+
plan;
|
|
13564
|
+
currentStepIndex = 0;
|
|
13565
|
+
constructor(plan) {
|
|
13566
|
+
this.plan = plan;
|
|
13144
13567
|
}
|
|
13145
|
-
|
|
13146
|
-
return this.
|
|
13568
|
+
getPlan() {
|
|
13569
|
+
return this.plan;
|
|
13147
13570
|
}
|
|
13148
|
-
|
|
13149
|
-
return
|
|
13571
|
+
getCurrentStepIndex() {
|
|
13572
|
+
return this.currentStepIndex;
|
|
13150
13573
|
}
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
if (count >= this.fileRewriteThreshold)
|
|
13154
|
-
return file;
|
|
13155
|
-
}
|
|
13156
|
-
return null;
|
|
13574
|
+
getCurrentStep() {
|
|
13575
|
+
return this.plan.steps[this.currentStepIndex];
|
|
13157
13576
|
}
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
return "";
|
|
13161
|
-
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
13162
|
-
return t("exec.repetitive_tool", {
|
|
13163
|
-
tool: last.name,
|
|
13164
|
-
count: this.repetitionThreshold
|
|
13165
|
-
});
|
|
13577
|
+
getStep(id) {
|
|
13578
|
+
return this.plan.steps.find((s) => s.id === id);
|
|
13166
13579
|
}
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
description: this.currentStepDescription,
|
|
13172
|
-
iterations: this.iterationsOnCurrentStep
|
|
13173
|
-
});
|
|
13174
|
-
}
|
|
13175
|
-
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
13176
|
-
if (errorTool) {
|
|
13177
|
-
return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
|
|
13178
|
-
}
|
|
13179
|
-
return "";
|
|
13580
|
+
updateStepStatus(id, status) {
|
|
13581
|
+
const step = this.getStep(id);
|
|
13582
|
+
if (step)
|
|
13583
|
+
step.status = status;
|
|
13180
13584
|
}
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
return t("exec.tool_errors_recovery", {
|
|
13192
|
-
tool: errorTool[0],
|
|
13193
|
-
count: errorTool[1]
|
|
13194
|
-
});
|
|
13195
|
-
}
|
|
13196
|
-
if (this.hasConsecutiveFailures()) {
|
|
13197
|
-
return t("exec.consecutive_failures_recovery", {
|
|
13198
|
-
count: this.consecutiveFailures
|
|
13199
|
-
});
|
|
13200
|
-
}
|
|
13201
|
-
if (this.hasRepetitiveToolCalls()) {
|
|
13202
|
-
return this.getRepetitiveToolMessage();
|
|
13585
|
+
addNote(id, note) {
|
|
13586
|
+
const step = this.getStep(id);
|
|
13587
|
+
if (step)
|
|
13588
|
+
step.note = note;
|
|
13589
|
+
}
|
|
13590
|
+
advance() {
|
|
13591
|
+
if (this.currentStepIndex < this.plan.steps.length - 1) {
|
|
13592
|
+
this.currentStepIndex++;
|
|
13593
|
+
this.plan.steps[this.currentStepIndex].status = "in_progress";
|
|
13594
|
+
return true;
|
|
13203
13595
|
}
|
|
13204
|
-
return
|
|
13596
|
+
return false;
|
|
13205
13597
|
}
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
const toolArgStr = JSON.stringify(call.arguments).toLowerCase();
|
|
13210
|
-
const pathInStep = (this.currentStepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
13211
|
-
const pathInCall = (toolArgStr.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
13212
|
-
const overlaps = pathInCall.some((p) => pathInStep.some((s) => p.includes(s) || s.includes(p)));
|
|
13213
|
-
if (!overlaps && pathInStep.length > 0 && pathInCall.length > 0) {
|
|
13214
|
-
return t("exec.off_track", {
|
|
13215
|
-
stepId: String(this.currentStepId),
|
|
13216
|
-
description: this.currentStepDescription,
|
|
13217
|
-
tool: toolName
|
|
13218
|
-
});
|
|
13598
|
+
syncCurrentStep() {
|
|
13599
|
+
while (this.currentStepIndex < this.plan.steps.length - 1 && (this.plan.steps[this.currentStepIndex].status === "done" || this.plan.steps[this.currentStepIndex].status === "skipped")) {
|
|
13600
|
+
this.currentStepIndex++;
|
|
13219
13601
|
}
|
|
13220
|
-
return null;
|
|
13221
13602
|
}
|
|
13222
|
-
|
|
13223
|
-
this.
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
this.
|
|
13603
|
+
isComplete() {
|
|
13604
|
+
return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
13605
|
+
}
|
|
13606
|
+
getProgressString() {
|
|
13607
|
+
const done = this.plan.steps.filter((s) => s.status === "done").length;
|
|
13608
|
+
const total = this.plan.steps.length;
|
|
13609
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
13610
|
+
const barWidth = 10;
|
|
13611
|
+
const filled = Math.round(done / total * barWidth);
|
|
13612
|
+
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
|
|
13613
|
+
return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
|
|
13614
|
+
}
|
|
13615
|
+
toPromptBlock() {
|
|
13616
|
+
return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
|
|
13227
13617
|
}
|
|
13228
13618
|
}
|
|
13229
|
-
var
|
|
13230
|
-
init_i18n();
|
|
13231
|
-
});
|
|
13619
|
+
var init_tracker = () => {};
|
|
13232
13620
|
|
|
13233
13621
|
// src/modules/execution/auditor.ts
|
|
13234
|
-
import { existsSync as
|
|
13235
|
-
import { resolve as
|
|
13622
|
+
import { existsSync as existsSync25 } from "fs";
|
|
13623
|
+
import { resolve as resolve14 } from "path";
|
|
13236
13624
|
|
|
13237
13625
|
class Auditor {
|
|
13238
13626
|
baseDir;
|
|
@@ -13246,8 +13634,8 @@ class Auditor {
|
|
|
13246
13634
|
const missingFiles = [];
|
|
13247
13635
|
const existingFiles = [];
|
|
13248
13636
|
for (const filePath of uniqueFiles) {
|
|
13249
|
-
const resolved =
|
|
13250
|
-
if (
|
|
13637
|
+
const resolved = resolve14(this.baseDir, filePath);
|
|
13638
|
+
if (existsSync25(resolved)) {
|
|
13251
13639
|
existingFiles.push(filePath);
|
|
13252
13640
|
} else {
|
|
13253
13641
|
missingFiles.push(filePath);
|
|
@@ -13282,14 +13670,14 @@ var init_auditor = __esm(() => {
|
|
|
13282
13670
|
});
|
|
13283
13671
|
|
|
13284
13672
|
// src/modules/execution/plan-persister.ts
|
|
13285
|
-
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as
|
|
13673
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as existsSync26 } from "fs";
|
|
13286
13674
|
import { join as join18 } from "path";
|
|
13287
13675
|
|
|
13288
13676
|
class PlanPersister {
|
|
13289
13677
|
filePath;
|
|
13290
13678
|
constructor(baseDir) {
|
|
13291
13679
|
const mmaDir = join18(baseDir, ".mma");
|
|
13292
|
-
if (!
|
|
13680
|
+
if (!existsSync26(mmaDir)) {
|
|
13293
13681
|
mkdirSync11(mmaDir, { recursive: true });
|
|
13294
13682
|
}
|
|
13295
13683
|
this.filePath = join18(mmaDir, "plan.json");
|
|
@@ -13306,7 +13694,7 @@ class PlanPersister {
|
|
|
13306
13694
|
writeFileSync9(this.filePath, JSON.stringify(file, null, 2), "utf-8");
|
|
13307
13695
|
}
|
|
13308
13696
|
load() {
|
|
13309
|
-
if (!
|
|
13697
|
+
if (!existsSync26(this.filePath))
|
|
13310
13698
|
return null;
|
|
13311
13699
|
try {
|
|
13312
13700
|
const raw = readFileSync14(this.filePath, "utf-8");
|
|
@@ -13323,7 +13711,7 @@ class PlanPersister {
|
|
|
13323
13711
|
}
|
|
13324
13712
|
}
|
|
13325
13713
|
clear() {
|
|
13326
|
-
if (
|
|
13714
|
+
if (existsSync26(this.filePath)) {
|
|
13327
13715
|
writeFileSync9(this.filePath, "", "utf-8");
|
|
13328
13716
|
}
|
|
13329
13717
|
}
|
|
@@ -13394,31 +13782,9 @@ var init_plan_coverage = __esm(() => {
|
|
|
13394
13782
|
]);
|
|
13395
13783
|
});
|
|
13396
13784
|
|
|
13397
|
-
// src/modules/skills/error-skill-map.ts
|
|
13398
|
-
function suggestSkill(errorOutput) {
|
|
13399
|
-
for (const { pattern, skill } of ERROR_SKILL_MAP) {
|
|
13400
|
-
if (pattern.test(errorOutput))
|
|
13401
|
-
return skill;
|
|
13402
|
-
}
|
|
13403
|
-
return null;
|
|
13404
|
-
}
|
|
13405
|
-
var ERROR_SKILL_MAP;
|
|
13406
|
-
var init_error_skill_map = __esm(() => {
|
|
13407
|
-
ERROR_SKILL_MAP = [
|
|
13408
|
-
{ pattern: /TS\d{4}:/i, skill: "typescript-expert" },
|
|
13409
|
-
{ pattern: /Cannot find name|No overload matches/i, skill: "typescript-type-expert" },
|
|
13410
|
-
{ pattern: /ts-node|tsx.*error/i, skill: "typescript-expert" },
|
|
13411
|
-
{ pattern: /Module not found|Cannot resolve module/i, skill: "typescript-expert" },
|
|
13412
|
-
{ pattern: /ECONNREFUSED|ETIMEDOUT|fetch failed/i, skill: "devops-expert" },
|
|
13413
|
-
{ pattern: /permission denied|EACCES/i, skill: "linux-server-expert" },
|
|
13414
|
-
{ pattern: /docker|container/i, skill: "docker-expert" },
|
|
13415
|
-
{ pattern: /jest|vitest|test.*fail/i, skill: "vitest-testing-expert" }
|
|
13416
|
-
];
|
|
13417
|
-
});
|
|
13418
|
-
|
|
13419
13785
|
// src/modules/execution/module.ts
|
|
13420
|
-
import { existsSync as
|
|
13421
|
-
import { resolve as
|
|
13786
|
+
import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
|
|
13787
|
+
import { resolve as resolve15 } from "path";
|
|
13422
13788
|
|
|
13423
13789
|
class ExecutionModule {
|
|
13424
13790
|
name = "execution";
|
|
@@ -13447,10 +13813,7 @@ class ExecutionModule {
|
|
|
13447
13813
|
if (!plan)
|
|
13448
13814
|
return false;
|
|
13449
13815
|
this.tracker = new PlanTracker(plan);
|
|
13450
|
-
|
|
13451
|
-
if (!this.tracker.advance())
|
|
13452
|
-
break;
|
|
13453
|
-
}
|
|
13816
|
+
this.tracker.syncCurrentStep();
|
|
13454
13817
|
return true;
|
|
13455
13818
|
}
|
|
13456
13819
|
getTracker() {
|
|
@@ -13562,9 +13925,11 @@ ${display}`,
|
|
|
13562
13925
|
this.tracker.updateStepStatus(Number(args.step), args.status || "done");
|
|
13563
13926
|
if (args.note)
|
|
13564
13927
|
this.tracker.addNote(Number(args.step), String(args.note));
|
|
13928
|
+
this.tracker.syncCurrentStep();
|
|
13565
13929
|
this.persister.save(this.tracker.getPlan());
|
|
13566
13930
|
const progress = this.tracker.getProgressString();
|
|
13567
13931
|
const display = this.tracker.toPromptBlock();
|
|
13932
|
+
_ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
13568
13933
|
return {
|
|
13569
13934
|
success: true,
|
|
13570
13935
|
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
|
|
@@ -13683,6 +14048,7 @@ Sub-tasks: ${note}`
|
|
|
13683
14048
|
const stuckReason = this.stuckDetector.getStuckReason();
|
|
13684
14049
|
if (stuckReason) {
|
|
13685
14050
|
ctx.logger?.warn(stuckReason);
|
|
14051
|
+
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
13686
14052
|
}
|
|
13687
14053
|
if (this.stuckDetector.isStuck() || this.stuckDetector.hasRepetitiveToolCalls()) {
|
|
13688
14054
|
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
@@ -13800,7 +14166,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
13800
14166
|
}
|
|
13801
14167
|
}
|
|
13802
14168
|
}
|
|
13803
|
-
this.advancePlanIfStepComplete(ctx.contextManager);
|
|
14169
|
+
this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
13804
14170
|
}
|
|
13805
14171
|
}
|
|
13806
14172
|
};
|
|
@@ -13840,7 +14206,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
13840
14206
|
tool: call.name
|
|
13841
14207
|
});
|
|
13842
14208
|
}
|
|
13843
|
-
advancePlanIfStepComplete(contextManager) {
|
|
14209
|
+
advancePlanIfStepComplete(contextManager, sessionLog) {
|
|
13844
14210
|
const step = this.tracker?.getCurrentStep();
|
|
13845
14211
|
if (!step)
|
|
13846
14212
|
return;
|
|
@@ -13860,7 +14226,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
13860
14226
|
"poetry.lock",
|
|
13861
14227
|
"requirements.txt"
|
|
13862
14228
|
];
|
|
13863
|
-
const hasLockFile = lockFiles.some((f) =>
|
|
14229
|
+
const hasLockFile = lockFiles.some((f) => existsSync27(resolve15(this.baseDir, f)));
|
|
13864
14230
|
if (!hasLockFile) {
|
|
13865
14231
|
if (contextManager) {
|
|
13866
14232
|
contextManager.addMessage({
|
|
@@ -13873,13 +14239,13 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
13873
14239
|
}
|
|
13874
14240
|
if (stepPaths.length === 0)
|
|
13875
14241
|
return;
|
|
13876
|
-
const allExist = stepPaths.every((p) =>
|
|
14242
|
+
const allExist = stepPaths.every((p) => existsSync27(resolve15(this.baseDir, p)));
|
|
13877
14243
|
if (!allExist)
|
|
13878
14244
|
return;
|
|
13879
14245
|
const emptyFiles = [];
|
|
13880
14246
|
for (const p of stepPaths) {
|
|
13881
14247
|
try {
|
|
13882
|
-
const content = readFileSync15(
|
|
14248
|
+
const content = readFileSync15(resolve15(this.baseDir, p), "utf-8");
|
|
13883
14249
|
if (content.trim().length < 10) {
|
|
13884
14250
|
emptyFiles.push(p);
|
|
13885
14251
|
}
|
|
@@ -13896,6 +14262,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
13896
14262
|
const hadNext = this.tracker?.advance() ?? false;
|
|
13897
14263
|
if (this.tracker) {
|
|
13898
14264
|
this.persister.save(this.tracker.getPlan());
|
|
14265
|
+
sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
13899
14266
|
}
|
|
13900
14267
|
if (contextManager) {
|
|
13901
14268
|
const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
|
|
@@ -13934,7 +14301,7 @@ var init_module = __esm(() => {
|
|
|
13934
14301
|
});
|
|
13935
14302
|
|
|
13936
14303
|
// src/modules/security/session-encryption.ts
|
|
13937
|
-
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as
|
|
14304
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync28, readdirSync as readdirSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
13938
14305
|
import { join as join19 } from "path";
|
|
13939
14306
|
import { homedir as homedir8 } from "os";
|
|
13940
14307
|
|
|
@@ -14017,7 +14384,7 @@ class SessionFileEncryptor {
|
|
|
14017
14384
|
const files = readdirSync6(sessionDir);
|
|
14018
14385
|
for (const file of files) {
|
|
14019
14386
|
const filePath = join19(sessionDir, file);
|
|
14020
|
-
if (
|
|
14387
|
+
if (existsSync28(filePath) && !file.endsWith(".enc")) {
|
|
14021
14388
|
try {
|
|
14022
14389
|
const content = readFileSync16(filePath, "utf8");
|
|
14023
14390
|
const encrypted = this.encryptFileContent(content);
|
|
@@ -14058,7 +14425,7 @@ var init_session_encryption = __esm(() => {
|
|
|
14058
14425
|
|
|
14059
14426
|
// src/modules/session/store.ts
|
|
14060
14427
|
import {
|
|
14061
|
-
existsSync as
|
|
14428
|
+
existsSync as existsSync29,
|
|
14062
14429
|
mkdirSync as mkdirSync12,
|
|
14063
14430
|
readdirSync as readdirSync7,
|
|
14064
14431
|
readFileSync as readFileSync17,
|
|
@@ -14104,7 +14471,7 @@ class SessionStore {
|
|
|
14104
14471
|
return join20(this.sessionDir(id), "session.jsonl");
|
|
14105
14472
|
}
|
|
14106
14473
|
sessionExists(id) {
|
|
14107
|
-
return
|
|
14474
|
+
return existsSync29(this.metaPath(id));
|
|
14108
14475
|
}
|
|
14109
14476
|
saveMeta(id, meta) {
|
|
14110
14477
|
const dir = this.sessionDir(id);
|
|
@@ -14118,7 +14485,7 @@ class SessionStore {
|
|
|
14118
14485
|
}
|
|
14119
14486
|
loadMeta(id) {
|
|
14120
14487
|
const path = this.metaPath(id);
|
|
14121
|
-
if (!
|
|
14488
|
+
if (!existsSync29(path))
|
|
14122
14489
|
return null;
|
|
14123
14490
|
try {
|
|
14124
14491
|
const raw = readFileSync17(path, "utf-8");
|
|
@@ -14148,7 +14515,7 @@ class SessionStore {
|
|
|
14148
14515
|
}
|
|
14149
14516
|
loadHistory(id) {
|
|
14150
14517
|
const path = this.historyPath(id);
|
|
14151
|
-
if (!
|
|
14518
|
+
if (!existsSync29(path))
|
|
14152
14519
|
return [];
|
|
14153
14520
|
try {
|
|
14154
14521
|
const raw = readFileSync17(path, "utf-8");
|
|
@@ -14177,7 +14544,7 @@ class SessionStore {
|
|
|
14177
14544
|
}
|
|
14178
14545
|
loadSessionLog(id) {
|
|
14179
14546
|
const path = this.sessionLogPath(id);
|
|
14180
|
-
if (!
|
|
14547
|
+
if (!existsSync29(path))
|
|
14181
14548
|
return [];
|
|
14182
14549
|
try {
|
|
14183
14550
|
const raw = readFileSync17(path, "utf-8");
|
|
@@ -14193,7 +14560,7 @@ class SessionStore {
|
|
|
14193
14560
|
}
|
|
14194
14561
|
}
|
|
14195
14562
|
listSessions() {
|
|
14196
|
-
if (!
|
|
14563
|
+
if (!existsSync29(this.baseDir))
|
|
14197
14564
|
return [];
|
|
14198
14565
|
const entries = readdirSync7(this.baseDir, { withFileTypes: true });
|
|
14199
14566
|
const sessions = [];
|
|
@@ -14209,7 +14576,7 @@ class SessionStore {
|
|
|
14209
14576
|
}
|
|
14210
14577
|
deleteSession(id) {
|
|
14211
14578
|
const dir = this.sessionDir(id);
|
|
14212
|
-
if (
|
|
14579
|
+
if (existsSync29(dir)) {
|
|
14213
14580
|
rmSync(dir, { recursive: true, force: true });
|
|
14214
14581
|
}
|
|
14215
14582
|
}
|
|
@@ -14221,7 +14588,7 @@ class SessionStore {
|
|
|
14221
14588
|
const updatedAt = new Date(session2.updatedAt);
|
|
14222
14589
|
if (updatedAt < thirtyDaysAgo) {
|
|
14223
14590
|
const historyPath = this.historyPath(session2.id);
|
|
14224
|
-
if (
|
|
14591
|
+
if (existsSync29(historyPath)) {
|
|
14225
14592
|
const content = readFileSync17(historyPath, "utf-8");
|
|
14226
14593
|
const compressed = gzipSync(content);
|
|
14227
14594
|
const gzPath = join20(this.baseDir, `${session2.id}.jsonl.gz`);
|
|
@@ -14431,9 +14798,9 @@ class ProfileCompressor {
|
|
|
14431
14798
|
}
|
|
14432
14799
|
|
|
14433
14800
|
// src/modules/user-profile/profile.ts
|
|
14434
|
-
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as
|
|
14801
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync30, mkdirSync as mkdirSync13 } from "fs";
|
|
14435
14802
|
import { join as join21 } from "path";
|
|
14436
|
-
import { homedir as homedir9, hostname, platform as
|
|
14803
|
+
import { homedir as homedir9, hostname, platform as platform3, type } from "os";
|
|
14437
14804
|
import { env } from "process";
|
|
14438
14805
|
|
|
14439
14806
|
class UserProfile {
|
|
@@ -14445,7 +14812,7 @@ class UserProfile {
|
|
|
14445
14812
|
}
|
|
14446
14813
|
collect() {
|
|
14447
14814
|
this.info = {
|
|
14448
|
-
platform:
|
|
14815
|
+
platform: platform3(),
|
|
14449
14816
|
os: `${type()} ${hostname()}`,
|
|
14450
14817
|
hostname: hostname(),
|
|
14451
14818
|
shell: env.SHELL || env.ComSpec || "unknown",
|
|
@@ -14456,14 +14823,14 @@ class UserProfile {
|
|
|
14456
14823
|
return this.info;
|
|
14457
14824
|
}
|
|
14458
14825
|
save() {
|
|
14459
|
-
if (!
|
|
14826
|
+
if (!existsSync30(this.profileDir)) {
|
|
14460
14827
|
mkdirSync13(this.profileDir, { recursive: true });
|
|
14461
14828
|
}
|
|
14462
14829
|
writeFileSync12(join21(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
|
|
14463
14830
|
}
|
|
14464
14831
|
load() {
|
|
14465
14832
|
const path = join21(this.profileDir, "profile.json");
|
|
14466
|
-
if (!
|
|
14833
|
+
if (!existsSync30(path))
|
|
14467
14834
|
return null;
|
|
14468
14835
|
try {
|
|
14469
14836
|
const data = JSON.parse(readFileSync18(path, "utf-8"));
|
|
@@ -14501,12 +14868,12 @@ class UserProfile {
|
|
|
14501
14868
|
var init_profile = () => {};
|
|
14502
14869
|
|
|
14503
14870
|
// src/modules/skills/loader.ts
|
|
14504
|
-
import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as
|
|
14871
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync31, statSync as statSync5 } from "fs";
|
|
14505
14872
|
import { join as join22 } from "path";
|
|
14506
14873
|
|
|
14507
14874
|
class SkillsLoader {
|
|
14508
14875
|
loadFromDir(dirPath) {
|
|
14509
|
-
if (!
|
|
14876
|
+
if (!existsSync31(dirPath))
|
|
14510
14877
|
return [];
|
|
14511
14878
|
const skills = [];
|
|
14512
14879
|
this.scanDir(dirPath, skills);
|
|
@@ -14795,8 +15162,8 @@ var init_browser2 = __esm(() => {
|
|
|
14795
15162
|
});
|
|
14796
15163
|
|
|
14797
15164
|
// src/modules/lsp/client.ts
|
|
14798
|
-
import { spawn as
|
|
14799
|
-
import { resolve as
|
|
15165
|
+
import { spawn as spawn5, execSync as execSync2 } from "child_process";
|
|
15166
|
+
import { resolve as resolve16 } from "path";
|
|
14800
15167
|
|
|
14801
15168
|
class LspClient {
|
|
14802
15169
|
process = null;
|
|
@@ -14812,7 +15179,7 @@ class LspClient {
|
|
|
14812
15179
|
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
14813
15180
|
try {
|
|
14814
15181
|
await this.startServer(config, baseDir);
|
|
14815
|
-
const rootUri = this.pathToUri(
|
|
15182
|
+
const rootUri = this.pathToUri(resolve16(baseDir));
|
|
14816
15183
|
const initResult = await this.sendRequest("initialize", {
|
|
14817
15184
|
processId: process.pid,
|
|
14818
15185
|
rootUri,
|
|
@@ -14821,11 +15188,11 @@ class LspClient {
|
|
|
14821
15188
|
}, timeout);
|
|
14822
15189
|
this.initialized = true;
|
|
14823
15190
|
this.sendNotification("initialized", {});
|
|
14824
|
-
const uri = this.pathToUri(
|
|
15191
|
+
const uri = this.pathToUri(resolve16(filePath));
|
|
14825
15192
|
const fs2 = await import("fs");
|
|
14826
15193
|
const content = fs2.readFileSync(filePath, "utf-8");
|
|
14827
|
-
const diagPromise = new Promise((
|
|
14828
|
-
this.diagnosticsResolve =
|
|
15194
|
+
const diagPromise = new Promise((resolve17) => {
|
|
15195
|
+
this.diagnosticsResolve = resolve17;
|
|
14829
15196
|
this.diagnostics = [];
|
|
14830
15197
|
this.diagnosticsTimer = setTimeout(() => {
|
|
14831
15198
|
if (this.diagnosticsResolve) {
|
|
@@ -14852,14 +15219,14 @@ class LspClient {
|
|
|
14852
15219
|
async startServer(config, baseDir) {
|
|
14853
15220
|
if (config.autoInstall === false) {
|
|
14854
15221
|
try {
|
|
14855
|
-
|
|
15222
|
+
execSync2(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
|
|
14856
15223
|
} catch {
|
|
14857
15224
|
throw new Error(`${config.command} not found in PATH`);
|
|
14858
15225
|
}
|
|
14859
15226
|
}
|
|
14860
|
-
return new Promise((
|
|
15227
|
+
return new Promise((resolve17, reject) => {
|
|
14861
15228
|
const args = config.args ?? [];
|
|
14862
|
-
const proc =
|
|
15229
|
+
const proc = spawn5(config.command, args, {
|
|
14863
15230
|
stdio: ["pipe", "pipe", "pipe"],
|
|
14864
15231
|
env: { ...process.env, ...config.env },
|
|
14865
15232
|
cwd: baseDir
|
|
@@ -14870,7 +15237,7 @@ class LspClient {
|
|
|
14870
15237
|
});
|
|
14871
15238
|
proc.stderr.on("data", () => {});
|
|
14872
15239
|
proc.once("spawn", () => {
|
|
14873
|
-
|
|
15240
|
+
resolve17();
|
|
14874
15241
|
});
|
|
14875
15242
|
this.process = proc;
|
|
14876
15243
|
setTimeout(() => {
|
|
@@ -14942,9 +15309,9 @@ class LspClient {
|
|
|
14942
15309
|
}
|
|
14943
15310
|
}
|
|
14944
15311
|
sendRequest(method, params, timeout) {
|
|
14945
|
-
return new Promise((
|
|
15312
|
+
return new Promise((resolve17, reject) => {
|
|
14946
15313
|
const id = ++this.requestId;
|
|
14947
|
-
this.pending.set(id, { resolve:
|
|
15314
|
+
this.pending.set(id, { resolve: resolve17, reject });
|
|
14948
15315
|
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
14949
15316
|
this.write(message);
|
|
14950
15317
|
setTimeout(() => {
|
|
@@ -15022,8 +15389,8 @@ var DEFAULT_TIMEOUT = 15000;
|
|
|
15022
15389
|
var init_client2 = () => {};
|
|
15023
15390
|
|
|
15024
15391
|
// src/modules/lsp/module.ts
|
|
15025
|
-
import { existsSync as
|
|
15026
|
-
import { resolve as
|
|
15392
|
+
import { existsSync as existsSync32 } from "fs";
|
|
15393
|
+
import { resolve as resolve17 } from "path";
|
|
15027
15394
|
|
|
15028
15395
|
class LspModule {
|
|
15029
15396
|
name = "lsp";
|
|
@@ -15047,8 +15414,8 @@ class LspModule {
|
|
|
15047
15414
|
const filePath = String(call.arguments.path ?? "");
|
|
15048
15415
|
if (!filePath)
|
|
15049
15416
|
return;
|
|
15050
|
-
const fullPath =
|
|
15051
|
-
if (!
|
|
15417
|
+
const fullPath = resolve17(_ctx.baseDir, filePath);
|
|
15418
|
+
if (!existsSync32(fullPath))
|
|
15052
15419
|
return;
|
|
15053
15420
|
const serverConfig = getServerForFile(fullPath, self.config);
|
|
15054
15421
|
if (!serverConfig)
|
|
@@ -15106,7 +15473,7 @@ var init_lsp = __esm(() => {
|
|
|
15106
15473
|
});
|
|
15107
15474
|
|
|
15108
15475
|
// src/modules/indexer/walker.ts
|
|
15109
|
-
import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as
|
|
15476
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync33, watch } from "fs";
|
|
15110
15477
|
import { join as join23, relative, extname as extname5 } from "path";
|
|
15111
15478
|
|
|
15112
15479
|
class Indexer {
|
|
@@ -15134,7 +15501,7 @@ class Indexer {
|
|
|
15134
15501
|
let totalSize = 0;
|
|
15135
15502
|
let count = 0;
|
|
15136
15503
|
const walkDir = (dir) => {
|
|
15137
|
-
if (!
|
|
15504
|
+
if (!existsSync33(dir))
|
|
15138
15505
|
return;
|
|
15139
15506
|
let entries;
|
|
15140
15507
|
try {
|
|
@@ -15208,7 +15575,7 @@ var init_walker = __esm(() => {
|
|
|
15208
15575
|
});
|
|
15209
15576
|
|
|
15210
15577
|
// src/modules/indexer/cache.ts
|
|
15211
|
-
import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as
|
|
15578
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync34, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
|
|
15212
15579
|
import { join as join24 } from "path";
|
|
15213
15580
|
|
|
15214
15581
|
class IndexCache {
|
|
@@ -15220,7 +15587,7 @@ class IndexCache {
|
|
|
15220
15587
|
load() {
|
|
15221
15588
|
if (this.cache)
|
|
15222
15589
|
return this.cache;
|
|
15223
|
-
if (!
|
|
15590
|
+
if (!existsSync34(this.cachePath))
|
|
15224
15591
|
return null;
|
|
15225
15592
|
try {
|
|
15226
15593
|
this.cache = JSON.parse(readFileSync21(this.cachePath, "utf-8"));
|
|
@@ -15232,13 +15599,13 @@ class IndexCache {
|
|
|
15232
15599
|
save(result) {
|
|
15233
15600
|
this.cache = result;
|
|
15234
15601
|
const dir = join24(this.cachePath, "..");
|
|
15235
|
-
if (!
|
|
15602
|
+
if (!existsSync34(dir))
|
|
15236
15603
|
mkdirSync14(dir, { recursive: true });
|
|
15237
15604
|
writeFileSync13(this.cachePath, JSON.stringify(result), "utf-8");
|
|
15238
15605
|
}
|
|
15239
15606
|
invalidate() {
|
|
15240
15607
|
this.cache = null;
|
|
15241
|
-
if (
|
|
15608
|
+
if (existsSync34(this.cachePath)) {
|
|
15242
15609
|
try {
|
|
15243
15610
|
rmSync2(this.cachePath);
|
|
15244
15611
|
} catch {}
|
|
@@ -15649,8 +16016,8 @@ __export(exports_bootstrap, {
|
|
|
15649
16016
|
bootstrap: () => bootstrap
|
|
15650
16017
|
});
|
|
15651
16018
|
import { homedir as homedir11 } from "os";
|
|
15652
|
-
import { join as join26, resolve as
|
|
15653
|
-
import { existsSync as
|
|
16019
|
+
import { join as join26, resolve as resolve18 } from "path";
|
|
16020
|
+
import { existsSync as existsSync35, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
|
|
15654
16021
|
function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
15655
16022
|
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
15656
16023
|
const isWin = profileCompressed.toLowerCase().includes("win32");
|
|
@@ -15669,9 +16036,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
15669
16036
|
`- DRY: Do not duplicate code, logic, or configuration — reuse existing utilities and patterns.`
|
|
15670
16037
|
];
|
|
15671
16038
|
if (isWin) {
|
|
15672
|
-
lines.push(``, `Windows environment — use Windows-compatible commands:`, `- Use "dir" instead of "ls". Use "dir /b" for bare listing.`, `- Use "type"
|
|
16039
|
+
lines.push(``, `Windows environment — use Windows-compatible commands (shell is cmd.exe, not PowerShell):`, `- Use "dir" instead of "ls". Use "dir /b" for bare listing. For file listings prefer the list_dir tool.`, `- Use "type" instead of "cat". For reading files prefer the read_file tool.`, `- Use "cd" instead of "pwd". Use "echo %cd%" to print working directory.`, `- Use "copy" instead of "cp", "move" instead of "mv", "del" instead of "rm".`, `- Do not use "mkdir -p" — Windows mkdir creates intermediate dirs by default. Use the create_dir tool instead.`, `- Do not use "head" or "tail" — they are Unix commands. Use the read_file tool with offset/limit instead.`, `- Do not use "grep" — use the grep tool instead. Do not use PowerShell cmdlets (Get-Content, Select-Object, cat) in bash — the shell is cmd.exe.`, `- Use forward slashes (/) or escaped backslashes (\\\\) in file paths.`, `- Your working directory is: ${baseDir}`);
|
|
15673
16040
|
}
|
|
15674
|
-
lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Prefer workdir over "cd dir && cmd" chaining.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `-
|
|
16041
|
+
lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Prefer workdir over "cd dir && cmd" chaining.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `- Dev servers, watchers and other long-running processes: pass "background: true" to get a process id immediately. Without it, any command still running after a few seconds is automatically moved to the background — check output with process_log.`);
|
|
15675
16042
|
lines.push(``, `=== DEVELOPMENT RULES — follow these strictly ===`, ``, `1. DEPENDENCIES FIRST: Before writing any source code, ALWAYS install project dependencies (e.g., "npm install", "pip install -r requirements.txt", "cargo build", "go mod tidy"). Verify the package manager's lock file or dependency directory exists. Never write code that imports/uses packages that aren't installed yet.`, `2. TOOLKIT/FWK FIRST: If the task specifies a framework or UI library, initialize and configure it BEFORE writing application code. Run its project init command first, then add components/modules. Never write your own version of what the framework already provides.`, `3. ONE STEP AT A TIME: Follow the plan sequentially. Complete step N before starting step N+1. When a step is done: verify the deliverables exist and have real content (not empty), then call "plan update step=N status=done". Do not redo completed work.`, `4. VERIFY YOUR WORK: After creating/modifying files, verify they exist on disk. After installing dependencies, verify the package manager completed successfully. After any command, check its output for errors. Don't assume operations succeeded.`, `5. NO PREMATURE WORK: Do not create files for future steps. Do not add imports/references to packages or modules that haven't been installed yet. Do not reference files or components that don't exist yet. Build incrementally — one layer at a time.`, `6. WHEN STUCK: If a command fails 2+ times, STOP and try a different approach. Write files directly instead of using commands. Ask the user for help. Never repeat the same failing command more than twice.`, ``, `=== PLAN QUALITY RULES — your plan MUST follow these ===`, ``, `- Each step must describe CONCRETE deliverables: exact filenames with paths, exact packages to install, exact CLI commands to run. Avoid vague steps — be specific.`, `- A step like "Настройка проекта" or "Setup the project" is too vague — describe what exactly needs to be configured or set up.`, `- A step like "Создать src/components/Header.tsx с навигацией и логотипом, добавить в src/App.tsx импорт <Header />" is GOOD.`, `- Include file extensions (.tsx, .css, .json) and directory paths. Every step must mention at least one file or command.`, `- The plan must cover EVERYTHING needed: init → deps → framework setup → code → verification.`, `- Number of steps: 5-8 for a typical task. Too few means you're being vague. Too many means you're over-splitting.`);
|
|
15676
16043
|
if (config.autoPlan) {
|
|
15677
16044
|
lines.push(``, `Plan rule (MANDATORY): For ANY task that requires creating files, installing packages, or multiple actions — you MUST create a plan using the "plan" tool BEFORE starting work. Each step must describe a concrete deliverable (specific files to create, packages to install, commands to run). Do not combine unrelated work into one step.`);
|
|
@@ -15728,7 +16095,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
15728
16095
|
retry: config.retry,
|
|
15729
16096
|
rateLimits: config.security?.rateLimits
|
|
15730
16097
|
});
|
|
15731
|
-
const baseDir = projectDir ?
|
|
16098
|
+
const baseDir = projectDir ? resolve18(projectDir) : process.cwd();
|
|
15732
16099
|
const projectMapCacheDir = join26(baseDir, ".mma");
|
|
15733
16100
|
const indexerModule = new IndexerModule({
|
|
15734
16101
|
baseDir,
|
|
@@ -15757,7 +16124,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
15757
16124
|
estimatedTokens: 250
|
|
15758
16125
|
};
|
|
15759
16126
|
const agentsMdGlobal = join26(dir, "AGENTS.md");
|
|
15760
|
-
if (!
|
|
16127
|
+
if (!existsSync35(agentsMdGlobal)) {
|
|
15761
16128
|
writeFileSync14(agentsMdGlobal, "", "utf-8");
|
|
15762
16129
|
}
|
|
15763
16130
|
const sessionDir = join26(dir, "sessions");
|
|
@@ -15797,12 +16164,23 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
15797
16164
|
timestamp: new Date().toISOString()
|
|
15798
16165
|
});
|
|
15799
16166
|
}
|
|
16167
|
+
},
|
|
16168
|
+
sessionLog: {
|
|
16169
|
+
plan: (event, detail, iteration) => {
|
|
16170
|
+
sessionManager.appendLog({
|
|
16171
|
+
ts: new Date().toISOString(),
|
|
16172
|
+
type: "plan",
|
|
16173
|
+
tool: event,
|
|
16174
|
+
content: detail,
|
|
16175
|
+
...iteration !== undefined ? { iteration } : {}
|
|
16176
|
+
});
|
|
16177
|
+
}
|
|
15800
16178
|
}
|
|
15801
16179
|
};
|
|
15802
16180
|
const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
|
|
15803
16181
|
toolCtx.llmProvider = llmProvider;
|
|
15804
16182
|
toolCtx.toolExecutor = toolExecutor;
|
|
15805
|
-
const hallucinationDetector = new HallucinationDetector;
|
|
16183
|
+
const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
|
|
15806
16184
|
const moduleRegistry = new ModuleRegistry;
|
|
15807
16185
|
const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
|
|
15808
16186
|
execModule.restorePlan();
|
|
@@ -15888,7 +16266,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
15888
16266
|
join26(dir, "AGENTS.md")
|
|
15889
16267
|
];
|
|
15890
16268
|
for (const p of agentsMdCandidates) {
|
|
15891
|
-
if (
|
|
16269
|
+
if (existsSync35(p)) {
|
|
15892
16270
|
const content = readFileSync22(p, "utf-8").trim();
|
|
15893
16271
|
if (content) {
|
|
15894
16272
|
agentsMdBlocks.push({
|
|
@@ -16483,6 +16861,10 @@ function box(lines, opts = {}) {
|
|
|
16483
16861
|
out.push(pc.dim(`└${"─".repeat(width - 2)}┘`));
|
|
16484
16862
|
return out;
|
|
16485
16863
|
}
|
|
16864
|
+
function divider(width) {
|
|
16865
|
+
const w = Math.min(width ?? getTerminalWidth(), 60);
|
|
16866
|
+
return pc.dim("─".repeat(w));
|
|
16867
|
+
}
|
|
16486
16868
|
var init_box = __esm(() => {
|
|
16487
16869
|
init_string_width();
|
|
16488
16870
|
init_colors();
|
|
@@ -16506,10 +16888,10 @@ function menuList(items) {
|
|
|
16506
16888
|
console.log(l);
|
|
16507
16889
|
}
|
|
16508
16890
|
function ask(rl, question, defaultValue) {
|
|
16509
|
-
return new Promise((
|
|
16891
|
+
return new Promise((resolve19) => {
|
|
16510
16892
|
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
16511
16893
|
rl.question(prompt, (answer) => {
|
|
16512
|
-
|
|
16894
|
+
resolve19(answer.trim() || defaultValue || "");
|
|
16513
16895
|
});
|
|
16514
16896
|
});
|
|
16515
16897
|
}
|
|
@@ -16728,12 +17110,12 @@ __export(exports_manifest, {
|
|
|
16728
17110
|
getCertMark: () => getCertMark,
|
|
16729
17111
|
MANIFEST_PATH: () => MANIFEST_PATH
|
|
16730
17112
|
});
|
|
16731
|
-
import { existsSync as
|
|
17113
|
+
import { existsSync as existsSync36, readFileSync as readFileSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
|
|
16732
17114
|
import { homedir as homedir13 } from "os";
|
|
16733
17115
|
import { join as join28 } from "path";
|
|
16734
17116
|
function readManifest(path = MANIFEST_PATH) {
|
|
16735
17117
|
try {
|
|
16736
|
-
if (
|
|
17118
|
+
if (existsSync36(path)) {
|
|
16737
17119
|
const raw = JSON.parse(readFileSync23(path, "utf-8"));
|
|
16738
17120
|
return { version: 1, certifications: raw.certifications ?? [] };
|
|
16739
17121
|
}
|
|
@@ -23899,7 +24281,7 @@ var init_scenarios = __esm(() => {
|
|
|
23899
24281
|
});
|
|
23900
24282
|
|
|
23901
24283
|
// src/modules/certification/loader.ts
|
|
23902
|
-
import { existsSync as
|
|
24284
|
+
import { existsSync as existsSync37, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
|
|
23903
24285
|
import { join as join29 } from "path";
|
|
23904
24286
|
function validateScenario(s) {
|
|
23905
24287
|
const errors2 = [];
|
|
@@ -23949,7 +24331,7 @@ function loadScenarios(userDir) {
|
|
|
23949
24331
|
else
|
|
23950
24332
|
scenarios.push(s);
|
|
23951
24333
|
}
|
|
23952
|
-
if (userDir &&
|
|
24334
|
+
if (userDir && existsSync37(userDir)) {
|
|
23953
24335
|
for (const file of readdirSync10(userDir)) {
|
|
23954
24336
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
|
|
23955
24337
|
continue;
|
|
@@ -24007,7 +24389,7 @@ var init_loader3 = __esm(() => {
|
|
|
24007
24389
|
});
|
|
24008
24390
|
|
|
24009
24391
|
// src/modules/certification/fact-checker.ts
|
|
24010
|
-
import { existsSync as
|
|
24392
|
+
import { existsSync as existsSync38, readFileSync as readFileSync25, statSync as statSync7 } from "fs";
|
|
24011
24393
|
import { join as join30 } from "path";
|
|
24012
24394
|
function checkSandbox(sandboxDir, checks, exitCode, output) {
|
|
24013
24395
|
const failures = [];
|
|
@@ -24027,7 +24409,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
|
|
|
24027
24409
|
case "fileExists":
|
|
24028
24410
|
return isFile(join30(sandboxDir, check.path));
|
|
24029
24411
|
case "fileNotExists":
|
|
24030
|
-
return !
|
|
24412
|
+
return !existsSync38(join30(sandboxDir, check.path));
|
|
24031
24413
|
case "dirExists":
|
|
24032
24414
|
return isDir(join30(sandboxDir, check.path));
|
|
24033
24415
|
case "fileContent": {
|
|
@@ -24053,14 +24435,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
|
|
|
24053
24435
|
}
|
|
24054
24436
|
function isFile(p) {
|
|
24055
24437
|
try {
|
|
24056
|
-
return
|
|
24438
|
+
return existsSync38(p) && statSync7(p).isFile();
|
|
24057
24439
|
} catch {
|
|
24058
24440
|
return false;
|
|
24059
24441
|
}
|
|
24060
24442
|
}
|
|
24061
24443
|
function isDir(p) {
|
|
24062
24444
|
try {
|
|
24063
|
-
return
|
|
24445
|
+
return existsSync38(p) && statSync7(p).isDirectory();
|
|
24064
24446
|
} catch {
|
|
24065
24447
|
return false;
|
|
24066
24448
|
}
|
|
@@ -24090,10 +24472,10 @@ function describe(check) {
|
|
|
24090
24472
|
var init_fact_checker = () => {};
|
|
24091
24473
|
|
|
24092
24474
|
// src/modules/certification/runner.ts
|
|
24093
|
-
import { spawn as
|
|
24094
|
-
import { existsSync as
|
|
24095
|
-
import { platform as
|
|
24096
|
-
import { join as join31, resolve as
|
|
24475
|
+
import { spawn as spawn6 } from "child_process";
|
|
24476
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync16, rmSync as rmSync3, cpSync as cpSync2 } from "fs";
|
|
24477
|
+
import { platform as platform4 } from "os";
|
|
24478
|
+
import { join as join31, resolve as resolve19, dirname as dirname9 } from "path";
|
|
24097
24479
|
async function runScenario(scenario, opts) {
|
|
24098
24480
|
if (scenario.mode === "skip") {
|
|
24099
24481
|
return {
|
|
@@ -24174,7 +24556,7 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
|
|
|
24174
24556
|
mkdirSync16(sandbox, { recursive: true });
|
|
24175
24557
|
for (const f of scenario.fixtures ?? []) {
|
|
24176
24558
|
const src = join31(mmaRoot, f.source);
|
|
24177
|
-
if (!
|
|
24559
|
+
if (!existsSync39(src)) {
|
|
24178
24560
|
throw new Error(`fixture missing: ${f.source}`);
|
|
24179
24561
|
}
|
|
24180
24562
|
const dest = join31(sandbox, f.dest);
|
|
@@ -24184,27 +24566,27 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
|
|
|
24184
24566
|
}
|
|
24185
24567
|
function resolveMmaEntry(mmaRoot) {
|
|
24186
24568
|
const dev = join31(mmaRoot, "src", "cli", "main.ts");
|
|
24187
|
-
if (
|
|
24569
|
+
if (existsSync39(dev))
|
|
24188
24570
|
return dev;
|
|
24189
24571
|
return join31(mmaRoot, "dist", "main.js");
|
|
24190
24572
|
}
|
|
24191
24573
|
function findMmaRoot(fromDir) {
|
|
24192
24574
|
const candidates = [
|
|
24193
|
-
|
|
24194
|
-
|
|
24575
|
+
resolve19(fromDir, "..", "..", ".."),
|
|
24576
|
+
resolve19(fromDir, "..")
|
|
24195
24577
|
];
|
|
24196
24578
|
for (const c of candidates) {
|
|
24197
|
-
if (
|
|
24579
|
+
if (existsSync39(join31(c, "package.json")))
|
|
24198
24580
|
return c;
|
|
24199
24581
|
}
|
|
24200
24582
|
return process.cwd();
|
|
24201
24583
|
}
|
|
24202
|
-
function
|
|
24584
|
+
function killTree2(child) {
|
|
24203
24585
|
const pid = child.pid;
|
|
24204
24586
|
if (!pid)
|
|
24205
24587
|
return;
|
|
24206
|
-
if (
|
|
24207
|
-
|
|
24588
|
+
if (platform4() === "win32") {
|
|
24589
|
+
spawn6("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
24208
24590
|
windowsHide: true,
|
|
24209
24591
|
stdio: "ignore"
|
|
24210
24592
|
});
|
|
@@ -24219,7 +24601,7 @@ function killTree3(child) {
|
|
|
24219
24601
|
}
|
|
24220
24602
|
}
|
|
24221
24603
|
var defaultRunner = (env2, cwd, args, timeoutMs) => new Promise((resolvePromise) => {
|
|
24222
|
-
const child =
|
|
24604
|
+
const child = spawn6(process.execPath, args, {
|
|
24223
24605
|
cwd,
|
|
24224
24606
|
env: env2,
|
|
24225
24607
|
windowsHide: true,
|
|
@@ -24236,7 +24618,7 @@ var defaultRunner = (env2, cwd, args, timeoutMs) => new Promise((resolvePromise)
|
|
|
24236
24618
|
});
|
|
24237
24619
|
const timer = setTimeout(() => {
|
|
24238
24620
|
timedOut = true;
|
|
24239
|
-
|
|
24621
|
+
killTree2(child);
|
|
24240
24622
|
}, timeoutMs);
|
|
24241
24623
|
child.on("close", (code) => {
|
|
24242
24624
|
clearTimeout(timer);
|
|
@@ -24264,13 +24646,13 @@ import { rmSync as rmSync4 } from "fs";
|
|
|
24264
24646
|
import { homedir as homedir14 } from "os";
|
|
24265
24647
|
import { join as join32, dirname as dirname10 } from "path";
|
|
24266
24648
|
import { fileURLToPath } from "url";
|
|
24267
|
-
import { existsSync as
|
|
24649
|
+
import { existsSync as existsSync40, readFileSync as readFileSync26 } from "fs";
|
|
24268
24650
|
function readVersion() {
|
|
24269
24651
|
const candidates = [
|
|
24270
24652
|
join32(MMA_ROOT, "package.json")
|
|
24271
24653
|
];
|
|
24272
24654
|
for (const p of candidates) {
|
|
24273
|
-
if (
|
|
24655
|
+
if (existsSync40(p)) {
|
|
24274
24656
|
const raw = JSON.parse(readFileSync26(p, "utf-8"));
|
|
24275
24657
|
if (raw.version)
|
|
24276
24658
|
return raw.version;
|
|
@@ -24425,7 +24807,7 @@ __export(exports_repl_commands, {
|
|
|
24425
24807
|
});
|
|
24426
24808
|
import { join as join34, dirname as dirname12 } from "path";
|
|
24427
24809
|
import { homedir as homedir16 } from "os";
|
|
24428
|
-
import { existsSync as
|
|
24810
|
+
import { existsSync as existsSync42, readFileSync as readFileSync28 } from "fs";
|
|
24429
24811
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
24430
24812
|
function readVersion3() {
|
|
24431
24813
|
const here = dirname12(fileURLToPath3(import.meta.url));
|
|
@@ -24434,7 +24816,7 @@ function readVersion3() {
|
|
|
24434
24816
|
join34(here, "..", "package.json")
|
|
24435
24817
|
];
|
|
24436
24818
|
for (const p of candidates) {
|
|
24437
|
-
if (
|
|
24819
|
+
if (existsSync42(p)) {
|
|
24438
24820
|
const raw = JSON.parse(readFileSync28(p, "utf8"));
|
|
24439
24821
|
if (raw.version)
|
|
24440
24822
|
return raw.version;
|
|
@@ -24498,8 +24880,8 @@ function registerMmaCommands(ctx) {
|
|
|
24498
24880
|
}
|
|
24499
24881
|
try {
|
|
24500
24882
|
const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
|
|
24501
|
-
const { existsSync:
|
|
24502
|
-
const { resolve:
|
|
24883
|
+
const { existsSync: existsSync43 } = await import("fs");
|
|
24884
|
+
const { resolve: resolve20 } = await import("path");
|
|
24503
24885
|
let dataUrl;
|
|
24504
24886
|
let label;
|
|
24505
24887
|
if (source.toLowerCase() === "clipboard") {
|
|
@@ -24517,8 +24899,8 @@ function registerMmaCommands(ctx) {
|
|
|
24517
24899
|
dataUrl = result.dataUrl;
|
|
24518
24900
|
label = source;
|
|
24519
24901
|
} else {
|
|
24520
|
-
const absPath =
|
|
24521
|
-
if (!
|
|
24902
|
+
const absPath = resolve20(process.cwd(), source);
|
|
24903
|
+
if (!existsSync43(absPath)) {
|
|
24522
24904
|
console.log(pc.red(t("image.not_found", { path: source })));
|
|
24523
24905
|
return;
|
|
24524
24906
|
}
|
|
@@ -25055,7 +25437,7 @@ init_setup();
|
|
|
25055
25437
|
init_i18n();
|
|
25056
25438
|
import { join as join33, dirname as dirname11 } from "path";
|
|
25057
25439
|
import { homedir as homedir15 } from "os";
|
|
25058
|
-
import { existsSync as
|
|
25440
|
+
import { existsSync as existsSync41, readFileSync as readFileSync27 } from "fs";
|
|
25059
25441
|
|
|
25060
25442
|
// src/cli/security-commands.ts
|
|
25061
25443
|
init_bootstrap();
|
|
@@ -25683,7 +26065,7 @@ function readVersion2() {
|
|
|
25683
26065
|
join33(here, "..", "package.json")
|
|
25684
26066
|
];
|
|
25685
26067
|
for (const p of candidates) {
|
|
25686
|
-
if (
|
|
26068
|
+
if (existsSync41(p)) {
|
|
25687
26069
|
const raw = JSON.parse(readFileSync27(p, "utf8"));
|
|
25688
26070
|
if (raw.version)
|
|
25689
26071
|
return raw.version;
|
|
@@ -25910,7 +26292,7 @@ init_bootstrap();
|
|
|
25910
26292
|
// src/cli/repl.ts
|
|
25911
26293
|
init_colors();
|
|
25912
26294
|
import * as readline2 from "readline";
|
|
25913
|
-
import { existsSync as
|
|
26295
|
+
import { existsSync as existsSync43, readFileSync as readFileSync29, writeFileSync as writeFileSync16 } from "fs";
|
|
25914
26296
|
import { join as join35, dirname as dirname13 } from "path";
|
|
25915
26297
|
import { homedir as homedir17 } from "os";
|
|
25916
26298
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -26225,6 +26607,28 @@ init_spinner();
|
|
|
26225
26607
|
init_box();
|
|
26226
26608
|
init_table();
|
|
26227
26609
|
init_i18n();
|
|
26610
|
+
var GUTTER = " ";
|
|
26611
|
+
function toolMarker(tool) {
|
|
26612
|
+
switch (tool) {
|
|
26613
|
+
case "write_file":
|
|
26614
|
+
case "edit_file":
|
|
26615
|
+
case "create_dir":
|
|
26616
|
+
case "move_file":
|
|
26617
|
+
case "delete_file":
|
|
26618
|
+
return "←";
|
|
26619
|
+
case "read_file":
|
|
26620
|
+
case "list_dir":
|
|
26621
|
+
case "file_info":
|
|
26622
|
+
return "→";
|
|
26623
|
+
case "glob":
|
|
26624
|
+
case "grep":
|
|
26625
|
+
return "✱";
|
|
26626
|
+
case "bash":
|
|
26627
|
+
return "$";
|
|
26628
|
+
default:
|
|
26629
|
+
return "⚙";
|
|
26630
|
+
}
|
|
26631
|
+
}
|
|
26228
26632
|
function isRichTerminal() {
|
|
26229
26633
|
return Boolean(process.stdout.isTTY) && !process.env.CI;
|
|
26230
26634
|
}
|
|
@@ -26254,12 +26658,14 @@ class Renderer {
|
|
|
26254
26658
|
out;
|
|
26255
26659
|
err;
|
|
26256
26660
|
width;
|
|
26661
|
+
toolStyle;
|
|
26257
26662
|
card = null;
|
|
26258
26663
|
constructor(opts = {}) {
|
|
26259
26664
|
this.rich = opts.rich ?? isRichTerminal();
|
|
26260
26665
|
this.out = opts.out ?? process.stdout;
|
|
26261
26666
|
this.err = opts.err ?? process.stderr;
|
|
26262
26667
|
this.width = opts.width ?? getTerminalWidth();
|
|
26668
|
+
this.toolStyle = opts.toolStyle ?? "inline";
|
|
26263
26669
|
this.spinner = new Spinner({
|
|
26264
26670
|
enabled: this.rich && (opts.spinner ?? true),
|
|
26265
26671
|
stream: this.err,
|
|
@@ -26276,11 +26682,24 @@ class Renderer {
|
|
|
26276
26682
|
meta(chunk) {
|
|
26277
26683
|
this.spinner.stop();
|
|
26278
26684
|
if (this.card) {
|
|
26279
|
-
this.
|
|
26685
|
+
if (this.toolStyle === "inline") {
|
|
26686
|
+
this.writeInlineBody(chunk);
|
|
26687
|
+
} else {
|
|
26688
|
+
this.card.body.push(chunk);
|
|
26689
|
+
}
|
|
26280
26690
|
} else {
|
|
26281
26691
|
this.out.write(chunk);
|
|
26282
26692
|
}
|
|
26283
26693
|
}
|
|
26694
|
+
writeInlineBody(chunk) {
|
|
26695
|
+
for (const line of chunk.split(`
|
|
26696
|
+
`)) {
|
|
26697
|
+
if (line.trim() === "")
|
|
26698
|
+
continue;
|
|
26699
|
+
this.out.write(`${GUTTER}${line}
|
|
26700
|
+
`);
|
|
26701
|
+
}
|
|
26702
|
+
}
|
|
26284
26703
|
reasoning(chunk) {
|
|
26285
26704
|
this.spinner.stop();
|
|
26286
26705
|
this.out.write(pc.dim(chunk));
|
|
@@ -26302,6 +26721,13 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
|
|
|
26302
26721
|
return;
|
|
26303
26722
|
}
|
|
26304
26723
|
this.card = { tool, args, body: [], start: Date.now() };
|
|
26724
|
+
if (this.toolStyle === "inline") {
|
|
26725
|
+
const marker = toolMarker(tool);
|
|
26726
|
+
this.out.write(`
|
|
26727
|
+
${pc.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
|
|
26728
|
+
`);
|
|
26729
|
+
return;
|
|
26730
|
+
}
|
|
26305
26731
|
this.spinner.start(`${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}`);
|
|
26306
26732
|
}
|
|
26307
26733
|
toolEnd(_tool, duration, error, ctxDelta) {
|
|
@@ -26317,6 +26743,20 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
|
|
|
26317
26743
|
if (!this.card)
|
|
26318
26744
|
return;
|
|
26319
26745
|
const { tool, args, body } = this.card;
|
|
26746
|
+
const marker = error ? pc.red("✗") : pc.green("✓");
|
|
26747
|
+
let footer = `${marker} ${pc.dim(`${duration}ms`)}`;
|
|
26748
|
+
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
26749
|
+
const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
|
|
26750
|
+
footer += ` ${pc.dim("ctx")} ${deltaStr}`;
|
|
26751
|
+
}
|
|
26752
|
+
if (this.toolStyle === "inline") {
|
|
26753
|
+
this.out.write(`${GUTTER}${footer}
|
|
26754
|
+
`);
|
|
26755
|
+
this.out.write(`${divider(this.width)}
|
|
26756
|
+
`);
|
|
26757
|
+
this.card = null;
|
|
26758
|
+
return;
|
|
26759
|
+
}
|
|
26320
26760
|
const lines = [];
|
|
26321
26761
|
const summary = summarizeArgs2(args);
|
|
26322
26762
|
if (summary)
|
|
@@ -26328,12 +26768,6 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
|
|
|
26328
26768
|
lines.push(line);
|
|
26329
26769
|
}
|
|
26330
26770
|
}
|
|
26331
|
-
const marker = error ? pc.red("✗") : pc.green("✓");
|
|
26332
|
-
let footer = `${marker} ${pc.dim(`${duration}ms`)}`;
|
|
26333
|
-
if (ctxDelta !== undefined && ctxDelta !== 0) {
|
|
26334
|
-
const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
|
|
26335
|
-
footer += ` ${pc.dim("ctx")} ${deltaStr}`;
|
|
26336
|
-
}
|
|
26337
26771
|
lines.push(footer);
|
|
26338
26772
|
const title = `${marker} ${friendlyTool(tool)}`;
|
|
26339
26773
|
for (const line of box(lines, { title, width: this.width })) {
|
|
@@ -26377,7 +26811,7 @@ function readVersion4() {
|
|
|
26377
26811
|
join35(here, "..", "package.json")
|
|
26378
26812
|
];
|
|
26379
26813
|
for (const p of candidates) {
|
|
26380
|
-
if (
|
|
26814
|
+
if (existsSync43(p)) {
|
|
26381
26815
|
const raw = JSON.parse(readFileSync29(p, "utf8"));
|
|
26382
26816
|
if (raw.version)
|
|
26383
26817
|
return raw.version;
|
|
@@ -26453,7 +26887,7 @@ class Repl {
|
|
|
26453
26887
|
this.setupListeners();
|
|
26454
26888
|
}
|
|
26455
26889
|
loadHistory() {
|
|
26456
|
-
if (
|
|
26890
|
+
if (existsSync43(this.historyPath)) {
|
|
26457
26891
|
try {
|
|
26458
26892
|
const raw = readFileSync29(this.historyPath, "utf-8");
|
|
26459
26893
|
this.history = raw.split(`
|
|
@@ -26556,18 +26990,20 @@ class Repl {
|
|
|
26556
26990
|
readline2.emitKeypressEvents(process.stdin);
|
|
26557
26991
|
process.stdin.on("keypress", async (str, key) => {
|
|
26558
26992
|
if (key.name === "escape") {
|
|
26993
|
+
const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
|
|
26559
26994
|
const now = Date.now();
|
|
26560
|
-
|
|
26995
|
+
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
26996
|
+
this.lastEscTime = now;
|
|
26997
|
+
if (escBytes >= 2 || withinWindow) {
|
|
26998
|
+
this.lastEscTime = 0;
|
|
26561
26999
|
if (this.agentRunning) {
|
|
26562
27000
|
process.stdout.write(pc.yellow(`
|
|
26563
27001
|
${t("repl.interrupt")}
|
|
26564
27002
|
`));
|
|
26565
27003
|
this.agent.shutdown();
|
|
26566
27004
|
}
|
|
26567
|
-
this.lastEscTime = 0;
|
|
26568
|
-
return;
|
|
26569
27005
|
}
|
|
26570
|
-
|
|
27006
|
+
return;
|
|
26571
27007
|
}
|
|
26572
27008
|
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
26573
27009
|
try {
|
|
@@ -26580,6 +27016,10 @@ ${t("repl.interrupt")}
|
|
|
26580
27016
|
console.log(pc.green(`
|
|
26581
27017
|
${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
26582
27018
|
this.rl.prompt();
|
|
27019
|
+
} else {
|
|
27020
|
+
console.log(pc.yellow(`
|
|
27021
|
+
${t("image.clipboard_empty")}`));
|
|
27022
|
+
this.rl.prompt();
|
|
26583
27023
|
}
|
|
26584
27024
|
} catch {}
|
|
26585
27025
|
}
|
|
@@ -26624,7 +27064,8 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
|
26624
27064
|
process.stdout.write(`
|
|
26625
27065
|
` + pc.green(t("repl.agent")));
|
|
26626
27066
|
const renderer = new Renderer({
|
|
26627
|
-
spinner: this.config.ui?.spinner ?? true
|
|
27067
|
+
spinner: this.config.ui?.spinner ?? true,
|
|
27068
|
+
toolStyle: this.config.ui?.toolStyle ?? "inline"
|
|
26628
27069
|
});
|
|
26629
27070
|
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
26630
27071
|
if (ev.type === "start") {
|
|
@@ -26767,7 +27208,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
|
26767
27208
|
join35(this.baseDir, ".mma", "AGENTS.md"),
|
|
26768
27209
|
join35(this.configDir, "AGENTS.md")
|
|
26769
27210
|
];
|
|
26770
|
-
const foundAgents = agentsMdCandidates.filter((p) =>
|
|
27211
|
+
const foundAgents = agentsMdCandidates.filter((p) => existsSync43(p));
|
|
26771
27212
|
if (foundAgents.length > 0) {
|
|
26772
27213
|
for (const p of foundAgents) {
|
|
26773
27214
|
row(t("repl.agents_label"), pc.dim(p));
|
|
@@ -26804,7 +27245,7 @@ init_setup();
|
|
|
26804
27245
|
init_config2();
|
|
26805
27246
|
init_i18n();
|
|
26806
27247
|
init_colors();
|
|
26807
|
-
import { existsSync as
|
|
27248
|
+
import { existsSync as existsSync44 } from "fs";
|
|
26808
27249
|
import { join as join36 } from "path";
|
|
26809
27250
|
import { homedir as homedir18 } from "os";
|
|
26810
27251
|
async function main() {
|
|
@@ -26841,7 +27282,10 @@ async function main() {
|
|
|
26841
27282
|
`);
|
|
26842
27283
|
process.exit(result2.success ? 0 : 1);
|
|
26843
27284
|
}
|
|
26844
|
-
const renderer = new Renderer({
|
|
27285
|
+
const renderer = new Renderer({
|
|
27286
|
+
spinner: config.ui?.spinner ?? true,
|
|
27287
|
+
toolStyle: config.ui?.toolStyle ?? "inline"
|
|
27288
|
+
});
|
|
26845
27289
|
const result = await agent.run(prompt, (chunk) => renderer.text(chunk), (meta) => renderer.meta(meta), (ev) => {
|
|
26846
27290
|
if (ev.type === "start") {
|
|
26847
27291
|
renderer.toolStart(ev.tool, ev.args);
|
|
@@ -26870,7 +27314,7 @@ async function main() {
|
|
|
26870
27314
|
agent.shutdown();
|
|
26871
27315
|
} else {
|
|
26872
27316
|
const configPath = join36(homedir18(), ".mma", "config.json");
|
|
26873
|
-
if (!
|
|
27317
|
+
if (!existsSync44(configPath)) {
|
|
26874
27318
|
console.log(pc.yellow(`
|
|
26875
27319
|
` + t("cli.first_run") + `
|
|
26876
27320
|
`));
|