micro-models-agent 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +1223 -885
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2391,7 +2391,7 @@ var init_en = __esm(() => {
2391
2391
  "tool.interactive_disabled": "Interactive tool is disabled in exit-on-complete mode. Proceed without asking the user.",
2392
2392
  "proc.started": `Started background process {id} (PID {pid}).
2393
2393
  Command: {command}`,
2394
- "proc.detected_hint": "[Long-running command detectedstarted in background]",
2394
+ "proc.promoted_hint": "Command still running after {ms} ms moved to the background",
2395
2395
  "proc.manage_hint": "Check output: process_log id={id}. Stop it: process_kill id={id}. List all: process_list.",
2396
2396
  "proc.none": "No background processes running.",
2397
2397
  "proc.not_found": "Process not found: {id}",
@@ -2400,7 +2400,6 @@ Command: {command}`,
2400
2400
  "proc.list_header": "Background processes",
2401
2401
  "proc.log_header": "Process {id} ({status}) output:",
2402
2402
  "proc.log_empty": "(no output yet)",
2403
- "proc.timed_out": "Command timed out after {ms} ms and was killed.",
2404
2403
  "proc.hint": "Manage them with {list}, {log}, {kill}.",
2405
2404
  "proc.status_running": "running",
2406
2405
  "proc.status_exited": "exited",
@@ -2717,7 +2716,7 @@ Use this knowledge to answer the user's question.`,
2717
2716
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
2718
2717
  "hall.uncertainty": "Uncertainty markers: {markers}",
2719
2718
  "hall.unknown_paths": "Mentioned file paths not found in known files: {paths}",
2720
- "hall.contradiction": 'Contradicts previous decision: "{decision}" in {location}',
2719
+ "hall.contradiction_llm": "Contradicts an earlier decision{reason}",
2721
2720
  "hall.uncertainty_prefix": `
2722
2721
 
2723
2722
  [⚠️ Uncertainty] `,
@@ -2936,7 +2935,7 @@ var init_ru = __esm(() => {
2936
2935
  "tool.interactive_disabled": "Интерактивный инструмент отключён в режиме exit-on-complete. Продолжай без вопроса пользователю.",
2937
2936
  "proc.started": `Фоновый процесс запущен: {id} (PID {pid}).
2938
2937
  Команда: {command}`,
2939
- "proc.detected_hint": "[Обнаружена длительная командазапущена в фоне]",
2938
+ "proc.promoted_hint": "Команда всё ещё выполняется через {ms} мс переведена в фоновый режим",
2940
2939
  "proc.manage_hint": "Проверить вывод: process_log id={id}. Остановить: process_kill id={id}. Список всех: process_list.",
2941
2940
  "proc.none": "Фоновых процессов нет.",
2942
2941
  "proc.not_found": "Процесс не найден: {id}",
@@ -2945,7 +2944,6 @@ var init_ru = __esm(() => {
2945
2944
  "proc.list_header": "Фоновые процессы",
2946
2945
  "proc.log_header": "Вывод процесса {id} ({status}):",
2947
2946
  "proc.log_empty": "(вывода пока нет)",
2948
- "proc.timed_out": "Команда превысила таймаут {ms} мс и была остановлена.",
2949
2947
  "proc.hint": "Управление: {list}, {log}, {kill}.",
2950
2948
  "proc.status_running": "работает",
2951
2949
  "proc.status_exited": "завершён",
@@ -3262,7 +3260,7 @@ var init_ru = __esm(() => {
3262
3260
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
3263
3261
  "hall.uncertainty": "Маркеры неопределённости: {markers}",
3264
3262
  "hall.unknown_paths": "Упомянутые файлы не найдены: {paths}",
3265
- "hall.contradiction": 'Противоречит предыдущему решению: "{decision}" в {location}',
3263
+ "hall.contradiction_llm": "Противоречит ранее принятому решению{reason}",
3266
3264
  "hall.uncertainty_prefix": `
3267
3265
 
3268
3266
  [⚠️ Неопределённость] `,
@@ -4929,8 +4927,12 @@ class ToolRegistry {
4929
4927
  }
4930
4928
 
4931
4929
  // src/modules/processes/runner.ts
4932
- import { spawn } from "child_process";
4933
- import { platform } from "os";
4930
+ function registerKillable(callId, kill) {
4931
+ activeChildren.set(callId, { kill });
4932
+ }
4933
+ function unregisterKillable(callId) {
4934
+ activeChildren.delete(callId);
4935
+ }
4934
4936
  function killByCallId(callId) {
4935
4937
  const entry = activeChildren.get(callId);
4936
4938
  if (!entry)
@@ -4941,95 +4943,6 @@ function killByCallId(callId) {
4941
4943
  } catch {}
4942
4944
  return true;
4943
4945
  }
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
4946
  var activeChildren;
5034
4947
  var init_runner = __esm(() => {
5035
4948
  activeChildren = new Map;
@@ -5045,6 +4958,16 @@ class ToolExecutor {
5045
4958
  this.ctx = ctx;
5046
4959
  this.pluginManager = pluginManager;
5047
4960
  }
4961
+ async executeByName(name, args, ctx) {
4962
+ const prevCtx = this.ctx;
4963
+ if (ctx)
4964
+ this.ctx = ctx;
4965
+ try {
4966
+ return await this.execute({ id: `redirect_${Date.now()}`, name, arguments: args });
4967
+ } finally {
4968
+ this.ctx = prevCtx;
4969
+ }
4970
+ }
5048
4971
  async execute(call, signal) {
5049
4972
  const tool = this.registry.get(call.name);
5050
4973
  if (!tool) {
@@ -6810,14 +6733,14 @@ var init_command_validator = __esm(() => {
6810
6733
  });
6811
6734
 
6812
6735
  // src/modules/processes/registry.ts
6813
- import { spawn as spawn2 } from "child_process";
6814
- import { platform as platform2 } from "os";
6815
- function killTree2(child) {
6736
+ import { spawn } from "child_process";
6737
+ import { platform } from "os";
6738
+ function killTree(child) {
6816
6739
  const pid = child.pid;
6817
6740
  if (!pid)
6818
6741
  return;
6819
- if (platform2() === "win32") {
6820
- spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], {
6742
+ if (platform() === "win32") {
6743
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
6821
6744
  windowsHide: true,
6822
6745
  stdio: "ignore"
6823
6746
  });
@@ -6835,6 +6758,7 @@ function killTree2(child) {
6835
6758
  class ProcessRegistry {
6836
6759
  procs = new Map;
6837
6760
  children = new Map;
6761
+ exitWaiters = new Map;
6838
6762
  start(command, cwd, sessionId) {
6839
6763
  const id = `proc_${Date.now()}_${++seq}`;
6840
6764
  const entry = {
@@ -6850,46 +6774,76 @@ class ProcessRegistry {
6850
6774
  };
6851
6775
  this.trimOldEntries();
6852
6776
  this.procs.set(id, entry);
6853
- const child = spawn2(command, {
6854
- cwd,
6855
- shell: true,
6856
- windowsHide: true,
6857
- detached: platform2() !== "win32",
6858
- stdio: ["ignore", "pipe", "pipe"]
6859
- });
6777
+ let child;
6778
+ try {
6779
+ child = spawn(command, {
6780
+ cwd,
6781
+ shell: true,
6782
+ windowsHide: true,
6783
+ detached: platform() !== "win32",
6784
+ stdio: ["ignore", "pipe", "pipe"]
6785
+ });
6786
+ } catch (e) {
6787
+ entry.status = "killed";
6788
+ entry.spawnError = e?.message || String(e);
6789
+ entry.log.push(`[process error] ${entry.spawnError}`);
6790
+ return entry;
6791
+ }
6860
6792
  this.children.set(id, child);
6861
6793
  entry.pid = child.pid ?? 0;
6862
- let partial = "";
6794
+ const decoder = platform() === "win32" ? new TextDecoder("ibm866") : null;
6795
+ let buf = "";
6796
+ const pushLine = (line) => {
6797
+ if (entry.log.length >= MAX_LOG_LINES) {
6798
+ entry.log.shift();
6799
+ }
6800
+ entry.log.push(line);
6801
+ };
6863
6802
  const append = (chunk) => {
6864
- const text = partial + chunk.toString();
6865
- const lines = text.split(/\r?\n/);
6866
- partial = lines.pop() ?? "";
6803
+ const decoded = decoder ? decoder.decode(chunk, { stream: true }) : chunk.toString();
6804
+ buf += decoded;
6805
+ const lines = buf.split(/\r?\n/);
6806
+ buf = lines.pop() ?? "";
6867
6807
  for (const line of lines) {
6868
- if (entry.log.length >= MAX_LOG_LINES) {
6869
- entry.log.shift();
6870
- }
6871
- entry.log.push(line);
6808
+ pushLine(line);
6809
+ }
6810
+ };
6811
+ const flush = () => {
6812
+ if (decoder) {
6813
+ buf += decoder.decode();
6814
+ }
6815
+ if (buf) {
6816
+ pushLine(buf);
6817
+ buf = "";
6872
6818
  }
6873
6819
  };
6820
+ const resolveExit = () => {
6821
+ this.exitWaiters.get(id)?.resolve();
6822
+ };
6874
6823
  child.stdout?.on("data", append);
6875
6824
  child.stderr?.on("data", append);
6876
6825
  child.on("error", (err) => {
6877
6826
  if (entry.status === "running") {
6878
6827
  entry.status = "killed";
6879
6828
  }
6829
+ entry.spawnError = err.message;
6880
6830
  append(Buffer.from(`[process error] ${err.message}`));
6831
+ resolveExit();
6881
6832
  });
6882
6833
  child.on("close", (code) => {
6834
+ flush();
6883
6835
  if (entry.status === "running") {
6884
6836
  entry.status = "exited";
6885
6837
  }
6886
6838
  entry.exitCode = code;
6887
6839
  this.children.delete(id);
6888
- if (partial) {
6889
- entry.log.push(partial);
6890
- partial = "";
6891
- }
6840
+ resolveExit();
6892
6841
  });
6842
+ let resolve10;
6843
+ const promise = new Promise((r) => {
6844
+ resolve10 = r;
6845
+ });
6846
+ this.exitWaiters.set(id, { promise, resolve: resolve10 });
6893
6847
  return entry;
6894
6848
  }
6895
6849
  list(sessionId) {
@@ -6917,7 +6871,7 @@ class ProcessRegistry {
6917
6871
  entry.status = "killed";
6918
6872
  const child = this.children.get(id);
6919
6873
  if (child) {
6920
- killTree2(child);
6874
+ killTree(child);
6921
6875
  }
6922
6876
  }
6923
6877
  return true;
@@ -6929,71 +6883,61 @@ class ProcessRegistry {
6929
6883
  }
6930
6884
  return targets.length;
6931
6885
  }
6886
+ waitForExit(id, timeoutMs) {
6887
+ const entry = this.procs.get(id);
6888
+ const waiter = this.exitWaiters.get(id);
6889
+ if (!entry || entry.status !== "running" || !waiter) {
6890
+ return Promise.resolve(true);
6891
+ }
6892
+ return new Promise((resolve10) => {
6893
+ const timer = setTimeout(() => resolve10(false), timeoutMs);
6894
+ waiter.promise.then(() => {
6895
+ clearTimeout(timer);
6896
+ resolve10(true);
6897
+ });
6898
+ });
6899
+ }
6900
+ remove(id) {
6901
+ const entry = this.procs.get(id);
6902
+ if (!entry)
6903
+ return false;
6904
+ if (entry.status === "running") {
6905
+ const child = this.children.get(id);
6906
+ if (child) {
6907
+ killTree(child);
6908
+ }
6909
+ entry.status = "killed";
6910
+ }
6911
+ this.procs.delete(id);
6912
+ this.children.delete(id);
6913
+ this.exitWaiters.delete(id);
6914
+ return true;
6915
+ }
6932
6916
  trimOldEntries() {
6933
6917
  if (this.procs.size < MAX_KEPT_PROCESSES)
6934
6918
  return;
6935
6919
  const sorted = Array.from(this.procs.values()).sort((a, b) => a.startedAt.localeCompare(b.startedAt));
6936
6920
  const toRemove = sorted.slice(0, sorted.length - MAX_KEPT_PROCESSES + 1);
6937
6921
  for (const entry of toRemove) {
6938
- if (entry.status === "running") {
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);
6922
+ this.remove(entry.id);
6946
6923
  }
6947
6924
  }
6948
6925
  }
6949
- var MAX_LOG_LINES = 300, MAX_KEPT_PROCESSES = 20, seq = 0, processRegistry;
6926
+ var MAX_LOG_LINES = 2000, MAX_KEPT_PROCESSES = 20, seq = 0, processRegistry;
6950
6927
  var init_registry = __esm(() => {
6951
6928
  processRegistry = new ProcessRegistry;
6952
6929
  });
6953
6930
 
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
6931
  // src/modules/processes/index.ts
6987
6932
  var init_processes = __esm(() => {
6988
6933
  init_runner();
6989
6934
  init_registry();
6990
- init_detect2();
6991
6935
  });
6992
6936
 
6993
6937
  // src/tools/bash.ts
6994
- import { platform as platform3 } from "os";
6938
+ import { platform as platform2 } from "os";
6995
6939
  function adaptCommandForWindows(command) {
6996
- if (platform3() !== "win32")
6940
+ if (platform2() !== "win32")
6997
6941
  return command;
6998
6942
  const trimmed = command.trim();
6999
6943
  if (trimmed.startsWith("mkdir -p ")) {
@@ -7002,9 +6946,45 @@ function adaptCommandForWindows(command) {
7002
6946
  if (trimmed === "mkdir -p" || trimmed.startsWith("mkdir -p ")) {
7003
6947
  return trimmed.replace(/mkdir -p/g, "mkdir");
7004
6948
  }
6949
+ const firstWord = trimmed.split(/\s+/)[0]?.split(/[\\/]/).pop();
6950
+ const translated = firstWord ? UNIX_TO_WIN_TRANSLATE[firstWord] : undefined;
6951
+ if (translated && !trimmed.includes("|") && !trimmed.includes(">") && !trimmed.includes("&&") && !trimmed.includes(";")) {
6952
+ return trimmed.replace(firstWord, translated);
6953
+ }
7005
6954
  return command;
7006
6955
  }
7007
- var BASH_TIMEOUT_MS = 120000, UNIX_TO_WIN_HINTS, bashTool;
6956
+ function detectToolCallInBash(command) {
6957
+ const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
6958
+ if (!match)
6959
+ return null;
6960
+ const tool = match[1];
6961
+ const rest = match[2].trim();
6962
+ if (!/^[a-z][a-z0-9_]+$/.test(tool))
6963
+ return null;
6964
+ if (!rest.includes("=") && !rest.startsWith("{"))
6965
+ return null;
6966
+ return { tool, args: rest };
6967
+ }
6968
+ function parseToolArgs(raw) {
6969
+ const trimmed = raw.trim();
6970
+ if (trimmed.startsWith("{")) {
6971
+ try {
6972
+ return JSON.parse(trimmed);
6973
+ } catch {}
6974
+ }
6975
+ const args = {};
6976
+ const tokens = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
6977
+ for (const token of tokens) {
6978
+ const eq = token.indexOf("=");
6979
+ if (eq > 0) {
6980
+ const key = token.slice(0, eq);
6981
+ const value = token.slice(eq + 1);
6982
+ args[key] = value.replace(/^["']|["']$/g, "");
6983
+ }
6984
+ }
6985
+ return args;
6986
+ }
6987
+ var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, bashGraceMs, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, bashTool;
7008
6988
  var init_bash = __esm(() => {
7009
6989
  init_command_validator();
7010
6990
  init_audit_log();
@@ -7012,6 +6992,7 @@ var init_bash = __esm(() => {
7012
6992
  init_security();
7013
6993
  init_processes();
7014
6994
  init_i18n();
6995
+ bashGraceMs = BASH_GRACE_MS;
7015
6996
  UNIX_TO_WIN_HINTS = {
7016
6997
  ls: 'Use "dir" or the list_dir tool instead.',
7017
6998
  pwd: 'Use "echo %cd%" or the file_info tool instead.',
@@ -7028,23 +7009,40 @@ var init_bash = __esm(() => {
7028
7009
  wc: "Use the read_file tool instead.",
7029
7010
  diff: "Use the diff tool instead.",
7030
7011
  which: 'Use "where" instead.',
7031
- echo: "echo works on Windows, but avoid pipes (|)."
7012
+ echo: "echo works on Windows, but avoid pipes (|).",
7013
+ "Get-Content": 'The shell is cmd.exe, not PowerShell. Use "type" or the read_file tool instead.',
7014
+ "Select-Object": "The shell is cmd.exe, not PowerShell. Use the read_file tool with offset/limit instead."
7015
+ };
7016
+ UNIX_TO_WIN_TRANSLATE = {
7017
+ ls: "dir",
7018
+ pwd: "echo %cd%",
7019
+ cat: "type",
7020
+ wc: 'find /c /v ""'
7032
7021
  };
7033
7022
  bashTool = {
7034
7023
  name: "bash",
7035
- description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Long-running commands (dev servers, watchers) start in the background and return a process id immediately — manage them with process_list, process_log, process_kill. Set background=true to force background execution.",
7024
+ 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
7025
  tags: ["shell", "code"],
7037
7026
  parameters: {
7038
7027
  type: "object",
7039
7028
  properties: {
7040
7029
  command: { type: "string", description: "Shell command to execute" },
7041
7030
  workdir: { type: "string", description: "Working directory (default: baseDir)" },
7042
- background: { type: "boolean", description: "Start the command in the background and return immediately with a process id (default: auto-detect long-running commands)" }
7031
+ 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
7032
  },
7044
7033
  required: ["command"]
7045
7034
  },
7046
7035
  handler: async (ctx, args) => {
7047
7036
  const originalCommand = String(args.command);
7037
+ const toolCall = detectToolCallInBash(originalCommand);
7038
+ if (toolCall && toolCall.tool !== "bash" && ctx.toolExecutor) {
7039
+ const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
7040
+ return {
7041
+ success: redirected.success,
7042
+ output: `[redirected to tool "${toolCall.tool}"]
7043
+ ${redirected.output}`
7044
+ };
7045
+ }
7048
7046
  const command = adaptCommandForWindows(originalCommand);
7049
7047
  const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
7050
7048
  const appConfig = ctx.config || {};
@@ -7062,68 +7060,67 @@ Hint: Use the "workdir" parameter to run commands in a specific directory instea
7062
7060
  if (securityConfig?.logCommands) {
7063
7061
  logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, `Working directory: ${workdir}`);
7064
7062
  }
7065
- const background = args.background === true || isLongRunningCommand(command);
7066
- if (background) {
7067
- const entry = processRegistry.start(command, workdir, ctx.sessionId);
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
- };
7063
+ const entry = processRegistry.start(command, workdir, ctx.sessionId);
7064
+ if (ctx.activeCallId) {
7065
+ registerKillable(ctx.activeCallId, () => processRegistry.kill(entry.id));
7084
7066
  }
7085
7067
  try {
7086
- const res = await runCommand(command, {
7087
- cwd: workdir,
7088
- callId: ctx.activeCallId,
7089
- timeoutMs: BASH_TIMEOUT_MS
7090
- });
7091
- const parts = [res.stdout.trimEnd(), res.stderr.trimEnd()].filter(Boolean);
7092
- let output = parts.join(`
7068
+ const settleMs = args.background === true ? SPAWN_SETTLE_MS : bashGraceMs;
7069
+ const exited = await processRegistry.waitForExit(entry.id, settleMs);
7070
+ if (exited) {
7071
+ if (entry.spawnError) {
7072
+ return {
7073
+ success: false,
7074
+ output: `[process error] ${entry.spawnError}
7075
+ Hint: check the "workdir" path exists and the command is valid for this OS.`
7076
+ };
7077
+ }
7078
+ const code = entry.exitCode;
7079
+ let output2 = entry.log.join(`
7093
7080
  `);
7094
- if (res.timedOut) {
7095
- output = `${output ? output + `
7096
- ` : ""}${t("proc.timed_out", {
7097
- ms: BASH_TIMEOUT_MS
7098
- })}`;
7099
- } else if (!output && res.code !== 0) {
7100
- output = `(exit code ${res.code})`;
7101
- }
7102
- if (platform3() === "win32" && res.code !== 0) {
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}
7081
+ processRegistry.remove(entry.id);
7082
+ if (!output2 && code !== 0) {
7083
+ output2 = `(exit code ${code})`;
7084
+ }
7085
+ if (platform2() === "win32" && code !== 0) {
7086
+ const firstWord = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
7087
+ const hint = firstWord ? UNIX_TO_WIN_HINTS[firstWord] : undefined;
7088
+ if (hint) {
7089
+ output2 = `${output2}
7107
7090
 
7108
7091
  Hint: "${firstWord}" may not work on Windows. ${hint}`;
7092
+ }
7109
7093
  }
7110
- }
7111
- if (securityConfig?.logCommands) {
7112
- logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), res.code === 0, `Working directory: ${workdir}, Output length: ${res.stdout.length}`);
7113
- }
7114
- const lines = output.split(`
7094
+ if (securityConfig?.logCommands) {
7095
+ logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output2.length}`);
7096
+ }
7097
+ const lines = output2.split(`
7115
7098
  `);
7116
- if (lines.length > MAX_PREVIEW_LINES) {
7117
- output = lines.slice(0, MAX_PREVIEW_LINES).join(`
7099
+ if (lines.length > MAX_PREVIEW_LINES) {
7100
+ output2 = lines.slice(0, MAX_PREVIEW_LINES).join(`
7118
7101
  `) + `
7119
7102
  ... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
7103
+ }
7104
+ return { success: code === 0, output: output2 };
7120
7105
  }
7121
- return { success: res.code === 0, output };
7122
- } catch (e) {
7106
+ const explicit = args.background === true;
7107
+ const output = `${t("proc.started", {
7108
+ id: entry.id,
7109
+ pid: entry.pid,
7110
+ command
7111
+ })}${explicit ? "" : `
7112
+ ${t("proc.promoted_hint", { ms: settleMs })}`}
7113
+ ${t("proc.manage_hint", {
7114
+ id: entry.id
7115
+ })}`;
7123
7116
  if (securityConfig?.logCommands) {
7124
- logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, `Working directory: ${workdir}, Error: ${e.message?.slice(0, 100) || ""}`);
7117
+ logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
7118
+ }
7119
+ return { success: true, output };
7120
+ } finally {
7121
+ if (ctx.activeCallId) {
7122
+ unregisterKillable(ctx.activeCallId);
7125
7123
  }
7126
- return { success: false, output: e.message || String(e) };
7127
7124
  }
7128
7125
  }
7129
7126
  };
@@ -7423,6 +7420,15 @@ class SessionLogger {
7423
7420
  iteration
7424
7421
  });
7425
7422
  }
7423
+ logPlan(event, detail, iteration) {
7424
+ this.session?.appendLog({
7425
+ ts: new Date().toISOString(),
7426
+ type: "plan",
7427
+ content: detail,
7428
+ tool: event,
7429
+ ...iteration !== undefined ? { iteration } : {}
7430
+ });
7431
+ }
7426
7432
  }
7427
7433
 
7428
7434
  // node_modules/jsonrepair/lib/esm/utils/JSONRepairError.js
@@ -8548,76 +8554,394 @@ function filterToolsByTags(tools, toolTags) {
8548
8554
  });
8549
8555
  }
8550
8556
 
8551
- // src/modules/execution/moe-executor.ts
8552
- function isTransientError(error) {
8553
- return TRANSIENT_ERROR_PATTERNS.some((p) => error.includes(p));
8554
- }
8555
- function sleep(ms) {
8556
- return new Promise((r) => setTimeout(r, ms));
8557
- }
8558
- function topologicalSort(subtasks) {
8559
- const sorted = [];
8560
- const remaining = new Set(subtasks.map((s) => s.id));
8561
- const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
8562
- while (remaining.size > 0) {
8563
- const ready = [];
8564
- for (const id of remaining) {
8565
- const sub = subtaskMap.get(id);
8566
- const depsSatisfied = (sub.depends_on || []).every((d) => !remaining.has(d));
8567
- if (depsSatisfied) {
8568
- ready.push(sub);
8569
- }
8570
- }
8571
- if (ready.length === 0) {
8572
- break;
8573
- }
8574
- sorted.push(ready);
8575
- for (const r of ready) {
8576
- remaining.delete(r.id);
8557
+ // src/modules/execution/stuck-detector.ts
8558
+ class StuckDetector {
8559
+ threshold;
8560
+ errorThreshold;
8561
+ iterationsOnCurrentStep = 0;
8562
+ currentStepId = null;
8563
+ toolErrors = new Map;
8564
+ currentStepDescription = "";
8565
+ recentToolCalls = [];
8566
+ maxRecentCalls = 10;
8567
+ repetitionThreshold = 3;
8568
+ consecutiveFailures = 0;
8569
+ lastFailedTool = "";
8570
+ lastErrorOutput = "";
8571
+ escalationCount = 0;
8572
+ escalationThreshold = 3;
8573
+ fileRewriteCount = new Map;
8574
+ fileRewriteThreshold = 3;
8575
+ constructor(threshold = 8, errorThreshold = 3) {
8576
+ this.threshold = threshold;
8577
+ this.errorThreshold = errorThreshold;
8578
+ }
8579
+ recordIteration(stepId) {
8580
+ if (stepId === this.currentStepId) {
8581
+ this.iterationsOnCurrentStep++;
8582
+ } else {
8583
+ this.resetStepState(stepId);
8577
8584
  }
8578
8585
  }
8579
- return sorted;
8580
- }
8581
- async function executeSubtask(subtask, deps, _sharedContext) {
8582
- const startTime = Date.now();
8583
- const expertConfig = getExpertConfig(deps.config, subtask.expert_tag);
8584
- if (!expertConfig) {
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
- };
8586
+ recordToolCall(name, args) {
8587
+ const argsKey = JSON.stringify(args);
8588
+ this.recentToolCalls.push({ name, argsKey });
8589
+ if (this.recentToolCalls.length > this.maxRecentCalls) {
8590
+ this.recentToolCalls.shift();
8591
+ }
8593
8592
  }
8594
- const subagentTool = deps.toolRegistry.get("subagent");
8595
- if (!subagentTool) {
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
- };
8593
+ recordToolError(toolName, output) {
8594
+ this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
8595
+ this.consecutiveFailures++;
8596
+ this.lastFailedTool = toolName;
8597
+ if (output)
8598
+ this.lastErrorOutput = output;
8604
8599
  }
8605
- const allTools = deps.toolRegistry.getAll();
8606
- const filteredTools = filterToolsByTags(allTools, expertConfig.tool_tags);
8607
- if (filteredTools.length === 0) {
8608
- return {
8609
- subtaskId: subtask.id,
8610
- success: false,
8611
- summary: `No tools available for expert "${subtask.expert_tag}" (tags: ${expertConfig.tool_tags.join(", ")})`,
8612
- result: "",
8613
- error: "No matching tools",
8614
- durationMs: Date.now() - startTime
8615
- };
8600
+ getLastErrorOutput() {
8601
+ return this.lastErrorOutput;
8602
+ }
8603
+ getLastFailedTool() {
8604
+ return this.lastFailedTool;
8605
+ }
8606
+ getIterationsOnCurrentStep() {
8607
+ return this.iterationsOnCurrentStep;
8608
+ }
8609
+ recordToolSuccess() {
8610
+ this.consecutiveFailures = 0;
8611
+ this.lastFailedTool = "";
8612
+ this.escalationCount = 0;
8613
+ }
8614
+ setCurrentStep(stepId, description) {
8615
+ if (stepId !== this.currentStepId) {
8616
+ this.resetStepState(stepId);
8617
+ }
8618
+ this.currentStepDescription = description;
8619
+ }
8620
+ isStuck() {
8621
+ return this.iterationsOnCurrentStep > this.threshold;
8622
+ }
8623
+ hasRepetitiveErrors(toolName) {
8624
+ if (toolName) {
8625
+ return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
8626
+ }
8627
+ return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
8628
+ }
8629
+ hasRepetitiveToolCalls() {
8630
+ if (this.recentToolCalls.length < this.repetitionThreshold)
8631
+ return false;
8632
+ const last = this.recentToolCalls[this.recentToolCalls.length - 1];
8633
+ let count = 1;
8634
+ for (let i = this.recentToolCalls.length - 2;i >= 0; i--) {
8635
+ if (this.recentToolCalls[i].name === last.name && this.recentToolCalls[i].argsKey === last.argsKey) {
8636
+ count++;
8637
+ } else {
8638
+ break;
8639
+ }
8640
+ }
8641
+ return count >= this.repetitionThreshold;
8642
+ }
8643
+ hasConsecutiveFailures() {
8644
+ return this.consecutiveFailures >= this.errorThreshold;
8645
+ }
8646
+ getConsecutiveFailuresCount() {
8647
+ return this.consecutiveFailures;
8648
+ }
8649
+ recordEscalation() {
8650
+ this.escalationCount++;
8651
+ }
8652
+ shouldEscalate() {
8653
+ return this.escalationCount >= this.escalationThreshold;
8654
+ }
8655
+ resetEscalation() {
8656
+ this.escalationCount = 0;
8657
+ }
8658
+ getEscalationCount() {
8659
+ return this.escalationCount;
8660
+ }
8661
+ getHints() {
8662
+ const hints = [];
8663
+ const desc = this.currentStepDescription.toLowerCase();
8664
+ if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
8665
+ hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
8666
+ }
8667
+ if (desc.includes("test") || desc.includes("spec")) {
8668
+ hints.push("Make sure the source files exist and have real code before running tests.");
8669
+ }
8670
+ if (desc.includes("build") || desc.includes("compile")) {
8671
+ hints.push("Check that all dependencies are installed and source files are not empty.");
8672
+ }
8673
+ if (desc.includes("deploy") || desc.includes("publish")) {
8674
+ hints.push("Verify credentials and network access before deploying.");
8675
+ }
8676
+ if (this.hasRepetitiveToolCalls()) {
8677
+ hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
8678
+ }
8679
+ if (this.hasConsecutiveFailures()) {
8680
+ hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
8681
+ }
8682
+ return hints;
8683
+ }
8684
+ getActionableHints() {
8685
+ const hints = [];
8686
+ const error = this.lastErrorOutput;
8687
+ if (!error)
8688
+ return hints;
8689
+ if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
8690
+ if (/node_modules/.test(error)) {
8691
+ hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
8692
+ }
8693
+ }
8694
+ if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
8695
+ hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
8696
+ }
8697
+ if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
8698
+ 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.");
8699
+ }
8700
+ if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
8701
+ hints.push("Module not found. Run npm install or check if the import path is correct.");
8702
+ }
8703
+ if (/EACCES|EPERM|permission denied/i.test(error)) {
8704
+ hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
8705
+ }
8706
+ if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
8707
+ hints.push("Network error. Check if the server is running and accessible.");
8708
+ }
8709
+ if (/is not recognized|command not found|not found in path/i.test(error)) {
8710
+ hints.push("Command not found. Check if the tool is installed and in PATH.");
8711
+ }
8712
+ if (this.hasExcessiveRewrites()) {
8713
+ const file = this.getExcessiveRewriteFile();
8714
+ hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
8715
+ }
8716
+ return hints;
8717
+ }
8718
+ getToolAlternative() {
8719
+ const tool = this.lastFailedTool;
8720
+ const error = this.lastErrorOutput;
8721
+ if (!tool)
8722
+ return null;
8723
+ if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
8724
+ const alternatives = {
8725
+ "ts-node": "tsx",
8726
+ jest: "vitest",
8727
+ mocha: "vitest",
8728
+ webpack: "vite",
8729
+ rollup: "vite",
8730
+ parcel: "vite",
8731
+ babel: "tsc",
8732
+ eslint: "biome",
8733
+ prettier: "biome"
8734
+ };
8735
+ for (const [failed, alt] of Object.entries(alternatives)) {
8736
+ if (tool.includes(failed) || error.includes(failed)) {
8737
+ return alt;
8738
+ }
8739
+ }
8740
+ }
8741
+ return null;
8742
+ }
8743
+ recordFileRewrite(filePath) {
8744
+ const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
8745
+ this.fileRewriteCount.set(filePath, count);
8746
+ }
8747
+ getFileRewriteCount(filePath) {
8748
+ return this.fileRewriteCount.get(filePath) || 0;
8749
+ }
8750
+ hasExcessiveRewrites() {
8751
+ return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
8752
+ }
8753
+ getExcessiveRewriteFile() {
8754
+ for (const [file, count] of this.fileRewriteCount) {
8755
+ if (count >= this.fileRewriteThreshold)
8756
+ return file;
8757
+ }
8758
+ return null;
8759
+ }
8760
+ getRepetitiveToolMessage() {
8761
+ if (!this.hasRepetitiveToolCalls())
8762
+ return "";
8763
+ const last = this.recentToolCalls[this.recentToolCalls.length - 1];
8764
+ return t("exec.repetitive_tool", {
8765
+ tool: last.name,
8766
+ count: this.repetitionThreshold
8767
+ });
8768
+ }
8769
+ getStuckReason() {
8770
+ if (this.isStuck()) {
8771
+ return t("exec.stuck", {
8772
+ stepId: String(this.currentStepId ?? "?"),
8773
+ description: this.currentStepDescription,
8774
+ iterations: this.iterationsOnCurrentStep
8775
+ });
8776
+ }
8777
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
8778
+ if (errorTool) {
8779
+ return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
8780
+ }
8781
+ return "";
8782
+ }
8783
+ getRecoveryMessage() {
8784
+ if (this.isStuck()) {
8785
+ return t("exec.stuck_recovery", {
8786
+ iterations: this.iterationsOnCurrentStep,
8787
+ stepId: String(this.currentStepId ?? "?"),
8788
+ description: this.currentStepDescription
8789
+ });
8790
+ }
8791
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
8792
+ if (errorTool) {
8793
+ return t("exec.tool_errors_recovery", {
8794
+ tool: errorTool[0],
8795
+ count: errorTool[1]
8796
+ });
8797
+ }
8798
+ if (this.hasConsecutiveFailures()) {
8799
+ return t("exec.consecutive_failures_recovery", {
8800
+ count: this.consecutiveFailures
8801
+ });
8802
+ }
8803
+ if (this.hasRepetitiveToolCalls()) {
8804
+ return this.getRepetitiveToolMessage();
8805
+ }
8806
+ return "";
8807
+ }
8808
+ checkOffTrack(toolName, call) {
8809
+ if (!this.currentStepDescription)
8810
+ return null;
8811
+ const toolArgStr = JSON.stringify(call.arguments).toLowerCase();
8812
+ const pathInStep = (this.currentStepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
8813
+ const pathInCall = (toolArgStr.match(/\b[\w./-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
8814
+ const overlaps = pathInCall.some((p) => pathInStep.some((s) => p.includes(s) || s.includes(p)));
8815
+ if (!overlaps && pathInStep.length > 0 && pathInCall.length > 0) {
8816
+ return t("exec.off_track", {
8817
+ stepId: String(this.currentStepId),
8818
+ description: this.currentStepDescription,
8819
+ tool: toolName
8820
+ });
8821
+ }
8822
+ return null;
8823
+ }
8824
+ reset() {
8825
+ this.currentStepId = null;
8826
+ this.iterationsOnCurrentStep = 0;
8827
+ this.toolErrors.clear();
8828
+ this.consecutiveFailures = 0;
8829
+ this.lastFailedTool = "";
8830
+ this.lastErrorOutput = "";
8831
+ this.recentToolCalls = [];
8832
+ this.fileRewriteCount.clear();
8833
+ this.escalationCount = 0;
8834
+ }
8835
+ resetStepState(stepId) {
8836
+ this.currentStepId = stepId;
8837
+ this.iterationsOnCurrentStep = 1;
8838
+ this.toolErrors.clear();
8839
+ this.consecutiveFailures = 0;
8840
+ this.lastFailedTool = "";
8841
+ this.lastErrorOutput = "";
8842
+ this.recentToolCalls = [];
8843
+ this.fileRewriteCount.clear();
8844
+ this.escalationCount = 0;
8845
+ }
8846
+ }
8847
+ var init_stuck_detector = __esm(() => {
8848
+ init_i18n();
8849
+ });
8850
+
8851
+ // src/modules/skills/error-skill-map.ts
8852
+ function suggestSkill(errorOutput) {
8853
+ for (const { pattern, skill } of ERROR_SKILL_MAP) {
8854
+ if (pattern.test(errorOutput))
8855
+ return skill;
8856
+ }
8857
+ return null;
8858
+ }
8859
+ var ERROR_SKILL_MAP;
8860
+ var init_error_skill_map = __esm(() => {
8861
+ ERROR_SKILL_MAP = [
8862
+ { pattern: /TS\d{4}:/i, skill: "typescript-expert" },
8863
+ { pattern: /Cannot find name|No overload matches/i, skill: "typescript-type-expert" },
8864
+ { pattern: /ts-node|tsx.*error/i, skill: "typescript-expert" },
8865
+ { pattern: /Module not found|Cannot resolve module/i, skill: "typescript-expert" },
8866
+ { pattern: /ECONNREFUSED|ETIMEDOUT|fetch failed/i, skill: "devops-expert" },
8867
+ { pattern: /permission denied|EACCES/i, skill: "linux-server-expert" },
8868
+ { pattern: /docker|container/i, skill: "docker-expert" },
8869
+ { pattern: /jest|vitest|test.*fail/i, skill: "vitest-testing-expert" }
8870
+ ];
8871
+ });
8872
+
8873
+ // src/modules/execution/moe-executor.ts
8874
+ function isTransientError(error) {
8875
+ return TRANSIENT_ERROR_PATTERNS.some((p) => error.includes(p));
8876
+ }
8877
+ function sleep(ms) {
8878
+ return new Promise((r) => setTimeout(r, ms));
8879
+ }
8880
+ function topologicalSort(subtasks) {
8881
+ const sorted = [];
8882
+ const remaining = new Set(subtasks.map((s) => s.id));
8883
+ const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
8884
+ while (remaining.size > 0) {
8885
+ const ready = [];
8886
+ for (const id of remaining) {
8887
+ const sub = subtaskMap.get(id);
8888
+ const depsSatisfied = (sub.depends_on || []).every((d) => !remaining.has(d));
8889
+ if (depsSatisfied) {
8890
+ ready.push(sub);
8891
+ }
8892
+ }
8893
+ if (ready.length === 0) {
8894
+ break;
8895
+ }
8896
+ sorted.push(ready);
8897
+ for (const r of ready) {
8898
+ remaining.delete(r.id);
8899
+ }
8900
+ }
8901
+ return sorted;
8902
+ }
8903
+ async function executeSubtask(subtask, deps, _sharedContext) {
8904
+ const startTime = Date.now();
8905
+ const expertConfig = getExpertConfig(deps.config, subtask.expert_tag);
8906
+ if (!expertConfig) {
8907
+ return {
8908
+ subtaskId: subtask.id,
8909
+ success: false,
8910
+ summary: `Unknown expert_tag: ${subtask.expert_tag}`,
8911
+ result: "",
8912
+ error: `No expert config found for tag "${subtask.expert_tag}"`,
8913
+ durationMs: Date.now() - startTime
8914
+ };
8915
+ }
8916
+ const subagentTool = deps.toolRegistry.get("subagent");
8917
+ if (!subagentTool) {
8918
+ return {
8919
+ subtaskId: subtask.id,
8920
+ success: false,
8921
+ summary: "Subagent tool not registered",
8922
+ result: "",
8923
+ error: "Subagent tool not found in registry",
8924
+ durationMs: Date.now() - startTime
8925
+ };
8926
+ }
8927
+ const allTools = deps.toolRegistry.getAll();
8928
+ const filteredTools = filterToolsByTags(allTools, expertConfig.tool_tags);
8929
+ if (filteredTools.length === 0) {
8930
+ return {
8931
+ subtaskId: subtask.id,
8932
+ success: false,
8933
+ summary: `No tools available for expert "${subtask.expert_tag}" (tags: ${expertConfig.tool_tags.join(", ")})`,
8934
+ result: "",
8935
+ error: "No matching tools",
8936
+ durationMs: Date.now() - startTime
8937
+ };
8616
8938
  }
8617
8939
  const maxAttempts = expertConfig.max_attempts || 3;
8618
8940
  let lastError = "";
8619
8941
  let lastResult = null;
8942
+ const stuckDetector = new StuckDetector(maxAttempts * 2, maxAttempts);
8620
8943
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
8944
+ stuckDetector.recordIteration(1);
8621
8945
  try {
8622
8946
  const allowedFiles = subtask.allowed_files || [];
8623
8947
  const readOnlyFiles = subtask.read_only_files || [];
@@ -8655,44 +8979,90 @@ ${(subtask.success_criteria || []).map((c) => `- ${c}`).join(`
8655
8979
  }
8656
8980
  lastError = result.output;
8657
8981
  lastResult = result;
8982
+ stuckDetector.recordToolError("subagent", result.output);
8983
+ if (attempt < maxAttempts) {
8984
+ const hints2 = stuckDetector.getActionableHints();
8985
+ const recovery = stuckDetector.getRecoveryMessage();
8986
+ if (hints2.length > 0) {
8987
+ deps.logger.debug(`Hint for subtask "${subtask.id}" retry: ${hints2.join("; ")}`);
8988
+ }
8989
+ if (recovery) {
8990
+ deps.logger.debug(`Recovery for subtask "${subtask.id}": ${recovery}`);
8991
+ }
8992
+ }
8658
8993
  if (isTransientError(lastError) && attempt < maxAttempts) {
8659
8994
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
8660
8995
  deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: transient error`);
8661
8996
  await sleep(delay);
8662
8997
  continue;
8663
8998
  }
8999
+ const hints = stuckDetector.getActionableHints();
9000
+ const suggestedSkill = suggestSkill(lastError);
9001
+ let errorDetail = result.output;
9002
+ if (hints.length > 0) {
9003
+ errorDetail += `
9004
+
9005
+ ${t("exec.hints", { hints: hints.map((h) => `- ${h}`).join(`
9006
+ `) })}`;
9007
+ }
9008
+ if (suggestedSkill) {
9009
+ errorDetail += `
9010
+
9011
+ Consider loading the "${suggestedSkill}" skill for expert guidance.`;
9012
+ }
8664
9013
  return {
8665
9014
  subtaskId: subtask.id,
8666
9015
  success: false,
8667
9016
  summary: `Failed: ${subtask.id}`,
8668
9017
  result: result.output,
8669
- error: result.output,
9018
+ error: errorDetail,
8670
9019
  durationMs: Date.now() - startTime
8671
9020
  };
8672
9021
  } catch (e) {
8673
9022
  lastError = e.message;
9023
+ stuckDetector.recordToolError("subagent", e.message);
8674
9024
  if (isTransientError(lastError) && attempt < maxAttempts) {
8675
9025
  const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
8676
9026
  deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: ${lastError}`);
8677
9027
  await sleep(delay);
8678
9028
  continue;
8679
9029
  }
9030
+ const hints = stuckDetector.getActionableHints();
9031
+ const suggestedSkill = suggestSkill(lastError);
9032
+ let errorDetail = e.message;
9033
+ if (hints.length > 0) {
9034
+ errorDetail += `
9035
+
9036
+ ${t("exec.hints", { hints: hints.map((h) => `- ${h}`).join(`
9037
+ `) })}`;
9038
+ }
9039
+ if (suggestedSkill) {
9040
+ errorDetail += `
9041
+
9042
+ Consider loading the "${suggestedSkill}" skill for expert guidance.`;
9043
+ }
8680
9044
  return {
8681
9045
  subtaskId: subtask.id,
8682
9046
  success: false,
8683
9047
  summary: `Error: ${subtask.id}`,
8684
9048
  result: "",
8685
- error: e.message,
9049
+ error: errorDetail,
8686
9050
  durationMs: Date.now() - startTime
8687
9051
  };
8688
9052
  }
8689
9053
  }
9054
+ const stuckReason = stuckDetector.getStuckReason();
9055
+ let finalError = lastError;
9056
+ if (stuckReason) {
9057
+ finalError += `
9058
+ ${stuckReason}`;
9059
+ }
8690
9060
  return {
8691
9061
  subtaskId: subtask.id,
8692
9062
  success: false,
8693
9063
  summary: `Failed after ${maxAttempts} attempts: ${subtask.id}`,
8694
9064
  result: "",
8695
- error: lastError,
9065
+ error: finalError,
8696
9066
  durationMs: Date.now() - startTime
8697
9067
  };
8698
9068
  }
@@ -8751,6 +9121,9 @@ class MoEExecutor {
8751
9121
  }
8752
9122
  var TRANSIENT_ERROR_PATTERNS;
8753
9123
  var init_moe_executor = __esm(() => {
9124
+ init_stuck_detector();
9125
+ init_error_skill_map();
9126
+ init_i18n();
8754
9127
  TRANSIENT_ERROR_PATTERNS = [
8755
9128
  "timeout",
8756
9129
  "Timeout",
@@ -8772,7 +9145,7 @@ var init_moe_executor = __esm(() => {
8772
9145
  // src/modules/execution/verifier.ts
8773
9146
  import { existsSync as existsSync17 } from "fs";
8774
9147
  import { resolve as resolve10, extname as extname2 } from "path";
8775
- import { execSync as execSync2 } from "child_process";
9148
+ import { spawn as spawn2 } from "child_process";
8776
9149
 
8777
9150
  class StepVerifier {
8778
9151
  baseDir;
@@ -8789,12 +9162,7 @@ class StepVerifier {
8789
9162
  }
8790
9163
  async runScript(scriptName) {
8791
9164
  try {
8792
- execSync2(`bun run ${scriptName}`, {
8793
- cwd: this.baseDir,
8794
- encoding: "utf-8",
8795
- timeout: 60000,
8796
- stdio: "pipe"
8797
- });
9165
+ await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60000);
8798
9166
  return { passed: true, message: t("verify.script_passed", { script: scriptName }) };
8799
9167
  } catch (e) {
8800
9168
  return { passed: false, message: t("verify.script_failed", { script: scriptName, message: e.message }) };
@@ -8806,12 +9174,7 @@ class StepVerifier {
8806
9174
  return { passed: true, message: "No tsconfig.json found — skipping type check" };
8807
9175
  }
8808
9176
  try {
8809
- execSync2("npx tsc --noEmit", {
8810
- cwd: this.baseDir,
8811
- encoding: "utf-8",
8812
- timeout: 60000,
8813
- stdio: "pipe"
8814
- });
9177
+ await this.runAsync("npx tsc --noEmit", this.baseDir, 60000);
8815
9178
  return { passed: true, message: "TypeScript type check passed" };
8816
9179
  } catch (e) {
8817
9180
  const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
@@ -8873,7 +9236,7 @@ class StepVerifier {
8873
9236
  results.push(result);
8874
9237
  if (result.passed) {
8875
9238
  const fullPath = resolve10(this.baseDir, filePath);
8876
- if (!this.validateSyntax(fullPath)) {
9239
+ if (!await this.validateSyntax(fullPath)) {
8877
9240
  syntaxValid = false;
8878
9241
  results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
8879
9242
  }
@@ -8885,11 +9248,11 @@ class StepVerifier {
8885
9248
  failed: results.filter((r) => !r.passed)
8886
9249
  };
8887
9250
  }
8888
- validateSyntax(filePath) {
9251
+ async validateSyntax(filePath) {
8889
9252
  const ext = extname2(filePath);
8890
9253
  if (ext === ".ts" || ext === ".tsx") {
8891
9254
  try {
8892
- execSync2(`npx tsc --noEmit --skipLibCheck ${filePath}`, { stdio: "pipe", timeout: 1e4 });
9255
+ await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 1e4);
8893
9256
  return true;
8894
9257
  } catch (err) {
8895
9258
  if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
@@ -8904,7 +9267,7 @@ class StepVerifier {
8904
9267
  }
8905
9268
  if (ext === ".js" || ext === ".jsx") {
8906
9269
  try {
8907
- execSync2(`node --check ${filePath}`, { stdio: "pipe", timeout: 5000 });
9270
+ await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
8908
9271
  return true;
8909
9272
  } catch {
8910
9273
  return false;
@@ -8912,6 +9275,44 @@ class StepVerifier {
8912
9275
  }
8913
9276
  return true;
8914
9277
  }
9278
+ runAsync(command, cwd, timeoutMs) {
9279
+ return new Promise((resolve11, reject) => {
9280
+ const child = spawn2(command, {
9281
+ cwd,
9282
+ shell: true,
9283
+ windowsHide: true,
9284
+ stdio: ["ignore", "pipe", "pipe"]
9285
+ });
9286
+ let stdout = "";
9287
+ let stderr = "";
9288
+ child.stdout?.on("data", (d) => {
9289
+ stdout += d.toString();
9290
+ });
9291
+ child.stderr?.on("data", (d) => {
9292
+ stderr += d.toString();
9293
+ });
9294
+ const timer = setTimeout(() => {
9295
+ child.kill();
9296
+ reject(new Error(`Command timed out after ${timeoutMs}ms`));
9297
+ }, timeoutMs);
9298
+ child.on("error", (err) => {
9299
+ clearTimeout(timer);
9300
+ reject(err);
9301
+ });
9302
+ child.on("close", (code) => {
9303
+ clearTimeout(timer);
9304
+ if (code === 0) {
9305
+ resolve11({ stdout, stderr });
9306
+ } else {
9307
+ const err = new Error(`Command failed with exit code ${code}`);
9308
+ err.stdout = stdout;
9309
+ err.stderr = stderr;
9310
+ err.status = code;
9311
+ reject(err);
9312
+ }
9313
+ });
9314
+ });
9315
+ }
8915
9316
  }
8916
9317
  var init_verifier = __esm(() => {
8917
9318
  init_i18n();
@@ -9240,6 +9641,17 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9240
9641
  logger,
9241
9642
  sessionManager: sessionManager?.getActiveMeta()
9242
9643
  });
9644
+ if (config.session?.baselineCheck !== false) {
9645
+ const verifier = new StepVerifier(baseDir);
9646
+ verifier.runTypeCheck().then((tc) => {
9647
+ if (!tc.passed) {
9648
+ logger.warn(`Baseline typecheck has issues: ${tc.message?.slice(0, 500)}`);
9649
+ onMeta?.(pc.yellow(`
9650
+ ⚠ Baseline typecheck has issues
9651
+ `));
9652
+ }
9653
+ }).catch(() => {});
9654
+ }
9243
9655
  }
9244
9656
  contextManager.addMessage({ role: "user", content: input });
9245
9657
  slog.logUser(input);
@@ -9287,7 +9699,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9287
9699
  logger,
9288
9700
  lastUserMessage: input,
9289
9701
  contextManager,
9290
- onMeta
9702
+ onMeta,
9703
+ sessionLog: {
9704
+ plan: (event, detail, iter) => slog.logPlan(event, detail, iter)
9705
+ }
9291
9706
  });
9292
9707
  if (contextManager.needsCompaction()) {
9293
9708
  contextManager.compact();
@@ -9296,6 +9711,12 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9296
9711
  }
9297
9712
  const currentTokens = contextManager.getEstimatedTokens();
9298
9713
  const budget2 = contextManager.getBudget();
9714
+ const quality = contextManager.getQuality();
9715
+ if (quality < QUALITY_TRIGGER_THRESHOLD && contextManager.getCompactionCount() > 0) {
9716
+ contextManager.compact();
9717
+ logger.warn(`Low context quality (${quality}%) — forced compaction`);
9718
+ slog.logCompaction(`quality-triggered compaction (${quality}% < ${QUALITY_TRIGGER_THRESHOLD}%), iteration ${iteration}`, iteration, currentTokens, budget2.history);
9719
+ }
9299
9720
  if (currentTokens > budget2.history) {
9300
9721
  contextManager.compact();
9301
9722
  logger.warn(`Context overflow (${currentTokens} > ${budget2.history}), forced compaction`);
@@ -9423,6 +9844,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9423
9844
  const duration = Date.now() - startTime;
9424
9845
  if (!result.success)
9425
9846
  anyToolFailed = true;
9847
+ if (result.success && call.arguments.path) {
9848
+ const filePath = String(call.arguments.path);
9849
+ if (call.name === "write_file" || call.name === "edit_file") {
9850
+ hallucinationDetector.getConsistencyCheck().trackCreatedFile(filePath);
9851
+ } else if (call.name === "delete_file") {
9852
+ hallucinationDetector.getConsistencyCheck().trackDeletedFile(filePath);
9853
+ }
9854
+ }
9426
9855
  pluginManager.runOnToolEnd({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments }, result, duration);
9427
9856
  if (result.display) {
9428
9857
  onMeta?.(`
@@ -9507,14 +9936,15 @@ ${taskReminder}</system-summary>`
9507
9936
  const ctxBar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
9508
9937
  const pctColor = ctxPct >= 75 ? pc.yellow : pc.dim;
9509
9938
  const compCount = contextManager.getCompactionCount();
9510
- const quality = contextManager.getQuality();
9511
- const qualityColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
9939
+ const quality2 = contextManager.getQuality();
9940
+ const qualityColor = quality2 >= 70 ? pc.green : quality2 >= 40 ? pc.yellow : pc.red;
9512
9941
  onMeta?.(`
9513
- ${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${quality}%`)}
9942
+ ${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${quality2}%`)}
9514
9943
  `);
9515
9944
  continue;
9516
9945
  }
9517
- const hallucinationResult = hallucinationDetector.validate(textContent);
9946
+ hallucinationDetector.getConfidenceCheck().setPreviousResponse(lastText);
9947
+ const hallucinationResult = await hallucinationDetector.validate(textContent);
9518
9948
  if (hallucinationResult.status === "block") {
9519
9949
  logger.warn(`Response blocked: ${hallucinationResult.reason}`);
9520
9950
  return {
@@ -9570,6 +10000,14 @@ ${taskReminder}</system-summary>`
9570
10000
  slog.logAssistant(textContent, reasoningContent, undefined, iteration);
9571
10001
  }
9572
10002
  lastText = textContent;
10003
+ {
10004
+ const decisionPatterns = [
10005
+ ...textContent.matchAll(/(?:plan|decided|decision|решено|план|решение):\s*(.+?)(?:\n|$)/gi)
10006
+ ];
10007
+ for (const match of decisionPatterns) {
10008
+ hallucinationDetector.getConsistencyCheck().trackDecision(match[1].trim(), "agent_response");
10009
+ }
10010
+ }
9573
10011
  if (!sawToolCall) {
9574
10012
  if (this.deps.finalAudit) {
9575
10013
  const audit = await this.deps.finalAudit();
@@ -9677,7 +10115,7 @@ ${taskReminder}</system-summary>`
9677
10115
  });
9678
10116
  }
9679
10117
  }
9680
- var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000;
10118
+ var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40;
9681
10119
  var init_agent = __esm(() => {
9682
10120
  init_i18n();
9683
10121
  init_colors();
@@ -9685,6 +10123,7 @@ var init_agent = __esm(() => {
9685
10123
  init_processes();
9686
10124
  init_agent_moe();
9687
10125
  init_store();
10126
+ init_verifier();
9688
10127
  });
9689
10128
 
9690
10129
  // src/modules/context/manager.ts
@@ -9990,53 +10429,16 @@ class ConsistencyCheck {
9990
10429
  getCreatedFiles() {
9991
10430
  return Array.from(this.createdFiles);
9992
10431
  }
9993
- validate(response) {
9994
- const lower = response.toLowerCase();
9995
- for (const d of this.decisions) {
9996
- const decisionWords = d.decision.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
9997
- const contradicts = decisionWords.some((word) => {
9998
- if (lower.includes(`instead of ${word}`))
9999
- return true;
10000
- if (lower.includes(`not ${word}`))
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" };
10432
+ getDeletedFiles() {
10433
+ return Array.from(this.deletedFiles);
10434
+ }
10435
+ getDecisions() {
10436
+ return this.decisions;
10437
+ }
10438
+ hasDecisions() {
10439
+ return this.decisions.length > 0;
10035
10440
  }
10036
10441
  }
10037
- var init_consistency = __esm(() => {
10038
- init_i18n();
10039
- });
10040
10442
 
10041
10443
  // src/modules/hallucination/confidence.ts
10042
10444
  class ConfidenceCheck {
@@ -10078,19 +10480,193 @@ class ConfidenceCheck {
10078
10480
  };
10079
10481
  }
10080
10482
  }
10081
- return { status: "pass" };
10082
- }
10083
- calculateOverlap(a, b) {
10084
- const wordsA = new Set(a.toLowerCase().split(/\s+/));
10085
- const wordsB = b.toLowerCase().split(/\s+/);
10086
- if (wordsB.length === 0)
10087
- return 0;
10088
- const matches = wordsB.filter((w) => wordsA.has(w));
10089
- return matches.length / wordsB.length;
10483
+ return { status: "pass" };
10484
+ }
10485
+ calculateOverlap(a, b) {
10486
+ const wordsA = new Set(a.toLowerCase().split(/\s+/));
10487
+ const wordsB = b.toLowerCase().split(/\s+/);
10488
+ if (wordsB.length === 0)
10489
+ return 0;
10490
+ const matches = wordsB.filter((w) => wordsA.has(w));
10491
+ return matches.length / wordsB.length;
10492
+ }
10493
+ }
10494
+ var MIN_CHARS = 1, MIN_WORDS = 5;
10495
+ var init_confidence = __esm(() => {
10496
+ init_i18n();
10497
+ });
10498
+
10499
+ // src/modules/hallucination/factual.ts
10500
+ import { existsSync as existsSync20 } from "fs";
10501
+ import { resolve as resolve11, isAbsolute } from "path";
10502
+ function looksLikeFilePath(s) {
10503
+ const lower = s.toLowerCase();
10504
+ if (TECH_NAMES.has(lower))
10505
+ return false;
10506
+ if (!s.includes("/") && !s.includes("\\") && !s.match(/^\w+\.[a-z]{2,4}$/i))
10507
+ return false;
10508
+ const ext = s.split(".").pop()?.toLowerCase() || "";
10509
+ return !NON_FILE_EXTENSIONS.has(ext);
10510
+ }
10511
+
10512
+ class FactualCheck {
10513
+ baseDir;
10514
+ constructor(baseDir) {
10515
+ this.baseDir = baseDir;
10516
+ }
10517
+ validate(response) {
10518
+ const filePaths = this.extractFilePaths(response);
10519
+ if (filePaths.length === 0)
10520
+ return { status: "pass" };
10521
+ const nonExistent = [];
10522
+ for (const fp of filePaths) {
10523
+ const abs = isAbsolute(fp) ? fp : resolve11(this.baseDir, fp);
10524
+ if (!existsSync20(abs)) {
10525
+ nonExistent.push(fp);
10526
+ }
10527
+ }
10528
+ if (nonExistent.length > 0) {
10529
+ return {
10530
+ status: "warn",
10531
+ reason: t("hall.unknown_paths", {
10532
+ paths: nonExistent.slice(0, 5).join(", ")
10533
+ })
10534
+ };
10535
+ }
10536
+ return { status: "pass" };
10537
+ }
10538
+ extractFilePaths(text) {
10539
+ const pattern = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{2,6}\b/gi;
10540
+ const matches = text.match(pattern) || [];
10541
+ return [...new Set(matches)].filter(looksLikeFilePath);
10542
+ }
10543
+ }
10544
+ var NON_FILE_EXTENSIONS, TECH_NAMES;
10545
+ var init_factual = __esm(() => {
10546
+ init_i18n();
10547
+ NON_FILE_EXTENSIONS = new Set([
10548
+ "com",
10549
+ "org",
10550
+ "net",
10551
+ "gov",
10552
+ "edu",
10553
+ "io",
10554
+ "dev",
10555
+ "app",
10556
+ "co",
10557
+ "ai",
10558
+ "ru",
10559
+ "de",
10560
+ "fr",
10561
+ "uk",
10562
+ "us",
10563
+ "es",
10564
+ "it",
10565
+ "jp",
10566
+ "cn",
10567
+ "br"
10568
+ ]);
10569
+ TECH_NAMES = new Set([
10570
+ "node.js",
10571
+ "react.js",
10572
+ "vue.js",
10573
+ "next.js",
10574
+ "angular.js",
10575
+ "express.js",
10576
+ "deno.js",
10577
+ "qwik.js",
10578
+ "svelte.js",
10579
+ "jquery.js"
10580
+ ]);
10581
+ });
10582
+
10583
+ // src/modules/hallucination/llm-judge.ts
10584
+ class LLMJudge {
10585
+ provider;
10586
+ constructor(provider) {
10587
+ this.provider = provider;
10588
+ }
10589
+ async validate(response, consistency) {
10590
+ const decisions = consistency.getDecisions();
10591
+ if (decisions.length === 0) {
10592
+ return { status: "pass" };
10593
+ }
10594
+ const shown = decisions.slice(-MAX_DECISIONS_SHOWN);
10595
+ const decisionLines = shown.map((d, i) => `${i + 1}. [${d.location || "agent"}] "${d.decision}"`).join(`
10596
+ `);
10597
+ const system = `You are a consistency checker for an AI coding agent. The agent previously made these decisions:
10598
+
10599
+ ${decisionLines}
10600
+
10601
+ 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).
10602
+
10603
+ Reply with ONLY valid JSON, no other text:
10604
+ - {"contradicts": false}
10605
+ - {"contradicts": true, "reason": "short explanation"}`;
10606
+ const user = `Agent's new response:
10607
+
10608
+ ${response.slice(0, MAX_RESPONSE_CHARS)}`;
10609
+ try {
10610
+ const verdictText = await this.askJudge([
10611
+ { role: "system", content: system },
10612
+ { role: "user", content: user }
10613
+ ]);
10614
+ const verdict = this.parseVerdict(verdictText);
10615
+ if (verdict.contradicts) {
10616
+ const reason = verdict.reason?.trim() ? ` — ${verdict.reason.trim()}` : "";
10617
+ return {
10618
+ status: "warn",
10619
+ reason: t("hall.contradiction_llm", { reason })
10620
+ };
10621
+ }
10622
+ return { status: "pass" };
10623
+ } catch {
10624
+ return { status: "pass" };
10625
+ }
10626
+ }
10627
+ async askJudge(messages) {
10628
+ let text = "";
10629
+ let timer;
10630
+ const consume = (async () => {
10631
+ for await (const chunk of this.provider.chat(messages, [])) {
10632
+ if (chunk.type === "text" && chunk.content) {
10633
+ text += chunk.content;
10634
+ }
10635
+ }
10636
+ return text;
10637
+ })();
10638
+ const timeout = new Promise((_, reject) => {
10639
+ timer = setTimeout(() => reject(new Error("LLM judge timed out")), JUDGE_TIMEOUT_MS);
10640
+ });
10641
+ try {
10642
+ return await Promise.race([consume, timeout]);
10643
+ } finally {
10644
+ if (timer)
10645
+ clearTimeout(timer);
10646
+ }
10647
+ }
10648
+ parseVerdict(text) {
10649
+ const block = text.match(/\{[\s\S]*\}/);
10650
+ if (block) {
10651
+ try {
10652
+ const obj = JSON.parse(block[0]);
10653
+ if (typeof obj.contradicts === "boolean") {
10654
+ return {
10655
+ contradicts: obj.contradicts,
10656
+ reason: typeof obj.reason === "string" ? obj.reason : undefined
10657
+ };
10658
+ }
10659
+ } catch {}
10660
+ }
10661
+ const lower = text.toLowerCase();
10662
+ if (lower.includes('"contradicts": true') || /^yes\b/.test(lower.trim())) {
10663
+ return { contradicts: true, reason: text.slice(0, 200) };
10664
+ }
10665
+ return { contradicts: false };
10090
10666
  }
10091
10667
  }
10092
- var MIN_CHARS = 1, MIN_WORDS = 5;
10093
- var init_confidence = __esm(() => {
10668
+ var JUDGE_TIMEOUT_MS = 15000, MAX_DECISIONS_SHOWN = 6, MAX_RESPONSE_CHARS = 2000;
10669
+ var init_llm_judge = __esm(() => {
10094
10670
  init_i18n();
10095
10671
  });
10096
10672
 
@@ -10098,9 +10674,13 @@ var init_confidence = __esm(() => {
10098
10674
  class HallucinationDetector {
10099
10675
  consistency;
10100
10676
  confidence;
10101
- constructor() {
10677
+ factual;
10678
+ judge;
10679
+ constructor(baseDir, llmProvider) {
10102
10680
  this.consistency = new ConsistencyCheck;
10103
10681
  this.confidence = new ConfidenceCheck;
10682
+ this.factual = baseDir ? new FactualCheck(baseDir) : null;
10683
+ this.judge = llmProvider ? new LLMJudge(llmProvider) : null;
10104
10684
  }
10105
10685
  getConsistencyCheck() {
10106
10686
  return this.consistency;
@@ -10108,15 +10688,19 @@ class HallucinationDetector {
10108
10688
  getConfidenceCheck() {
10109
10689
  return this.confidence;
10110
10690
  }
10111
- validate(response) {
10691
+ async validate(response) {
10112
10692
  const confidenceResult = this.confidence.validate(response);
10113
10693
  if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
10114
10694
  return confidenceResult;
10115
10695
  }
10116
- const consistencyResult = this.consistency.validate(response);
10696
+ const factualResult = this.factual?.validate(response);
10697
+ if (factualResult && factualResult.status !== "pass") {
10698
+ return factualResult;
10699
+ }
10700
+ const judgeResult = this.judge ? await this.judge.validate(response, this.consistency) : null;
10117
10701
  const warnings = [];
10118
- if (consistencyResult.status === "warn")
10119
- warnings.push(consistencyResult.reason || "");
10702
+ if (judgeResult && judgeResult.status === "warn")
10703
+ warnings.push(judgeResult.reason || "");
10120
10704
  if (confidenceResult.status === "warn")
10121
10705
  warnings.push(confidenceResult.reason || "");
10122
10706
  if (warnings.length > 0) {
@@ -10126,8 +10710,9 @@ class HallucinationDetector {
10126
10710
  }
10127
10711
  }
10128
10712
  var init_detector = __esm(() => {
10129
- init_consistency();
10130
10713
  init_confidence();
10714
+ init_factual();
10715
+ init_llm_judge();
10131
10716
  });
10132
10717
 
10133
10718
  // src/modules/plugins/manager.ts
@@ -10370,7 +10955,7 @@ var init_subagent = __esm(() => {
10370
10955
  try {
10371
10956
  const subContextManager = new ContextManager(ctx.config.contextWindow, ctx.config.contextBudget);
10372
10957
  const subPluginManager = new PluginManager;
10373
- const hallucinationDetector = new HallucinationDetector;
10958
+ const hallucinationDetector = new HallucinationDetector(ctx.baseDir, ctx.llmProvider);
10374
10959
  const systemPrompt = {
10375
10960
  content: `You are a sub-agent working on a specific task. ${context ? `Context: ${context}` : ""}`,
10376
10961
  priority: "critical",
@@ -10896,7 +11481,7 @@ async function runStep(ctx, step, params, outputs) {
10896
11481
  toolExecutor: ctx.toolExecutor,
10897
11482
  pluginManager: subPluginManager,
10898
11483
  contextManager: subContextManager,
10899
- hallucinationDetector: new HallucinationDetector,
11484
+ hallucinationDetector: new HallucinationDetector(ctx.baseDir, ctx.llmProvider),
10900
11485
  logger: ctx.logger,
10901
11486
  baseDir: ctx.baseDir,
10902
11487
  scope: ctx.scope,
@@ -11126,7 +11711,7 @@ class MCPClient {
11126
11711
  }
11127
11712
  }
11128
11713
  connectStdio() {
11129
- return new Promise((resolve11, reject) => {
11714
+ return new Promise((resolve12, reject) => {
11130
11715
  const child = spawn3(this.config.command, this.config.args || [], {
11131
11716
  env: { ...process.env, ...this.config.env },
11132
11717
  stdio: ["pipe", "pipe", "pipe"]
@@ -11144,7 +11729,7 @@ class MCPClient {
11144
11729
  child.on("spawn", () => {
11145
11730
  this._connected = true;
11146
11731
  this.process = child;
11147
- resolve11();
11732
+ resolve12();
11148
11733
  });
11149
11734
  });
11150
11735
  }
@@ -11213,7 +11798,7 @@ class MCPClient {
11213
11798
  if (!this.process) {
11214
11799
  throw new Error("Not connected to MCP server");
11215
11800
  }
11216
- return new Promise((resolve11, reject) => {
11801
+ return new Promise((resolve12, reject) => {
11217
11802
  const request = {
11218
11803
  jsonrpc: "2.0",
11219
11804
  id: Date.now(),
@@ -11237,7 +11822,7 @@ class MCPClient {
11237
11822
  if (response.error) {
11238
11823
  reject(new Error(response.error.message));
11239
11824
  } else {
11240
- resolve11(response.result?.tools || []);
11825
+ resolve12(response.result?.tools || []);
11241
11826
  }
11242
11827
  return;
11243
11828
  }
@@ -11319,8 +11904,8 @@ class MCPClient {
11319
11904
  arguments: args
11320
11905
  }
11321
11906
  };
11322
- return new Promise((resolve11, reject) => {
11323
- this.pendingResolve = resolve11;
11907
+ return new Promise((resolve12, reject) => {
11908
+ this.pendingResolve = resolve12;
11324
11909
  this.pendingReject = reject;
11325
11910
  const timeout = this.config.timeout || 30000;
11326
11911
  const timer = setTimeout(() => {
@@ -11347,7 +11932,7 @@ class MCPClient {
11347
11932
  if (result.error) {
11348
11933
  reject(new Error(result.error.message));
11349
11934
  } else {
11350
- resolve11(result.result || result);
11935
+ resolve12(result.result || result);
11351
11936
  }
11352
11937
  }).catch((err) => {
11353
11938
  clearTimeout(timer);
@@ -11361,7 +11946,7 @@ class MCPClient {
11361
11946
  if (!this.process) {
11362
11947
  throw new Error("Not connected to MCP server");
11363
11948
  }
11364
- return new Promise((resolve11, reject) => {
11949
+ return new Promise((resolve12, reject) => {
11365
11950
  const request = {
11366
11951
  jsonrpc: "2.0",
11367
11952
  id: Date.now(),
@@ -11389,7 +11974,7 @@ class MCPClient {
11389
11974
  if (response.error) {
11390
11975
  reject(new Error(response.error.message));
11391
11976
  } else {
11392
- resolve11(response.result);
11977
+ resolve12(response.result);
11393
11978
  }
11394
11979
  return;
11395
11980
  }
@@ -12377,14 +12962,14 @@ async function readClipboardImage() {
12377
12962
  return readClipboardFallback();
12378
12963
  }
12379
12964
  async function readClipboardFallback() {
12380
- const { platform: platform4 } = await import("os");
12381
- const { execSync: execSync3 } = await import("child_process");
12965
+ const { platform: platform3 } = await import("os");
12966
+ const { execSync: execSync2 } = await import("child_process");
12382
12967
  const { readFileSync: readFileSync13, unlinkSync: unlinkSync3 } = await import("fs");
12383
12968
  const { join: join16 } = await import("path");
12384
12969
  const tmpPath = join16(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
12385
12970
  try {
12386
- if (platform4() === "linux") {
12387
- execSync3(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
12971
+ if (platform3() === "linux") {
12972
+ execSync2(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
12388
12973
  } else {
12389
12974
  return null;
12390
12975
  }
@@ -12459,8 +13044,8 @@ var init_image_utils = __esm(() => {
12459
13044
  });
12460
13045
 
12461
13046
  // src/tools/attach-image.ts
12462
- import { existsSync as existsSync21 } from "fs";
12463
- import { resolve as resolve11 } from "path";
13047
+ import { existsSync as existsSync22 } from "fs";
13048
+ import { resolve as resolve12 } from "path";
12464
13049
  var attachImageTool;
12465
13050
  var init_attach_image = __esm(() => {
12466
13051
  init_i18n();
@@ -12501,8 +13086,8 @@ var init_attach_image = __esm(() => {
12501
13086
  const result = await loadUrlAsDataUrl(source);
12502
13087
  dataUrl = result.dataUrl;
12503
13088
  } else {
12504
- const absPath = resolve11(ctx.baseDir, source);
12505
- if (!existsSync21(absPath)) {
13089
+ const absPath = resolve12(ctx.baseDir, source);
13090
+ if (!existsSync22(absPath)) {
12506
13091
  return {
12507
13092
  success: false,
12508
13093
  output: t("image.not_found", { path: source })
@@ -12658,12 +13243,12 @@ class ModuleRegistry {
12658
13243
  }
12659
13244
 
12660
13245
  // src/modules/plugins/loader.ts
12661
- import { readdirSync as readdirSync5, existsSync as existsSync22, statSync as statSync4 } from "fs";
13246
+ import { readdirSync as readdirSync5, existsSync as existsSync23, statSync as statSync4 } from "fs";
12662
13247
  import { join as join16 } from "path";
12663
13248
 
12664
13249
  class PluginLoader {
12665
13250
  loadFromDir(dirPath, pluginManager, logger) {
12666
- if (!existsSync22(dirPath))
13251
+ if (!existsSync23(dirPath))
12667
13252
  return;
12668
13253
  const entries = readdirSync5(dirPath);
12669
13254
  for (const entry of entries) {
@@ -12690,9 +13275,9 @@ var init_loader = __esm(() => {
12690
13275
  });
12691
13276
 
12692
13277
  // src/modules/plugins/builtin/lint-on-write.ts
12693
- import { execSync as execSync3 } from "child_process";
12694
- import { existsSync as existsSync23, readFileSync as readFileSync13 } from "fs";
12695
- import { resolve as resolve12, extname as extname4, join as join17 } from "path";
13278
+ import { spawn as spawn4 } from "child_process";
13279
+ import { existsSync as existsSync24, readFileSync as readFileSync13 } from "fs";
13280
+ import { resolve as resolve13, extname as extname4, join as join17 } from "path";
12696
13281
 
12697
13282
  class LintOnWritePlugin {
12698
13283
  name = "lint-on-write";
@@ -12707,11 +13292,11 @@ class LintOnWritePlugin {
12707
13292
  const path = String(call.arguments.path || "");
12708
13293
  if (!path)
12709
13294
  return;
12710
- const fullPath = resolve12(ctx.baseDir, path);
12711
- if (!existsSync23(fullPath))
13295
+ const fullPath = resolve13(ctx.baseDir, path);
13296
+ if (!existsSync24(fullPath))
12712
13297
  return;
12713
13298
  const ext = extname4(fullPath);
12714
- const syntaxError = this.checkSyntax(fullPath, ext, ctx.baseDir);
13299
+ const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
12715
13300
  if (syntaxError) {
12716
13301
  result.output += `
12717
13302
 
@@ -12721,10 +13306,10 @@ class LintOnWritePlugin {
12721
13306
  await this.runProjectLint(ctx, result);
12722
13307
  await this.runProjectTypeCheck(ctx, result);
12723
13308
  }
12724
- checkSyntax(filePath, ext, baseDir) {
13309
+ async checkSyntax(filePath, ext, baseDir) {
12725
13310
  if (ext === ".ts" || ext === ".tsx") {
12726
13311
  try {
12727
- execSync3(`npx tsc --noEmit --skipLibCheck "${filePath}"`, { cwd: baseDir, stdio: "pipe", timeout: 1e4 });
13312
+ await runAsync(`npx tsc --noEmit --skipLibCheck "${filePath}"`, baseDir, 1e4);
12728
13313
  return null;
12729
13314
  } catch (err) {
12730
13315
  if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
@@ -12741,7 +13326,7 @@ class LintOnWritePlugin {
12741
13326
  }
12742
13327
  if (ext === ".js" || ext === ".jsx") {
12743
13328
  try {
12744
- execSync3(`node --check "${filePath}"`, { cwd: baseDir, stdio: "pipe", timeout: 5000 });
13329
+ await runAsync(`node --check "${filePath}"`, baseDir, 5000);
12745
13330
  return null;
12746
13331
  } catch (err) {
12747
13332
  const stderr = err.stderr?.toString() || "";
@@ -12755,7 +13340,7 @@ class LintOnWritePlugin {
12755
13340
  async runProjectLint(ctx, result) {
12756
13341
  try {
12757
13342
  const packageJsonPath = join17(ctx.baseDir, "package.json");
12758
- if (!existsSync23(packageJsonPath)) {
13343
+ if (!existsSync24(packageJsonPath)) {
12759
13344
  return;
12760
13345
  }
12761
13346
  const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf-8"));
@@ -12764,7 +13349,7 @@ class LintOnWritePlugin {
12764
13349
  return;
12765
13350
  }
12766
13351
  ctx.logger.debug(`Running lint: ${lintScript}`);
12767
- execSync3(lintScript, { cwd: ctx.baseDir, stdio: "pipe" });
13352
+ await runAsync(lintScript, ctx.baseDir, 30000);
12768
13353
  ctx.logger.debug("Lint passed");
12769
13354
  } catch (err) {
12770
13355
  ctx.logger.warn(`Lint failed: ${err.message}`);
@@ -12775,7 +13360,7 @@ class LintOnWritePlugin {
12775
13360
  }
12776
13361
  async runProjectTypeCheck(ctx, result) {
12777
13362
  const tsconfigPath = join17(ctx.baseDir, "tsconfig.json");
12778
- if (!existsSync23(tsconfigPath)) {
13363
+ if (!existsSync24(tsconfigPath)) {
12779
13364
  return;
12780
13365
  }
12781
13366
  const now = Date.now();
@@ -12799,7 +13384,7 @@ class LintOnWritePlugin {
12799
13384
  }
12800
13385
  async runTscCheck(baseDir) {
12801
13386
  try {
12802
- execSync3(`npx tsc --noEmit --skipLibCheck`, { cwd: baseDir, stdio: "pipe", timeout: 30000 });
13387
+ await runAsync(`npx tsc --noEmit --skipLibCheck`, baseDir, 30000);
12803
13388
  return null;
12804
13389
  } catch (err) {
12805
13390
  if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
@@ -12815,6 +13400,44 @@ class LintOnWritePlugin {
12815
13400
  }
12816
13401
  }
12817
13402
  }
13403
+ function runAsync(command, cwd, timeoutMs) {
13404
+ return new Promise((resolve14, reject) => {
13405
+ const child = spawn4(command, {
13406
+ cwd,
13407
+ shell: true,
13408
+ windowsHide: true,
13409
+ stdio: ["ignore", "pipe", "pipe"]
13410
+ });
13411
+ let stdout = "";
13412
+ let stderr = "";
13413
+ child.stdout?.on("data", (d) => {
13414
+ stdout += d.toString();
13415
+ });
13416
+ child.stderr?.on("data", (d) => {
13417
+ stderr += d.toString();
13418
+ });
13419
+ const timer = setTimeout(() => {
13420
+ child.kill();
13421
+ reject(new Error(`Command timed out after ${timeoutMs}ms`));
13422
+ }, timeoutMs);
13423
+ child.on("error", (err) => {
13424
+ clearTimeout(timer);
13425
+ reject(err);
13426
+ });
13427
+ child.on("close", (code) => {
13428
+ clearTimeout(timer);
13429
+ if (code === 0) {
13430
+ resolve14({ stdout, stderr });
13431
+ } else {
13432
+ const err = new Error(`Command failed with exit code ${code}`);
13433
+ err.stdout = stdout;
13434
+ err.stderr = stderr;
13435
+ err.status = code;
13436
+ reject(err);
13437
+ }
13438
+ });
13439
+ });
13440
+ }
12818
13441
  var projectTypeCheckPromise = null, projectTypeCheckTimestamp = 0, TYPE_CHECK_DEBOUNCE_MS = 2000, plugin;
12819
13442
  var init_lint_on_write = __esm(() => {
12820
13443
  plugin = new LintOnWritePlugin;
@@ -12859,380 +13482,105 @@ class PlanCreator {
12859
13482
  "make",
12860
13483
  "develop"
12861
13484
  ];
12862
- const words = task.split(/\s+/);
12863
- const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
12864
- return hasActionWord && words.length > 8;
12865
- }
12866
- static createPlan(title, stepDescriptions, baseDir) {
12867
- const stepCount = stepDescriptions.length;
12868
- return {
12869
- 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;
13485
+ const words = task.split(/\s+/);
13486
+ const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
13487
+ return hasActionWord && words.length > 8;
13081
13488
  }
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;
13489
+ static createPlan(title, stepDescriptions, baseDir) {
13490
+ const stepCount = stepDescriptions.length;
13491
+ return {
13492
+ id: generatePlanId(),
13493
+ title: `[${stepCount} ст.] ${title}`,
13494
+ steps: stepDescriptions.map((desc, i) => ({
13495
+ id: i + 1,
13496
+ description: desc,
13497
+ status: "pending"
13498
+ })),
13499
+ createdAt: new Date().toISOString(),
13500
+ baseDir
13501
+ };
13115
13502
  }
13116
- getToolAlternative() {
13117
- const tool = this.lastFailedTool;
13118
- const error = this.lastErrorOutput;
13119
- if (!tool)
13120
- return null;
13121
- if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
13122
- const alternatives = {
13123
- "ts-node": "tsx",
13124
- jest: "vitest",
13125
- mocha: "vitest",
13126
- webpack: "vite",
13127
- rollup: "vite",
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
- }
13503
+ static toPromptBlock(plan, currentStepIndex) {
13504
+ const date = plan.createdAt.slice(0, 10);
13505
+ const lines = [
13506
+ `[${plan.id}] ${plan.title}`,
13507
+ `Dir: ${plan.baseDir}`,
13508
+ `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
13509
+ ``
13510
+ ];
13511
+ for (const step of plan.steps) {
13512
+ const icon = step.status === "done" ? "[x]" : step.status === "in_progress" ? "[*]" : step.status === "failed" ? "[!]" : step.status === "skipped" ? "[-]" : "[ ]";
13513
+ const note = step.note ? ` — ${step.note}` : "";
13514
+ lines.push(`${icon} ${step.id}. ${step.description}${note}`);
13138
13515
  }
13139
- return null;
13516
+ return lines.join(`
13517
+ `);
13140
13518
  }
13141
- recordFileRewrite(filePath) {
13142
- const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
13143
- this.fileRewriteCount.set(filePath, count);
13519
+ }
13520
+
13521
+ // src/modules/execution/tracker.ts
13522
+ class PlanTracker {
13523
+ plan;
13524
+ currentStepIndex = 0;
13525
+ constructor(plan) {
13526
+ this.plan = plan;
13144
13527
  }
13145
- getFileRewriteCount(filePath) {
13146
- return this.fileRewriteCount.get(filePath) || 0;
13528
+ getPlan() {
13529
+ return this.plan;
13147
13530
  }
13148
- hasExcessiveRewrites() {
13149
- return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
13531
+ getCurrentStepIndex() {
13532
+ return this.currentStepIndex;
13150
13533
  }
13151
- getExcessiveRewriteFile() {
13152
- for (const [file, count] of this.fileRewriteCount) {
13153
- if (count >= this.fileRewriteThreshold)
13154
- return file;
13155
- }
13156
- return null;
13534
+ getCurrentStep() {
13535
+ return this.plan.steps[this.currentStepIndex];
13157
13536
  }
13158
- getRepetitiveToolMessage() {
13159
- if (!this.hasRepetitiveToolCalls())
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
- });
13537
+ getStep(id) {
13538
+ return this.plan.steps.find((s) => s.id === id);
13166
13539
  }
13167
- getStuckReason() {
13168
- if (this.isStuck()) {
13169
- return t("exec.stuck", {
13170
- stepId: String(this.currentStepId ?? "?"),
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 "";
13540
+ updateStepStatus(id, status) {
13541
+ const step = this.getStep(id);
13542
+ if (step)
13543
+ step.status = status;
13180
13544
  }
13181
- getRecoveryMessage() {
13182
- if (this.isStuck()) {
13183
- return t("exec.stuck_recovery", {
13184
- iterations: this.iterationsOnCurrentStep,
13185
- stepId: String(this.currentStepId ?? "?"),
13186
- description: this.currentStepDescription
13187
- });
13188
- }
13189
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
13190
- if (errorTool) {
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();
13545
+ addNote(id, note) {
13546
+ const step = this.getStep(id);
13547
+ if (step)
13548
+ step.note = note;
13549
+ }
13550
+ advance() {
13551
+ if (this.currentStepIndex < this.plan.steps.length - 1) {
13552
+ this.currentStepIndex++;
13553
+ this.plan.steps[this.currentStepIndex].status = "in_progress";
13554
+ return true;
13203
13555
  }
13204
- return "";
13556
+ return false;
13205
13557
  }
13206
- checkOffTrack(toolName, call) {
13207
- if (!this.currentStepDescription)
13208
- return null;
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
- });
13558
+ syncCurrentStep() {
13559
+ while (this.currentStepIndex < this.plan.steps.length - 1 && (this.plan.steps[this.currentStepIndex].status === "done" || this.plan.steps[this.currentStepIndex].status === "skipped")) {
13560
+ this.currentStepIndex++;
13219
13561
  }
13220
- return null;
13221
13562
  }
13222
- reset() {
13223
- this.iterationsOnCurrentStep = 0;
13224
- this.toolErrors.clear();
13225
- this.consecutiveFailures = 0;
13226
- this.lastFailedTool = "";
13563
+ isComplete() {
13564
+ return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
13565
+ }
13566
+ getProgressString() {
13567
+ const done = this.plan.steps.filter((s) => s.status === "done").length;
13568
+ const total = this.plan.steps.length;
13569
+ const pct = total > 0 ? Math.round(done / total * 100) : 0;
13570
+ const barWidth = 10;
13571
+ const filled = Math.round(done / total * barWidth);
13572
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
13573
+ return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
13574
+ }
13575
+ toPromptBlock() {
13576
+ return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
13227
13577
  }
13228
13578
  }
13229
- var init_stuck_detector = __esm(() => {
13230
- init_i18n();
13231
- });
13579
+ var init_tracker = () => {};
13232
13580
 
13233
13581
  // src/modules/execution/auditor.ts
13234
- import { existsSync as existsSync24 } from "fs";
13235
- import { resolve as resolve13 } from "path";
13582
+ import { existsSync as existsSync25 } from "fs";
13583
+ import { resolve as resolve14 } from "path";
13236
13584
 
13237
13585
  class Auditor {
13238
13586
  baseDir;
@@ -13246,8 +13594,8 @@ class Auditor {
13246
13594
  const missingFiles = [];
13247
13595
  const existingFiles = [];
13248
13596
  for (const filePath of uniqueFiles) {
13249
- const resolved = resolve13(this.baseDir, filePath);
13250
- if (existsSync24(resolved)) {
13597
+ const resolved = resolve14(this.baseDir, filePath);
13598
+ if (existsSync25(resolved)) {
13251
13599
  existingFiles.push(filePath);
13252
13600
  } else {
13253
13601
  missingFiles.push(filePath);
@@ -13282,14 +13630,14 @@ var init_auditor = __esm(() => {
13282
13630
  });
13283
13631
 
13284
13632
  // src/modules/execution/plan-persister.ts
13285
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as existsSync25 } from "fs";
13633
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as existsSync26 } from "fs";
13286
13634
  import { join as join18 } from "path";
13287
13635
 
13288
13636
  class PlanPersister {
13289
13637
  filePath;
13290
13638
  constructor(baseDir) {
13291
13639
  const mmaDir = join18(baseDir, ".mma");
13292
- if (!existsSync25(mmaDir)) {
13640
+ if (!existsSync26(mmaDir)) {
13293
13641
  mkdirSync11(mmaDir, { recursive: true });
13294
13642
  }
13295
13643
  this.filePath = join18(mmaDir, "plan.json");
@@ -13306,7 +13654,7 @@ class PlanPersister {
13306
13654
  writeFileSync9(this.filePath, JSON.stringify(file, null, 2), "utf-8");
13307
13655
  }
13308
13656
  load() {
13309
- if (!existsSync25(this.filePath))
13657
+ if (!existsSync26(this.filePath))
13310
13658
  return null;
13311
13659
  try {
13312
13660
  const raw = readFileSync14(this.filePath, "utf-8");
@@ -13323,7 +13671,7 @@ class PlanPersister {
13323
13671
  }
13324
13672
  }
13325
13673
  clear() {
13326
- if (existsSync25(this.filePath)) {
13674
+ if (existsSync26(this.filePath)) {
13327
13675
  writeFileSync9(this.filePath, "", "utf-8");
13328
13676
  }
13329
13677
  }
@@ -13394,31 +13742,9 @@ var init_plan_coverage = __esm(() => {
13394
13742
  ]);
13395
13743
  });
13396
13744
 
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
13745
  // src/modules/execution/module.ts
13420
- import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
13421
- import { resolve as resolve14 } from "path";
13746
+ import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
13747
+ import { resolve as resolve15 } from "path";
13422
13748
 
13423
13749
  class ExecutionModule {
13424
13750
  name = "execution";
@@ -13447,10 +13773,7 @@ class ExecutionModule {
13447
13773
  if (!plan)
13448
13774
  return false;
13449
13775
  this.tracker = new PlanTracker(plan);
13450
- while (this.tracker.getCurrentStep()?.status === "done" || this.tracker.getCurrentStep()?.status === "skipped") {
13451
- if (!this.tracker.advance())
13452
- break;
13453
- }
13776
+ this.tracker.syncCurrentStep();
13454
13777
  return true;
13455
13778
  }
13456
13779
  getTracker() {
@@ -13562,9 +13885,11 @@ ${display}`,
13562
13885
  this.tracker.updateStepStatus(Number(args.step), args.status || "done");
13563
13886
  if (args.note)
13564
13887
  this.tracker.addNote(Number(args.step), String(args.note));
13888
+ this.tracker.syncCurrentStep();
13565
13889
  this.persister.save(this.tracker.getPlan());
13566
13890
  const progress = this.tracker.getProgressString();
13567
13891
  const display = this.tracker.toPromptBlock();
13892
+ _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
13893
  return {
13569
13894
  success: true,
13570
13895
  output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
@@ -13683,6 +14008,7 @@ Sub-tasks: ${note}`
13683
14008
  const stuckReason = this.stuckDetector.getStuckReason();
13684
14009
  if (stuckReason) {
13685
14010
  ctx.logger?.warn(stuckReason);
14011
+ ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
13686
14012
  }
13687
14013
  if (this.stuckDetector.isStuck() || this.stuckDetector.hasRepetitiveToolCalls()) {
13688
14014
  const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
@@ -13800,7 +14126,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
13800
14126
  }
13801
14127
  }
13802
14128
  }
13803
- this.advancePlanIfStepComplete(ctx.contextManager);
14129
+ this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
13804
14130
  }
13805
14131
  }
13806
14132
  };
@@ -13840,7 +14166,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
13840
14166
  tool: call.name
13841
14167
  });
13842
14168
  }
13843
- advancePlanIfStepComplete(contextManager) {
14169
+ advancePlanIfStepComplete(contextManager, sessionLog) {
13844
14170
  const step = this.tracker?.getCurrentStep();
13845
14171
  if (!step)
13846
14172
  return;
@@ -13860,7 +14186,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
13860
14186
  "poetry.lock",
13861
14187
  "requirements.txt"
13862
14188
  ];
13863
- const hasLockFile = lockFiles.some((f) => existsSync26(resolve14(this.baseDir, f)));
14189
+ const hasLockFile = lockFiles.some((f) => existsSync27(resolve15(this.baseDir, f)));
13864
14190
  if (!hasLockFile) {
13865
14191
  if (contextManager) {
13866
14192
  contextManager.addMessage({
@@ -13873,13 +14199,13 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
13873
14199
  }
13874
14200
  if (stepPaths.length === 0)
13875
14201
  return;
13876
- const allExist = stepPaths.every((p) => existsSync26(resolve14(this.baseDir, p)));
14202
+ const allExist = stepPaths.every((p) => existsSync27(resolve15(this.baseDir, p)));
13877
14203
  if (!allExist)
13878
14204
  return;
13879
14205
  const emptyFiles = [];
13880
14206
  for (const p of stepPaths) {
13881
14207
  try {
13882
- const content = readFileSync15(resolve14(this.baseDir, p), "utf-8");
14208
+ const content = readFileSync15(resolve15(this.baseDir, p), "utf-8");
13883
14209
  if (content.trim().length < 10) {
13884
14210
  emptyFiles.push(p);
13885
14211
  }
@@ -13896,6 +14222,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
13896
14222
  const hadNext = this.tracker?.advance() ?? false;
13897
14223
  if (this.tracker) {
13898
14224
  this.persister.save(this.tracker.getPlan());
14225
+ sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
13899
14226
  }
13900
14227
  if (contextManager) {
13901
14228
  const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
@@ -13934,7 +14261,7 @@ var init_module = __esm(() => {
13934
14261
  });
13935
14262
 
13936
14263
  // src/modules/security/session-encryption.ts
13937
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync27, readdirSync as readdirSync6, unlinkSync as unlinkSync3 } from "fs";
14264
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync28, readdirSync as readdirSync6, unlinkSync as unlinkSync3 } from "fs";
13938
14265
  import { join as join19 } from "path";
13939
14266
  import { homedir as homedir8 } from "os";
13940
14267
 
@@ -14017,7 +14344,7 @@ class SessionFileEncryptor {
14017
14344
  const files = readdirSync6(sessionDir);
14018
14345
  for (const file of files) {
14019
14346
  const filePath = join19(sessionDir, file);
14020
- if (existsSync27(filePath) && !file.endsWith(".enc")) {
14347
+ if (existsSync28(filePath) && !file.endsWith(".enc")) {
14021
14348
  try {
14022
14349
  const content = readFileSync16(filePath, "utf8");
14023
14350
  const encrypted = this.encryptFileContent(content);
@@ -14058,7 +14385,7 @@ var init_session_encryption = __esm(() => {
14058
14385
 
14059
14386
  // src/modules/session/store.ts
14060
14387
  import {
14061
- existsSync as existsSync28,
14388
+ existsSync as existsSync29,
14062
14389
  mkdirSync as mkdirSync12,
14063
14390
  readdirSync as readdirSync7,
14064
14391
  readFileSync as readFileSync17,
@@ -14104,7 +14431,7 @@ class SessionStore {
14104
14431
  return join20(this.sessionDir(id), "session.jsonl");
14105
14432
  }
14106
14433
  sessionExists(id) {
14107
- return existsSync28(this.metaPath(id));
14434
+ return existsSync29(this.metaPath(id));
14108
14435
  }
14109
14436
  saveMeta(id, meta) {
14110
14437
  const dir = this.sessionDir(id);
@@ -14118,7 +14445,7 @@ class SessionStore {
14118
14445
  }
14119
14446
  loadMeta(id) {
14120
14447
  const path = this.metaPath(id);
14121
- if (!existsSync28(path))
14448
+ if (!existsSync29(path))
14122
14449
  return null;
14123
14450
  try {
14124
14451
  const raw = readFileSync17(path, "utf-8");
@@ -14148,7 +14475,7 @@ class SessionStore {
14148
14475
  }
14149
14476
  loadHistory(id) {
14150
14477
  const path = this.historyPath(id);
14151
- if (!existsSync28(path))
14478
+ if (!existsSync29(path))
14152
14479
  return [];
14153
14480
  try {
14154
14481
  const raw = readFileSync17(path, "utf-8");
@@ -14177,7 +14504,7 @@ class SessionStore {
14177
14504
  }
14178
14505
  loadSessionLog(id) {
14179
14506
  const path = this.sessionLogPath(id);
14180
- if (!existsSync28(path))
14507
+ if (!existsSync29(path))
14181
14508
  return [];
14182
14509
  try {
14183
14510
  const raw = readFileSync17(path, "utf-8");
@@ -14193,7 +14520,7 @@ class SessionStore {
14193
14520
  }
14194
14521
  }
14195
14522
  listSessions() {
14196
- if (!existsSync28(this.baseDir))
14523
+ if (!existsSync29(this.baseDir))
14197
14524
  return [];
14198
14525
  const entries = readdirSync7(this.baseDir, { withFileTypes: true });
14199
14526
  const sessions = [];
@@ -14209,7 +14536,7 @@ class SessionStore {
14209
14536
  }
14210
14537
  deleteSession(id) {
14211
14538
  const dir = this.sessionDir(id);
14212
- if (existsSync28(dir)) {
14539
+ if (existsSync29(dir)) {
14213
14540
  rmSync(dir, { recursive: true, force: true });
14214
14541
  }
14215
14542
  }
@@ -14221,7 +14548,7 @@ class SessionStore {
14221
14548
  const updatedAt = new Date(session2.updatedAt);
14222
14549
  if (updatedAt < thirtyDaysAgo) {
14223
14550
  const historyPath = this.historyPath(session2.id);
14224
- if (existsSync28(historyPath)) {
14551
+ if (existsSync29(historyPath)) {
14225
14552
  const content = readFileSync17(historyPath, "utf-8");
14226
14553
  const compressed = gzipSync(content);
14227
14554
  const gzPath = join20(this.baseDir, `${session2.id}.jsonl.gz`);
@@ -14431,9 +14758,9 @@ class ProfileCompressor {
14431
14758
  }
14432
14759
 
14433
14760
  // src/modules/user-profile/profile.ts
14434
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync29, mkdirSync as mkdirSync13 } from "fs";
14761
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync30, mkdirSync as mkdirSync13 } from "fs";
14435
14762
  import { join as join21 } from "path";
14436
- import { homedir as homedir9, hostname, platform as platform4, type } from "os";
14763
+ import { homedir as homedir9, hostname, platform as platform3, type } from "os";
14437
14764
  import { env } from "process";
14438
14765
 
14439
14766
  class UserProfile {
@@ -14445,7 +14772,7 @@ class UserProfile {
14445
14772
  }
14446
14773
  collect() {
14447
14774
  this.info = {
14448
- platform: platform4(),
14775
+ platform: platform3(),
14449
14776
  os: `${type()} ${hostname()}`,
14450
14777
  hostname: hostname(),
14451
14778
  shell: env.SHELL || env.ComSpec || "unknown",
@@ -14456,14 +14783,14 @@ class UserProfile {
14456
14783
  return this.info;
14457
14784
  }
14458
14785
  save() {
14459
- if (!existsSync29(this.profileDir)) {
14786
+ if (!existsSync30(this.profileDir)) {
14460
14787
  mkdirSync13(this.profileDir, { recursive: true });
14461
14788
  }
14462
14789
  writeFileSync12(join21(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
14463
14790
  }
14464
14791
  load() {
14465
14792
  const path = join21(this.profileDir, "profile.json");
14466
- if (!existsSync29(path))
14793
+ if (!existsSync30(path))
14467
14794
  return null;
14468
14795
  try {
14469
14796
  const data = JSON.parse(readFileSync18(path, "utf-8"));
@@ -14501,12 +14828,12 @@ class UserProfile {
14501
14828
  var init_profile = () => {};
14502
14829
 
14503
14830
  // src/modules/skills/loader.ts
14504
- import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync30, statSync as statSync5 } from "fs";
14831
+ import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync31, statSync as statSync5 } from "fs";
14505
14832
  import { join as join22 } from "path";
14506
14833
 
14507
14834
  class SkillsLoader {
14508
14835
  loadFromDir(dirPath) {
14509
- if (!existsSync30(dirPath))
14836
+ if (!existsSync31(dirPath))
14510
14837
  return [];
14511
14838
  const skills = [];
14512
14839
  this.scanDir(dirPath, skills);
@@ -14795,8 +15122,8 @@ var init_browser2 = __esm(() => {
14795
15122
  });
14796
15123
 
14797
15124
  // src/modules/lsp/client.ts
14798
- import { spawn as spawn4, execSync as execSync4 } from "child_process";
14799
- import { resolve as resolve15 } from "path";
15125
+ import { spawn as spawn5, execSync as execSync2 } from "child_process";
15126
+ import { resolve as resolve16 } from "path";
14800
15127
 
14801
15128
  class LspClient {
14802
15129
  process = null;
@@ -14812,7 +15139,7 @@ class LspClient {
14812
15139
  const timeout = config.timeout ?? DEFAULT_TIMEOUT;
14813
15140
  try {
14814
15141
  await this.startServer(config, baseDir);
14815
- const rootUri = this.pathToUri(resolve15(baseDir));
15142
+ const rootUri = this.pathToUri(resolve16(baseDir));
14816
15143
  const initResult = await this.sendRequest("initialize", {
14817
15144
  processId: process.pid,
14818
15145
  rootUri,
@@ -14821,11 +15148,11 @@ class LspClient {
14821
15148
  }, timeout);
14822
15149
  this.initialized = true;
14823
15150
  this.sendNotification("initialized", {});
14824
- const uri = this.pathToUri(resolve15(filePath));
15151
+ const uri = this.pathToUri(resolve16(filePath));
14825
15152
  const fs2 = await import("fs");
14826
15153
  const content = fs2.readFileSync(filePath, "utf-8");
14827
- const diagPromise = new Promise((resolve16) => {
14828
- this.diagnosticsResolve = resolve16;
15154
+ const diagPromise = new Promise((resolve17) => {
15155
+ this.diagnosticsResolve = resolve17;
14829
15156
  this.diagnostics = [];
14830
15157
  this.diagnosticsTimer = setTimeout(() => {
14831
15158
  if (this.diagnosticsResolve) {
@@ -14852,14 +15179,14 @@ class LspClient {
14852
15179
  async startServer(config, baseDir) {
14853
15180
  if (config.autoInstall === false) {
14854
15181
  try {
14855
- execSync4(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
15182
+ execSync2(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
14856
15183
  } catch {
14857
15184
  throw new Error(`${config.command} not found in PATH`);
14858
15185
  }
14859
15186
  }
14860
- return new Promise((resolve16, reject) => {
15187
+ return new Promise((resolve17, reject) => {
14861
15188
  const args = config.args ?? [];
14862
- const proc = spawn4(config.command, args, {
15189
+ const proc = spawn5(config.command, args, {
14863
15190
  stdio: ["pipe", "pipe", "pipe"],
14864
15191
  env: { ...process.env, ...config.env },
14865
15192
  cwd: baseDir
@@ -14870,7 +15197,7 @@ class LspClient {
14870
15197
  });
14871
15198
  proc.stderr.on("data", () => {});
14872
15199
  proc.once("spawn", () => {
14873
- resolve16();
15200
+ resolve17();
14874
15201
  });
14875
15202
  this.process = proc;
14876
15203
  setTimeout(() => {
@@ -14942,9 +15269,9 @@ class LspClient {
14942
15269
  }
14943
15270
  }
14944
15271
  sendRequest(method, params, timeout) {
14945
- return new Promise((resolve16, reject) => {
15272
+ return new Promise((resolve17, reject) => {
14946
15273
  const id = ++this.requestId;
14947
- this.pending.set(id, { resolve: resolve16, reject });
15274
+ this.pending.set(id, { resolve: resolve17, reject });
14948
15275
  const message = JSON.stringify({ jsonrpc: "2.0", id, method, params });
14949
15276
  this.write(message);
14950
15277
  setTimeout(() => {
@@ -15022,8 +15349,8 @@ var DEFAULT_TIMEOUT = 15000;
15022
15349
  var init_client2 = () => {};
15023
15350
 
15024
15351
  // src/modules/lsp/module.ts
15025
- import { existsSync as existsSync31 } from "fs";
15026
- import { resolve as resolve16 } from "path";
15352
+ import { existsSync as existsSync32 } from "fs";
15353
+ import { resolve as resolve17 } from "path";
15027
15354
 
15028
15355
  class LspModule {
15029
15356
  name = "lsp";
@@ -15047,8 +15374,8 @@ class LspModule {
15047
15374
  const filePath = String(call.arguments.path ?? "");
15048
15375
  if (!filePath)
15049
15376
  return;
15050
- const fullPath = resolve16(_ctx.baseDir, filePath);
15051
- if (!existsSync31(fullPath))
15377
+ const fullPath = resolve17(_ctx.baseDir, filePath);
15378
+ if (!existsSync32(fullPath))
15052
15379
  return;
15053
15380
  const serverConfig = getServerForFile(fullPath, self.config);
15054
15381
  if (!serverConfig)
@@ -15106,7 +15433,7 @@ var init_lsp = __esm(() => {
15106
15433
  });
15107
15434
 
15108
15435
  // src/modules/indexer/walker.ts
15109
- import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync32, watch } from "fs";
15436
+ import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync33, watch } from "fs";
15110
15437
  import { join as join23, relative, extname as extname5 } from "path";
15111
15438
 
15112
15439
  class Indexer {
@@ -15134,7 +15461,7 @@ class Indexer {
15134
15461
  let totalSize = 0;
15135
15462
  let count = 0;
15136
15463
  const walkDir = (dir) => {
15137
- if (!existsSync32(dir))
15464
+ if (!existsSync33(dir))
15138
15465
  return;
15139
15466
  let entries;
15140
15467
  try {
@@ -15208,7 +15535,7 @@ var init_walker = __esm(() => {
15208
15535
  });
15209
15536
 
15210
15537
  // src/modules/indexer/cache.ts
15211
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync33, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
15538
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync34, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
15212
15539
  import { join as join24 } from "path";
15213
15540
 
15214
15541
  class IndexCache {
@@ -15220,7 +15547,7 @@ class IndexCache {
15220
15547
  load() {
15221
15548
  if (this.cache)
15222
15549
  return this.cache;
15223
- if (!existsSync33(this.cachePath))
15550
+ if (!existsSync34(this.cachePath))
15224
15551
  return null;
15225
15552
  try {
15226
15553
  this.cache = JSON.parse(readFileSync21(this.cachePath, "utf-8"));
@@ -15232,13 +15559,13 @@ class IndexCache {
15232
15559
  save(result) {
15233
15560
  this.cache = result;
15234
15561
  const dir = join24(this.cachePath, "..");
15235
- if (!existsSync33(dir))
15562
+ if (!existsSync34(dir))
15236
15563
  mkdirSync14(dir, { recursive: true });
15237
15564
  writeFileSync13(this.cachePath, JSON.stringify(result), "utf-8");
15238
15565
  }
15239
15566
  invalidate() {
15240
15567
  this.cache = null;
15241
- if (existsSync33(this.cachePath)) {
15568
+ if (existsSync34(this.cachePath)) {
15242
15569
  try {
15243
15570
  rmSync2(this.cachePath);
15244
15571
  } catch {}
@@ -15649,8 +15976,8 @@ __export(exports_bootstrap, {
15649
15976
  bootstrap: () => bootstrap
15650
15977
  });
15651
15978
  import { homedir as homedir11 } from "os";
15652
- import { join as join26, resolve as resolve17 } from "path";
15653
- import { existsSync as existsSync34, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
15979
+ import { join as join26, resolve as resolve18 } from "path";
15980
+ import { existsSync as existsSync35, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
15654
15981
  function buildSystemInfo(config, baseDir, profileCompressed) {
15655
15982
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
15656
15983
  const isWin = profileCompressed.toLowerCase().includes("win32");
@@ -15669,9 +15996,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
15669
15996
  `- DRY: Do not duplicate code, logic, or configuration — reuse existing utilities and patterns.`
15670
15997
  ];
15671
15998
  if (isWin) {
15672
- lines.push(``, `Windows environment — use Windows-compatible commands:`, `- Use "dir" instead of "ls". Use "dir /b" for bare listing.`, `- Use "type" or "Get-Content" instead of "cat".`, `- 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 "Select-Object -First N" or "Get-Content ... | Select-Object -First N" instead (PowerShell).`, `- Use forward slashes (/) or escaped backslashes (\\\\) in file paths.`, `- Your working directory is: ${baseDir}`);
15999
+ 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
16000
  }
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.`, `- Background processes (dev servers, watchers, long-running npm install): use the "background: true" parameter or the tool will auto-detect and background them. Check output with process_log.`);
16001
+ 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
16002
  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
16003
  if (config.autoPlan) {
15677
16004
  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 +16055,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
15728
16055
  retry: config.retry,
15729
16056
  rateLimits: config.security?.rateLimits
15730
16057
  });
15731
- const baseDir = projectDir ? resolve17(projectDir) : process.cwd();
16058
+ const baseDir = projectDir ? resolve18(projectDir) : process.cwd();
15732
16059
  const projectMapCacheDir = join26(baseDir, ".mma");
15733
16060
  const indexerModule = new IndexerModule({
15734
16061
  baseDir,
@@ -15757,7 +16084,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
15757
16084
  estimatedTokens: 250
15758
16085
  };
15759
16086
  const agentsMdGlobal = join26(dir, "AGENTS.md");
15760
- if (!existsSync34(agentsMdGlobal)) {
16087
+ if (!existsSync35(agentsMdGlobal)) {
15761
16088
  writeFileSync14(agentsMdGlobal, "", "utf-8");
15762
16089
  }
15763
16090
  const sessionDir = join26(dir, "sessions");
@@ -15797,12 +16124,23 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
15797
16124
  timestamp: new Date().toISOString()
15798
16125
  });
15799
16126
  }
16127
+ },
16128
+ sessionLog: {
16129
+ plan: (event, detail, iteration) => {
16130
+ sessionManager.appendLog({
16131
+ ts: new Date().toISOString(),
16132
+ type: "plan",
16133
+ tool: event,
16134
+ content: detail,
16135
+ ...iteration !== undefined ? { iteration } : {}
16136
+ });
16137
+ }
15800
16138
  }
15801
16139
  };
15802
16140
  const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
15803
16141
  toolCtx.llmProvider = llmProvider;
15804
16142
  toolCtx.toolExecutor = toolExecutor;
15805
- const hallucinationDetector = new HallucinationDetector;
16143
+ const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
15806
16144
  const moduleRegistry = new ModuleRegistry;
15807
16145
  const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
15808
16146
  execModule.restorePlan();
@@ -15888,7 +16226,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
15888
16226
  join26(dir, "AGENTS.md")
15889
16227
  ];
15890
16228
  for (const p of agentsMdCandidates) {
15891
- if (existsSync34(p)) {
16229
+ if (existsSync35(p)) {
15892
16230
  const content = readFileSync22(p, "utf-8").trim();
15893
16231
  if (content) {
15894
16232
  agentsMdBlocks.push({
@@ -16506,10 +16844,10 @@ function menuList(items) {
16506
16844
  console.log(l);
16507
16845
  }
16508
16846
  function ask(rl, question, defaultValue) {
16509
- return new Promise((resolve18) => {
16847
+ return new Promise((resolve19) => {
16510
16848
  const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
16511
16849
  rl.question(prompt, (answer) => {
16512
- resolve18(answer.trim() || defaultValue || "");
16850
+ resolve19(answer.trim() || defaultValue || "");
16513
16851
  });
16514
16852
  });
16515
16853
  }
@@ -16728,12 +17066,12 @@ __export(exports_manifest, {
16728
17066
  getCertMark: () => getCertMark,
16729
17067
  MANIFEST_PATH: () => MANIFEST_PATH
16730
17068
  });
16731
- import { existsSync as existsSync35, readFileSync as readFileSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
17069
+ import { existsSync as existsSync36, readFileSync as readFileSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
16732
17070
  import { homedir as homedir13 } from "os";
16733
17071
  import { join as join28 } from "path";
16734
17072
  function readManifest(path = MANIFEST_PATH) {
16735
17073
  try {
16736
- if (existsSync35(path)) {
17074
+ if (existsSync36(path)) {
16737
17075
  const raw = JSON.parse(readFileSync23(path, "utf-8"));
16738
17076
  return { version: 1, certifications: raw.certifications ?? [] };
16739
17077
  }
@@ -23899,7 +24237,7 @@ var init_scenarios = __esm(() => {
23899
24237
  });
23900
24238
 
23901
24239
  // src/modules/certification/loader.ts
23902
- import { existsSync as existsSync36, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
24240
+ import { existsSync as existsSync37, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
23903
24241
  import { join as join29 } from "path";
23904
24242
  function validateScenario(s) {
23905
24243
  const errors2 = [];
@@ -23949,7 +24287,7 @@ function loadScenarios(userDir) {
23949
24287
  else
23950
24288
  scenarios.push(s);
23951
24289
  }
23952
- if (userDir && existsSync36(userDir)) {
24290
+ if (userDir && existsSync37(userDir)) {
23953
24291
  for (const file of readdirSync10(userDir)) {
23954
24292
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
23955
24293
  continue;
@@ -24007,7 +24345,7 @@ var init_loader3 = __esm(() => {
24007
24345
  });
24008
24346
 
24009
24347
  // src/modules/certification/fact-checker.ts
24010
- import { existsSync as existsSync37, readFileSync as readFileSync25, statSync as statSync7 } from "fs";
24348
+ import { existsSync as existsSync38, readFileSync as readFileSync25, statSync as statSync7 } from "fs";
24011
24349
  import { join as join30 } from "path";
24012
24350
  function checkSandbox(sandboxDir, checks, exitCode, output) {
24013
24351
  const failures = [];
@@ -24027,7 +24365,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
24027
24365
  case "fileExists":
24028
24366
  return isFile(join30(sandboxDir, check.path));
24029
24367
  case "fileNotExists":
24030
- return !existsSync37(join30(sandboxDir, check.path));
24368
+ return !existsSync38(join30(sandboxDir, check.path));
24031
24369
  case "dirExists":
24032
24370
  return isDir(join30(sandboxDir, check.path));
24033
24371
  case "fileContent": {
@@ -24053,14 +24391,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
24053
24391
  }
24054
24392
  function isFile(p) {
24055
24393
  try {
24056
- return existsSync37(p) && statSync7(p).isFile();
24394
+ return existsSync38(p) && statSync7(p).isFile();
24057
24395
  } catch {
24058
24396
  return false;
24059
24397
  }
24060
24398
  }
24061
24399
  function isDir(p) {
24062
24400
  try {
24063
- return existsSync37(p) && statSync7(p).isDirectory();
24401
+ return existsSync38(p) && statSync7(p).isDirectory();
24064
24402
  } catch {
24065
24403
  return false;
24066
24404
  }
@@ -24090,10 +24428,10 @@ function describe(check) {
24090
24428
  var init_fact_checker = () => {};
24091
24429
 
24092
24430
  // src/modules/certification/runner.ts
24093
- import { spawn as spawn5 } from "child_process";
24094
- import { existsSync as existsSync38, mkdirSync as mkdirSync16, rmSync as rmSync3, cpSync as cpSync2 } from "fs";
24095
- import { platform as platform5 } from "os";
24096
- import { join as join31, resolve as resolve18, dirname as dirname9 } from "path";
24431
+ import { spawn as spawn6 } from "child_process";
24432
+ import { existsSync as existsSync39, mkdirSync as mkdirSync16, rmSync as rmSync3, cpSync as cpSync2 } from "fs";
24433
+ import { platform as platform4 } from "os";
24434
+ import { join as join31, resolve as resolve19, dirname as dirname9 } from "path";
24097
24435
  async function runScenario(scenario, opts) {
24098
24436
  if (scenario.mode === "skip") {
24099
24437
  return {
@@ -24174,7 +24512,7 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
24174
24512
  mkdirSync16(sandbox, { recursive: true });
24175
24513
  for (const f of scenario.fixtures ?? []) {
24176
24514
  const src = join31(mmaRoot, f.source);
24177
- if (!existsSync38(src)) {
24515
+ if (!existsSync39(src)) {
24178
24516
  throw new Error(`fixture missing: ${f.source}`);
24179
24517
  }
24180
24518
  const dest = join31(sandbox, f.dest);
@@ -24184,27 +24522,27 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
24184
24522
  }
24185
24523
  function resolveMmaEntry(mmaRoot) {
24186
24524
  const dev = join31(mmaRoot, "src", "cli", "main.ts");
24187
- if (existsSync38(dev))
24525
+ if (existsSync39(dev))
24188
24526
  return dev;
24189
24527
  return join31(mmaRoot, "dist", "main.js");
24190
24528
  }
24191
24529
  function findMmaRoot(fromDir) {
24192
24530
  const candidates = [
24193
- resolve18(fromDir, "..", "..", ".."),
24194
- resolve18(fromDir, "..")
24531
+ resolve19(fromDir, "..", "..", ".."),
24532
+ resolve19(fromDir, "..")
24195
24533
  ];
24196
24534
  for (const c of candidates) {
24197
- if (existsSync38(join31(c, "package.json")))
24535
+ if (existsSync39(join31(c, "package.json")))
24198
24536
  return c;
24199
24537
  }
24200
24538
  return process.cwd();
24201
24539
  }
24202
- function killTree3(child) {
24540
+ function killTree2(child) {
24203
24541
  const pid = child.pid;
24204
24542
  if (!pid)
24205
24543
  return;
24206
- if (platform5() === "win32") {
24207
- spawn5("taskkill", ["/pid", String(pid), "/T", "/F"], {
24544
+ if (platform4() === "win32") {
24545
+ spawn6("taskkill", ["/pid", String(pid), "/T", "/F"], {
24208
24546
  windowsHide: true,
24209
24547
  stdio: "ignore"
24210
24548
  });
@@ -24219,7 +24557,7 @@ function killTree3(child) {
24219
24557
  }
24220
24558
  }
24221
24559
  var defaultRunner = (env2, cwd, args, timeoutMs) => new Promise((resolvePromise) => {
24222
- const child = spawn5(process.execPath, args, {
24560
+ const child = spawn6(process.execPath, args, {
24223
24561
  cwd,
24224
24562
  env: env2,
24225
24563
  windowsHide: true,
@@ -24236,7 +24574,7 @@ var defaultRunner = (env2, cwd, args, timeoutMs) => new Promise((resolvePromise)
24236
24574
  });
24237
24575
  const timer = setTimeout(() => {
24238
24576
  timedOut = true;
24239
- killTree3(child);
24577
+ killTree2(child);
24240
24578
  }, timeoutMs);
24241
24579
  child.on("close", (code) => {
24242
24580
  clearTimeout(timer);
@@ -24264,13 +24602,13 @@ import { rmSync as rmSync4 } from "fs";
24264
24602
  import { homedir as homedir14 } from "os";
24265
24603
  import { join as join32, dirname as dirname10 } from "path";
24266
24604
  import { fileURLToPath } from "url";
24267
- import { existsSync as existsSync39, readFileSync as readFileSync26 } from "fs";
24605
+ import { existsSync as existsSync40, readFileSync as readFileSync26 } from "fs";
24268
24606
  function readVersion() {
24269
24607
  const candidates = [
24270
24608
  join32(MMA_ROOT, "package.json")
24271
24609
  ];
24272
24610
  for (const p of candidates) {
24273
- if (existsSync39(p)) {
24611
+ if (existsSync40(p)) {
24274
24612
  const raw = JSON.parse(readFileSync26(p, "utf-8"));
24275
24613
  if (raw.version)
24276
24614
  return raw.version;
@@ -24425,7 +24763,7 @@ __export(exports_repl_commands, {
24425
24763
  });
24426
24764
  import { join as join34, dirname as dirname12 } from "path";
24427
24765
  import { homedir as homedir16 } from "os";
24428
- import { existsSync as existsSync41, readFileSync as readFileSync28 } from "fs";
24766
+ import { existsSync as existsSync42, readFileSync as readFileSync28 } from "fs";
24429
24767
  import { fileURLToPath as fileURLToPath3 } from "url";
24430
24768
  function readVersion3() {
24431
24769
  const here = dirname12(fileURLToPath3(import.meta.url));
@@ -24434,7 +24772,7 @@ function readVersion3() {
24434
24772
  join34(here, "..", "package.json")
24435
24773
  ];
24436
24774
  for (const p of candidates) {
24437
- if (existsSync41(p)) {
24775
+ if (existsSync42(p)) {
24438
24776
  const raw = JSON.parse(readFileSync28(p, "utf8"));
24439
24777
  if (raw.version)
24440
24778
  return raw.version;
@@ -24498,8 +24836,8 @@ function registerMmaCommands(ctx) {
24498
24836
  }
24499
24837
  try {
24500
24838
  const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
24501
- const { existsSync: existsSync42 } = await import("fs");
24502
- const { resolve: resolve19 } = await import("path");
24839
+ const { existsSync: existsSync43 } = await import("fs");
24840
+ const { resolve: resolve20 } = await import("path");
24503
24841
  let dataUrl;
24504
24842
  let label;
24505
24843
  if (source.toLowerCase() === "clipboard") {
@@ -24517,8 +24855,8 @@ function registerMmaCommands(ctx) {
24517
24855
  dataUrl = result.dataUrl;
24518
24856
  label = source;
24519
24857
  } else {
24520
- const absPath = resolve19(process.cwd(), source);
24521
- if (!existsSync42(absPath)) {
24858
+ const absPath = resolve20(process.cwd(), source);
24859
+ if (!existsSync43(absPath)) {
24522
24860
  console.log(pc.red(t("image.not_found", { path: source })));
24523
24861
  return;
24524
24862
  }
@@ -25055,7 +25393,7 @@ init_setup();
25055
25393
  init_i18n();
25056
25394
  import { join as join33, dirname as dirname11 } from "path";
25057
25395
  import { homedir as homedir15 } from "os";
25058
- import { existsSync as existsSync40, readFileSync as readFileSync27 } from "fs";
25396
+ import { existsSync as existsSync41, readFileSync as readFileSync27 } from "fs";
25059
25397
 
25060
25398
  // src/cli/security-commands.ts
25061
25399
  init_bootstrap();
@@ -25683,7 +26021,7 @@ function readVersion2() {
25683
26021
  join33(here, "..", "package.json")
25684
26022
  ];
25685
26023
  for (const p of candidates) {
25686
- if (existsSync40(p)) {
26024
+ if (existsSync41(p)) {
25687
26025
  const raw = JSON.parse(readFileSync27(p, "utf8"));
25688
26026
  if (raw.version)
25689
26027
  return raw.version;
@@ -25910,7 +26248,7 @@ init_bootstrap();
25910
26248
  // src/cli/repl.ts
25911
26249
  init_colors();
25912
26250
  import * as readline2 from "readline";
25913
- import { existsSync as existsSync42, readFileSync as readFileSync29, writeFileSync as writeFileSync16 } from "fs";
26251
+ import { existsSync as existsSync43, readFileSync as readFileSync29, writeFileSync as writeFileSync16 } from "fs";
25914
26252
  import { join as join35, dirname as dirname13 } from "path";
25915
26253
  import { homedir as homedir17 } from "os";
25916
26254
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -26377,7 +26715,7 @@ function readVersion4() {
26377
26715
  join35(here, "..", "package.json")
26378
26716
  ];
26379
26717
  for (const p of candidates) {
26380
- if (existsSync42(p)) {
26718
+ if (existsSync43(p)) {
26381
26719
  const raw = JSON.parse(readFileSync29(p, "utf8"));
26382
26720
  if (raw.version)
26383
26721
  return raw.version;
@@ -26453,7 +26791,7 @@ class Repl {
26453
26791
  this.setupListeners();
26454
26792
  }
26455
26793
  loadHistory() {
26456
- if (existsSync42(this.historyPath)) {
26794
+ if (existsSync43(this.historyPath)) {
26457
26795
  try {
26458
26796
  const raw = readFileSync29(this.historyPath, "utf-8");
26459
26797
  this.history = raw.split(`
@@ -26767,7 +27105,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
26767
27105
  join35(this.baseDir, ".mma", "AGENTS.md"),
26768
27106
  join35(this.configDir, "AGENTS.md")
26769
27107
  ];
26770
- const foundAgents = agentsMdCandidates.filter((p) => existsSync42(p));
27108
+ const foundAgents = agentsMdCandidates.filter((p) => existsSync43(p));
26771
27109
  if (foundAgents.length > 0) {
26772
27110
  for (const p of foundAgents) {
26773
27111
  row(t("repl.agents_label"), pc.dim(p));
@@ -26804,7 +27142,7 @@ init_setup();
26804
27142
  init_config2();
26805
27143
  init_i18n();
26806
27144
  init_colors();
26807
- import { existsSync as existsSync43 } from "fs";
27145
+ import { existsSync as existsSync44 } from "fs";
26808
27146
  import { join as join36 } from "path";
26809
27147
  import { homedir as homedir18 } from "os";
26810
27148
  async function main() {
@@ -26870,7 +27208,7 @@ async function main() {
26870
27208
  agent.shutdown();
26871
27209
  } else {
26872
27210
  const configPath = join36(homedir18(), ".mma", "config.json");
26873
- if (!existsSync43(configPath)) {
27211
+ if (!existsSync44(configPath)) {
26874
27212
  console.log(pc.yellow(`
26875
27213
  ` + t("cli.first_run") + `
26876
27214
  `));