micro-models-agent 2.11.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/assets/app.js +22 -2
  3. package/dist/assets/index.html +39 -9
  4. package/dist/assets/js/dom.js +15 -0
  5. package/dist/assets/js/feed.js +129 -10
  6. package/dist/assets/js/sidebar.js +512 -0
  7. package/dist/assets/js/tabs.js +30 -5
  8. package/dist/assets/setup.js +267 -8
  9. package/dist/assets/styles.css +168 -3
  10. package/dist/cli/cache-line.js +1 -0
  11. package/dist/cli/command-suggest.js +5 -4
  12. package/dist/cli/commands.js +231 -122
  13. package/dist/cli/completer.js +132 -31
  14. package/dist/cli/index.js +1 -0
  15. package/dist/cli/json-payload.js +4 -4
  16. package/dist/cli/main.js +166 -61
  17. package/dist/cli/onboarding-commands.js +126 -0
  18. package/dist/cli/plugin-commands.js +2 -1
  19. package/dist/cli/relaunch.js +16 -13
  20. package/dist/cli/repl-commands-lsp.js +75 -0
  21. package/dist/cli/repl-commands-skills.js +105 -0
  22. package/dist/cli/repl-commands.js +589 -266
  23. package/dist/cli/repl.js +436 -177
  24. package/dist/cli/run-result.js +5 -6
  25. package/dist/cli/security-commands.js +37 -28
  26. package/dist/cli/setup-order.js +4 -55
  27. package/dist/cli/setup-prompt.js +3 -2
  28. package/dist/cli/setup.js +27 -107
  29. package/dist/cli/web-command.js +98 -0
  30. package/dist/config/budget.js +5 -5
  31. package/dist/config/config.js +103 -96
  32. package/dist/config/defaults.js +38 -8
  33. package/dist/config/domains.js +24 -27
  34. package/dist/config/env.js +46 -0
  35. package/dist/config/experts.js +2 -0
  36. package/dist/config/index.js +1 -0
  37. package/dist/config/paths.js +56 -0
  38. package/dist/config/redact.js +44 -0
  39. package/dist/config/registry-extra.js +334 -0
  40. package/dist/config/registry.js +584 -0
  41. package/dist/config/security.js +35 -36
  42. package/dist/config/timeouts.js +74 -0
  43. package/dist/config/utils.js +16 -15
  44. package/dist/core/agent/artifact-progress.js +59 -0
  45. package/dist/core/agent/artifact-resolve.js +79 -0
  46. package/dist/core/agent/audit-gate.js +7 -3
  47. package/dist/core/agent/compaction.js +2 -2
  48. package/dist/core/agent/constants.js +33 -4
  49. package/dist/core/agent/context-log.js +454 -0
  50. package/dist/core/agent/context-renderer.js +5 -0
  51. package/dist/core/agent/critic-gate.js +132 -0
  52. package/dist/core/agent/hallucination-gate.js +148 -38
  53. package/dist/core/agent/loop-state.js +15 -0
  54. package/dist/core/agent/prefix-monitor.js +1 -1
  55. package/dist/core/agent/reasoning-resolver.js +10 -1
  56. package/dist/core/agent/token-tracker.js +7 -4
  57. package/dist/core/agent/tool-batch.js +93 -18
  58. package/dist/core/agent/tool-images.js +18 -0
  59. package/dist/core/agent/tool-output.js +65 -11
  60. package/dist/core/agent/tool-pairing.js +60 -0
  61. package/dist/core/agent-moe.js +25 -24
  62. package/dist/core/agent.js +429 -108
  63. package/dist/core/bootstrap/agent-phase.js +66 -0
  64. package/dist/core/bootstrap/config-phase.js +86 -0
  65. package/dist/core/bootstrap/modules-phase.js +269 -0
  66. package/dist/core/bootstrap/provider-phase.js +183 -0
  67. package/dist/core/bootstrap/session-phase.js +204 -0
  68. package/dist/core/bootstrap/state.js +94 -0
  69. package/dist/core/bootstrap/workspace-phase.js +115 -0
  70. package/dist/core/bootstrap.js +92 -660
  71. package/dist/core/crash-handler.js +17 -7
  72. package/dist/core/environment.js +37 -12
  73. package/dist/core/host-plan.js +43 -0
  74. package/dist/core/host.js +23 -0
  75. package/dist/core/index.js +1 -0
  76. package/dist/core/prompt-builder.js +17 -1
  77. package/dist/core/prompt-overflow.js +42 -43
  78. package/dist/core/session-logger.js +122 -30
  79. package/dist/core/system-prompt.js +117 -0
  80. package/dist/core/version.js +6 -4
  81. package/dist/core/web-host.js +34 -0
  82. package/dist/core/workspace.js +19 -15
  83. package/dist/i18n/en.json +89 -3
  84. package/dist/i18n/index.js +37 -3
  85. package/dist/i18n/ru.json +89 -3
  86. package/dist/index.js +7 -3
  87. package/dist/llm/image-utils.js +55 -64
  88. package/dist/llm/llm-errors.js +9 -0
  89. package/dist/llm/model-loader.js +10 -32
  90. package/dist/llm/models-dev.js +121 -0
  91. package/dist/llm/openai-compat.js +146 -73
  92. package/dist/llm/orchestrator.js +33 -11
  93. package/dist/llm/provider-budget.js +2 -2
  94. package/dist/llm/provider-context.js +233 -0
  95. package/dist/llm/provider.js +1 -1
  96. package/dist/llm/response.js +5 -0
  97. package/dist/llm/token-counter.js +27 -11
  98. package/dist/logger/app-logger.js +48 -22
  99. package/dist/logger/file-log.js +28 -19
  100. package/dist/logger/index.js +1 -0
  101. package/dist/main.js +888 -139
  102. package/dist/migration/backup.js +14 -3
  103. package/dist/migration/detect.js +12 -3
  104. package/dist/migration/index.js +1 -0
  105. package/dist/modules/artifacts/store.js +9 -8
  106. package/dist/modules/browser/actions.js +6 -0
  107. package/dist/modules/browser/bridge-client.js +14 -12
  108. package/dist/modules/browser/bridge-path.js +4 -4
  109. package/dist/modules/browser/console-buffer.js +37 -0
  110. package/dist/modules/browser/cookie-store.js +5 -0
  111. package/dist/modules/browser/driver-types.js +1 -0
  112. package/dist/modules/browser/driver.js +19 -11
  113. package/dist/modules/browser/index.js +3 -1
  114. package/dist/modules/browser/module.js +2 -1
  115. package/dist/modules/browser/session.js +10 -30
  116. package/dist/modules/browser/snapshot.js +19 -13
  117. package/dist/modules/browser/types.js +1 -0
  118. package/dist/modules/certification/cli.js +28 -20
  119. package/dist/modules/certification/fact-checker.js +9 -1
  120. package/dist/modules/certification/loader.js +9 -2
  121. package/dist/modules/certification/manifest.js +83 -21
  122. package/dist/modules/certification/runner.js +23 -13
  123. package/dist/modules/certification/scenarios.js +20 -20
  124. package/dist/modules/certification/syntax-scenarios.js +1 -2
  125. package/dist/modules/context/chunk-query.js +11 -9
  126. package/dist/modules/context/fact-extractor.js +37 -39
  127. package/dist/modules/context/index.js +1 -0
  128. package/dist/modules/context/manager.js +106 -80
  129. package/dist/modules/critic/client.js +176 -0
  130. package/dist/modules/critic/evidence.js +81 -0
  131. package/dist/modules/critic/provider.js +45 -0
  132. package/dist/modules/critic/tools.js +78 -0
  133. package/dist/modules/critic/types.js +1 -0
  134. package/dist/modules/execution/audit-runners.js +28 -34
  135. package/dist/modules/execution/auditor.js +53 -49
  136. package/dist/modules/execution/execution-plugin.js +142 -102
  137. package/dist/modules/execution/index.js +1 -1
  138. package/dist/modules/execution/input-from.js +37 -13
  139. package/dist/modules/execution/module.js +368 -191
  140. package/dist/modules/execution/moe-executor.js +53 -26
  141. package/dist/modules/execution/plan-actions.js +320 -202
  142. package/dist/modules/execution/plan-checks.js +35 -0
  143. package/dist/modules/execution/plan-coverage.js +36 -33
  144. package/dist/modules/execution/plan-steps.js +11 -3
  145. package/dist/modules/execution/plan-store.js +83 -17
  146. package/dist/modules/execution/plan-tool.js +89 -35
  147. package/dist/modules/execution/plan-validator.js +30 -10
  148. package/dist/modules/execution/planner.js +22 -13
  149. package/dist/modules/execution/stuck-detector.js +196 -93
  150. package/dist/modules/execution/tracker.js +22 -8
  151. package/dist/modules/execution/verifier.js +69 -48
  152. package/dist/modules/execution/windows-commands.js +12 -12
  153. package/dist/modules/hallucination/artifact-claim.js +93 -0
  154. package/dist/modules/hallucination/confidence.js +13 -11
  155. package/dist/modules/hallucination/consistency.js +10 -2
  156. package/dist/modules/hallucination/detector.js +10 -5
  157. package/dist/modules/hallucination/factual.js +43 -26
  158. package/dist/modules/hallucination/index.js +1 -0
  159. package/dist/modules/hallucination/js-identifiers.js +44 -45
  160. package/dist/modules/hallucination/llm-judge.js +17 -12
  161. package/dist/modules/hallucination/success-claim.js +59 -0
  162. package/dist/modules/indexer/cache.js +52 -4
  163. package/dist/modules/indexer/index.js +1 -0
  164. package/dist/modules/indexer/map-command.js +2 -4
  165. package/dist/modules/indexer/map-select.js +16 -16
  166. package/dist/modules/indexer/module.js +47 -12
  167. package/dist/modules/indexer/project-profile.js +8 -7
  168. package/dist/modules/indexer/symbols.js +15 -15
  169. package/dist/modules/indexer/walker.js +159 -29
  170. package/dist/modules/lsp/check-tool.js +7 -5
  171. package/dist/modules/lsp/client.js +39 -39
  172. package/dist/modules/lsp/command.js +17 -24
  173. package/dist/modules/lsp/config.js +12 -11
  174. package/dist/modules/lsp/index.js +1 -0
  175. package/dist/modules/lsp/module.js +48 -46
  176. package/dist/modules/lsp/probe.js +13 -14
  177. package/dist/modules/lsp/project-root.js +14 -17
  178. package/dist/modules/lsp/startup-check.js +18 -16
  179. package/dist/modules/mcp/client.js +59 -62
  180. package/dist/modules/mcp/index.js +1 -0
  181. package/dist/modules/mcp/module.js +66 -37
  182. package/dist/modules/mcp/registry.js +5 -0
  183. package/dist/modules/memory/index.js +1 -0
  184. package/dist/modules/memory/module.js +12 -8
  185. package/dist/modules/memory/search.js +4 -1
  186. package/dist/modules/memory/store.js +40 -2
  187. package/dist/modules/onboarding/init.js +47 -0
  188. package/dist/modules/onboarding/meet.js +30 -0
  189. package/dist/modules/onboarding/mode.js +30 -0
  190. package/dist/modules/onboarding/prompts.js +43 -0
  191. package/dist/modules/pipelines/engine.js +10 -0
  192. package/dist/modules/pipelines/index.js +1 -0
  193. package/dist/modules/pipelines/parser.js +4 -1
  194. package/dist/modules/pipelines/template.js +4 -0
  195. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  196. package/dist/modules/plugins/builtin/notify.js +2 -0
  197. package/dist/modules/plugins/index.js +1 -0
  198. package/dist/modules/plugins/loader.js +18 -3
  199. package/dist/modules/plugins/manager.js +37 -17
  200. package/dist/modules/pricing/index.js +10 -11
  201. package/dist/modules/pricing/prices.js +12 -12
  202. package/dist/modules/processes/index.js +1 -0
  203. package/dist/modules/processes/kill-tree.js +27 -31
  204. package/dist/modules/processes/registry.js +44 -25
  205. package/dist/modules/processes/runner.js +4 -4
  206. package/dist/modules/providers/capabilities.js +28 -0
  207. package/dist/modules/providers/create.js +3 -4
  208. package/dist/modules/providers/factory.js +7 -7
  209. package/dist/modules/providers/fallback.js +12 -12
  210. package/dist/modules/providers/health.js +4 -4
  211. package/dist/modules/providers/manager.js +50 -19
  212. package/dist/modules/providers/naming.js +15 -0
  213. package/dist/modules/providers/preflight.js +71 -0
  214. package/dist/modules/providers/presets.js +39 -8
  215. package/dist/modules/providers/registry.js +7 -2
  216. package/dist/modules/reasoning/policy.js +8 -6
  217. package/dist/modules/reasoning/probe.js +30 -25
  218. package/dist/modules/security/audit-log.js +19 -36
  219. package/dist/modules/security/audit-notifier.js +35 -75
  220. package/dist/modules/security/command-validator.js +99 -51
  221. package/dist/modules/security/content-scanner.js +8 -8
  222. package/dist/modules/security/data-sanitizer.js +9 -16
  223. package/dist/modules/security/encryption.js +32 -52
  224. package/dist/modules/security/index.js +2 -1
  225. package/dist/modules/security/network-validator.js +120 -43
  226. package/dist/modules/security/path-validator.js +61 -46
  227. package/dist/modules/security/protected-paths.js +73 -0
  228. package/dist/modules/security/rate-limiter.js +17 -49
  229. package/dist/modules/security/security-policies.js +25 -57
  230. package/dist/modules/security/session-encryption.js +32 -71
  231. package/dist/modules/security/session-isolation.js +15 -28
  232. package/dist/modules/session/auto-name.js +63 -0
  233. package/dist/modules/session/export.js +98 -0
  234. package/dist/modules/session/index.js +1 -0
  235. package/dist/modules/session/manager.js +79 -28
  236. package/dist/modules/session/module.js +4 -3
  237. package/dist/modules/session/store.js +57 -20
  238. package/dist/modules/setup/answers.js +35 -0
  239. package/dist/modules/setup/apply.js +79 -0
  240. package/dist/modules/setup/discovery.js +72 -0
  241. package/dist/modules/skills/index.js +1 -0
  242. package/dist/modules/skills/loader.js +9 -1
  243. package/dist/modules/skills/module.js +11 -0
  244. package/dist/modules/updater/changelog-reader.js +15 -16
  245. package/dist/modules/updater/checker.js +9 -4
  246. package/dist/modules/updater/dev-detect.js +7 -6
  247. package/dist/modules/updater/index.js +1 -0
  248. package/dist/modules/updater/module.js +17 -15
  249. package/dist/modules/user-profile/compressor.js +2 -0
  250. package/dist/modules/user-profile/index.js +1 -0
  251. package/dist/modules/user-profile/profile.js +10 -1
  252. package/dist/modules/webui/answer-api.js +29 -0
  253. package/dist/modules/webui/assets.js +50 -0
  254. package/dist/modules/webui/body.js +21 -0
  255. package/dist/modules/webui/commands-settings.js +209 -0
  256. package/dist/modules/webui/commands-shared.js +60 -0
  257. package/dist/modules/webui/commands.js +174 -0
  258. package/dist/modules/webui/config-api.js +134 -0
  259. package/dist/modules/webui/file-api.js +202 -0
  260. package/dist/modules/webui/forward.js +159 -0
  261. package/dist/modules/webui/history-api.js +112 -0
  262. package/dist/modules/webui/host-state.js +60 -0
  263. package/dist/modules/webui/i18n-api.js +9 -0
  264. package/dist/modules/webui/limits.js +6 -0
  265. package/dist/modules/webui/log-forward.js +20 -0
  266. package/dist/modules/webui/memory-commands.js +64 -0
  267. package/dist/modules/webui/model-cache.js +18 -0
  268. package/dist/modules/webui/module.js +148 -0
  269. package/dist/modules/webui/persist.js +24 -0
  270. package/dist/modules/webui/prompt-io.js +97 -0
  271. package/dist/modules/webui/providers-api.js +337 -0
  272. package/dist/modules/webui/providers-view.js +56 -0
  273. package/dist/modules/webui/reasoning-view.js +15 -0
  274. package/dist/modules/webui/run-agent.js +81 -0
  275. package/dist/modules/webui/server.js +284 -0
  276. package/dist/modules/webui/sessions-api.js +178 -0
  277. package/dist/modules/webui/settings-api.js +561 -0
  278. package/dist/modules/webui/setup-api.js +201 -0
  279. package/dist/modules/webui/state.js +74 -0
  280. package/dist/modules/webui/util.js +261 -0
  281. package/dist/output/bus.js +7 -2
  282. package/dist/output/channel.js +80 -28
  283. package/dist/output/format.js +2 -1
  284. package/dist/output/index.js +1 -0
  285. package/dist/output/json-sink.js +5 -3
  286. package/dist/output/machine.js +4 -3
  287. package/dist/output/session-sink.js +4 -4
  288. package/dist/tools/approve.js +4 -0
  289. package/dist/tools/attach-image.js +5 -4
  290. package/dist/tools/auto-fixer.js +26 -44
  291. package/dist/tools/auto-tags.js +83 -0
  292. package/dist/tools/bash.js +193 -116
  293. package/dist/tools/browser.js +76 -31
  294. package/dist/tools/chunk-query.js +14 -6
  295. package/dist/tools/create-dir.js +9 -5
  296. package/dist/tools/delete-file.js +9 -5
  297. package/dist/tools/download-file.js +32 -7
  298. package/dist/tools/edit-file.js +52 -14
  299. package/dist/tools/enable-tools.js +6 -6
  300. package/dist/tools/executor.js +132 -27
  301. package/dist/tools/file-info.js +3 -1
  302. package/dist/tools/filter-tools.js +3 -3
  303. package/dist/tools/glob-tool.js +4 -1
  304. package/dist/tools/grep-tool.js +13 -10
  305. package/dist/tools/hidden-tools-block.js +6 -6
  306. package/dist/tools/index.js +16 -8
  307. package/dist/tools/list-dir.js +4 -2
  308. package/dist/tools/load-skill.js +7 -0
  309. package/dist/tools/mcp-call.js +15 -4
  310. package/dist/tools/meet-done.js +14 -0
  311. package/dist/tools/move-file.js +14 -7
  312. package/dist/tools/path-utils.js +29 -29
  313. package/dist/tools/pipeline-run.js +29 -7
  314. package/dist/tools/preview.js +1 -1
  315. package/dist/tools/process-ids.js +31 -0
  316. package/dist/tools/process-kill.js +9 -16
  317. package/dist/tools/process-list.js +1 -0
  318. package/dist/tools/process-log.js +11 -16
  319. package/dist/tools/process-wait.js +63 -0
  320. package/dist/tools/prompt-io.js +4 -3
  321. package/dist/tools/question.js +172 -25
  322. package/dist/tools/read-file.js +55 -34
  323. package/dist/tools/recall.js +14 -8
  324. package/dist/tools/registry.js +6 -5
  325. package/dist/tools/remember.js +8 -4
  326. package/dist/tools/scope-request.js +11 -6
  327. package/dist/tools/search-history.js +5 -2
  328. package/dist/tools/session-info.js +2 -0
  329. package/dist/tools/set-thinking.js +8 -4
  330. package/dist/tools/subagent.js +59 -39
  331. package/dist/tools/syntax-validator.js +292 -90
  332. package/dist/tools/user-input.js +22 -18
  333. package/dist/tools/web-browse.js +9 -3
  334. package/dist/tools/web-fetch.js +12 -23
  335. package/dist/tools/web-search.js +7 -5
  336. package/dist/tools/write-file.js +45 -15
  337. package/dist/ui/action-log.js +13 -0
  338. package/dist/ui/box.js +4 -3
  339. package/dist/ui/colors.js +2 -0
  340. package/dist/ui/diff.js +6 -5
  341. package/dist/ui/index.js +1 -0
  342. package/dist/ui/line-editor.js +230 -122
  343. package/dist/ui/line-math.js +10 -2
  344. package/dist/ui/md-formatter.js +57 -37
  345. package/dist/ui/output.js +8 -3
  346. package/dist/ui/plan-view.js +21 -10
  347. package/dist/ui/renderer.js +393 -67
  348. package/dist/ui/spinner.js +16 -5
  349. package/dist/ui/table.js +15 -17
  350. package/dist/ui/theme.js +143 -0
  351. package/dist/utils/child-run.js +20 -0
  352. package/dist/utils/error.js +1 -1
  353. package/dist/utils/index.js +2 -0
  354. package/dist/utils/retry.js +2 -2
  355. package/dist/utils/truncate.js +1 -1
  356. package/package.json +1 -1
