micro-models-agent 2.11.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/assets/app.js +22 -2
  3. package/dist/assets/index.html +39 -9
  4. package/dist/assets/js/dom.js +15 -0
  5. package/dist/assets/js/feed.js +129 -10
  6. package/dist/assets/js/sidebar.js +512 -0
  7. package/dist/assets/js/tabs.js +30 -5
  8. package/dist/assets/setup.js +267 -8
  9. package/dist/assets/styles.css +168 -3
  10. package/dist/cli/cache-line.js +1 -0
  11. package/dist/cli/command-suggest.js +5 -4
  12. package/dist/cli/commands.js +231 -122
  13. package/dist/cli/completer.js +132 -31
  14. package/dist/cli/index.js +1 -0
  15. package/dist/cli/json-payload.js +4 -4
  16. package/dist/cli/main.js +166 -61
  17. package/dist/cli/onboarding-commands.js +126 -0
  18. package/dist/cli/plugin-commands.js +2 -1
  19. package/dist/cli/relaunch.js +16 -13
  20. package/dist/cli/repl-commands-lsp.js +75 -0
  21. package/dist/cli/repl-commands-skills.js +105 -0
  22. package/dist/cli/repl-commands.js +589 -266
  23. package/dist/cli/repl.js +436 -177
  24. package/dist/cli/run-result.js +5 -6
  25. package/dist/cli/security-commands.js +37 -28
  26. package/dist/cli/setup-order.js +4 -55
  27. package/dist/cli/setup-prompt.js +3 -2
  28. package/dist/cli/setup.js +27 -107
  29. package/dist/cli/web-command.js +98 -0
  30. package/dist/config/budget.js +5 -5
  31. package/dist/config/config.js +103 -96
  32. package/dist/config/defaults.js +38 -8
  33. package/dist/config/domains.js +24 -27
  34. package/dist/config/env.js +46 -0
  35. package/dist/config/experts.js +2 -0
  36. package/dist/config/index.js +1 -0
  37. package/dist/config/paths.js +56 -0
  38. package/dist/config/redact.js +44 -0
  39. package/dist/config/registry-extra.js +334 -0
  40. package/dist/config/registry.js +584 -0
  41. package/dist/config/security.js +35 -36
  42. package/dist/config/timeouts.js +74 -0
  43. package/dist/config/utils.js +16 -15
  44. package/dist/core/agent/artifact-progress.js +59 -0
  45. package/dist/core/agent/artifact-resolve.js +79 -0
  46. package/dist/core/agent/audit-gate.js +7 -3
  47. package/dist/core/agent/compaction.js +2 -2
  48. package/dist/core/agent/constants.js +33 -4
  49. package/dist/core/agent/context-log.js +454 -0
  50. package/dist/core/agent/context-renderer.js +5 -0
  51. package/dist/core/agent/critic-gate.js +132 -0
  52. package/dist/core/agent/hallucination-gate.js +148 -38
  53. package/dist/core/agent/loop-state.js +15 -0
  54. package/dist/core/agent/prefix-monitor.js +1 -1
  55. package/dist/core/agent/reasoning-resolver.js +10 -1
  56. package/dist/core/agent/token-tracker.js +7 -4
  57. package/dist/core/agent/tool-batch.js +93 -18
  58. package/dist/core/agent/tool-images.js +18 -0
  59. package/dist/core/agent/tool-output.js +65 -11
  60. package/dist/core/agent/tool-pairing.js +60 -0
  61. package/dist/core/agent-moe.js +25 -24
  62. package/dist/core/agent.js +429 -108
  63. package/dist/core/bootstrap/agent-phase.js +66 -0
  64. package/dist/core/bootstrap/config-phase.js +86 -0
  65. package/dist/core/bootstrap/modules-phase.js +269 -0
  66. package/dist/core/bootstrap/provider-phase.js +183 -0
  67. package/dist/core/bootstrap/session-phase.js +204 -0
  68. package/dist/core/bootstrap/state.js +94 -0
  69. package/dist/core/bootstrap/workspace-phase.js +115 -0
  70. package/dist/core/bootstrap.js +92 -660
  71. package/dist/core/crash-handler.js +17 -7
  72. package/dist/core/environment.js +37 -12
  73. package/dist/core/host-plan.js +43 -0
  74. package/dist/core/host.js +23 -0
  75. package/dist/core/index.js +1 -0
  76. package/dist/core/prompt-builder.js +17 -1
  77. package/dist/core/prompt-overflow.js +42 -43
  78. package/dist/core/session-logger.js +122 -30
  79. package/dist/core/system-prompt.js +117 -0
  80. package/dist/core/version.js +6 -4
  81. package/dist/core/web-host.js +34 -0
  82. package/dist/core/workspace.js +19 -15
  83. package/dist/i18n/en.json +89 -3
  84. package/dist/i18n/index.js +37 -3
  85. package/dist/i18n/ru.json +89 -3
  86. package/dist/index.js +7 -3
  87. package/dist/llm/image-utils.js +55 -64
  88. package/dist/llm/llm-errors.js +9 -0
  89. package/dist/llm/model-loader.js +10 -32
  90. package/dist/llm/models-dev.js +121 -0
  91. package/dist/llm/openai-compat.js +146 -73
  92. package/dist/llm/orchestrator.js +33 -11
  93. package/dist/llm/provider-budget.js +2 -2
  94. package/dist/llm/provider-context.js +233 -0
  95. package/dist/llm/provider.js +1 -1
  96. package/dist/llm/response.js +5 -0
  97. package/dist/llm/token-counter.js +27 -11
  98. package/dist/logger/app-logger.js +48 -22
  99. package/dist/logger/file-log.js +28 -19
  100. package/dist/logger/index.js +1 -0
  101. package/dist/main.js +888 -139
  102. package/dist/migration/backup.js +14 -3
  103. package/dist/migration/detect.js +12 -3
  104. package/dist/migration/index.js +1 -0
  105. package/dist/modules/artifacts/store.js +9 -8
  106. package/dist/modules/browser/actions.js +6 -0
  107. package/dist/modules/browser/bridge-client.js +14 -12
  108. package/dist/modules/browser/bridge-path.js +4 -4
  109. package/dist/modules/browser/console-buffer.js +37 -0
  110. package/dist/modules/browser/cookie-store.js +5 -0
  111. package/dist/modules/browser/driver-types.js +1 -0
  112. package/dist/modules/browser/driver.js +19 -11
  113. package/dist/modules/browser/index.js +3 -1
  114. package/dist/modules/browser/module.js +2 -1
  115. package/dist/modules/browser/session.js +10 -30
  116. package/dist/modules/browser/snapshot.js +19 -13
  117. package/dist/modules/browser/types.js +1 -0
  118. package/dist/modules/certification/cli.js +28 -20
  119. package/dist/modules/certification/fact-checker.js +9 -1
  120. package/dist/modules/certification/loader.js +9 -2
  121. package/dist/modules/certification/manifest.js +83 -21
  122. package/dist/modules/certification/runner.js +23 -13
  123. package/dist/modules/certification/scenarios.js +20 -20
  124. package/dist/modules/certification/syntax-scenarios.js +1 -2
  125. package/dist/modules/context/chunk-query.js +11 -9
  126. package/dist/modules/context/fact-extractor.js +37 -39
  127. package/dist/modules/context/index.js +1 -0
  128. package/dist/modules/context/manager.js +106 -80
  129. package/dist/modules/critic/client.js +176 -0
  130. package/dist/modules/critic/evidence.js +81 -0
  131. package/dist/modules/critic/provider.js +45 -0
  132. package/dist/modules/critic/tools.js +78 -0
  133. package/dist/modules/critic/types.js +1 -0
  134. package/dist/modules/execution/audit-runners.js +28 -34
  135. package/dist/modules/execution/auditor.js +53 -49
  136. package/dist/modules/execution/execution-plugin.js +142 -102
  137. package/dist/modules/execution/index.js +1 -1
  138. package/dist/modules/execution/input-from.js +37 -13
  139. package/dist/modules/execution/module.js +368 -191
  140. package/dist/modules/execution/moe-executor.js +53 -26
  141. package/dist/modules/execution/plan-actions.js +320 -202
  142. package/dist/modules/execution/plan-checks.js +35 -0
  143. package/dist/modules/execution/plan-coverage.js +36 -33
  144. package/dist/modules/execution/plan-steps.js +11 -3
  145. package/dist/modules/execution/plan-store.js +83 -17
  146. package/dist/modules/execution/plan-tool.js +89 -35
  147. package/dist/modules/execution/plan-validator.js +30 -10
  148. package/dist/modules/execution/planner.js +22 -13
  149. package/dist/modules/execution/stuck-detector.js +196 -93
  150. package/dist/modules/execution/tracker.js +22 -8
  151. package/dist/modules/execution/verifier.js +69 -48
  152. package/dist/modules/execution/windows-commands.js +12 -12
  153. package/dist/modules/hallucination/artifact-claim.js +93 -0
  154. package/dist/modules/hallucination/confidence.js +13 -11
  155. package/dist/modules/hallucination/consistency.js +10 -2
  156. package/dist/modules/hallucination/detector.js +10 -5
  157. package/dist/modules/hallucination/factual.js +43 -26
  158. package/dist/modules/hallucination/index.js +1 -0
  159. package/dist/modules/hallucination/js-identifiers.js +44 -45
  160. package/dist/modules/hallucination/llm-judge.js +17 -12
  161. package/dist/modules/hallucination/success-claim.js +59 -0
  162. package/dist/modules/indexer/cache.js +52 -4
  163. package/dist/modules/indexer/index.js +1 -0
  164. package/dist/modules/indexer/map-command.js +2 -4
  165. package/dist/modules/indexer/map-select.js +16 -16
  166. package/dist/modules/indexer/module.js +47 -12
  167. package/dist/modules/indexer/project-profile.js +8 -7
  168. package/dist/modules/indexer/symbols.js +15 -15
  169. package/dist/modules/indexer/walker.js +159 -29
  170. package/dist/modules/lsp/check-tool.js +7 -5
  171. package/dist/modules/lsp/client.js +39 -39
  172. package/dist/modules/lsp/command.js +17 -24
  173. package/dist/modules/lsp/config.js +12 -11
  174. package/dist/modules/lsp/index.js +1 -0
  175. package/dist/modules/lsp/module.js +48 -46
  176. package/dist/modules/lsp/probe.js +13 -14
  177. package/dist/modules/lsp/project-root.js +14 -17
  178. package/dist/modules/lsp/startup-check.js +18 -16
  179. package/dist/modules/mcp/client.js +59 -62
  180. package/dist/modules/mcp/index.js +1 -0
  181. package/dist/modules/mcp/module.js +66 -37
  182. package/dist/modules/mcp/registry.js +5 -0
  183. package/dist/modules/memory/index.js +1 -0
  184. package/dist/modules/memory/module.js +12 -8
  185. package/dist/modules/memory/search.js +4 -1
  186. package/dist/modules/memory/store.js +40 -2
  187. package/dist/modules/onboarding/init.js +47 -0
  188. package/dist/modules/onboarding/meet.js +30 -0
  189. package/dist/modules/onboarding/mode.js +30 -0
  190. package/dist/modules/onboarding/prompts.js +43 -0
  191. package/dist/modules/pipelines/engine.js +10 -0
  192. package/dist/modules/pipelines/index.js +1 -0
  193. package/dist/modules/pipelines/parser.js +4 -1
  194. package/dist/modules/pipelines/template.js +4 -0
  195. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  196. package/dist/modules/plugins/builtin/notify.js +2 -0
  197. package/dist/modules/plugins/index.js +1 -0
  198. package/dist/modules/plugins/loader.js +18 -3
  199. package/dist/modules/plugins/manager.js +37 -17
  200. package/dist/modules/pricing/index.js +10 -11
  201. package/dist/modules/pricing/prices.js +12 -12
  202. package/dist/modules/processes/index.js +1 -0
  203. package/dist/modules/processes/kill-tree.js +27 -31
  204. package/dist/modules/processes/registry.js +44 -25
  205. package/dist/modules/processes/runner.js +4 -4
  206. package/dist/modules/providers/capabilities.js +28 -0
  207. package/dist/modules/providers/create.js +3 -4
  208. package/dist/modules/providers/factory.js +7 -7
  209. package/dist/modules/providers/fallback.js +12 -12
  210. package/dist/modules/providers/health.js +4 -4
  211. package/dist/modules/providers/manager.js +50 -19
  212. package/dist/modules/providers/naming.js +15 -0
  213. package/dist/modules/providers/preflight.js +71 -0
  214. package/dist/modules/providers/presets.js +39 -8
  215. package/dist/modules/providers/registry.js +7 -2
  216. package/dist/modules/reasoning/policy.js +8 -6
  217. package/dist/modules/reasoning/probe.js +30 -25
  218. package/dist/modules/security/audit-log.js +19 -36
  219. package/dist/modules/security/audit-notifier.js +35 -75
  220. package/dist/modules/security/command-validator.js +99 -51
  221. package/dist/modules/security/content-scanner.js +8 -8
  222. package/dist/modules/security/data-sanitizer.js +9 -16
  223. package/dist/modules/security/encryption.js +32 -52
  224. package/dist/modules/security/index.js +2 -1
  225. package/dist/modules/security/network-validator.js +120 -43
  226. package/dist/modules/security/path-validator.js +61 -46
  227. package/dist/modules/security/protected-paths.js +73 -0
  228. package/dist/modules/security/rate-limiter.js +17 -49
  229. package/dist/modules/security/security-policies.js +25 -57
  230. package/dist/modules/security/session-encryption.js +32 -71
  231. package/dist/modules/security/session-isolation.js +15 -28
  232. package/dist/modules/session/auto-name.js +63 -0
  233. package/dist/modules/session/export.js +98 -0
  234. package/dist/modules/session/index.js +1 -0
  235. package/dist/modules/session/manager.js +79 -28
  236. package/dist/modules/session/module.js +4 -3
  237. package/dist/modules/session/store.js +57 -20
  238. package/dist/modules/setup/answers.js +35 -0
  239. package/dist/modules/setup/apply.js +79 -0
  240. package/dist/modules/setup/discovery.js +72 -0
  241. package/dist/modules/skills/index.js +1 -0
  242. package/dist/modules/skills/loader.js +9 -1
  243. package/dist/modules/skills/module.js +11 -0
  244. package/dist/modules/updater/changelog-reader.js +15 -16
  245. package/dist/modules/updater/checker.js +9 -4
  246. package/dist/modules/updater/dev-detect.js +7 -6
  247. package/dist/modules/updater/index.js +1 -0
  248. package/dist/modules/updater/module.js +17 -15
  249. package/dist/modules/user-profile/compressor.js +2 -0
  250. package/dist/modules/user-profile/index.js +1 -0
  251. package/dist/modules/user-profile/profile.js +10 -1
  252. package/dist/modules/webui/answer-api.js +29 -0
  253. package/dist/modules/webui/assets.js +50 -0
  254. package/dist/modules/webui/body.js +21 -0
  255. package/dist/modules/webui/commands-settings.js +209 -0
  256. package/dist/modules/webui/commands-shared.js +60 -0
  257. package/dist/modules/webui/commands.js +174 -0
  258. package/dist/modules/webui/config-api.js +134 -0
  259. package/dist/modules/webui/file-api.js +202 -0
  260. package/dist/modules/webui/forward.js +159 -0
  261. package/dist/modules/webui/history-api.js +112 -0
  262. package/dist/modules/webui/host-state.js +60 -0
  263. package/dist/modules/webui/i18n-api.js +9 -0
  264. package/dist/modules/webui/limits.js +6 -0
  265. package/dist/modules/webui/log-forward.js +20 -0
  266. package/dist/modules/webui/memory-commands.js +64 -0
  267. package/dist/modules/webui/model-cache.js +18 -0
  268. package/dist/modules/webui/module.js +148 -0
  269. package/dist/modules/webui/persist.js +24 -0
  270. package/dist/modules/webui/prompt-io.js +97 -0
  271. package/dist/modules/webui/providers-api.js +337 -0
  272. package/dist/modules/webui/providers-view.js +56 -0
  273. package/dist/modules/webui/reasoning-view.js +15 -0
  274. package/dist/modules/webui/run-agent.js +81 -0
  275. package/dist/modules/webui/server.js +284 -0
  276. package/dist/modules/webui/sessions-api.js +178 -0
  277. package/dist/modules/webui/settings-api.js +561 -0
  278. package/dist/modules/webui/setup-api.js +201 -0
  279. package/dist/modules/webui/state.js +74 -0
  280. package/dist/modules/webui/util.js +261 -0
  281. package/dist/output/bus.js +7 -2
  282. package/dist/output/channel.js +80 -28
  283. package/dist/output/format.js +2 -1
  284. package/dist/output/index.js +1 -0
  285. package/dist/output/json-sink.js +5 -3
  286. package/dist/output/machine.js +4 -3
  287. package/dist/output/session-sink.js +4 -4
  288. package/dist/tools/approve.js +4 -0
  289. package/dist/tools/attach-image.js +5 -4
  290. package/dist/tools/auto-fixer.js +26 -44
  291. package/dist/tools/auto-tags.js +83 -0
  292. package/dist/tools/bash.js +193 -116
  293. package/dist/tools/browser.js +76 -31
  294. package/dist/tools/chunk-query.js +14 -6
  295. package/dist/tools/create-dir.js +9 -5
  296. package/dist/tools/delete-file.js +9 -5
  297. package/dist/tools/download-file.js +32 -7
  298. package/dist/tools/edit-file.js +52 -14
  299. package/dist/tools/enable-tools.js +6 -6
  300. package/dist/tools/executor.js +132 -27
  301. package/dist/tools/file-info.js +3 -1
  302. package/dist/tools/filter-tools.js +3 -3
  303. package/dist/tools/glob-tool.js +4 -1
  304. package/dist/tools/grep-tool.js +13 -10
  305. package/dist/tools/hidden-tools-block.js +6 -6
  306. package/dist/tools/index.js +16 -8
  307. package/dist/tools/list-dir.js +4 -2
  308. package/dist/tools/load-skill.js +7 -0
  309. package/dist/tools/mcp-call.js +15 -4
  310. package/dist/tools/meet-done.js +14 -0
  311. package/dist/tools/move-file.js +14 -7
  312. package/dist/tools/path-utils.js +29 -29
  313. package/dist/tools/pipeline-run.js +29 -7
  314. package/dist/tools/preview.js +1 -1
  315. package/dist/tools/process-ids.js +31 -0
  316. package/dist/tools/process-kill.js +9 -16
  317. package/dist/tools/process-list.js +1 -0
  318. package/dist/tools/process-log.js +11 -16
  319. package/dist/tools/process-wait.js +63 -0
  320. package/dist/tools/prompt-io.js +4 -3
  321. package/dist/tools/question.js +172 -25
  322. package/dist/tools/read-file.js +55 -34
  323. package/dist/tools/recall.js +14 -8
  324. package/dist/tools/registry.js +6 -5
  325. package/dist/tools/remember.js +8 -4
  326. package/dist/tools/scope-request.js +11 -6
  327. package/dist/tools/search-history.js +5 -2
  328. package/dist/tools/session-info.js +2 -0
  329. package/dist/tools/set-thinking.js +8 -4
  330. package/dist/tools/subagent.js +59 -39
  331. package/dist/tools/syntax-validator.js +292 -90
  332. package/dist/tools/user-input.js +22 -18
  333. package/dist/tools/web-browse.js +9 -3
  334. package/dist/tools/web-fetch.js +12 -23
  335. package/dist/tools/web-search.js +7 -5
  336. package/dist/tools/write-file.js +45 -15
  337. package/dist/ui/action-log.js +13 -0
  338. package/dist/ui/box.js +4 -3
  339. package/dist/ui/colors.js +2 -0
  340. package/dist/ui/diff.js +6 -5
  341. package/dist/ui/index.js +1 -0
  342. package/dist/ui/line-editor.js +230 -122
  343. package/dist/ui/line-math.js +10 -2
  344. package/dist/ui/md-formatter.js +57 -37
  345. package/dist/ui/output.js +8 -3
  346. package/dist/ui/plan-view.js +21 -10
  347. package/dist/ui/renderer.js +393 -67
  348. package/dist/ui/spinner.js +16 -5
  349. package/dist/ui/table.js +15 -17
  350. package/dist/ui/theme.js +143 -0
  351. package/dist/utils/child-run.js +20 -0
  352. package/dist/utils/error.js +1 -1
  353. package/dist/utils/index.js +2 -0
  354. package/dist/utils/retry.js +2 -2
  355. package/dist/utils/truncate.js +1 -1
  356. package/package.json +1 -1
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",
@@ -4015,7 +4059,7 @@ Updated: {updatedAt}`,
4015
4059
  "web.setup.language.title": "Choose your language",
4016
4060
  "web.setup.language.hint": "Used for the interface.",
4017
4061
  "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.",
4062
+ "web.setup.provider.hint": "For a local model pick the OpenAI-compatible option, scan for your server, then test the connection before continuing.",
4019
4063
  "web.setup.provider.base_url": "Base URL",
4020
4064
  "web.setup.provider.scan": "Scan for local servers",
4021
4065
  "web.setup.provider.scan_found_one": "Found {count} server",
@@ -4024,6 +4068,10 @@ Updated: {updatedAt}`,
4024
4068
  "web.setup.provider.scan_none": "No local servers found — enter the URL manually.",
