micro-models-agent 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/app.js +22 -4
  3. package/dist/assets/index.html +29 -0
  4. package/dist/assets/js/dom.js +2 -1
  5. package/dist/assets/js/feed.js +38 -26
  6. package/dist/assets/js/format.js +1 -1
  7. package/dist/assets/js/rail.js +114 -0
  8. package/dist/assets/js/settings-controls.js +2 -2
  9. package/dist/assets/js/settings.js +246 -51
  10. package/dist/assets/js/store.js +3 -0
  11. package/dist/assets/js/tabs.js +104 -2
  12. package/dist/assets/styles.css +215 -66
  13. package/dist/cli/cache-line.js +1 -0
  14. package/dist/cli/command-suggest.js +5 -4
  15. package/dist/cli/commands.js +231 -122
  16. package/dist/cli/completer.js +132 -31
  17. package/dist/cli/index.js +1 -0
  18. package/dist/cli/json-payload.js +4 -4
  19. package/dist/cli/main.js +166 -61
  20. package/dist/cli/onboarding-commands.js +126 -0
  21. package/dist/cli/plugin-commands.js +2 -1
  22. package/dist/cli/relaunch.js +16 -13
  23. package/dist/cli/repl-commands-lsp.js +75 -0
  24. package/dist/cli/repl-commands-skills.js +105 -0
  25. package/dist/cli/repl-commands.js +589 -266
  26. package/dist/cli/repl.js +436 -177
  27. package/dist/cli/run-result.js +5 -6
  28. package/dist/cli/security-commands.js +37 -28
  29. package/dist/cli/setup-order.js +4 -55
  30. package/dist/cli/setup-prompt.js +3 -2
  31. package/dist/cli/setup.js +27 -107
  32. package/dist/cli/web-command.js +98 -0
  33. package/dist/config/budget.js +5 -5
  34. package/dist/config/config.js +103 -96
  35. package/dist/config/defaults.js +38 -8
  36. package/dist/config/domains.js +24 -27
  37. package/dist/config/env.js +46 -0
  38. package/dist/config/experts.js +2 -0
  39. package/dist/config/index.js +1 -0
  40. package/dist/config/paths.js +56 -0
  41. package/dist/config/redact.js +44 -0
  42. package/dist/config/registry-extra.js +334 -0
  43. package/dist/config/registry.js +584 -0
  44. package/dist/config/security.js +35 -36
  45. package/dist/config/timeouts.js +74 -0
  46. package/dist/config/utils.js +16 -15
  47. package/dist/core/agent/artifact-progress.js +59 -0
  48. package/dist/core/agent/artifact-resolve.js +79 -0
  49. package/dist/core/agent/audit-gate.js +7 -3
  50. package/dist/core/agent/compaction.js +2 -2
  51. package/dist/core/agent/constants.js +33 -4
  52. package/dist/core/agent/context-log.js +454 -0
  53. package/dist/core/agent/context-renderer.js +5 -0
  54. package/dist/core/agent/critic-gate.js +132 -0
  55. package/dist/core/agent/hallucination-gate.js +148 -38
  56. package/dist/core/agent/loop-state.js +15 -0
  57. package/dist/core/agent/prefix-monitor.js +1 -1
  58. package/dist/core/agent/reasoning-resolver.js +10 -1
  59. package/dist/core/agent/token-tracker.js +7 -4
  60. package/dist/core/agent/tool-batch.js +93 -18
  61. package/dist/core/agent/tool-images.js +18 -0
  62. package/dist/core/agent/tool-output.js +65 -11
  63. package/dist/core/agent/tool-pairing.js +60 -0
  64. package/dist/core/agent-moe.js +25 -24
  65. package/dist/core/agent.js +429 -108
  66. package/dist/core/bootstrap/agent-phase.js +66 -0
  67. package/dist/core/bootstrap/config-phase.js +86 -0
  68. package/dist/core/bootstrap/modules-phase.js +269 -0
  69. package/dist/core/bootstrap/provider-phase.js +183 -0
  70. package/dist/core/bootstrap/session-phase.js +204 -0
  71. package/dist/core/bootstrap/state.js +94 -0
  72. package/dist/core/bootstrap/workspace-phase.js +115 -0
  73. package/dist/core/bootstrap.js +92 -660
  74. package/dist/core/crash-handler.js +17 -7
  75. package/dist/core/environment.js +37 -12
  76. package/dist/core/host-plan.js +43 -0
  77. package/dist/core/host.js +23 -0
  78. package/dist/core/index.js +1 -0
  79. package/dist/core/prompt-builder.js +17 -1
  80. package/dist/core/prompt-overflow.js +42 -43
  81. package/dist/core/session-logger.js +122 -30
  82. package/dist/core/system-prompt.js +117 -0
  83. package/dist/core/version.js +6 -4
  84. package/dist/core/web-host.js +34 -0
  85. package/dist/core/workspace.js +19 -15
  86. package/dist/i18n/en.json +106 -21
  87. package/dist/i18n/index.js +37 -3
  88. package/dist/i18n/ru.json +109 -21
  89. package/dist/index.js +7 -3
  90. package/dist/llm/image-utils.js +55 -64
  91. package/dist/llm/llm-errors.js +9 -0
  92. package/dist/llm/model-loader.js +10 -32
  93. package/dist/llm/models-dev.js +121 -0
  94. package/dist/llm/openai-compat.js +146 -73
  95. package/dist/llm/orchestrator.js +33 -11
  96. package/dist/llm/provider-budget.js +2 -2
  97. package/dist/llm/provider-context.js +233 -0
  98. package/dist/llm/provider.js +1 -1
  99. package/dist/llm/response.js +5 -0
  100. package/dist/llm/token-counter.js +27 -11
  101. package/dist/logger/app-logger.js +48 -22
  102. package/dist/logger/file-log.js +28 -19
  103. package/dist/logger/index.js +1 -0
  104. package/dist/main.js +24285 -22447
  105. package/dist/migration/backup.js +14 -3
  106. package/dist/migration/detect.js +12 -3
  107. package/dist/migration/index.js +1 -0
  108. package/dist/modules/artifacts/store.js +9 -8
  109. package/dist/modules/browser/actions.js +6 -0
  110. package/dist/modules/browser/bridge-client.js +14 -12
  111. package/dist/modules/browser/bridge-path.js +4 -4
  112. package/dist/modules/browser/console-buffer.js +37 -0
  113. package/dist/modules/browser/cookie-store.js +5 -0
  114. package/dist/modules/browser/driver-types.js +1 -0
  115. package/dist/modules/browser/driver.js +19 -11
  116. package/dist/modules/browser/index.js +3 -1
  117. package/dist/modules/browser/module.js +2 -1
  118. package/dist/modules/browser/session.js +10 -30
  119. package/dist/modules/browser/snapshot.js +19 -13
  120. package/dist/modules/browser/types.js +1 -0
  121. package/dist/modules/certification/cli.js +28 -20
  122. package/dist/modules/certification/fact-checker.js +9 -1
  123. package/dist/modules/certification/loader.js +9 -2
  124. package/dist/modules/certification/manifest.js +83 -21
  125. package/dist/modules/certification/runner.js +23 -13
  126. package/dist/modules/certification/scenarios.js +20 -20
  127. package/dist/modules/certification/syntax-scenarios.js +1 -2
  128. package/dist/modules/context/chunk-query.js +11 -9
  129. package/dist/modules/context/fact-extractor.js +37 -39
  130. package/dist/modules/context/index.js +1 -0
  131. package/dist/modules/context/manager.js +106 -80
  132. package/dist/modules/critic/client.js +176 -0
  133. package/dist/modules/critic/evidence.js +81 -0
  134. package/dist/modules/critic/provider.js +45 -0
  135. package/dist/modules/critic/tools.js +78 -0
  136. package/dist/modules/critic/types.js +1 -0
  137. package/dist/modules/execution/audit-runners.js +28 -34
  138. package/dist/modules/execution/auditor.js +53 -49
  139. package/dist/modules/execution/execution-plugin.js +142 -102
  140. package/dist/modules/execution/index.js +1 -1
  141. package/dist/modules/execution/input-from.js +37 -13
  142. package/dist/modules/execution/module.js +368 -191
  143. package/dist/modules/execution/moe-executor.js +53 -26
  144. package/dist/modules/execution/plan-actions.js +320 -202
  145. package/dist/modules/execution/plan-checks.js +35 -0
  146. package/dist/modules/execution/plan-coverage.js +36 -33
  147. package/dist/modules/execution/plan-steps.js +11 -3
  148. package/dist/modules/execution/plan-store.js +83 -17
  149. package/dist/modules/execution/plan-tool.js +89 -35
  150. package/dist/modules/execution/plan-validator.js +30 -10
  151. package/dist/modules/execution/planner.js +22 -13
  152. package/dist/modules/execution/stuck-detector.js +196 -93
  153. package/dist/modules/execution/tracker.js +22 -8
  154. package/dist/modules/execution/verifier.js +69 -48
  155. package/dist/modules/execution/windows-commands.js +12 -12
  156. package/dist/modules/hallucination/artifact-claim.js +93 -0
  157. package/dist/modules/hallucination/confidence.js +13 -11
  158. package/dist/modules/hallucination/consistency.js +10 -2
  159. package/dist/modules/hallucination/detector.js +10 -5
  160. package/dist/modules/hallucination/factual.js +43 -26
  161. package/dist/modules/hallucination/index.js +1 -0
  162. package/dist/modules/hallucination/js-identifiers.js +44 -45
  163. package/dist/modules/hallucination/llm-judge.js +17 -12
  164. package/dist/modules/hallucination/success-claim.js +59 -0
  165. package/dist/modules/indexer/cache.js +52 -4
  166. package/dist/modules/indexer/index.js +1 -0
  167. package/dist/modules/indexer/map-command.js +2 -4
  168. package/dist/modules/indexer/map-select.js +16 -16
  169. package/dist/modules/indexer/module.js +47 -12
  170. package/dist/modules/indexer/project-profile.js +8 -7
  171. package/dist/modules/indexer/symbols.js +15 -15
  172. package/dist/modules/indexer/walker.js +159 -29
  173. package/dist/modules/lsp/check-tool.js +7 -5
  174. package/dist/modules/lsp/client.js +39 -39
  175. package/dist/modules/lsp/command.js +17 -24
  176. package/dist/modules/lsp/config.js +12 -11
  177. package/dist/modules/lsp/index.js +1 -0
  178. package/dist/modules/lsp/module.js +48 -46
  179. package/dist/modules/lsp/probe.js +13 -14
  180. package/dist/modules/lsp/project-root.js +14 -17
  181. package/dist/modules/lsp/startup-check.js +18 -16
  182. package/dist/modules/mcp/client.js +59 -62
  183. package/dist/modules/mcp/index.js +1 -0
  184. package/dist/modules/mcp/module.js +66 -37
  185. package/dist/modules/mcp/registry.js +5 -0
  186. package/dist/modules/memory/index.js +1 -0
  187. package/dist/modules/memory/module.js +12 -8
  188. package/dist/modules/memory/search.js +4 -1
  189. package/dist/modules/memory/store.js +40 -2
  190. package/dist/modules/onboarding/init.js +47 -0
  191. package/dist/modules/onboarding/meet.js +30 -0
  192. package/dist/modules/onboarding/mode.js +30 -0
  193. package/dist/modules/onboarding/prompts.js +43 -0
  194. package/dist/modules/pipelines/engine.js +10 -0
  195. package/dist/modules/pipelines/index.js +1 -0
  196. package/dist/modules/pipelines/parser.js +4 -1
  197. package/dist/modules/pipelines/template.js +4 -0
  198. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  199. package/dist/modules/plugins/builtin/notify.js +2 -0
  200. package/dist/modules/plugins/index.js +1 -0
  201. package/dist/modules/plugins/loader.js +18 -3
  202. package/dist/modules/plugins/manager.js +37 -17
  203. package/dist/modules/pricing/index.js +10 -11
  204. package/dist/modules/pricing/prices.js +12 -12
  205. package/dist/modules/processes/index.js +1 -0
  206. package/dist/modules/processes/kill-tree.js +27 -31
  207. package/dist/modules/processes/registry.js +44 -25
  208. package/dist/modules/processes/runner.js +4 -4
  209. package/dist/modules/providers/capabilities.js +28 -0
  210. package/dist/modules/providers/create.js +3 -4
  211. package/dist/modules/providers/factory.js +7 -7
  212. package/dist/modules/providers/fallback.js +12 -12
  213. package/dist/modules/providers/health.js +4 -4
  214. package/dist/modules/providers/manager.js +50 -19
  215. package/dist/modules/providers/naming.js +15 -0
  216. package/dist/modules/providers/preflight.js +71 -0
  217. package/dist/modules/providers/presets.js +39 -8
  218. package/dist/modules/providers/registry.js +7 -2
  219. package/dist/modules/reasoning/policy.js +8 -6
  220. package/dist/modules/reasoning/probe.js +30 -25
  221. package/dist/modules/security/audit-log.js +19 -36
  222. package/dist/modules/security/audit-notifier.js +35 -75
  223. package/dist/modules/security/command-validator.js +99 -51
  224. package/dist/modules/security/content-scanner.js +8 -8
  225. package/dist/modules/security/data-sanitizer.js +9 -16
  226. package/dist/modules/security/encryption.js +32 -52
  227. package/dist/modules/security/index.js +2 -1
  228. package/dist/modules/security/network-validator.js +120 -43
  229. package/dist/modules/security/path-validator.js +61 -46
  230. package/dist/modules/security/protected-paths.js +73 -0
  231. package/dist/modules/security/rate-limiter.js +17 -49
  232. package/dist/modules/security/security-policies.js +25 -57
  233. package/dist/modules/security/session-encryption.js +32 -71
  234. package/dist/modules/security/session-isolation.js +15 -28
  235. package/dist/modules/session/auto-name.js +63 -0
  236. package/dist/modules/session/export.js +98 -0
  237. package/dist/modules/session/index.js +1 -0
  238. package/dist/modules/session/manager.js +79 -28
  239. package/dist/modules/session/module.js +4 -3
  240. package/dist/modules/session/store.js +57 -20
  241. package/dist/modules/setup/answers.js +35 -0
  242. package/dist/modules/setup/apply.js +79 -0
  243. package/dist/modules/setup/discovery.js +72 -0
  244. package/dist/modules/skills/index.js +1 -0
  245. package/dist/modules/skills/loader.js +9 -1
  246. package/dist/modules/skills/module.js +11 -0
  247. package/dist/modules/updater/changelog-reader.js +15 -16
  248. package/dist/modules/updater/checker.js +9 -4
  249. package/dist/modules/updater/dev-detect.js +7 -6
  250. package/dist/modules/updater/index.js +1 -0
  251. package/dist/modules/updater/module.js +17 -15
  252. package/dist/modules/user-profile/compressor.js +2 -0
  253. package/dist/modules/user-profile/index.js +1 -0
  254. package/dist/modules/user-profile/profile.js +10 -1
  255. package/dist/modules/webui/answer-api.js +29 -0
  256. package/dist/modules/webui/assets.js +50 -0
  257. package/dist/modules/webui/body.js +21 -0
  258. package/dist/modules/webui/commands-settings.js +209 -0
  259. package/dist/modules/webui/commands-shared.js +60 -0
  260. package/dist/modules/webui/commands.js +174 -0
  261. package/dist/modules/webui/config-api.js +134 -0
  262. package/dist/modules/webui/file-api.js +202 -0
  263. package/dist/modules/webui/forward.js +159 -0
  264. package/dist/modules/webui/history-api.js +112 -0
  265. package/dist/modules/webui/host-state.js +60 -0
  266. package/dist/modules/webui/i18n-api.js +9 -0
  267. package/dist/modules/webui/limits.js +6 -0
  268. package/dist/modules/webui/log-forward.js +20 -0
  269. package/dist/modules/webui/memory-commands.js +64 -0
  270. package/dist/modules/webui/model-cache.js +18 -0
  271. package/dist/modules/webui/module.js +148 -0
  272. package/dist/modules/webui/persist.js +24 -0
  273. package/dist/modules/webui/prompt-io.js +97 -0
  274. package/dist/modules/webui/providers-api.js +337 -0
  275. package/dist/modules/webui/providers-view.js +56 -0
  276. package/dist/modules/webui/reasoning-view.js +15 -0
  277. package/dist/modules/webui/run-agent.js +81 -0
  278. package/dist/modules/webui/server.js +284 -0
  279. package/dist/modules/webui/sessions-api.js +178 -0
  280. package/dist/modules/webui/settings-api.js +561 -0
  281. package/dist/modules/webui/setup-api.js +201 -0
  282. package/dist/modules/webui/state.js +74 -0
  283. package/dist/modules/webui/util.js +261 -0
  284. package/dist/output/bus.js +7 -2
  285. package/dist/output/channel.js +80 -28
  286. package/dist/output/format.js +2 -1
  287. package/dist/output/index.js +1 -0
  288. package/dist/output/json-sink.js +5 -3
  289. package/dist/output/machine.js +4 -3
  290. package/dist/output/session-sink.js +4 -4
  291. package/dist/tools/approve.js +4 -0
  292. package/dist/tools/attach-image.js +5 -4
  293. package/dist/tools/auto-fixer.js +26 -44
  294. package/dist/tools/auto-tags.js +83 -0
  295. package/dist/tools/bash.js +193 -116
  296. package/dist/tools/browser.js +76 -31
  297. package/dist/tools/chunk-query.js +14 -6
  298. package/dist/tools/create-dir.js +9 -5
  299. package/dist/tools/delete-file.js +9 -5
  300. package/dist/tools/download-file.js +32 -7
  301. package/dist/tools/edit-file.js +52 -14
  302. package/dist/tools/enable-tools.js +6 -6
  303. package/dist/tools/executor.js +132 -27
  304. package/dist/tools/file-info.js +3 -1
  305. package/dist/tools/filter-tools.js +3 -3
  306. package/dist/tools/glob-tool.js +4 -1
  307. package/dist/tools/grep-tool.js +13 -10
  308. package/dist/tools/hidden-tools-block.js +6 -6
  309. package/dist/tools/index.js +16 -8
  310. package/dist/tools/list-dir.js +4 -2
  311. package/dist/tools/load-skill.js +7 -0
  312. package/dist/tools/mcp-call.js +15 -4
  313. package/dist/tools/meet-done.js +14 -0
  314. package/dist/tools/move-file.js +14 -7
  315. package/dist/tools/path-utils.js +29 -29
  316. package/dist/tools/pipeline-run.js +29 -7
  317. package/dist/tools/preview.js +1 -1
  318. package/dist/tools/process-ids.js +31 -0
  319. package/dist/tools/process-kill.js +9 -16
  320. package/dist/tools/process-list.js +1 -0
  321. package/dist/tools/process-log.js +11 -16
  322. package/dist/tools/process-wait.js +63 -0
  323. package/dist/tools/prompt-io.js +4 -3
  324. package/dist/tools/question.js +172 -25
  325. package/dist/tools/read-file.js +55 -34
  326. package/dist/tools/recall.js +14 -8
  327. package/dist/tools/registry.js +6 -5
  328. package/dist/tools/remember.js +8 -4
  329. package/dist/tools/scope-request.js +11 -6
  330. package/dist/tools/search-history.js +5 -2
  331. package/dist/tools/session-info.js +2 -0
  332. package/dist/tools/set-thinking.js +8 -4
  333. package/dist/tools/subagent.js +59 -39
  334. package/dist/tools/syntax-validator.js +292 -90
  335. package/dist/tools/user-input.js +22 -18
  336. package/dist/tools/web-browse.js +9 -3
  337. package/dist/tools/web-fetch.js +12 -23
  338. package/dist/tools/web-search.js +7 -5
  339. package/dist/tools/write-file.js +45 -15
  340. package/dist/ui/action-log.js +13 -0
  341. package/dist/ui/box.js +4 -3
  342. package/dist/ui/colors.js +2 -0
  343. package/dist/ui/diff.js +6 -5
  344. package/dist/ui/index.js +1 -0
  345. package/dist/ui/line-editor.js +230 -122
  346. package/dist/ui/line-math.js +10 -2
  347. package/dist/ui/md-formatter.js +57 -37
  348. package/dist/ui/output.js +8 -3
  349. package/dist/ui/plan-view.js +21 -10
  350. package/dist/ui/renderer.js +393 -67
  351. package/dist/ui/spinner.js +16 -5
  352. package/dist/ui/table.js +15 -17
  353. package/dist/ui/theme.js +143 -0
  354. package/dist/utils/child-run.js +20 -0
  355. package/dist/utils/error.js +1 -1
  356. package/dist/utils/index.js +2 -0
  357. package/dist/utils/retry.js +2 -2
  358. package/dist/utils/truncate.js +1 -1
  359. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.12.0] - 2026-09-29
