micro-models-agent 0.50.1 → 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 +254 -60
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2276,7 +2276,7 @@ var init_defaults = __esm(() => {
2276
2276
  baseDelay: 1000,
2277
2277
  maxDelay: 30000,
2278
2278
  maxStreamRetries: 2,
2279
- noDataTimeoutMs: 60000
2279
+ noDataTimeoutMs: 180000
2280
2280
  },
2281
2281
  maxToolIterations: 1000,
2282
2282
  stuckThreshold: 6,
@@ -2388,6 +2388,7 @@ The path was joined onto the working directory because it does not exist as give
2388
2388
  "file.truncated": `
2389
2389
  ... (truncated)`,
2390
2390
  "file.read_header": "── {path} ({ext}, {total} lines, lines {from}-{to})",
2391
+ "file.read_range": "lines {from}-{to} of {total}",
2391
2392
  "file.read_truncated": `
2392
2393
 
2393
2394
  [Truncated: {remaining} more lines. Use read_file with offset={next} to continue]`,
@@ -2411,7 +2412,10 @@ The path was joined onto the working directory because it does not exist as give
2411
2412
  "error.llm_retries": "LLM request failed after retries",
2412
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}",
2413
2414
  "error.no_response_body": "No response body stream",
2414
- "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',
2415
2419
  "error.llm_timeout": "LLM request timed out ({timeout}ms)",
2416
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.",
2417
2421
  "env.runtime_old": "Runtime version {version} is below the required engines {engine}.",
@@ -2910,6 +2914,7 @@ Use this knowledge to answer the user's question.`,
2910
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.",
2911
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.",
2912
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",
2913
2918
  "exec.read_only_loop": "No write/exec for {count} tool calls — the agent is only reading/exploring.",
