micro-models-agent 2.12.0 → 2.14.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
@@ -2609,6 +2609,8 @@ The path was joined onto the working directory because it does not exist as give
2609
2609
  {issues}
2610
2610
  Summary: {summary}`,
2611
2611
  "critic.advice": `A verifier flagged issues worth checking before you finish:
2612
+ {issues}`,
2613
+ "critic.repeat": `The verifier repeats a previously flagged problem — do not produce the same answer again. Change the approach, or finish honestly if the complaint does not apply:
2612
2614
  {issues}`,
2613
2615
  "tool.subagent_queued": "Sub-agent task queued: {task}",
2614
2616
  "tool.subagent_artifact": `Sub-agent completed. Full result saved to artifact: {path}
@@ -3404,6 +3406,7 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
3404
3406
  "ui.todo_action": "{action} · {count}",
3405
3407
  "ui.tool_running": "{tool}…",
3406
3408
  "ui.step_context": "step {id}: {desc}",
3409
+ "ui.draft_retracted": "↳ draft rejected by verification, refining…",
3407
3410
  "indexer.map_header": "Project map",
3408
3411
  "indexer.top_directories": "Top directories",
3409
3412
  "indexer.files": "Files",
@@ -3578,6 +3581,46 @@ Updated: {updatedAt}`,
3578
3581
  "web.sidebar.session": "session",
3579
3582
  "web.sidebar.usage": "usage",
3580
3583
  "web.sidebar.env": "environment",
3584
+ "web.sidebar.tab_session": "Session",
3585
+ "web.sidebar.tab_map": "Map",
3586
+ "web.sidebar.tab_trace": "Trace",
3587
+ "web.sidebar.map_status": "status",
3588
+ "web.sidebar.map_ready": "ready",
3589
+ "web.sidebar.map_indexing": "indexing…",
3590
+ "web.sidebar.map_stale": "stale",
3591
+ "web.sidebar.map_updated": "updated",
3592
+ "web.sidebar.map_never": "never",
3593
+ "web.sidebar.map_today": "today, {time}",
3594
+ "web.sidebar.map_watcher": "watcher",
3595
+ "web.sidebar.map_on": "on",
3596
+ "web.sidebar.map_off": "off",
3597
+ "web.sidebar.map_files": "files",
3598
+ "web.sidebar.map_symbols": "symbols",
3599
+ "web.sidebar.map_tokens": "tokens",
3600
+ "web.sidebar.map_root": "root",
3601
+ "web.sidebar.map_languages": "languages",
3602
+ "web.sidebar.map_reindex": "Re-index",
3603
+ "web.sidebar.map_reindexing": "re-indexing…",
3604
+ "web.sidebar.map_reindex_failed": "re-index failed",
3605
+ "web.sidebar.trace_empty": "no events yet",
3606
+ "web.sidebar.trace_turn": "turn {n}",
3607
+ "web.sidebar.trace_text": "composing… {count} chars",
3608
+ "web.sidebar.trace_plan": "plan update",
3609
+ "web.sidebar.trace_question": "question",
3610
+ "web.sidebar.detail_metadata": "metadata",
3611
+ "web.sidebar.detail_arguments": "arguments",
3612
+ "web.sidebar.detail_output": "output",
3613
+ "web.sidebar.detail_error": "error",
3614
+ "web.sidebar.detail_text": "text",
3615
+ "web.sidebar.detail_plan": "plan",
3616
+ "web.sidebar.detail_reasoning": "reasoning",
3617
+ "web.sidebar.detail_status": "status",
3618
+ "web.sidebar.detail_ok": "ok",
3619
+ "web.sidebar.detail_fail": "failed",
3620
+ "web.sidebar.detail_duration": "duration",
3621
+ "web.sidebar.detail_time": "time",
3622
+ "web.sidebar.detail_tool_call": "tool call",
3623
+ "web.sidebar.detail_session": "session",
3581
3624
  "web.env.dir": "dir",
3582
3625
  "web.env.agents": "AGENTS.md",
3583
3626
  "web.env.map": "map",
@@ -3972,6 +4015,7 @@ Updated: {updatedAt}`,
3972
4015
  "web.history.loading": "loading…",
3973
4016
  "web.history.unavailable": "— history unavailable",
3974
4017
  "web.history.empty": "— empty session —",
4018
+ "web.scroll.latest": "latest",
3975
4019
  "web.tool.output_one": "output · {count} char",
3976
4020
  "web.tool.output_few": "output · {count} chars",
3977
4021
  "web.tool.output_many": "output · {count} chars",
@@ -3981,13 +4025,12 @@ Updated: {updatedAt}`,
3981
4025
  "web.code.copy_failed": "copy failed",
3982
4026
  "web.msg.copy": "copy",
3983
4027
  "web.msg.copy_md": "copy as markdown",
3984
- "web.card.open": "open ↗",
3985
- "web.card.download": "download ⤓",
3986
- "web.card.copy_path": "copy path",
3987
4028
  "web.card.open_title": "Preview in a browser tab",
3988
- "web.card.download_title": "Download the file",
3989
4029
  "web.card.copy_title": "Copy the workspace path",
3990
4030
  "web.card.drawio_hint": "Draw.io diagram is XML — preview shows code; open it in draw.io / VS Code to see the scheme.",
4031
+ "web.card.files_one": "{count} file",
4032
+ "web.card.files_few": "{count} files",
4033
+ "web.card.files_many": "{count} files",
3991
4034
  "web.card.screenshot": "screenshot",
3992
4035
  "web.card.attached_image": "attached image",
3993
4036
  "web.card.browser_viewport": "browser · viewport",
@@ -4015,7 +4058,7 @@ Updated: {updatedAt}`,
4015
4058
  "web.setup.language.title": "Choose your language",
4016
4059
  "web.setup.language.hint": "Used for the interface.",
4017
4060
  "web.setup.provider.title": "Choose a provider",
4018
- "web.setup.provider.hint": "For a local model pick the OpenAI-compatible option and scan for your server.",
4061
+ "web.setup.provider.hint": "For a local model pick the OpenAI-compatible option, scan for your server, then test the connection before continuing.",
4019
4062
  "web.setup.provider.base_url": "Base URL",
4020
4063
  "web.setup.provider.scan": "Scan for local servers",
4021
4064
  "web.setup.provider.scan_found_one": "Found {count} server",
@@ -4024,6 +4067,10 @@ Updated: {updatedAt}`,
4024
4067
  "web.setup.provider.scan_none": "No local servers found — enter the URL manually.",
4025
4068
  "web.setup.provider.scan_failed": "Scan failed. Enter the URL manually.",
4026
4069
  "web.setup.provider.required": "Please enter a base URL.",
4070
+ "web.setup.provider.test": "Test connection",
4071
+ "web.setup.provider.testing": "Testing…",
4072
+ "web.setup.provider.test_ok": "Server reachable.",
4073
+ "web.setup.provider.test_fail": "Could not reach the server at this URL.",
4027
4074
  "web.setup.key.title": "API key",
4028
4075
  "web.setup.key.hint": "Leave empty for local servers. Stored only in your config file and never shown again.",
4029
4076
  "web.setup.key.label": "Key",
@@ -4042,15 +4089,54 @@ Updated: {updatedAt}`,
4042
4089
  "web.setup.model.test_fail": "Could not reach the model.",
4043
4090
  "web.setup.model.required": "Please choose or type a model.",
4044
4091
  "web.setup.context.title": "Context & iterations",
4045
- "web.setup.context.hint": "How much context the model gets and how many tool steps it may take.",
4092
+ "web.setup.context.hint": "Must match the context your provider gives this model. Press Detect, or copy the value from LM Studio / Ollama / provider docs.",
4046
4093
  "web.setup.context.contextWindow": "Context window (tokens)",
4047
4094
  "web.setup.context.maxToolIterations": "Max tool iterations",
4048
4095
  "web.setup.context.required": "Enter a positive number.",
4096
+ "web.setup.context.detect": "Detect from provider",
4097
+ "web.setup.context.detecting": "Detecting…",
4098
+ "web.setup.context.detect_ok": "Context set from provider: {count} tokens.",
4099
+ "web.setup.context.detect_fail": "Could not detect context — enter it manually.",
4049
4100
  "web.setup.security.title": "Security",
4050
- "web.setup.security.hint": "Safe defaults are on; turn off only what you need.",
4101
+ "web.setup.security.hint": "Safe defaults are on; turn off only what you need. Nothing is blocked silently — each toggle only asks the agent to confirm first.",
4051
4102
  "web.setup.security.paths": "Restrict file access to the workspace",
4103
+ "web.setup.security.paths_desc": "The agent reads and writes only inside the workspace, and asks before touching .git, .env, keys and other sensitive paths.",
4052
4104
  "web.setup.security.bash": "Block dangerous shell commands",
4105
+ "web.setup.security.bash_desc": "Blocks destructive commands (rm, sudo, ssh, curl, shutdown, …). The agent asks instead of running them.",
4053
4106
  "web.setup.security.flags": "Block dangerous command flags",