4025
4069
  "web.setup.provider.scan_failed": "Scan failed. Enter the URL manually.",
4026
4070
  "web.setup.provider.required": "Please enter a base URL.",
4071
+ "web.setup.provider.test": "Test connection",
4072
+ "web.setup.provider.testing": "Testing…",
4073
+ "web.setup.provider.test_ok": "Server reachable.",
4074
+ "web.setup.provider.test_fail": "Could not reach the server at this URL.",
4027
4075
  "web.setup.key.title": "API key",
4028
4076
  "web.setup.key.hint": "Leave empty for local servers. Stored only in your config file and never shown again.",
4029
4077
  "web.setup.key.label": "Key",
@@ -4042,15 +4090,54 @@ Updated: {updatedAt}`,
4042
4090
  "web.setup.model.test_fail": "Could not reach the model.",
4043
4091
  "web.setup.model.required": "Please choose or type a model.",
4044
4092
  "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.",
4093
+ "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
4094
  "web.setup.context.contextWindow": "Context window (tokens)",
4047
4095
  "web.setup.context.maxToolIterations": "Max tool iterations",
4048
4096
  "web.setup.context.required": "Enter a positive number.",
4097
+ "web.setup.context.detect": "Detect from provider",
4098
+ "web.setup.context.detecting": "Detecting…",
4099
+ "web.setup.context.detect_ok": "Context set from provider: {count} tokens.",
4100
+ "web.setup.context.detect_fail": "Could not detect context — enter it manually.",
4049
4101
  "web.setup.security.title": "Security",
4050
- "web.setup.security.hint": "Safe defaults are on; turn off only what you need.",
4102
+ "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
4103
  "web.setup.security.paths": "Restrict file access to the workspace",
4104
+ "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
4105
  "web.setup.security.bash": "Block dangerous shell commands",
4106
+ "web.setup.security.bash_desc": "Blocks destructive commands (rm, sudo, ssh, curl, shutdown, …). The agent asks instead of running them.",
4053
4107
  "web.setup.security.flags": "Block dangerous command flags",
4108
+ "web.setup.security.flags_desc": "Blocks risky flags (--force, -rf, …) that make safe commands dangerous. Works together with the command block.",
4109
+ "web.setup.security.configure": "Configure",
4110
+ "web.setup.security.advanced": "Advanced security settings",
4111
+ "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.",
4112
+ "web.setup.security.popup_done": "Done",
4113
+ "web.setup.security.reset": "Reset to defaults",
4114
+ "web.setup.security.denied": "Always denied paths",
4115
+ "web.setup.security.allowed": "Only allowed paths (empty = all except denied)",
4116
+ "web.setup.security.blacklist": "Always blocked commands",
4117
+ "web.setup.security.whitelist": "Only allowed commands (empty = all except blocked)",
4118
+ "web.setup.security.log_commands": "Log every executed command to the audit log",
4119
+ "web.setup.security.dangerous_flags": "Blocked command flags",
4120
+ "web.setup.security.dangerous_operators": "Blocked shell operators",
4121
+ "web.setup.security.network_enabled": "Network protection (block private/internal addresses)",
4122
+ "web.setup.security.denied_domains": "Always denied domains",
4123
+ "web.setup.security.allowed_domains": "Only allowed domains (empty = all except denied)",
4124
+ "web.setup.security.request_timeout": "Network request timeout (ms)",
4125
+ "web.setup.security.max_response_size": "Max response size (bytes)",
4126
+ "web.setup.security.max_recursion": "Max subagent recursion depth",
4127
+ "web.setup.security.max_file_ops": "Max file operations per session",
4128
+ "web.setup.security.rate_rpm": "Max LLM requests per minute",
4129
+ "web.setup.security.rate_parallel": "Max parallel tasks",
4130
+ "web.setup.security.content_scan": "Scan file content for dangerous patterns",
4131
+ "web.setup.security.session_encryption": "Encrypt session logs on disk",
4132
+ "web.setup.security.audit_enabled": "Send security events to the audit notifier",
4133
+ "web.setup.security.audit_severity": "Minimum severity to notify",
4134
+ "web.setup.security.audit_events": "Notified event types",
4135
+ "web.setup.security.audit_retries": "Webhook max retries",
4136
+ "web.setup.security.audit_timeout": "Webhook timeout (ms)",
4137
+ "web.setup.security.severity.low": "low",
4138
+ "web.setup.security.severity.medium": "medium",
4139
+ "web.setup.security.severity.high": "high",
4140
+ "web.setup.security.severity.critical": "critical",
4054
4141
  "web.setup.review.title": "Review",
4055
4142
  "web.setup.review.hint": "Check the values, then finish. Settings are saved to your config.",
4056
4143
  "web.setup.review.provider": "Provider",
@@ -4423,6 +4510,8 @@ var init_ru = __esm(() => {
4423
4510
  {issues}
4424
4511
  Сводка: {summary}`,
4425
4512
  "critic.advice": `Верификатор отметил проблемы, которые стоит проверить до завершения:
4513
+ {issues}`,
4514
+ "critic.repeat": `Верификатор повторяет уже отмеченную проблему — не выдавай тот же ответ снова. Смени подход или заверши честно, если жалоба неприменима:
4426
4515
  {issues}`,
4427
4516
  "tool.subagent_queued": "Задача подагенту добавлена: {task}",