8
+
9
+ ### Added
10
+
11
+ - **Session-bound plans** (`src/modules/execution/plan-store.ts`, `module.ts`, `src/core/bootstrap/modules-phase.ts`, `src/cli/repl-commands.ts`, `src/modules/webui/sessions-api.ts`, `state.ts`, `module.ts`, `assets/js/tabs.js`): the active plan is now stored per session (`.mma/plans/sessions/<sessionId>/active.json`) instead of a single project-global `active.json`. Switching sessions (`/resume`, `/new`, `POST /api/sessions/activate` / `POST /api/sessions`) moves the `ExecutionModule` tracker to that session's plan (new sessions start plan-less, `/resume` prints a `◇ <plan>` status line); deleting a session drops its plan slot. Plan tools writing past `setPlan` (`abort`/`switch`/`delete`/`purge`) and internal saves mirror the slot, so an aborted plan never resurrects on return. Drafts/archive stay a shared library; the legacy global plan is adopted by the active session on first start after upgrade. Web sidebar refreshes immediately after tab switch instead of waiting for the 2s poll. Spec: `docs/specs/2026-09-29-session-plan-binding-design.md`.
12
+
13
+ ### Fixed
14
+
15
+ - **Provider identity sync on swap** (`src/modules/providers/manager.ts`, `src/config/registry.ts`, `src/core/agent.ts`): switching `provider.active` now mirrors the legacy flat fields (`baseUrl`/`apiKey`/`type`) from the active entry, closing the desync where banner and probes used a stale flat endpoint (e.g. `localhost:1234` while `opencode-go` was active); `reasoning_effort` is normalized and the reasoning strategy refreshed on provider swap; assistant `tool_calls` are kept paired with their tool results.
16
+
17
+ ## [2.11.0] - 2026-09-28
18
+
19
+ ### Added
20
+
21
+ - **Plan contracts for small models: `plan_step` tool + per-step `checks`** (`src/modules/execution/plan-tool.ts`, `plan-actions.ts`, `plan-checks.ts`, `planner.ts`, `plan-steps.ts`, `module.ts`): `plan create`/`re-plan` now require `checks` 1:1 with steps (`file-exists:<path>` hard-verified on done via `StepVerifier`, `command:<cmd>`/`manual:<text>` self-certified with an explicit note; delete-kind inverts the file check). New alwaysOn tool `plan_step step=N status=done|failed|skipped` replaces `plan update step=` for status changes (no `action` parameter to confuse; old form keeps working as an alias) and is wired everywhere `plan`/`todo` are (alignment exemption, stuck-detector read-only set, bookkeeping key, terminal checklist refresh, web plan snapshot, meta-color sets). `plan` coverage validation is now a hard block (`plan.coverage_blocked`, validated before any state mutation so a rejected create leaks no active plan). `kinds` removed from the model-facing schema — create/delete intent is fully auto-detected (`switchStepKind` both directions: verified deletions flip to delete, never-deleted-missing files flip back to create). `todo done` accepts `ids=[...]` (list/add print `[id]`); text matching stays as fallback. Step checks render as `↳ check:` in the prompt block; system prompt and all model-facing `plan update` directives now teach `plan_step`; i18n en+ru (`plan.checks_*`, `plan.check_failed`, `plan.check_self_certified`, `plan.coverage_blocked`, `plan.step_required`, `todo.subtask_id_not_found`).
22
+
23
+ - **Memory-aware `question` tool in the core set** (`src/tools/question.ts`, `src/tools/index.ts`, `src/core/system-prompt.ts`, `src/i18n/en.json`, `src/i18n/ru.json`): single/multi-choice and free-text prompts now registered in `registerAllTools()` (visible in REPL and web UI; `approve` registered alongside for explicit Yes/No — reject returns `success:false` so the loop treats it as a failure signal). The handler pre-checks agent memory itself (`splitByMemory` over `MemoryStore.search`, fail-open) — questions memory already answers are reported, not asked; answers live only in the session (persist via `remember` only when asked to); at most `MAX_QUESTIONS_PER_CALL = 3` unknown questions per call with a re-call note. System prompt carries a one-line `ASK RULE`; REPL rendering already matched `STYLE.md`, web rendering reuses `feed.js onQuestion`.
24
+
25
+ - **Activity rail above the composer** (`src/modules/webui/assets/js/rail.js`, `index.html`, `styles.css`, `app.js`, `tabs.js`, `settings.js`): an indicator that lives for the whole turn — indeterminate scanner with phase/elapsed/tool & event counters when no plan exists, honest `done/total` progress with step ticks when an MoE plan is active, and a short flash on every real SSE event (`tool_start`/`tool_end`/`text`/`plan`). Layouts `off|line|meta|full` via the new config key `ui.activityRail` (default `line`), editable in `/config` and web Settings → Chat output and applied live; en/ru localized, `prefers-reduced-motion` respected, phase announced politely (`role="status"`, timer/counters `aria-hidden`). Spec: `docs/specs/2026-09-27-activity-rail-design.md`.
26
+
27
+ - **Web chat font size** (`src/config/types.ts`, `defaults.ts`, `registry-extra.ts`, `src/modules/webui/assets/styles.css`, `assets/js/settings.js`, `app.js`, `src/i18n/en.json`, `ru.json`): new `ui.fontSize` key (`compact` = 13px / `comfortable` = 15px / `large` = 17px, default `comfortable`), editable via `/config`, `mma config set` and web Settings → Chat output; applied live through `body[data-font]` without a reload (unknown values fall back to the default). Web-only — the terminal is unaffected.
28
+
29
+ - **Composer selector switches with loaders + 4th reasoning selector** (`src/modules/webui/reasoning-view.ts`, `config-api.ts`, `assets/js/settings.js`, `store.js`, `index.html`, `styles.css`, `src/i18n/en.json`, `ru.json`): switching provider/model/context/reasoning now paints a pending mark (`⋯` + `aria-busy`, all selector menus inert) from click until config + model list are re-read (provider/model re-reads run in parallel; context/reasoning skip the model round-trip), with single-flight (double-click answers `web.selectors.switch_busy`) and a stale-guard so a slow A→B→A never repaints over the fresh state; boot shows the same mark instead of bare `—`. `GET /api/models` additively carries `reasoning: {strategy, supported, mode}` (strategy from the active provider preset via entries, `supported` from the probe cache, `null` until probed), rendered as a 4th row showing only supported levels (`openai-effort` → full set, `prompt-tag`/`template-kwarg` → coarse set without `medium`/`max`); `strategy: "none"` hides the row, a failed probe shows an honest `off` mark with a shortcut to Settings → Reasoning. Spec: `docs/specs/2026-09-28-web-selector-loaders-reasoning-design.md`.
30
+
31
+ ### Changed
32
+
33
+ - **Internal loop diagnostics are verbose-only** (`src/logger/app-logger.ts`, `src/output/channel.ts`, `src/cli/main.ts`, `src/cli/repl-commands.ts`): new `Logger.diagnostic(level, msg, meta)` marks loop internals (hallucination retries, audit/compaction notes, repeated-tool nudges, MoE/critic internals, `LLM warning`) with `data.diagnostic` on the output bus. `OutputChannel.setShowDiagnostics()` drops them unless `ui.verbosity === "verbose"`, so chat shows user-facing notices only; file logging (`.log`, `app.jsonl`) is unchanged and diagnostics stay diagnosable. `/config ui.verbosity` now also hides `info` logs outside verbose, matching the startup rule.
34
+ - **Critic verdict no longer renders in chat** (`src/core/agent/critic-gate.ts`, `src/ui/renderer.ts`, `src/modules/webui/forward.ts`, `assets/app.js`, `assets/js/feed.js`): `Renderer.criticVerdict`, the SSE `critic` event, the `onCritic` UI callback and the `ui.critic_*`/`web.critic_*` i18n keys are removed. The verdict remains in `session.jsonl` (`critic_check`/`critic_verdict`/`critic_escalation`) and as a verbose diagnostic line; model-facing repair/advice still goes through `<system-summary>` only.
35
+ - **Hallucination uncertainty line follows the same rule** (`src/core/agent/hallucination-gate.ts`): the `[⚠️ Uncertainty]` meta line is rendered only in verbose mode; the model still receives the retry context.
36
+ - **Web quiet hides model-facing tool bodies** (`src/core/types.ts`, `src/core/agent/tool-batch.ts`, `src/modules/webui/forward.ts`, `run-agent.ts`, `assets/app.js`, `assets/js/feed.js`, `src/tools/question.ts`): `onMeta` carries an optional `MetaKind` (`tool` for display/output/diff/failure-gist from `ToolBatch`, `status` otherwise); the SSE `meta` event tags tool bodies with `kind: "tool"` (status events stay wire-identical) and `feed.js onMeta` drops them in `quiet`, mirroring the terminal renderer. `question` returns `display: ""` so its model directives (memory/session/batch rules) never echo — the model still gets the full `output` in `role:"tool"`. Plan/file/question cards and the single `↳` failure cause are unaffected. Spec: `docs/specs/2026-09-28-web-quiet-tool-meta-design.md`.
37
+ - **System prompt routes questions through tools, not plain text** (`src/core/system-prompt.ts`, `src/tools/question.ts`): observed in `ses_mukw2cxn` — the model asked a choice question as a chat dash-list instead of calling `question`, so no interactive buttons appeared. `SCOPE`/`STOP AND ASK` now mandate the `question` tool (options when variants are known), `PROTECTED PATHS` mandates `approve`; plain text remains only as a headless fallback. The `question` description itself now forbids plain-text choice lists.
38
+ - **Web default font size 13px → 15px** (`src/modules/webui/assets/styles.css`): the `body` base size moves to the new `comfortable` step; `compact` restores the old 13px via `body[data-font]`.
39
+
7
40
  ## [2.10.0] - 2026-09-27
