micro-models-agent 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -2395,18 +2395,7 @@ var init_defaults = __esm(() => {
2395
2395
  verbosity: "quiet",
2396
2396
  fontSize: "comfortable"
2397
2397
  },
2398
- mcpServers: {
2399
- context7: {
2400
- name: "context7",
2401
- transport: "http",
2402
- url: "https://mcp.context7.com/mcp",
2403
- headers: {
2404
- CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY || ""
2405
- },
2406
- enabled: true,
2407
- timeout: 30000
2408
- }
2409
- },
2398
+ mcpServers: {},
2410
2399
  security: DEFAULT_SECURITY_CONFIG,
2411
2400
  sessionIsolation: {
2412
2401
  enabled: false,
@@ -3073,6 +3062,9 @@ Available commands:`,
3073
3062
  "repl.model": "Model:",
3074
3063
  "repl.provider": "Provider:",
3075
3064
  "repl.context": "Context:",
3065
+ "repl.ctx_part_system": "system",
3066
+ "repl.ctx_part_reserve": "reserve",
3067
+ "repl.ctx_part_history": "history",
3076
3068
  "repl.sysprompt_label": "System prompt:",
3077
3069
  "repl.sysprompt_size": "{used} / {budget} tokens",
3078
3070
  "repl.sysprompt_desc": "System prompt size and blocks (use --full for the text)",
@@ -3577,6 +3569,8 @@ Updated: {updatedAt}`,
3577
3569
  "web.sessbar.prev": "previous session",
3578
3570
  "web.sessbar.next": "next session",
3579
3571
  "web.conn.reconnecting": "reconnecting…",
3572
+ "web.i18n.stale": "translations updated — restart the server",
3573
+ "web.i18n.reload": "reload page",
3580
3574
  "web.sidebar.plan": "plan",
3581
3575
  "web.sidebar.session": "session",
3582
3576
  "web.sidebar.usage": "usage",
@@ -3607,6 +3601,18 @@ Updated: {updatedAt}`,
3607
3601
  "web.sidebar.trace_text": "composing… {count} chars",
3608
3602
  "web.sidebar.trace_plan": "plan update",
3609
3603
  "web.sidebar.trace_question": "question",
3604
+ "web.sidebar.trace_status": "status",
3605
+ "web.sidebar.trace_log": "log · {level}",
3606
+ "web.sidebar.trace_retract": "draft retracted",
3607
+ "web.sidebar.trace_index": "index · {phase}",
3608
+ "web.sidebar.trace_critic": "critic · {status}",
3609
+ "web.sidebar.trace_moe": "moe",
3610
+ "web.sidebar.trace_compaction": "compaction",
3611
+ "web.sidebar.trace_probe": "context probe",
3612
+ "web.sidebar.trace_usage": "llm usage",
3613
+ "web.sidebar.trace_audit": "audit",
3614
+ "web.sidebar.trace_claim": "claim retry · {kind}",
3615
+ "web.sidebar.trace_output": "output",
3610
3616
  "web.sidebar.detail_metadata": "metadata",
3611
3617
  "web.sidebar.detail_arguments": "arguments",
3612
3618
  "web.sidebar.detail_output": "output",
@@ -3621,6 +3627,10 @@ Updated: {updatedAt}`,
3621
3627
  "web.sidebar.detail_time": "time",
3622
3628
  "web.sidebar.detail_tool_call": "tool call",
3623
3629
  "web.sidebar.detail_session": "session",
3630
+ "web.sidebar.detail_turn": "turn",
3631
+ "web.sidebar.detail_iteration": "iteration",
3632
+ "web.sidebar.detail_provenance": "session-log provenance",
3633
+ "web.sidebar.detail_provenance_count": "{n} assistant messages in log",
3624
3634
  "web.env.dir": "dir",
3625
3635
  "web.env.agents": "AGENTS.md",
3626
3636
  "web.env.map": "map",
@@ -3643,6 +3653,11 @@ Updated: {updatedAt}`,
3643
3653
  "web.usage.cache": "cache",
3644
3654
  "web.usage.cost": "cost",
3645
3655
  "web.usage.compactions": "compactions",
3656
+ "web.usage.part_system": "instructions",
3657
+ "web.usage.part_tools": "tools",
3658
+ "web.usage.part_history": "dialogue",
3659
+ "web.usage.part_reserve": "response reserve",
3660
+ "web.usage.part_free": "free",
3646
3661
  "web.usage.quality": "quality",
3647
3662
  "web.usage.probe": "probe",
3648
3663
  "web.usage.probe_loaded": "{actual} loaded",
@@ -3935,9 +3950,10 @@ Updated: {updatedAt}`,
3935
3950
  "web.input.shift_enter": "Shift+Enter — newline",
3936
3951
  "web.input.esc": "Esc — stop",
3937
3952
  "web.input.attach": "Ctrl+V — attach",
3938
- "web.input.busy": "still working — Esc to stop",
3939
3953
  "web.input.send": "send",
3940
3954
  "web.input.stop": "stop",
3955
+ "web.input.queued": "queued · position {n}",
3956
+ "web.input.queue_full": "message queue is full — wait for the turn to end",
3941
3957
  "web.empty.title": "new chat",
3942
3958
  "web.empty.hint": "type a message below · / — commands · model and context are chosen above the input",
3943
3959
  "web.attach.title": "attach image",
@@ -3954,6 +3970,7 @@ Updated: {updatedAt}`,
3954
3970
  "web.sessions.none_active": "— no active session —",
3955
3971
  "web.sessions.none": "— none —",
3956
3972
  "web.sessions.delete": "delete",
3973
+ "web.sessions.search": "search sessions…",
3957
3974
  "web.sessions.create_failed": "session create failed",
3958
3975
  "web.sessions.delete_failed": "delete failed",
3959
3976
  "web.sessions.confirm_title": "Delete session?",
@@ -3966,6 +3983,7 @@ Updated: {updatedAt}`,
3966
3983
  "web.selectors.switch_busy": "switch in progress, please wait",
3967
3984
  "web.selectors.reasoning_off": "off (backend ignores it)",
3968
3985
  "web.selectors.no_models": "no models",
3986
+ "web.selectors.search_models": "search models…",
3969
3987
  "web.selectors.refresh_list": "↻ refresh list",
3970
3988
  "web.selectors.custom": "custom…",
3971
3989
  "web.selectors.current": "current",
@@ -4972,6 +4990,9 @@ var init_ru = __esm(() => {
4972
4990
  "repl.model": "Модель:",
4973
4991
  "repl.provider": "Провайдер:",
4974
4992
  "repl.context": "Контекст:",
4993
+ "repl.ctx_part_system": "система",
4994
+ "repl.ctx_part_reserve": "резерв",
4995
+ "repl.ctx_part_history": "история",
4975
4996
  "repl.sysprompt_label": "Системный промпт:",
4976
4997
  "repl.sysprompt_size": "{used} / {budget} токенов",
4977
4998
  "repl.sysprompt_desc": "Размер системного промпта и блоки (текст — через --full)",
@@ -5483,6 +5504,8 @@ var init_ru = __esm(() => {
5483
5504
  "web.sessbar.prev": "предыдущая сессия",
5484
5505
  "web.sessbar.next": "следующая сессия",
5485
5506
  "web.conn.reconnecting": "переподключение…",
5507
+ "web.i18n.stale": "переводы обновлены — перезапустите сервер",
5508
+ "web.i18n.reload": "обновить страницу",
5486
5509
  "web.sidebar.plan": "план",
5487
5510
  "web.sidebar.session": "сессия",
5488
5511
  "web.sidebar.usage": "использование",
@@ -5513,6 +5536,18 @@ var init_ru = __esm(() => {
5513
5536
  "web.sidebar.trace_text": "печатает… {count} симв.",
5514
5537
  "web.sidebar.trace_plan": "обновление плана",
5515
5538
  "web.sidebar.trace_question": "вопрос",
5539
+ "web.sidebar.trace_status": "статус",
5540
+ "web.sidebar.trace_log": "лог · {level}",
5541
+ "web.sidebar.trace_retract": "черновик отозван",
5542
+ "web.sidebar.trace_index": "индекс · {phase}",
5543
+ "web.sidebar.trace_critic": "критик · {status}",
5544
+ "web.sidebar.trace_moe": "moe",
5545
+ "web.sidebar.trace_compaction": "компакция",
5546
+ "web.sidebar.trace_probe": "проба контекста",
5547
+ "web.sidebar.trace_usage": "расход llm",
5548
+ "web.sidebar.trace_audit": "аудит",
5549
+ "web.sidebar.trace_claim": "перепроверка · {kind}",
5550
+ "web.sidebar.trace_output": "вывод",
5516
5551
  "web.sidebar.detail_metadata": "метаданные",
5517
5552
  "web.sidebar.detail_arguments": "аргументы",
5518
5553
  "web.sidebar.detail_output": "вывод",
@@ -5527,6 +5562,10 @@ var init_ru = __esm(() => {
5527
5562
  "web.sidebar.detail_time": "время",
5528
5563
  "web.sidebar.detail_tool_call": "вызов тула",
5529
5564
  "web.sidebar.detail_session": "сессия",
5565
+ "web.sidebar.detail_turn": "ход",
5566
+ "web.sidebar.detail_iteration": "итерация",
5567
+ "web.sidebar.detail_provenance": "сверка с логом",
5568
+ "web.sidebar.detail_provenance_count": "assistant-сообщений в логе: {n}",
5530
5569
  "web.env.dir": "директория",
5531
5570
  "web.env.agents": "AGENTS.md",
5532
5571
  "web.env.map": "карта",
@@ -5549,6 +5588,11 @@ var init_ru = __esm(() => {
5549
5588
  "web.usage.cache": "кеш",
5550
5589
  "web.usage.cost": "стоимость",
5551
5590
  "web.usage.compactions": "сжатия",
5591
+ "web.usage.part_system": "инструкции",
5592
+ "web.usage.part_tools": "инструменты",
5593
+ "web.usage.part_history": "диалог",
5594
+ "web.usage.part_reserve": "резерв ответа",
5595
+ "web.usage.part_free": "свободно",
5552
5596
  "web.usage.quality": "качество",
5553
5597
  "web.usage.probe": "проб",
5554
5598
  "web.usage.probe_loaded": "загружено {actual}",
@@ -5814,9 +5858,10 @@ var init_ru = __esm(() => {
5814
5858
  "web.input.shift_enter": "Shift+Enter — новая строка",
5815
5859
  "web.input.esc": "Esc — стоп",
5816
5860
  "web.input.attach": "Ctrl+V — вложить",
5817
- "web.input.busy": "ещё работаю — Esc для остановки",
5818
5861
  "web.input.send": "отправить",
5819
5862
  "web.input.stop": "остановить",
5863
+ "web.input.queued": "в очереди · позиция {n}",
5864
+ "web.input.queue_full": "очередь сообщений полна — дождись конца хода",
5820
5865
  "web.empty.title": "новый чат",
5821
5866
  "web.empty.hint": "введи сообщение ниже · / — команды · модель и контекст выбираются над полем",
5822
5867
  "web.attach.title": "прикрепить изображение",
@@ -5833,6 +5878,7 @@ var init_ru = __esm(() => {
5833
5878
  "web.sessions.none_active": "— нет активной сессии —",
5834
5879
  "web.sessions.none": "— нет —",
5835
5880
  "web.sessions.delete": "удалить",
5881
+ "web.sessions.search": "поиск сессий…",
5836
5882
  "web.sessions.create_failed": "не удалось создать сессию",
5837
5883
  "web.sessions.delete_failed": "не удалось удалить",
5838
5884
  "web.sessions.confirm_title": "Удалить сессию?",
@@ -5845,6 +5891,7 @@ var init_ru = __esm(() => {
5845
5891
  "web.selectors.switch_busy": "переключение уже идёт, подождите",
5846
5892
  "web.selectors.reasoning_off": "выкл (бэкенд игнорирует)",
5847
5893
  "web.selectors.no_models": "нет моделей",
5894
+ "web.selectors.search_models": "поиск моделей…",
5848
5895
  "web.selectors.refresh_list": "↻ обновить список",
5849
5896
  "web.selectors.custom": "другое…",
5850
5897
  "web.selectors.current": "текущее",
@@ -11093,7 +11140,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
11093
11140
  if (config.autoPlan) {
11094
11141
  lines.push(`PLAN (MANDATORY): file creation, package installs, or multi-action tasks MUST create a "plan" BEFORE starting.`);
11095
11142
  }
11096
- lines.push(`Runtime verification: when a bug is described in terms of runtime behavior`, `(what happens when the app runs), reproduce it —`, `run the app / build / browser tool — confirm the fix against actual runtime output`, `before calling "plan_step step=N status=done".`, `Check existing servers first (process_list/process_log): a stale dev server`, `runs the old pipeline and keeps failing after the fix.`, `Restart/kill it (process_kill) and verify the exact endpoint/port reported —`, `a fresh port does NOT prove the user's error is gone.`);
11143
+ lines.push(`Runtime verification: when a bug is described in terms of runtime behavior`, `(what happens when the app runs), reproduce it —`, `run the app / build / browser tool — confirm the fix against actual runtime output`, `before calling "plan_step step=N status=done".`, `Check existing servers first (process_list/process_log): a stale dev server`, `runs the old pipeline and keeps failing after the fix.`, `Restart/kill it (process_kill) and verify the exact endpoint/port reported —`, `a fresh port does NOT prove the user's error is gone.`, `Verification "mma web" servers run isolated (MMA_CONFIG_DIR=<empty tmp dir>) —`, `never verify against the user's live server/sessions/state.`);
11097
11144
  const sec = config.security;
11098
11145
  if (sec?.enabled) {
11099
11146
  const secLines = [
@@ -14404,6 +14451,40 @@ var init_write_file = __esm(() => {
14404
14451
  // src/tools/edit-file.ts
14405
14452
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
14406
14453
  import { resolve as resolve6 } from "path";
14454
+ function spliceNormalized(content, oldStr, newStr) {
14455
+ const normOld = oldStr.replace(/\r\n/g, `
14456
+ `);
14457
+ const norm = [];
14458
+ const map = [];
14459
+ for (let i = 0;i < content.length; i++) {
14460
+ if (content[i] === "\r" && content[i + 1] === `
14461
+ `) {
14462
+ norm.push(`
14463
+ `);
14464
+ map.push(i);
14465
+ i++;
14466
+ } else {
14467
+ norm.push(content[i]);
14468
+ map.push(i);
14469
+ }
14470
+ }
14471
+ const normContent = norm.join("");
14472
+ const idx = normContent.indexOf(normOld);
14473
+ if (idx === -1 || normOld.length === 0)
14474
+ return null;
14475
+ const startOrig = map[idx];
14476
+ const endNorm = idx + normOld.length;
14477
+ const endOrig = endNorm < map.length ? map[endNorm] : content.length;
14478
+ let insert = newStr;
14479
+ if (content.includes(`\r
14480
+ `) && !newStr.includes(`\r
14481
+ `) && newStr.includes(`
14482
+ `)) {
14483
+ insert = newStr.replace(/\n/g, `\r
14484
+ `);
14485
+ }
14486
+ return content.slice(0, startOrig) + insert + content.slice(endOrig);
14487
+ }
14407
14488
  var editFileTool;
14408
14489
  var init_edit_file = __esm(() => {
14409
14490
  init_i18n();
@@ -14488,13 +14569,19 @@ var init_edit_file = __esm(() => {
14488
14569
  output: t("file.read_failed", { path, error: errMsg(err) })
14489
14570
  };
14490
14571
  }
14491
- if (!content.includes(oldStr)) {
14492
- return {
14493
- success: false,
14494
- output: t("file.string_not_found", { str: oldStr, path })
14495
- };
14572
+ let updated;
14573
+ if (content.includes(oldStr)) {
14574
+ updated = content.replace(oldStr, () => newStr);
14575
+ } else {
14576
+ const spliced = spliceNormalized(content, oldStr, newStr);
14577
+ if (!spliced) {
14578
+ return {
14579
+ success: false,
14580
+ output: t("file.string_not_found", { str: oldStr, path })
14581
+ };
14582
+ }
14583
+ updated = spliced;
14496
14584
  }
14497
- const updated = content.replace(oldStr, () => newStr);
14498
14585
  if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
14499
14586
  const scanResult = scanContent(updated, path, securityConfig.contentScan);
14500
14587
  if (!scanResult.allowed) {
@@ -21460,6 +21547,27 @@ function queueToolImages(sink, images) {
21460
21547
  }
21461
21548
 
21462
21549
  // src/core/agent/tool-batch.ts
21550
+ function diagnoseRepeatedFailures(tool, headlines) {
21551
+ const genericCause = "Repeated failures suggest a systemic problem (config, environment, or a broken tool), not a one-off";
21552
+ const genericSolution = "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool";
21553
+ const modelMiss = tool === "bash" ? [/did not match any test files?/i, /no such file/i, /cannot find (the )?path/i] : [];
21554
+ if (headlines.length > 0 && headlines.every((h) => /^\(exit code \d+\)$/.test(h.trim()) || modelMiss.some((re) => re.test(h)))) {
21555
+ return {
21556
+ skip: true,
21557
+ cause: "Model named a wrong target (path/filter); the tool itself works",
21558
+ solution: "Re-check the target path or filter instead of blaming the tool"
21559
+ };
21560
+ }
21561
+ if (tool === "edit_file" && headlines.some((h) => /string not found|не найдена/i.test(h))) {
21562
+ return {
21563
+ skip: false,
21564
+ cause: "Anchor mismatch between the model anchor and file content — often CRLF vs LF line endings or a stale read, not a broken tool",
21565
+ solution: "Re-read the exact block with read_file and retry with a short unique single-line anchor"
21566
+ };
21567
+ }
21568
+ return { skip: false, cause: genericCause, solution: genericSolution };
21569
+ }
21570
+
21463
21571
  class ToolBatchExecutor {
21464
21572
  deps;
21465
21573
  constructor(deps) {
@@ -21619,15 +21727,22 @@ ${result.diff}
21619
21727
  this.deps.compactionService.compactAfterTool(state);
21620
21728
  if (!result.success && !result.blocked && !infoTools.has(call.name)) {
21621
21729
  const key = call.name;
21622
- const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "" };
21730
+ const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "", samples: [] };
21623
21731
  prev.count++;
21624
- prev.error = String(result.output ?? "").slice(0, 200);
21732
+ const errText = String(result.output ?? "");
21733
+ prev.error = errText.slice(0, 200);
21734
+ const headline = errText.split(`
21735
+ `)[0].slice(0, 160).trim();
21736
+ if (headline && !prev.samples.includes(headline) && prev.samples.length < 3) {
21737
+ prev.samples.push(headline);
21738
+ }
21625
21739
  state.toolFailureCounts.set(key, prev);
21626
21740
  if (prev.count >= MIN_REPEATED_TOOL_FAILURES && !state.memoryRuleRecorded.has(key)) {
21627
21741
  state.memoryRuleRecorded.add(key);
21628
21742
  const memStore = this.deps.memoryStore;
21629
- if (memStore) {
21630
- memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
21743
+ const diagnosis = diagnoseRepeatedFailures(key, prev.samples);
21744
+ if (memStore && !diagnosis.skip) {
21745
+ memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.samples.join(" | ") || prev.error})`, diagnosis.cause, diagnosis.solution);
21631
21746
  logger3.diagnostic("warn", `Recorded repeated ${key} failures to memory (${prev.count}x)`);
21632
21747
  }
21633
21748
  }
@@ -21704,7 +21819,7 @@ function checkSuccessClaim(response, tools) {
21704
21819
  return null;
21705
21820
  if (HEDGE_RE.test(response))
21706
21821
  return null;
21707
- const recent = tools.filter((t2) => t2.text?.trim()).slice(-EVIDENCE_WINDOW);
21822
+ const recent = tools.filter((t2) => t2.text?.trim() && !BOOKKEEPING_TOOLS.has(t2.name ?? "")).slice(-EVIDENCE_WINDOW);
21708
21823
  for (let i = recent.length - 1;i >= 0; i--) {
21709
21824
  const entry = recent[i];
21710
21825
  const text = entry.text.slice(0, EVIDENCE_TEXT_CAP);
@@ -21721,11 +21836,12 @@ function checkSuccessClaim(response, tools) {
21721
21836
  }
21722
21837
  return null;
21723
21838
  }
21724
- var WORD = "A-Za-zА-Яа-яЁё0-9_", wb = (body) => new RegExp(`(?<![${WORD}])(?:${body})(?![${WORD}])`, "i"), SUCCESS_CLAIM_RE, HEDGE_RE, FAILURE_SIGNAL_RE, EVIDENCE_WINDOW = 10, EVIDENCE_TEXT_CAP = 400, SNIPPET_MAX_CHARS = 160;
21839
+ var WORD = "A-Za-zА-Яа-яЁё0-9_", wb = (body) => new RegExp(`(?<![${WORD}])(?:${body})(?![${WORD}])`, "i"), SUCCESS_CLAIM_RE, HEDGE_RE, FAILURE_SIGNAL_RE, EVIDENCE_WINDOW = 10, EVIDENCE_TEXT_CAP = 400, SNIPPET_MAX_CHARS = 160, BOOKKEEPING_TOOLS;
21725
21840
  var init_success_claim = __esm(() => {
21726
21841
  SUCCESS_CLAIM_RE = wb("published|опубликован[а-яё]*|задеплоен[а-яё]*|deployed|shipped|готово|выполнено|завершен[а-яё]*|успешно|success(?:ful(?:ly)?)?|done|completed?|finished|все сделано|всё сделано");
21727
21842
  HEDGE_RE = wb("may take|being processed|processing|staged|propagat[a-z]*|in progress|shortly|soon|will appear|несколько минут|обрабатывается|застейджен[а-яё]*|подожд[а-яё]*|проверь[а-яё]* позже|появится|пропагир[а-яё]*|кэш[а-яё]*|cach[a-z]*|обычно|typically");
21728
21843
  FAILURE_SIGNAL_RE = new RegExp(`${wb("404|409|40[03]|50\\d|failed|failure|not found|ERR_[A-Z_]+|E(?:404|409|ACCES|PERM|CONNREFUSED|TIMED?OUT)|conflict|denied|ошибка|ошибк[а-яё]*|не найден[а-яё]*|не удалось|отклонено").source}|✗|⛔`, "i");
21844
+ BOOKKEEPING_TOOLS = new Set(["plan", "plan_step", "todo"]);
21729
21845
  });
21730
21846
 
21731
21847
  // src/modules/hallucination/artifact-claim.ts
@@ -22459,6 +22575,7 @@ var init_provider = __esm(() => {
22459
22575
 
22460
22576
  // src/modules/critic/evidence.ts
22461
22577
  import { readFileSync as readFileSync26 } from "fs";
22578
+ import { execFileSync } from "node:child_process";
22462
22579
  import { resolve as resolve15 } from "path";
22463
22580
  function isWorkMutation(name) {
22464
22581
  return MUTATING_TOOLS.has(name) || name === "bash";
@@ -22476,6 +22593,10 @@ function buildEvidencePack(input) {
22476
22593
  if (cmd)
22477
22594
  commands.push(cmd);
22478
22595
  }
22596
+ if (input.baseDir) {
22597
+ for (const f of gitChangedFiles(input.baseDir))
22598
+ changedFiles.add(f);
22599
+ }
22479
22600
  return {
22480
22601
  task: input.task,
22481
22602
  planSummary: input.planSummary,
@@ -22500,6 +22621,45 @@ function changedFileOf(ev) {
22500
22621
  return;
22501
22622
  return typeof ev.args?.path === "string" ? ev.args.path : undefined;
22502
22623
  }
22624
+ function parsePorcelainLine(line) {
22625
+ if (line.length < 4)
22626
+ return null;
22627
+ let p = line.slice(3).trim();
22628
+ const arrow = p.indexOf(" -> ");
22629
+ if (arrow !== -1)
22630
+ p = p.slice(arrow + 4).trim();
22631
+ if (p.length >= 2 && p.startsWith('"') && p.endsWith('"')) {
22632
+ try {
22633
+ p = JSON.parse(p);
22634
+ } catch {
22635
+ p = p.slice(1, -1);
22636
+ }
22637
+ }
22638
+ return p || null;
22639
+ }
22640
+ function gitChangedFiles(baseDir) {
22641
+ let out;
22642
+ try {
22643
+ out = execFileSync("git", ["status", "--porcelain"], {
22644
+ cwd: resolve15(baseDir),
22645
+ encoding: "utf-8",
22646
+ stdio: ["ignore", "pipe", "ignore"],
22647
+ timeout: 1e4
22648
+ });
22649
+ } catch {
22650
+ return [];
22651
+ }
22652
+ const files = [];
22653
+ for (const line of out.split(`
22654
+ `)) {
22655
+ const p = parsePorcelainLine(line);
22656
+ if (p)
22657
+ files.push(p);
22658
+ if (files.length >= MAX_GIT_FILES)
22659
+ break;
22660
+ }
22661
+ return files;
22662
+ }
22503
22663
  function commandOf(ev) {
22504
22664
  if (ev.tool !== "bash" || typeof ev.args?.command !== "string")
22505
22665
  return;
@@ -22523,7 +22683,7 @@ function readExcerpts(baseDir, paths) {
22523
22683
  }
22524
22684
  return out;
22525
22685
  }
22526
- var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200, MAX_ANSWER_CHARS = 2000;
22686
+ var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200, MAX_ANSWER_CHARS = 2000, MAX_GIT_FILES = 20;
22527
22687
  var init_evidence = __esm(() => {
22528
22688
  init_tool_output();
22529
22689
  init_context_log();
@@ -23660,7 +23820,8 @@ class Agent {
23660
23820
  costBreakdown: this.costTracker.breakdown(),
23661
23821
  cache: cacheStats,
23662
23822
  compactionCount: contextManager.getCompactionCount(),
23663
- contextQuality: contextManager.getQuality()
23823
+ contextQuality: contextManager.getQuality(),
23824
+ contextBreakdown: contextManager.getBreakdown?.()
23664
23825
  };
23665
23826
  }
23666
23827
  return {
@@ -23678,6 +23839,7 @@ class Agent {
23678
23839
  cache: cacheStats,
23679
23840
  compactionCount: contextManager.getCompactionCount(),
23680
23841
  contextQuality: contextManager.getQuality(),
23842
+ contextBreakdown: contextManager.getBreakdown?.(),
23681
23843
  llmDurationMs: state.totalLlmDuration
23682
23844
  };
23683
23845
  }
@@ -24290,6 +24452,28 @@ ${lines.join(`
24290
24452
  summary: this.compactedBlock ?? ""
24291
24453
  };
24292
24454
  }
24455
+ systemTokens() {
24456
+ const sys = this.messages.find((m) => m.role === "system");
24457
+ return sys ? this.estimateMessageTokensCached(sys) : 0;
24458
+ }
24459
+ getBreakdown() {
24460
+ const used = this.getEstimatedTokens();
24461
+ const system = this.systemTokens();
24462
+ const tools = this.toolTokens;
24463
+ const history = Math.max(0, used - system - tools);
24464
+ const b = this.budget;
24465
+ return {
24466
+ system,
24467
+ tools,
24468
+ history,
24469
+ used,
24470
+ systemBudget: b.systemPrompt,
24471
+ historyBudget: b.history,
24472
+ reserve: b.responseReserve,
24473
+ free: Math.max(0, this.contextWindow - used - b.responseReserve),
24474
+ window: this.contextWindow
24475
+ };
24476
+ }
24293
24477
  getSnapshot() {
24294
24478
  return {
24295
24479
  window: this.contextWindow,
@@ -24299,7 +24483,8 @@ ${lines.join(`
24299
24483
  messageCount: this.messages.length,
24300
24484
  quality: this.getQuality(),
24301
24485
  compactionCount: this.compactionCount,
24302
- iterationsSinceCompaction: this.iterationsSinceCompaction
24486
+ iterationsSinceCompaction: this.iterationsSinceCompaction,
24487
+ breakdown: this.getBreakdown()
24303
24488
  };
24304
24489
  }
24305
24490
  updateSystemPrompt(content) {
@@ -24504,7 +24689,33 @@ var init_factual = __esm(() => {
24504
24689
  if (!fp.includes("/") && !fp.includes("\\")) {
24505
24690
  return this.bareNameExists(fp);
24506
24691
  }
24507
- return false;
24692
+ return this.suffixExists(fp);
24693
+ }
24694
+ suffixExists(fp) {
24695
+ const norm = fp.replace(/\\/g, "/");
24696
+ const tail = `/${norm}`;
24697
+ const base = norm.split("/").pop() ?? norm;
24698
+ const fwd = (p) => p.replace(/\\/g, "/");
24699
+ for (const known of this.knownFiles) {
24700
+ if (fwd(known).endsWith(tail))
24701
+ return true;
24702
+ }
24703
+ const checkIndex = () => {
24704
+ for (const full of this.getNameIndex().get(base) ?? []) {
24705
+ if (fwd(full).endsWith(tail))
24706
+ return true;
24707
+ }
24708
+ return false;
24709
+ };
24710
+ if (checkIndex())
24711
+ return true;
24712
+ const now = Date.now();
24713
+ if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
24714
+ return false;
24715
+ this.lastRescan = now;
24716
+ this.nameCache = null;
24717
+ this.nameCacheTime = 0;
24718
+ return checkIndex();
24508
24719
  }
24509
24720
  dotfileVariants(fp) {
24510
24721
  const variants = [fp];
@@ -28751,6 +28962,7 @@ function createBrowserTool() {
28751
28962
  "Do NOT use it for search queries: search engines (Google, Yandex, Bing) block headless browsers with CAPTCHA/429/redirect pages — use the web_search tool for searching.",
28752
28963
  "Each snapshot returns: a numbered list of interactive elements, the visible page text (Content section), browser console messages, and network errors.",
28753
28964
  "Use the Content section to understand what the page says; use element numbers as targets for click/type.",
28965
+ "Without vision capabilities screenshots return only a text note — verify visuals through snapshot text, and for local web UI prefer DOM/API assertions (fetch /api/* endpoints, grep built assets) over driving the browser.",
28754
28966
  "Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms)."
28755
28967
  ].join(" "),
28756
28968
  parameters: {
@@ -32393,11 +32605,26 @@ var init_plan_tool = __esm(() => {
32393
32605
  });
32394
32606
 
32395
32607
  // src/modules/execution/module.ts
32396
- import { existsSync as existsSync51, readFileSync as readFileSync37 } from "fs";
32608
+ import { existsSync as existsSync51, readFileSync as readFileSync37, statSync as statSync11 } from "fs";
32397
32609
  import { resolve as resolve22 } from "path";
32398
32610
  function planPathToken(p) {
32399
32611
  return toForwardSlash(p).toLowerCase();
32400
32612
  }
32613
+ function provablyStale(resolved, sinceMs) {
32614
+ if (Number.isNaN(sinceMs))
32615
+ return false;
32616
+ for (const r of resolved) {
32617
+ if (!r)
32618
+ return false;
32619
+ try {
32620
+ if (statSync11(r).mtimeMs > sinceMs)
32621
+ return false;
32622
+ } catch {
32623
+ return false;
32624
+ }
32625
+ }
32626
+ return true;
32627
+ }
32401
32628
 
32402
32629
  class ExecutionModule {
32403
32630
  name = "execution";
@@ -32641,6 +32868,8 @@ class ExecutionModule {
32641
32868
  });
32642
32869
  if (empty)
32643
32870
  continue;
32871
+ if (provablyStale(resolved, Date.parse(plan.createdAt)))
32872
+ continue;
32644
32873
  }
32645
32874
  step.status = "done";
32646
32875
  step.note = `auto-closed by final audit: ${step.kind === "delete" ? "all named files removed" : "all named files exist with content"} (${tokens.join(", ")})`;
@@ -32868,6 +33097,11 @@ class ExecutionModule {
32868
33097
  const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
32869
33098
  if (!satisfied)
32870
33099
  return;
33100
+ if (step.kind !== "delete") {
33101
+ const plan = this.tracker?.getPlan();
33102
+ if (plan && provablyStale(resolved, Date.parse(plan.createdAt)))
33103
+ return;
33104
+ }
32871
33105
  if (step.kind !== "delete") {
32872
33106
  const emptyFiles = [];
32873
33107
  for (const r of resolved) {
@@ -44567,7 +44801,7 @@ var init_loader3 = __esm(() => {
44567
44801
  });
44568
44802
 
44569
44803
  // src/modules/certification/fact-checker.ts
44570
- import { existsSync as existsSync56, readFileSync as readFileSync40, statSync as statSync11 } from "fs";
44804
+ import { existsSync as existsSync56, readFileSync as readFileSync40, statSync as statSync12 } from "fs";
44571
44805
  import { join as join50 } from "path";
44572
44806
  function checkSandbox(sandboxDir, checks, exitCode, output) {
44573
44807
  const failures = [];
@@ -44613,14 +44847,14 @@ function runCheck2(sandboxDir, check, exitCode, output) {
44613
44847
  }
44614
44848
  function isFile(p) {
44615
44849
  try {
44616
- return existsSync56(p) && statSync11(p).isFile();
44850
+ return existsSync56(p) && statSync12(p).isFile();
44617
44851
  } catch {
44618
44852
  return false;
44619
44853
  }
44620
44854
  }
44621
44855
  function isDir(p) {
44622
44856
  try {
44623
- return existsSync56(p) && statSync11(p).isDirectory();
44857
+ return existsSync56(p) && statSync12(p).isDirectory();
44624
44858
  } catch {
44625
44859
  return false;
44626
44860
  }
@@ -49242,7 +49476,7 @@ import { homedir as homedir8 } from "os";
49242
49476
  // src/cli/completer.ts
49243
49477
  init_registry2();
49244
49478
  init_i18n();
49245
- import { readdirSync as readdirSync19, statSync as statSync12 } from "fs";
49479
+ import { readdirSync as readdirSync19, statSync as statSync13 } from "fs";
49246
49480
  import { join as join54 } from "path";
49247
49481
  function buildSlashCompletions(commands) {
49248
49482
  return Array.from(commands.entries()).map(([name, cmd]) => ({
@@ -49316,7 +49550,7 @@ class FilePathProvider {
49316
49550
  const matches = [];
49317
49551
  for (const entry of entries) {
49318
49552
  const entryPath = join54(fullPath, entry);
49319
- const stat2 = statSync12(entryPath);
49553
+ const stat2 = statSync13(entryPath);
49320
49554
  const completion = prefix + entry + (stat2.isDirectory() ? "/" : "");
49321
49555
  if (completion.startsWith(ctx.partial)) {
49322
49556
  matches.push(completion);
@@ -51320,7 +51554,7 @@ ${divider()}`);
51320
51554
  row(t("repl.model"), pc.white(this.config.model));
51321
51555
  const endpoint = activeProviderEndpoint(this.config.provider);
51322
51556
  row(t("repl.provider"), `${endpoint.type} → ${pc.dim(endpoint.baseUrl)}`);
51323
- row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
51557
+ row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(${t("repl.ctx_part_system")}:${sysBudget} ${t("repl.ctx_part_reserve")}:${resBudget} ${t("repl.ctx_part_history")}:${histBudget})`)}`);
51324
51558
  const si = this.agent.getSystemPromptInfo();
51325
51559
  row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
51326
51560
  if (this.skillsModule) {
@@ -51544,7 +51778,7 @@ init_output();
51544
51778
  init_i18n();
51545
51779
  init_colors();
51546
51780
  import { existsSync as existsSync63 } from "node:fs";
51547
- import { join as join60 } from "node:path";
51781
+ import { join as join61 } from "node:path";
51548
51782
 
51549
51783
  // src/modules/webui/module.ts
51550
51784
  init_config2();
@@ -51945,6 +52179,7 @@ function exportCmd(state, args, out) {
51945
52179
 
51946
52180
  // src/modules/webui/limits.ts
51947
52181
  var MAX_BODY_BYTES = 24 * 1024 * 1024;
52182
+ var MAX_QUEUE_LENGTH = 5;
51948
52183
 
51949
52184
  // src/modules/webui/state.ts
51950
52185
  init_env();
@@ -51964,6 +52199,8 @@ function createWebUiState() {
51964
52199
  busy: false,
51965
52200
  agentRunning: false,
51966
52201
  turnToken: null,
52202
+ turnSeq: 0,
52203
+ pendingQueue: [],
51967
52204
  lastPostTs: 0,
51968
52205
  minPostIntervalMs: 500,
51969
52206
  historyTail: 200,
@@ -52290,111 +52527,14 @@ function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
52290
52527
  s.busy = false;
52291
52528
  s.agentRunning = false;
52292
52529
  s.turnToken = null;
52293
- sink({ type: "turn_end", ts: Date.now(), ...summary });
52530
+ s.turnSeq += 1;
52531
+ sink({ type: "turn_end", ts: Date.now(), turnSeq: s.turnSeq, ...summary });
52532
+ opts.onQueueDrained?.();
52294
52533
  },
52295
52534
  flushNow: flush
52296
52535
  };
52297
52536
  }
52298
52537
 
52299
- // src/modules/webui/log-forward.ts
52300
- init_bus();
52301
- init_context_log();
52302
- var FORWARDED = ["info", "warn", "error"];
52303
- function installOutputForwarding(state, bus = defaultOutputBus) {
52304
- return bus.subscribe((ev) => {
52305
- if (!FORWARDED.includes(ev.level))
52306
- return;
52307
- broadcast({
52308
- type: "log",
52309
- ts: ev.ts,
52310
- level: ev.level,
52311
- source: ev.source,
52312
- text: redactContextSecrets(ev.text),
52313
- diagnostic: ev.data?.diagnostic === true
52314
- }, state);
52315
- });
52316
- }
52317
-
52318
- // src/modules/webui/prompt-io.ts
52319
- init_question();
52320
- init_approve();
52321
- var questionSeq = 0;
52322
- function nextQuestionId() {
52323
- questionSeq += 1;
52324
- return `q${Date.now().toString(36)}-${questionSeq}`;
52325
- }
52326
- function toIndexes(value) {
52327
- if (!Array.isArray(value))
52328
- return [];
52329
- const out = [];
52330
- for (const item of value) {
52331
- const n = typeof item === "number" ? item : Number(item);
52332
- if (Number.isInteger(n) && n >= -1)
52333
- out.push(n);
52334
- }
52335
- return out;
52336
- }
52337
- function toText(value) {
52338
- if (typeof value === "string")
52339
- return value;
52340
- if (value == null)
52341
- return "";
52342
- return String(value);
52343
- }
52344
- function ask2(state, payload, coerce) {
52345
- return new Promise((resolve27) => {
52346
- const id = nextQuestionId();
52347
- state.pendingQuestions.set(id, (value) => resolve27(coerce(value)));
52348
- broadcast({ type: "question", ts: Date.now(), id, ...payload }, state);
52349
- });
52350
- }
52351
- function createWebPromptIO(state) {
52352
- return {
52353
- askText(question) {
52354
- return ask2(state, { text: question, kind: "text" }, toText);
52355
- },
52356
- askChoice(question, options, opts = {}) {
52357
- return ask2(state, {
52358
- text: question,
52359
- kind: "choice",
52360
- options: options.map((o) => o.label ?? String(o)),
52361
- multiple: !!opts.multiple,
52362
- allowCustom: !!opts.allowCustom
52363
- }, toIndexes);
52364
- }
52365
- };
52366
- }
52367
- function resolveAnswer(state, id, value) {
52368
- const resolver = state.pendingQuestions.get(id);
52369
- if (!resolver)
52370
- return false;
52371
- state.pendingQuestions.delete(id);
52372
- resolver(value);
52373
- return true;
52374
- }
52375
- function cancelPendingQuestions(state) {
52376
- const pending = [...state.pendingQuestions.values()];
52377
- state.pendingQuestions.clear();
52378
- for (const resolve27 of pending)
52379
- resolve27(undefined);
52380
- }
52381
- function installWebPromptIO(deps) {
52382
- if (deps.registry) {
52383
- for (const tool of [questionTool, approveTool]) {
52384
- if (!deps.registry.has(tool.name))
52385
- deps.registry.register(tool);
52386
- }
52387
- }
52388
- if (deps.toolCtx)
52389
- deps.toolCtx.exitOnComplete = false;
52390
- const io = createWebPromptIO(deps.state);
52391
- deps.agent.setPromptIO(io);
52392
- return io;
52393
- }
52394
-
52395
- // src/modules/webui/server.ts
52396
- import { createServer } from "node:http";
52397
-
52398
52538
  // src/modules/webui/util.ts
52399
52539
  init_context_log();
52400
52540
  import { exec } from "node:child_process";
@@ -52535,6 +52675,32 @@ function readBodyError(req, res, err) {
52535
52675
  }
52536
52676
  sendJson(res, 400, { error: "bad request" });
52537
52677
  }
52678
+ function submitTurn(s, bridge, text, images, token = reserveBusy(s)) {
52679
+ broadcast({ type: "state", ts: Date.now(), busy: true, queueLen: s.pendingQueue.length }, s);
52680
+ const run = Promise.resolve(bridge.submit(text, { images }));
52681
+ run.catch((err) => {
52682
+ if (releaseBusy(s, token)) {
52683
+ broadcast({ type: "error", ts: Date.now(), error: scrubError(err) }, s);
52684
+ broadcast({ type: "state", ts: Date.now(), busy: false, queueLen: s.pendingQueue.length }, s);
52685
+ }
52686
+ });
52687
+ }
52688
+ function drainQueueTurn(s) {
52689
+ const bridge = s.bridge;
52690
+ if (!bridge || s.busy || bridge.isBusy())
52691
+ return false;
52692
+ const next = s.pendingQueue.shift();
52693
+ if (!next)
52694
+ return false;
52695
+ submitTurn(s, bridge, next.text, next.images);
52696
+ return true;
52697
+ }
52698
+ function enqueueMessage(s, text, images) {
52699
+ if (s.pendingQueue.length >= MAX_QUEUE_LENGTH)
52700
+ return null;
52701
+ s.pendingQueue.push({ text, images, ts: Date.now() });
52702
+ return s.pendingQueue.length;
52703
+ }
52538
52704
  function handleMessagePost(req, res, s) {
52539
52705
  if (s.setupRequired) {
52540
52706
  sendJson(res, 409, { error: "setup_required" });
@@ -52545,7 +52711,27 @@ function handleMessagePost(req, res, s) {
52545
52711
  return;
52546
52712
  }
52547
52713
  if (s.busy || s.bridge.isBusy()) {
52548
- sendJson(res, 409, { error: "busy" });
52714
+ if (s.minPostIntervalMs > 0 && Date.now() - s.lastPostTs < s.minPostIntervalMs) {
52715
+ sendJson(res, 429, { error: "rate_limited" });
52716
+ return;
52717
+ }
52718
+ readBody(req, s.maxBodyBytes).then((raw) => {
52719
+ const body = parseMessageBody(raw);
52720
+ if ("error" in body) {
52721
+ sendJson(res, 400, { error: body.error });
52722
+ return;
52723
+ }
52724
+ s.lastPostTs = Date.now();
52725
+ const pos = enqueueMessage(s, body.text, body.images);
52726
+ if (pos === null) {
52727
+ sendJson(res, 429, { error: "queue_full" });
52728
+ return;
52729
+ }
52730
+ broadcast({ type: "state", ts: Date.now(), busy: true, queueLen: s.pendingQueue.length }, s);
52731
+ sendJson(res, 202, { ok: true, queued: pos });
52732
+ }).catch((err) => {
52733
+ readBodyError(req, res, err);
52734
+ });
52549
52735
  return;
52550
52736
  }
52551
52737
  if (s.minPostIntervalMs > 0 && Date.now() - s.lastPostTs < s.minPostIntervalMs) {
@@ -52554,28 +52740,22 @@ function handleMessagePost(req, res, s) {
52554
52740
  }
52555
52741
  const bridge = s.bridge;
52556
52742
  const token = reserveBusy(s);
52557
- broadcast({ type: "state", ts: Date.now(), busy: true }, s);
52743
+ broadcast({ type: "state", ts: Date.now(), busy: true, queueLen: s.pendingQueue.length }, s);
52558
52744
  readBody(req, s.maxBodyBytes).then((raw) => {
52559
52745
  const body = parseMessageBody(raw);
52560
52746
  if ("error" in body) {
52561
52747
  if (releaseBusy(s, token)) {
52562
- broadcast({ type: "state", ts: Date.now(), busy: false }, s);
52748
+ broadcast({ type: "state", ts: Date.now(), busy: false, queueLen: s.pendingQueue.length }, s);
52563
52749
  }
52564
52750
  sendJson(res, 400, { error: body.error });
52565
52751
  return;
52566
52752
  }
52567
52753
  s.lastPostTs = Date.now();
52568
- const run = Promise.resolve(bridge.submit(body.text, { images: body.images }));
52569
- run.catch((err) => {
52570
- if (releaseBusy(s, token)) {
52571
- broadcast({ type: "error", ts: Date.now(), error: scrubError(err) }, s);
52572
- broadcast({ type: "state", ts: Date.now(), busy: false }, s);
52573
- }
52574
- });
52754
+ submitTurn(s, bridge, body.text, body.images, token);
52575
52755
  sendJson(res, 202, { ok: true });
52576
52756
  }).catch((err) => {
52577
52757
  if (releaseBusy(s, token)) {
52578
- broadcast({ type: "state", ts: Date.now(), busy: false }, s);
52758
+ broadcast({ type: "state", ts: Date.now(), busy: false, queueLen: s.pendingQueue.length }, s);
52579
52759
  }
52580
52760
  readBodyError(req, res, err);
52581
52761
  });
@@ -52591,6 +52771,105 @@ function tryOpenBrowser(url) {
52591
52771
  } catch {}
52592
52772
  }
52593
52773
 
52774
+ // src/modules/webui/log-forward.ts
52775
+ init_bus();
52776
+ init_context_log();
52777
+ var FORWARDED = ["info", "warn", "error"];
52778
+ function installOutputForwarding(state, bus = defaultOutputBus) {
52779
+ return bus.subscribe((ev) => {
52780
+ if (!FORWARDED.includes(ev.level))
52781
+ return;
52782
+ broadcast({
52783
+ type: "log",
52784
+ ts: ev.ts,
52785
+ level: ev.level,
52786
+ source: ev.source,
52787
+ text: redactContextSecrets(ev.text),
52788
+ diagnostic: ev.data?.diagnostic === true
52789
+ }, state);
52790
+ });
52791
+ }
52792
+
52793
+ // src/modules/webui/prompt-io.ts
52794
+ init_question();
52795
+ init_approve();
52796
+ var questionSeq = 0;
52797
+ function nextQuestionId() {
52798
+ questionSeq += 1;
52799
+ return `q${Date.now().toString(36)}-${questionSeq}`;
52800
+ }
52801
+ function toIndexes(value) {
52802
+ if (!Array.isArray(value))
52803
+ return [];
52804
+ const out = [];
52805
+ for (const item of value) {
52806
+ const n = typeof item === "number" ? item : Number(item);
52807
+ if (Number.isInteger(n) && n >= -1)
52808
+ out.push(n);
52809
+ }
52810
+ return out;
52811
+ }
52812
+ function toText(value) {
52813
+ if (typeof value === "string")
52814
+ return value;
52815
+ if (value == null)
52816
+ return "";
52817
+ return String(value);
52818
+ }
52819
+ function ask2(state, payload, coerce) {
52820
+ return new Promise((resolve27) => {
52821
+ const id = nextQuestionId();
52822
+ state.pendingQuestions.set(id, (value) => resolve27(coerce(value)));
52823
+ broadcast({ type: "question", ts: Date.now(), id, ...payload }, state);
52824
+ });
52825
+ }
52826
+ function createWebPromptIO(state) {
52827
+ return {
52828
+ askText(question) {
52829
+ return ask2(state, { text: question, kind: "text" }, toText);
52830
+ },
52831
+ askChoice(question, options, opts = {}) {
52832
+ return ask2(state, {
52833
+ text: question,
52834
+ kind: "choice",
52835
+ options: options.map((o) => o.label ?? String(o)),
52836
+ multiple: !!opts.multiple,
52837
+ allowCustom: !!opts.allowCustom
52838
+ }, toIndexes);
52839
+ }
52840
+ };
52841
+ }
52842
+ function resolveAnswer(state, id, value) {
52843
+ const resolver = state.pendingQuestions.get(id);
52844
+ if (!resolver)
52845
+ return false;
52846
+ state.pendingQuestions.delete(id);
52847
+ resolver(value);
52848
+ return true;
52849
+ }
52850
+ function cancelPendingQuestions(state) {
52851
+ const pending = [...state.pendingQuestions.values()];
52852
+ state.pendingQuestions.clear();
52853
+ for (const resolve27 of pending)
52854
+ resolve27(undefined);
52855
+ }
52856
+ function installWebPromptIO(deps) {
52857
+ if (deps.registry) {
52858
+ for (const tool of [questionTool, approveTool]) {
52859
+ if (!deps.registry.has(tool.name))
52860
+ deps.registry.register(tool);
52861
+ }
52862
+ }
52863
+ if (deps.toolCtx)
52864
+ deps.toolCtx.exitOnComplete = false;
52865
+ const io = createWebPromptIO(deps.state);
52866
+ deps.agent.setPromptIO(io);
52867
+ return io;
52868
+ }
52869
+
52870
+ // src/modules/webui/server.ts
52871
+ import { createServer } from "node:http";
52872
+
52594
52873
  // src/modules/webui/body.ts
52595
52874
  function parseJsonBody(raw) {
52596
52875
  let parsed;
@@ -52760,10 +53039,28 @@ function handleModelsGet(res, s) {
52760
53039
 
52761
53040
  // src/modules/webui/i18n-api.ts
52762
53041
  init_i18n();
53042
+
53043
+ // src/modules/webui/i18n-stale.ts
53044
+ import { statSync as statSync14 } from "fs";
53045
+ import { join as join60 } from "path";
53046
+ var serverStartedAt = Date.now();
53047
+ var dictFiles = ["en.json", "ru.json"].map((f) => join60(import.meta.dirname, "..", "..", "i18n", f));
53048
+ function isI18nStale() {
53049
+ for (const p of dictFiles) {
53050
+ try {
53051
+ if (statSync14(p).mtimeMs > serverStartedAt)
53052
+ return true;
53053
+ } catch {}
53054
+ }
53055
+ return false;
53056
+ }
53057
+
53058
+ // src/modules/webui/i18n-api.ts
52763
53059
  function handleI18nGet(res, s) {
52764
53060
  sendJson(res, 200, {
52765
53061
  locale: s.config?.locale ?? "en",
52766
- dicts: getFlatDicts()
53062
+ dicts: getFlatDicts(),
53063
+ i18nStale: isI18nStale()
52767
53064
  });
52768
53065
  }
52769
53066
 
@@ -52887,6 +53184,134 @@ function handleSessionHistory(res, id, s) {
52887
53184
  sendJson(res, 200, { id, history: normalizeHistory(log2, s.historyTail, messages) });
52888
53185
  }
52889
53186
 
53187
+ // src/modules/webui/log-api.ts
53188
+ init_context_log();
53189
+ var TRACE_LOG_TYPES = [
53190
+ "assistant",
53191
+ "llm_usage",
53192
+ "compaction",
53193
+ "context_probe",
53194
+ "audit",
53195
+ "moe_plan",
53196
+ "moe_subtask",
53197
+ "moe_replan",
53198
+ "moe_verify",
53199
+ "output",
53200
+ "critic_check",
53201
+ "critic_verdict",
53202
+ "critic_escalation",
53203
+ "claim_retry"
53204
+ ];
53205
+ var ALLOWED_TYPES = new Set([
53206
+ ...TRACE_LOG_TYPES,
53207
+ "tool_call",
53208
+ "tool_result",
53209
+ "user",
53210
+ "error",
53211
+ "context"
53212
+ ]);
53213
+ var DEFAULT_LIMIT2 = 100;
53214
+ var MAX_LIMIT = 500;
53215
+ function scrub2(text) {
53216
+ return text ? stripAnsi2(redactContextSecrets(text)) : undefined;
53217
+ }
53218
+ var STR_FIELDS = [
53219
+ "status",
53220
+ "kind",
53221
+ "provider",
53222
+ "model",
53223
+ "tool",
53224
+ "title",
53225
+ "subtaskId",
53226
+ "expertTag"
53227
+ ];
53228
+ var NUM_FIELDS = [
53229
+ "iteration",
53230
+ "promptTokens",
53231
+ "completionTokens",
53232
+ "totalTokens",
53233
+ "duration",
53234
+ "cycle"
53235
+ ];
53236
+ function copyStr(out, e, f) {
53237
+ const v = e[f];
53238
+ if (typeof v === "string")
53239
+ out[f] = v;
53240
+ }
53241
+ function copyNum(out, e, f) {
53242
+ const v = e[f];
53243
+ if (typeof v === "number")
53244
+ out[f] = v;
53245
+ }
53246
+ function pick(e) {
53247
+ const out = { ts: e.ts, type: e.type };
53248
+ for (const f of STR_FIELDS)
53249
+ copyStr(out, e, f);
53250
+ for (const f of NUM_FIELDS)
53251
+ copyNum(out, e, f);
53252
+ if (typeof e.success === "boolean")
53253
+ out.success = e.success;
53254
+ if (typeof e.tool_call_id === "string")
53255
+ out.toolCallId = e.tool_call_id;
53256
+ const text = scrub2(e.content ?? e.summary ?? e.message);
53257
+ if (text !== undefined)
53258
+ out.content = text;
53259
+ return out;
53260
+ }
53261
+ function parseQuery(url) {
53262
+ try {
53263
+ return new URL(url ?? "", "http://127.0.0.1").searchParams;
53264
+ } catch {
53265
+ return new URLSearchParams;
53266
+ }
53267
+ }
53268
+ function tailSessionLog(entries, opts = {}) {
53269
+ const types2 = opts.types && opts.types.length > 0 ? new Set(opts.types) : new Set(TRACE_LOG_TYPES);
53270
+ const since = opts.since ? Date.parse(opts.since) : NaN;
53271
+ const limit = Math.min(Math.max(opts.limit ?? DEFAULT_LIMIT2, 1), MAX_LIMIT);
53272
+ const out = [];
53273
+ for (const e of entries) {
53274
+ if (!ALLOWED_TYPES.has(e.type) || !types2.has(e.type))
53275
+ continue;
53276
+ if (!Number.isNaN(since)) {
53277
+ const ts = Date.parse(e.ts);
53278
+ if (Number.isNaN(ts) || ts <= since)
53279
+ continue;
53280
+ }
53281
+ out.push(pick(e));
53282
+ }
53283
+ return out.slice(-limit);
53284
+ }
53285
+ function handleSessionLog(req, res, id, s) {
53286
+ if (!s.sessionManager) {
53287
+ sendJson(res, 503, { error: "sessions_unavailable" });
53288
+ return;
53289
+ }
53290
+ const manager = s.sessionManager;
53291
+ if (!id || !manager.get(id)) {
53292
+ sendJson(res, 404, { error: "not_found" });
53293
+ return;
53294
+ }
53295
+ const q = parseQuery(req.url);
53296
+ const types2 = (q.get("types") ?? "").split(",").map((t2) => t2.trim()).filter((t2) => ALLOWED_TYPES.has(t2));
53297
+ const rawLimit = q.get("limit");
53298
+ const limit = rawLimit === null || rawLimit.trim() === "" ? undefined : Number(rawLimit);
53299
+ let log2 = [];
53300
+ try {
53301
+ log2 = manager.loadSessionLog?.(id) ?? [];
53302
+ } catch {
53303
+ log2 = [];
53304
+ }
53305
+ sendJson(res, 200, {
53306
+ id,
53307
+ log: tailSessionLog(log2, {
53308
+ types: types2,
53309
+ since: q.get("since") ?? undefined,
53310
+ limit: limit !== undefined && Number.isFinite(limit) ? limit : undefined
53311
+ })
53312
+ });
53313
+ }
53314
+
52890
53315
  // src/modules/webui/sessions-api.ts
52891
53316
  function handleSessionsGet(res, s) {
52892
53317
  if (!s.sessionManager) {
@@ -53016,6 +53441,11 @@ function handleSessionsApi(req, res, reqUrl, s) {
53016
53441
  handleSessionHistory(res, id, s);
53017
53442
  return true;
53018
53443
  }
53444
+ if (p.startsWith("/api/sessions/") && p.endsWith("/log") && req.method === "GET") {
53445
+ const id = decodeURIComponent(p.slice("/api/sessions/".length, -"/log".length));
53446
+ handleSessionLog(req, res, id, s);
53447
+ return true;
53448
+ }
53019
53449
  if (p.startsWith("/api/sessions/") && req.method === "DELETE") {
53020
53450
  const id = decodeURIComponent(p.slice("/api/sessions/".length));
53021
53451
  handleSessionDelete(res, id, s);
@@ -54350,7 +54780,7 @@ function handleEvents(req, res, s) {
54350
54780
  const greeted = res.write(`retry: 1200
54351
54781
 
54352
54782
  `) && res.write(`event: state
54353
- data: ${JSON.stringify({ busy: s.busy })}
54783
+ data: ${JSON.stringify({ busy: s.busy, queueLen: s.pendingQueue.length })}
54354
54784
 
54355
54785
  `);
54356
54786
  if (!greeted) {
@@ -54383,7 +54813,11 @@ function dropClient(res, s) {
54383
54813
  }
54384
54814
  var API_ROUTES = {
54385
54815
  "GET /api/state": (_req, res, s) => {
54386
- sendJson(res, 200, { busy: s.busy, state: s.bridge?.getState?.() ?? null });
54816
+ sendJson(res, 200, {
54817
+ busy: s.busy,
54818
+ queueLen: s.pendingQueue.length,
54819
+ state: s.bridge?.getState?.() ?? null
54820
+ });
54387
54821
  },
54388
54822
  "POST /api/message": (req, res, s) => handleMessagePost(req, res, s),
54389
54823
  "GET /api/config": (_req, res, s) => handleConfigGet(res, s),
@@ -54545,6 +54979,7 @@ function stopServer(s = getDefaultState()) {
54545
54979
  s.busy = false;
54546
54980
  s.agentRunning = false;
54547
54981
  s.turnToken = null;
54982
+ s.pendingQueue.length = 0;
54548
54983
  s.lastPostTs = 0;
54549
54984
  }
54550
54985
 
@@ -54584,6 +55019,8 @@ function captureUsage(result, fallbackLimit) {
54584
55019
  }
54585
55020
  if (typeof result.contextQuality === "number")
54586
55021
  usage.contextQuality = result.contextQuality;
55022
+ if (result.contextBreakdown)
55023
+ usage.contextBreakdown = result.contextBreakdown;
54587
55024
  return usage;
54588
55025
  }
54589
55026
  function planEventFor(toolName, execModule) {
@@ -54606,10 +55043,12 @@ function buildHostState(deps, busy) {
54606
55043
  compactionCount: deps.usage?.compactionCount,
54607
55044
  cacheHitRate: deps.usage?.cacheHitRate,
54608
55045
  contextQuality: deps.usage?.contextQuality,
55046
+ contextBreakdown: deps.usage?.contextBreakdown,
54609
55047
  contextProbe: deps.contextProbe,
54610
55048
  workspace: deps.workspace,
54611
55049
  instructions: deps.instructions,
54612
55050
  projectMap: deps.projectMap ?? undefined,
55051
+ i18nStale: isI18nStale(),
54613
55052
  plan: planSnapshotFromModule(adaptActivePlan(deps.execModule?.getActivePlan()))
54614
55053
  };
54615
55054
  }
@@ -54662,7 +55101,7 @@ function makeRunAgent(deps) {
54662
55101
  toolCalls,
54663
55102
  interrupted: interrupted || undefined
54664
55103
  });
54665
- broadcast({ type: "state", ts: Date.now(), busy: state.busy, usage: state.usage }, state);
55104
+ broadcast({ type: "state", ts: Date.now(), busy: state.busy, queueLen: state.pendingQueue.length, usage: state.usage }, state);
54666
55105
  return result;
54667
55106
  } catch (err) {
54668
55107
  forwarder.onTurnEnd({ success: false, interrupted: interrupted || undefined });
@@ -54687,7 +55126,7 @@ function makeReconfigure(state, agent, after) {
54687
55126
  await after();
54688
55127
  } catch {}
54689
55128
  }
54690
- broadcast({ type: "state", ts: Date.now(), busy: state.busy, usage: state.usage }, state);
55129
+ broadcast({ type: "state", ts: Date.now(), busy: state.busy, queueLen: state.pendingQueue.length, usage: state.usage }, state);
54691
55130
  };
54692
55131
  }
54693
55132
  async function startWebUi(deps) {
@@ -54711,8 +55150,16 @@ async function startWebUi(deps) {
54711
55150
  if (deps.indexer.refresh) {
54712
55151
  const refresh = deps.indexer.refresh.bind(deps.indexer);
54713
55152
  state.refreshIndex = async () => {
54714
- await refresh();
54715
- broadcast({ type: "state", ts: Date.now(), busy: state.busy, usage: state.usage }, state);
55153
+ broadcast({ type: "index", ts: Date.now(), phase: "start" }, state);
55154
+ try {
55155
+ await refresh();
55156
+ } catch {
55157
+ broadcast({ type: "index", ts: Date.now(), phase: "error" }, state);
55158
+ throw new Error("index refresh failed");
55159
+ }
55160
+ const stats = state.projectMapStats?.() ?? null;
55161
+ broadcast({ type: "index", ts: Date.now(), phase: "done", stats }, state);
55162
+ broadcast({ type: "state", ts: Date.now(), busy: state.busy, queueLen: state.pendingQueue.length, usage: state.usage }, state);
54716
55163
  };
54717
55164
  }
54718
55165
  }
@@ -54728,7 +55175,9 @@ async function startWebUi(deps) {
54728
55175
  if (modelCache)
54729
55176
  modelCache.refresh();
54730
55177
  state.reconfigure = makeReconfigure(state, deps.agent, modelCache ? () => modelCache.refresh() : undefined);
54731
- const forwarder = makeForwarder(state);
55178
+ const forwarder = makeForwarder(state, undefined, {
55179
+ onQueueDrained: () => drainQueueTurn(state)
55180
+ });
54732
55181
  const stopOutputForwarding = installOutputForwarding(state);
54733
55182
  let probe;
54734
55183
  if (deps.contextProbe) {
@@ -54811,7 +55260,7 @@ async function runWeb(opts) {
54811
55260
  process.env[MMA_ENV.WEBUI_OPEN] = "0";
54812
55261
  const mmaDir = globalMmaDir();
54813
55262
  const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
54814
- const virgin = !existsSync63(join60(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
55263
+ const virgin = !existsSync63(join61(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
54815
55264
  const {
54816
55265
  agent,
54817
55266
  config,
@@ -54933,7 +55382,7 @@ init_config2();
54933
55382
  init_i18n();
54934
55383
  init_colors();
54935
55384
  import { existsSync as existsSync64, readFileSync as readFileSync45 } from "fs";
54936
- import { join as join62, dirname as dirname30 } from "path";
55385
+ import { join as join63, dirname as dirname30 } from "path";
54937
55386
  import { fileURLToPath as fileURLToPath7 } from "url";
54938
55387
 
54939
55388
  // src/modules/updater/index.ts
@@ -55055,7 +55504,7 @@ init_i18n();
55055
55504
  init_utils();
55056
55505
  init_paths();
55057
55506
  import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync30 } from "fs";
55058
- import { join as join61 } from "path";
55507
+ import { join as join62 } from "path";
55059
55508
  import { homedir as homedir9 } from "os";
55060
55509
  var CRASH_LOG_DIR = globalLogsDir();
55061
55510
  var CRASH_LOG_FILE = "crash.jsonl";
@@ -55073,7 +55522,7 @@ function formatCrashEntry(type2, err) {
55073
55522
  function writeCrashEntry(dir, entry) {
55074
55523
  try {
55075
55524
  mkdirSync30(dir, { recursive: true });
55076
- appendFileSync8(join61(dir, CRASH_LOG_FILE), `${JSON.stringify(entry)}
55525
+ appendFileSync8(join62(dir, CRASH_LOG_FILE), `${JSON.stringify(entry)}
55077
55526
  `, "utf-8");
55078
55527
  } catch {}
55079
55528
  }
@@ -55144,7 +55593,7 @@ function resolveEarlyLocale() {
55144
55593
  if (env3)
55145
55594
  return env3;
55146
55595
  const dir = globalMmaDir();
55147
- for (const p of [join62(dir, "config", "core.json"), join62(dir, "config.json")]) {
55596
+ for (const p of [join63(dir, "config", "core.json"), join63(dir, "config.json")]) {
55148
55597
  try {
55149
55598
  const parsed = JSON.parse(readFileSync45(p, "utf-8"));
55150
55599
  if (typeof parsed.locale === "string")
@@ -55244,7 +55693,7 @@ async function main() {
55244
55693
  const prompt = program2.args.join(" ");
55245
55694
  const mmaDir = globalMmaDir();
55246
55695
  const { hasDomainFiles: hasDomainFiles2 } = await Promise.resolve().then(() => (init_domains(), exports_domains));
55247
- const virgin = !existsSync64(join62(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
55696
+ const virgin = !existsSync64(join63(mmaDir, "config.json")) && !hasDomainFiles2(mmaDir);
55248
55697
  const { agent, config, baseDir, legacyDetected, contextProbe } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete, reasoningLevel, contextLog);
55249
55698
  if (verboseOpt)
55250
55699
  config.ui.verbosity = "verbose";
@@ -55335,7 +55784,7 @@ async function main() {
55335
55784
  if (onboardingStart === "init")
55336
55785
  process.env[MMA_ENV.START_INIT] = "1";
55337
55786
  const mmaDir = globalMmaDir();
55338
- const legacyConfigPath = join62(mmaDir, "config.json");
55787
+ const legacyConfigPath = join63(mmaDir, "config.json");
55339
55788
  let hasAnyConfig = existsSync64(legacyConfigPath);
55340
55789
  if (!hasAnyConfig) {
55341
55790
  try {
@@ -55343,7 +55792,7 @@ async function main() {
55343
55792
  hasAnyConfig = hasDomainFiles2(mmaDir);
55344
55793
  } catch {}
55345
55794
  }
55346
- const projectConfigPath = projectDir ? join62(projectDir, ".mmrc") : join62(process.cwd(), ".mmrc");
55795
+ const projectConfigPath = projectDir ? join63(projectDir, ".mmrc") : join63(process.cwd(), ".mmrc");
55347
55796
  if (!hasAnyConfig && !exitOnComplete && readEnv("POST_SETUP") !== "1") {
55348
55797
  getDefaultChannel().writeLine(`
55349
55798
  ${t("cli.first_run")}