2914
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.",
2915
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.',
@@ -3103,6 +3108,7 @@ var init_ru = __esm(() => {
3103
3108
  "file.truncated": `
3104
3109
  ... (обрезано)`,
3105
3110
  "file.read_header": "── {path} ({ext}, {total} строк, строки {from}-{to})",
3111
+ "file.read_range": "строки {from}-{to} из {total}",
3106
3112
  "file.read_truncated": `
3107
3113
 
3108
3114
  [Обрезано: ещё {remaining} строк. Продолжите чтение через read_file с offset={next}]`,
@@ -3126,7 +3132,10 @@ var init_ru = __esm(() => {
3126
3132
  "error.llm_retries": "Запрос LLM не удался после повторов",
3127
3133
  "error.llm_429": "Превышен лимит запросов (HTTP 429) для {model} на {baseUrl}. Провайдер ограничивает трафик — особенно строгие free-модели. Получите API-ключ или переключитесь на платную/быструю модель: {baseUrl}",
3128
3134
  "error.no_response_body": "Нет потока тела ответа",
3129
- "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" в конфиге',
3130
3139
  "error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
3131
3140
  "env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
3132
3141
  "env.runtime_old": "Версия рантайма {version} ниже требуемой engines {engine}.",
@@ -3624,6 +3633,7 @@ var init_ru = __esm(() => {
3624
3633
  "exec.tool_errors_recovery": "Инструмент {tool} упал {count} раз подряд. Попробуйте альтернативу: создайте файлы напрямую через write_file, используйте другую команду, или пропустите этот шаг через plan update step=N status=skipped с пометкой почему.",
3625
3634
  "exec.repetitive_tool": "Инструмент {tool} вызван {count} раз с одинаковыми аргументами и результатом. Попробуйте другой подход — создайте файлы напрямую, измените аргументы или проверьте статус процесса через process_log.",
3626
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 для поиска",
3627
3637
  "exec.read_only_loop": "Нет записи/выполнения за {count} вызовов — агент только читает/исследует.",
3628
3638
  "exec.read_only_loop_recovery": "{count} вызовов только для чтения подряд (read_file/glob/grep/browser) без записи. Хватит исследовать — внесите правку, которую требует задача: прочитайте файл, затем вызовите write_file или edit_file. Если не можете завершить задачу — спросите пользователя, а не перечитывайте одни и те же файлы.",
3629
3639
  "exec.plan_warning": 'Текущий шаг плана {step} — "{description}", но вызывается {tool} для файлов вне этого шага. Завершите текущий шаг, вызовите plan update step={step} status=done, затем переходите к следующему.',
@@ -5344,7 +5354,7 @@ class OpenAICompatProvider {
5344
5354
  baseDelay: 1000,
5345
5355
  maxDelay: 30000,
5346
5356
  maxStreamRetries: 2,
5347
- noDataTimeoutMs: 60000
5357
+ noDataTimeoutMs: 180000
5348
5358
  };
5349
5359
  this.rateLimiter = createRateLimiter(config.rateLimits);
5350
5360
  }
@@ -5373,7 +5383,7 @@ class OpenAICompatProvider {
5373
5383
  async* doStream(messages, tools, signal, options) {
5374
5384
  const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
5375
5385
  const streamRetries = maxStreamRetries ?? 2;
5376
- const idleTimeoutMs = noDataTimeoutMs ?? 60000;
5386
+ const idleTimeoutMs = noDataTimeoutMs ?? 180000;
5377
5387
  for (let attempt = 0;; attempt++) {
5378
5388
  let emitted = false;
5379
5389
  const onEmit = () => {
@@ -5397,12 +5407,13 @@ class OpenAICompatProvider {
5397
5407
  }
5398
5408
  }
5399
5409
  async* streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs) {
5410
+ const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
5400
5411
  const body = buildRequestBody({
5401
5412
  model: this.model,
5402
5413
  messages,
5403
5414
  tools,
5404
5415
  stream: true,
5405
- maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5416
+ maxTokens,
5406
5417
  reasoningEffort: options?.reasoningEffort
5407
5418
  });
5408
5419
  const { headers, abortSignal, cleanup, isTimeout, flagTimeout, controller } = this.buildRequestSetup(signal);
@@ -5440,8 +5451,11 @@ class OpenAICompatProvider {
5440
5451
  const decoder = new TextDecoder;
5441
5452
  let buffer = "";
5442
5453
  const toolCallAccs = new Map;
5454
+ let sawToolCallStart = false;
5443
5455
  let usage;
5444
5456
  let sawDone = false;
5457
+ let lastFinishReason;
5458
+ let sawText = false;
5445
5459
  const readIdle = () => new Promise((resolve, reject) => {
5446
5460
  const idleTimer = setTimeout(() => {
5447
5461
  flagTimeout();
@@ -5450,13 +5464,13 @@ class OpenAICompatProvider {
5450
5464
  reader.read().then((result) => {
5451
5465
  clearTimeout(idleTimer);
5452
5466
  if (isTimeout())
5453
- 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 })));
5454
5468
  else
5455
5469
  resolve(result);
5456
5470
  }, (err) => {
5457
5471
  clearTimeout(idleTimer);
5458
5472
  if (isTimeout())
5459
- 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 })));
5460
5474
  else
5461
5475
  reject(err);
5462
5476
  });
@@ -5494,11 +5508,14 @@ class OpenAICompatProvider {
5494
5508
  }
5495
5509
  const delta = choice.delta || {};
5496
5510
  const finishReason = choice.finish_reason;
5511
+ if (finishReason)
5512
+ lastFinishReason = finishReason;
5497
5513
  if (delta.reasoning_content) {
5498
5514
  onEmit();
5499
5515
  yield { type: "reasoning", content: delta.reasoning_content };
5500
5516
  }
5501
5517
  if (delta.tool_calls) {
5518
+ sawToolCallStart = true;
5502
5519
  for (const tc of delta.tool_calls) {
5503
5520
  const idx = tc.index ?? 0;
5504
5521
  if (!toolCallAccs.has(idx)) {
@@ -5516,6 +5533,7 @@ class OpenAICompatProvider {
5516
5533
  }
5517
5534
  if (delta.content) {
5518
5535
  onEmit();
5536
+ sawText = true;
5519
5537
  yield { type: "text", content: delta.content };
5520
5538
  }
5521
5539
  if (finishReason === "tool_calls" && toolCallAccs.size > 0) {
@@ -5541,6 +5559,17 @@ class OpenAICompatProvider {
5541
5559
  onEmit();
5542
5560
  yield { type: "done", usage };
5543
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
+ }
5544
5573
  } finally {
5545
5574
  cleanup();
5546
5575
  reader.releaseLock();
@@ -7117,7 +7146,12 @@ var init_read_file = __esm(() => {
7117
7146
  remaining: String(total - end),
7118
7147
  next: String(end + 1)
7119
7148
  }) : "";
7120
- 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
+ });
7121
7155
  return {
7122
7156
  success: true,
7123
7157
  output: `${header}
@@ -10504,6 +10538,7 @@ class StuckDetector {
10504
10538
  lastBashOutput = "";
10505
10539
  emptyBashRunCount = 0;
10506
10540
  readOnlyStreak = 0;
10541
+ bashRecent = [];
10507
10542
  errorSignatureCounts = new Map;
10508
10543
  searchedErrorSignatures = new Set;
10509
10544
  webSearchThreshold;
@@ -10558,6 +10593,20 @@ class StuckDetector {
10558
10593
  this.lastBashCommand = command;
10559
10594
  this.lastBashOutput = output;
10560
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
+ }
10561
10610
  getLastBashCommand() {
10562
10611
  return this.lastBashCommand;
10563
10612
  }
@@ -10796,6 +10845,8 @@ class StuckDetector {
10796
10845
  const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
10797
10846
  if (errorTool)
10798
10847
  return `tool-errors:${errorTool[0]}`;
10848
+ if (this.hasBashFlailing())
10849
+ return "bash-flailing";
10799
10850
  if (this.hasConsecutiveFailures())
10800
10851
  return "consecutive";
10801
10852
  if (this.hasRepetitiveToolCalls())
@@ -10844,6 +10895,13 @@ class StuckDetector {
10844
10895
  count: this.consecutiveFailures
10845
10896
  });
10846
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
+ }
10847
10905
  if (reason === "repetitive") {
10848
10906
  return this.getRepetitiveToolMessage();
10849
10907
  }
@@ -10860,6 +10918,8 @@ class StuckDetector {
10860
10918
  const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
10861
10919
  if (errorTool)
10862
10920
  return "tool-errors";
10921
+ if (this.hasBashFlailing())
10922
+ return "bash-flailing";
10863
10923
  if (this.hasConsecutiveFailures())
10864
10924
  return "consecutive";
10865
10925
  if (this.hasRepetitiveToolCalls())
@@ -10881,6 +10941,7 @@ class StuckDetector {
10881
10941
  this.lastBashCommand = "";
10882
10942
  this.lastBashOutput = "";
10883
10943
  this.emptyBashRunCount = 0;
10944
+ this.bashRecent = [];
10884
10945
  this.readOnlyStreak = 0;
10885
10946
  this.errorSignatureCounts.clear();
10886
10947
  this.searchedErrorSignatures.clear();
@@ -10897,6 +10958,7 @@ class StuckDetector {
10897
10958
  this.lastBashCommand = "";
10898
10959
  this.lastBashOutput = "";
10899
10960
  this.emptyBashRunCount = 0;
10961
+ this.bashRecent = [];
10900
10962
  this.errorSignatureCounts.clear();
10901
10963
  this.searchedErrorSignatures.clear();
10902
10964
  }
@@ -10912,12 +10974,13 @@ class StuckDetector {
10912
10974
  this.lastBashCommand = "";
10913
10975
  this.lastBashOutput = "";
10914
10976
  this.emptyBashRunCount = 0;
10977
+ this.bashRecent = [];
10915
10978
  this.readOnlyStreak = 0;
10916
10979
  this.errorSignatureCounts.clear();
10917
10980
  this.searchedErrorSignatures.clear();
10918
10981
  }
10919
10982
  }
10920
- 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;
10921
10984
  var init_stuck_detector = __esm(() => {
10922
10985
  init_i18n();
10923
10986
  READ_ONLY_TOOLS = new Set([
@@ -12626,6 +12689,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12626
12689
  let suppressRepetitionRetry = false;
12627
12690
  let repeatedToolCount = 0;
12628
12691
  const MAX_REPEATED_TOOL_CALLS = 2;
12692
+ let llmErrorRetries = 0;
12629
12693
  let allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
12630
12694
  let boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
12631
12695
  let toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
@@ -12750,6 +12814,17 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12750
12814
  logger.info("LLM call aborted (interrupt)");
12751
12815
  break;
12752
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
+ }
12753
12828
  logger.logLLMResponse(config.model, textContent.length, Date.now() - llmStart, err.message, "agent");
12754
12829
  logger.error(`LLM call failed: ${err.message}`);
12755
12830
  slog.logError(err.message);
@@ -13245,7 +13320,7 @@ ${warnLine}
13245
13320
  });
13246
13321
  }
13247
13322
  }
13248
- 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;
13249
13324
  var init_agent = __esm(() => {
13250
13325
  init_manager();
13251
13326
  init_i18n();
@@ -18072,7 +18147,7 @@ function lintCacheKey(baseDir, lintScript) {
18072
18147
  return `${baseDir}::${lintScript}`;
18073
18148
  }
18074
18149
  function isMissingBinaryError(err, combinedOutput) {
18075
- if (err.status === 127)
18150
+ if (err.status === 127 || err.status === 9009)
18076
18151
  return true;
18077
18152
  if (err.code === "ENOENT")
18078
18153
  return true;
@@ -18086,24 +18161,43 @@ function contentHash(content) {
18086
18161
  return String(h);
18087
18162
  }
18088
18163
  function getWinDecoder() {
18089
- if (_winDecoder === undefined) {
18090
- if (platform5() !== "win32") {
18091
- _winDecoder = null;
18092
- } else {
18093
- try {
18094
- const cpOut = execSync("chcp.com", {
18095
- encoding: "buffer",
18096
- timeout: 2000,
18097
- windowsHide: true
18098
- }).toString("latin1");
18099
- const m = cpOut.match(/(\d+)/);
18100
- _winDecoder = m ? new TextDecoder("ibm" + m[1]) : null;
18101
- } catch {
18102
- _winDecoder = null;
18103
- }
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");
18104
18186
  }
18105
18187
  }
18106
- 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
+ }
18107
18201
  }
18108
18202
 
18109
18203
  class LintOnWritePlugin {
@@ -18275,14 +18369,13 @@ function runAsync(command, cwd, timeoutMs, signal) {
18275
18369
  windowsHide: true,
18276
18370
  stdio: ["ignore", "pipe", "pipe"]
18277
18371
  });
18278
- let stdout = "";
18279
- let stderr = "";
18280
- const decoder = getWinDecoder();
18372
+ const utf8Chunks = [];
18373
+ const oemChunks = [];
18281
18374
  child.stdout?.on("data", (d) => {
18282
- stdout += decoder.decode(d, { stream: true });
18375
+ utf8Chunks.push(Buffer.from(d));
18283
18376
  });
18284
18377
  child.stderr?.on("data", (d) => {
18285
- stderr += decoder.decode(d, { stream: true });
18378
+ oemChunks.push(Buffer.from(d));
18286
18379
  });
18287
18380
  const timer = setTimeout(() => {
18288
18381
  child.kill();
@@ -18298,7 +18391,7 @@ function runAsync(command, cwd, timeoutMs, signal) {
18298
18391
  clearTimeout(timer);
18299
18392
  signal?.removeEventListener("abort", onAbort);
18300
18393
  if (signal?.aborted) {
18301
- resolve19({ stdout, stderr });
18394
+ resolve19({ stdout: decodeOutput(utf8Chunks), stderr: decodeOutput(oemChunks) });
18302
18395
  } else {
18303
18396
  reject(err);
18304
18397
  }
@@ -18306,8 +18399,8 @@ function runAsync(command, cwd, timeoutMs, signal) {
18306
18399
  child.on("close", (code) => {
18307
18400
  clearTimeout(timer);
18308
18401
  signal?.removeEventListener("abort", onAbort);
18309
- stdout += decoder.decode();
18310
- stderr += decoder.decode();
18402
+ const stdout = decodeOutput(utf8Chunks);
18403
+ const stderr = decodeOutput(oemChunks);
18311
18404
  if (signal?.aborted || code === 0) {
18312
18405
  resolve19({ stdout, stderr });
18313
18406
  } else {
@@ -18991,6 +19084,9 @@ Last compile error: ${first[1]}`;
18991
19084
  }
18992
19085
  }
18993
19086
  if (!result.success) {
19087
+ if (call.name === "bash") {
19088
+ deps.stuckDetector.recordBashAttempt(false);
19089
+ }
18994
19090
  if (call.name === "bash" && platform6() === "win32") {
18995
19091
  const cmd = String(call.arguments?.command ?? "");
18996
19092
  const forbidden = forbiddenWindowsCommand(cmd);
@@ -19010,6 +19106,9 @@ Last compile error: ${first[1]}`;
19010
19106
  }
19011
19107
  } else {
19012
19108
  deps.stuckDetector.recordToolSuccess();
19109
+ if (call.name === "bash") {
19110
+ deps.stuckDetector.recordBashAttempt(true);
19111
+ }
19013
19112
  if (call.name === "bash" && result.success) {
19014
19113
  const cmd = String(call.arguments?.command ?? "");
19015
19114
  const testRun = detectTestResults(String(result.output ?? ""));
@@ -33371,8 +33470,9 @@ function stripAnsi2(s) {
33371
33470
  function charLen(s) {
33372
33471
  return Array.from(s).length;
33373
33472
  }
33374
- function wrapLine(line, width) {
33473
+ function wrapLine(line, width, firstRowWidth) {
33375
33474
  const w = width > 0 ? width : 80;
33475
+ let limit = firstRowWidth !== undefined && firstRowWidth > 0 ? firstRowWidth : w;
33376
33476
  const chars = Array.from(line);
33377
33477
  const rows = [];
33378
33478
  let start = 0;
@@ -33387,10 +33487,11 @@ function wrapLine(line, width) {
33387
33487
  continue;
33388
33488
  }
33389
33489
  const cw = stringWidth(ch);
33390
- if (cells + cw > w && cells > 0) {
33490
+ if (cells + cw > limit && cells > 0) {
33391
33491
  rows.push({ text: chars.slice(start, i).join(""), charStart: start });
33392
33492
  start = i;
33393
33493
  cells = cw;
33494
+ limit = w;
33394
33495
  } else {
33395
33496
  cells += cw;
33396
33497
  }
@@ -33398,10 +33499,10 @@ function wrapLine(line, width) {
33398
33499
  rows.push({ text: chars.slice(start).join(""), charStart: start });
33399
33500
  return rows;
33400
33501
  }
33401
- function buildLayout(lines, width) {
33502
+ function buildLayout(lines, width, firstRowWidth) {
33402
33503
  const rows = [];
33403
33504
  for (let b = 0;b < lines.length; b++) {
33404
- for (const r of wrapLine(lines[b], width)) {
33505
+ for (const r of wrapLine(lines[b], width, b === 0 ? firstRowWidth : undefined)) {
33405
33506
  rows.push({ bufRow: b, charStart: r.charStart, text: r.text });
33406
33507
  }
33407
33508
  }
@@ -33438,6 +33539,7 @@ class LineEditor {
33438
33539
  input;
33439
33540
  output;
33440
33541
  onKeyInput;
33542
+ onInterrupt;
33441
33543
  promptStr;
33442
33544
  completer;
33443
33545
  history;
@@ -33456,11 +33558,15 @@ class LineEditor {
33456
33558
  rawMode = false;
33457
33559
  listeners = {};
33458
33560
  prevCursorRow = 0;
33561
+ frameTopAbs = null;
33562
+ dsrPending = false;
33563
+ dsrTail = "";
33459
33564
  constructor(opts) {
33460
33565
  this.input = opts.input;
33461
33566
  this.output = opts.output;
33462
33567
  this.promptStr = opts.prompt ?? "";
33463
33568
  this.completer = opts.completer;
33569
+ this.onInterrupt = opts.onInterrupt;
33464
33570
  this.history = opts.history ?? [];
33465
33571
  this.historySize = opts.historySize ?? 50;
33466
33572
  if (this.history.length > this.historySize) {
@@ -33471,6 +33577,7 @@ class LineEditor {
33471
33577
  if (this.rawMode)
33472
33578
  this.input.setRawMode(true);
33473
33579
  this.enableTerminalProtocols();
33580
+ this.input.on("data", (buf) => this.consumeDsrReplies(buf.toString("utf-8")));
33474
33581
  readline2.emitKeypressEvents(this.input);
33475
33582
  this.input.on("keypress", (str, key) => this.onKey(str, key));
33476
33583
  } else {
@@ -33513,7 +33620,7 @@ class LineEditor {
33513
33620
  this.col = 0;
33514
33621
  this.historyIndex = -1;
33515
33622
  this.stash = null;
33516
- this.prevCursorRow = 0;
33623
+ this.invalidateAnchor();
33517
33624
  if (this.input.isTTY)
33518
33625
  this.render();
33519
33626
  }
@@ -33523,7 +33630,7 @@ class LineEditor {
33523
33630
  this.lines = [""];
33524
33631
  this.row = 0;
33525
33632
  this.col = 0;
33526
- this.prevCursorRow = 0;
33633
+ this.invalidateAnchor();
33527
33634
  if (this.input.isTTY)
33528
33635
  this.render();
33529
33636
  }
@@ -33743,10 +33850,10 @@ class LineEditor {
33743
33850
  this.commitFrame();
33744
33851
  this.output.write(`\r
33745
33852
  `);
33853
+ this.invalidateAnchor();
33746
33854
  this.lines = [""];
33747
33855
  this.row = 0;
33748
33856
  this.col = 0;
33749
- this.prevCursorRow = 0;
33750
33857
  cb(answer);
33751
33858
  return;
33752
33859
  }
@@ -33763,17 +33870,21 @@ class LineEditor {
33763
33870
  this.commitFrame();
33764
33871
  this.output.write(`\r
33765
33872
  `);
33873
+ this.invalidateAnchor();
33766
33874
  this.lines = [""];
33767
33875
  this.row = 0;
33768
33876
  this.col = 0;
33769
- this.prevCursorRow = 0;
33770
33877
  this.emitLine(text);
33771
33878
  }
33772
33879
  commitFrame() {
33773
- 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
+ }
33774
33886
  const idx = layoutIndexFor(layout, this.row, this.col);
33775
33887
  const rowsDown = layout.length - 1 - idx;
33776
- const lastLen = charLen(stripAnsi2(layout[layout.length - 1].text));
33777
33888
  const out = [];
33778
33889
  if (rowsDown > 0)
33779
33890
  out.push(`\x1B[${rowsDown}B`);
@@ -33786,9 +33897,15 @@ class LineEditor {
33786
33897
  this.lines = [""];
33787
33898
  this.row = 0;
33788
33899
  this.col = 0;
33789
- this.prevCursorRow = 0;
33900
+ this.invalidateAnchor();
33790
33901
  this.render();
33791
- process.emit("SIGINT");
33902
+ if (this.onInterrupt) {
33903
+ this.onInterrupt();
33904
+ return;
33905
+ }
33906
+ if (process.listenerCount("SIGINT") > 0) {
33907
+ process.emit("SIGINT");
33908
+ }
33792
33909
  }
33793
33910
  ctrlD() {
33794
33911
  if (this.lines.length === 1 && this.lines[0] === "") {
@@ -34055,28 +34172,68 @@ class LineEditor {
34055
34172
  }
34056
34173
  clearScreen() {
34057
34174
  this.output.write("\x1B[2J\x1B[H");
34058
- this.prevCursorRow = 0;
34175
+ this.invalidateAnchor();
34059
34176
  this.render();
34060
34177
  }
34061
34178
  columns() {
34062
34179
  return this.output.columns || 80;
34063
34180
  }
34064
34181
  layout() {
34065
- 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;
34066
34218
  }
34067
34219
  render() {
34068
34220
  if (!this.input.isTTY || this.isDone)
34069
34221
  return;
34070
- const layout = this.layout();
34222
+ const layout = this.visibleLayout();
34071
34223
  const promptWidth = stringWidth(stripAnsi2(this.promptStr));
34072
34224
  const idx = layoutIndexFor(layout, this.row, this.col);
34073
34225
  const vcol = visualColAt(layout, idx, this.col);
34074
34226
  const rowsAfter = layout.length - idx - 1;
34075
34227
  const ccol = (idx === 0 ? promptWidth : 0) + vcol;
34076
34228
  const out = [];
34077
- if (this.prevCursorRow > 0)
34229
+ if (this.frameTopAbs !== null) {
34230
+ out.push(`\x1B[${this.frameTopAbs};1H`);
34231
+ } else if (this.prevCursorRow > 0) {
34078
34232
  out.push(`\x1B[${this.prevCursorRow}A`);
34079
- out.push("\r");
34233
+ out.push("\r");
34234
+ } else {
34235
+ out.push("\r");
34236
+ }
34080
34237
  for (let i = 0;i < layout.length; i++) {
34081
34238
  out.push("\x1B[2K");
34082
34239
  if (i === 0)
@@ -34092,8 +34249,17 @@ class LineEditor {
34092
34249
  out.push("\r");
34093
34250
  if (ccol > 0)
34094
34251
  out.push(`\x1B[${ccol}C`);
34095
- this.prevCursorRow = idx;
34096
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;
34097
34263
  }
34098
34264
  enableTerminalProtocols() {
34099
34265
  this.output.write("\x1B[?2004h");
@@ -34450,9 +34616,28 @@ init_box();
34450
34616
  init_table();
34451
34617
  init_i18n();
34452
34618
  init_prices();
34619
+ import { isAbsolute as isAbsolute4, relative as relative6, sep as sep2 } from "path";
34453
34620
  function formatUsd(cost) {
34454
34621
  return formatCost(cost);
34455
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
+ }
34456
34641
  var GUTTER = " ";
34457
34642
  var BUSY_TOOLS = new Set(["lsp_check"]);
34458
34643
  function toolMarker(tool) {
@@ -34506,6 +34691,7 @@ class Renderer {
34506
34691
  err;
34507
34692
  width;
34508
34693
  toolStyle;
34694
+ baseDir;
34509
34695
  card = null;
34510
34696
  constructor(opts = {}) {
34511
34697
  this.rich = opts.rich ?? isRichTerminal();
@@ -34513,6 +34699,7 @@ class Renderer {
34513
34699
  this.err = opts.err ?? process.stderr;
34514
34700
  this.width = opts.width ?? getTerminalWidth();
34515
34701
  this.toolStyle = opts.toolStyle ?? "inline";
34702
+ this.baseDir = opts.baseDir;
34516
34703
  this.spinner = new Spinner({
34517
34704
  enabled: this.rich && (opts.spinner ?? true),
34518
34705
  stream: this.err,
@@ -34560,7 +34747,8 @@ class Renderer {
34560
34747
  toolStart(tool, args, stepContext, icon) {
34561
34748
  this.endCard();
34562
34749
  this.spinner.stop();
34563
- 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);
34564
34752
  const step = stepContext ? ` ${pc2.cyan(`← ${stepContext}`)}` : "";
34565
34753
  const marker = icon || toolMarker(tool);
34566
34754
  if (!this.rich) {
@@ -35071,7 +35259,10 @@ class Repl {
35071
35259
  this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
35072
35260
  }
35073
35261
  let forceExitTimer = null;
35074
- process.on("SIGINT", () => {
35262
+ if (typeof Repl.sigintHandler === "function") {
35263
+ process.removeListener("SIGINT", Repl.sigintHandler);
35264
+ }
35265
+ Repl.sigintHandler = () => {
35075
35266
  if (this.agentRunning) {
35076
35267
  console.log(pc2.yellow(t("repl.ctrl_c_interrupt")));
35077
35268
  this.agent.shutdown();
@@ -35082,7 +35273,8 @@ class Repl {
35082
35273
  } else {
35083
35274
  process.exit(0);
35084
35275
  }
35085
- });
35276
+ };
35277
+ process.on("SIGINT", Repl.sigintHandler);
35086
35278
  }
35087
35279
  handleSpecialKey(str, key) {
35088
35280
  if (key.name === "escape") {
@@ -35160,7 +35352,8 @@ ${t("image.clipboard_empty")}`));
35160
35352
  ` + pc2.green(t("repl.agent")));
35161
35353
  const renderer = new Renderer({
35162
35354
  spinner: this.config.ui?.spinner ?? true,
35163
- toolStyle: this.config.ui?.toolStyle ?? "inline"
35355
+ toolStyle: this.config.ui?.toolStyle ?? "inline",
35356
+ baseDir: this.baseDir
35164
35357
  });
35165
35358
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
35166
35359
  if (ev.type === "start") {
@@ -35642,7 +35835,7 @@ async function main() {
35642
35835
  }
35643
35836
  if (program2.args.length > 0) {
35644
35837
  const prompt = program2.args.join(" ");
35645
- const { agent, config } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
35838
+ const { agent, config, baseDir } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
35646
35839
  const updater = exitOnComplete ? undefined : startAutoUpdate(config);
35647
35840
  if (jsonMode) {
35648
35841
  const result2 = await agent.run(prompt);
@@ -35667,7 +35860,8 @@ async function main() {
35667
35860
  }
35668
35861
  const renderer = new Renderer({
35669
35862
  spinner: config.ui?.spinner ?? true,
35670
- toolStyle: config.ui?.toolStyle ?? "inline"
35863
+ toolStyle: config.ui?.toolStyle ?? "inline",
35864
+ baseDir
35671
35865
  });
35672
35866
  const result = await agent.run(prompt, (chunk) => renderer.text(chunk), (meta) => renderer.meta(meta), (ev) => {
35673
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.1",
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": {