micro-models-agent 0.50.0 → 0.50.4

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 +385 -99
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1991,7 +1991,6 @@ var init_security = __esm(() => {
1991
1991
  "python",
1992
1992
  "python3",
1993
1993
  "node",
1994
- "bun",
1995
1994
  "perl",
1996
1995
  "ruby",
1997
1996
  "del",
@@ -2013,8 +2012,6 @@ var init_security = __esm(() => {
2013
2012
  "--delete",
2014
2013
  "--remove",
2015
2014
  "--dangerous",
2016
- "--yes",
2017
- "-y",
2018
2015
  "--no-confirm"
2019
2016
  ],
2020
2017
  dangerousOperators: [">", ">>", "2>", "2>>", ";", "&&", "||", "|", "`", "$("]
@@ -2279,7 +2276,7 @@ var init_defaults = __esm(() => {
2279
2276
  baseDelay: 1000,
2280
2277
  maxDelay: 30000,
2281
2278
  maxStreamRetries: 2,
2282
- noDataTimeoutMs: 60000
2279
+ noDataTimeoutMs: 180000
2283
2280
  },
2284
2281
  maxToolIterations: 1000,
2285
2282
  stuckThreshold: 6,
@@ -2391,6 +2388,7 @@ The path was joined onto the working directory because it does not exist as give
2391
2388
  "file.truncated": `
2392
2389
  ... (truncated)`,
2393
2390
  "file.read_header": "── {path} ({ext}, {total} lines, lines {from}-{to})",
2391
+ "file.read_range": "lines {from}-{to} of {total}",
2394
2392
  "file.read_truncated": `
2395
2393
 
2396
2394
  [Truncated: {remaining} more lines. Use read_file with offset={next} to continue]`,
@@ -2414,7 +2412,10 @@ The path was joined onto the working directory because it does not exist as give
2414
2412
  "error.llm_retries": "LLM request failed after retries",
2415
2413
  "error.llm_429": "Rate limit exceeded (HTTP 429) for {model} on {baseUrl}. The provider is throttling requests — free models are especially strict. Get an API key or switch to a paid/faster model: {baseUrl}",
2416
2414
  "error.no_response_body": "No response body stream",
2417
- "error.llm_stream_idle": "LLM stream stalled — no data for {timeout}ms",
2415
+ "error.llm_stream_idle": 'LLM stream stalled — no data for {timeout}ms. If the provider buffers streaming (e.g. LM Studio) or generates very long tool calls, raise "retry.noDataTimeoutMs" in ~/.mma/config.json (restart required)',
2416
+ "error.llm_stream_idle_toolcall": 'LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise "retry.noDataTimeoutMs" in ~/.mma/config.json (restart required)',
2417
+ "error.llm_truncated": 'Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise "maxCompletionTokens" in the config',
2418
+ "error.llm_truncated_toolcall": 'Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise "maxCompletionTokens" in the config',
2418
2419
  "error.llm_timeout": "LLM request timed out ({timeout}ms)",
2419
2420
  "env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
2420
2421
  "env.runtime_old": "Runtime version {version} is below the required engines {engine}.",
@@ -2539,6 +2540,8 @@ Command: {command}`,
2539
2540
  {lines}`,
2540
2541
  "proc.none": "No background processes running.",
2541
2542
  "proc.not_found": "Process not found: {id}",
2543
+ "proc.missing_id": "Provide the process id: process_kill id=proc_xxx (the id is returned by bash when a command goes to the background, or by process_list).",
2544
+ "proc.missing_id_list": "No background processes are currently running.",
2542
2545
  "proc.killed": "Process {id} (PID {pid}) killed.",
2543
2546
  "proc.kill_failed": "Failed to kill process {id}",
2544
2547
  "proc.list_header": "Background processes",
@@ -2595,6 +2598,8 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2595
2598
  "plan.deleted": "Plan {id} deleted permanently.",
2596
2599
  "plan.purged": "Deleted {count} plan(s). Plan storage is empty.",
2597
2600
  "plan.no_deliverables": "note: step {step} names no files — its completion cannot be auto-verified; run an explicit check (e.g. build/tests) before your final answer.",
2601
+ "plan.bulk_update_rejected": 'Bulk steps[] rewrite via action=update is not supported: it would discard all step statuses. Use "re-plan" (keeps completed steps, replaces the rest) or mark steps individually: plan update step=N status=done|failed|skipped.',
2602
+ "plan.readonly": 'Plan {id} is {status} (read-only) — it cannot be updated. Use "plan switch" to activate it, or create a new plan for new work.',
2598
2603
  "plan.list_legend": "[*] active · [ ] draft · [-] archived",
2599
2604
  "exec.plan_nudge": "You made {count} file-changing tool call(s) without a plan. For tasks that create or modify files, or span multiple steps, create a plan first (plan create) with concrete steps (exact filenames, commands, deliverables), then continue.",
2600
2605
  "todo.added": "Added {count} todo(s): {items}",
@@ -2909,6 +2914,7 @@ Use this knowledge to answer the user's question.`,
2909
2914
  "exec.tool_errors_recovery": "Tool {tool} has failed {count} times in a row. Try an alternative: create files directly via write_file, use a different command, or if nothing works — skip this step via plan update step=N status=skipped with a note explaining why.",
2910
2915
  "exec.repetitive_tool": "Called {tool} {count} times with identical arguments and result. Try a different approach — create files directly, change arguments, or check process status via process_log.",
2911
2916
  "exec.consecutive_failures_recovery": "{count} consecutive tool failures. Create files directly via write_file instead of terminal commands. Check that dependencies are installed (npm install). Do not run build/tests until all files are created.",
2917
+ "exec.bash_flailing_recovery": "bash failed in {fails} of the last {window} attempts. Stop using shell commands for file operations on this machine — they do not work. Use the dedicated tools instead: read_file (the header shows line counts), write_file, edit_file, file_info (file size), glob/grep for search",
2912
2918
  "exec.read_only_loop": "No write/exec for {count} tool calls — the agent is only reading/exploring.",
2913
2919
  "exec.read_only_loop_recovery": "{count} consecutive read-only tool calls (read_file/glob/grep/browser) with no writes. STOP exploring and make the edit the task requires: read the file, then call write_file or edit_file. If you cannot finish the task, ask the user instead of re-reading the same files.",
2914
2920
  "exec.plan_warning": 'Current plan step {step} is "{description}", but {tool} is being called for files outside this step. Complete the current step first, then call plan update step={step} status=done before moving to the next step.',
@@ -2941,6 +2947,7 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
2941
2947
  "exec.error_search_failed": 'Web search returned nothing for "{query}".',
2942
2948
  "exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
2943
2949
  "exec.npm_exec_hint": '"could not determine executable to run" — no "bin" for that package/script. Use "npm run <script>" (script must exist in package.json) or "bunx <pkg>" for a package that declares a bin.',
2950
+ "exec.hidden_tool_hint": '"{tool}" is not a shell command — it is an MMA tool that is currently hidden. Call the enable_tools tool with tags ["shell"] to unlock it; it becomes available on the next iteration.',
2944
2951
  "exec.task_reminder": "Task: {task}. Continue making progress — do not repeat failed actions.",
2945
2952
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2946
2953
  "hall.short_response": "Response too short or empty",
@@ -3101,6 +3108,7 @@ var init_ru = __esm(() => {
3101
3108
  "file.truncated": `
3102
3109
  ... (обрезано)`,
3103
3110
  "file.read_header": "── {path} ({ext}, {total} строк, строки {from}-{to})",
3111
+ "file.read_range": "строки {from}-{to} из {total}",
3104
3112
  "file.read_truncated": `
3105
3113
 
3106
3114
  [Обрезано: ещё {remaining} строк. Продолжите чтение через read_file с offset={next}]`,
@@ -3124,7 +3132,10 @@ var init_ru = __esm(() => {
3124
3132
  "error.llm_retries": "Запрос LLM не удался после повторов",
3125
3133
  "error.llm_429": "Превышен лимит запросов (HTTP 429) для {model} на {baseUrl}. Провайдер ограничивает трафик — особенно строгие free-модели. Получите API-ключ или переключитесь на платную/быструю модель: {baseUrl}",
3126
3134
  "error.no_response_body": "Нет потока тела ответа",
3127
- "error.llm_stream_idle": "Поток LLM завис — нет данных {timeout}мс",
3135
+ "error.llm_stream_idle": 'Поток LLM завис — нет данных {timeout}мс. Если провайдер буферизирует стрим (например, LM Studio) или генерирует очень длинные tool calls, увеличьте "retry.noDataTimeoutMs" в ~/.mma/config.json (нужен рестарт)',
3136
+ "error.llm_stream_idle_toolcall": 'Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте "retry.noDataTimeoutMs" в ~/.mma/config.json (нужен рестарт)',
3137
+ "error.llm_truncated": 'Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте "maxCompletionTokens" в конфиге',
3138
+ "error.llm_truncated_toolcall": 'Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте "maxCompletionTokens" в конфиге',
3128
3139
  "error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
3129
3140
  "env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
3130
3141
  "env.runtime_old": "Версия рантайма {version} ниже требуемой engines {engine}.",
@@ -3248,6 +3259,8 @@ var init_ru = __esm(() => {
3248
3259
  {lines}`,
3249
3260
  "proc.none": "Фоновых процессов нет.",
3250
3261
  "proc.not_found": "Процесс не найден: {id}",
3262
+ "proc.missing_id": "Укажите id процесса: process_kill id=proc_xxx (id возвращается bash при уходе команды в фон или выводится через process_list).",
3263
+ "proc.missing_id_list": "Сейчас фоновых процессов нет.",
3251
3264
  "proc.killed": "Процесс {id} (PID {pid}) остановлен.",
3252
3265
  "proc.kill_failed": "Не удалось остановить процесс {id}",
3253
3266
  "proc.list_header": "Фоновые процессы",
@@ -3304,6 +3317,8 @@ var init_ru = __esm(() => {
3304
3317
  "plan.deleted": "План {id} удалён окончательно.",
3305
3318
  "plan.purged": "Удалено планов: {count}. Хранилище планов пусто.",
3306
3319
  "plan.no_deliverables": "примечание: шаг {step} не называет файлов — его выполнение нельзя проверить автоматически; перед финальным ответом явно проверь результат (например, запусти сборку/тесты).",
3320
+ "plan.bulk_update_rejected": 'Массовая перезапись steps[] через action=update не поддерживается: она бы стёрла статусы всех шагов. Используй "re-plan" (сохраняет завершённые шаги, заменяет остальные) или отмечай шаги по одному: plan update step=N status=done|failed|skipped.',
3321
+ "plan.readonly": 'План {id} имеет статус {status} (только чтение) — обновить его нельзя. Используй "plan switch", чтобы активировать его, или создай новый план для новой задачи.',
3307
3322
  "plan.list_legend": "[*] активный · [ ] черновик · [-] архив",
3308
3323
  "exec.plan_nudge": "Ты сделал(а) {count} вызов(ов), изменяющих файлы, без плана. Для задач, создающих/изменяющих файлы или состоящих из нескольких шагов, сначала создай план (plan create) с конкретными шагами (точные имена файлов, команды, результаты), а потом продолжай.",
3309
3324
  "todo.added": "Добавлено {count} задач: {items}",
@@ -3618,6 +3633,7 @@ var init_ru = __esm(() => {
3618
3633
  "exec.tool_errors_recovery": "Инструмент {tool} упал {count} раз подряд. Попробуйте альтернативу: создайте файлы напрямую через write_file, используйте другую команду, или пропустите этот шаг через plan update step=N status=skipped с пометкой почему.",
3619
3634
  "exec.repetitive_tool": "Инструмент {tool} вызван {count} раз с одинаковыми аргументами и результатом. Попробуйте другой подход — создайте файлы напрямую, измените аргументы или проверьте статус процесса через process_log.",
3620
3635
  "exec.consecutive_failures_recovery": "{count} инструментов подряд упали. Создавайте файлы напрямую через write_file вместо команд терминала. Проверьте что зависимости установлены (npm install). Не запускайте сборку/тесты пока не созданы все файлы.",
3636
+ "exec.bash_flailing_recovery": "bash упал в {fails} из последних {window} попыток. Хватит использовать шелл-команды для файловых операций на этой машине — они не работают. Используй специализированные инструменты: read_file (в заголовке число строк), write_file, edit_file, file_info (размер файла), glob/grep для поиска",
3621
3637
  "exec.read_only_loop": "Нет записи/выполнения за {count} вызовов — агент только читает/исследует.",
3622
3638
  "exec.read_only_loop_recovery": "{count} вызовов только для чтения подряд (read_file/glob/grep/browser) без записи. Хватит исследовать — внесите правку, которую требует задача: прочитайте файл, затем вызовите write_file или edit_file. Если не можете завершить задачу — спросите пользователя, а не перечитывайте одни и те же файлы.",
3623
3639
  "exec.plan_warning": 'Текущий шаг плана {step} — "{description}", но вызывается {tool} для файлов вне этого шага. Завершите текущий шаг, вызовите plan update step={step} status=done, затем переходите к следующему.',
@@ -3650,6 +3666,7 @@ var init_ru = __esm(() => {
3650
3666
  "exec.error_search_failed": 'Поиск в интернете для "{query}" ничего не дал.',
3651
3667
  "exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
3652
3668
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3669
+ "exec.hidden_tool_hint": '"{tool}" — не команда оболочки, это инструмент MMA, который сейчас скрыт. Вызови тул enable_tools с tags ["shell"], чтобы включить его; он станет доступен на следующей итерации.',
3653
3670
  "exec.task_reminder": "Задача: {task}. Продолжай работу — не повторяй неудачные действия.",
3654
3671
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3655
3672
  "hall.short_response": "Слишком короткий или пустой ответ",
@@ -4200,22 +4217,6 @@ function normalizeLspServerArgs(config) {
4200
4217
  }
4201
4218
  }
4202
4219
  }
4203
- function normalizeStaleSecurityConfig(config) {
4204
- const sec = config.security;
4205
- if (!sec || sec.enabled !== false)
4206
- return;
4207
- const bash = sec.bash;
4208
- if (!bash || bash.enabled !== false || bash.blockDangerousFlags !== false)
4209
- return;
4210
- const LEGACY_OPERATORS = [">", ">>", "2>", "2>>", "`"];
4211
- if (JSON.stringify(bash.dangerousOperators ?? []) !== JSON.stringify(LEGACY_OPERATORS))
4212
- return;
4213
- const defaultCmds = new Set(DEFAULT_SECURITY_CONFIG.bash.blacklist);
4214
- const blacklist = Array.isArray(bash.blacklist) ? bash.blacklist : [];
4215
- if (blacklist.some((cmd) => !defaultCmds.has(cmd)))
4216
- return;
4217
- config.security = JSON.parse(JSON.stringify(DEFAULT_SECURITY_CONFIG));
4218
- }
4219
4220
  function loadJSON(path) {
4220
4221
  try {
4221
4222
  if (existsSync4(path)) {
@@ -4289,7 +4290,6 @@ function loadConfig(options) {
4289
4290
  config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
4290
4291
  }
4291
4292
  normalizeLspServerArgs(config);
4292
- normalizeStaleSecurityConfig(config);
4293
4293
  config = applyEnvVars(config);
4294
4294
  try {
4295
4295
  const encryptor = new ConfigEncryptor;
@@ -5354,7 +5354,7 @@ class OpenAICompatProvider {
5354
5354
  baseDelay: 1000,
5355
5355
  maxDelay: 30000,
5356
5356
  maxStreamRetries: 2,
5357
- noDataTimeoutMs: 60000
5357
+ noDataTimeoutMs: 180000
5358
5358
  };
5359
5359
  this.rateLimiter = createRateLimiter(config.rateLimits);
5360
5360
  }
@@ -5383,7 +5383,7 @@ class OpenAICompatProvider {
5383
5383
  async* doStream(messages, tools, signal, options) {
5384
5384
  const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
5385
5385
  const streamRetries = maxStreamRetries ?? 2;
5386
- const idleTimeoutMs = noDataTimeoutMs ?? 60000;
5386
+ const idleTimeoutMs = noDataTimeoutMs ?? 180000;
5387
5387
  for (let attempt = 0;; attempt++) {
5388
5388
  let emitted = false;
5389
5389
  const onEmit = () => {
@@ -5407,12 +5407,13 @@ class OpenAICompatProvider {
5407
5407
  }
5408
5408
  }
5409
5409
  async* streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs) {
5410
+ const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
5410
5411
  const body = buildRequestBody({
5411
5412
  model: this.model,
5412
5413
  messages,
5413
5414
  tools,
5414
5415
  stream: true,
5415
- maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5416
+ maxTokens,
5416
5417
  reasoningEffort: options?.reasoningEffort
5417
5418
  });
5418
5419
  const { headers, abortSignal, cleanup, isTimeout, flagTimeout, controller } = this.buildRequestSetup(signal);
@@ -5450,8 +5451,11 @@ class OpenAICompatProvider {
5450
5451
  const decoder = new TextDecoder;
5451
5452
  let buffer = "";
5452
5453
  const toolCallAccs = new Map;
5454
+ let sawToolCallStart = false;
5453
5455
  let usage;
5454
5456
  let sawDone = false;
5457
+ let lastFinishReason;
5458
+ let sawText = false;
5455
5459
  const readIdle = () => new Promise((resolve, reject) => {
5456
5460
  const idleTimer = setTimeout(() => {
5457
5461
  flagTimeout();
@@ -5460,13 +5464,13 @@ class OpenAICompatProvider {
5460
5464
  reader.read().then((result) => {
5461
5465
  clearTimeout(idleTimer);
5462
5466
  if (isTimeout())
5463
- reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
5467
+ reject(new Error(t(sawToolCallStart ? "error.llm_stream_idle_toolcall" : "error.llm_stream_idle", { timeout: idleTimeoutMs })));
5464
5468
  else
5465
5469
  resolve(result);
5466
5470
  }, (err) => {
5467
5471
  clearTimeout(idleTimer);
5468
5472
  if (isTimeout())
5469
- reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
5473
+ reject(new Error(t(sawToolCallStart ? "error.llm_stream_idle_toolcall" : "error.llm_stream_idle", { timeout: idleTimeoutMs })));
5470
5474
  else
5471
5475
  reject(err);
5472
5476
  });
@@ -5504,11 +5508,14 @@ class OpenAICompatProvider {
5504
5508
  }
5505
5509
  const delta = choice.delta || {};
5506
5510
  const finishReason = choice.finish_reason;
5511
+ if (finishReason)
5512
+ lastFinishReason = finishReason;
5507
5513
  if (delta.reasoning_content) {
5508
5514
  onEmit();
5509
5515
  yield { type: "reasoning", content: delta.reasoning_content };
5510
5516
  }
5511
5517
  if (delta.tool_calls) {
5518
+ sawToolCallStart = true;
5512
5519
  for (const tc of delta.tool_calls) {
5513
5520
  const idx = tc.index ?? 0;
5514
5521
  if (!toolCallAccs.has(idx)) {
@@ -5526,6 +5533,7 @@ class OpenAICompatProvider {
5526
5533
  }
5527
5534
  if (delta.content) {
5528
5535
  onEmit();
5536
+ sawText = true;
5529
5537
  yield { type: "text", content: delta.content };
5530
5538
  }
5531
5539
  if (finishReason === "tool_calls" && toolCallAccs.size > 0) {
@@ -5551,6 +5559,17 @@ class OpenAICompatProvider {
5551
5559
  onEmit();
5552
5560
  yield { type: "done", usage };
5553
5561
  }
5562
+ if (lastFinishReason === "length") {
5563
+ const truncatedToolCall = sawToolCallStart && toolCallAccs.size > 0;
5564
+ if (truncatedToolCall || !sawText) {
5565
+ const err = new Error(t(truncatedToolCall ? "error.llm_truncated_toolcall" : "error.llm_truncated", {
5566
+ tokens: maxTokens
5567
+ }));
5568
+ err.llmTerminal = true;
5569
+ err.recoverableLlm = true;
5570
+ throw err;
5571
+ }
5572
+ }
5554
5573
  } finally {
5555
5574
  cleanup();
5556
5575
  reader.releaseLock();
@@ -7127,7 +7146,12 @@ var init_read_file = __esm(() => {
7127
7146
  remaining: String(total - end),
7128
7147
  next: String(end + 1)
7129
7148
  }) : "";
7130
- const display = truncated ? `${header}${truncated}` : header;
7149
+ const fullRead = offset === 1 && end >= total;
7150
+ const display = fullRead ? "" : t("file.read_range", {
7151
+ from: String(offset),
7152
+ to: String(end),
7153
+ total: String(total)
7154
+ });
7131
7155
  return {
7132
7156
  success: true,
7133
7157
  output: `${header}
@@ -8628,7 +8652,7 @@ Hint: ${t("exec.npm_exec_hint")}`;
8628
8652
  }
8629
8653
  return output;
8630
8654
  }
8631
- var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, BG_OUTPUT_PREVIEW_LINES = 15, bashGraceMs, FAILING_FIRST_WORDS, HARD_BLOCK_THRESHOLD = 3, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, NEVER_TOOL_CALLS, CLI_FILE_RUN_RE, NPM_EXEC_RE, bashTool;
8655
+ var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, BG_OUTPUT_PREVIEW_LINES = 15, bashGraceMs, FAILING_FIRST_WORDS, HARD_BLOCK_THRESHOLD = 3, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, NEVER_TOOL_CALLS, CLI_FILE_RUN_RE, NPM_EXEC_RE, HIDDEN_PROCESS_TOOLS, bashTool;
8632
8656
  var init_bash = __esm(() => {
8633
8657
  init_command_validator();
8634
8658
  init_audit_log();
@@ -8694,6 +8718,7 @@ var init_bash = __esm(() => {
8694
8718
  ]);
8695
8719
  CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
8696
8720
  NPM_EXEC_RE = /could not determine executable to run/i;
8721
+ HIDDEN_PROCESS_TOOLS = ["process_list", "process_log", "process_kill"];
8697
8722
  bashTool = {
8698
8723
  name: "bash",
8699
8724
  icon: "\uD83D\uDCBB",
@@ -8728,6 +8753,13 @@ ${redirected.output}`
8728
8753
  };
8729
8754
  }
8730
8755
  const command = adaptCommandForWindows(originalCommand);
8756
+ const baseCmd = originalCommand.trim().split(/\s+/)[0];
8757
+ if (HIDDEN_PROCESS_TOOLS.includes(baseCmd) && ctx.toolExecutor?.hasTool?.(baseCmd)) {
8758
+ return {
8759
+ success: false,
8760
+ output: t("exec.hidden_tool_hint", { tool: baseCmd })
8761
+ };
8762
+ }
8731
8763
  if (platform2() === "win32") {
8732
8764
  const echoWrite = extractEchoFileWrite(originalCommand);
8733
8765
  if (echoWrite) {
@@ -8930,6 +8962,14 @@ var init_process_log = __esm(() => {
8930
8962
  required: ["id"]
8931
8963
  },
8932
8964
  handler: async (ctx, args) => {
8965
+ if (args.id === undefined || String(args.id).trim() === "") {
8966
+ const known = processRegistry.list();
8967
+ const ids = known.map((p) => p.id);
8968
+ const listPart = ids.length ? `
8969
+ ${t("proc.list_header")}: ${ids.join(", ")}` : `
8970
+ ${t("proc.missing_id_list")}`;
8971
+ return { success: false, output: `${t("proc.missing_id")}${listPart}` };
8972
+ }
8933
8973
  const id = String(args.id);
8934
8974
  const entry = processRegistry.get(id);
8935
8975
  if (!entry) {
@@ -8971,6 +9011,14 @@ var init_process_kill = __esm(() => {
8971
9011
  required: ["id"]
8972
9012
  },
8973
9013
  handler: async (ctx, args) => {
9014
+ if (args.id === undefined || String(args.id).trim() === "") {
9015
+ const known = processRegistry.list();
9016
+ const ids = known.map((p) => p.id);
9017
+ const listPart = ids.length ? `
9018
+ ${t("proc.list_header")}: ${ids.join(", ")}` : `
9019
+ ${t("proc.missing_id_list")}`;
9020
+ return { success: false, output: `${t("proc.missing_id")}${listPart}` };
9021
+ }
8974
9022
  const id = String(args.id);
8975
9023
  const entry = processRegistry.get(id);
8976
9024
  if (!entry) {
@@ -10490,6 +10538,7 @@ class StuckDetector {
10490
10538
  lastBashOutput = "";
10491
10539
  emptyBashRunCount = 0;
10492
10540
  readOnlyStreak = 0;
10541
+ bashRecent = [];
10493
10542
  errorSignatureCounts = new Map;
10494
10543
  searchedErrorSignatures = new Set;
10495
10544
  webSearchThreshold;
@@ -10544,6 +10593,20 @@ class StuckDetector {
10544
10593
  this.lastBashCommand = command;
10545
10594
  this.lastBashOutput = output;
10546
10595
  }
10596
+ recordBashAttempt(success) {
10597
+ this.bashRecent.push(success);
10598
+ if (this.bashRecent.length > BASH_FLAIL_WINDOW)
10599
+ this.bashRecent.shift();
10600
+ }
10601
+ hasBashFlailing() {
10602
+ if (this.bashRecent.length < BASH_FLAIL_THRESHOLD)
10603
+ return false;
10604
+ let failures = 0;
10605
+ for (const ok of this.bashRecent)
10606
+ if (!ok)
10607
+ failures++;
10608
+ return failures >= BASH_FLAIL_THRESHOLD;
10609
+ }
10547
10610
  getLastBashCommand() {
10548
10611
  return this.lastBashCommand;
10549
10612
  }
@@ -10580,6 +10643,12 @@ class StuckDetector {
10580
10643
  this.consecutiveFailures = 0;
10581
10644
  this.lastFailedTool = "";
10582
10645
  this.escalationCount = 0;
10646
+ for (const [tool, count] of this.toolErrors) {
10647
+ if (count <= 1)
10648
+ this.toolErrors.delete(tool);
10649
+ else
10650
+ this.toolErrors.set(tool, count - 1);
10651
+ }
10583
10652
  if (this.iterationsOnCurrentStep > 0) {
10584
10653
  this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
10585
10654
  }
@@ -10770,6 +10839,22 @@ class StuckDetector {
10770
10839
  count: this.repetitionThreshold
10771
10840
  });
10772
10841
  }
10842
+ getWarningKey() {
10843
+ if (this.isStuck())
10844
+ return "stuck";
10845
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
10846
+ if (errorTool)
10847
+ return `tool-errors:${errorTool[0]}`;
10848
+ if (this.hasBashFlailing())
10849
+ return "bash-flailing";
10850
+ if (this.hasConsecutiveFailures())
10851
+ return "consecutive";
10852
+ if (this.hasRepetitiveToolCalls())
10853
+ return "repetitive";
10854
+ if (this.hasReadOnlyLoop())
10855
+ return "read-only";
10856
+ return "";
10857
+ }
10773
10858
  getStuckReason() {
10774
10859
  if (this.isStuck()) {
10775
10860
  return t("exec.stuck", {
@@ -10810,6 +10895,13 @@ class StuckDetector {
10810
10895
  count: this.consecutiveFailures
10811
10896
  });
10812
10897
  }
10898
+ if (reason === "bash-flailing") {
10899
+ const fails = this.bashRecent.filter((ok) => !ok).length;
10900
+ return t("exec.bash_flailing_recovery", {
10901
+ fails: String(fails),
10902
+ window: String(this.bashRecent.length)
10903
+ });
10904
+ }
10813
10905
  if (reason === "repetitive") {
10814
10906
  return this.getRepetitiveToolMessage();
10815
10907
  }
@@ -10826,6 +10918,8 @@ class StuckDetector {
10826
10918
  const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
10827
10919
  if (errorTool)
10828
10920
  return "tool-errors";
10921
+ if (this.hasBashFlailing())
10922
+ return "bash-flailing";
10829
10923
  if (this.hasConsecutiveFailures())
10830
10924
  return "consecutive";
10831
10925
  if (this.hasRepetitiveToolCalls())
@@ -10847,6 +10941,7 @@ class StuckDetector {
10847
10941
  this.lastBashCommand = "";
10848
10942
  this.lastBashOutput = "";
10849
10943
  this.emptyBashRunCount = 0;
10944
+ this.bashRecent = [];
10850
10945
  this.readOnlyStreak = 0;
10851
10946
  this.errorSignatureCounts.clear();
10852
10947
  this.searchedErrorSignatures.clear();
@@ -10863,6 +10958,7 @@ class StuckDetector {
10863
10958
  this.lastBashCommand = "";
10864
10959
  this.lastBashOutput = "";
10865
10960
  this.emptyBashRunCount = 0;
10961
+ this.bashRecent = [];
10866
10962
  this.errorSignatureCounts.clear();
10867
10963
  this.searchedErrorSignatures.clear();
10868
10964
  }
@@ -10878,12 +10974,13 @@ class StuckDetector {
10878
10974
  this.lastBashCommand = "";
10879
10975
  this.lastBashOutput = "";
10880
10976
  this.emptyBashRunCount = 0;
10977
+ this.bashRecent = [];
10881
10978
  this.readOnlyStreak = 0;
10882
10979
  this.errorSignatureCounts.clear();
10883
10980
  this.searchedErrorSignatures.clear();
10884
10981
  }
10885
10982
  }
10886
- var READ_ONLY_TOOLS, READ_ONLY_LOOP_THRESHOLD = 10, ANSI_RE, ERROR_LOC_RE, SIGNATURE_MAX_CHARS = 120, SEARCHABLE_PATTERNS, RUNTIME_WORDS, FRAMEWORK_WORDS;
10983
+ var READ_ONLY_TOOLS, READ_ONLY_LOOP_THRESHOLD = 10, BASH_FLAIL_WINDOW = 6, BASH_FLAIL_THRESHOLD = 4, ANSI_RE, ERROR_LOC_RE, SIGNATURE_MAX_CHARS = 120, SEARCHABLE_PATTERNS, RUNTIME_WORDS, FRAMEWORK_WORDS;
10887
10984
  var init_stuck_detector = __esm(() => {
10888
10985
  init_i18n();
10889
10986
  READ_ONLY_TOOLS = new Set([
@@ -12592,6 +12689,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12592
12689
  let suppressRepetitionRetry = false;
12593
12690
  let repeatedToolCount = 0;
12594
12691
  const MAX_REPEATED_TOOL_CALLS = 2;
12692
+ let llmErrorRetries = 0;
12595
12693
  let allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
12596
12694
  let boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
12597
12695
  let toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
@@ -12716,6 +12814,17 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12716
12814
  logger.info("LLM call aborted (interrupt)");
12717
12815
  break;
12718
12816
  }
12817
+ if (err?.recoverableLlm && llmErrorRetries < MAX_LLM_ERROR_RETRIES) {
12818
+ llmErrorRetries++;
12819
+ logger.warn(`Recoverable LLM error, feeding back (${llmErrorRetries}/${MAX_LLM_ERROR_RETRIES}): ${err.message}`);
12820
+ slog.logError(err.message);
12821
+ pluginManager.runOnError({ iteration, logger, contextManager }, err);
12822
+ contextManager.addMessage({
12823
+ role: "user",
12824
+ content: `<system-summary>${err.message}</system-summary>`
12825
+ });
12826
+ continue;
12827
+ }
12719
12828
  logger.logLLMResponse(config.model, textContent.length, Date.now() - llmStart, err.message, "agent");
12720
12829
  logger.error(`LLM call failed: ${err.message}`);
12721
12830
  slog.logError(err.message);
@@ -13211,7 +13320,7 @@ ${warnLine}
13211
13320
  });
13212
13321
  }
13213
13322
  }
13214
- var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40, FORCED_COMPACTION_COOLDOWN = 3;
13323
+ var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40, FORCED_COMPACTION_COOLDOWN = 3, MAX_LLM_ERROR_RETRIES = 2;
13215
13324
  var init_agent = __esm(() => {
13216
13325
  init_manager();
13217
13326
  init_i18n();
@@ -18034,6 +18143,16 @@ import { spawn as spawn6, execSync } from "child_process";
18034
18143
  import { existsSync as existsSync32, readFileSync as readFileSync17 } from "fs";
18035
18144
  import { resolve as resolve18, extname as extname4, join as join26 } from "path";
18036
18145
  import { platform as platform5 } from "os";
18146
+ function lintCacheKey(baseDir, lintScript) {
18147
+ return `${baseDir}::${lintScript}`;
18148
+ }
18149
+ function isMissingBinaryError(err, combinedOutput) {
18150
+ if (err.status === 127 || err.status === 9009)
18151
+ return true;
18152
+ if (err.code === "ENOENT")
18153
+ return true;
18154
+ return MISSING_BINARY_RE.test(combinedOutput);
18155
+ }
18037
18156
  function contentHash(content) {
18038
18157
  let h = 5381;
18039
18158
  for (let i = 0;i < content.length; i++) {
@@ -18042,24 +18161,43 @@ function contentHash(content) {
18042
18161
  return String(h);
18043
18162
  }
18044
18163
  function getWinDecoder() {
18045
- if (_winDecoder === undefined) {
18046
- if (platform5() !== "win32") {
18047
- _winDecoder = null;
18048
- } else {
18049
- try {
18050
- const cpOut = execSync("chcp.com", {
18051
- encoding: "buffer",
18052
- timeout: 2000,
18053
- windowsHide: true
18054
- }).toString("latin1");
18055
- const m = cpOut.match(/(\d+)/);
18056
- _winDecoder = m ? new TextDecoder("ibm" + m[1]) : null;
18057
- } catch {
18058
- _winDecoder = null;
18059
- }
18164
+ if (_winDecoder !== undefined)
18165
+ return _winDecoder;
18166
+ if (platform5() !== "win32") {
18167
+ _winDecoder = new TextDecoder("utf-8");
18168
+ return _winDecoder;
18169
+ }
18170
+ let decoder = null;
18171
+ try {
18172
+ const cpOut = execSync("chcp.com", {
18173
+ encoding: "buffer",
18174
+ timeout: 2000,
18175
+ windowsHide: true
18176
+ }).toString("latin1");
18177
+ const m = cpOut.match(/(\d+)/);
18178
+ if (m)
18179
+ decoder = new TextDecoder("ibm" + m[1]);
18180
+ } catch {}
18181
+ if (!decoder) {
18182
+ try {
18183
+ decoder = new TextDecoder("ibm866");
18184
+ } catch {
18185
+ return new TextDecoder("utf-8");
18060
18186
  }
18061
18187
  }
18062
- return _winDecoder ?? new TextDecoder("utf-8");
18188
+ _winDecoder = decoder;
18189
+ return _winDecoder;
18190
+ }
18191
+ function decodeOutput(chunks) {
18192
+ const raw = Buffer.concat(chunks);
18193
+ try {
18194
+ return new TextDecoder("utf-8", { fatal: true }).decode(raw);
18195
+ } catch {}
18196
+ try {
18197
+ return getWinDecoder().decode(raw);
18198
+ } catch {
18199
+ return raw.toString("latin1");
18200
+ }
18063
18201
  }
18064
18202
 
18065
18203
  class LintOnWritePlugin {
@@ -18143,23 +18281,35 @@ class LintOnWritePlugin {
18143
18281
  return null;
18144
18282
  }
18145
18283
  async runProjectLint(ctx, result, signal) {
18284
+ let lintScript;
18146
18285
  try {
18147
18286
  const packageJsonPath = join26(ctx.baseDir, "package.json");
18148
18287
  if (!existsSync32(packageJsonPath)) {
18149
18288
  return;
18150
18289
  }
18151
18290
  const packageJson = JSON.parse(readFileSync17(packageJsonPath, "utf-8"));
18152
- const lintScript = packageJson.scripts?.lint;
18291
+ lintScript = packageJson.scripts?.lint;
18153
18292
  if (!lintScript) {
18154
18293
  return;
18155
18294
  }
18295
+ if (missingLintBinaries.has(lintCacheKey(ctx.baseDir, lintScript))) {
18296
+ ctx.logger.debug(`Lint skipped (linter for "${lintScript}" not installed)`);
18297
+ return;
18298
+ }
18156
18299
  ctx.logger.debug(`Running lint: ${lintScript}`);
18157
18300
  await runAsync(lintScript, ctx.baseDir, 30000, signal);
18158
18301
  ctx.logger.debug("Lint passed");
18159
18302
  } catch (err) {
18160
18303
  const stderr = (err.stderr?.toString?.() || "").trim();
18161
18304
  const stdout = (err.stdout?.toString?.() || "").trim();
18162
- const detail = (stderr || stdout || err.message).split(`
18305
+ const combined = `${stderr}
18306
+ ${stdout}`;
18307
+ if (lintScript && isMissingBinaryError(err, combined)) {
18308
+ missingLintBinaries.add(lintCacheKey(ctx.baseDir, lintScript));
18309
+ ctx.logger.debug(`Lint binary missing — skipping project lint for this session`);
18310
+ return;
18311
+ }
18312
+ const detail = combined.trim().split(`
18163
18313
  `).filter(Boolean).slice(-5).join(`
18164
18314
  `);
18165
18315
  ctx.logger.warn(`Lint failed: ${err.message}`);
@@ -18219,14 +18369,13 @@ function runAsync(command, cwd, timeoutMs, signal) {
18219
18369
  windowsHide: true,
18220
18370
  stdio: ["ignore", "pipe", "pipe"]
18221
18371
  });
18222
- let stdout = "";
18223
- let stderr = "";
18224
- const decoder = getWinDecoder();
18372
+ const utf8Chunks = [];
18373
+ const oemChunks = [];
18225
18374
  child.stdout?.on("data", (d) => {
18226
- stdout += decoder.decode(d, { stream: true });
18375
+ utf8Chunks.push(Buffer.from(d));
18227
18376
  });
18228
18377
  child.stderr?.on("data", (d) => {
18229
- stderr += decoder.decode(d, { stream: true });
18378
+ oemChunks.push(Buffer.from(d));
18230
18379
  });
18231
18380
  const timer = setTimeout(() => {
18232
18381
  child.kill();
@@ -18242,7 +18391,7 @@ function runAsync(command, cwd, timeoutMs, signal) {
18242
18391
  clearTimeout(timer);
18243
18392
  signal?.removeEventListener("abort", onAbort);
18244
18393
  if (signal?.aborted) {
18245
- resolve19({ stdout, stderr });
18394
+ resolve19({ stdout: decodeOutput(utf8Chunks), stderr: decodeOutput(oemChunks) });
18246
18395
  } else {
18247
18396
  reject(err);
18248
18397
  }
@@ -18250,8 +18399,8 @@ function runAsync(command, cwd, timeoutMs, signal) {
18250
18399
  child.on("close", (code) => {
18251
18400
  clearTimeout(timer);
18252
18401
  signal?.removeEventListener("abort", onAbort);
18253
- stdout += decoder.decode();
18254
- stderr += decoder.decode();
18402
+ const stdout = decodeOutput(utf8Chunks);
18403
+ const stderr = decodeOutput(oemChunks);
18255
18404
  if (signal?.aborted || code === 0) {
18256
18405
  resolve19({ stdout, stderr });
18257
18406
  } else {
@@ -18264,9 +18413,11 @@ function runAsync(command, cwd, timeoutMs, signal) {
18264
18413
  });
18265
18414
  });
18266
18415
  }
18267
- var TYPE_CHECK_DEBOUNCE_MS = 2000, syntaxCache, _winDecoder, plugin;
18416
+ var TYPE_CHECK_DEBOUNCE_MS = 2000, missingLintBinaries, MISSING_BINARY_RE, syntaxCache, _winDecoder, plugin;
18268
18417
  var init_lint_on_write = __esm(() => {
18269
18418
  init_project_root();
18419
+ missingLintBinaries = new Set;
18420
+ MISSING_BINARY_RE = /command not found|not recognized|не является внутренней|не распознан|не є внутрішньою|".+?" не является/i;
18270
18421
  syntaxCache = new Map;
18271
18422
  plugin = new LintOnWritePlugin;
18272
18423
  });
@@ -18294,6 +18445,13 @@ function generatePlanId() {
18294
18445
  }
18295
18446
  return `plan_${id}`;
18296
18447
  }
18448
+ function getPlanProgress(plan) {
18449
+ const done = plan.steps.filter((s) => s.status === "done").length;
18450
+ const skipped = plan.steps.filter((s) => s.status === "skipped").length;
18451
+ const total = plan.steps.length;
18452
+ const settled = done + skipped;
18453
+ return { done, skipped, total, settled, terminal: total > 0 && settled === total };
18454
+ }
18297
18455
 
18298
18456
  class PlanCreator {
18299
18457
  static createPlan(title, stepDescriptions, baseDir, kinds) {
@@ -18334,9 +18492,9 @@ class PlanCreator {
18334
18492
  }
18335
18493
  static toPromptBlock(plan, currentStepIndex) {
18336
18494
  const date = plan.createdAt.slice(0, 10);
18337
- const doneCount = plan.steps.filter((s) => s.status === "done").length;
18338
- const terminal = plan.steps.every((s) => s.status === "done" || s.status === "skipped");
18339
- const progress = terminal ? `${doneCount}/${plan.steps.length} done, complete` : `${doneCount}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`;
18495
+ const p = getPlanProgress(plan);
18496
+ const skippedPart = p.skipped > 0 ? `, ${p.skipped} skipped` : "";
18497
+ const progress = p.terminal ? `${p.settled}/${p.total} done${skippedPart}, complete` : `${p.settled}/${p.total} done${skippedPart}, current: step ${currentStepIndex + 1}`;
18340
18498
  const lines = [
18341
18499
  `[${plan.id}] ${plan.title}`,
18342
18500
  `Dir: ${plan.baseDir}`,
@@ -18402,13 +18560,15 @@ class PlanTracker {
18402
18560
  return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
18403
18561
  }
18404
18562
  getProgressString() {
18405
- const done = this.plan.steps.filter((s) => s.status === "done").length;
18406
- const total = this.plan.steps.length;
18407
- const pct = total > 0 ? Math.round(done / total * 100) : 0;
18563
+ const p = getPlanProgress(this.plan);
18564
+ const total = p.total;
18565
+ const settled = p.settled;
18566
+ const pct = total > 0 ? Math.round(settled / total * 100) : 0;
18408
18567
  const barWidth = 10;
18409
- const filled = Math.round(done / total * barWidth);
18568
+ const filled = Math.round(settled / total * barWidth);
18410
18569
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
18411
- return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
18570
+ const skippedPart = p.skipped > 0 ? ` (${p.skipped} skipped)` : "";
18571
+ return `[${this.plan.id}] ${this.plan.title} ${settled}/${total}${skippedPart} ${bar} ${pct}%`;
18412
18572
  }
18413
18573
  toPromptBlock() {
18414
18574
  return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
@@ -18786,13 +18946,20 @@ function createExecutionPlugin(deps) {
18786
18946
  }
18787
18947
  const stuckReason = deps.stuckDetector.getStuckReason();
18788
18948
  if (stuckReason) {
18789
- const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
18949
+ const isStuck = deps.stuckDetector.isStuck();
18950
+ const warnKey = deps.stuckDetector.getWarningKey();
18951
+ const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
18952
+ const logIt = isStuck ? !deps.state.stuckNotified : warnKey !== deps.state.lastStuckWarnKey || iter - deps.state.lastStuckWarnIter >= STUCK_WARN_REPEAT_EVERY;
18790
18953
  if (logIt) {
18791
18954
  ctx.logger?.warn(stuckReason);
18792
18955
  ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
18793
- if (deps.stuckDetector.isStuck())
18956
+ deps.state.lastStuckWarnKey = warnKey;
18957
+ deps.state.lastStuckWarnIter = iter;
18958
+ if (isStuck)
18794
18959
  deps.state.stuckNotified = true;
18795
18960
  }
18961
+ } else {
18962
+ deps.state.lastStuckWarnKey = "";
18796
18963
  }
18797
18964
  if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
18798
18965
  const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
@@ -18917,6 +19084,9 @@ Last compile error: ${first[1]}`;
18917
19084
  }
18918
19085
  }
18919
19086
  if (!result.success) {
19087
+ if (call.name === "bash") {
19088
+ deps.stuckDetector.recordBashAttempt(false);
19089
+ }
18920
19090
  if (call.name === "bash" && platform6() === "win32") {
18921
19091
  const cmd = String(call.arguments?.command ?? "");
18922
19092
  const forbidden = forbiddenWindowsCommand(cmd);
@@ -18936,6 +19106,9 @@ Last compile error: ${first[1]}`;
18936
19106
  }
18937
19107
  } else {
18938
19108
  deps.stuckDetector.recordToolSuccess();
19109
+ if (call.name === "bash") {
19110
+ deps.stuckDetector.recordBashAttempt(true);
19111
+ }
18939
19112
  if (call.name === "bash" && result.success) {
18940
19113
  const cmd = String(call.arguments?.command ?? "");
18941
19114
  const testRun = detectTestResults(String(result.output ?? ""));
@@ -18973,7 +19146,7 @@ Last compile error: ${first[1]}`;
18973
19146
  }
18974
19147
  };
18975
19148
  }
18976
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
19149
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, STUCK_WARN_REPEAT_EVERY = 5, PLAN_NUDGE_THRESHOLD = 2;
18977
19150
  var init_execution_plugin = __esm(() => {
18978
19151
  init_i18n();
18979
19152
  init_bash();
@@ -19228,12 +19401,12 @@ ${display}`,
19228
19401
  deps.store.archivePlan(plan);
19229
19402
  deps.recordCompleted(plan);
19230
19403
  deps.trackerRef.current = null;
19231
- const done = plan.steps.filter((s) => s.status === "done").length;
19404
+ const p = getPlanProgress(plan);
19232
19405
  return {
19233
19406
  success: true,
19234
19407
  output: t("plan.completed_archived", {
19235
19408
  id: plan.id,
19236
- done: String(done),
19409
+ done: String(p.settled),
19237
19410
  total: String(plan.steps.length)
19238
19411
  })
19239
19412
  };
@@ -19327,13 +19500,13 @@ ${t("plan.no_deliverables", { step: String(stepId) })}` : "";
19327
19500
  deps.store.archivePlan(plan);
19328
19501
  deps.recordCompleted(plan);
19329
19502
  deps.trackerRef.current = null;
19330
- const done = plan.steps.filter((s) => s.status === "done").length;
19503
+ const p = getPlanProgress(plan);
19331
19504
  return {
19332
19505
  success: true,
19333
19506
  output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
19334
19507
  ${t("plan.completed_archived", {
19335
19508
  id: plan.id,
19336
- done: String(done),
19509
+ done: String(p.settled),
19337
19510
  total: String(plan.steps.length)
19338
19511
  })}${vacuousNote}`
19339
19512
  };
@@ -19348,8 +19521,15 @@ ${progress}${vacuousNote}`,
19348
19521
  display
19349
19522
  };
19350
19523
  }
19351
- if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && deps.trackerRef.current) {
19524
+ if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && !args.step) {
19352
19525
  const tracker = deps.trackerRef.current;
19526
+ if (!tracker) {
19527
+ return { success: false, output: t("plan.no_active") };
19528
+ }
19529
+ const progressed = tracker.getPlan().steps.some((s) => s.status !== "pending");
19530
+ if (progressed) {
19531
+ return { success: false, output: t("plan.bulk_update_rejected") };
19532
+ }
19353
19533
  const title = String(args.title || tracker.getPlan().title);
19354
19534
  const steps = args.steps.map(String);
19355
19535
  const parsed = deps.parseKinds(args, steps);
@@ -19436,6 +19616,15 @@ ${progress}${vacuousNote}`,
19436
19616
  };
19437
19617
  }
19438
19618
  if (!deps.trackerRef.current) {
19619
+ if (args.id) {
19620
+ const found = deps.store.find(String(args.id));
19621
+ if (found && found.status !== "active") {
19622
+ return {
19623
+ success: false,
19624
+ output: t("plan.readonly", { id: found.plan.id, status: found.status })
19625
+ };
19626
+ }
19627
+ }
19439
19628
  return { success: false, output: t("plan.no_active") };
19440
19629
  }
19441
19630
  return {
@@ -19610,6 +19799,8 @@ class ExecutionModule {
19610
19799
  consecutivePlanWarnings: 0,
19611
19800
  lastStepId: -1,
19612
19801
  stuckNotified: false,
19802
+ lastStuckWarnKey: "",
19803
+ lastStuckWarnIter: -STUCK_WARN_REPEAT_EVERY,
19613
19804
  depsGateHints: new Map,
19614
19805
  mutationsWithoutPlan: 0,
19615
19806
  planNudgeSent: false,
@@ -33279,8 +33470,9 @@ function stripAnsi2(s) {
33279
33470
  function charLen(s) {
33280
33471
  return Array.from(s).length;
33281
33472
  }
33282
- function wrapLine(line, width) {
33473
+ function wrapLine(line, width, firstRowWidth) {
33283
33474
  const w = width > 0 ? width : 80;
33475
+ let limit = firstRowWidth !== undefined && firstRowWidth > 0 ? firstRowWidth : w;
33284
33476
  const chars = Array.from(line);
33285
33477
  const rows = [];
33286
33478
  let start = 0;
@@ -33295,10 +33487,11 @@ function wrapLine(line, width) {
33295
33487
  continue;
33296
33488
  }
33297
33489
  const cw = stringWidth(ch);
33298
- if (cells + cw > w && cells > 0) {
33490
+ if (cells + cw > limit && cells > 0) {
33299
33491
  rows.push({ text: chars.slice(start, i).join(""), charStart: start });
33300
33492
  start = i;
33301
33493
  cells = cw;
33494
+ limit = w;
33302
33495
  } else {
33303
33496
  cells += cw;
33304
33497
  }
@@ -33306,10 +33499,10 @@ function wrapLine(line, width) {
33306
33499
  rows.push({ text: chars.slice(start).join(""), charStart: start });
33307
33500
  return rows;
33308
33501
  }
33309
- function buildLayout(lines, width) {
33502
+ function buildLayout(lines, width, firstRowWidth) {
33310
33503
  const rows = [];
33311
33504
  for (let b = 0;b < lines.length; b++) {
33312
- for (const r of wrapLine(lines[b], width)) {
33505
+ for (const r of wrapLine(lines[b], width, b === 0 ? firstRowWidth : undefined)) {
33313
33506
  rows.push({ bufRow: b, charStart: r.charStart, text: r.text });
33314
33507
  }
33315
33508
  }
@@ -33346,6 +33539,7 @@ class LineEditor {
33346
33539
  input;
33347
33540
  output;
33348
33541
  onKeyInput;
33542
+ onInterrupt;
33349
33543
  promptStr;
33350
33544
  completer;
33351
33545
  history;
@@ -33364,11 +33558,15 @@ class LineEditor {
33364
33558
  rawMode = false;
33365
33559
  listeners = {};
33366
33560
  prevCursorRow = 0;
33561
+ frameTopAbs = null;
33562
+ dsrPending = false;
33563
+ dsrTail = "";
33367
33564
  constructor(opts) {
33368
33565
  this.input = opts.input;
33369
33566
  this.output = opts.output;
33370
33567
  this.promptStr = opts.prompt ?? "";
33371
33568
  this.completer = opts.completer;
33569
+ this.onInterrupt = opts.onInterrupt;
33372
33570
  this.history = opts.history ?? [];
33373
33571
  this.historySize = opts.historySize ?? 50;
33374
33572
  if (this.history.length > this.historySize) {
@@ -33379,6 +33577,7 @@ class LineEditor {
33379
33577
  if (this.rawMode)
33380
33578
  this.input.setRawMode(true);
33381
33579
  this.enableTerminalProtocols();
33580
+ this.input.on("data", (buf) => this.consumeDsrReplies(buf.toString("utf-8")));
33382
33581
  readline2.emitKeypressEvents(this.input);
33383
33582
  this.input.on("keypress", (str, key) => this.onKey(str, key));
33384
33583
  } else {
@@ -33421,7 +33620,7 @@ class LineEditor {
33421
33620
  this.col = 0;
33422
33621
  this.historyIndex = -1;
33423
33622
  this.stash = null;
33424
- this.prevCursorRow = 0;
33623
+ this.invalidateAnchor();
33425
33624
  if (this.input.isTTY)
33426
33625
  this.render();
33427
33626
  }
@@ -33431,7 +33630,7 @@ class LineEditor {
33431
33630
  this.lines = [""];
33432
33631
  this.row = 0;
33433
33632
  this.col = 0;
33434
- this.prevCursorRow = 0;
33633
+ this.invalidateAnchor();
33435
33634
  if (this.input.isTTY)
33436
33635
  this.render();
33437
33636
  }
@@ -33651,10 +33850,10 @@ class LineEditor {
33651
33850
  this.commitFrame();
33652
33851
  this.output.write(`\r
33653
33852
  `);
33853
+ this.invalidateAnchor();
33654
33854
  this.lines = [""];
33655
33855
  this.row = 0;
33656
33856
  this.col = 0;
33657
- this.prevCursorRow = 0;
33658
33857
  cb(answer);
33659
33858
  return;
33660
33859
  }
@@ -33671,17 +33870,21 @@ class LineEditor {
33671
33870
  this.commitFrame();
33672
33871
  this.output.write(`\r
33673
33872
  `);
33873
+ this.invalidateAnchor();
33674
33874
  this.lines = [""];
33675
33875
  this.row = 0;
33676
33876
  this.col = 0;
33677
- this.prevCursorRow = 0;
33678
33877
  this.emitLine(text);
33679
33878
  }
33680
33879
  commitFrame() {
33681
- const layout = this.layout();
33880
+ const layout = this.visibleLayout();
33881
+ const lastLen = charLen(stripAnsi2(layout[layout.length - 1].text));
33882
+ if (this.frameTopAbs !== null) {
33883
+ this.output.write(`\x1B[${this.frameTopAbs + layout.length - 1};${lastLen + 1}H`);
33884
+ return;
33885
+ }
33682
33886
  const idx = layoutIndexFor(layout, this.row, this.col);
33683
33887
  const rowsDown = layout.length - 1 - idx;
33684
- const lastLen = charLen(stripAnsi2(layout[layout.length - 1].text));
33685
33888
  const out = [];
33686
33889
  if (rowsDown > 0)
33687
33890
  out.push(`\x1B[${rowsDown}B`);
@@ -33694,9 +33897,15 @@ class LineEditor {
33694
33897
  this.lines = [""];
33695
33898
  this.row = 0;
33696
33899
  this.col = 0;
33697
- this.prevCursorRow = 0;
33900
+ this.invalidateAnchor();
33698
33901
  this.render();
33699
- process.emit("SIGINT");
33902
+ if (this.onInterrupt) {
33903
+ this.onInterrupt();
33904
+ return;
33905
+ }
33906
+ if (process.listenerCount("SIGINT") > 0) {
33907
+ process.emit("SIGINT");
33908
+ }
33700
33909
  }
33701
33910
  ctrlD() {
33702
33911
  if (this.lines.length === 1 && this.lines[0] === "") {
@@ -33963,28 +34172,68 @@ class LineEditor {
33963
34172
  }
33964
34173
  clearScreen() {
33965
34174
  this.output.write("\x1B[2J\x1B[H");
33966
- this.prevCursorRow = 0;
34175
+ this.invalidateAnchor();
33967
34176
  this.render();
33968
34177
  }
33969
34178
  columns() {
33970
34179
  return this.output.columns || 80;
33971
34180
  }
33972
34181
  layout() {
33973
- return buildLayout(this.lines, this.columns());
34182
+ const promptWidth = stringWidth(stripAnsi2(this.promptStr));
34183
+ return buildLayout(this.lines, this.columns(), Math.max(1, this.columns() - promptWidth));
34184
+ }
34185
+ screenRows() {
34186
+ const r = this.output.rows;
34187
+ return typeof r === "number" && r > 3 ? r : null;
34188
+ }
34189
+ visibleLayout() {
34190
+ const layout = this.layout();
34191
+ const H = this.screenRows();
34192
+ const maxRows = H ? H - 2 : 0;
34193
+ if (maxRows > 0 && layout.length > maxRows) {
34194
+ return layout.slice(layout.length - maxRows);
34195
+ }
34196
+ return layout;
34197
+ }
34198
+ invalidateAnchor() {
34199
+ this.frameTopAbs = null;
34200
+ this.dsrPending = false;
34201
+ this.dsrTail = "";
34202
+ this.prevCursorRow = 0;
34203
+ }
34204
+ consumeDsrReplies(chunk) {
34205
+ this.dsrTail = (this.dsrTail + chunk).slice(-80);
34206
+ const m = /\x1b\[(\d+);(\d+)R/.exec(this.dsrTail);
34207
+ if (!m)
34208
+ return;
34209
+ this.dsrTail = "";
34210
+ if (!this.dsrPending)
34211
+ return;
34212
+ this.dsrPending = false;
34213
+ const row = Number(m[1]);
34214
+ const idx = Number(m[2]);
34215
+ const top = row - this.prevCursorRow;
34216
+ if (top >= 1)
34217
+ this.frameTopAbs = top;
33974
34218
  }
33975
34219
  render() {
33976
34220
  if (!this.input.isTTY || this.isDone)
33977
34221
  return;
33978
- const layout = this.layout();
34222
+ const layout = this.visibleLayout();
33979
34223
  const promptWidth = stringWidth(stripAnsi2(this.promptStr));
33980
34224
  const idx = layoutIndexFor(layout, this.row, this.col);
33981
34225
  const vcol = visualColAt(layout, idx, this.col);
33982
34226
  const rowsAfter = layout.length - idx - 1;
33983
34227
  const ccol = (idx === 0 ? promptWidth : 0) + vcol;
33984
34228
  const out = [];
33985
- if (this.prevCursorRow > 0)
34229
+ if (this.frameTopAbs !== null) {
34230
+ out.push(`\x1B[${this.frameTopAbs};1H`);
34231
+ } else if (this.prevCursorRow > 0) {
33986
34232
  out.push(`\x1B[${this.prevCursorRow}A`);
33987
- out.push("\r");
34233
+ out.push("\r");
34234
+ } else {
34235
+ out.push("\r");
34236
+ }
33988
34237
  for (let i = 0;i < layout.length; i++) {
33989
34238
  out.push("\x1B[2K");
33990
34239
  if (i === 0)
@@ -34000,8 +34249,17 @@ class LineEditor {
34000
34249
  out.push("\r");
34001
34250
  if (ccol > 0)
34002
34251
  out.push(`\x1B[${ccol}C`);
34003
- this.prevCursorRow = idx;
34004
34252
  this.output.write(out.join(""));
34253
+ if (this.frameTopAbs !== null) {
34254
+ const H = this.screenRows();
34255
+ if (H) {
34256
+ const T = this.frameTopAbs;
34257
+ const L = layout.length;
34258
+ if (T + L - 1 > H)
34259
+ this.frameTopAbs = H - L + 1;
34260
+ }
34261
+ } else if (!this.dsrPending) {}
34262
+ this.prevCursorRow = idx;
34005
34263
  }
34006
34264
  enableTerminalProtocols() {
34007
34265
  this.output.write("\x1B[?2004h");
@@ -34358,9 +34616,28 @@ init_box();
34358
34616
  init_table();
34359
34617
  init_i18n();
34360
34618
  init_prices();
34619
+ import { isAbsolute as isAbsolute4, relative as relative6, sep as sep2 } from "path";
34361
34620
  function formatUsd(cost) {
34362
34621
  return formatCost(cost);
34363
34622
  }
34623
+ var PATH_TOOLS = new Set([
34624
+ "read_file",
34625
+ "write_file",
34626
+ "edit_file",
34627
+ "delete_file",
34628
+ "create_dir",
34629
+ "move_file",
34630
+ "list_dir",
34631
+ "file_info"
34632
+ ]);
34633
+ function toDisplayPath(baseDir, p) {
34634
+ if (!baseDir || !isAbsolute4(p))
34635
+ return p;
34636
+ const rel = relative6(baseDir, p);
34637
+ if (!rel || rel.startsWith("..") || isAbsolute4(rel))
34638
+ return p;
34639
+ return rel.split(sep2).join("/");
34640
+ }
34364
34641
  var GUTTER = " ";
34365
34642
  var BUSY_TOOLS = new Set(["lsp_check"]);
34366
34643
  function toolMarker(tool) {
@@ -34414,6 +34691,7 @@ class Renderer {
34414
34691
  err;
34415
34692
  width;
34416
34693
  toolStyle;
34694
+ baseDir;
34417
34695
  card = null;
34418
34696
  constructor(opts = {}) {
34419
34697
  this.rich = opts.rich ?? isRichTerminal();
@@ -34421,6 +34699,7 @@ class Renderer {
34421
34699
  this.err = opts.err ?? process.stderr;
34422
34700
  this.width = opts.width ?? getTerminalWidth();
34423
34701
  this.toolStyle = opts.toolStyle ?? "inline";
34702
+ this.baseDir = opts.baseDir;
34424
34703
  this.spinner = new Spinner({
34425
34704
  enabled: this.rich && (opts.spinner ?? true),
34426
34705
  stream: this.err,
@@ -34468,7 +34747,8 @@ class Renderer {
34468
34747
  toolStart(tool, args, stepContext, icon) {
34469
34748
  this.endCard();
34470
34749
  this.spinner.stop();
34471
- const summary = summarizeArgs2(args);
34750
+ const displayArgs = PATH_TOOLS.has(tool) && typeof args.path === "string" ? { ...args, path: toDisplayPath(this.baseDir, args.path) } : args;
34751
+ const summary = summarizeArgs2(displayArgs);
34472
34752
  const step = stepContext ? ` ${pc2.cyan(`← ${stepContext}`)}` : "";
34473
34753
  const marker = icon || toolMarker(tool);
34474
34754
  if (!this.rich) {
@@ -34979,7 +35259,10 @@ class Repl {
34979
35259
  this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
34980
35260
  }
34981
35261
  let forceExitTimer = null;
34982
- process.on("SIGINT", () => {
35262
+ if (typeof Repl.sigintHandler === "function") {
35263
+ process.removeListener("SIGINT", Repl.sigintHandler);
35264
+ }
35265
+ Repl.sigintHandler = () => {
34983
35266
  if (this.agentRunning) {
34984
35267
  console.log(pc2.yellow(t("repl.ctrl_c_interrupt")));
34985
35268
  this.agent.shutdown();
@@ -34990,7 +35273,8 @@ class Repl {
34990
35273
  } else {
34991
35274
  process.exit(0);
34992
35275
  }
34993
- });
35276
+ };
35277
+ process.on("SIGINT", Repl.sigintHandler);
34994
35278
  }
34995
35279
  handleSpecialKey(str, key) {
34996
35280
  if (key.name === "escape") {
@@ -35068,7 +35352,8 @@ ${t("image.clipboard_empty")}`));
35068
35352
  ` + pc2.green(t("repl.agent")));
35069
35353
  const renderer = new Renderer({
35070
35354
  spinner: this.config.ui?.spinner ?? true,
35071
- toolStyle: this.config.ui?.toolStyle ?? "inline"
35355
+ toolStyle: this.config.ui?.toolStyle ?? "inline",
35356
+ baseDir: this.baseDir
35072
35357
  });
35073
35358
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
35074
35359
  if (ev.type === "start") {
@@ -35550,7 +35835,7 @@ async function main() {
35550
35835
  }
35551
35836
  if (program2.args.length > 0) {
35552
35837
  const prompt = program2.args.join(" ");
35553
- const { agent, config } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
35838
+ const { agent, config, baseDir } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
35554
35839
  const updater = exitOnComplete ? undefined : startAutoUpdate(config);
35555
35840
  if (jsonMode) {
35556
35841
  const result2 = await agent.run(prompt);
@@ -35575,7 +35860,8 @@ async function main() {
35575
35860
  }
35576
35861
  const renderer = new Renderer({
35577
35862
  spinner: config.ui?.spinner ?? true,
35578
- toolStyle: config.ui?.toolStyle ?? "inline"
35863
+ toolStyle: config.ui?.toolStyle ?? "inline",
35864
+ baseDir
35579
35865
  });
35580
35866
  const result = await agent.run(prompt, (chunk) => renderer.text(chunk), (meta) => renderer.meta(meta), (ev) => {
35581
35867
  if (ev.type === "start") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.50.0",
3
+ "version": "0.50.4",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {