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
@@ -1,5 +1,7 @@
1
+ // Канал вывода: единственный владелец терминала (режимы idle/stream/modal/quiet).
1
2
  import { defaultOutputBus } from "./bus";
2
3
  import { formatEventLine } from "./format";
4
+ /** Единственный писатель в терминал; подписывается на шину и рендерит события. */
3
5
  export class OutputChannel {
4
6
  bus;
5
7
  out;
@@ -14,14 +16,26 @@ export class OutputChannel {
14
16
  pending = null;
15
17
  partialOpen = false;
16
18
  /**
17
- * Log/block stdout events captured while a prompt is active. They are
18
- * flushed once per microtask so a synchronous burst (e.g. a 20-line command
19
- * result) produces a single `printAbove` + one frame render instead of one
20
- * per line (which erased/redrew the frame and fired a DSR query each time).
19
+ * Log/block-события stdout, пойманные при активном промпте. Сбрасываются раз
20
+ * в микротаску, чтобы синхронный всплеск (например, 20-строчный результат
21
+ * команды) дал один `printAbove` + один ререндер кадра, а не по одному на
22
+ * строку (каждый стирал/перерисовывал кадр и слал DSR-запрос).
21
23
  */
22
24
  blockLines = null;
23
25
  blockFlushScheduled = false;
24
26
  unsub;
27
+ /**
28
+ * Глушит `info`-логи на человеческом терминале. Структурные логи — это
29
+ * диагностика, а не чат: quiet оставляет только warn/error. `writeLine`-блоки
30
+ * (help, status, вывод команд) не затрагиваются.
31
+ */
32
+ suppressInfoLogs = false;
33
+ /**
34
+ * Показ `data.diagnostic`-событий (внутренняя диагностика петли) в терминале.
35
+ * По умолчанию выкл: диагностика живёт в `.log`/`app.jsonl` и выходит в чат
36
+ * только при `ui.verbosity === "verbose"`.
37
+ */
38
+ showDiagnostics = false;
25
39
  constructor(bus = defaultOutputBus, opts = {}) {
26
40
  this.bus = bus;
27
41
  this.out = opts.out ?? process.stdout;
@@ -30,6 +44,7 @@ export class OutputChannel {
30
44
  this.color = opts.color ?? (!process.env.NO_COLOR && !process.env.CI && outTTY);
31
45
  this.unsub = this.bus.subscribe((ev) => this.onEvent(ev));
32
46
  }
47
+ /** Отписка от шины и сброс состояния. */
33
48
  dispose() {
34
49
  this.flushBlocks();
35
50
  this.unsub();
@@ -39,40 +54,46 @@ export class OutputChannel {
39
54
  this.sink = null;
40
55
  this.mode = "idle";
41
56
  }
57
+ /** Привязка/отвязка промпта (null — терминал свободен). */
42
58
  attachPrompt(sink) {
43
59
  this.sink = sink;
44
60
  }
61
+ /** Текущий режим канала. */
45
62
  getMode() {
46
63
  return this.mode;
47
64
  }
65
+ /** Начало стрима ответа. Quiet не сбрасывается: `--json` молчит весь прогон. */
48
66
  beginStreaming() {
49
- // A streaming transition must never discard the quiet state: `--json`
50
- // sets quiet for the whole run and streamed runs must stay silent.
67
+ // Переход в стрим не должен сбрасывать quiet: `--json` ставит quiet на весь
68
+ // прогон, и стримящиеся прогоны обязаны молчать.
51
69
  if (this.mode === "quiet")
52
70
  return;
53
71
  this.flushBlocks();
54
72
  this.flushPendingAsBlock();
55
73
  this.mode = "streaming";
56
74
  }
75
+ /** Конец стрима. Симметрично beginStreaming: quiet владеет режимом до setQuiet(false). */
57
76
  endStreaming() {
58
- // Symmetric with beginStreaming: quiet owns the mode until setQuiet(false).
77
+ // Симметрично beginStreaming: quiet владеет режимом до setQuiet(false).
59
78
  if (this.mode === "quiet")
60
79
  return;
61
80
  this.closePartial();
62
81
  this.mode = "idle";
63
82
  }
83
+ /** Вход в модалку: события копятся в очередь до endModal. */
64
84
  beginModal() {
65
85
  this.flushBlocks();
66
86
  this.flushPendingAsBlock();
67
87
  if (this.mode === "quiet")
68
- return; // setQuiet owns quiet
69
- // Nested modals (e.g. a wizard command inside another exclusive scope)
70
- // must not overwrite the mode saved by the outermost beginModal.
88
+ return; // quiet владеет setQuiet
89
+ // Вложенные модалки (визард внутри другой эксклюзивной области) не должны
90
+ // затирать режим, сохранённый самой внешней beginModal.
71
91
  if (this.modalDepth === 0)
72
92
  this.modeBeforeModal = this.mode;
73
93
  this.modalDepth++;
74
94
  this.mode = "modal";
75
95
  }
96
+ /** Выход из модалки: сброс очереди накопленных событий в onEvent. */
76
97
  endModal() {
77
98
  if (this.modalDepth > 0)
78
99
  this.modalDepth--;
@@ -83,6 +104,7 @@ export class OutputChannel {
83
104
  for (const ev of q)
84
105
  this.onEvent(ev);
85
106
  }
107
+ /** Вкл/выкл quiet (`--json`): человеческий вывод дропается. */
86
108
  setQuiet(quiet) {
87
109
  this.flushBlocks();
88
110
  if (quiet) {
@@ -95,10 +117,19 @@ export class OutputChannel {
95
117
  this.mode = this.modeBeforeQuiet;
96
118
  }
97
119
  }
120
+ /** Скрыть `info`-логи терминала (warn/error остаются). */
121
+ setSuppressInfoLogs(suppress) {
122
+ this.suppressInfoLogs = suppress;
123
+ }
124
+ /** Показать внутреннюю диагностику (`Logger.diagnostic`) — только verbose. */
125
+ setShowDiagnostics(show) {
126
+ this.showDiagnostics = show;
127
+ }
128
+ /** Сырая запись в поток. В modal/quiet дропается, в idle идёт поверх промпта. */
98
129
  writeRaw(text, stream = "stdout") {
99
- // Raw output must never jump ahead of queued block lines.
130
+ // Сырой вывод не должен обгонять очередь блочных строк.
100
131
  this.flushBlocks();
101
- // modal intentionally drops direct writes: the Renderer never runs during a modal prompt.
132
+ // modal осознанно дропает прямые записи: рендерер в модальном промпте не работает.
102
133
  if (this.mode === "modal" || this.mode === "quiet")
103
134
  return;
104
135
  if (this.mode === "idle" && this.sink?.isActive() && stream === "stdout") {
@@ -111,12 +142,14 @@ export class OutputChannel {
111
142
  this.partialOpen = !text.endsWith("\n");
112
143
  (stream === "stderr" ? this.err : this.out).write(text);
113
144
  }
145
+ /** Блочная запись через шину (help, status, вывод команд). */
114
146
  writeLine(text, stream = "stdout") {
115
147
  this.bus.emit({ level: "info", source: "", kind: "block", text, stream });
116
148
  }
149
+ /** Очистка экрана через промпт или ANSI. Буферный raw-партиал сбрасывается первым. */
117
150
  clearScreen() {
118
151
  this.flushBlocks();
119
- // A buffered raw partial line would otherwise re-appear after the clear.
152
+ // Иначе буферная raw-частичная строка всплывёт после очистки.
120
153
  this.flushPendingAsBlock();
121
154
  this.partialOpen = false;
122
155
  if (this.mode === "quiet")
@@ -127,17 +160,43 @@ export class OutputChannel {
127
160
  }
128
161
  this.out.write("\x1b[2J\x1b[H");
129
162
  }
163
+ /** Оверлей (спиннер) в stderr. В modal/quiet и при активном промпте дропается. */
130
164
  writeOverlay(text) {
131
- // modal intentionally drops overlays: the Renderer never runs during a modal prompt.
132
- // Task 6's Spinner must stop before any log so an overlay never shares a row with a log line.
165
+ // modal осознанно дропает оверлеи: рендерер в модальном промпте не работает.
166
+ // Спиннер обязан остановиться до любого лога, иначе оверлей делит строку с логом.
133
167
  if (this.mode === "modal" || this.mode === "quiet")
134
168
  return;
135
169
  if (this.mode === "idle" && this.sink?.isActive())
136
170
  return;
137
171
  this.err.write(text);
138
172
  }
139
- onEvent(ev) {
173
+ /** Режимы и фильтры, держащие событие вне терминала. */
174
+ shouldDrop(ev) {
140
175
  if (this.mode === "quiet")
176
+ return true;
177
+ // Структурные info-логи — диагностика, а не чат: quiet их прячет.
178
+ if (this.suppressInfoLogs && ev.kind === "log" && ev.level === "info")
179
+ return true;
180
+ // Внутренняя диагностика петли — материал логов сессии, а не чата: в терминал
181
+ // только через verbose (setShowDiagnostics).
182
+ if (!this.showDiagnostics && ev.kind === "log" && ev.data?.diagnostic === true)
183
+ return true;
184
+ return false;
185
+ }
186
+ /** Пакует синхронный всплеск в один printAbove/рендер (см. blockLines). */
187
+ queueBlockLine(line) {
188
+ // Оба потока идут поверх промпта, иначе stderr-лог ляжет прямо на кадр
189
+ // ввода в обход порядка flushBlocks.
190
+ if (this.blockLines === null)
191
+ this.blockLines = [];
192
+ this.blockLines.push(line);
193
+ if (this.blockFlushScheduled)
194
+ return;
195
+ this.blockFlushScheduled = true;
196
+ queueMicrotask(() => this.flushBlocks());
197
+ }
198
+ onEvent(ev) {
199
+ if (this.shouldDrop(ev))
141
200
  return;
142
201
  if (this.mode === "modal") {
143
202
  this.queue.push(ev);
@@ -150,20 +209,11 @@ export class OutputChannel {
150
209
  const stream = ev.stream ?? "stdout";
151
210
  const line = ev.kind === "block" ? ev.text : formatEventLine(ev, this.color);
152
211
  if (this.mode === "idle" && this.sink?.isActive()) {
153
- // Batch a synchronous burst into one printAbove/render (see blockLines).
154
- // Both streams route above the prompt, otherwise a stderr log writes
155
- // directly over the active input frame and skips flushBlocks ordering.
156
- if (this.blockLines === null)
157
- this.blockLines = [];
158
- this.blockLines.push(line);
159
- if (!this.blockFlushScheduled) {
160
- this.blockFlushScheduled = true;
161
- queueMicrotask(() => this.flushBlocks());
162
- }
212
+ this.queueBlockLine(line);
163
213
  return;
164
214
  }
165
- // Preserve FIFO ordering: buffered blocks must not be overtaken by an
166
- // event written straight to a raw stream (e.g. a stderr log mid-stream).
215
+ // Сохраняем FIFO: буферные блоки не должен обгонять эвент, писанный
216
+ // напрямую в raw-поток (например, stderr-лог посреди стрима).
167
217
  this.flushBlocks();
168
218
  if (this.mode === "streaming")
169
219
  this.closePartial();
@@ -222,11 +272,13 @@ export class OutputChannel {
222
272
  }
223
273
  }
224
274
  let defaultChannel = null;
275
+ /** Канал-синглтон текущего процесса. */
225
276
  export function getDefaultChannel() {
226
277
  if (!defaultChannel)
227
278
  defaultChannel = new OutputChannel(defaultOutputBus);
228
279
  return defaultChannel;
229
280
  }
281
+ /** Сброс синглтона (изоляция тестов). */
230
282
  export function resetDefaultChannel() {
231
283
  defaultChannel?.dispose();
232
284
  defaultChannel = null;
@@ -1,3 +1,4 @@
1
+ // Форматирование событий в строки вида `[LEVEL] [source] ISO — text`.
1
2
  import { createColors } from "picocolors";
2
3
  const colored = createColors(true);
3
4
  const LEVEL_COLORS = {
@@ -6,7 +7,7 @@ const LEVEL_COLORS = {
6
7
  warn: (s) => colored.yellow(s),
7
8
  error: (s) => colored.red(s),
8
9
  };
9
- /** Reproduces the legacy Logger format: `[LEVEL] [source] ISO — text`. */
10
+ /** Форматирует событие как в legacy-логере: `[LEVEL] [source] ISO — text`. */
10
11
  export function formatEventLine(ev, color) {
11
12
  const prefix = ev.source ? ` [${ev.source}]` : "";
12
13
  const line = `[${ev.level.toUpperCase()}]${prefix} ${new Date(ev.ts).toISOString()} — ${ev.text}`;
@@ -1,3 +1,4 @@
1
+ // Публичный фасад вывода: типы, шина, канал, стоки, машинный вывод.
1
2
  export * from "./types";
2
3
  export { OutputBus, defaultOutputBus } from "./bus";
3
4
  export { formatEventLine } from "./format";
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Collects bus events so a machine-readable run (`--json`) can include the
3
- * warn/error diagnostics that the human channel suppresses via `setQuiet`.
4
- * Info/debug events are ignored: they are progress noise, not diagnostics.
2
+ * Копит события шины, чтобы машиночитаемый прогон (`--json`) включил
3
+ * warn/error-диагностику, которую человеческий канал гасит через `setQuiet`.
4
+ * Info/debug игнорируются: это шум прогресса, а не диагностика.
5
5
  */
6
+ /** Сток warn/error-диагностики для `--json`. @param bus шина для подписки. */
6
7
  export class JsonOutputSink {
7
8
  events = [];
8
9
  unsub;
@@ -11,6 +12,7 @@ export class JsonOutputSink {
11
12
  this.events.push(ev);
12
13
  });
13
14
  }
15
+ /** Только warn/error события в виде плоских записей. */
14
16
  diagnostics() {
15
17
  return this.events
16
18
  .filter((e) => e.level === "warn" || e.level === "error")
@@ -1,8 +1,9 @@
1
- /** Machine-readable output (CLI JSON contracts). The only place with raw stdout. */
1
+ // Машиночитаемый вывод (JSON-контракты CLI). Единственное место с raw stdout.
2
+ /** Пишет JSON в stdout (контракт `--json`). @param value любой сериализуемый объект. */
2
3
  export function writeMachineJson(value) {
3
4
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
4
5
  }
5
- /** Raw stdout write for the non-TTY REPL fallback (documented single-writer exception). */
6
+ /** Строка в stdout для non-TTY фолбэка REPL (оговорённое исключение single-writer). */
6
7
  export function writeNonTtyLine(text) {
7
- process.stdout.write(text + "\n");
8
+ process.stdout.write(`${text}\n`);
8
9
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Persists explicitly marked output events (`data.persist === true`) into the
3
- * session log. Most output is transient terminal progress and is deliberately
4
- * NOT persisted; the primary file path for durable records stays
5
- * `SessionLogger.logSilent` / `logStructured`.
2
+ * Сохраняет в лог сессии только явно помеченные события (`data.persist === true`).
3
+ * Остальной вывод — транзиентный прогресс терминала и осознанно НЕ персистится;
4
+ * основной файловый путь долговечных записей — `SessionLogger.logSilent` /
5
+ * `logStructured`.
6
6
  */
7
7
  export class SessionOutputSink {
8
8
  session;
@@ -1,10 +1,14 @@
1
+ /** Тул явного Yes/No-подтверждения действия (интерактивный, alwaysOn). */
1
2
  import { t } from "../i18n/index";
2
3
  import { askChoice } from "./user-input";
4
+ /** Просит подтверждение; reject — success:false (сигнал сбоя для цикла). */
3
5
  export const approveTool = {
4
6
  name: "approve",
5
7
  icon: "🙋",
6
8
  description: "Request user approval for an action. The user picks Yes or No from a menu.",
7
9
  tags: ["core"],
10
+ // Как в question.ts: интерактивные тулы переживают сужение (P5).
11
+ alwaysOn: true,
8
12
  interactive: true,
9
13
  parameters: {
10
14
  type: "object",
@@ -1,8 +1,10 @@
1
+ /** Тул аттача картинки в контекст (файл/URL/буфер обмена). */
1
2
  import { existsSync } from "fs";
2
3
  import { resolve } from "path";
3
4
  import { t } from "../i18n/index";
4
5
  import { loadFileAsDataUrl, loadUrlAsDataUrl, readClipboardImage, bufferToDataUrl, } from "../llm/image-utils";
5
6
  import { logSecurityBlock } from "../modules/security/audit-log";
7
+ /** Кладёт картинку в pending-очередь контекста (PNG/JPEG/GIF/WebP). */
6
8
  export const attachImageTool = {
7
9
  name: "attach_image",
8
10
  icon: "🖼️",
@@ -37,7 +39,7 @@ export const attachImageTool = {
37
39
  dataUrl = result.dataUrl;
38
40
  }
39
41
  else if (source.startsWith("http://") || source.startsWith("https://")) {
40
- // Security: validate URL against the network policy (SSRF).
42
+ // URL — по сетевой политике (SSRF).
41
43
  const { isUrlAllowed } = await import("../modules/security/network-validator");
42
44
  const { getSessionSecurityConfig } = await import("../modules/security/session-isolation");
43
45
  const networkConfig = ctx.sessionContext
@@ -59,8 +61,7 @@ export const attachImageTool = {
59
61
  output: t("image.not_found", { path: source }),
60
62
  };
61
63
  }
62
- // Security: check global path policy AND subagent scope (same
63
- // combination as read_file).
64
+ // Пути: глобальная политика + скоуп субагента (та же связка, что у read_file).
64
65
  const { isPathInScope } = await import("../modules/security/path-validator");
65
66
  const { DEFAULT_SECURITY_CONFIG } = await import("../config/security");
66
67
  const validation = isPathInScope(ctx.baseDir, absPath, ctx.scope, ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths);
@@ -74,7 +75,7 @@ export const attachImageTool = {
74
75
  const result = await loadFileAsDataUrl(absPath);
75
76
  dataUrl = result.dataUrl;
76
77
  }
77
- // Add the image as a pending content part on the context manager
78
+ // Картинка — pending content part на менеджере контекста.
78
79
  if (!ctx.contextManager) {
79
80
  return {
80
81
  success: false,
@@ -1,7 +1,6 @@
1
+ /** Автофиксы LSP-ошибок tsc (конфликты импортов, неиспользуемые). */
1
2
  import { extname } from "path";
2
- /**
3
- * LSP error codes that can be auto-fixed.
4
- */
3
+ /** Коды LSP-ошибок, поддающиеся автофиксу. */
5
4
  const AUTO_FIXABLE_CODES = new Set([
6
5
  2307, // Cannot find module (import missing)
7
6
  2440, // Import declaration conflicts with local declaration
@@ -9,8 +8,8 @@ const AUTO_FIXABLE_CODES = new Set([
9
8
  2769, // No overload matches this call (simple type mismatches)
10
9
  ]);
11
10
  /**
12
- * Extract LSP errors from tsc output.
13
- * Format: file(line,col): error TSxxxx: message
11
+ * Извлекает LSP-ошибки из вывода tsc.
12
+ * Формат: file(line,col): error TSxxxx: message. Возврат: список ошибок.
14
13
  */
15
14
  export function parseTscOutput(output) {
16
15
  const errors = [];
@@ -30,10 +29,9 @@ export function parseTscOutput(output) {
30
29
  return errors;
31
30
  }
32
31
  /**
33
- * Try to auto-fix LSP errors in TypeScript/JavaScript code.
34
- * Only fixes common, safe patterns:
35
- * - TS2440: Import conflict → rename import with `as`
36
- * - TS6133: Unused import → remove from import list
32
+ * Пробует автофиксить LSP-ошибки в TS/JS. Только частые безопасные паттерны:
33
+ * - TS2440: конфликт импорта → переименование через `as`;
34
+ * - TS6133: неиспользуемый импорт → удаление из списка.
37
35
  */
38
36
  export function autoFixErrors(filePath, content, errors) {
39
37
  const ext = extname(filePath).toLowerCase();
@@ -71,16 +69,14 @@ export function autoFixErrors(filePath, content, errors) {
71
69
  remaining,
72
70
  };
73
71
  }
74
- /**
75
- * Try to fix a single LSP error.
76
- */
72
+ /** Пробует починить одну LSP-ошибку. */
77
73
  function fixError(content, error) {
78
74
  const lines = content.split("\n");
79
75
  const lineIndex = error.line - 1;
80
76
  if (lineIndex < 0 || lineIndex >= lines.length) {
81
77
  return { fixed: false, newContent: content };
82
78
  }
83
- const line = lines[lineIndex];
79
+ const _line = lines[lineIndex];
84
80
  switch (error.code) {
85
81
  case 2440: // Import declaration conflicts with local declaration
86
82
  return fixImportConflict(lines, lineIndex, error);
@@ -91,25 +87,18 @@ function fixError(content, error) {
91
87
  }
92
88
  }
93
89
  /**
94
- * Fix TS2440: Import declaration conflicts with local declaration.
95
- * Strategy: Add `as LocalName` to the import.
96
- *
97
- * Example:
98
- * import { foo } from './module';
99
- * export function foo() { ... }
100
- * Becomes:
101
- * import { foo as fooImport } from './module';
102
- * export function foo() { ... }
90
+ * Фикс TS2440: конфликт импорта с локальной декларацией.
91
+ * Стратегия: добавить `as LocalName` к импорту (`foo` → `foo as fooImport`).
103
92
  */
104
93
  function fixImportConflict(lines, lineIndex, error) {
105
- const line = lines[lineIndex];
106
- // Find the conflicting name in the error message
94
+ const _line = lines[lineIndex];
95
+ // Конфликтное имя — из текста ошибки.
107
96
  const nameMatch = error.message.match(/Import declaration conflicts with local declaration of '(\w+)'/);
108
97
  if (!nameMatch) {
109
98
  return { fixed: false, newContent: lines.join("\n") };
110
99
  }
111
100
  const conflictingName = nameMatch[1];
112
- // Find the import line (might be above the current line)
101
+ // Строка импорта (может быть выше текущей) — для TS2440-фикса.
113
102
  let importLineIndex = lineIndex;
114
103
  while (importLineIndex >= 0 && !lines[importLineIndex].includes("import")) {
115
104
  importLineIndex--;
@@ -118,14 +107,14 @@ function fixImportConflict(lines, lineIndex, error) {
118
107
  return { fixed: false, newContent: lines.join("\n") };
119
108
  }
120
109
  const importLine = lines[importLineIndex];
121
- // Check if this is a named import: import { ... } from '...'
110
+ // Только именованный импорт вида import { ... } from '...'.
122
111
  const namedImportMatch = importLine.match(/import\s+(?:type\s+)?\{([^}]+)\}\s+from/);
123
112
  if (!namedImportMatch) {
124
113
  return { fixed: false, newContent: lines.join("\n") };
125
114
  }
126
115
  const imports = namedImportMatch[1];
127
116
  const importParts = imports.split(",").map((s) => s.trim());
128
- // Check if the conflicting name is in the imports
117
+ // Конфликтное имя — в списке импортов?
129
118
  const nameIndex = importParts.findIndex((part) => {
130
119
  const baseName = part.split(/\s+as\s+/)[0].trim();
131
120
  return baseName === conflictingName;
@@ -133,36 +122,29 @@ function fixImportConflict(lines, lineIndex, error) {
133
122
  if (nameIndex === -1) {
134
123
  return { fixed: false, newContent: lines.join("\n") };
135
124
  }
136
- // Check if already has an alias
125
+ // Алиас уже есть — не трогаем.
137
126
  const currentImport = importParts[nameIndex];
138
127
  if (currentImport.includes(" as ")) {
139
128
  return { fixed: false, newContent: lines.join("\n") };
140
129
  }
141
- // Add alias
130
+ // Добавить алиас.
142
131
  importParts[nameIndex] = `${conflictingName} as ${conflictingName}Import`;
143
132
  lines[importLineIndex] = importLine.replace(namedImportMatch[1], importParts.join(", "));
144
133
  return { fixed: true, newContent: lines.join("\n") };
145
134
  }
146
135
  /**
147
- * Fix TS6133: Variable is declared but its value is never read.
148
- * Strategy: Remove the unused import from the import list.
149
- *
150
- * Example:
151
- * import { foo, bar } from './module';
152
- * console.log(bar);
153
- * Becomes:
154
- * import { bar } from './module';
155
- * console.log(bar);
136
+ * Фикс TS6133: объявлена, но не читается. Стратегия: выкинуть неиспользуемый
137
+ * импорт из списка (`{ foo, bar }` → `{ bar }`); пустой импорт — строкой целиком.
156
138
  */
157
139
  function fixUnusedImport(lines, lineIndex, error) {
158
- const line = lines[lineIndex];
159
- // Find the unused name in the error message
140
+ const _line = lines[lineIndex];
141
+ // Неиспользуемое имя — из текста ошибки.
160
142
  const nameMatch = error.message.match(/'(\w+)' is declared but its value is never read/);
161
143
  if (!nameMatch) {
162
144
  return { fixed: false, newContent: lines.join("\n") };
163
145
  }
164
146
  const unusedName = nameMatch[1];
165
- // Find the import line (might be above the current line)
147
+ // Строка импорта (может быть выше текущей) — для TS6133-фикса.
166
148
  let importLineIndex = lineIndex;
167
149
  while (importLineIndex >= 0 && !lines[importLineIndex].includes("import")) {
168
150
  importLineIndex--;
@@ -171,14 +153,14 @@ function fixUnusedImport(lines, lineIndex, error) {
171
153
  return { fixed: false, newContent: lines.join("\n") };
172
154
  }
173
155
  const importLine = lines[importLineIndex];
174
- // Check if this is a named import: import { ... } from '...'
156
+ // Только именованный импорт вида import { ... } from '...' (TS6133).
175
157
  const namedImportMatch = importLine.match(/import\s+(?:type\s+)?\{([^}]+)\}\s+from/);
176
158
  if (!namedImportMatch) {
177
159
  return { fixed: false, newContent: lines.join("\n") };
178
160
  }
179
161
  const imports = namedImportMatch[1];
180
162
  const importParts = imports.split(",").map((s) => s.trim());
181
- // Find and remove the unused import
163
+ // Найти и выкинуть неиспользуемый импорт.
182
164
  const nameIndex = importParts.findIndex((part) => {
183
165
  const baseName = part.split(/\s+as\s+/)[0].trim();
184
166
  return baseName === unusedName;
@@ -187,7 +169,7 @@ function fixUnusedImport(lines, lineIndex, error) {
187
169
  return { fixed: false, newContent: lines.join("\n") };
188
170
  }
189
171
  importParts.splice(nameIndex, 1);
190
- // If no imports left, remove the entire import line
172
+ // Импортов не осталось — снести строку целиком.
191
173
  if (importParts.length === 0) {
192
174
  lines.splice(importLineIndex, 1);
193
175
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Детерминированный вывод tool-тегов по типу задачи (P5 auto-narrowing).
3
+ *
4
+ * Главный цикл показывает LLM только `defaultTags` (+ структурные `alwaysOn`);
5
+ * остальное скрыто за `enable_tools`. Без preseed веб-задача тратит первый ход
6
+ * на поиск `enable_tools`, а 9B-модель иногда уходит в shell/MCP (из-за этого
7
+ * сужение и откатывали к `enableOnDemand: false`).
8
+ *
9
+ * Классификатор работает ДО первого LLM-вызова на сыром сообщении и только
10
+ * ДОБАВЛЯЕТ теги (дефолты не снимает): простой чат остаётся узким (file/code +
11
+ * alwaysOn), а research/browser/memory-задача сразу видит нужные тулы.
12
+ * Без LLM-вызова и затрат контекста.
13
+ *
14
+ * Правила — keyword-эвристики (en+ru): `research` со сдвигом в recall
15
+ * (пропуск веб-намерения стоит лишний enable_tools round-trip), `browser`/
16
+ * `shell`/`memory`/`vision` — в precision (простые ходы остаются узкими).
17
+ * Неизвестный текст — дефолты без изменений.
18
+ */
19
+ /** Известные теги тулов. */
20
+ export const KNOWN_TOOL_TAGS = [
21
+ "file",
22
+ "code",
23
+ "shell",
24
+ "research",
25
+ "browser",
26
+ "vision",
27
+ "image",
28
+ "memory",
29
+ "core",
30
+ ];
31
+ const RESEARCH_RE = /search|google|найди|найти|поиск|погод|новост|интернет|сайт|документ|стать|цен|стоим|курс|билет|рейтинг|обзор|рецепт|http|www\.|url|\bsite\b|\bweb\b|fetch|download|скача|загруз|stackoverflow|github\.com|wikipedia|википеди/i;
32
+ const BROWSER_RE = /screenshot|скриншот|playwright|browser|браузер|кликни|click\b|открой.*стран|automation|автоматизац.*браузер|селектор|selector/i;
33
+ const SHELL_RE = /process_|фонов|background|dev.?server|docker|deploy|депло|pipeline|пайплайн|контейнер|процесс/i;
34
+ const MEMORY_RE = /запомни|remember|recall|вспомни|память|\bmemory\b|мой день рождения|my birthday|мой рост|обо мне|preferences|предпочтени/i;
35
+ const VISION_RE = /image|картинк|фото|изображен|скрин|attach|vision|png|\bjpg\b|jpeg|сфотографируй|разбери.*фото/i;
36
+ const CODE_RE = /код|code|typescript|python|javascript|bun|npm|git|коммит|тест|lint|build|сборк|баг|ошибк.*код|файл|file|папк|директори|рефактор|refactor/i;
37
+ /**
38
+ * Дополнительные теги из сообщения пользователя. Всегда подмножество
39
+ * KNOWN_TOOL_TAGS, без `file`/`code` (это дефолты). Чистая функция —
40
+ * безопасно вызывать на каждом ходе.
41
+ */
42
+ export function inferExtraToolTags(input) {
43
+ if (!input || !input.trim())
44
+ return [];
45
+ const extra = new Set();
46
+ if (RESEARCH_RE.test(input))
47
+ extra.add("research");
48
+ if (BROWSER_RE.test(input)) {
49
+ extra.add("browser");
50
+ extra.add("vision");
51
+ }
52
+ if (SHELL_RE.test(input))
53
+ extra.add("shell");
54
+ if (MEMORY_RE.test(input))
55
+ extra.add("memory");
56
+ if (VISION_RE.test(input)) {
57
+ extra.add("vision");
58
+ extra.add("image");
59
+ }
60
+ // Явное code/shell-намерение возвращает `shell` (bash виден и через `code`,
61
+ // но process_*/pipeline нужен именно shell-тег).
62
+ if (CODE_RE.test(input) && /process|фонов|background|pipeline|docker/i.test(input)) {
63
+ extra.add("shell");
64
+ }
65
+ return [...extra];
66
+ }
67
+ /**
68
+ * Аддитивный preseed: дописывает выведенные теги в `activeTags`, возвращает
69
+ * добавленные. Ничего не удаляет (гранты `enable_tools` сохраняются).
70
+ * No-op при выключенном сужении (`activeTags` undefined = legacy-режим).
71
+ */
72
+ export function seedToolTags(activeTags, input) {
73
+ if (!activeTags)
74
+ return [];
75
+ const added = [];
76
+ for (const tag of inferExtraToolTags(input)) {
77
+ if (!activeTags.includes(tag)) {
78
+ activeTags.push(tag);
79
+ added.push(tag);
80
+ }
81
+ }
82
+ return added;
83
+ }