4107
+ "web.setup.security.flags_desc": "Blocks risky flags (--force, -rf, …) that make safe commands dangerous. Works together with the command block.",
4108
+ "web.setup.security.configure": "Configure",
4109
+ "web.setup.security.advanced": "Advanced security settings",
4110
+ "web.setup.security.popup_hint": "One item per line. Empty list = disabled. Changes apply only to this install; you can edit them later in Settings → Security.",
4111
+ "web.setup.security.popup_done": "Done",
4112
+ "web.setup.security.reset": "Reset to defaults",
4113
+ "web.setup.security.denied": "Always denied paths",
4114
+ "web.setup.security.allowed": "Only allowed paths (empty = all except denied)",
4115
+ "web.setup.security.blacklist": "Always blocked commands",
4116
+ "web.setup.security.whitelist": "Only allowed commands (empty = all except blocked)",
4117
+ "web.setup.security.log_commands": "Log every executed command to the audit log",
4118
+ "web.setup.security.dangerous_flags": "Blocked command flags",
4119
+ "web.setup.security.dangerous_operators": "Blocked shell operators",
4120
+ "web.setup.security.network_enabled": "Network protection (block private/internal addresses)",
4121
+ "web.setup.security.denied_domains": "Always denied domains",
4122
+ "web.setup.security.allowed_domains": "Only allowed domains (empty = all except denied)",
4123
+ "web.setup.security.request_timeout": "Network request timeout (ms)",
4124
+ "web.setup.security.max_response_size": "Max response size (bytes)",
4125
+ "web.setup.security.max_recursion": "Max subagent recursion depth",
4126
+ "web.setup.security.max_file_ops": "Max file operations per session",
4127
+ "web.setup.security.rate_rpm": "Max LLM requests per minute",
4128
+ "web.setup.security.rate_parallel": "Max parallel tasks",
4129
+ "web.setup.security.content_scan": "Scan file content for dangerous patterns",
4130
+ "web.setup.security.session_encryption": "Encrypt session logs on disk",
4131
+ "web.setup.security.audit_enabled": "Send security events to the audit notifier",
4132
+ "web.setup.security.audit_severity": "Minimum severity to notify",
4133
+ "web.setup.security.audit_events": "Notified event types",
4134
+ "web.setup.security.audit_retries": "Webhook max retries",
4135
+ "web.setup.security.audit_timeout": "Webhook timeout (ms)",
4136
+ "web.setup.security.severity.low": "low",
4137
+ "web.setup.security.severity.medium": "medium",
4138
+ "web.setup.security.severity.high": "high",
4139
+ "web.setup.security.severity.critical": "critical",
4054
4140
  "web.setup.review.title": "Review",
4055
4141
  "web.setup.review.hint": "Check the values, then finish. Settings are saved to your config.",
4056
4142
  "web.setup.review.provider": "Provider",
