micro-models-agent 0.26.0 → 0.28.0

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