4428
4517
  "tool.subagent_artifact": `Суб-агент завершён. Полный результат сохранён в артефакт: {path}
@@ -5221,6 +5310,7 @@ var init_ru = __esm(() => {
5221
5310
  "ui.todo_action": "{action} · {count}",
5222
5311
  "ui.tool_running": "{tool}…",
5223
5312
  "ui.step_context": "шаг {id}: {desc}",
5313
+ "ui.draft_retracted": "↳ черновик отклонён проверкой, уточняю…",
5224
5314
  "indexer.map_header": "Карта проекта",
5225
5315
  "indexer.top_directories": "Основные директории",
5226
5316
  "indexer.files": "Файлы",
@@ -5398,6 +5488,46 @@ var init_ru = __esm(() => {
5398
5488
  "web.sidebar.session": "сессия",
5399
5489
  "web.sidebar.usage": "использование",
5400
5490
  "web.sidebar.env": "окружение",
5491
+ "web.sidebar.tab_session": "Сессия",
5492
+ "web.sidebar.tab_map": "Карта",
5493
+ "web.sidebar.tab_trace": "Трассировка",
5494
+ "web.sidebar.map_status": "статус",
5495
+ "web.sidebar.map_ready": "готово",
5496
+ "web.sidebar.map_indexing": "индексация…",
5497
+ "web.sidebar.map_stale": "устарело",
5498
+ "web.sidebar.map_updated": "обновлено",
5499
+ "web.sidebar.map_never": "никогда",
5500
+ "web.sidebar.map_today": "сегодня, {time}",
5501
+ "web.sidebar.map_watcher": "вотчер",
5502
+ "web.sidebar.map_on": "вкл.",
5503
+ "web.sidebar.map_off": "выкл.",
5504
+ "web.sidebar.map_files": "файлы",
5505
+ "web.sidebar.map_symbols": "символы",
5506
+ "web.sidebar.map_tokens": "токены",
5507
+ "web.sidebar.map_root": "корень",
5508
+ "web.sidebar.map_languages": "языки",
5509
+ "web.sidebar.map_reindex": "Переиндексировать",
5510
+ "web.sidebar.map_reindexing": "переиндексация…",
5511
+ "web.sidebar.map_reindex_failed": "не удалось переиндексировать",
5512
+ "web.sidebar.trace_empty": "событий пока нет",
5513
+ "web.sidebar.trace_turn": "ход {n}",
5514
+ "web.sidebar.trace_text": "печатает… {count} симв.",
5515
+ "web.sidebar.trace_plan": "обновление плана",
5516
+ "web.sidebar.trace_question": "вопрос",
5517
+ "web.sidebar.detail_metadata": "метаданные",
5518
+ "web.sidebar.detail_arguments": "аргументы",
5519
+ "web.sidebar.detail_output": "вывод",
5520
+ "web.sidebar.detail_error": "ошибка",
5521
+ "web.sidebar.detail_text": "текст",
5522
+ "web.sidebar.detail_plan": "план",
5523
+ "web.sidebar.detail_reasoning": "размышление",
5524
+ "web.sidebar.detail_status": "статус",
5525
+ "web.sidebar.detail_ok": "ok",
5526
+ "web.sidebar.detail_fail": "ошибка",
5527
+ "web.sidebar.detail_duration": "длительность",
5528
+ "web.sidebar.detail_time": "время",
5529
+ "web.sidebar.detail_tool_call": "вызов тула",
5530
+ "web.sidebar.detail_session": "сессия",
5401
5531
  "web.env.dir": "директория",
5402
5532
  "web.env.agents": "AGENTS.md",
5403
5533
  "web.env.map": "карта",
@@ -5765,6 +5895,7 @@ var init_ru = __esm(() => {
5765
5895
  "web.history.loading": "загрузка…",
5766
5896
  "web.history.unavailable": "— история недоступна",
5767
5897
  "web.history.empty": "— пустая сессия —",
5898
+ "web.scroll.latest": "к новому",
5768
5899
  "web.tool.output_one": "вывод · {count} символ",
5769
5900
  "web.tool.output_few": "вывод · {count} символа",
5770
5901
  "web.tool.output_many": "вывод · {count} символов",
@@ -5835,7 +5966,7 @@ var init_ru = __esm(() => {
5835
5966
  "web.setup.language.title": "Выберите язык",
5836
5967
  "web.setup.language.hint": "Используется для интерфейса.",
5837
5968
  "web.setup.provider.title": "Выберите провайдера",
5838
- "web.setup.provider.hint": "Для локальной модели выберите OpenAI-совместимый вариант и просканируйте сервер.",
5969
+ "web.setup.provider.hint": "Для локальной модели выберите OpenAI-совместимый вариант, просканируйте сервер и проверьте подключение перед продолжением.",
5839
5970
  "web.setup.provider.base_url": "Базовый URL",
5840
5971
  "web.setup.provider.scan": "Найти локальные серверы",
5841
5972
  "web.setup.provider.scan_found_one": "Найден {count} сервер",
@@ -5844,6 +5975,10 @@ var init_ru = __esm(() => {
5844
5975
  "web.setup.provider.scan_none": "Локальные серверы не найдены — введите URL вручную.",
5845
5976
  "web.setup.provider.scan_failed": "Сканирование не удалось. Введите URL вручную.",
5846
5977
  "web.setup.provider.required": "Укажите базовый URL.",
5978
+ "web.setup.provider.test": "Проверить подключение",
5979
+ "web.setup.provider.testing": "Проверка…",
5980
+ "web.setup.provider.test_ok": "Сервер отвечает.",
5981
+ "web.setup.provider.test_fail": "Не удалось связаться с сервером по этому URL.",
5847
5982
  "web.setup.key.title": "API-ключ",
5848
5983
  "web.setup.key.hint": "Для локальных серверов можно оставить пустым. Хранится только в файле конфигурации и больше не показывается.",
5849
5984
  "web.setup.key.label": "Ключ",
@@ -5862,15 +5997,54 @@ var init_ru = __esm(() => {
5862
5997
  "web.setup.model.test_fail": "Не удалось связаться с моделью.",
5863
5998
  "web.setup.model.required": "Выберите или введите модель.",
5864
5999
  "web.setup.context.title": "Контекст и итерации",
5865
- "web.setup.context.hint": "Сколько контекста получает модель и сколько шагов с тулами она может сделать.",
6000
+ "web.setup.context.hint": "Должно совпадать с контекстом, который провайдер выделяет этой модели. Нажмите «Определить» либо сверьтесь с LM Studio / Ollama / документацией провайдера.",
5866
6001
  "web.setup.context.contextWindow": "Окно контекста (токены)",
5867
6002
  "web.setup.context.maxToolIterations": "Максимум итераций тулов",
5868
6003
  "web.setup.context.required": "Введите положительное число.",
6004
+ "web.setup.context.detect": "Определить у провайдера",
6005
+ "web.setup.context.detecting": "Определение…",
6006
+ "web.setup.context.detect_ok": "Контекст от провайдера: {count} токенов.",
6007
+ "web.setup.context.detect_fail": "Не удалось определить контекст — введите вручную.",
5869
6008
  "web.setup.security.title": "Безопасность",
5870
- "web.setup.security.hint": "Безопасные значения включены; отключайте только нужное.",
6009
+ "web.setup.security.hint": "Безопасные значения включены; отключайте только нужное. Ничего не блокируется молча — каждый переключатель лишь требует подтверждения перед опасным действием.",
5871
6010
  "web.setup.security.paths": "Ограничить доступ к файлам рабочей папкой",
6011
+ "web.setup.security.paths_desc": "Агент читает и пишет только внутри рабочей папки, а перед доступом к .git, .env, ключам и другим чувствительным путям спросит подтверждения.",
5872
6012
  "web.setup.security.bash": "Блокировать опасные shell-команды",
6013
+ "web.setup.security.bash_desc": "Блокирует разрушительные команды (rm, sudo, ssh, curl, shutdown, …). Вместо запуска агент спросит подтверждения.",
5873
6014
  "web.setup.security.flags": "Блокировать опасные флаги команд",
6015
+ "web.setup.security.flags_desc": "Блокирует рискованные флаги (--force, -rf, …), которые делают безопасные команды опасными. Работает вместе с блокировкой команд.",
6016
+ "web.setup.security.configure": "Настроить",
6017
+ "web.setup.security.advanced": "Расширенные настройки безопасности",
6018
+ "web.setup.security.popup_hint": "По одному элементу в строке. Пустой список = выключено. Изменения применяются только к этой установке; позже их можно править в Настройках → Безопасность.",
6019
+ "web.setup.security.popup_done": "Готово",
6020
+ "web.setup.security.reset": "Сбросить к значениям по умолчанию",
6021
+ "web.setup.security.denied": "Всегда запрещённые пути",
6022
+ "web.setup.security.allowed": "Разрешены только эти пути (пусто = все, кроме запрещённых)",
6023
+ "web.setup.security.blacklist": "Всегда блокируемые команды",
6024
+ "web.setup.security.whitelist": "Разрешены только эти команды (пусто = все, кроме блокируемых)",
6025
+ "web.setup.security.log_commands": "Писать каждую выполненную команду в аудит-лог",
6026
+ "web.setup.security.dangerous_flags": "Блокируемые флаги команд",
6027
+ "web.setup.security.dangerous_operators": "Блокируемые shell-операторы",
6028
+ "web.setup.security.network_enabled": "Сетевая защита (блокировать приватные/внутренние адреса)",
6029
+ "web.setup.security.denied_domains": "Всегда запрещённые домены",
6030
+ "web.setup.security.allowed_domains": "Разрешены только эти домены (пусто = все, кроме запрещённых)",
6031
+ "web.setup.security.request_timeout": "Таймаут сетевых запросов (мс)",
6032
+ "web.setup.security.max_response_size": "Максимум размера ответа (байты)",
6033
+ "web.setup.security.max_recursion": "Максимум глубины рекурсии субагентов",
6034
+ "web.setup.security.max_file_ops": "Максимум файловых операций за сессию",
6035
+ "web.setup.security.rate_rpm": "Максимум LLM-запросов в минуту",
6036
+ "web.setup.security.rate_parallel": "Максимум параллельных задач",
6037
+ "web.setup.security.content_scan": "Сканировать содержимое файлов на опасные паттерны",
6038
+ "web.setup.security.session_encryption": "Шифровать логи сессий на диске",
6039
+ "web.setup.security.audit_enabled": "Отправлять события безопасности в аудит-нотификатор",
6040
+ "web.setup.security.audit_severity": "Минимальная severity для уведомления",
6041
+ "web.setup.security.audit_events": "Типы событий для уведомления",
6042
+ "web.setup.security.audit_retries": "Повторы вебхука",
6043
+ "web.setup.security.audit_timeout": "Таймаут вебхука (мс)",
6044
+ "web.setup.security.severity.low": "низкая",
6045
+ "web.setup.security.severity.medium": "средняя",
6046
+ "web.setup.security.severity.high": "высокая",
6047
+ "web.setup.security.severity.critical": "критическая",
5874
6048
  "web.setup.review.title": "Проверка",
5875
6049
  "web.setup.review.hint": "Проверьте значения и завершите. Настройки сохранятся в конфиг.",
5876
6050
  "web.setup.review.provider": "Провайдер",
@@ -9386,6 +9560,13 @@ function defaultUserAgent() {
9386
9560
  cachedUserAgent = `micro-models-agent/${readMmaVersion()}`;
9387
9561
  return cachedUserAgent;
9388
9562
  }
9563
+ function normalizeReasoningEffort(level) {
9564
+ if (!level || level === "default" || level === "none")
9565
+ return;
9566
+ if (level === "max")
9567
+ return "high";
9568
+ return level;
9569
+ }
9389
9570
  function buildRequestBody(opts) {
9390
9571
  const body = {
9391
9572
  model: opts.model,
@@ -9403,13 +9584,15 @@ function buildRequestBody(opts) {
9403
9584
  if (opts.stream && opts.streamUsage)
9404
9585
  body.stream_options = { include_usage: true };
9405
9586
  const strategy = opts.reasoningStrategy ?? "openai-effort";
9406
- const level = opts.reasoningEffort;
9407
- if (strategy === "openai-effort" && level && level !== "default") {
9408
- body.reasoning_effort = level;
9587
+ const effort = normalizeReasoningEffort(opts.reasoningEffort);
9588
+ if (strategy === "openai-effort") {
9589
+ if (effort)
9590
+ body.reasoning_effort = effort;
9409
9591
  } else if (strategy === "template-kwarg") {
9410
- body.chat_template_kwargs = { enable_thinking: level !== "none" };
9411
- } else if (strategy === "prompt-tag" && level && level !== "default") {
9412
- body.reasoning_effort = level;
9592
+ body.chat_template_kwargs = { enable_thinking: opts.reasoningEffort !== "none" };
9593
+ } else if (strategy === "prompt-tag") {
9594
+ if (effort)
9595
+ body.reasoning_effort = effort;
9413
9596
  }
9414
9597
  if (opts.tools && opts.tools.length > 0) {
9415
9598
  body.tools = opts.tools.map((t2) => ({
@@ -9456,6 +9639,9 @@ class OpenAICompatProvider {
9456
9639
  this.userAgent = config.userAgent ?? defaultUserAgent();
9457
9640
  this.debug = config.logger ? config.logger.debug.bind(config.logger) : null;
9458
9641
  }
9642
+ get idleTimeoutMs() {
9643
+ return this.retryConfig.noDataTimeoutMs ?? 180000;
9644
+ }
9459
9645
  async* chat(messages, tools, signal, options) {
9460
9646
  if (!this.rateLimiter.canMakeRequest()) {
9461
9647
  const waitMs = Math.min(this.rateLimiter.msUntilRequest(), LLM_MAX_RATE_WAIT_MS);
@@ -9486,7 +9672,7 @@ class OpenAICompatProvider {
9486
9672
  }
9487
9673
  if (!hasToolCall && !hasText) {
9488
9674
  this.debug?.("LLM streaming produced no text/tool_call — falling back to non-streaming");
9489
- const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options);
9675
+ const fallback = await this.doNonStreaming(effectiveMessages, tools, signal, options, this.idleTimeoutMs);
9490
9676
  this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
9491
9677
  for (const chunk of fallback) {
9492
9678
  if (hasReasoning && chunk.type === "reasoning")
@@ -9513,9 +9699,9 @@ ${tag}`;
9513
9699
  return copy;
9514
9700
  }
9515
9701
  async* doStream(messages, tools, signal, options) {
9516
- const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
9702
+ const { baseDelay, maxDelay, maxStreamRetries } = this.retryConfig;
9517
9703
  const streamRetries = maxStreamRetries ?? 2;
9518
- const idleTimeoutMs = noDataTimeoutMs ?? 180000;
9704
+ const idleTimeoutMs = this.idleTimeoutMs;
9519
9705
  let usedNonStreamingFallback = false;
9520
9706
  for (let attempt = 0;; attempt++) {
9521
9707
  let emitted = false;
@@ -9534,7 +9720,7 @@ ${tag}`;
9534
9720
  if (err?.stalledAfterToolCall === true && !usedNonStreamingFallback) {
9535
9721
  usedNonStreamingFallback = true;
9536
9722
  this.debug?.("LLM stream stalled after tool_call — falling back to non-streaming request");
9537
- const fallback = await this.doNonStreaming(messages, tools, signal, options);
9723
+ const fallback = await this.doNonStreaming(messages, tools, signal, options, idleTimeoutMs);
9538
9724
  this.debug?.("LLM non-streaming fallback result", { chunks: fallback.length });
9539
9725
  for (const chunk of fallback) {
9540
9726
  if (chunk.type === "reasoning")
@@ -9581,8 +9767,12 @@ ${tag}`;
9581
9767
  signal: abortSignal
9582
9768
  });
9583
9769
  } catch (err) {
9584
- if (err?.name === "AbortError")
9770
+ if (err?.name === "AbortError") {
9771
+ if (isTimeout()) {
9772
+ throw new Error(t("error.llm_timeout", { timeout: LLM_REQUEST_TIMEOUT_MS }));
9773
+ }
9585
9774
  throw err;
9775
+ }
9586
9776
  const wrapped = err instanceof Error ? err : new Error(String(err));
9587
9777
  if (wrapped instanceof LlmError) {
9588
9778
  wrapped.markTerminal();
@@ -9609,6 +9799,7 @@ ${tag}`;
9609
9799
  cleanup();
9610
9800
  throw new Error(t("error.no_response_body"));
9611
9801
  }
9802
+ cleanup();
9612
9803
  const decoder = new TextDecoder;
9613
9804
  let buffer = "";
9614
9805
  const toolCallAccs = new Map;
@@ -9774,7 +9965,7 @@ ${tag}`;
9774
9965
  streamUsage: this.cache?.requestStreamUsage === true
9775
9966
  };
9776
9967
  }
9777
- buildRequestSetup(signal) {
9968
+ buildRequestSetup(signal, totalTimeoutMs = LLM_REQUEST_TIMEOUT_MS) {
9778
9969
  const headers = {
9779
9970
  "Content-Type": "application/json",
9780
9971
  "User-Agent": this.userAgent
@@ -9791,7 +9982,7 @@ ${tag}`;
9791
9982
  const timeoutId = setTimeout(() => {
9792
9983
  timedOut = true;
9793
9984
  controller.abort();
9794
- }, LLM_REQUEST_TIMEOUT_MS);
9985
+ }, totalTimeoutMs);
9795
9986
  const abortSignal = (() => {
9796
9987
  if (!signal)
9797
9988
  return controller.signal;
@@ -9815,7 +10006,7 @@ ${tag}`;
9815
10006
  controller
9816
10007
  };
9817
10008
  }
9818
- async doNonStreaming(messages, tools, signal, options) {
10009
+ async doNonStreaming(messages, tools, signal, options, totalTimeoutMs = LLM_REQUEST_TIMEOUT_MS) {
9819
10010
  const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
9820
10011
  const body = buildRequestBody({
9821
10012
  model: this.model,
@@ -9827,7 +10018,7 @@ ${tag}`;
9827
10018
  reasoningStrategy: options?.reasoningStrategy,
9828
10019
  ...this.cacheHints()
9829
10020
  });
9830
- const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal);
10021
+ const { headers, abortSignal, cleanup, isTimeout } = this.buildRequestSetup(signal, totalTimeoutMs);
9831
10022
  try {
9832
10023
  const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
9833
10024
  method: "POST",
@@ -9892,7 +10083,7 @@ ${tag}`;
9892
10083
  return chunks;
9893
10084
  } catch (err) {
9894
10085
  if (isTimeout() && err?.name === "AbortError") {
9895
- throw new Error(t("error.llm_timeout", { timeout: LLM_REQUEST_TIMEOUT_MS }));
10086
+ throw new Error(t("error.llm_timeout", { timeout: totalTimeoutMs }));
9896
10087
  }
9897
10088
  throw err instanceof Error ? err : new Error(String(err));
9898
10089
  } finally {
@@ -10281,6 +10472,14 @@ var init_cache = __esm(() => {
10281
10472
  });
10282
10473
 
10283
10474
  // src/modules/providers/manager.ts
10475
+ var exports_manager = {};
10476
+ __export(exports_manager, {
10477
+ reasoningStrategyForProvider: () => reasoningStrategyForProvider,
10478
+ presetOf: () => presetOf,
10479
+ activeProviderEndpoint: () => activeProviderEndpoint,
10480
+ ProviderManager: () => ProviderManager
10481
+ });
10482
+
10284
10483
  class ProviderManager {
10285
10484
  entries;
10286
10485
  lastKnownFallback;
@@ -10420,6 +10619,12 @@ class ProviderManager {
10420
10619
  this.cache.clear();
10421
10620
  }
10422
10621
  }
10622
+ function presetOf(type) {
10623
+ const spec = BUILTIN_PROVIDERS.find((p) => p.type === type);
10624
+ if (!spec)
10625
+ return;
10626
+ return { label: spec.label, baseUrl: spec.baseUrl };
10627
+ }
10423
10628
  function activeProviderEndpoint(config) {
10424
10629
  const entries = Array.isArray(config?.entries) ? config.entries : [];
10425
10630
  const active = entries.find((e) => e.label === config?.active || e.type === config?.active) ?? entries[0];
@@ -10428,6 +10633,10 @@ function activeProviderEndpoint(config) {
10428
10633
  }
10429
10634
  return { baseUrl: config?.baseUrl ?? "", apiKey: config?.apiKey, type: config?.type ?? "openai-compat" };
10430
10635
  }
10636
+ function reasoningStrategyForProvider(config) {
10637
+ const type = activeProviderEndpoint(config).type;
10638
+ return BUILTIN_PROVIDERS.find((p) => p.type === type)?.capabilities.reasoningStrategy ?? "none";
10639
+ }
10431
10640
  var init_manager = __esm(() => {
10432
10641
  init_create();
10433
10642
  init_cache();
@@ -10629,6 +10838,8 @@ function cachePath2() {
10629
10838
  async function probeReasoningSupport(provider, strategy, signal) {
10630
10839
  if (strategy === "none")
10631
10840
  return false;
10841
+ if (strategy === "openai-effort")
10842
+ return true;
10632
10843
  try {
10633
10844
  let hasReasoning = false;
10634
10845
  for await (const chunk of provider.chat(PROBE_MESSAGES, undefined, signal, {
@@ -10775,8 +10986,7 @@ async function setupProviderPhase(config, logger, dir) {
10775
10986
  const builtProvider = buildActiveProvider(config, logger, { getSessionId });
10776
10987
  const providerManager = builtProvider.manager;
10777
10988
  const llmProvider = builtProvider.provider;
10778
- const providerSpec = BUILTIN_PROVIDERS.find((p) => p.type === config.provider.type);
10779
- const reasoningStrategy = providerSpec?.capabilities.reasoningStrategy ?? "none";
10989
+ const reasoningStrategy = reasoningStrategyForProvider(config.provider);
10780
10990
  let reasoningProbePromise = Promise.resolve(false);
10781
10991
  if (config.reasoning && config.reasoning.mode !== "none" && reasoningStrategy !== "none") {
10782
10992
  reasoningProbePromise = (async () => {
@@ -10816,7 +11026,6 @@ var init_provider_phase = __esm(() => {
10816
11026
  init_model_loader();
10817
11027
  init_factory();
10818
11028
  init_manager();
10819
- init_presets();
10820
11029
  init_profile();
10821
11030
  init_i18n();
10822
11031
  });
@@ -11703,6 +11912,7 @@ import { join as join20 } from "path";
11703
11912
  class IndexCache {
11704
11913
  cachePath;
11705
11914
  cache = null;
11915
+ cachedBuiltAt = null;
11706
11916
  root;
11707
11917
  signature;
11708
11918
  constructor(cacheDir, opts = {}) {
@@ -11724,17 +11934,21 @@ class IndexCache {
11724
11934
  if (this.signature && parsed.signature !== this.currentSignature())
11725
11935
  return null;
11726
11936
  this.cache = parsed.result;
11937
+ this.cachedBuiltAt = typeof parsed.builtAt === "number" ? parsed.builtAt : null;
11727
11938
  return this.cache;
11728
11939
  } catch {
11729
11940
  return null;
11730
11941
  }
11731
11942
  }
11732
11943
  save(result) {
11944
+ const now = Date.now();
11733
11945
  this.cache = result;
11946
+ this.cachedBuiltAt = now;
11734
11947
  const envelope = {
11735
11948
  version: CACHE_VERSION,
11736
11949
  root: this.root ?? "",
11737
11950
  signature: this.currentSignature(),
11951
+ builtAt: now,
11738
11952
  result
11739
11953
  };
11740
11954
  const dir = join20(this.cachePath, "..");
@@ -11744,12 +11958,16 @@ class IndexCache {
11744
11958
  }
11745
11959
  invalidate() {
11746
11960
  this.cache = null;
11961
+ this.cachedBuiltAt = null;
11747
11962
  if (existsSync18(this.cachePath)) {
11748
11963
  try {
11749
11964
  rmSync(this.cachePath);
11750
11965
  } catch {}
11751
11966
  }
11752
11967
  }
11968
+ getBuiltAt() {
11969
+ return this.cachedBuiltAt;
11970
+ }
11753
11971
  currentSignature() {
11754
11972
  if (!this.signature)
11755
11973
  return "";
@@ -12077,7 +12295,11 @@ class IndexerModule {
12077
12295
  cache;
12078
12296
  baseDir;
12079
12297
  index = null;
12080
- statsCache = null;
12298
+ mapCache = null;
12299
+ buildPromise = null;
12300
+ indexing = false;
12301
+ builtAt = null;
12302
+ watcherActive = false;
12081
12303
  rebuildTimeout = null;
12082
12304
  constructor(opts) {
12083
12305
  this.baseDir = opts.baseDir;
@@ -12087,26 +12309,38 @@ class IndexerModule {
12087
12309
  signature: () => this.indexer.scanSignature()
12088
12310
  });
12089
12311
  }
12090
- async buildIndex() {
12312
+ buildIndex() {
12091
12313
  if (this.index)
12092
- return this.index;
12314
+ return Promise.resolve(this.index);
12315
+ if (this.buildPromise)
12316
+ return this.buildPromise;
12093
12317
  const cached = this.cache.load();
12094
12318
  if (cached) {
12095
12319
  this.index = cached;
12096
- return cached;
12097
- }
12098
- try {
12099
- const result = await this.indexer.walk();
12100
- this.cache.save(result);
12101
- this.index = result;
12102
- return result;
12103
- } catch {
12104
- return null;
12320
+ this.builtAt = this.cache.getBuiltAt();
12321
+ return Promise.resolve(cached);
12105
12322
  }
12323
+ this.indexing = true;
12324
+ this.buildPromise = (async () => {
12325
+ try {
12326
+ const result = await this.indexer.walk();
12327
+ this.cache.save(result);
12328
+ this.index = result;
12329
+ this.builtAt = Date.now();
12330
+ return result;
12331
+ } catch {
12332
+ return null;
12333
+ } finally {
12334
+ this.indexing = false;
12335
+ this.buildPromise = null;
12336
+ }
12337
+ })();
12338
+ return this.buildPromise;
12106
12339
  }
12107
12340
  async refresh() {
12108
12341
  this.cache.invalidate();
12109
12342
  this.index = null;
12343
+ this.builtAt = null;
12110
12344
  return this.buildIndex();
12111
12345
  }
12112
12346
  find(query) {
@@ -12146,9 +12380,11 @@ class IndexerModule {
12146
12380
  this.refresh().catch(() => {});
12147
12381
  }, 2000);
12148
12382
  });
12383
+ this.watcherActive = true;
12149
12384
  } catch {}
12150
12385
  }
12151
12386
  unwatch() {
12387
+ this.watcherActive = false;
12152
12388
  if (this.rebuildTimeout) {
12153
12389
  clearTimeout(this.rebuildTimeout);
12154
12390
  this.rebuildTimeout = null;
@@ -12167,19 +12403,47 @@ class IndexerModule {
12167
12403
  kind: "project-map"
12168
12404
  };
12169
12405
  }
12170
- stats() {
12171
- if (!this.index)
12172
- return null;
12173
- if (this.statsCache && this.statsCache.index === this.index)
12174
- return this.statsCache.value;
12406
+ mapInfo() {
12407
+ if (!this.index) {
12408
+ return {
12409
+ indexed: false,
12410
+ indexing: this.indexing,
12411
+ files: 0,
12412
+ symbols: 0,
12413
+ tokens: 0,
12414
+ updatedAt: this.builtAt,
12415
+ languages: [],
12416
+ watcher: this.watcherActive
12417
+ };
12418
+ }
12419
+ if (this.mapCache && this.mapCache.index === this.index) {
12420
+ return { ...this.mapCache.value, indexing: this.indexing, watcher: this.watcherActive };
12421
+ }
12175
12422
  const symbols = this.index.files.reduce((n, f) => n + f.exports.length, 0);
12423
+ const langMap = new Map;
12424
+ for (const f of this.index.files) {
12425
+ const lang = f.language;
12426
+ const cur = langMap.get(lang) ?? { files: 0, symbols: 0 };
12427
+ cur.files += 1;
12428
+ cur.symbols += f.exports.length;
12429
+ langMap.set(lang, cur);
12430
+ }
12431
+ const languages = [...langMap.entries()].map(([language, v]) => ({ language, ...v })).sort((a, b) => b.files - a.files || a.language.localeCompare(b.language)).slice(0, 8);
12176
12432
  const value = {
12433
+ indexed: true,
12177
12434
  files: this.index.files.length,
12178
12435
  symbols,
12179
- tokens: this.estimateTokens(this.formatMap(this.index))
12436
+ tokens: this.estimateTokens(this.formatMap(this.index)),
12437
+ updatedAt: this.builtAt,
12438
+ languages,
12439
+ watcher: this.watcherActive
12180
12440
  };
12181
- this.statsCache = { index: this.index, value };
12182
- return value;
12441
+ this.mapCache = { index: this.index, value };
12442
+ return { ...value, indexing: this.indexing };
12443
+ }
12444
+ stats() {
12445
+ const info = this.mapInfo();
12446
+ return info.indexed ? { files: info.files, symbols: info.symbols, tokens: info.tokens } : null;
12183
12447
  }
12184
12448
  getToolDefinitions() {
12185
12449
  return [this.createProjectMapTool()];
@@ -16801,6 +17065,15 @@ class SessionLogger {
16801
17065
  ...data
16802
17066
  });
16803
17067
  }
17068
+ logClaimRetry(data) {
17069
+ this.session?.appendLog({
17070
+ ts: new Date().toISOString(),
17071
+ type: "claim_retry",
17072
+ content: data.reason,
17073
+ kind: data.kind,
17074
+ iteration: data.iteration
17075
+ });
17076
+ }
16804
17077
  logCritic(event, data = {}) {
16805
17078
  this.session?.appendLog({
16806
17079
  ts: new Date().toISOString(),
@@ -19391,8 +19664,17 @@ function isFilePathLike(s) {
19391
19664
  return true;
19392
19665
  }
19393
19666
  function extractFileLikeTokens(text) {
19394
- const matches = text.match(FILE_TOKEN_RE) || [];
19395
- return [...new Set(matches.filter(isFilePathLike))];
19667
+ const out = new Set;
19668
+ const re = new RegExp(FILE_TOKEN_RE.source, "gi");
19669
+ for (const m of text.matchAll(re)) {
19670
+ const token = m[0];
19671
+ const after = text[(m.index ?? 0) + token.length];
19672
+ if (after === "(")
19673
+ continue;
19674
+ if (isFilePathLike(token))
19675
+ out.add(token);
19676
+ }
19677
+ return [...out];
19396
19678
  }
19397
19679
  function stripUrls(text) {
19398
19680
  return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
@@ -20720,6 +21002,7 @@ function createLoopState() {
20720
21002
  criticCalls: 0,
20721
21003
  criticRepairCycles: 0,
20722
21004
  mutationsSinceCritic: 0,
21005
+ criticHistoryMark: 0,
20723
21006
  seenCriticIssues: new Set,
20724
21007
  emptyResponseRetries: 0,
20725
21008
  emptyResponseExhausted: false,
@@ -20991,6 +21274,55 @@ class ReasoningEffortResolver {
20991
21274
  }
20992
21275
  var init_reasoning_resolver = () => {};
20993
21276
 
21277
+ // src/core/agent/tool-pairing.ts
21278
+ function repairToolCallPairing(messages) {
21279
+ const out = [];
21280
+ let pending = [];
21281
+ const closePending = () => {
21282
+ for (const call of pending) {
21283
+ out.push({
21284
+ role: "tool",
21285
+ name: call.name,
21286
+ content: UNRESOLVED_TOOL_RESULT,
21287
+ tool_call_id: call.id,
21288
+ success: false,
21289
+ blocked: true
21290
+ });
21291
+ }
21292
+ pending = [];
21293
+ };
21294
+ for (const msg of messages) {
21295
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
21296
+ const last = out[out.length - 1];
21297
+ if (last?.role === "assistant" && last.tool_calls?.length) {
21298
+ const merged = {
21299
+ ...last,
21300
+ tool_calls: [...last.tool_calls, ...msg.tool_calls]
21301
+ };
21302
+ out[out.length - 1] = merged;
21303
+ pending = merged.tool_calls.map((tc) => ({ id: tc.id, name: tc.function.name }));
21304
+ continue;
21305
+ }
21306
+ closePending();
21307
+ out.push(msg);
21308
+ pending = msg.tool_calls.map((tc) => ({ id: tc.id, name: tc.function.name }));
21309
+ continue;
21310
+ }
21311
+ if (msg.role === "tool" && msg.tool_call_id) {
21312
+ if (pending.some((c) => c.id === msg.tool_call_id)) {
21313
+ out.push(msg);
21314
+ pending = pending.filter((c) => c.id !== msg.tool_call_id);
21315
+ }
21316
+ continue;
21317
+ }
21318
+ closePending();
21319
+ out.push(msg);
21320
+ }
21321
+ closePending();
21322
+ return out;
21323
+ }
21324
+ var UNRESOLVED_TOOL_RESULT = "[Result unavailable]";
21325
+
20994
21326
  // src/core/agent/context-renderer.ts
20995
21327
  class ContextRenderer {
20996
21328
  lastCompactionShown = 0;
@@ -21481,6 +21813,7 @@ class HallucinationGate {
21481
21813
  return { action: "warn" };
21482
21814
  }
21483
21815
  if (result.status === "retry") {
21816
+ this.retractDraft(deps, result.kind ?? "retry", result.reason ?? "");
21484
21817
  return this.applyRetry(deps, textContent, result);
21485
21818
  }
21486
21819
  const claimRetry = this.checkSuccessClaim(deps, textContent);
@@ -21529,30 +21862,35 @@ class HallucinationGate {
21529
21862
  return this.retryClaim(deps, textContent, t("hall.claim_contradiction", {
21530
21863
  tool: contradiction.tool,
21531
21864
  snippet: contradiction.snippet
21532
- }), `Success-claim contradiction: ${contradiction.tool}: ${contradiction.snippet}`);
21865
+ }), `Success-claim contradiction: ${contradiction.tool}: ${contradiction.snippet}`, "success");
21533
21866
  }
21534
21867
  }
21535
21868
  if (state.artifactClaimRetries < MAX_CLAIM_CHECK_RETRIES && deps.artifacts && deps.artifacts.length > 0) {
21536
21869
  const artifact = checkArtifactClaims(text, deps.artifacts);
21537
21870
  if (artifact) {
21538
21871
  state.artifactClaimRetries++;
21539
- return this.retryClaim(deps, textContent, t("hall.claim_artifact", { path: artifact.path, detail: artifact.snippet }), `Artifact-claim contradiction: ${artifact.snippet}`);
21872
+ return this.retryClaim(deps, textContent, t("hall.claim_artifact", { path: artifact.path, detail: artifact.snippet }), `Artifact-claim contradiction: ${artifact.snippet}`, "artifact");
21540
21873
  }
21541
21874
  }
21542
21875
  if (state.outputClaimRetries < MAX_CLAIM_CHECK_RETRIES) {
21543
21876
  const unverified = checkUnverifiedOutput(text, evidence);
21544
21877
  if (unverified) {
21545
21878
  state.outputClaimRetries++;
21546
- return this.retryClaim(deps, textContent, t("hall.claim_output_unverified", { detail: unverified.snippet }), `Unverified output: ${unverified.snippet}`);
21879
+ return this.retryClaim(deps, textContent, t("hall.claim_output_unverified", { detail: unverified.snippet }), `Unverified output: ${unverified.snippet}`, "output");
21547
21880
  }
21548
21881
  }
21549
21882
  return null;
21550
21883
  }
21551
- retryClaim(deps, textContent, reason, logLine) {
21884
+ retractDraft(deps, kind, reason) {
21885
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21886
+ deps.slog?.logClaimRetry({ kind, reason, iteration: deps.state.iteration });
21887
+ }
21888
+ retryClaim(deps, textContent, reason, logLine, kind) {
21552
21889
  const { state, logger: logger3 } = deps;
21553
21890
  state.iterHallucinationRetry = true;
21554
21891
  state.suppressRepetitionRetry = true;
21555
21892
  logger3.diagnostic("warn", logLine);
21893
+ this.retractDraft(deps, kind, reason);
21556
21894
  if (textContent) {
21557
21895
  deps.contextManager.addMessage({ role: "assistant", content: textContent });
21558
21896
  }
@@ -21611,27 +21949,30 @@ function resolveArtifactStates(baseDir, response) {
21611
21949
  const rel = relative6(baseDir, abs);
21612
21950
  if (rel.startsWith("..") || isAbsolute4(rel))
21613
21951
  continue;
21614
- const state = resolveOneArtifact(token, abs);
21952
+ const state = resolveOneArtifact(token, baseDir);
21615
21953
  if (state)
21616
21954
  out.push(state);
21617
21955
  }
21618
21956
  return out;
21619
21957
  }
21620
- function resolveOneArtifact(token, abs) {
21958
+ function resolveOneArtifact(token, baseDir) {
21959
+ const found = findExistingFile(baseDir, token);
21960
+ if (!found)
21961
+ return { path: token, exists: false };
21621
21962
  let size;
21622
21963
  try {
21623
- const st = statSync8(abs);
21964
+ const st = statSync8(found);
21624
21965
  if (!st.isFile())
21625
21966
  return { path: token, exists: false };
21626
21967
  size = st.size;
21627
21968
  } catch {
21628
- return { path: token, exists: false };
21969
+ return { path: token, exists: true };
21629
21970
  }
21630
21971
  if (size > MAX_BYTES)
21631
21972
  return { path: token, exists: true };
21632
21973
  let content;
21633
21974
  try {
21634
- content = readFileSync25(abs, "utf-8");
21975
+ content = readFileSync25(found, "utf-8");
21635
21976
  } catch {
21636
21977
  return { path: token, exists: true };
21637
21978
  }
@@ -21641,6 +21982,7 @@ var MAX_PATHS = 10, MAX_BYTES, JSON_EXTS, XML_EXTS;
21641
21982
  var init_artifact_resolve = __esm(() => {
21642
21983
  init_js_identifiers();
21643
21984
  init_syntax_validator();
21985
+ init_auditor();
21644
21986
  MAX_BYTES = 500 * 1024;
21645
21987
  JSON_EXTS = new Set([".json", ".jsonc"]);
21646
21988
  XML_EXTS = new Set([".xml", ".drawio", ".svg", ".xaml", ".plist", ".iml"]);
@@ -21678,6 +22020,7 @@ class AuditGate {
21678
22020
  return { action: "exhausted", summary: audit.summary };
21679
22021
  }
21680
22022
  state.suppressRepetitionRetry = true;
22023
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21681
22024
  return { action: "reject" };
21682
22025
  }
21683
22026
  }
@@ -21689,10 +22032,17 @@ var init_audit_gate = __esm(() => {
21689
22032
  function issueSignature(issue) {
21690
22033
  return `${issue.severity}:${issue.claim}`.toLowerCase().replace(/\s+/g, " ").slice(0, 120);
21691
22034
  }
22035
+ function normalizedSignature(issue) {
22036
+ const body = `${issue.severity}:${issue.claim}`.toLowerCase().replace(/[^a-z0-9а-яё\s]/gi, " ").replace(/\s+/g, " ").trim().slice(0, 120);
22037
+ return `~${body}`;
22038
+ }
21692
22039
  function renderIssues(issues) {
21693
22040
  return issues.slice(0, 5).map((i) => `- [${i.severity}] ${i.problem || i.claim}${i.suggestion ? ` → ${i.suggestion}` : ""}`).join(`
21694
22041
  `);
21695
22042
  }
22043
+ function isRepeatedVerdict(state, issues) {
22044
+ return issues.length > 0 && issues.every((i) => state.seenCriticIssues.has(normalizedSignature(i)));
22045
+ }
21696
22046
 
21697
22047
  class CriticGate {
21698
22048
  opts;
@@ -21708,18 +22058,26 @@ class CriticGate {
21708
22058
  const verdict = await this.verifySafely(deps);
21709
22059
  if (!verdict || verdict.status === "ok")
21710
22060
  return "pass";
22061
+ const isRepeat = isRepeatedVerdict(deps.state, verdict.issues);
21711
22062
  const fresh = this.takeFreshIssues(deps.state, verdict.issues);
21712
22063
  if (fresh.length === 0)
21713
22064
  return "pass";
21714
22065
  this.logVerdict(deps, verdict, fresh.length);
21715
- const canRepair = verdict.status === "critical" && deps.beforeFinish && deps.state.criticRepairCycles < this.opts.maxRepairCycles;
21716
- this.inject(deps, verdict, fresh, canRepair);
22066
+ if (!this.shouldRepair(deps, verdict, isRepeat)) {
22067
+ this.injectRepeatOrAdvice(deps, fresh, isRepeat);
22068
+ return "pass";
22069
+ }
22070
+ this.injectRepair(deps, verdict, fresh);
21717
22071
  return "retry";
21718
22072
  }
22073
+ shouldRepair(deps, verdict, isRepeat) {
22074
+ return verdict.status === "critical" && deps.beforeFinish && deps.state.criticRepairCycles < this.opts.maxRepairCycles && !isRepeat;
22075
+ }
21719
22076
  async verifySafely(deps) {
21720
22077
  try {
21721
22078
  return await this.opts.verify(this.opts.buildPack(deps), this.opts.signal);
21722
- } catch {
22079
+ } catch (err) {
22080
+ deps.logger.diagnostic("warn", `Critic verify failed (fail-open): ${String(err?.message ?? err)}`);
21723
22081
  return null;
21724
22082
  }
21725
22083
  }
@@ -21728,9 +22086,8 @@ class CriticGate {
21728
22086
  return false;
21729
22087
  if (deps.state.criticCalls >= this.opts.maxCalls)
21730
22088
  return false;
21731
- if (!deps.beforeFinish && deps.state.mutationsSinceCritic < MUTATIONS_BEFORE_CHECK) {
22089
+ if (!deps.beforeFinish)
21732
22090
  return false;
21733
- }
21734
22091
  return deps.toolEvents.length !== 0;
21735
22092
  }
21736
22093
  takeFreshIssues(state, issues) {
@@ -21740,6 +22097,7 @@ class CriticGate {
21740
22097
  if (state.seenCriticIssues.has(sig))
21741
22098
  continue;
21742
22099
  state.seenCriticIssues.add(sig);
22100
+ state.seenCriticIssues.add(normalizedSignature(issue));
21743
22101
  fresh.push(issue);
21744
22102
  }
21745
22103
  return fresh;
@@ -21760,15 +22118,15 @@ class CriticGate {
21760
22118
  });
21761
22119
  deps.logger.diagnostic("info", `Critic ${verdict.status}: ${verdict.summary} (${count} issue(s))`);
21762
22120
  }
21763
- inject(deps, verdict, fresh, canRepair) {
21764
- if (!canRepair) {
21765
- deps.contextManager.addMessage({
21766
- role: "user",
21767
- content: `<system-summary>${t("critic.advice", { issues: renderIssues(fresh) })}</system-summary>`
21768
- });
21769
- return;
21770
- }
22121
+ injectRepeatOrAdvice(deps, fresh, isRepeat) {
22122
+ deps.contextManager.addMessage({
22123
+ role: "user",
22124
+ content: `<system-summary>${isRepeat ? t("critic.repeat", { issues: renderIssues(fresh) }) : t("critic.advice", { issues: renderIssues(fresh) })}</system-summary>`
22125
+ });
22126
+ }
22127
+ injectRepair(deps, verdict, fresh) {
21771
22128
  deps.state.criticRepairCycles++;
22129
+ deps.onMeta?.(t("ui.draft_retracted"), "retract");
21772
22130
  deps.slog.logCritic("critic_escalation", {
21773
22131
  summary: verdict.summary,
21774
22132
  cycle: deps.state.criticRepairCycles
@@ -21782,9 +22140,11 @@ class CriticGate {
21782
22140
  });
21783
22141
  }
21784
22142
  }
21785
- function gatherTurnToolEvents(contextManager) {
22143
+ function gatherTurnToolEvents(contextManager, fromIndex = 0) {
21786
22144
  const events = [];
21787
- for (const m of contextManager.getActiveHistory()) {
22145
+ const history = contextManager.getActiveHistory();
22146
+ const start = Math.max(0, Math.min(fromIndex, history.length));
22147
+ for (const m of history.slice(start)) {
21788
22148
  if (m.role !== "tool" || !m.name)
21789
22149
  continue;
21790
22150
  const output = typeof m.content === "string" ? m.content.slice(-MAX_EVENT_OUTPUT_CHARS) : undefined;
@@ -21792,7 +22152,7 @@ function gatherTurnToolEvents(contextManager) {
21792
22152
  }
21793
22153
  return events;
21794
22154
  }
21795
- var MUTATIONS_BEFORE_CHECK = 3, MAX_EVENT_OUTPUT_CHARS = 600;
22155
+ var MAX_EVENT_OUTPUT_CHARS = 600;
21796
22156
  var init_critic_gate = __esm(() => {
21797
22157
  init_i18n();
21798
22158
  });
@@ -21979,7 +22339,9 @@ ${f.excerpt}`);
21979
22339
  pack.toolDigest || "(empty)",
21980
22340
  files.length > 0 ? `FILE CONTENTS:
21981
22341
  ${files.join(`
21982
- `)}` : ""
22342
+ `)}` : "",
22343
+ pack.answerExcerpt ? `ANSWER (the agent's chat delivery — may satisfy the task without files):
22344
+ ${pack.answerExcerpt}` : ""
21983
22345
  ].filter(Boolean).join(`
21984
22346
  `);
21985
22347
  }
@@ -22033,6 +22395,7 @@ Rules:
22033
22395
  - Flag ONLY issues that can be verified from the evidence provided. Do not speculate or invent.
22034
22396
  - Do not nitpick style, wording, or preferences unless the user's task demanded it.
22035
22397
  - If the evidence is insufficient to judge, return status "ok" (prefer silence over noise).
22398
+ - 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.
22036
22399
  - "critical" = the task's stated result is demonstrably not delivered. "concern" = a concrete, checkable gap. "info" = minor.
22037
22400
  - Every issue must reference concrete evidence (file, command, output).
22038
22401
  Reply with JSON ONLY:
@@ -22047,8 +22410,12 @@ var init_client = __esm(() => {
22047
22410
  // src/modules/critic/provider.ts
22048
22411
  function buildCriticProvider(critic, config, defaultProvider, deps) {
22049
22412
  const model = critic?.model?.trim();
22050
- if (!model)
22413
+ if (!model) {
22414
+ if (critic?.provider && Object.keys(critic.provider).length > 0) {
22415
+ deps?.logger?.warn("critic.provider is set without critic.model — provider ignored, using active provider");
22416
+ }
22051
22417
  return defaultProvider;
22418
+ }
22052
22419
  try {
22053
22420
  const providerCfg = critic?.provider ?? {
22054
22421
  type: "openai-compat",
@@ -22106,7 +22473,8 @@ function buildEvidencePack(input) {
22106
22473
  commands,
22107
22474
  toolDigest: digest.slice(-MAX_DIGEST_LINES).join(`
22108
22475
  `),
22109
- fileExcerpts: input.baseDir ? readExcerpts(input.baseDir, [...changedFiles]) : undefined
22476
+ fileExcerpts: input.baseDir ? readExcerpts(input.baseDir, [...changedFiles]) : undefined,
22477
+ answerExcerpt: input.answerText ? redactContextSecrets(input.answerText).slice(0, MAX_ANSWER_CHARS) : undefined
22110
22478
  };
22111
22479
  }
22112
22480
  function digestLine(ev) {
@@ -22127,7 +22495,7 @@ function commandOf(ev) {
22127
22495
  return;
22128
22496
  return {
22129
22497
  command: ev.args.command,
22130
- exitCode: ev.ok === false ? 1 : 0,
22498
+ exitCode: ev.ok === false ? 1 : undefined,
22131
22499
  output: ev.output?.slice(-MAX_DIGEST_OUTPUT_CHARS)
22132
22500
  };
22133
22501
  }
@@ -22145,9 +22513,10 @@ function readExcerpts(baseDir, paths) {
22145
22513
  }
22146
22514
  return out;
22147
22515
  }
22148
- var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200;
22516
+ var MUTATING_TOOLS, MAX_DIGEST_LINES = 40, MAX_DIGEST_OUTPUT_CHARS = 300, MAX_EXCERPT_FILES = 5, MAX_EXCERPT_CHARS = 1200, MAX_ANSWER_CHARS = 2000;
22149
22517
  var init_evidence = __esm(() => {
22150
22518
  init_tool_output();
22519
+ init_context_log();
22151
22520
  MUTATING_TOOLS = new Set(["write_file", "edit_file", "delete_file", "move_file", "create_dir"]);
22152
22521
  });
22153
22522
 
@@ -22842,6 +23211,7 @@ class Agent {
22842
23211
  } = this.deps;
22843
23212
  this.abortController = new AbortController;
22844
23213
  const state = createLoopState();
23214
+ state.criticHistoryMark = contextManager.getActiveHistory().length;
22845
23215
  const compactionService = new CompactionService({
22846
23216
  contextManager,
22847
23217
  logger: logger3,
@@ -22899,7 +23269,8 @@ class Agent {
22899
23269
  task: d.task,
22900
23270
  planSummary: d.planSummary,
22901
23271
  toolEvents: d.toolEvents,
22902
- baseDir: this.deps.baseDir
23272
+ baseDir: this.deps.baseDir,
23273
+ answerText: d.answerText
22903
23274
  }),
22904
23275
  signal: this.abortController?.signal
22905
23276
  });
@@ -22927,7 +23298,7 @@ class Agent {
22927
23298
  });
22928
23299
  compactionService.compactIfNeeded(state);
22929
23300
  this.refreshSystemPrompt();
22930
- const history = contextManager.getActiveHistory();
23301
+ const history = repairToolCallPairing(contextManager.getActiveHistory());
22931
23302
  const promptSnapshot = buildPromptSnapshot(history, allToolsForBudget);
22932
23303
  state.prefixDelta = diffPrompt(state.prevPrompt, promptSnapshot, estimateTokens);
22933
23304
  state.prevPrompt = promptSnapshot;
@@ -23180,7 +23551,8 @@ class Agent {
23180
23551
  onMeta,
23181
23552
  onChunk,
23182
23553
  verbose: resolveVerbosity(this.deps.config.ui) === "verbose",
23183
- artifacts: resolveArtifactStates(this.deps.baseDir, textContent)
23554
+ artifacts: resolveArtifactStates(this.deps.baseDir, textContent),
23555
+ slog
23184
23556
  });
23185
23557
  if (verdict.action === "block") {
23186
23558
  return {
@@ -23233,8 +23605,10 @@ class Agent {
23233
23605
  slog,
23234
23606
  task: input,
23235
23607
  planSummary: this.deps.planSummary?.() ?? undefined,
23236
- toolEvents: gatherTurnToolEvents(contextManager),
23237
- beforeFinish: true
23608
+ toolEvents: gatherTurnToolEvents(contextManager, state.criticHistoryMark),
23609
+ beforeFinish: true,
23610
+ answerText: state.mutationsSinceCritic === 0 ? textContent : undefined,
23611
+ onMeta
23238
23612
  });
23239
23613
  if (criticAction === "retry") {
23240
23614
  state.suppressRepetitionRetry = true;
@@ -23246,7 +23620,8 @@ class Agent {
23246
23620
  maxIterations: config.maxToolIterations,
23247
23621
  logger: logger3,
23248
23622
  contextManager,
23249
- slog
23623
+ slog,
23624
+ onMeta
23250
23625
  });
23251
23626
  if (auditVerdict === null || auditVerdict.action === "pass") {
23252
23627
  state.finalAnswerAccepted = true;
@@ -23351,6 +23726,13 @@ class Agent {
23351
23726
  if (this.deps.configRef)
23352
23727
  this.deps.configRef.current = config;
23353
23728
  this.costTracker.setModel(config.model);
23729
+ this.applyReasoningStrategy(config.provider);
23730
+ }
23731
+ applyReasoningStrategy(provider) {
23732
+ const strategy = reasoningStrategyForProvider(provider);
23733
+ this.deps.reasoningStrategy = strategy;
23734
+ if (strategy === "none")
23735
+ this.probePassed = false;
23354
23736
  }
23355
23737
  setProvider(name, model) {
23356
23738
  const { manager, provider } = buildActiveProvider(this.deps.config, this.deps.logger, {
@@ -23359,6 +23741,7 @@ class Agent {
23359
23741
  manager.switch(name, model);
23360
23742
  const providerCfg = manager.toConfig();
23361
23743
  this.deps.config.provider = providerCfg;
23744
+ this.applyReasoningStrategy(providerCfg);
23362
23745
  this.deps.llmProvider = provider;
23363
23746
  this.deps.getProviderName = () => manager.activeName;
23364
23747
  this.deps.toolExecutor.updateProvider(provider);
@@ -30508,6 +30891,33 @@ class PlanStore {
30508
30891
  activePath() {
30509
30892
  return join44(this.plansDir, "active.json");
30510
30893
  }
30894
+ sessionsDir() {
30895
+ return join44(this.plansDir, "sessions");
30896
+ }
30897
+ sessionActivePath(sessionId) {
30898
+ if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
30899
+ throw new Error(`invalid session id: ${sessionId}`);
30900
+ }
30901
+ return join44(this.sessionsDir(), sessionId, "active.json");
30902
+ }
30903
+ saveSessionActive(sessionId, plan) {
30904
+ const p = this.sessionActivePath(sessionId);
30905
+ const dir = join44(this.sessionsDir(), sessionId);
30906
+ if (!existsSync50(dir))
30907
+ mkdirSync24(dir, { recursive: true });
30908
+ writePlanFile(p, plan);
30909
+ }
30910
+ loadSessionActive(sessionId) {
30911
+ const p = this.sessionActivePath(sessionId);
30912
+ if (!existsSync50(p))
30913
+ return null;
30914
+ return readPlanFile(p, this.baseDir);
30915
+ }
30916
+ clearSessionActive(sessionId) {
30917
+ const p = this.sessionActivePath(sessionId);
30918
+ if (existsSync50(p))
30919
+ rmSync3(p, { force: true });
30920
+ }
30511
30921
  saveActive(plan) {
30512
30922
  writePlanFile(this.activePath(), plan);
30513
30923
  }
@@ -31033,7 +31443,7 @@ Last compile error: ${first[1]}`;
31033
31443
  }
31034
31444
  }
31035
31445
  if (result.success && FS_MUTATING_TOOLS.has(call.name)) {
31036
- deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
31446
+ deps.advancePlanIfStepComplete(ctx.sessionLog);
31037
31447
  }
31038
31448
  deps.maybeSearchError(ctx, call);
31039
31449
  }
@@ -31982,6 +32392,7 @@ function planPathToken(p) {
31982
32392
  class ExecutionModule {
31983
32393
  name = "execution";
31984
32394
  tracker = null;
32395
+ sessionId = null;
31985
32396
  verifier;
31986
32397
  stuckDetector;
31987
32398
  auditor;
@@ -32018,9 +32429,20 @@ class ExecutionModule {
32018
32429
  },
32019
32430
  set current(v) {
32020
32431
  self.tracker = v;
32432
+ self.flushSessionPlan();
32021
32433
  }
32022
32434
  };
32023
32435
  })();
32436
+ flushSessionPlan() {
32437
+ if (!this.sessionId)
32438
+ return;
32439
+ try {
32440
+ if (this.tracker)
32441
+ this.store.saveSessionActive(this.sessionId, this.tracker.getPlan());
32442
+ else
32443
+ this.store.clearSessionActive(this.sessionId);
32444
+ } catch {}
32445
+ }
32024
32446
  constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = EXECUTION_ERROR_SEARCH_MIN_INTERVAL_MS) {
32025
32447
  this.baseDir = baseDir;
32026
32448
  this.verifier = new StepVerifier(baseDir);
@@ -32034,9 +32456,69 @@ class ExecutionModule {
32034
32456
  this.tracker = new PlanTracker(plan);
32035
32457
  this.tracker.syncCurrentStep();
32036
32458
  this.store.saveActive(plan);
32459
+ if (this.sessionId) {
32460
+ try {
32461
+ this.store.saveSessionActive(this.sessionId, plan);
32462
+ } catch {}
32463
+ }
32037
32464
  this.completedPlan = null;
32038
32465
  this._auditSkipsRemaining = 0;
32039
32466
  }
32467
+ switchSession(id) {
32468
+ const prev = this.sessionId;
32469
+ if (prev === id)
32470
+ return;
32471
+ if (this.tracker) {
32472
+ try {
32473
+ this.store.saveSessionActive(prev ?? id ?? "", this.tracker.getPlan());
32474
+ } catch {}
32475
+ } else if (prev) {
32476
+ try {
32477
+ this.store.clearSessionActive(prev);
32478
+ } catch {}
32479
+ }
32480
+ this.sessionId = id;
32481
+ this.completedPlan = null;
32482
+ if (!id) {
32483
+ this.tracker = null;
32484
+ this._auditSkipsRemaining = 0;
32485
+ return;
32486
+ }
32487
+ let plan = null;
32488
+ try {
32489
+ plan = this.store.loadSessionActive(id);
32490
+ } catch {
32491
+ plan = null;
32492
+ }
32493
+ if (!plan) {
32494
+ this.tracker = null;
32495
+ this._auditSkipsRemaining = 0;
32496
+ return;
32497
+ }
32498
+ const restored = new PlanTracker(plan);
32499
+ if (restored.isComplete()) {
32500
+ this.store.archivePlan(plan);
32501
+ try {
32502
+ this.store.clearSessionActive(id);
32503
+ } catch {}
32504
+ this.tracker = null;
32505
+ return;
32506
+ }
32507
+ this.tracker = restored;
32508
+ this.tracker.syncCurrentStep();
32509
+ this._auditSkipsRemaining = 1;
32510
+ }
32511
+ forgetSession(id) {
32512
+ try {
32513
+ this.store.clearSessionActive(id);
32514
+ } catch {}
32515
+ if (this.sessionId === id) {
32516
+ this.sessionId = null;
32517
+ this.tracker = null;
32518
+ this.completedPlan = null;
32519
+ this._auditSkipsRemaining = 0;
32520
+ }
32521
+ }
32040
32522
  setHallucinationDetector(detector) {
32041
32523
  this.hallucinationDetector = detector;
32042
32524
  }
@@ -32167,6 +32649,7 @@ class ExecutionModule {
32167
32649
  const done2 = plan2.steps.filter((s) => s.status === "done").length;
32168
32650
  if (autoClosed2 > 0) {
32169
32651
  this.store.saveActive(plan2);
32652
+ this.flushSessionPlan();
32170
32653
  }
32171
32654
  return {
32172
32655
  passed: audit2.passed && pendingSteps2.length === 0,
@@ -32185,6 +32668,7 @@ class ExecutionModule {
32185
32668
  const autoClosed = this.autoCloseEvidenceSteps(plan);
32186
32669
  if (autoClosed > 0) {
32187
32670
  this.store.saveActive(plan);
32671
+ this.flushSessionPlan();
32188
32672
  }
32189
32673
  const audit = await this.auditor.audit(plan);
32190
32674
  const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
@@ -32261,7 +32745,7 @@ class ExecutionModule {
32261
32745
  state: this.state,
32262
32746
  store: this.store,
32263
32747
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
32264
- advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
32748
+ advancePlanIfStepComplete: (sl) => this.advancePlanIfStepComplete(sl),
32265
32749
  maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
32266
32750
  });
32267
32751
  }
@@ -32330,7 +32814,7 @@ class ExecutionModule {
32330
32814
  }
32331
32815
  return out;
32332
32816
  }
32333
- advancePlanIfStepComplete(contextManager, sessionLog) {
32817
+ advancePlanIfStepComplete(sessionLog) {
32334
32818
  const step = this.tracker?.getCurrentStep();
32335
32819
  if (!step)
32336
32820
  return;
@@ -32352,19 +32836,17 @@ class ExecutionModule {
32352
32836
  ];
32353
32837
  const hasLockFile = lockFiles.some((f) => existsSync51(resolve22(this.baseDir, f)));
32354
32838
  if (!hasLockFile) {
32355
- if (contextManager) {
32356
- const hints = this.state.depsGateHints.get(step.id) || 0;
32357
- this.state.depsGateHints.set(step.id, hints + 1);
32358
- const force = hints >= 1;
32359
- const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
32360
- step: String(step.id),
32361
- description: step.description
32362
- });
32363
- contextManager.addMessage({
32364
- role: "user",
32365
- content: `<system-summary>${content}</system-summary>`
32366
- });
32367
- }
32839
+ const hints = this.state.depsGateHints.get(step.id) || 0;
32840
+ this.state.depsGateHints.set(step.id, hints + 1);
32841
+ const force = hints >= 1;
32842
+ const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
32843
+ step: String(step.id),
32844
+ description: step.description
32845
+ });
32846
+ this.pendingMessages.push({
32847
+ role: "user",
32848
+ content: `<system-summary>${content}</system-summary>`
32849
+ });
32368
32850
  return;
32369
32851
  }
32370
32852
  }
@@ -32388,8 +32870,8 @@ class ExecutionModule {
32388
32870
  }
32389
32871
  } catch {}
32390
32872
  }
32391
- if (emptyFiles.length > 0 && contextManager) {
32392
- contextManager.addMessage({
32873
+ if (emptyFiles.length > 0) {
32874
+ this.pendingMessages.push({
32393
32875
  role: "user",
32394
32876
  content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
32395
32877
  });
@@ -32400,25 +32882,25 @@ class ExecutionModule {
32400
32882
  const hadNext = this.tracker?.advance() ?? false;
32401
32883
  if (this.tracker) {
32402
32884
  this.store.saveActive(this.tracker.getPlan());
32885
+ this.flushSessionPlan();
32403
32886
  sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
32404
32887
  }
32405
- if (contextManager) {
32406
- const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
32407
- const nextMsg = nextStep ? t("exec.step_gate_ok", {
32408
- step: String(step.id),
32409
- nextStep: String(nextStep.id),
32410
- nextDesc: nextStep.description
32411
- }) : t("exec.step_gate_last", { step: String(step.id) });
32412
- contextManager.addMessage({
32413
- role: "user",
32414
- content: `<system-summary>${nextMsg}</system-summary>`
32415
- });
32416
- }
32888
+ const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
32889
+ const nextMsg = nextStep ? t("exec.step_gate_ok", {
32890
+ step: String(step.id),
32891
+ nextStep: String(nextStep.id),
32892
+ nextDesc: nextStep.description
32893
+ }) : t("exec.step_gate_last", { step: String(step.id) });
32894
+ this.pendingMessages.push({
32895
+ role: "user",
32896
+ content: `<system-summary>${nextMsg}</system-summary>`
32897
+ });
32417
32898
  if (this.tracker?.isComplete()) {
32418
32899
  const completed = this.tracker.getPlan();
32419
32900
  this.store.archivePlan(completed);
32420
32901
  this.recordCompleted(completed);
32421
32902
  this.tracker = null;
32903
+ this.flushSessionPlan();
32422
32904
  sessionLog?.plan("auto-archive", `Plan ${completed.id} complete — archived`);
32423
32905
  }
32424
32906
  }
@@ -33777,6 +34259,9 @@ async function registerCoreModules(input, moduleRegistry) {
33777
34259
  const { config, logger: logger5, dir, baseDir, hallucinationDetector } = input;
33778
34260
  const execModule = new ExecutionModule(baseDir, config.stuckThreshold, config.errorWebSearch?.threshold ?? 5);
33779
34261
  execModule.restorePlan();
34262
+ try {
34263
+ execModule.switchSession(input.sessionManager.getActive());
34264
+ } catch {}
33780
34265
  moduleRegistry.register(execModule);
33781
34266
  execModule.setHallucinationDetector(hallucinationDetector);
33782
34267
  const sessionModule = new SessionModule(input.sessionManager);
@@ -34746,6 +35231,40 @@ var init_box = __esm(() => {
34746
35231
  });
34747
35232
 
34748
35233
  // src/modules/setup/answers.ts
35234
+ function securityDetailFromConfig(s) {
35235
+ return {
35236
+ bash: {
35237
+ blacklist: [...s.bash.blacklist],
35238
+ whitelist: [...s.bash.whitelist],
35239
+ logCommands: s.bash.logCommands
35240
+ },
35241
+ paths: { denied: [...s.paths.denied], allowed: [...s.paths.allowed] },
35242
+ flags: {
35243
+ dangerousFlags: [...s.bash.dangerousFlags],
35244
+ dangerousOperators: [...s.bash.dangerousOperators]
35245
+ },
35246
+ network: {
35247
+ enabled: s.network.enabled,
35248
+ deniedDomains: [...s.network.deniedDomains],
35249
+ allowedDomains: [...s.network.allowedDomains],
35250
+ requestTimeout: s.network.requestTimeout,
35251
+ maxResponseSize: s.network.maxResponseSize
35252
+ },
35253
+ advanced: {
35254
+ maxRecursionDepth: s.maxRecursionDepth,
35255
+ maxFileOperations: s.maxFileOperations,
35256
+ maxRequestsPerMinute: s.rateLimits.maxRequestsPerMinute,
35257
+ maxParallelTasks: s.rateLimits.maxParallelTasks,
35258
+ contentScanEnabled: s.contentScan.enabled,
35259
+ sessionEncryptionEnabled: s.sessionEncryption.enabled,
35260
+ auditNotifierEnabled: s.auditNotifier.enabled,
35261
+ auditNotifierMinSeverity: s.auditNotifier.minSeverity ?? "medium",
35262
+ auditNotifierEventTypes: [...s.auditNotifier.eventTypes ?? []],
35263
+ auditNotifierMaxRetries: s.auditNotifier.maxRetries ?? 3,
35264
+ auditNotifierWebhookTimeout: s.auditNotifier.webhookTimeout ?? 5000
35265
+ }
35266
+ };
35267
+ }
34749
35268
  function buildSetupDisplay() {
34750
35269
  const providers = BUILTIN_PROVIDERS.map((spec) => {
34751
35270
  const option = { type: spec.type, label: spec.label };
@@ -35135,20 +35654,62 @@ function applyAnswersToConfig(config, a) {
35135
35654
  config.provider.entries = next;
35136
35655
  config.provider.active = updated.label || updated.type;
35137
35656
  }
35138
- if (config.security) {
35139
- config.security.enabled = true;
35140
- config.security.bash.enabled = true;
35141
- config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
35142
- if (a.securityBashBlock) {
35143
- config.security.bash.blacklist = [
35144
- ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
35145
- ];
35146
- }
35147
- config.security.paths.enabled = a.securityPathsRestrict;
35148
- }
35657
+ applySecurityAnswers(config, a);
35149
35658
  return config;
35150
35659
  }
35151
- var SECURITY_BASH_BLOCKLIST;
35660
+ function applySecurityAnswers(config, a) {
35661
+ if (!config.security)
35662
+ return;
35663
+ config.security.enabled = true;
35664
+ config.security.bash.enabled = true;
35665
+ config.security.bash.blockDangerousFlags = a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
35666
+ if (a.securityBashBlock) {
35667
+ config.security.bash.blacklist = [
35668
+ ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST])
35669
+ ];
35670
+ }
35671
+ config.security.paths.enabled = a.securityPathsRestrict;
35672
+ if (a.securityDetail)
35673
+ applySecurityDetail(config.security, a.securityDetail);
35674
+ }
35675
+ function readPath(obj, path) {
35676
+ let cur = obj;
35677
+ for (const key of path.split(".")) {
35678
+ if (cur === null || typeof cur !== "object")
35679
+ return;
35680
+ cur = cur[key];
35681
+ }
35682
+ return cur;
35683
+ }
35684
+ function writePath(obj, path, value) {
35685
+ const keys = path.split(".");
35686
+ let cur = obj;
35687
+ for (let i = 0;i < keys.length - 1; i++) {
35688
+ if (cur[keys[i]] === null || typeof cur[keys[i]] !== "object")
35689
+ cur[keys[i]] = {};
35690
+ cur = cur[keys[i]];
35691
+ }
35692
+ cur[keys[keys.length - 1]] = value;
35693
+ }
35694
+ function isFieldValue(v, kind) {
35695
+ if (kind === "list")
35696
+ return Array.isArray(v) && v.every((x) => typeof x === "string");
35697
+ if (kind === "bool")
35698
+ return typeof v === "boolean";
35699
+ if (kind === "int")
35700
+ return typeof v === "number" && Number.isInteger(v) && v > 0;
35701
+ return typeof v === "string" && SEVERITIES2.includes(v);
35702
+ }
35703
+ function applySecurityDetail(security, detail) {
35704
+ const target = security;
35705
+ for (const field of DETAIL_FIELDS) {
35706
+ const value = readPath(detail, field.from);
35707
+ if (!isFieldValue(value, field.kind))
35708
+ continue;
35709
+ writePath(target, field.to, Array.isArray(value) ? [...value] : value);
35710
+ }
35711
+ }
35712
+ var SECURITY_BASH_BLOCKLIST, SEVERITIES2, DETAIL_FIELDS;
35152
35713
  var init_apply = __esm(() => {
35153
35714
  SECURITY_BASH_BLOCKLIST = [
35154
35715
  "rm",
@@ -35168,6 +35729,32 @@ var init_apply = __esm(() => {
35168
35729
  "shutdown",
35169
35730
  "reboot"
35170
35731
  ];
35732
+ SEVERITIES2 = ["low", "medium", "high", "critical"];
35733
+ DETAIL_FIELDS = [
35734
+ { from: "bash.blacklist", to: "bash.blacklist", kind: "list" },
35735
+ { from: "bash.whitelist", to: "bash.whitelist", kind: "list" },
35736
+ { from: "bash.logCommands", to: "bash.logCommands", kind: "bool" },
35737
+ { from: "paths.denied", to: "paths.denied", kind: "list" },
35738
+ { from: "paths.allowed", to: "paths.allowed", kind: "list" },
35739
+ { from: "flags.dangerousFlags", to: "bash.dangerousFlags", kind: "list" },
35740
+ { from: "flags.dangerousOperators", to: "bash.dangerousOperators", kind: "list" },
35741
+ { from: "network.enabled", to: "network.enabled", kind: "bool" },
35742
+ { from: "network.deniedDomains", to: "network.deniedDomains", kind: "list" },
35743
+ { from: "network.allowedDomains", to: "network.allowedDomains", kind: "list" },
35744
+ { from: "network.requestTimeout", to: "network.requestTimeout", kind: "int" },
35745
+ { from: "network.maxResponseSize", to: "network.maxResponseSize", kind: "int" },
35746
+ { from: "advanced.maxRecursionDepth", to: "maxRecursionDepth", kind: "int" },
35747
+ { from: "advanced.maxFileOperations", to: "maxFileOperations", kind: "int" },
35748
+ { from: "advanced.maxRequestsPerMinute", to: "rateLimits.maxRequestsPerMinute", kind: "int" },
35749
+ { from: "advanced.maxParallelTasks", to: "rateLimits.maxParallelTasks", kind: "int" },
35750
+ { from: "advanced.contentScanEnabled", to: "contentScan.enabled", kind: "bool" },
35751
+ { from: "advanced.sessionEncryptionEnabled", to: "sessionEncryption.enabled", kind: "bool" },
35752
+ { from: "advanced.auditNotifierEnabled", to: "auditNotifier.enabled", kind: "bool" },
35753
+ { from: "advanced.auditNotifierMinSeverity", to: "auditNotifier.minSeverity", kind: "severity" },
35754
+ { from: "advanced.auditNotifierEventTypes", to: "auditNotifier.eventTypes", kind: "list" },
35755
+ { from: "advanced.auditNotifierMaxRetries", to: "auditNotifier.maxRetries", kind: "int" },
35756
+ { from: "advanced.auditNotifierWebhookTimeout", to: "auditNotifier.webhookTimeout", kind: "int" }
35757
+ ];
35171
35758
  });
35172
35759
 
35173
35760
  // src/cli/setup-order.ts
@@ -35914,6 +36501,14 @@ function getConfigValue(config, name) {
35914
36501
  const value = def.get(config);
35915
36502
  return { ok: true, key: def.key, def, value, display: formatConfigValue(value) };
35916
36503
  }
36504
+ function mirrorActiveEntryFields(provider, activeName) {
36505
+ const entry = (provider.entries ?? []).find((e) => e.label === activeName || e.type === activeName);
36506
+ if (!entry)
36507
+ return;
36508
+ provider.type = entry.type;
36509
+ provider.baseUrl = entry.baseUrl;
36510
+ provider.apiKey = entry.apiKey;
36511
+ }
35917
36512
  function applyProviderModelMemory(config, key, value) {
35918
36513
  const provider = config.provider;
35919
36514
  const activeName = provider.active ?? provider.type;
@@ -35927,6 +36522,7 @@ function applyProviderModelMemory(config, key, value) {
35927
36522
  const remembered = provider.models?.[String(value)];
35928
36523
  if (remembered)
35929
36524
  config.model = remembered;
36525
+ mirrorActiveEntryFields(provider, String(value));
35930
36526
  }
35931
36527
  }
35932
36528
  function setConfigValue(config, name, raw) {
@@ -45457,8 +46053,9 @@ function registerProviderCommands(ctx) {
45457
46053
  const list2 = () => {
45458
46054
  const providers = ctx.agent.listProviders();
45459
46055
  if (providers.length === 0) {
45460
- ctx.output.writeLine(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
45461
- ctx.output.writeLine(` ${ctx.config.provider.baseUrl}`);
46056
+ const endpoint = activeProviderEndpoint(ctx.config.provider);
46057
+ ctx.output.writeLine(`${t("repl.provider_current")} ${endpoint.type}`);
46058
+ ctx.output.writeLine(` ${endpoint.baseUrl}`);
45462
46059
  return;
45463
46060
  }
45464
46061
  ctx.output.writeLine(t("repl.provider_current"));
@@ -45720,7 +46317,8 @@ function registerAgentCommands(ctx) {
45720
46317
  ctx.output.writeLine(pc.green(t("repl.reloaded")));
45721
46318
  ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
45722
46319
  ctx.output.writeLine(`${t("repl.context")} ${ctx.config.contextWindow}`);
45723
- ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
46320
+ const endpoint = activeProviderEndpoint(ctx.config.provider);
46321
+ ctx.output.writeLine(`${t("repl.provider")} ${endpoint.type} @ ${endpoint.baseUrl}`);
45724
46322
  }
45725
46323
  });
45726
46324
  ctx.registerCommand({
@@ -45826,6 +46424,10 @@ function registerSessionCommands(ctx) {
45826
46424
  const name = args.join(" ") || undefined;
45827
46425
  const meta = ctx.sessionManager.create(name);
45828
46426
  ctx.agent.clearContext();
46427
+ const exec = ctx.agent.getModule("execution");
46428
+ try {
46429
+ exec?.switchSession(meta.id);
46430
+ } catch {}
45829
46431
  ctx.output.clearScreen();
45830
46432
  ctx.output.writeLine(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
45831
46433
  ctx.output.writeLine(pc.dim(` ${t("session.chat_cleared")}
@@ -45860,6 +46462,12 @@ function registerSessionCommands(ctx) {
45860
46462
  ctx.sessionManager.setActive(match.id);
45861
46463
  const history = ctx.sessionManager.loadHistory();
45862
46464
  ctx.agent.setContext(history);
46465
+ const exec = ctx.agent.getModule("execution");
46466
+ let planLine = null;
46467
+ try {
46468
+ exec?.switchSession(match.id);
46469
+ planLine = exec?.getPlanSummary() ?? null;
46470
+ } catch {}
45863
46471
  ctx.output.clearScreen();
45864
46472
  ctx.output.writeLine(pc.bold(pc.green(t("session.resumed", { name: match.name }))) + " " + pc.dim(`(${match.id.slice(0, 12)})`) + " — " + match.messageCount + " " + t("repl.msgs"));
45865
46473
  ctx.output.writeLine(pc.dim("─".repeat(50)));
@@ -45882,6 +46490,7 @@ function registerSessionCommands(ctx) {
45882
46490
  }
45883
46491
  ctx.output.writeLine(pc.dim("─".repeat(50)));
45884
46492
  ctx.output.writeLine(pc.dim(` ${t("session.chat_loaded")}`));
46493
+ ctx.output.writeLine(pc.dim(` ◇ ${planLine ?? t("plan.no_active")}`));
45885
46494
  }
45886
46495
  });
45887
46496
  ctx.registerCommand({
@@ -45921,6 +46530,9 @@ function registerSessionCommands(ctx) {
45921
46530
  return;
45922
46531
  }
45923
46532
  ctx.sessionManager.delete(match.id);
46533
+ try {
46534
+ ctx.agent.getModule("execution")?.forgetSession(match.id);
46535
+ } catch {}
45924
46536
  ctx.output.writeLine(`${t("session.deleted", { id: match.id })}: ${match.name}`);
45925
46537
  }
45926
46538
  });
@@ -46031,6 +46643,7 @@ var init_repl_commands = __esm(() => {
46031
46643
  init_setup_order();
46032
46644
  init_config2();
46033
46645
  init_presets();
46646
+ init_manager();
46034
46647
  init_budget();
46035
46648
  init_registry2();
46036
46649
  init_export();
@@ -47083,10 +47696,12 @@ function buildModelCommands(program2) {
47083
47696
  const { config } = await bootstrap();
47084
47697
  getDefaultChannel().writeLine(`${t("cli.current_model")} ${config.model}`);
47085
47698
  const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_create(), exports_create));
47086
- const provider = createProvider2(config.provider.type, {
47699
+ const { activeProviderEndpoint: activeProviderEndpoint2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
47700
+ const endpoint = activeProviderEndpoint2(config.provider);
47701
+ const provider = createProvider2(endpoint.type, {
47087
47702
  model: config.model,
47088
- baseUrl: config.provider.baseUrl,
47089
- apiKey: config.provider.apiKey,
47703
+ baseUrl: endpoint.baseUrl,
47704
+ apiKey: endpoint.apiKey,
47090
47705
  contextWindow: config.contextWindow
47091
47706
  });
47092
47707
  const spinner = (await Promise.resolve().then(() => (init_spinner(), exports_spinner))).Spinner;
@@ -49692,6 +50307,7 @@ function adaptActivePlan(plan) {
49692
50307
  // src/cli/repl.ts
49693
50308
  init_repl_commands();
49694
50309
  init_registry2();
50310
+ init_manager();
49695
50311
 
49696
50312
  // src/modules/lsp/probe.ts
49697
50313
  init_client3();
@@ -50692,7 +51308,8 @@ ${divider()}`);
50692
51308
  const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
50693
51309
  const histBudget = ctx - sysBudget - resBudget;
50694
51310
  row(t("repl.model"), pc.white(this.config.model));
50695
- row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
51311
+ const endpoint = activeProviderEndpoint(this.config.provider);
51312
+ row(t("repl.provider"), `${endpoint.type} → ${pc.dim(endpoint.baseUrl)}`);
50696
51313
  row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
50697
51314
  const si = this.agent.getSystemPromptInfo();
50698
51315
  row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
@@ -51607,6 +52224,11 @@ function makeForwarder(s, sink = (ev) => broadcast(ev, s), opts = {}) {
51607
52224
  schedule();
51608
52225
  },
51609
52226
  onMeta(chunk, kind) {
52227
+ if (kind === "retract") {
52228
+ flush();
52229
+ sink({ type: "retract", ts: Date.now(), reason: stripAnsi2(chunk) });
52230
+ return;
52231
+ }
51610
52232
  const next = kind ?? "status";
51611
52233
  if (buf.meta && (buf.metaKind ?? "status") !== next)
51612
52234
  flush();
@@ -52290,6 +52912,9 @@ function handleSessionCreate(req, res, s) {
52290
52912
  }
52291
52913
  try {
52292
52914
  const meta = create(name);
52915
+ try {
52916
+ s.execModule?.switchSession(meta.id);
52917
+ } catch {}
52293
52918
  sendJson(res, 201, {
52294
52919
  ok: true,
52295
52920
  session: {
@@ -52330,6 +52955,9 @@ function handleSessionActivate(req, res, s) {
52330
52955
  sendJson(res, 404, { error: "not_found" });
52331
52956
  return;
52332
52957
  }
52958
+ try {
52959
+ s.execModule?.switchSession(id);
52960
+ } catch {}
52333
52961
  sendJson(res, 200, { ok: true });
52334
52962
  }).catch((err) => {
52335
52963
  readBodyError(req, res, err);
@@ -52354,6 +52982,9 @@ function handleSessionDelete(res, id, s) {
52354
52982
  sendJson(res, 404, { error: "not_found" });
52355
52983
  return;
52356
52984
  }
52985
+ try {
52986
+ s.execModule?.forgetSession(id);
52987
+ } catch {}
52357
52988
  sendJson(res, 200, { ok: true });
52358
52989
  }
52359
52990
  function handleSessionsApi(req, res, reqUrl, s) {
@@ -53408,9 +54039,11 @@ function servePreview(res, absReal, buf) {
53408
54039
 
53409
54040
  // src/modules/webui/setup-api.ts
53410
54041
  init_i18n();
54042
+ init_registry_extra();
53411
54043
  init_apply();
53412
54044
  init_answers();
53413
54045
  init_discovery();
54046
+ init_provider_context();
53414
54047
  function str3(rec, key) {
53415
54048
  const v = rec[key];
53416
54049
  return typeof v === "string" ? v.trim() : "";
@@ -53420,6 +54053,76 @@ function positiveInt(rec, key) {
53420
54053
  const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
53421
54054
  return Number.isInteger(n) && n > 0 ? n : null;
53422
54055
  }
54056
+ var SEVERITIES3 = ["low", "medium", "high", "critical"];
54057
+ var MAX_DETAIL_LIST = 200;
54058
+ var MAX_DETAIL_ITEM = 200;
54059
+ function strList(v) {
54060
+ if (!Array.isArray(v))
54061
+ return;
54062
+ 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);
54063
+ return out;
54064
+ }
54065
+ function optBool(v) {
54066
+ return typeof v === "boolean" ? v : undefined;
54067
+ }
54068
+ function optInt(v) {
54069
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
54070
+ return Number.isInteger(n) && n > 0 ? n : undefined;
54071
+ }
54072
+ function parseSecurityDetail(v) {
54073
+ if (!v || typeof v !== "object")
54074
+ return;
54075
+ const rec = v;
54076
+ const detail = {};
54077
+ const bash = rec.bash;
54078
+ if (bash && typeof bash === "object") {
54079
+ detail.bash = {
54080
+ blacklist: strList(bash.blacklist),
54081
+ whitelist: strList(bash.whitelist),
54082
+ logCommands: optBool(bash.logCommands)
54083
+ };
54084
+ }
54085
+ const paths = rec.paths;
54086
+ if (paths && typeof paths === "object") {
54087
+ detail.paths = { denied: strList(paths.denied), allowed: strList(paths.allowed) };
54088
+ }
54089
+ const flags = rec.flags;
54090
+ if (flags && typeof flags === "object") {
54091
+ detail.flags = {
54092
+ dangerousFlags: strList(flags.dangerousFlags),
54093
+ dangerousOperators: strList(flags.dangerousOperators)
54094
+ };
54095
+ }
54096
+ const network = rec.network;
54097
+ if (network && typeof network === "object") {
54098
+ detail.network = {
54099
+ enabled: optBool(network.enabled),
54100
+ deniedDomains: strList(network.deniedDomains),
54101
+ allowedDomains: strList(network.allowedDomains),
54102
+ requestTimeout: optInt(network.requestTimeout),
54103
+ maxResponseSize: optInt(network.maxResponseSize)
54104
+ };
54105
+ }
54106
+ const adv = rec.advanced;
54107
+ if (adv && typeof adv === "object") {
54108
+ const rawSev = adv.auditNotifierMinSeverity;
54109
+ const sev = typeof rawSev === "string" && SEVERITIES3.includes(rawSev) ? rawSev : undefined;
54110
+ detail.advanced = {
54111
+ maxRecursionDepth: optInt(adv.maxRecursionDepth),
54112
+ maxFileOperations: optInt(adv.maxFileOperations),
54113
+ maxRequestsPerMinute: optInt(adv.maxRequestsPerMinute),
54114
+ maxParallelTasks: optInt(adv.maxParallelTasks),
54115
+ contentScanEnabled: optBool(adv.contentScanEnabled),
54116
+ sessionEncryptionEnabled: optBool(adv.sessionEncryptionEnabled),
54117
+ auditNotifierEnabled: optBool(adv.auditNotifierEnabled),
54118
+ auditNotifierMinSeverity: sev,
54119
+ auditNotifierEventTypes: strList(adv.auditNotifierEventTypes)?.filter((e) => AUDIT_EVENT_TYPES.includes(e)),
54120
+ auditNotifierMaxRetries: optInt(adv.auditNotifierMaxRetries),
54121
+ auditNotifierWebhookTimeout: optInt(adv.auditNotifierWebhookTimeout)
54122
+ };
54123
+ }
54124
+ return detail;
54125
+ }
53423
54126
  function parseSetupAnswers(rec) {
53424
54127
  const provider = str3(rec, "provider");
53425
54128
  const apiBase = str3(rec, "apiBase");
@@ -53449,15 +54152,17 @@ function parseSetupAnswers(rec) {
53449
54152
  locale,
53450
54153
  securityBashBlock: rec.securityBashBlock,
53451
54154
  securityFlagsBlock: rec.securityFlagsBlock,
53452
- securityPathsRestrict: rec.securityPathsRestrict
54155
+ securityPathsRestrict: rec.securityPathsRestrict,
54156
+ securityDetail: parseSecurityDetail(rec.securityDetail)
53453
54157
  };
53454
54158
  }
53455
54159
  function handleSetupGet(res, s) {
54160
+ const defaults = s.setup?.defaults ?? { contextWindow: 0, maxToolIterations: 0 };
53456
54161
  sendJson(res, 200, {
53457
54162
  required: s.setupRequired,
53458
54163
  languages: s.setup?.languages ?? [],
53459
54164
  providers: s.setup?.providers ?? [],
53460
- defaults: s.setup?.defaults ?? { contextWindow: 0, maxToolIterations: 0 }
54165
+ defaults: s.config?.security ? { ...defaults, security: securityDetailFromConfig(s.config.security) } : defaults
53461
54166
  });
53462
54167
  }
53463
54168
  function handleSetupScan(req, res, s) {
@@ -53503,6 +54208,31 @@ function handleSetupTest(req, res, s) {
53503
54208
  readBodyError(req, res, err);
53504
54209
  });
53505
54210
  }
54211
+ function handleSetupContext(req, res, s) {
54212
+ readJsonBody(req, s.maxBodyBytes).then(async (rec) => {
54213
+ if (!rec) {
54214
+ sendJson(res, 400, { ok: false, error: "invalid JSON" });
54215
+ return;
54216
+ }
54217
+ const baseUrl = str3(rec, "baseUrl");
54218
+ const model = str3(rec, "model");
54219
+ if (!baseUrl || !model) {
54220
+ sendJson(res, 400, { ok: false, error: "invalid-context-request" });
54221
+ return;
54222
+ }
54223
+ const probe = await probeProviderContext(baseUrl, model, {
54224
+ apiKey: str3(rec, "apiKey") || undefined,
54225
+ providerType: str3(rec, "providerType") || undefined
54226
+ });
54227
+ if (!probe) {
54228
+ sendJson(res, 200, { ok: false });
54229
+ return;
54230
+ }
54231
+ sendJson(res, 200, { ok: true, contextWindow: probe.actual, model: probe.model });
54232
+ }).catch((err) => {
54233
+ readBodyError(req, res, err);
54234
+ });
54235
+ }
53506
54236
  function handleSetupLocale(req, res, s) {
53507
54237
  readJsonBody(req, s.maxBodyBytes).then((rec) => {
53508
54238
  if (!rec) {
@@ -53575,6 +54305,10 @@ function handleSetupApi(req, res, reqUrl, s) {
53575
54305
  handleSetupTest(req, res, s);
53576
54306
  return true;
53577
54307
  }
54308
+ if (p === "/api/setup/context" && req.method === "POST") {
54309
+ handleSetupContext(req, res, s);
54310
+ return true;
54311
+ }
53578
54312
  if (p === "/api/setup/locale" && req.method === "POST") {
53579
54313
  handleSetupLocale(req, res, s);
53580
54314
  return true;
@@ -53652,6 +54386,7 @@ var API_ROUTES = {
53652
54386
  "GET /api/models": (_req, res, s) => handleModelsGet(res, s),
53653
54387
  "POST /api/command": (req, res, s) => handleCommandPost(req, res, s),
53654
54388
  "POST /api/interrupt": (_req, res, s) => handleInterruptPost(res, s),
54389
+ "POST /api/index": (_req, res, s) => handleIndexPost(res, s),
53655
54390
  "POST /api/mcp": (req, res, s) => handleMcpCreate(req, res, s),
53656
54391
  "GET /api/lsp": (_req, res, s) => handleLspGet(res, s)
53657
54392
  };
@@ -53693,6 +54428,14 @@ function handleInterruptPost(res, s) {
53693
54428
  s.bridge?.interrupt();
53694
54429
  sendJson(res, 200, { ok: true });
53695
54430
  }
54431
+ function handleIndexPost(res, s) {
54432
+ if (!s.refreshIndex) {
54433
+ sendJson(res, 503, { error: "unavailable" });
54434
+ return;
54435
+ }
54436
+ Promise.resolve(s.refreshIndex()).catch(() => {});
54437
+ sendJson(res, 202, { ok: true });
54438
+ }
53696
54439
  function handleRequest(req, res, assets, s) {
53697
54440
  let reqUrl;
53698
54441
  try {
@@ -53947,13 +54690,22 @@ async function startWebUi(deps) {
53947
54690
  state.setup = buildSetupDisplay();
53948
54691
  state.config = deps.config;
53949
54692
  state.sessionManager = deps.sessionManager;
54693
+ state.execModule = deps.execModule;
53950
54694
  state.memory = deps.memory;
53951
54695
  state.skills = deps.skills;
53952
54696
  state.plugins = deps.plugins;
53953
54697
  state.workspace = deps.baseDir;
53954
54698
  state.agentsMd = deps.agentsMd;
53955
- if (deps.indexer)
54699
+ if (deps.indexer) {
53956
54700
  state.projectMapStats = () => deps.indexer?.stats() ?? null;
54701
+ if (deps.indexer.refresh) {
54702
+ const refresh = deps.indexer.refresh.bind(deps.indexer);
54703
+ state.refreshIndex = async () => {
54704
+ await refresh();
54705
+ broadcast({ type: "state", ts: Date.now(), busy: state.busy, usage: state.usage }, state);
54706
+ };
54707
+ }
54708
+ }
53957
54709
  state.saveConfig = (config) => saveConfig(config, CONFIG_FILE, deps.configDir);
53958
54710
  installWebPromptIO({
53959
54711
  state,
@@ -53997,10 +54749,7 @@ async function startWebUi(deps) {
53997
54749
  contextProbe: probe,
53998
54750
  workspace: deps.baseDir,
53999
54751
  instructions: deps.agentsMd,
54000
- projectMap: (() => {
54001
- const s = deps.indexer?.stats();
54002
- return s ? { indexed: true, ...s } : { indexed: false, files: 0, symbols: 0, tokens: 0 };
54003
- })()
54752
+ projectMap: deps.indexer?.mapInfo?.()
54004
54753
  }, state.busy),
54005
54754
  addPendingImage: (url2) => {
54006
54755
  deps.agent.contextManager?.addPendingImage?.({