@@ -4423,6 +4509,8 @@ var init_ru = __esm(() => {
4423
4509
  {issues}
4424
4510
  Сводка: {summary}`,
4425
4511
  "critic.advice": `Верификатор отметил проблемы, которые стоит проверить до завершения:
4512
+ {issues}`,
4513
+ "critic.repeat": `Верификатор повторяет уже отмеченную проблему — не выдавай тот же ответ снова. Смени подход или заверши честно, если жалоба неприменима:
4426
4514
  {issues}`,
4427
4515
  "tool.subagent_queued": "Задача подагенту добавлена: {task}",
4428
4516
  "tool.subagent_artifact": `Суб-агент завершён. Полный результат сохранён в артефакт: {path}
@@ -5221,6 +5309,7 @@ var init_ru = __esm(() => {
5221
5309
  "ui.todo_action": "{action} · {count}",
5222
5310
  "ui.tool_running": "{tool}…",
5223
5311
  "ui.step_context": "шаг {id}: {desc}",
5312
+ "ui.draft_retracted": "↳ черновик отклонён проверкой, уточняю…",
5224
5313
  "indexer.map_header": "Карта проекта",
5225
5314
  "indexer.top_directories": "Основные директории",
5226
5315
  "indexer.files": "Файлы",
@@ -5398,6 +5487,46 @@ var init_ru = __esm(() => {
5398
5487
  "web.sidebar.session": "сессия",
5399
5488
  "web.sidebar.usage": "использование",
5400
5489
  "web.sidebar.env": "окружение",
5490
+ "web.sidebar.tab_session": "Сессия",
5491
+ "web.sidebar.tab_map": "Карта",
5492
+ "web.sidebar.tab_trace": "Трассировка",
5493
+ "web.sidebar.map_status": "статус",
5494
+ "web.sidebar.map_ready": "готово",
5495
+ "web.sidebar.map_indexing": "индексация…",
5496
+ "web.sidebar.map_stale": "устарело",
5497
+ "web.sidebar.map_updated": "обновлено",
5498
+ "web.sidebar.map_never": "никогда",
5499
+ "web.sidebar.map_today": "сегодня, {time}",
5500
+ "web.sidebar.map_watcher": "вотчер",
5501
+ "web.sidebar.map_on": "вкл.",
5502
+ "web.sidebar.map_off": "выкл.",
5503
+ "web.sidebar.map_files": "файлы",
5504
+ "web.sidebar.map_symbols": "символы",
5505
+ "web.sidebar.map_tokens": "токены",
5506
+ "web.sidebar.map_root": "корень",
5507
+ "web.sidebar.map_languages": "языки",
5508
+ "web.sidebar.map_reindex": "Переиндексировать",
5509
+ "web.sidebar.map_reindexing": "переиндексация…",
5510
+ "web.sidebar.map_reindex_failed": "не удалось переиндексировать",
5511
+ "web.sidebar.trace_empty": "событий пока нет",
5512
+ "web.sidebar.trace_turn": "ход {n}",
5513
+ "web.sidebar.trace_text": "печатает… {count} симв.",
5514
+ "web.sidebar.trace_plan": "обновление плана",
5515
+ "web.sidebar.trace_question": "вопрос",
5516
+ "web.sidebar.detail_metadata": "метаданные",
5517
+ "web.sidebar.detail_arguments": "аргументы",
5518
+ "web.sidebar.detail_output": "вывод",
5519
+ "web.sidebar.detail_error": "ошибка",
5520
+ "web.sidebar.detail_text": "текст",
5521
+ "web.sidebar.detail_plan": "план",
5522
+ "web.sidebar.detail_reasoning": "размышление",
5523
+ "web.sidebar.detail_status": "статус",
5524
+ "web.sidebar.detail_ok": "ok",
5525
+ "web.sidebar.detail_fail": "ошибка",
5526
+ "web.sidebar.detail_duration": "длительность",
5527
+ "web.sidebar.detail_time": "время",
5528
+ "web.sidebar.detail_tool_call": "вызов тула",
5529
+ "web.sidebar.detail_session": "сессия",
5401
5530
  "web.env.dir": "директория",
5402
5531
  "web.env.agents": "AGENTS.md",
5403
5532
  "web.env.map": "карта",
@@ -5765,6 +5894,7 @@ var init_ru = __esm(() => {
5765
5894
  "web.history.loading": "загрузка…",
5766
5895
  "web.history.unavailable": "— история недоступна",
5767
5896
  "web.history.empty": "— пустая сессия —",
5897
+ "web.scroll.latest": "к новому",
5768
5898
  "web.tool.output_one": "вывод · {count} символ",
5769
5899
  "web.tool.output_few": "вывод · {count} символа",
5770
5900
  "web.tool.output_many": "вывод · {count} символов",
@@ -5774,13 +5904,12 @@ var init_ru = __esm(() => {
5774
5904
  "web.code.copy_failed": "не скопировано",
5775
5905
  "web.msg.copy": "копировать",
5776
5906
  "web.msg.copy_md": "копировать как markdown",
5777
- "web.card.open": "открыть ↗",
5778
- "web.card.download": "скачать ⤓",
5779
- "web.card.copy_path": "путь",
5780
5907
  "web.card.open_title": "Предпросмотр во вкладке браузера",
5781
- "web.card.download_title": "Скачать файл",
5782
5908
  "web.card.copy_title": "Скопировать путь в рабочей папке",
5783
5909
  "web.card.drawio_hint": "Диаграмма draw.io — это XML: предпросмотр покажет код, схему смотри в draw.io / VS Code.",
5910
+ "web.card.files_one": "{count} файл",
5911
+ "web.card.files_few": "{count} файла",
5912
+ "web.card.files_many": "{count} файлов",
5784
5913
  "web.card.screenshot": "снимок экрана",
5785
5914
  "web.card.attached_image": "прикреплённое изображение",
5786
5915
  "web.card.browser_viewport": "браузер · область просмотра",
@@ -5835,7 +5964,7 @@ var init_ru = __esm(() => {
5835
5964
  "web.setup.language.title": "Выберите язык",
5836
5965
  "web.setup.language.hint": "Используется для интерфейса.",
5837
5966
  "web.setup.provider.title": "Выберите провайдера",
5838
- "web.setup.provider.hint": "Для локальной модели выберите OpenAI-совместимый вариант и просканируйте сервер.",
5967
+ "web.setup.provider.hint": "Для локальной модели выберите OpenAI-совместимый вариант, просканируйте сервер и проверьте подключение перед продолжением.",
5839
5968
  "web.setup.provider.base_url": "Базовый URL",
5840
5969
  "web.setup.provider.scan": "Найти локальные серверы",
5841
5970
  "web.setup.provider.scan_found_one": "Найден {count} сервер",
@@ -5844,6 +5973,10 @@ var init_ru = __esm(() => {
5844
5973
  "web.setup.provider.scan_none": "Локальные серверы не найдены — введите URL вручную.",
5845
5974
  "web.setup.provider.scan_failed": "Сканирование не удалось. Введите URL вручную.",
5846
5975
  "web.setup.provider.required": "Укажите базовый URL.",
5976
+ "web.setup.provider.test": "Проверить подключение",
5977
+ "web.setup.provider.testing": "Проверка…",
5978
+ "web.setup.provider.test_ok": "Сервер отвечает.",
5979
+ "web.setup.provider.test_fail": "Не удалось связаться с сервером по этому URL.",
5847
5980
  "web.setup.key.title": "API-ключ",
5848
5981
  "web.setup.key.hint": "Для локальных серверов можно оставить пустым. Хранится только в файле конфигурации и больше не показывается.",
5849
5982
  "web.setup.key.label": "Ключ",
@@ -5862,15 +5995,54 @@ var init_ru = __esm(() => {
5862
5995
  "web.setup.model.test_fail": "Не удалось связаться с моделью.",
5863
5996
  "web.setup.model.required": "Выберите или введите модель.",
5864
5997
  "web.setup.context.title": "Контекст и итерации",
5865
- "web.setup.context.hint": "Сколько контекста получает модель и сколько шагов с тулами она может сделать.",
5998
+ "web.setup.context.hint": "Должно совпадать с контекстом, который провайдер выделяет этой модели. Нажмите «Определить» либо сверьтесь с LM Studio / Ollama / документацией провайдера.",
5866
5999
  "web.setup.context.contextWindow": "Окно контекста (токены)",
5867
6000
  "web.setup.context.maxToolIterations": "Максимум итераций тулов",
5868
6001
  "web.setup.context.required": "Введите положительное число.",
6002
+ "web.setup.context.detect": "Определить у провайдера",
6003
+ "web.setup.context.detecting": "Определение…",
6004
+ "web.setup.context.detect_ok": "Контекст от провайдера: {count} токенов.",
6005
+ "web.setup.context.detect_fail": "Не удалось определить контекст — введите вручную.",
5869
6006
  "web.setup.security.title": "Безопасность",
5870
- "web.setup.security.hint": "Безопасные значения включены; отключайте только нужное.",
6007
+ "web.setup.security.hint": "Безопасные значения включены; отключайте только нужное. Ничего не блокируется молча — каждый переключатель лишь требует подтверждения перед опасным действием.",
5871
6008
  "web.setup.security.paths": "Ограничить доступ к файлам рабочей папкой",
6009
+ "web.setup.security.paths_desc": "Агент читает и пишет только внутри рабочей папки, а перед доступом к .git, .env, ключам и другим чувствительным путям спросит подтверждения.",
5872
6010
  "web.setup.security.bash": "Блокировать опасные shell-команды",
6011
+ "web.setup.security.bash_desc": "Блокирует разрушительные команды (rm, sudo, ssh, curl, shutdown, …). Вместо запуска агент спросит подтверждения.",
5873
6012
  "web.setup.security.flags": "Блокировать опасные флаги команд",
6013
+ "web.setup.security.flags_desc": "Блокирует рискованные флаги (--force, -rf, …), которые делают безопасные команды опасными. Работает вместе с блокировкой команд.",
6014
+ "web.setup.security.configure": "Настроить",
6015
+ "web.setup.security.advanced": "Расширенные настройки безопасности",
6016
+ "web.setup.security.popup_hint": "По одному элементу в строке. Пустой список = выключено. Изменения применяются только к этой установке; позже их можно править в Настройках → Безопасность.",
6017
+ "web.setup.security.popup_done": "Готово",
6018
+ "web.setup.security.reset": "Сбросить к значениям по умолчанию",
6019
+ "web.setup.security.denied": "Всегда запрещённые пути",
6020
+ "web.setup.security.allowed": "Разрешены только эти пути (пусто = все, кроме запрещённых)",
6021
+ "web.setup.security.blacklist": "Всегда блокируемые команды",
6022
+ "web.setup.security.whitelist": "Разрешены только эти команды (пусто = все, кроме блокируемых)",
6023
+ "web.setup.security.log_commands": "Писать каждую выполненную команду в аудит-лог",
6024
+ "web.setup.security.dangerous_flags": "Блокируемые флаги команд",
6025
+ "web.setup.security.dangerous_operators": "Блокируемые shell-операторы",
6026
+ "web.setup.security.network_enabled": "Сетевая защита (блокировать приватные/внутренние адреса)",
6027
+ "web.setup.security.denied_domains": "Всегда запрещённые домены",
6028
+ "web.setup.security.allowed_domains": "Разрешены только эти домены (пусто = все, кроме запрещённых)",
6029
+ "web.setup.security.request_timeout": "Таймаут сетевых запросов (мс)",
6030
+ "web.setup.security.max_response_size": "Максимум размера ответа (байты)",
6031
+ "web.setup.security.max_recursion": "Максимум глубины рекурсии субагентов",
6032
+ "web.setup.security.max_file_ops": "Максимум файловых операций за сессию",
6033
+ "web.setup.security.rate_rpm": "Максимум LLM-запросов в минуту",
6034
+ "web.setup.security.rate_parallel": "Максимум параллельных задач",
6035
+ "web.setup.security.content_scan": "Сканировать содержимое файлов на опасные паттерны",
6036
+ "web.setup.security.session_encryption": "Шифровать логи сессий на диске",
6037
+ "web.setup.security.audit_enabled": "Отправлять события безопасности в аудит-нотификатор",
6038
+ "web.setup.security.audit_severity": "Минимальная severity для уведомления",
6039
+ "web.setup.security.audit_events": "Типы событий для уведомления",
6040
+ "web.setup.security.audit_retries": "Повторы вебхука",
6041
+ "web.setup.security.audit_timeout": "Таймаут вебхука (мс)",
6042
+ "web.setup.security.severity.low": "низкая",
6043
+ "web.setup.security.severity.medium": "средняя",
6044
+ "web.setup.security.severity.high": "высокая",
6045
+ "web.setup.security.severity.critical": "критическая",
5874
6046
  "web.setup.review.title": "Проверка",
5875
6047
  "web.setup.review.hint": "Проверьте значения и завершите. Настройки сохранятся в конфиг.",
5876
6048
  "web.setup.review.provider": "Провайдер",
@@ -9465,6 +9637,9 @@ class OpenAICompatProvider {
9465
9637
  this.userAgent = config.userAgent ?? defaultUserAgent();
9466
9638
  this.debug = config.logger ? config.logger.debug.bind(config.logger) : null;
9467
9639
  }
9640
+ get idleTimeoutMs() {
9641
+ return this.retryConfig.noDataTimeoutMs ?? 180000;
9642
+ }
9468
9643
  async* chat(messages, tools, signal, options) {
9469
9644
  if (!this.rateLimiter.canMakeRequest()) {
9470
9645
  const waitMs = Math.min(this.rateLimiter.msUntilRequest(), LLM_MAX_RATE_WAIT_MS);
@@ -9495,7 +9670,7 @@ class OpenAICompatProvider {
9495
9670
  }
9496
9671
  if (!hasToolCall && !hasText) {
9497
9672
  this.debug?.("LLM streaming produced no text/tool_call — falling back to non-streaming");
9498
- const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options);
9673
+ const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options, this.idleTimeoutMs);
9499
9674
  this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
9500
9675
  for (const chunk of fallback) {
9501
9676
  if (hasReasoning && chunk.type === "reasoning")
@@ -9522,9 +9697,9 @@ ${tag}`;
9522
9697
  return copy;
9523
9698
  }
9524
9699
  async* doStream(messages, tools, signal, options) {
9525
- const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
9700
+ const { baseDelay, maxDelay, maxStreamRetries } = this.retryConfig;
9526
9701
  const streamRetries = maxStreamRetries ?? 2;
9527
- const idleTimeoutMs = noDataTimeoutMs ?? 180000;
9702
+ const idleTimeoutMs = this.idleTimeoutMs;
9528
9703
  let usedNonStreamingFallback = false;
9529
9704
  for (let attempt = 0;; attempt++) {
9530
9705
  let emitted = false;
@@ -9543,7 +9718,7 @@ ${tag}`;
9543
9718
  if (err?.stalledAfterToolCall === true && !usedNonStreamingFallback) {
9544
9719
  usedNonStreamingFallback = true;
9545
9720
  this.debug?.("LLM stream stalled after tool_call — falling back to non-streaming request");
9546
- const fallback = await this.doNonStreaming(messages, tools, signal, options);
9721
+ const fallback = await this.doNonStreaming(messages, tools, signal, options, idleTimeoutMs);
9547
9722
  this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
9548
9723
  for (const chunk of fallback) {
9549
9724
  if (chunk.type === "reasoning")
@@ -9590,8 +9765,12 @@ ${tag}`;
9590
9765
  signal: abortSignal
9591
9766
  });
9592
9767
  } catch (err) {
9593
- if (err?.name === "AbortError")
9768
+ if (err?.name === "AbortError") {
9769
+ if (isTimeout()) {
9770
+ throw new Error(t("error.llm_timeout", { timeout: LLM_REQUEST_TIMEOUT_MS }));
9771
+ }
9594
9772
  throw err;
9773
+ }
9595
9774
  const wrapped = err instanceof Error ? err : new Error(String(err));
9596
9775
  if (wrapped instanceof LlmError) {
9597
9776
  wrapped.markTerminal();
@@ -9618,6 +9797,7 @@ ${tag}`;
9618
9797
  cleanup();
9619
9798
  throw new Error(t("error.no_response_body"));
9620
9799
  }
9800
+ cleanup();
9621
9801
  const decoder = new TextDecoder;
9622
9802
  let buffer = "";
9623
9803
  const toolCallAccs = new Map;
@@ -9783,7 +9963,7 @@ ${tag}`;
9783
9963
  streamUsage: this.cache?.requestStreamUsage === true
9784
9964
  };
9785
9965
  }
9786
- buildRequestSetup(signal) {
9966
+ buildRequestSetup(signal, totalTimeoutMs = LLM_REQUEST_TIMEOUT_MS) {
9787
9967
  const headers = {
9788
9968
  "Content-Type": "application/json",
9789
9969
  "User-Agent": this.userAgent
@@ -9800,7 +9980,7 @@ ${tag}`;
9800
9980
  const timeoutId = setTimeout(() => {
9801
9981
  timedOut = true;
9802
9982
  controller.abort();
9803
- }, LLM_REQUEST_TIMEOUT_MS);
9983
+ }, totalTimeoutMs);
9804
9984
  const abortSignal = (() => {
9805
9985
  if (!signal)
9806
9986
  return controller.signal;
@@ -9824,7 +10004,7 @@ ${tag}`;
9824
10004
  controller
9825
10005
  };
9826
10006
  }
9827
- async doNonStreaming(messages, tools, signal, options) {
10007
+ async doNonStreaming(messages, tools, signal, options, totalTimeoutMs = LLM_REQUEST_TIMEOUT_MS) {
9828
10008
  const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
9829
10009
  const body = buildRequestBody({
9830
10010
  model: this.model,
@@ -9836,7 +10016,7 @@ ${tag}`;
9836
10016
  reasoningStrategy: options?.reasoningStrategy,
9837
10017
  ...this.cacheHints()
9838
10018
  });
9839
- const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal);
10019
+ const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal, totalTimeoutMs);
9840
10020
  try {
9841
10021
  const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
9842
10022
  method: "POST",
@@ -9901,7 +10081,7 @@ ${tag}`;
9901
10081
  return chunks;
9902
10082
  } catch (err) {
9903
10083
  if (isTimeout() && err?.name === "AbortError") {
9904
- throw new Error(t("error.llm_timeout", { timeout: LLM_REQUEST_TIMEOUT_MS }));
10084
+ throw new Error(t("error.llm_timeout", { timeout: totalTimeoutMs }));
9905
10085
  }
9906
10086
  throw err instanceof Error ? err : new Error(String(err));
9907
10087
  } finally {
@@ -11726,10 +11906,23 @@ var init_walker = __esm(() => {
11726
11906
  // src/modules/indexer/cache.ts
11727
11907
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync18, mkdirSync as mkdirSync11, rmSync } from "fs";
11728
11908
  import { join as join20 } from "path";
11909
+ function isIndexedFile(v) {
11910
+ if (typeof v !== "object" || v === null)
11911
+ return false;
11912
+ const f = v;
11913
+ return typeof f.path === "string" && typeof f.language === "string" && Array.isArray(f.exports) && typeof f.size === "number";
11914
+ }
11915
+ function isIndexResult(v) {
11916
+ if (typeof v !== "object" || v === null)
11917
+ return false;
11918
+ const r = v;
11919
+ return Array.isArray(r.files) && r.files.every(isIndexedFile) && typeof r.totalSize === "number";
11920
+ }
11729
11921
 
11730
11922
  class IndexCache {
11731
11923
  cachePath;
11732
11924
  cache = null;
11925
+ cachedBuiltAt = null;
11733
11926
  root;
11734
11927
  signature;
11735
11928
  constructor(cacheDir, opts = {}) {
@@ -11744,24 +11937,28 @@ class IndexCache {
11744
11937
  return null;
11745
11938
  try {
11746
11939
  const parsed = JSON.parse(readFileSync14(this.cachePath, "utf-8"));
11747
- if (parsed.version !== CACHE_VERSION || !parsed.result)
11940
+ if (parsed.version !== CACHE_VERSION || !isIndexResult(parsed.result))
11748
11941
  return null;
11749
11942
  if (this.root !== undefined && parsed.root !== this.root)
11750
11943
  return null;
11751
11944
  if (this.signature && parsed.signature !== this.currentSignature())
11752
11945
  return null;
11753
11946
  this.cache = parsed.result;
11947
+ this.cachedBuiltAt = typeof parsed.builtAt === "number" ? parsed.builtAt : null;
11754
11948
  return this.cache;
11755
11949
  } catch {
11756
11950
  return null;
11757
11951
  }
11758
11952
  }
11759
11953
  save(result) {
11954
+ const now = Date.now();
11760
11955
  this.cache = result;
11956
+ this.cachedBuiltAt = now;
11761
11957
  const envelope = {
11762
11958
  version: CACHE_VERSION,
11763
11959
  root: this.root ?? "",
11764
11960
  signature: this.currentSignature(),
11961
+ builtAt: now,
11765
11962
  result
11766
11963
  };
11767
11964
  const dir = join20(this.cachePath, "..");
@@ -11771,12 +11968,16 @@ class IndexCache {
11771
11968
  }
11772
11969
  invalidate() {
11773
11970
  this.cache = null;
11971
+ this.cachedBuiltAt = null;
11774
11972
  if (existsSync18(this.cachePath)) {
11775
11973
  try {
11776
11974
  rmSync(this.cachePath);
11777
11975
  } catch {}
11778
11976
  }
11779
11977
  }
11978
+ getBuiltAt() {
11979
+ return this.cachedBuiltAt;
11980
+ }
11780
11981
  currentSignature() {
11781
11982
  if (!this.signature)
11782
11983
  return "";
@@ -12104,7 +12305,11 @@ class IndexerModule {
12104
12305
  cache;
12105
12306
  baseDir;
12106
12307
  index = null;
12107
- statsCache = null;
12308
+ mapCache = null;
12309
+ buildPromise = null;
12310
+ indexing = false;
12311
+ builtAt = null;
12312
+ watcherActive = false;
12108
12313
  rebuildTimeout = null;
12109
12314
  constructor(opts) {
12110
12315
  this.baseDir = opts.baseDir;
@@ -12114,26 +12319,38 @@ class IndexerModule {
12114
12319
  signature: () => this.indexer.scanSignature()
12115
12320
  });
12116
12321
  }
12117
- async buildIndex() {
12322
+ buildIndex() {
12118
12323
  if (this.index)
12119
- return this.index;
12324
+ return Promise.resolve(this.index);
12325
+ if (this.buildPromise)
12326
+ return this.buildPromise;
12120
12327
  const cached = this.cache.load();
12121
12328
  if (cached) {
12122
12329
  this.index = cached;
12123
- return cached;
12124
- }
12125
- try {
12126
- const result = await this.indexer.walk();
12127
- this.cache.save(result);
12128
- this.index = result;
12129
- return result;
12130
- } catch {
12131
- return null;
12330
+ this.builtAt = this.cache.getBuiltAt();
12331
+ return Promise.resolve(cached);
12132
12332
  }
12333
+ this.indexing = true;
12334
+ this.buildPromise = (async () => {
12335
+ try {
12336
+ const result = await this.indexer.walk();
12337
+ this.cache.save(result);
12338
+ this.index = result;
12339
+ this.builtAt = Date.now();
12340
+ return result;
12341
+ } catch {
12342
+ return null;
12343
+ } finally {
12344
+ this.indexing = false;
12345
+ this.buildPromise = null;
12346
+ }
12347
+ })();
12348
+ return this.buildPromise;
12133
12349
  }
12134
12350
  async refresh() {
12135
12351
  this.cache.invalidate();
12136
12352
  this.index = null;
12353
+ this.builtAt = null;
12137
12354
  return this.buildIndex();
12138
12355
  }
12139
12356
  find(query) {
@@ -12173,9 +12390,11 @@ class IndexerModule {
12173
12390
  this.refresh().catch(() => {});
12174
12391
  }, 2000);
12175
12392
  });
12393
+ this.watcherActive = true;
12176
12394
  } catch {}
12177
12395
  }
12178
12396
  unwatch() {
12397
+ this.watcherActive = false;
12179
12398
  if (this.rebuildTimeout) {
12180
12399
  clearTimeout(this.rebuildTimeout);
12181
12400
  this.rebuildTimeout = null;
@@ -12194,19 +12413,47 @@ class IndexerModule {
12194
12413
  kind: "project-map"
12195
12414
  };
12196
12415
  }
12197
- stats() {
12198
- if (!this.index)
12199
- return null;
12200
- if (this.statsCache && this.statsCache.index === this.index)
12201
- return this.statsCache.value;
12416
+ mapInfo() {
12417
+ if (!this.index) {
12418
+ return {
12419
+ indexed: false,
12420
+ indexing: this.indexing,
12421
+ files: 0,
12422
+ symbols: 0,
12423
+ tokens: 0,
12424
+ updatedAt: this.builtAt,
12425
+ languages: [],
12426
+ watcher: this.watcherActive
12427
+ };
12428
+ }
12429
+ if (this.mapCache && this.mapCache.index === this.index) {
12430
+ return { ...this.mapCache.value, indexing: this.indexing, watcher: this.watcherActive };
12431
+ }
12202
12432
  const symbols = this.index.files.reduce((n, f) => n + f.exports.length, 0);
12433
+ const langMap = new Map;
12434
+ for (const f of this.index.files) {
12435
+ const lang = f.language;
12436
+ const cur = langMap.get(lang) ?? { files: 0, symbols: 0 };
12437
+ cur.files += 1;
12438
+ cur.symbols += f.exports.length;
12439
+ langMap.set(lang, cur);
12440
+ }
12441
+ const languages = [...langMap.entries()].map(([language, v]) => ({ language, ...v })).sort((a, b) => b.files - a.files || a.language.localeCompare(b.language)).slice(0, 8);
12203
12442
  const value = {
12443
+ indexed: true,
12204
12444
  files: this.index.files.length,
12205
12445
  symbols,
12206
- tokens: this.estimateTokens(this.formatMap(this.index))
12446
+ tokens: this.estimateTokens(this.formatMap(this.index)),
12447
+ updatedAt: this.builtAt,
12448
+ languages,
12449
+ watcher: this.watcherActive
12207
12450
  };
12208
- this.statsCache = { index: this.index, value };
12209
- return value;
12451
+ this.mapCache = { index: this.index, value };
12452
+ return { ...value, indexing: this.indexing };
12453
+ }
12454
+ stats() {
12455
+ const info = this.mapInfo();
12456
+ return info.indexed ? { files: info.files, symbols: info.symbols, tokens: info.tokens } : null;
12210
12457
  }
12211
12458
  getToolDefinitions() {
12212
12459
  return [this.createProjectMapTool()];
@@ -16828,6 +17075,15 @@ class SessionLogger {
16828
17075
  ...data
16829
17076
  });
16830
17077
  }
17078
+ logClaimRetry(data) {
17079
+ this.session?.appendLog({
17080
+ ts: new Date().toISOString(),
17081
+ type: "claim_retry",
17082
+ content: data.reason,
17083
+ kind: data.kind,
17084
+ iteration: data.iteration
17085
+ });
17086
+ }
16831
17087
  logCritic(event, data = {}) {
16832
17088
  this.session?.appendLog({
16833
17089
  ts: new Date().toISOString(),
@@ -19418,8 +19674,17 @@ function isFilePathLike(s) {
19418
19674
  return true;
19419
19675
  }
19420
19676
  function extractFileLikeTokens(text) {
19421
- const matches = text.match(FILE_TOKEN_RE) || [];
19422
- return [...new Set(matches.filter(isFilePathLike))];
19677
+ const out = new Set;
19678
+ const re = new RegExp(FILE_TOKEN_RE.source, "gi");
19679
+ for (const m of text.matchAll(re)) {
19680
+ const token = m[0];
19681
+ const after = text[(m.index ?? 0) + token.length];
19682
+ if (after === "(")
19683
+ continue;
19684
+ if (isFilePathLike(token))
19685
+ out.add(token);
19686
+ }
19687
+ return [...out];
19423
19688
  }
19424
19689
  function stripUrls(text) {
19425
19690
  return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
@@ -20747,6 +21012,7 @@ function createLoopState() {
20747
21012
  criticCalls: 0,
20748
21013
  criticRepairCycles: 0,
20749
21014
  mutationsSinceCritic: 0,
21015
+ criticHistoryMark: 0,
20750
21016
  seenCriticIssues: new Set,
20751
21017
  emptyResponseRetries: 0,
20752
21018
  emptyResponseExhausted: false,
@@ -21557,6 +21823,7 @@ class HallucinationGate {
21557
21823
  return { action: "warn" };
21558
21824
  }
21559
21825
  if (result.status === "retry") {
21826
+ this.retractDraft(deps, result.kind ?? "retry", result.reason ?? "");
21560
21827
  return this.applyRetry(deps, textContent, result);
21561
21828
  }
21562
21829
  const claimRetry = this.checkSuccessClaim(deps, textContent);
@@ -21605,30 +21872,35 @@ class HallucinationGate {
21605
21872
  return this.retryClaim(deps, textContent, t("hall.claim_contradiction", {
21606
21873
  tool: contradiction.tool,
21607
21874
  snippet: contradiction.snippet
21608
- }), `Success-claim contradiction: ${contradiction.tool}: ${contradiction.snippet}`);
21875
+ }), `Success-claim contradiction: ${contradiction.tool}: ${contradiction.snippet}`, "success");
21609
21876
  }
21610
21877
  }
21611
21878
  if (state.artifactClaimRetries < MAX_CLAIM_CHECK_RETRIES && deps.artifacts && deps.artifacts.length > 0) {
21612
21879
  const artifact = checkArtifactClaims(text, deps.artifacts);
21613
21880
  if (artifact) {
21614
21881
  state.artifactClaimRetries++;
21615
- return this.retryClaim(deps, textContent, t("hall.claim_artifact", { path: artifact.path, detail: artifact.snippet }), `Artifact-claim contradiction: ${artifact.snippet}`);
21882
+ return this.retryClaim(deps, textContent, t("hall.claim_artifact", { path: artifact.path, detail: artifact.snippet }), `Artifact-claim contradiction: ${artifact.snippet}`, "artifact");
21616
21883
  }
21617
21884
  }
21618
21885
  if (state.outputClaimRetries < MAX_CLAIM_CHECK_RETRIES) {
21619
21886
  const unverified = checkUnverifiedOutput(text, evidence);
21620
21887
  if (unverified) {
21621
21888
  state.outputClaimRetries++;
21622
- return this.retryClaim(deps, textContent, t("hall.claim_output_unverified", { detail: unverified.snippet }), `Unverified output: ${unverified.snippet}`);
21889
+ return this.retryClaim(deps, textContent, t("hall.claim_output_unverified", { detail: unverified.snippet }), `Unverified output: ${unverified.snippet}`, "output");
21623
21890
  }
21624
21891
  }
21625
21892
  return null;
21626
21893
  }
21627
- retryClaim(deps, textContent, reason, logLine) {
21894
+ retractDraft(deps, kind, reason) {
21895
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21896
+ deps.slog?.logClaimRetry({ kind, reason, iteration: deps.state.iteration });
21897
+ }
21898
+ retryClaim(deps, textContent, reason, logLine, kind) {
21628
21899
  const { state, logger: logger3 } = deps;
21629
21900
  state.iterHallucinationRetry = true;
21630
21901
  state.suppressRepetitionRetry = true;
21631
21902
  logger3.diagnostic("warn", logLine);
21903
+ this.retractDraft(deps, kind, reason);
21632
21904
  if (textContent) {
21633
21905
  deps.contextManager.addMessage({ role: "assistant", content: textContent });
21634
21906
  }
@@ -21687,27 +21959,30 @@ function resolveArtifactStates(baseDir, response) {
21687
21959
  const rel = relative6(baseDir, abs);
21688
21960
  if (rel.startsWith("..") || isAbsolute4(rel))
21689
21961
  continue;
21690
- const state = resolveOneArtifact(token, abs);
21962
+ const state = resolveOneArtifact(token, baseDir);
21691
21963
  if (state)
21692
21964
  out.push(state);
21693
21965
  }
21694
21966
  return out;
21695
21967
  }
21696
- function resolveOneArtifact(token, abs) {
21968
+ function resolveOneArtifact(token, baseDir) {
21969
+ const found = findExistingFile(baseDir, token);
21970
+ if (!found)
21971
+ return { path: token, exists: false };
21697
21972
  let size;
21698
21973
  try {
21699
- const st = statSync8(abs);
21974
+ const st = statSync8(found);
21700
21975
  if (!st.isFile())
21701
21976
  return { path: token, exists: false };
21702
21977
  size = st.size;
21703
21978
  } catch {
21704
- return { path: token, exists: false };
21979
+ return { path: token, exists: true };
21705
21980
  }
21706
21981
  if (size > MAX_BYTES)
21707
21982
  return { path: token, exists: true };
21708
21983
  let content;
21709
21984
  try {
21710
- content = readFileSync25(abs, "utf-8");
21985
+ content = readFileSync25(found, "utf-8");
21711
21986
  } catch {
21712
21987
  return { path: token, exists: true };
21713
21988
  }
@@ -21717,6 +21992,7 @@ var MAX_PATHS = 10, MAX_BYTES, JSON_EXTS, XML_EXTS;
21717
21992
  var init_artifact_resolve = __esm(() => {
21718
21993
  init_js_identifiers();
21719
21994
  init_syntax_validator();
21995
+ init_auditor();
21720
21996
  MAX_BYTES = 500 * 1024;
21721
21997
  JSON_EXTS = new Set([".json", ".jsonc"]);
21722
21998
  XML_EXTS = new Set([".xml", ".drawio", ".svg", ".xaml", ".plist", ".iml"]);
@@ -21754,6 +22030,7 @@ class AuditGate {
21754
22030
  return { action: "exhausted", summary: audit.summary };
21755
22031
  }
21756
22032
  state.suppressRepetitionRetry = true;
22033
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21757
22034
  return { action: "reject" };
21758
22035
  }
21759
22036
  }
@@ -21765,10 +22042,17 @@ var init_audit_gate = __esm(() => {
21765
22042
  function issueSignature(issue) {
21766
22043
  return `${issue.severity}:${issue.claim}`.toLowerCase().replace(/\s+/g, " ").slice(0, 120);
21767
22044
  }
22045
+ function normalizedSignature(issue) {
22046
+ const body = `${issue.severity}:${issue.claim}`.toLowerCase().replace(/[^a-z0-9а-яё\s]/gi, " ").replace(/\s+/g, " ").trim().slice(0, 120);
22047
+ return `~${body}`;
22048
+ }
21768
22049
  function renderIssues(issues) {
21769
22050
  return issues.slice(0, 5).map((i) => `- [${i.severity}] ${i.problem || i.claim}${i.suggestion ? ` → ${i.suggestion}` : ""}`).join(`
21770
22051
  `);
21771
22052
  }
22053
+ function isRepeatedVerdict(state, issues) {
22054
+ return issues.length > 0 && issues.every((i) => state.seenCriticIssues.has(normalizedSignature(i)));
22055
+ }
21772
22056
 
21773
22057
  class CriticGate {
21774
22058
  opts;
@@ -21784,18 +22068,26 @@ class CriticGate {
21784
22068
  const verdict = await this.verifySafely(deps);
21785
22069
  if (!verdict || verdict.status === "ok")
21786
22070
  return "pass";
22071
+ const isRepeat = isRepeatedVerdict(deps.state, verdict.issues);
21787
22072
  const fresh = this.takeFreshIssues(deps.state, verdict.issues);
21788
22073
  if (fresh.length === 0)
21789
22074
  return "pass";
21790
22075
  this.logVerdict(deps, verdict, fresh.length);
21791
- const canRepair = verdict.status === "critical" && deps.beforeFinish && deps.state.criticRepairCycles < this.opts.maxRepairCycles;
21792
- this.inject(deps, verdict, fresh, canRepair);
22076
+ if (!this.shouldRepair(deps, verdict, isRepeat)) {
22077
+ this.injectRepeatOrAdvice(deps, fresh, isRepeat);
22078
+ return "pass";
22079
+ }
22080
+ this.injectRepair(deps, verdict, fresh);
21793
22081
  return "retry";
21794
22082
  }
22083
+ shouldRepair(deps, verdict, isRepeat) {
22084
+ return verdict.status === "critical" && deps.beforeFinish && deps.state.criticRepairCycles < this.opts.maxRepairCycles && !isRepeat;
22085
+ }
21795
22086
  async verifySafely(deps) {
21796
22087
  try {
21797
22088
  return await this.opts.verify(this.opts.buildPack(deps), this.opts.signal);
21798
- } catch {
22089
+ } catch (err) {
22090
+ deps.logger.diagnostic("warn", `Critic verify failed (fail-open): ${String(err?.message ?? err)}`);
21799
22091
  return null;
21800
22092
  }
21801
22093
  }
@@ -21804,9 +22096,8 @@ class CriticGate {
21804
22096
  return false;
21805
22097
  if (deps.state.criticCalls >= this.opts.maxCalls)
21806
22098
  return false;
21807
- if (!deps.beforeFinish && deps.state.mutationsSinceCritic < MUTATIONS_BEFORE_CHECK) {
22099
+ if (!deps.beforeFinish)
21808
22100
  return false;
21809
- }
21810
22101
  return deps.toolEvents.length !== 0;
21811
22102
  }
21812
22103
  takeFreshIssues(state, issues) {
@@ -21816,6 +22107,7 @@ class CriticGate {
21816
22107
  if (state.seenCriticIssues.has(sig))
21817
22108
  continue;
21818
22109
  state.seenCriticIssues.add(sig);
22110
+ state.seenCriticIssues.add(normalizedSignature(issue));
21819
22111
  fresh.push(issue);
21820
22112
  }
21821
22113
  return fresh;
@@ -21836,15 +22128,15 @@ class CriticGate {
21836
22128
  });
21837
22129
  deps.logger.diagnostic("info", `Critic ${verdict.status}: ${verdict.summary} (${count} issue(s))`);
21838
22130
  }
21839
- inject(deps, verdict, fresh, canRepair) {
21840
- if (!canRepair) {
21841
- deps.contextManager.addMessage({
21842
- role: "user",
21843
- content: `<system-summary>${t("critic.advice", { issues: renderIssues(fresh) })}</system-summary>`
21844
- });
21845
- return;
21846
- }
22131
+ injectRepeatOrAdvice(deps, fresh, isRepeat) {
22132
+ deps.contextManager.addMessage({
22133
+ role: "user",
22134
+ content: `<system-summary>${isRepeat ? t("critic.repeat", { issues: renderIssues(fresh) }) : t("critic.advice", { issues: renderIssues(fresh) })}</system-summary>`
22135
+ });
22136
+ }
22137
+ injectRepair(deps, verdict, fresh) {
21847
22138
  deps.state.criticRepairCycles++;
22139
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21848
22140
  deps.slog.logCritic("critic_escalation", {
21849
22141
  summary: verdict.summary,
21850
22142
  cycle: deps.state.criticRepairCycles
@@ -21858,9 +22150,11 @@ class CriticGate {
21858
22150
  });
21859
22151
  }
21860
22152
  }
21861
- function gatherTurnToolEvents(contextManager) {
22153
+ function gatherTurnToolEvents(contextManager, fromIndex = 0) {
21862
22154
  const events = [];
21863
- for (const m of contextManager.getActiveHistory()) {
22155
+ const history = contextManager.getActiveHistory();
22156
+ const start = Math.max(0, Math.min(fromIndex, history.length));
22157
+ for (const m of history.slice(start)) {
21864
22158
  if (m.role !== "tool" || !m.name)
21865
22159
  continue;
21866
22160
  const output = typeof m.content === "string" ? m.content.slice(-MAX_EVENT_OUTPUT_CHARS) : undefined;
@@ -21868,7 +22162,7 @@ function gatherTurnToolEvents(contextManager) {
21868
22162
  }
21869
22163
  return events;
21870
22164
  }
21871
- var MUTATIONS_BEFORE_CHECK = 3, MAX_EVENT_OUTPUT_CHARS = 600;
22165
+ var MAX_EVENT_OUTPUT_CHARS = 600;
21872
22166
  var init_critic_gate = __esm(() => {
21873
22167
  init_i18n();
21874
22168
  });
@@ -22055,7 +22349,9 @@ ${f.excerpt}`);
22055
22349
  pack.toolDigest || "(empty)",
22056
22350
  files.length > 0 ? `FILE CONTENTS:
22057
22351
  ${files.join(`
22058
- `)}` : ""
22352
+ `)}` : "",
22353
+ pack.answerExcerpt ? `ANSWER (the agent's chat delivery — may satisfy the task without files):
22354
+ ${pack.answerExcerpt}` : ""
22059
22355
  ].filter(Boolean).join(`
22060
22356
  `);
22061
22357
  }
@@ -22109,6 +22405,7 @@ Rules:
22109
22405
  - Flag ONLY issues that can be verified from the evidence provided. Do not speculate or invent.
22110
22406
  - Do not nitpick style, wording, or preferences unless the user's task demanded it.
22111
22407
  - If the evidence is insufficient to judge, return status "ok" (prefer silence over noise).
22408
+ - A chat-delivered answer (ANSWER section) can fully satisfy the task without any files or commands; absence of changed files is NOT itself an issue when ANSWER contains the requested result.
22112
22409
  - "critical" = the task's stated result is demonstrably not delivered. "concern" = a concrete, checkable gap. "info" = minor.
22113
22410
  - Every issue must reference concrete evidence (file, command, output).
22114
22411
  Reply with JSON ONLY:
@@ -22123,8 +22420,12 @@ var init_client = __esm(() => {
22123
22420
  // src/modules/critic/provider.ts
22124
22421
  function buildCriticProvider(critic, config, defaultProvider, deps) {
22125
22422
  const model = critic?.model?.trim();
22126
- if (!model)
22423
+ if (!model) {
22424
+ if (critic?.provider && Object.keys(critic.provider).length > 0) {
22425
+ deps?.logger?.warn("critic.provider is set without critic.model — provider ignored, using active provider");
22426
+ }
22127
22427
  return defaultProvider;
22428
+ }
22128
22429
  try {
22129
22430
  const providerCfg = critic?.provider ?? {
22130
22431
  type: "openai-compat",
@@ -22182,7 +22483,8 @@ function buildEvidencePack(input) {
22182
22483
  commands,
22183
22484
  toolDigest: digest.slice(-MAX_DIGEST_LINES).join(`
22184
22485
  `),
22185
- fileExcerpts: input.baseDir ? readExcerpts(input.baseDir, [...changedFiles]) : undefined
22486
+ fileExcerpts: input.baseDir ? readExcerpts(input.baseDir, [...changedFiles]) : undefined,
22487
+ answerExcerpt: input.answerText ? redactContextSecrets(input.answerText).slice(0, MAX_ANSWER_CHARS) : undefined
22186
22488
  };
22187
22489
  }
22188
22490
  function digestLine(ev) {
@@ -22203,7 +22505,7 @@ function commandOf(ev) {
22203
22505
  return;
22204
22506
  return {
22205
22507
  command: ev.args.command,
22206
- exitCode: ev.ok === false ? 1 : 0,
22508
+ exitCode: ev.ok === false ? 1 : undefined,
22207
22509
  output: ev.output?.slice(-MAX_DIGEST_OUTPUT_CHARS)
22208
22510
  };
22209
22511
  }
@@ -22221,9 +22523,10 @@ function readExcerpts(baseDir, paths) {
22221
22523
  }
22222
22524
  return out;
22223
22525
  }
22224
- var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200;
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;
22225
22527
  var init_evidence = __esm(() => {
22226
22528
  init_tool_output();
22529
+ init_context_log();
22227
22530
  MUTATING_TOOLS = new Set(["write_file", "edit_file", "delete_file", "move_file", "create_dir"]);
22228
22531
  });
22229
22532
 
@@ -22918,6 +23221,7 @@ class Agent {
22918
23221
  } = this.deps;
22919
23222
  this.abortController = new AbortController;
22920
23223
  const state = createLoopState();
23224
+ state.criticHistoryMark = contextManager.getActiveHistory().length;
22921
23225
  const compactionService = new CompactionService({
22922
23226
  contextManager,
22923
23227
  logger: logger3,
@@ -22975,7 +23279,8 @@ class Agent {
22975
23279
  task: d.task,
22976
23280
  planSummary: d.planSummary,
22977
23281
  toolEvents: d.toolEvents,
22978
- baseDir: this.deps.baseDir
23282
+ baseDir: this.deps.baseDir,
23283
+ answerText: d.answerText
22979
23284
  }),
22980
23285
  signal: this.abortController?.signal
22981
23286
  });
@@ -23256,7 +23561,8 @@ class Agent {
23256
23561
  onMeta,
23257
23562
  onChunk,
23258
23563
  verbose: resolveVerbosity(this.deps.config.ui) === "verbose",
23259
- artifacts: resolveArtifactStates(this.deps.baseDir, textContent)
23564
+ artifacts: resolveArtifactStates(this.deps.baseDir, textContent),
23565
+ slog
23260
23566
  });
23261
23567
  if (verdict.action === "block") {
23262
23568
  return {
@@ -23309,8 +23615,10 @@ class Agent {
23309
23615
  slog,
23310
23616
  task: input,
23311
23617
  planSummary: this.deps.planSummary?.() ?? undefined,
23312
- toolEvents: gatherTurnToolEvents(contextManager),
23313
- beforeFinish: true
23618
+ toolEvents: gatherTurnToolEvents(contextManager, state.criticHistoryMark),
23619
+ beforeFinish: true,
23620
+ answerText: state.mutationsSinceCritic === 0 ? textContent : undefined,
23621
+ onMeta
23314
23622
  });
23315
23623
  if (criticAction === "retry") {
23316
23624
  state.suppressRepetitionRetry = true;
@@ -23322,7 +23630,8 @@ class Agent {
23322
23630
  maxIterations: config.maxToolIterations,
23323
23631
  logger: logger3,
23324
23632
  contextManager,
23325
- slog
23633
+ slog,
23634
+ onMeta
23326
23635
  });
23327
23636
  if (auditVerdict === null || auditVerdict.action === "pass") {
23328
23637
  state.finalAnswerAccepted = true;
@@ -34932,6 +35241,40 @@ var init_box = __esm(() => {
34932
35241
  });
34933
35242
 
34934
35243
  // src/modules/setup/answers.ts
35244
+ function securityDetailFromConfig(s) {
35245
+ return {
35246
+ bash: {
35247
+ blacklist: [...s.bash.blacklist],
35248
+ whitelist: [...s.bash.whitelist],
35249
+ logCommands: s.bash.logCommands
35250
+ },
35251
+ paths: { denied: [...s.paths.denied], allowed: [...s.paths.allowed] },
35252
+ flags: {
35253
+ dangerousFlags: [...s.bash.dangerousFlags],
35254
+ dangerousOperators: [...s.bash.dangerousOperators]
35255
+ },
35256
+ network: {
35257
+ enabled: s.network.enabled,
35258
+ deniedDomains: [...s.network.deniedDomains],
35259
+ allowedDomains: [...s.network.allowedDomains],
35260
+ requestTimeout: s.network.requestTimeout,
35261
+ maxResponseSize: s.network.maxResponseSize
35262
+ },
35263
+ advanced: {
35264
+ maxRecursionDepth: s.maxRecursionDepth,
35265
+ maxFileOperations: s.maxFileOperations,
35266
+ maxRequestsPerMinute: s.rateLimits.maxRequestsPerMinute,
35267
+ maxParallelTasks: s.rateLimits.maxParallelTasks,
35268
+ contentScanEnabled: s.contentScan.enabled,
35269
+ sessionEncryptionEnabled: s.sessionEncryption.enabled,
35270
+ auditNotifierEnabled: s.auditNotifier.enabled,
35271
+ auditNotifierMinSeverity: s.auditNotifier.minSeverity ?? "medium",
35272
+ auditNotifierEventTypes: [...s.auditNotifier.eventTypes ?? []],
35273
+ auditNotifierMaxRetries: s.auditNotifier.maxRetries ?? 3,
35274
+ auditNotifierWebhookTimeout: s.auditNotifier.webhookTimeout ?? 5000
35275
+ }
35276
+ };
35277
+ }
34935
35278
  function buildSetupDisplay() {
34936
35279
  const providers = BUILTIN_PROVIDERS.map((spec) => {
34937
35280
  const option = { type: spec.type, label: spec.label };
@@ -35321,20 +35664,62 @@ function applyAnswersToConfig(config, a) {
35321
35664
  config.provider.entries = next;
35322
35665
  config.provider.active = updated.label || updated.type;
35323
35666
  }
35324
- if (config.security) {
35325
- config.security.enabled = true;
35326
- config.security.bash.enabled = true;
35327
- config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
35328
- if (a.securityBashBlock) {
35329
- config.security.bash.blacklist = [
35330
- ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
35331
- ];
35332
- }
35333
- config.security.paths.enabled = a.securityPathsRestrict;
35334
- }
35667
+ applySecurityAnswers(config, a);
35335
35668
  return config;
35336
35669
  }
35337
- var SECURITY_BASH_BLOCKLIST;
35670
+ function applySecurityAnswers(config, a) {
35671
+ if (!config.security)
35672
+ return;
35673
+ config.security.enabled = true;
35674
+ config.security.bash.enabled = true;
35675
+ config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
35676
+ if (a.securityBashBlock) {
35677
+ config.security.bash.blacklist = [
35678
+ ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
35679
+ ];
35680
+ }
35681
+ config.security.paths.enabled = a.securityPathsRestrict;
35682
+ if (a.securityDetail)
35683
+ applySecurityDetail(config.security, a.securityDetail);
35684
+ }
35685
+ function readPath(obj, path) {
35686
+ let cur = obj;
35687
+ for (const key of path.split(".")) {
35688
+ if (cur === null || typeof cur !== "object")
35689
+ return;
35690
+ cur = cur[key];
35691
+ }
35692
+ return cur;
35693
+ }
35694
+ function writePath(obj, path, value) {
35695
+ const keys = path.split(".");
35696
+ let cur = obj;
35697
+ for (let i = 0;i < keys.length - 1; i++) {
35698
+ if (cur[keys[i]] === null || typeof cur[keys[i]] !== "object")
35699
+ cur[keys[i]] = {};
35700
+ cur = cur[keys[i]];
35701
+ }
35702
+ cur[keys[keys.length - 1]] = value;
35703
+ }
35704
+ function isFieldValue(v, kind) {
35705
+ if (kind === "list")
35706
+ return Array.isArray(v) && v.every((x) => typeof x === "string");
35707
+ if (kind === "bool")
35708
+ return typeof v === "boolean";
35709
+ if (kind === "int")
35710
+ return typeof v === "number" && Number.isInteger(v) && v > 0;
35711
+ return typeof v === "string" && SEVERITIES2.includes(v);
35712
+ }
35713
+ function applySecurityDetail(security, detail) {
35714
+ const target = security;
35715
+ for (const field of DETAIL_FIELDS) {
35716
+ const value = readPath(detail, field.from);
35717
+ if (!isFieldValue(value, field.kind))
35718
+ continue;
35719
+ writePath(target, field.to, Array.isArray(value) ? [...value] : value);
35720
+ }
35721
+ }
35722
+ var SECURITY_BASH_BLOCKLIST, SEVERITIES2, DETAIL_FIELDS;
35338
35723
  var init_apply = __esm(() => {
35339
35724
  SECURITY_BASH_BLOCKLIST = [
35340
35725
  "rm",
@@ -35354,6 +35739,32 @@ var init_apply = __esm(() => {
35354
35739
  "shutdown",
35355
35740
  "reboot"
35356
35741
  ];
35742
+ SEVERITIES2 = ["low", "medium", "high", "critical"];
35743
+ DETAIL_FIELDS = [
35744
+ { from: "bash.blacklist", to: "bash.blacklist", kind: "list" },
35745
+ { from: "bash.whitelist", to: "bash.whitelist", kind: "list" },
35746
+ { from: "bash.logCommands", to: "bash.logCommands", kind: "bool" },
35747
+ { from: "paths.denied", to: "paths.denied", kind: "list" },
35748
+ { from: "paths.allowed", to: "paths.allowed", kind: "list" },
35749
+ { from: "flags.dangerousFlags", to: "bash.dangerousFlags", kind: "list" },
35750
+ { from: "flags.dangerousOperators", to: "bash.dangerousOperators", kind: "list" },
35751
+ { from: "network.enabled", to: "network.enabled", kind: "bool" },
35752
+ { from: "network.deniedDomains", to: "network.deniedDomains", kind: "list" },
35753
+ { from: "network.allowedDomains", to: "network.allowedDomains", kind: "list" },
35754
+ { from: "network.requestTimeout", to: "network.requestTimeout", kind: "int" },
35755
+ { from: "network.maxResponseSize", to: "network.maxResponseSize", kind: "int" },
35756
+ { from: "advanced.maxRecursionDepth", to: "maxRecursionDepth", kind: "int" },
35757
+ { from: "advanced.maxFileOperations", to: "maxFileOperations", kind: "int" },
35758
+ { from: "advanced.maxRequestsPerMinute", to: "rateLimits.maxRequestsPerMinute", kind: "int" },
35759
+ { from: "advanced.maxParallelTasks", to: "rateLimits.maxParallelTasks", kind: "int" },
35760
+ { from: "advanced.contentScanEnabled", to: "contentScan.enabled", kind: "bool" },
35761
+ { from: "advanced.sessionEncryptionEnabled", to: "sessionEncryption.enabled", kind: "bool" },
35762
+ { from: "advanced.auditNotifierEnabled", to: "auditNotifier.enabled", kind: "bool" },
35763
+ { from: "advanced.auditNotifierMinSeverity", to: "auditNotifier.minSeverity", kind: "severity" },
35764
+ { from: "advanced.auditNotifierEventTypes", to: "auditNotifier.eventTypes", kind: "list" },
35765
+ { from: "advanced.auditNotifierMaxRetries", to: "auditNotifier.maxRetries", kind: "int" },
35766
+ { from: "advanced.auditNotifierWebhookTimeout", to: "auditNotifier.webhookTimeout", kind: "int" }
35767
+ ];
35357
35768
  });
35358
35769
 
35359
35770
  // src/cli/setup-order.ts
@@ -51823,6 +52234,11 @@ function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
51823
52234
  schedule();
51824
52235
  },
51825
52236
  onMeta(chunk, kind) {
52237
+ if (kind === "retract") {
52238
+ flush();
52239
+ sink({ type: "retract", ts: Date.now(), reason: stripAnsi2(chunk) });
52240
+ return;
52241
+ }
51826
52242
  const next = kind ?? "status";
51827
52243
  if (buf.meta && (buf.metaKind ?? "status") !== next)
51828
52244
  flush();
@@ -53633,9 +54049,11 @@ function servePreview(res, absReal, buf) {
53633
54049
 
53634
54050
  // src/modules/webui/setup-api.ts
53635
54051
  init_i18n();
54052
+ init_registry_extra();
53636
54053
  init_apply();
53637
54054
  init_answers();
53638
54055
  init_discovery();
54056
+ init_provider_context();
53639
54057
  function str3(rec, key) {
53640
54058
  const v = rec[key];
53641
54059
  return typeof v === "string" ? v.trim() : "";
@@ -53645,6 +54063,76 @@ function positiveInt(rec, key) {
53645
54063
  const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
53646
54064
  return Number.isInteger(n) && n > 0 ? n : null;
53647
54065
  }
54066
+ var SEVERITIES3 = ["low", "medium", "high", "critical"];
54067
+ var MAX_DETAIL_LIST = 200;
54068
+ var MAX_DETAIL_ITEM = 200;
54069
+ function strList(v) {
54070
+ if (!Array.isArray(v))
54071
+ return;
54072
+ const out = v.filter((x) => typeof x === "string").map((x) => x.trim()).filter((x) => x !== "" && x.length <= MAX_DETAIL_ITEM).slice(0, MAX_DETAIL_LIST);
54073
+ return out;
54074
+ }
54075
+ function optBool(v) {
54076
+ return typeof v === "boolean" ? v : undefined;
54077
+ }
54078
+ function optInt(v) {
54079
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
54080
+ return Number.isInteger(n) && n > 0 ? n : undefined;
54081
+ }
54082
+ function parseSecurityDetail(v) {
54083
+ if (!v || typeof v !== "object")
54084
+ return;
54085
+ const rec = v;
54086
+ const detail = {};
54087
+ const bash = rec.bash;
54088
+ if (bash && typeof bash === "object") {
54089
+ detail.bash = {
54090
+ blacklist: strList(bash.blacklist),
54091
+ whitelist: strList(bash.whitelist),
54092
+ logCommands: optBool(bash.logCommands)
54093
+ };
54094
+ }
54095
+ const paths = rec.paths;
54096
+ if (paths && typeof paths === "object") {
54097
+ detail.paths = { denied: strList(paths.denied), allowed: strList(paths.allowed) };
54098
+ }
54099
+ const flags = rec.flags;
54100
+ if (flags && typeof flags === "object") {
54101
+ detail.flags = {
54102
+ dangerousFlags: strList(flags.dangerousFlags),
54103
+ dangerousOperators: strList(flags.dangerousOperators)
54104
+ };
54105
+ }
54106
+ const network = rec.network;
54107
+ if (network && typeof network === "object") {
54108
+ detail.network = {
54109
+ enabled: optBool(network.enabled),
54110
+ deniedDomains: strList(network.deniedDomains),
54111
+ allowedDomains: strList(network.allowedDomains),
54112
+ requestTimeout: optInt(network.requestTimeout),
54113
+ maxResponseSize: optInt(network.maxResponseSize)
54114
+ };
54115
+ }
54116
+ const adv = rec.advanced;
54117
+ if (adv && typeof adv === "object") {
54118
+ const rawSev = adv.auditNotifierMinSeverity;
54119
+ const sev = typeof rawSev === "string" && SEVERITIES3.includes(rawSev) ? rawSev : undefined;
54120
+ detail.advanced = {
54121
+ maxRecursionDepth: optInt(adv.maxRecursionDepth),
54122
+ maxFileOperations: optInt(adv.maxFileOperations),
54123
+ maxRequestsPerMinute: optInt(adv.maxRequestsPerMinute),
54124
+ maxParallelTasks: optInt(adv.maxParallelTasks),
54125
+ contentScanEnabled: optBool(adv.contentScanEnabled),
54126
+ sessionEncryptionEnabled: optBool(adv.sessionEncryptionEnabled),
54127
+ auditNotifierEnabled: optBool(adv.auditNotifierEnabled),
54128
+ auditNotifierMinSeverity: sev,
54129
+ auditNotifierEventTypes: strList(adv.auditNotifierEventTypes)?.filter((e) => AUDIT_EVENT_TYPES.includes(e)),
54130
+ auditNotifierMaxRetries: optInt(adv.auditNotifierMaxRetries),
54131
+ auditNotifierWebhookTimeout: optInt(adv.auditNotifierWebhookTimeout)
54132
+ };
54133
+ }
54134
+ return detail;
54135
+ }
53648
54136
  function parseSetupAnswers(rec) {
53649
54137
  const provider = str3(rec, "provider");
53650
54138
  const apiBase = str3(rec, "apiBase");
@@ -53674,15 +54162,17 @@ function parseSetupAnswers(rec) {
53674
54162
  locale,
53675
54163
  securityBashBlock: rec.securityBashBlock,
53676
54164
  securityFlagsBlock: rec.securityFlagsBlock,
53677
- securityPathsRestrict: rec.securityPathsRestrict
54165
+ securityPathsRestrict: rec.securityPathsRestrict,
54166
+ securityDetail: parseSecurityDetail(rec.securityDetail)
53678
54167
  };
53679
54168
  }
53680
54169
  function handleSetupGet(res, s) {
54170
+ const defaults = s.setup?.defaults ?? { contextWindow: 0, maxToolIterations: 0 };
53681
54171
  sendJson(res, 200, {
53682
54172
  required: s.setupRequired,
53683
54173
  languages: s.setup?.languages ?? [],
53684
54174
  providers: s.setup?.providers ?? [],
53685
- defaults: s.setup?.defaults ?? { contextWindow: 0, maxToolIterations: 0 }
54175
+ defaults: s.config?.security ? { ...defaults, security: securityDetailFromConfig(s.config.security) } : defaults
53686
54176
  });
53687
54177
  }
53688
54178
  function handleSetupScan(req, res, s) {
@@ -53728,6 +54218,31 @@ function handleSetupTest(req, res, s) {
53728
54218
  readBodyError(req, res, err);
53729
54219
  });
53730
54220
  }
54221
+ function handleSetupContext(req, res, s) {
54222
+ readJsonBody(req, s.maxBodyBytes).then(async (rec) => {
54223
+ if (!rec) {
54224
+ sendJson(res, 400, { ok: false, error: "invalid JSON" });
54225
+ return;
54226
+ }
54227
+ const baseUrl = str3(rec, "baseUrl");
54228
+ const model = str3(rec, "model");
54229
+ if (!baseUrl || !model) {
54230
+ sendJson(res, 400, { ok: false, error: "invalid-context-request" });
54231
+ return;
54232
+ }
54233
+ const probe = await probeProviderContext(baseUrl, model, {
54234
+ apiKey: str3(rec, "apiKey") || undefined,
54235
+ providerType: str3(rec, "providerType") || undefined
54236
+ });
54237
+ if (!probe) {
54238
+ sendJson(res, 200, { ok: false });
54239
+ return;
54240
+ }
54241
+ sendJson(res, 200, { ok: true, contextWindow: probe.actual, model: probe.model });
54242
+ }).catch((err) => {
54243
+ readBodyError(req, res, err);
54244
+ });
54245
+ }
53731
54246
  function handleSetupLocale(req, res, s) {
53732
54247
  readJsonBody(req, s.maxBodyBytes).then((rec) => {
53733
54248
  if (!rec) {
@@ -53800,6 +54315,10 @@ function handleSetupApi(req, res, reqUrl, s) {
53800
54315
  handleSetupTest(req, res, s);
53801
54316
  return true;
53802
54317
  }
54318
+ if (p === "/api/setup/context" && req.method === "POST") {
54319
+ handleSetupContext(req, res, s);
54320
+ return true;
54321
+ }
53803
54322
  if (p === "/api/setup/locale" && req.method === "POST") {
53804
54323
  handleSetupLocale(req, res, s);
53805
54324
  return true;
@@ -53877,6 +54396,7 @@ var API_ROUTES = {
53877
54396
  "GET /api/models": (_req, res, s) => handleModelsGet(res, s),
53878
54397
  "POST /api/command": (req, res, s) => handleCommandPost(req, res, s),
53879
54398
  "POST /api/interrupt": (_req, res, s) => handleInterruptPost(res, s),
54399
+ "POST /api/index": (_req, res, s) => handleIndexPost(res, s),
53880
54400
  "POST /api/mcp": (req, res, s) => handleMcpCreate(req, res, s),
53881
54401
  "GET /api/lsp": (_req, res, s) => handleLspGet(res, s)
53882
54402
  };
@@ -53918,6 +54438,14 @@ function handleInterruptPost(res, s) {
53918
54438
  s.bridge?.interrupt();
53919
54439
  sendJson(res, 200, { ok: true });
53920
54440
  }
54441
+ function handleIndexPost(res, s) {
54442
+ if (!s.refreshIndex) {
54443
+ sendJson(res, 503, { error: "unavailable" });
54444
+ return;
54445
+ }
54446
+ Promise.resolve(s.refreshIndex()).catch(() => {});
54447
+ sendJson(res, 202, { ok: true });
54448
+ }
53921
54449
  function handleRequest(req, res, assets, s) {
53922
54450
  let reqUrl;
53923
54451
  try {
@@ -54178,8 +54706,16 @@ async function startWebUi(deps) {
54178
54706
  state.plugins = deps.plugins;
54179
54707
  state.workspace = deps.baseDir;
54180
54708
  state.agentsMd = deps.agentsMd;
54181
- if (deps.indexer)
54709
+ if (deps.indexer) {
54182
54710
  state.projectMapStats = () => deps.indexer?.stats() ?? null;
54711
+ if (deps.indexer.refresh) {
54712
+ const refresh = deps.indexer.refresh.bind(deps.indexer);
54713
+ state.refreshIndex = async () => {
54714
+ await refresh();
54715
+ broadcast({ type: "state", ts: Date.now(), busy: state.busy, usage: state.usage }, state);
54716
+ };
54717
+ }
54718
+ }
54183
54719
  state.saveConfig = (config) => saveConfig(config, CONFIG_FILE, deps.configDir);
54184
54720
  installWebPromptIO({
54185
54721
  state,
@@ -54223,10 +54759,7 @@ async function startWebUi(deps) {
54223
54759
  contextProbe: probe,
54224
54760
  workspace: deps.baseDir,
54225
54761
  instructions: deps.agentsMd,
54226
- projectMap: (() => {
54227
- const s = deps.indexer?.stats();
54228
- return s ? { indexed: true, ...s } : { indexed: false, files: 0, symbols: 0, tokens: 0 };
54229
- })()
54762
+ projectMap: deps.indexer?.mapInfo?.()
54230
54763
  }, state.busy),
54231
54764
  addPendingImage: (url2) => {
54232
54765
  deps.agent.contextManager?.addPendingImage?.({