@@ -85,8 +85,9 @@ Respond with JSON only (no markdown fences):
85
85
  or
86
86
  {"action": "reject", "explanation": "why"}`;
87
87
  const DEFAULT_BASE_URL = "http://localhost:1234/v1";
88
- /** Fallback context window when neither the provider entry nor the config sets one. */
88
+ /** Запасное окно контекста, если ни запись провайдера, ни конфиг его не задают. */
89
89
  const DEFAULT_ORCH_CONTEXT_WINDOW = 32768;
90
+ /** Клиент оркестратора: строит план, сливает результаты, роутит scope-запросы. */
90
91
  export class OrchestratorClient {
91
92
  config;
92
93
  provider = null;
@@ -110,11 +111,10 @@ export class OrchestratorClient {
110
111
  }
111
112
  }
112
113
  /**
113
- * Build the orchestrator provider through ProviderManager so per-entry
114
- * contextWindow/retry/rateLimits and failover entries are honored
115
- * (legacy {type,baseUrl,apiKey} configs are normalized by the manager).
116
- * Previously the provider was built directly with a hardcoded 32768 window
117
- * and no retry/rate-limit/failover support at all.
114
+ * Строит провайдер оркестратора через ProviderManager: учитываются
115
+ * per-entry contextWindow/retry/rateLimits и failover-записи (легаси
116
+ * {type,baseUrl,apiKey} менеджер нормализует). Раньше провайдер строился
117
+ * напрямую с хардкодом окна 32768 — без ретраев/лимитов/failover вовсе.
118
118
  */
119
119
  buildManagedProvider(config) {
120
120
  const providerCfg = config.provider;
@@ -134,6 +134,10 @@ export class OrchestratorClient {
134
134
  }
135
135
  return provider;
136
136
  }
137
+ /**
138
+ * Включён ли оркестратор (задана ли модель).
139
+ * @returns true, если провайдер построен.
140
+ */
137
141
  isEnabled() {
138
142
  return this.provider !== null;
139
143
  }
@@ -171,6 +175,11 @@ export class OrchestratorClient {
171
175
  }
172
176
  }
173
177
  }
178
+ /**
179
+ * Строит MoE-план из задачи пользователя.
180
+ * @param userPrompt задача; @param _context неиспользуемый контекст;
181
+ * @returns план с сырым ответом либо ошибку.
182
+ */
174
183
  async plan(userPrompt, _context, signal) {
175
184
  if (!this.provider)
176
185
  return { error: "Orchestrator not enabled — no orchestrator model configured" };
@@ -180,11 +189,16 @@ export class OrchestratorClient {
180
189
  ];
181
190
  const text = await this.chat(messages, signal);
182
191
  const parsed = this.parseJSON(text);
183
- if (parsed && parsed.subtasks && parsed.subtasks.length > 0) {
192
+ if (parsed?.subtasks && parsed.subtasks.length > 0) {
184
193
  return { plan: parsed, raw: text };
185
194
  }
186
195
  return { error: `Failed to parse plan from LLM output. Raw: ${text.slice(0, 500)}` };
187
196
  }
197
+ /**
198
+ * Сливает результаты сабтасков: финал либо запрос re-plan.
199
+ * @param input план, результаты, ошибки/варнинги верификатора;
200
+ * @returns финал с ответом либо replan с обновлённым планом.
201
+ */
188
202
  async verifyAndMerge(input, signal) {
189
203
  if (!this.provider)
190
204
  return { type: "final", finalAnswer: input.results.map((r) => r.summary).join("\n") };
@@ -195,7 +209,7 @@ export class OrchestratorClient {
195
209
  ];
196
210
  const text = await this.chat(messages, signal);
197
211
  const parsed = this.parseJSON(text);
198
- if (parsed && parsed.type) {
212
+ if (parsed?.type) {
199
213
  return parsed;
200
214
  }
201
215
  return {
@@ -205,8 +219,8 @@ export class OrchestratorClient {
205
219
  };
206
220
  }
207
221
  /**
208
- * Router decision on a sub-agent's scope request. Defaults to reject on any
209
- * parse failure — scope must never expand silently.
222
+ * Решение роутера по scope-запросу субагента. Дефолт на любой ошибке
223
+ * парсинга — reject: scope молча не расширяется никогда.
210
224
  */
211
225
  async resolveScopeRequest(subtaskId, request, signal) {
212
226
  if (!this.provider)
@@ -223,7 +237,7 @@ export class OrchestratorClient {
223
237
  if (parsed && parsed.action === "approve") {
224
238
  const write = Array.isArray(parsed.write) ? parsed.write.map(String) : [];
225
239
  const read = Array.isArray(parsed.read) ? parsed.read.map(String) : [];
226
- // An approve without any concrete files is meaningless — treat as reject.
240
+ // Approve без конкретных файлов бессмыслен — считаем reject.
227
241
  if (write.length === 0 && read.length === 0) {
228
242
  return { action: "reject", explanation: "approve carried no files" };
229
243
  }
@@ -236,6 +250,10 @@ export class OrchestratorClient {
236
250
  : `unparseable router decision: ${text.slice(0, 200)}`,
237
251
  };
238
252
  }
253
+ /**
254
+ * Строит простой план-список шагов (легаси-поверхность).
255
+ * @param task задача; @returns шаги плана (фолбэк — сама задача).
256
+ */
239
257
  async createPlan(task) {
240
258
  if (!this.provider)
241
259
  throw new Error("Orchestrator not enabled");
@@ -252,6 +270,10 @@ export class OrchestratorClient {
252
270
  return parsed;
253
271
  return { steps: [task] };
254
272
  }
273
+ /**
274
+ * Рекомендует путь разрешения конфликта.
275
+ * @param context описание ситуации; @returns рекомендация текстом.
276
+ */
255
277
  async resolveConflict(context) {
256
278
  if (!this.provider)
257
279
  throw new Error("Orchestrator not enabled");
@@ -1,10 +1,10 @@
1
- // src/llm/provider-budget
1
+ /** Баланс провайдера: запрос расхода/остатка (только OpenRouter по API). */
2
2
  /**
3
3
  * Запрос баланса/расхода у провайдера. Поддерживается только OpenRouter —
4
4
  * единственный, кто отдаёт эти данные по API-ключу. Zen/Go баланс не
5
5
  * возвращают (только веб-дашборд), поэтому для них честно `unsupported`.
6
6
  */
7
- const DEFAULT_TIMEOUT_MS = 5000;
7
+ import { PROVIDER_BUDGET_TIMEOUT_MS as DEFAULT_TIMEOUT_MS } from "../config/timeouts";
8
8
  /** Приводит значение к конечному числу; иначе undefined. */
9
9
  function num(value) {
10
10
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
@@ -0,0 +1,233 @@
1
+ import { lookupModelsDevContext } from "./models-dev";
2
+ /**
3
+ * Спрашивает нативный API бэкенда (LM Studio `/api/v1/models`) о длине
4
+ * контекста загруженной модели И заявленных capabilities. Длина — из живого
5
+ * инстанса (`loaded_instances[].config.context_length`), фолбэк —
6
+ * `max_context_length`. Capabilities читаются из `capabilities` (`vision`,
7
+ * `trained_for_tool_use`), если есть — это единый источник правды вместо
8
+ * гадания по имени.
9
+ *
10
+ * Null, если эндпоинт недоступен (не LM Studio), модели нет/снята или запрос
11
+ * упал — вызывающий деградирует молча.
12
+ */
13
+ export async function probeModel(baseUrl, model, timeoutMs = 3000) {
14
+ const base = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
15
+ try {
16
+ const response = await fetch(`${base}/api/v1/models`, {
17
+ signal: AbortSignal.timeout(timeoutMs),
18
+ });
19
+ if (!response.ok)
20
+ return null;
21
+ const data = (await response.json());
22
+ const models = data.models ?? [];
23
+ const found = models.find((m) => m.key === model || m.name === model || (m.key ? model.includes(m.key) : false));
24
+ if (!found)
25
+ return null;
26
+ const loadedInstance = (found.loaded_instances ?? [])[0];
27
+ const contextLength = loadedInstance?.config?.context_length ?? found.max_context_length;
28
+ if (typeof contextLength !== "number" || contextLength <= 0)
29
+ return null;
30
+ const raw = found.capabilities;
31
+ let capabilities;
32
+ if (raw && typeof raw.vision === "boolean") {
33
+ capabilities = { vision: raw.vision };
34
+ if (typeof raw.trained_for_tool_use === "boolean") {
35
+ capabilities.tools = raw.trained_for_tool_use;
36
+ }
37
+ }
38
+ return { model: found.key ?? found.name ?? model, contextLength, capabilities };
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Тонкая context-only проекция {@link probeModel}. Для вызывающих, кому нужно
46
+ * только окно контекста (баннер, диагностика context-probe).
47
+ */
48
+ export async function getLoadedContextLength(baseUrl, model, timeoutMs = 3000) {
49
+ const probe = await probeModel(baseUrl, model, timeoutMs);
50
+ return probe ? { model: probe.model, actual: probe.contextLength } : null;
51
+ }
52
+ /** Числовые context-подобные поля, что может нести запись `/models`. */
53
+ const CONTEXT_KEYS = [
54
+ "context_length",
55
+ "context_window",
56
+ "max_context_length",
57
+ "max_context_window",
58
+ "max_model_len",
59
+ "maxModelLen",
60
+ "n_ctx",
61
+ "num_ctx",
62
+ "contextLength",
63
+ "contextWindow",
64
+ ];
65
+ function asPositiveInt(v) {
66
+ const n = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
67
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
68
+ }
69
+ /**
70
+ * Первое положительное среди известных context-полей объекта. OpenRouter
71
+ * кладёт per-endpoint значение в `top_provider.context_length`, поэтому один
72
+ * уровень вложенности тоже покрыт.
73
+ */
74
+ function pickContextField(obj) {
75
+ for (const k of CONTEXT_KEYS) {
76
+ const n = asPositiveInt(obj[k]);
77
+ if (n !== null)
78
+ return n;
79
+ }
80
+ const top = obj.top_provider ?? obj.topProvider;
81
+ if (top && typeof top === "object") {
82
+ for (const k of CONTEXT_KEYS) {
83
+ const n = asPositiveInt(top[k]);
84
+ if (n !== null)
85
+ return n;
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ function matchesModel(entry, model) {
91
+ for (const k of ["id", "name", "model", "key"]) {
92
+ const v = entry[k];
93
+ if (typeof v === "string" && v && (v === model || model.includes(v) || v.includes(model))) {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ async function fetchJson(url, timeoutMs, init) {
100
+ try {
101
+ const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
102
+ if (!response.ok)
103
+ return null;
104
+ return (await response.json());
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /** Context-поля живого инстанса из Ollama `/api/ps`, если есть. */
111
+ async function probeOllamaPs(origin, model, timeoutMs) {
112
+ const ps = await fetchJson(`${origin}/api/ps`, timeoutMs);
113
+ const running = ps?.models;
114
+ if (!Array.isArray(running))
115
+ return null;
116
+ const found = running.find((m) => m && typeof m === "object" && matchesModel(m, model));
117
+ if (!found)
118
+ return null;
119
+ return pickContextField(found);
120
+ }
121
+ function contextFromModelInfo(bag) {
122
+ for (const [k, v] of Object.entries(bag)) {
123
+ if (k.toLowerCase().includes("context_length") || k.toLowerCase().endsWith("n_ctx")) {
124
+ const n = asPositiveInt(v);
125
+ if (n !== null)
126
+ return n;
127
+ }
128
+ }
129
+ return pickContextField(bag);
130
+ }
131
+ /** Ollama `/api/show`: детали модели, пробуем полное и короткое имя. */
132
+ async function probeOllamaShow(origin, model, timeoutMs) {
133
+ const short = model.split(":")[0];
134
+ for (const name of [model, short]) {
135
+ const show = await fetchJson(`${origin}/api/show`, timeoutMs, {
136
+ method: "POST",
137
+ headers: { "Content-Type": "application/json" },
138
+ body: JSON.stringify({ name }),
139
+ });
140
+ const info = show;
141
+ const bag = info?.model_info ?? info?.modelInfo;
142
+ if (bag && typeof bag === "object") {
143
+ const n = contextFromModelInfo(bag);
144
+ if (n !== null)
145
+ return n;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ /** Нативный Ollama: живые инстансы (`/api/ps`) + детали (`/api/show`). */
151
+ async function probeOllama(origin, model, timeoutMs) {
152
+ // `/api/show` сообщает контекст модели (напр. `llama.context_length` в
153
+ // `model_info`), даже когда `/api/ps` размерных полей не несёт.
154
+ return ((await probeOllamaPs(origin, model, timeoutMs)) ??
155
+ (await probeOllamaShow(origin, model, timeoutMs)));
156
+ }
157
+ /** Сервер llama.cpp: одномодельный `/props` несёт загруженный `n_ctx`. */
158
+ async function probeLlamaCpp(origin, timeoutMs) {
159
+ const props = await fetchJson(`${origin}/props`, timeoutMs);
160
+ if (!props || typeof props !== "object")
161
+ return null;
162
+ const bag = props;
163
+ const direct = pickContextField(bag);
164
+ if (direct !== null)
165
+ return direct;
166
+ const gen = bag.default_generation_settings;
167
+ if (gen && typeof gen === "object") {
168
+ return pickContextField(gen);
169
+ }
170
+ return null;
171
+ }
172
+ /**
173
+ * OpenAI-совместимый `/models` с расширенными метаданными. Серверы с голыми id
174
+ * (чистый OpenAI, Zen/Go) context-полей не несут → null.
175
+ */
176
+ async function probeOpenAICompatModels(baseUrl, model, apiKey, timeoutMs) {
177
+ const url = `${baseUrl.replace(/\/+$/, "")}/models`;
178
+ const headers = { "Content-Type": "application/json" };
179
+ if (apiKey && apiKey !== "not-needed")
180
+ headers.Authorization = `Bearer ${apiKey}`;
181
+ const data = await fetchJson(url, timeoutMs, { headers });
182
+ const list = data?.data;
183
+ if (!Array.isArray(list))
184
+ return null;
185
+ const found = list.find((m) => !!m && typeof m === "object" && matchesModel(m, model));
186
+ if (!found)
187
+ return null;
188
+ return pickContextField(found);
189
+ }
190
+ /**
191
+ * Лукап каталога models.dev — последний шанс для OpenCode Zen/Go. Их `/models`
192
+ * отдаёт голые id, а models.dev (открытая БД, что использует сам opencode)
193
+ * держит модели провайдера `opencode` под теми же id с `limit.context`.
194
+ * Гейт на Zen-URL/тип: локальные бэкенды ради этого в сеть не ходят.
195
+ */
196
+ async function probeModelsDev(baseUrl, model, providerType) {
197
+ const isZen = providerType === "opencode-zen" ||
198
+ providerType === "opencode-go" ||
199
+ /opencode\.ai\/zen/.test(baseUrl);
200
+ if (!isZen)
201
+ return null;
202
+ return lookupModelsDevContext(model);
203
+ }
204
+ /**
205
+ * Пробует все источники по порядку, возвращает первый давший окно.
206
+ * @param baseUrl базовый URL провайдера; @param model имя модели;
207
+ * @param opts ключ/таймаут/тип; @returns окно контекста либо null (fail-open).
208
+ */
209
+ export async function probeProviderContext(baseUrl, model, opts = {}) {
210
+ const timeoutMs = opts.timeoutMs ?? 3000;
211
+ const origin = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
212
+ try {
213
+ const lm = await probeModel(baseUrl, model, timeoutMs);
214
+ if (lm)
215
+ return { model: lm.model, actual: lm.contextLength };
216
+ const ollama = await probeOllama(origin, model, timeoutMs);
217
+ if (ollama !== null)
218
+ return { model, actual: ollama };
219
+ const llama = await probeLlamaCpp(origin, timeoutMs);
220
+ if (llama !== null)
221
+ return { model, actual: llama };
222
+ const compat = await probeOpenAICompatModels(baseUrl, model, opts.apiKey, timeoutMs);
223
+ if (compat !== null)
224
+ return { model, actual: compat };
225
+ const catalog = await probeModelsDev(baseUrl, model, opts.providerType);
226
+ if (catalog !== null)
227
+ return { model, actual: catalog };
228
+ }
229
+ catch {
230
+ // Fail-open: любая ошибка пробы оставляет настроенный contextWindow.
231
+ }
232
+ return null;
233
+ }
@@ -1,4 +1,4 @@
1
- /** Helper: extract plain text from message content (for token counting, logging, etc.) */
1
+ /** Хелпер: плоский текст из контента (подсчёт токенов, логи и т.п.). */
2
2
  export function getMessageText(content) {
3
3
  if (typeof content === "string")
4
4
  return content;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Сворачивает чанки стрима в ответ (текст склеивается, reasoning копится,
3
+ * аргументы тулов JSON-парсятся с фолбэком {}).
4
+ * @param chunks чанки стрима; @returns распарсенный ответ.
5
+ */
1
6
  export function parseChunks(chunks) {
2
7
  let text = "";
3
8
  let reasoning;
@@ -1,14 +1,13 @@
1
- // src/llm/token-counter.ts
1
+ /** Подсчёт токенов: точный (tiktoken) и дешёвая эвристика для блоков. */
2
2
  import { encodingForModel, getEncoding } from "js-tiktoken";
3
3
  import { getMessageText } from "./provider";
4
4
  /**
5
- * Script-aware token ESTIMATE (cheap, no full tokenization). Cheap enough for
6
- * every hand-built PromptBlock; used at block-build time where the exact
7
- * TokenCounter may not be in scope. Measured on cl100k_base: Latin AND
8
- * Cyrillic both encode at ~3.3-5 chars/token (the old flat len/4 was actually
9
- * close for Cyrillic — the "cyr ≈ /2" assumption was wrong); CJK/Hangul are
10
- * the real outliers at ~1-1.5 tokens/char, so they get /1.5. Within ±30% of
11
- * tiktoken for Russian text.
5
+ * Скриптозависимая ОЦЕНКА токенов (дешёвая, без полной токенизации). Достаточно
6
+ * дешёвая для каждого PromptBlock; на этапе сборки точного TokenCounter может
7
+ * не быть в scope. Замерено на cl100k_base: латиница И кириллица — ~3.3–5
8
+ * символов/токен (старый плоский len/4 для кириллицы был почти верен —
9
+ * гипотеза «кириллица ≈ /2» ошибочна); CJK/хангыль — выбросы ~1–1.5 токена/
10
+ * символ, им /1.5. В пределах ±30% от tiktoken на русском.
12
11
  */
13
12
  export function estimateTokens(text) {
14
13
  if (!text)
@@ -29,6 +28,7 @@ export function estimateTokens(text) {
29
28
  }
30
29
  return Math.ceil(cjk / 1.5 + rest / 4);
31
30
  }
31
+ /** Точный счётчик токенов поверх tiktoken (энкодинг по модели). */
32
32
  export class TokenCounter {
33
33
  encoder;
34
34
  constructor(model = "gpt-4o") {
@@ -36,31 +36,47 @@ export class TokenCounter {
36
36
  this.encoder = encodingForModel(model);
37
37
  }
38
38
  catch {
39
- // Fallback to cl100k_base for unknown models
39
+ // Фолбэк на cl100k_base для неизвестных моделей
40
40
  this.encoder = getEncoding("cl100k_base");
41
41
  }
42
42
  }
43
+ /**
44
+ * Считает токены текста.
45
+ * @param text текст; @returns число токенов.
46
+ */
43
47
  count(text) {
44
48
  if (!text)
45
49
  return 0;
46
50
  return this.encoder.encode(text).length;
47
51
  }
52
+ /**
53
+ * Считает токены сообщений с оверхедом ролей/границ (~4/сообщение).
54
+ * @param messages сообщения чата; @returns суммарные токены.
55
+ */
48
56
  countMessages(messages) {
49
57
  let total = 0;
50
58
  for (const msg of messages) {
51
- // ~4 tokens per message overhead (role, boundaries)
59
+ // ~4 токена оверхеда на сообщение (роль, границы)
52
60
  total += 4;
53
61
  const text = getMessageText(msg.content);
54
62
  total += this.count(text);
55
63
  if (msg.role === "tool")
56
64
  total += 2; // tool_call_id
57
65
  }
58
- total += 2; // assistant priming
66
+ total += 2; // прайминг ассистента
59
67
  return total;
60
68
  }
69
+ /**
70
+ * Кодирует текст в токены.
71
+ * @param text текст; @returns массив id токенов.
72
+ */
61
73
  encode(text) {
62
74
  return this.encoder.encode(text);
63
75
  }
76
+ /**
77
+ * Декодирует токены в текст.
78
+ * @param tokens id токенов; @returns текст.
79
+ */
64
80
  decode(tokens) {
65
81
  return this.encoder.decode(tokens);
66
82
  }
@@ -1,9 +1,11 @@
1
+ // Логер: шина вывода + файловые логи (.log, app.jsonl), чистка секретов.
1
2
  import { appendFileSync, mkdirSync, existsSync } from "fs";
2
3
  import { join } from "path";
3
4
  import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
4
5
  import { FileLogWriter } from "./file-log";
5
6
  import { defaultOutputBus } from "../output";
6
7
  const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
8
+ /** Центральный логер: консоль через шину, файлы, JSONL, тегированные хелперы. */
7
9
  export class Logger {
8
10
  level;
9
11
  prefix;
@@ -36,36 +38,47 @@ export class Logger {
36
38
  clearSessionDir() {
37
39
  this.sessionDir = null;
38
40
  }
39
- /** Open a per-session `.log` file (legacy v1 behavior). */
41
+ /** Открывает посессионный `.log`-файл (поведение legacy v1). */
40
42
  initSessionLog(sessionId) {
41
43
  this.fileLog.initSessionLog(sessionId);
42
44
  }
43
- /** Fall back to daily log files when no session is active. */
45
+ /** Возврат к дневным лог-файлам, когда нет активной сессии. */
44
46
  closeSessionLog() {
45
47
  this.fileLog.closeSessionLog();
46
48
  }
49
+ /** Путь к текущему лог-файлу (сессионный или дневной). */
47
50
  getLogPath() {
48
51
  return this.fileLog.getLogPath();
49
52
  }
53
+ /** Удаляет старые логи по возрасту/количеству. */
50
54
  cleanupOldLogs(maxDays, maxFiles) {
51
55
  this.fileLog.cleanupOldLogs(maxDays, maxFiles);
52
56
  }
53
- // --- Tagged helpers (legacy v1 API) ---
57
+ // Тегированные хелперы (API legacy v1).
58
+ /** Пишет запрос к LLM в файловый лог. */
54
59
  logLLMRequest(model, messagesCount, promptPreview, caller) {
55
60
  this.fileLog.logLLMRequest(model, messagesCount, sanitizeLogMessage(promptPreview), caller);
56
61
  }
62
+ /** Пишет ответ LLM (или ошибку) в файловый лог. */
57
63
  logLLMResponse(model, responseLength, genTimeMs, error, caller) {
58
64
  this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
59
65
  }
66
+ /** Пишет вызов тула и превью результата в файловый лог. */
60
67
  logToolCall(tool, preview, result) {
61
68
  this.fileLog.logToolCall(tool, sanitizeLogMessage(preview), result !== undefined ? sanitizeLogMessage(result) : undefined);
62
69
  }
70
+ /** Пишет вывод тула с кодом выхода в файловый лог. */
63
71
  logToolOutput(tool, output, exitCode) {
64
72
  this.fileLog.logToolOutput(tool, sanitizeLogMessage(output), exitCode);
65
73
  }
74
+ /** Пишет реплику REPL (user/assistant/system) в файловый лог. */
66
75
  logREPL(tag, content) {
67
76
  this.fileLog.logREPL(tag, sanitizeLogMessage(content));
68
77
  }
78
+ /**
79
+ * Дочерний логер с расширенным префиксом; наследует уровень, sink и каталоги.
80
+ * @param prefix суффикс префикса. @returns новый Logger.
81
+ */
69
82
  child(prefix) {
70
83
  const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix, this.sink);
71
84
  if (this.logDir)
@@ -86,42 +99,55 @@ export class Logger {
86
99
  error(msg, meta) {
87
100
  this.log("error", msg, meta);
88
101
  }
89
- log(level, msg, meta) {
102
+ /**
103
+ * Внутренняя диагностика петли (ретраи галлюцинаций, аудит/компакция,
104
+ * вердикты критика, …). Персистентность та же, что у warn/info, но событие
105
+ * шины несёт `data.diagnostic = true`, и чат дропает его, пока
106
+ * `ui.verbosity !== "verbose"`.
107
+ */
108
+ diagnostic(level, msg, meta) {
109
+ this.log(level, msg, meta, true);
110
+ }
111
+ log(level, msg, meta, diagnostic) {
90
112
  if (LEVELS[level] < LEVELS[this.level])
91
113
  return;
92
- // Sanitize log message to remove sensitive data
114
+ // Чистим сообщение — вырезаем секреты.
93
115
  const sanitizedMsg = sanitizeLogMessage(msg);
94
116
  const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
95
117
  const ts = new Date().toISOString();
96
118
  const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` : "";