8
41
 
9
42
  ### Added
@@ -4,7 +4,7 @@ import { I18N, t, loadI18n } from "./js/i18n.js";
4
4
  import { S, hooks } from "./js/store.js";
5
5
  import {
6
6
  onText, onReasoning, onMeta, onPhase, onToolStart, onToolEnd,
7
- onTurnEnd, onErrorEvent, onCommandOutput, onLog, onCritic, onPlan, onQuestion,
7
+ onTurnEnd, onErrorEvent, onCommandOutput, onLog, onPlan, onQuestion,
8
8
  showEmptyFeed, renderPlan, renderUsage, renderEnvironment, renderWorkspace,
9
9
  feedHasContent, loadHistory,
10
10
  } from "./js/feed.js";
@@ -14,8 +14,11 @@ loadCommands,
14
14
  } from "./js/tabs.js";
15
15
  import {
16
16
  renderSelectors, renderSettings, loadConfig, loadModels, loadProviders,
17
- loadMemory, loadSkills, loadPlugins, applyTheme, currentTheme, setConnStatus,
17
+ loadMemory, loadSkills, loadPlugins, applyTheme, currentTheme, applyFontSize, setConnStatus,
18
18
  } from "./js/settings.js";
19
+ import {
20
+ initRail, setPlan as railPlan, railEvent, applyRailI18n,
21
+ } from "./js/rail.js";
19
22
 
20
23
  document.querySelectorAll("[data-collapse]").forEach((head) => {
21
24
  const initArrow = head.querySelector(".arrow");
@@ -64,6 +67,7 @@ function renderState(snapshot, busy) {
64
67
  if (sessionId) updateTabName(sessionId, S.state.host.sessionName);
65
68
  if (!S.tabs.length) showEmptyFeed();
66
69
  renderPlan(S.state.host.plan);
70
+ railPlan(busy && snapshot ? snapshot.plan : null);
67
71
  renderUsage(S.state.host);
68
72
  // Late provider auto-detect may change the effective window after boot —
69
73
  // the snapshot carries it so the topbar follows without a config re-fetch.
@@ -120,13 +124,12 @@ function connectSSE() {
120
124
  // checks and stay bespoke below; everything else is `parse → null-guard → call`.
121
125
  const handlers = [
122
126
  ["text", (d) => onText(d.chunk || "", d.sessionId)],
123
- ["meta", (d) => onMeta(d.chunk || "", d.sessionId)],
127
+ ["meta", (d) => onMeta(d.chunk || "", d.sessionId, d.kind)],
124
128
  ["reasoning", (d) => onReasoning(d.chunk || "", d.sessionId)],
125
129
  ["phase", (d) => onPhase(d.phase, d.sessionId)],
126
130
  ["tool_start", onToolStart],
127
131
  ["tool_end", onToolEnd],
128
132
  ["plan", onPlan],
129
- ["critic", onCritic],
130
133
  ["question", onQuestion],
131
134
  ["turn_end", onTurnEnd],
132
135
  ["log", onLog],
@@ -137,6 +140,15 @@ for (const [event, fn] of handlers) {
137
140
  if (d) fn(d);
138
141
  });
139
142
  }
143
+ // Activity rail: its own listeners — feed handlers stay untouched.
144
+ for (const ev of ["tool_start", "tool_end", "text", "plan"]) {
145
+ es.addEventListener(ev, (e) => {
146
+ const d = parse(e);
147
+ if (!d) return;
148
+ if (ev === "plan") railPlan(d);
149
+ else railEvent(ev, d.name);
150
+ });
151
+ }
140
152
  es.addEventListener("command", (e) => {
141
153
  const d = parse(e);
142
154
  if (d && typeof d.text === "string") onCommandOutput(d.text, d.sessionId);
@@ -188,6 +200,9 @@ function applyStaticI18n() {
188
200
  closeSettings: ["x", t("web.topbar.close")],
189
201
  sessNew: ["plus", t("web.sessions.new")],
190
202
  sessRefresh: ["refresh", t("web.sessions.refresh")],
203
+ sessPrev: ["chevron-left", t("web.sessbar.prev")],
204
+ sessNext: ["chevron-right", t("web.sessbar.next")],
205
+ sessClose: ["x", t("web.topbar.close")],
191
206
  };
192
207
  Object.keys(controls).forEach((id) => {
193
208
  const node = document.getElementById(id);
@@ -223,6 +238,7 @@ function applyStaticI18n() {
223
238
 
224
239
  renderSelectors();
225
240
  renderSessionsDropdown();
241
+ applyRailI18n();
226
242
  if (msgs.querySelector(".empty")) showEmptyFeed();
227
243
  if (connStatus && !connStatus.hidden) setConnStatus(true);
228
244
  }
@@ -242,11 +258,13 @@ async function boot() {
242
258
  await loadI18n();
243
259
  await loadCommands();
244
260
  applyTheme(currentTheme());
261
+ applyFontSize();
245
262
  applyStaticI18n();
246
263
  showEmptyFeed();
247
264
  renderSelectors();
248
265
  renderSettings();
249
266
  await loadConfig();
267
+ initRail(S.state.config && S.state.config.ui ? S.state.config.ui.activityRail : undefined);
250
268
  await loadModels();
251
269
  await loadProviders();
252
270
  await loadSessions();
@@ -31,12 +31,37 @@
31
31
  <span class="conn" id="connStatus" role="status" aria-live="polite" hidden></span>
32
32
  </div>
33
33
 
34
+ <div class="bar-sub" id="sessBar">
35
+ <button class="icon-btn" id="sessPrev" type="button">‹</button>
36
+ <button class="sess-name" id="sessNameBtn" type="button"><span id="sessName">—</span></button>
37
+ <button class="icon-btn" id="sessNext" type="button">›</button>
38
+ <button class="icon-btn" id="sessClose" type="button">✕</button>
39
+ <span class="sess-tools" id="sessTools"></span>
40
+ </div>
41
+
34
42
  <div class="layout">
35
43
  <div class="chat">
36
44
  <div class="msgs" id="msgs" role="log" aria-label="conversation"></div>
37
45
  <div class="visually-hidden" id="live" role="status" aria-live="polite" aria-atomic="true"></div>
38
46
 
39
47
  <div class="input-area">
48
+ <div class="rail-wrap l-line" id="railWrap" role="status" aria-live="polite" hidden>
49
+ <div class="rail-l1">
50
+ <span class="lab" id="railPhase">✦ thinking</span>
51
+ <span class="s" aria-hidden="true">·</span><span class="time" id="railTime" aria-hidden="true">0.0s</span>
52
+ <span class="s" aria-hidden="true">·</span><span class="st" id="railSteps" aria-hidden="true">no plan</span>
53
+ <span class="right" id="railEsc" aria-hidden="true">Esc — stop</span>
54
+ </div>
55
+ <div class="rl-track" id="railTrack" data-mode="scan">
56
+ <div class="rl-fill" id="railFill"></div>
57
+ <div class="rl-scan"></div>
58
+ </div>
59
+ <div class="rail-l2" aria-hidden="true">
60
+ <span class="tools" id="railTools">0 tools</span>
61
+ <span class="s">·</span><span class="events" id="railEvents">0 events</span>
62
+ <span class="s">·</span><span class="last" id="railLast">—</span>
63
+ </div>
64
+ </div>
40
65
  <div class="selectors">
41
66
  <div class="sel" data-sel="provider">
42
67
  <button id="selProviderBtn">provider: <b>—</b> ▾</button>
@@ -50,6 +75,10 @@
50
75
  <button id="selContextBtn">context: <b>—</b> ▾</button>
51
76
  <div class="menu" id="selContextMenu"></div>
52
77
  </div>
78
+ <div class="sel" data-sel="reasoning">
79
+ <button id="selReasoningBtn">reasoning: <b>—</b> ▾</button>
80
+ <div class="menu" id="selReasoningMenu"></div>
81
+ </div>
53
82
  </div>
54
83
  <input type="file" id="imgPicker" accept="image/*" multiple hidden>
55
84
  <div class="attach-strip" id="attachStrip" hidden></div>
@@ -67,7 +67,8 @@ refresh: [
67
67
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }],
68
68
  ["path", { d: "M8 21H3v-5" }],
69
69
  ],
70
- "chevron-down": [["path", { d: "m6 9 6 6 6-6" }]],
70
+ "chevron-down": [["path", { d: "m6 9 6 6 6-6" }]],
71
+ "chevron-left": [["path", { d: "m15 18-6-6 6-6" }]],
71
72
  "chevron-right": [["path", { d: "m9 18 6-6-6-6" }]],
72
73
  search: [
73
74
  ["circle", { cx: 11, cy: 11, r: 8 }],
@@ -247,8 +247,12 @@ export function onReasoning(chunk, sid) {
247
247
  }
248
248
 
249
249
  // Generic agent status (context stats, compaction, MoE status, warnings):
250
- // always visible, one dim line per chunk, never collapsed.
251
- export function onMeta(chunk, sid) {
250
+ // always visible, one dim line per chunk, never collapsed. Tool bodies
251
+ // (`kind: "tool"` — display/output/diff/failure-gist from ToolBatch) are
252
+ // model-facing and hidden in quiet mode, mirroring the terminal renderer
253
+ // (`writeInlineBody`); failures still surface once via `tool_end` ↳ cause.
254
+ export function onMeta(chunk, sid, kind) {
255
+ if (kind === "tool" && verbosity() === "quiet") return;
252
256
  const feed = feedForEvent(sid);
253
257
  clearBusy(feed);
254
258
  const text = (chunk || "").trim();
@@ -343,14 +347,39 @@ function cardFor(name, args, sid) {
343
347
  card.appendChild(top);
344
348
  const bottom = el("div", "fc-bottom");
345
349
  bottom.appendChild(el("span", "lab", labelOf(name)));
350
+ const actions = el("span", "fc-actions");
351
+ const baseName = String(path.split(/[\\/]/).pop() || path);
346
352
  if (OPENABLE_TOOLS.indexOf(name) !== -1) {
347
353
  const openLink = el("a", "fc-open", t("web.card.open"));
348
354
  openLink.href = "/api/file?path=" + encodeURIComponent(path);
349
355
  openLink.target = "_blank";
350
356
  openLink.rel = "noopener noreferrer";
351
- bottom.appendChild(openLink);
357
+ openLink.title = t("web.card.open_title");
358
+ actions.appendChild(openLink);
359
+ const dlLink = el("a", "fc-open", t("web.card.download"));
360
+ dlLink.href = "/api/file?path=" + encodeURIComponent(path) + "&download=1";
361
+ dlLink.title = t("web.card.download_title");
362
+ dlLink.setAttribute("download", baseName);
363
+ actions.appendChild(dlLink);
352
364
  }
365
+ const copyBtn = el("button", "fc-open", t("web.card.copy_path"));
366
+ copyBtn.type = "button";
367
+ copyBtn.title = t("web.card.copy_title");
368
+ copyBtn.addEventListener("click", () => {
369
+ copyText(path).then((ok) => {
370
+ copyBtn.textContent = ok ? "✓ " + t("web.code.copied") : "⚠ " + t("web.code.copy_failed");
371
+ window.setTimeout(() => {
372
+ copyBtn.textContent = t("web.card.copy_path");
373
+ }, 1400);
374
+ });
375
+ });
376
+ actions.appendChild(copyBtn);
377
+ bottom.appendChild(actions);
353
378
  card.appendChild(bottom);
379
+ if (/\.drawio(\.xml)?$/i.test(path)) {
380
+ const hint = el("div", "fc-hint", t("web.card.drawio_hint"));
381
+ card.appendChild(hint);
382
+ }
354
383
  appendTo(feed, card);
355
384
  return card;
356
385
  }
@@ -507,6 +536,9 @@ export function onCommandOutput(text, sid) {
507
536
  // `textContent` so nothing untrusted is parsed as HTML.
508
537
  export function onLog(data) {
509
538
  if (!data || typeof data.text !== "string" || !data.text) return;
539
+ // Internal loop diagnostics (retry counters, audit/compaction internals) are
540
+ // session-log material — the chat shows them only in verbose mode.
541
+ if (data.diagnostic && verbosity() !== "verbose") return;
510
542
  const feed = feedForEvent(data.sessionId) || activeFeed();
511
543
  if (!feed) return;
512
544
  const level = data.level;
@@ -518,29 +550,9 @@ export function onLog(data) {
518
550
  if (level === "error" || level === "warn") announce(data.text);
519
551
  }
520
552
 
521
- // In-loop critic verdict (concern/critical). Mirrors the terminal verbosity
522
- // (quiet hides, normal shows one line, verbose adds the summary). The
523
- // repair/advice directive itself is model-facing and never sent here.
524
- export function onCritic(data) {
525
- if (!data || (data.status !== "concern" && data.status !== "critical")) return;
526
- const level = verbosity();
527
- if (level === "quiet") return;
528
- const feed = feedForEvent(data.sessionId) || activeFeed();
529
- if (!feed) return;
530
- const critical = data.status === "critical" || data.repair === true;
531
- const label = critical
532
- ? t("web.critic_repair")
533
- : pluralCount("web.critic_concern", Number(data.issueCount) || 0);
534
- const line = el("div", critical ? "warnline" : "detail");
535
- line.textContent = (critical ? "⚠ " : "⟡ ") + label;
536
- appendTo(feed, line);
537
- if (level === "verbose" && typeof data.summary === "string" && data.summary) {
538
- const detail = el("div", "detail");
539
- detail.textContent = "↳ " + data.summary;
540
- appendTo(feed, detail);
541
- }
542
- if (critical) announce(label);
543
- }
553
+ // In-loop critic verdicts are session-log only (`critic_verdict` in
554
+ // `session.jsonl`): the chat keeps user-facing notices and verbose-mode
555
+ // diagnostics, not verifier verdicts.
544
556
 
545
557
  function renderAssistantText(feed, text) {
546
558
  const raw = String(text == null ? "" : text);
@@ -18,7 +18,7 @@ execute: [
18
18
  "subagent", "mcp_call",
19
19
  ],
20
20
  meta: [
21
- "plan", "todo", "verify", "lsp_check", "enable_tools", "set_thinking",
21
+ "plan", "plan_step", "todo", "verify", "lsp_check", "enable_tools", "set_thinking",
22
22
  "load_skill", "remember",
23
23
  ],
24
24
  };
@@ -0,0 +1,114 @@
1
+ // Activity rail — the indicator above the composer for the whole `busy` period.
2
+ //
3
+ // The in-feed `.busy` line clears on the first `text` event; the rail lives
4
+ // until the turn ends and shows only what we actually know: phase, elapsed,
5
+ // plan progress when a plan exists (MoE), tool/event counters. Without a plan
6
+ // the rail is an indeterminate scanner — no invented percentages.
7
+ // Layouts: off | line (default) | meta | full — see docs/specs/2026-09-27-activity-rail-design.md.
8
+
9
+ import { t, pluralCount } from "./i18n.js";
10
+ import { labelOf } from "./format.js";
11
+
12
+ const LAYOUTS = ["off", "line", "meta", "full"];
13
+
14
+ let root = null, trackEl = null, fillEl = null;
15
+ let labEl = null, timeEl = null, stepsEl = null, escEl = null;
16
+ let toolsEl = null, eventsEl = null, lastEl = null;
17
+ let layout = "line";
18
+ let busy = false, startedAt = 0;
19
+ let tools = 0, events = 0, lastTool = "";
20
+ let sawText = false, pendingTool = false;
21
+ let plan = null;
22
+ let timer = null;
23
+
24
+ function render() {
25
+ if (!root) return;
26
+ root.hidden = layout === "off" || !busy;
27
+ if (root.hidden) return;
28
+ const phase = pendingTool ? "tool" : sawText ? "composing" : "thinking";
29
+ root.classList.toggle("phase-tool", phase === "tool");
30
+ root.classList.toggle("phase-compose", phase === "composing");
31
+ labEl.textContent = "✦ " + (phase === "tool" ? t("web.rail.working")
32
+ : phase === "composing" ? t("web.rail.composing") : t("web.action.thinking"));
33
+ timeEl.textContent = ((Date.now() - startedAt) / 1000).toFixed(1) + "s";
34
+ const steps = plan && Array.isArray(plan.steps) ? plan.steps : [];
35
+ if (steps.length > 0) {
36
+ const done = steps.filter((s) => s && s.status === "done").length;
37
+ trackEl.dataset.mode = "plan";
38
+ fillEl.style.setProperty("--prog", String((done / steps.length) * 100));
39
+ stepsEl.textContent = t("web.rail.step_of", { cur: Math.min(done + 1, steps.length), total: steps.length });
40
+ } else {
41
+ trackEl.dataset.mode = "scan";
42
+ stepsEl.textContent = t("web.rail.no_plan");
43
+ }
44
+ toolsEl.textContent = pluralCount("web.rail.tools", tools);
45
+ eventsEl.textContent = pluralCount("web.rail.events", events);
46
+ lastEl.textContent = lastTool ? t("web.rail.last") + " " + lastTool : t("web.rail.none");
47
+ escEl.textContent = t("web.input.esc");
48
+ }
49
+
50
+ function flash() {
51
+ if (!trackEl || !busy) return;
52
+ trackEl.classList.remove("flash");
53
+ void trackEl.offsetWidth;
54
+ trackEl.classList.add("flash");
55
+ }
56
+
57
+ export function initRail(cfgLayout) {
58
+ root = document.getElementById("railWrap");
59
+ if (!root) return;
60
+ trackEl = root.querySelector(".rl-track");
61
+ fillEl = root.querySelector(".rl-fill");
62
+ labEl = root.querySelector(".lab");
63
+ timeEl = root.querySelector(".time");
64
+ stepsEl = root.querySelector(".st");
65
+ escEl = root.querySelector(".right");
66
+ toolsEl = root.querySelector(".tools");
67
+ eventsEl = root.querySelector(".events");
68
+ lastEl = root.querySelector(".last");
69
+ setLayout(cfgLayout);
70
+ if (!timer) timer = setInterval(render, 250);
71
+ }
72
+
73
+ export function setLayout(next) {
74
+ layout = LAYOUTS.includes(next) ? next : "line";
75
+ if (!root) return;
76
+ root.classList.remove("l-line", "l-meta", "l-full");
77
+ root.classList.add("l-" + layout);
78
+ const inpMeta = document.querySelector(".inp-meta");
79
+ const inputArea = document.querySelector(".input-area");
80
+ if (layout === "meta" && inpMeta && root.parentElement !== inpMeta) {
81
+ inpMeta.prepend(root);
82
+ } else if (layout !== "meta" && inputArea && root.parentElement !== inputArea) {
83
+ const selectors = inputArea.querySelector(".selectors");
84
+ inputArea.insertBefore(root, selectors || inputArea.firstChild);
85
+ }
86
+ render();
87
+ }
88
+
89
+ export function setBusy(on) {
90
+ const next = !!on;
91
+ if (next && !busy) {
92
+ startedAt = Date.now();
93
+ tools = 0; events = 0; lastTool = "";
94
+ sawText = false; pendingTool = false; plan = null;
95
+ }
96
+ busy = next;
97
+ render();
98
+ }
99
+
100
+ export function setPlan(p) {
101
+ plan = p && Array.isArray(p.steps) && p.steps.length > 0 ? p : null;
102
+ render();
103
+ }
104
+
105
+ export function railEvent(kind, name) {
106
+ events += 1;
107
+ if (kind === "tool_start") { pendingTool = true; lastTool = labelOf(name); }
108
+ else if (kind === "tool_end") { pendingTool = false; tools += 1; lastTool = labelOf(name); }
109
+ else if (kind === "text") sawText = true;
110
+ flash();
111
+ render();
112
+ }
113
+
114
+ export function applyRailI18n() { render(); }
@@ -402,7 +402,7 @@ export const PANEL_SECTIONS = {
402
402
  ],
403
403
  agent: [
404
404
  { id: "agent.moe", titleKey: "web.settings.sec.agent.moe", descKey: "web.settings.sec.agent.moe.desc", keys: ["moe.enabled", "moe.maxReplanCycles", "experts"] },
405
- { id: "agent.critic", titleKey: "web.settings.sec.agent.critic", descKey: "web.settings.sec.agent.critic.desc", keys: ["critic.enabled", "critic.maxCalls", "critic.maxRepairCycles", "critic.timeoutMs"] },
405
+ { id: "agent.critic", titleKey: "web.settings.sec.agent.critic", descKey: "web.settings.sec.agent.critic.desc", keys: ["critic.enabled", "critic.model", "critic.provider", "critic.tools", "critic.maxCalls", "critic.maxRepairCycles", "critic.timeoutMs"] },
406
406
  { id: "agent.tools", titleKey: "web.settings.sec.agent.tools", descKey: "web.settings.sec.agent.tools.desc", keys: ["tools.enableOnDemand", "tools.defaultTags", "skills.budget"] },
407
407
  { id: "agent.subagent", titleKey: "web.settings.sec.agent.subagent", descKey: "web.settings.sec.agent.subagent.desc", keys: ["subagent.resultMode", "subagent.maxSummaryChars", "subagent.maxParallelChunkQueries", "subagent.stableSystemPrompt"] },
408
408
  { id: "agent.hallucination", titleKey: "web.settings.sec.agent.hallucination", descKey: "web.settings.sec.agent.hallucination.desc", keys: ["hallucination.judge.enabled"] },
@@ -421,7 +421,7 @@ export const PANEL_SECTIONS = {
421
421
  { id: "sessions.isolation", titleKey: "web.settings.sec.sessions.isolation", descKey: "web.settings.sec.sessions.isolation.desc", keys: ["sessionIsolation.enabled", "sessionIsolation.isolatePlugins", "sessionIsolation.isolateTempFiles"] },
422
422
  ],
423
423
  ui: [
424
- { id: "ui.chat", titleKey: "web.settings.sec.ui.chat", descKey: "web.settings.sec.ui.chat.desc", keys: ["ui.spinner", "ui.showContextStats", "ui.showCompaction"] },
424
+ { id: "ui.chat", titleKey: "web.settings.sec.ui.chat", descKey: "web.settings.sec.ui.chat.desc", keys: ["ui.fontSize", "ui.activityRail", "ui.spinner", "ui.showContextStats", "ui.showCompaction"] },
425
425
  ],
426
426
  security: [
427
427
  { id: "security.switches", titleKey: "web.settings.sec.security.switches", descKey: "web.settings.sec.security.switches.desc", keys: ["security.enabled", "security.bash.enabled", "security.bash.blockDangerousFlags", "security.paths.enabled", "security.contentScan.enabled", "security.network.enabled", "security.sessionEncryption.enabled"] },