97
119
  const line = `${sanitizedMsg}${metaStr}`;
98
120
  try {
99
- this.sink.emit({ level, source: this.prefix || "mma", kind: "log", text: line });
121
+ this.sink.emit({
122
+ level,
123
+ source: this.prefix || "mma",
124
+ kind: "log",
125
+ text: line,
126
+ data: diagnostic ? { diagnostic: true } : undefined,
127
+ });
100
128
  }
101
129
  catch {
102
130
  /* terminal output is best-effort; file logging below must still run */
103
131
  }
104
- // Legacy v1 behavior: also append to the tagged `.log` file (session or daily).
132
+ // Поведение legacy v1: дубль в тегированный `.log` (сессионный или дневной).
105
133
  this.fileLog.log(level.toUpperCase(), this.prefix || "MMA", `${ts} — ${sanitizedMsg}${metaStr}`);
106
134
  const logTarget = this.sessionDir ?? this.logDir;
107
135
  if (logTarget) {
108
136
  try {
109
- appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
137
+ appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
110
138
  level,
111
139
  ts,
112
140
  prefix: this.prefix,
113
141
  msg: sanitizedMsg,
114
142
  meta: sanitizedMeta ?? null,
115
- }) + "\n", "utf-8");
143
+ })}\n`, "utf-8");
116
144
  }
117
145
  catch {
118
146
  /* file logging is best-effort */
119
147
  }
120
148
  }
121
149
  }
122
- /**
123
- * Sanitize metadata object to remove sensitive data
124
- */
150
+ /** Рекурсивно чистит секреты в объекте метаданных. */
125
151
  sanitizeMeta(meta) {
126
152
  const sanitized = {};
127
153
  for (const [key, value] of Object.entries(meta)) {
@@ -138,30 +164,30 @@ export class Logger {
138
164
  return sanitized;
139
165
  }
140
166
  /**
141
- * Write a structured record to app.jsonl only (no console / .log output).
142
- * Used for machine-readable reports (environment, etc.) that would be too
143
- * noisy on the terminal but are valuable for cross-device diagnosis.
167
+ * Структурная запись только в app.jsonl (без консоли и .log).
168
+ * Для машиночитаемых отчётов (окружение и т.п.), слишком шумных для терминала,
169
+ * но ценных для кросс-девайс диагностики.
144
170
  */
145
171
  logStructured(type, data) {
146
172
  const logTarget = this.sessionDir ?? this.logDir;
147
173
  if (!logTarget)
148
174
  return;
149
175
  try {
150
- appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
176
+ appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
151
177
  level: "info",
152
178
  ts: new Date().toISOString(),
153
179
  type,
154
180
  meta: this.sanitizeMeta(data),
155
- }) + "\n", "utf-8");
181
+ })}\n`, "utf-8");
156
182
  }
157
183
  catch {
158
184
  /* file logging is best-effort */
159
185
  }
160
186
  }
161
187
  /**
162
- * Write a log entry to app.jsonl only — no console output, no .log file.
163
- * Use this for noisy subsystems (LSP, probes) whose warnings should be
164
- * diagnosable from session files but not pollute the user's terminal.
188
+ * Запись только в app.jsonl — без консоли и .log. Для шумных подсистем
189
+ * (LSP, пробники): предупреждения диагностируются по файлам сессии,
190
+ * но не мусорят в терминале.
165
191
  */
166
192
  logSilent(level, msg, meta) {
167
193
  const logTarget = this.sessionDir ?? this.logDir;
@@ -171,13 +197,13 @@ export class Logger {
171
197
  const sanitizedMsg = sanitizeLogMessage(msg);
172
198
  const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
173
199
  try {
174
- appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
200
+ appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
175
201
  level,
176
202
  ts,
177
203
  prefix: this.prefix,
178
204
  msg: sanitizedMsg,
179
205
  meta: sanitizedMeta ?? null,
180
- }) + "\n", "utf-8");
206
+ })}\n`, "utf-8");
181
207
  }
182
208
  catch {
183
209
  /* file logging is best-effort */