micro-models-agent 2.11.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 (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -1,14 +1,12 @@
1
1
  import { setAuditSessionDir } from "../modules/security/audit-log";
2
2
  import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
3
- import { join } from "path";
4
- import { readFileSync, existsSync } from "fs";
3
+ import { redactContextSecrets } from "./agent/context-log";
5
4
  /**
6
- * Thin wrapper around SessionManager that eliminates repetitive
7
- * `if (sessionManager)` + `new Date().toISOString()` boilerplate
8
- * from the agent loop.
5
+ * Тонкая обёртка над SessionManager: убирает повторяющиеся
6
+ * `if (sessionManager)` + `new Date().toISOString()` из цикла агента.
9
7
  *
10
- * When constructed, it configures the app logger and audit log to write into
11
- * the session directory (instead of global ~/.mma/logs/).
8
+ * При создании привязывает логгер приложения и аудит-лог к каталогу сессии
9
+ * (вместо глобального ~/.mma/logs/).
12
10
  */
13
11
  export class SessionLogger {
14
12
  session;
@@ -21,26 +19,36 @@ export class SessionLogger {
21
19
  this.bindSessionDir();
22
20
  }
23
21
  bindSessionDir() {
24
- const meta = this.session?.getActiveMeta();
25
- if (!meta)
22
+ const session = this.session;
23
+ const meta = session?.getActiveMeta();
24
+ if (!session || !meta)
26
25
  return;
27
- const dir = this.session.getSessionDirectory(meta.id);
26
+ const dir = session.getSessionDirectory(meta.id);
28
27
  if (this.logger) {
29
28
  this.logger.setSessionDir(dir);
30
29
  this.logger.initSessionLog(meta.id);
31
30
  }
32
31
  setAuditSessionDir(dir);
33
32
  }
33
+ /** Есть ли активная сессия. */
34
34
  get active() {
35
35
  return !!this.session?.getActive();
36
36
  }
37
+ /**
38
+ * Пишет системное сообщение в лог сессии.
39
+ * @param content текст системного сообщения.
40
+ */
37
41
  logSystem(content) {
38
42
  this.session?.appendLog({
39
43
  ts: new Date().toISOString(),
40
44
  type: "system",
41
- content,
45
+ content: redactContextSecrets(content),
42
46
  });
43
47
  }
48
+ /**
49
+ * Пишет сообщение пользователя (санитизированное).
50
+ * @param content текст пользователя.
51
+ */
44
52
  logUser(content) {
45
53
  const sanitized = sanitizeLogMessage(content);
46
54
  this.session?.appendMessage({
@@ -54,13 +62,18 @@ export class SessionLogger {
54
62
  content: sanitized,
55
63
  });
56
64
  }
65
+ /**
66
+ * Пишет ответ ассистента (+ reasoning и tool_calls при наличии).
67
+ * @param content текст ответа; @param reasoning reasoning-контент;
68
+ * @param toolCalls вызовы тулов; @param iteration номер итерации.
69
+ */
57
70
  logAssistant(content, reasoning, toolCalls, iteration) {
58
71
  const caller = this.getCaller?.();
59
72
  if (reasoning) {
60
73
  this.session?.appendLog({
61
74
  ts: new Date().toISOString(),
62
75
  type: "reasoning",
63
- content: reasoning,
76
+ content: redactContextSecrets(reasoning),
64
77
  iteration,
65
78
  provider: caller?.provider,
66
79
  model: caller?.model,
@@ -69,7 +82,7 @@ export class SessionLogger {
69
82
  this.session?.appendLog({
70
83
  ts: new Date().toISOString(),
71
84
  type: "assistant",
72
- content,
85
+ content: redactContextSecrets(content),
73
86
  ...(toolCalls
74
87
  ? {
75
88
  tool_calls: toolCalls.map((tc) => ({
@@ -84,16 +97,24 @@ export class SessionLogger {
84
97
  model: caller?.model,
85
98
  });
86
99
  }
100
+ /**
101
+ * Сохраняет сообщение ассистента в историю сессии (для resume).
102
+ * @param content текст сообщения.
103
+ */
87
104
  saveAssistantMessage(content) {
88
105
  const caller = this.getCaller?.();
89
106
  this.session?.appendMessage({
90
107
  role: "assistant",
91
- content,
108
+ content: redactContextSecrets(content),
92
109
  timestamp: new Date().toISOString(),
93
110
  provider: caller?.provider,
94
111
  model: caller?.model,
95
112
  });
96
113
  }
114
+ /**
115
+ * Пишет набор определений тулов итерации (состав для пост-анализа).
116
+ * @param toolCount число тулов; @param toolNames имена; @param iteration итерация.
117
+ */
97
118
  logToolDefs(toolCount, toolNames, iteration) {
98
119
  this.session?.appendLog({
99
120
  ts: new Date().toISOString(),
@@ -103,16 +124,46 @@ export class SessionLogger {
103
124
  iteration,
104
125
  });
105
126
  }
127
+ /**
128
+ * Пишет вызов тула (имя, id, санитизированные аргументы).
129
+ * @param call вызов тула; @param iteration номер итерации.
130
+ */
106
131
  logToolCall(call, iteration) {
107
132
  this.session?.appendLog({
108
133
  ts: new Date().toISOString(),
109
134
  type: "tool_call",
110
135
  tool: call.name,
111
136
  tool_call_id: call.id,
112
- args: JSON.parse(sanitizeLogMessage(JSON.stringify(call.arguments))),
137
+ args: this.sanitizeArgs(call.arguments),
113
138
  iteration,
114
139
  });
115
140
  }
141
+ /**
142
+ * Сериализует аргументы тула для лога. Санитайзер может испортить JSON
143
+ * (например, режет закрывающую кавычку в `password=secret"`), поэтому при
144
+ * ошибке парсинга пишем сырую санитизированную строку и предупреждаем —
145
+ * запись tool_call не должна теряться.
146
+ */
147
+ sanitizeArgs(args) {
148
+ const raw = JSON.stringify(args ?? {});
149
+ const sanitized = sanitizeLogMessage(raw);
150
+ try {
151
+ const parsed = JSON.parse(sanitized);
152
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
153
+ return parsed;
154
+ }
155
+ return { raw: sanitized };
156
+ }
157
+ catch {
158
+ this.logger?.warn(`tool_call args sanitization broke JSON; storing raw string (${raw.length} chars)`);
159
+ return { raw: sanitized };
160
+ }
161
+ }
162
+ /**
163
+ * Пишет результат тула (успех, вывод, diff, длительность).
164
+ * @param call вызов; @param result результат; @param duration длительность, мс;
165
+ * @param iteration номер итерации.
166
+ */
116
167
  logToolResult(call, result, duration, iteration) {
117
168
  const caller = this.getCaller?.();
118
169
  this.session?.appendMessage({
@@ -137,6 +188,10 @@ export class SessionLogger {
137
188
  model: caller?.model,
138
189
  });
139
190
  }
191
+ /**
192
+ * Пишет событие компакции (причина, токены/качество до и после).
193
+ * @param info параметры компакции.
194
+ */
140
195
  logCompaction(info) {
141
196
  this.session?.appendLog({
142
197
  ts: new Date().toISOString(),
@@ -155,6 +210,10 @@ export class SessionLogger {
155
210
  summary: info.summary,
156
211
  });
157
212
  }
213
+ /**
214
+ * Пишет снапшот контекста (бюджеты, токены, качество, состав блоков).
215
+ * @param info параметры снапшота.
216
+ */
158
217
  logContext(info) {
159
218
  this.session?.appendLog({
160
219
  ts: new Date().toISOString(),
@@ -179,6 +238,10 @@ export class SessionLogger {
179
238
  blocks: info.blocks,
180
239
  });
181
240
  }
241
+ /**
242
+ * Пишет usage LLM-вызова (токены, источник, кеш, стабильность префикса).
243
+ * @param iteration номер итерации; @param usage токены и мета.
244
+ */
182
245
  logLlmUsage(iteration, usage) {
183
246
  this.session?.appendLog({
184
247
  ts: new Date().toISOString(),
@@ -198,13 +261,21 @@ export class SessionLogger {
198
261
  cacheSource: usage.cache?.source,
199
262
  });
200
263
  }
264
+ /**
265
+ * Пишет ошибку в лог сессии.
266
+ * @param message текст ошибки.
267
+ */
201
268
  logError(message) {
202
269
  this.session?.appendLog({
203
270
  ts: new Date().toISOString(),
204
271
  type: "error",
205
- content: message,
272
+ content: redactContextSecrets(message),
206
273
  });
207
274
  }
275
+ /**
276
+ * Пишет вердикт финального аудита.
277
+ * @param summary сводка аудита; @param iteration номер итерации.
278
+ */
208
279
  logAudit(summary, iteration) {
209
280
  this.session?.appendLog({
210
281
  ts: new Date().toISOString(),
@@ -213,6 +284,10 @@ export class SessionLogger {
213
284
  iteration,
214
285
  });
215
286
  }
287
+ /**
288
+ * Пишет событие плана MoE/обычного (создание, шаги, re-plan).
289
+ * @param event имя события; @param detail детали; @param iteration итерация.
290
+ */
216
291
  logPlan(event, detail, iteration) {
217
292
  this.session?.appendLog({
218
293
  ts: new Date().toISOString(),
@@ -223,24 +298,23 @@ export class SessionLogger {
223
298
  });
224
299
  }
225
300
  /**
226
- * Log session startup diagnostics: environment, LSP probe, plugins, skills.
227
- * Should be the FIRST entry in session.jsonl so the file is self-describing.
228
- * Skips if a session_start entry already exists (e.g. logged by REPL).
301
+ * Пишет стартовую диагностику сессии: окружение, LSP-проба, плагины, скиллы.
302
+ * Должна быть ПЕРВОЙ записью в session.jsonl (файл самоописываем).
303
+ * Пропускает, если session_start уже есть (напр., записан REPL).
229
304
  */
230
305
  logSessionStart(data) {
231
- // Skip if session_start already logged (REPL may have logged it with LSP probe)
306
+ // Пропуск при уже записанном session_start (REPL мог записать с LSP-пробой).
307
+ // Чтение через decrypt-aware store: при шифровании сессии строки на диске —
308
+ // шифртекст, поэтому проверка подстрокой никогда не срабатывала и дубль дописывался.
232
309
  const meta = this.session?.getActiveMeta();
233
310
  if (meta) {
234
- const logPath = join(this.session.getSessionDirectory(meta.id), "session.jsonl");
235
311
  try {
236
- if (existsSync(logPath)) {
237
- const content = readFileSync(logPath, "utf-8");
238
- if (content.includes('"type":"session_start"'))
239
- return;
240
- }
312
+ const entries = this.session.loadSessionLog(meta.id);
313
+ if (entries.some((e) => e.type === "session_start"))
314
+ return;
241
315
  }
242
316
  catch {
243
- // File read error — proceed to write
317
+ // Ошибка чтения — всё равно пишем
244
318
  }
245
319
  }
246
320
  this.session?.appendLog({
@@ -254,7 +328,7 @@ export class SessionLogger {
254
328
  });
255
329
  }
256
330
  /**
257
- * Log a reasoning control event (level set, policy evaluation).
331
+ * Пишет событие управления reasoning (уровень, оценка политики).
258
332
  */
259
333
  logReasoningControl(data) {
260
334
  this.session?.appendLog({
@@ -269,8 +343,8 @@ export class SessionLogger {
269
343
  });
270
344
  }
271
345
  /**
272
- * Log a MoE observability event (moe_plan / moe_subtask / moe_replan /
273
- * moe_verify). Emitted by runWithMoE and MoEExecutor (Plan 4.2).
346
+ * Пишет событие наблюдаемости MoE (moe_plan / moe_subtask / moe_replan /
347
+ * moe_verify). Эмитят runWithMoE и MoEExecutor (Plan 4.2).
274
348
  */
275
349
  logMoE(event, data) {
276
350
  this.session?.appendLog({
@@ -280,4 +354,22 @@ export class SessionLogger {
280
354
  ...data,
281
355
  });
282
356
  }
357
+ /** Пишет событие онбординга (meet/init) для пост-анализа. */
358
+ logOnboarding(event, data = {}) {
359
+ this.session?.appendLog({
360
+ ts: new Date().toISOString(),
361
+ type: `onboarding_${event}`,
362
+ content: event,
363
+ ...data,
364
+ });
365
+ }
366
+ /** Пишет событие критика (контр-агент). */
367
+ logCritic(event, data = {}) {
368
+ this.session?.appendLog({
369
+ ts: new Date().toISOString(),
370
+ type: event,
371
+ content: data.summary ?? event,
372
+ ...data,
373
+ });
374
+ }
283
375
  }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Рендерит системный промпт (чистая функция).
3
+ * @param config конфиг приложения; @param baseDir workspace;
4
+ * @param profileCompressed сжатый профиль окружения.
5
+ * @returns текст системного сообщения для модели.
6
+ */
7
+ export function buildSystemInfo(config, baseDir, profileCompressed) {
8
+ const now = new Date().toISOString().replace("T", " ").slice(0, 19);
9
+ const isWin = profileCompressed.toLowerCase().includes("win32");
10
+ const lines = [
11
+ `You are MMA v2, an AI agent for small models (${config.model}). Date: ${now}.`,
12
+ `Workspace: ${baseDir}. ${profileCompressed}.`,
13
+ `Reply in the user's language. Explain briefly if not obvious.`,
14
+ ``,
15
+ `TOOLS: file ops (read/write/edit/delete), search (glob/grep), shell (bash), web, subagents, browser, MCP.`,
16
+ `On tool failure: analyze, fix the call, retry up to 2x with different approaches, then ask the user.`,
17
+ ``,
18
+ `SCOPE — do ONLY what the user explicitly asked: no extra features, files, refactors, or fixes beyond the request.`,
19
+ `Read-only requests ("расскажи", "покажи", "объясни", "check") mean READ-ONLY: inspect and answer, never modify anything.`,
20
+ `Ambiguous task (unclear what/where/which) or target not found — STOP and ask via the "question" tool, do not guess.`,
21
+ `A "not found" tool result means the PATH was wrong, not that the project is missing: list_dir the workspace first,`,
22
+ `then the parent of a user-named path. NEVER scaffold a new project for questions about an existing one.`,
23
+ ``,
24
+ `ARTIFACTS: CREATE a file ONLY when the user explicitly asks for a file — "write/save to file", "create a file/document",`,
25
+ `or a filename/path with an extension.`,
26
+ `Bare "recommendation", "summary", "report", "result" mean ANSWER IN CHAT, never files.`,
27
+ `Use the requested format and stack; never add unagreed libraries.`,
28
+ `If a needed choice is unspecified, ask or search first.`,
29
+ ``,
30
+ `MEMORY: the current user message outranks stored memory/preferences.`,
31
+ `Never "correct" an explicit value from stored facts.`,
32
+ `Never ask what memory already answers (the "question" tool checks memory itself); "remember" only on explicit request.`,
33
+ `MEMORY PRIVACY: never copy stored personal facts (birthday, height, name, contacts) into workspace files unless asked.`,
34
+ ``,
35
+ `PROTECTED PATHS: never touch VCS internals (.git/, .hg/, .svn/) or secrets (.env*, *.key, *.pem, *.p12, *.pfx, *.crt,`,
36
+ `*.secret, *.token, .npmrc, .netrc, .git-credentials, .gitconfig) without explicit confirmation via the "approve" tool.`,
37
+ `Do not retry around a refusal or work around it with bash.`,
38
+ ``,
39
+ `WORK RULES: 1) install deps BEFORE source (verify lockfile; no uninstalled imports); 2) framework init BEFORE app code;`,
40
+ `3) plan sequentially, verify each step's deliverables exist with real content; 4) verify on disk + command output;`,
41
+ `5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, change approach, ask.`,
42
+ ``,
43
+ `SESSION START (first turn with a workspace task): 1) inspect — list_dir the workspace, note package.json/tests/config;`,
44
+ `2) baseline for CODE tasks only — run project checks (bun test / tsc --noEmit),`,
45
+ `report pass/fail BEFORE changing code;`,
46
+ `3) trivial chat/questions skip the baseline. Then proceed with the user's task.`,
47
+ ``,
48
+ `FORBIDDEN:`,
49
+ `- Marking done without proof (no clean check output) — see VERIFY.`,
50
+ `- Creating files the user didn't ask for — see ARTIFACTS.`,
51
+ `- Retrying the same failing command a 3rd time — change approach or ask.`,
52
+ `- Claiming "fixed" from a fresh port/server while the user's endpoint still fails.`,
53
+ ``,
54
+ `DONE CONDITIONS — finished only when ALL hold: all plan steps are marked done;`,
55
+ `requested files/artifacts exist with real content; each runnable verification reports clean.`,
56
+ `Then reply with a short summary of what changed (files created/edited).`,
57
+ `STOP AND ASK THE USER WHEN: the task is ambiguous (multiple approaches, unclear what/where);`,
58
+ `you could not find what the user asked about after listing the parent directory;`,
59
+ `finishing would delete/overwrite important files;`,
60
+ `or you are stuck after 2+ different approaches.`,
61
+ `Ask via the "question" tool (one short question, options when known) and wait.`,
62
+ `Plain text only when interactive tools are unavailable.`,
63
+ ];
64
+ if (isWin) {
65
+ lines.push(`Windows (PowerShell): use list_dir/read_file/delete_file/create_dir tools instead of dir/type/del/mkdir.`, `No PowerShell cmdlets (Get-Content, Select-Object, Write-Output), no head/tail/grep/cat.`, `Forward slashes in paths. CWD: ${baseDir}`);
66
+ }
67
+ // MMA — Bun-only проект (скрипты package.json, Bun API в коде): модель
68
+ // должна предпочитать bun, а не node/tsx/ts-node, для запуска TypeScript
69
+ // (наблюдалось: модель жгла 10+ итераций на установку tsx вместо `bun file.ts`).
70
+ lines.push(`Runtime: Bun is available — run TypeScript directly with "bun <file.ts>", tests with "bun test" (no tsx/ts-node needed).`, `Bash: pass "workdir", never "cd dir && cmd". One command per call.`, `Long-running: "background: true", else auto-backgrounded — check with process_log.`, ``, `VERIFY EVERY WRITE: after write_file/edit_file with NO [LSP errors], [LSP warnings], [Syntax check failed],`, `or [Project typecheck failed] in the result — call "lsp_check" on that file BEFORE marking the step done.`, `Never assume, prove.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact paths, packages, commands).`, `No vague steps. 5-8 steps, init → verify.`, `Each step needs a 1:1 verification contract in "checks":`, `"file-exists:<path>", "command:<exact command>", or "manual:<what>".`, `For "check/fix errors" tasks each verification step names the exact proving command`, `("bun run build", "bun test", "lsp_check").`, `A step is done only after its check reports clean — then "plan_step step=N status=done".`);
71
+ if (config.autoPlan) {
72
+ lines.push(`PLAN (MANDATORY): file creation, package installs, or multi-action tasks MUST create a "plan" BEFORE starting.`);
73
+ }
74
+ // Баг в терминах runtime-поведения (что происходит при запуске) доказывается
75
+ // только ВОСПРОИЗВЕДЕНИЕМ — запуском app/build/browser и сверкой с реальным
76
+ // выводом. Модель решает сама, когда это применимо (без классификации по
77
+ // ключевым словам). Протухшие dev-серверы держат СТАРЫЙ пайплайн в памяти и
78
+ // продолжают кидать исходную ошибку (наблюдалось: старый vite на 5173–5176
79
+ // сыпал postcss после фикса на диске, а свежий на 5177 был чист) — фикс
80
+ // реален, только когда чист сервер ПОЛЬЗОВАТЕЛЯ.
81
+ lines.push(`Runtime verification: when a bug is described in terms of runtime behavior`, `(what happens when the app runs), reproduce it —`, `run the app / build / browser tool — confirm the fix against actual runtime output`, `before calling "plan_step step=N status=done".`, `Check existing servers first (process_list/process_log): a stale dev server`, `runs the old pipeline and keeps failing after the fix.`, `Restart/kill it (process_kill) and verify the exact endpoint/port reported —`, `a fresh port does NOT prove the user's error is gone.`);
82
+ // Правила безопасности: при включённой защите модель должна знать ограничения
83
+ // заранее — заблокированный вызов стоит итерацию, а этот блок ~100 токенов
84
+ // один раз. Компактно и с cap, чтобы длинный blacklist не рвал бюджет.
85
+ const sec = config.security;
86
+ if (sec?.enabled) {
87
+ const secLines = [
88
+ `SECURITY RESTRICTIONS (enforced — blocked calls return "[SECURITY BLOCKED]", do not retry them):`,
89
+ ];
90
+ if (sec.bash?.enabled && sec.bash.blacklist.length > 0) {
91
+ const cmds = sec.bash.blacklist.slice(0, 15).join(", ");
92
+ secLines.push(`Forbidden bash commands: ${cmds}${sec.bash.blacklist.length > 15 ? ", …" : ""}.`);
93
+ if (sec.bash.whitelist.length > 0) {
94
+ secLines.push(`Only whitelisted bash commands are allowed: ${sec.bash.whitelist.slice(0, 15).join(", ")}.`);
95
+ }
96
+ if (sec.bash.blockDangerousFlags) {
97
+ secLines.push(`Pipes/redirects/chaining (| > >> ; &&) in bash are blocked — one plain command per call.`);
98
+ }
99
+ }
100
+ if (sec.paths?.enabled && sec.paths.denied.length > 0) {
101
+ const paths = sec.paths.denied.slice(0, 10).join(", ");
102
+ secLines.push(`Denied paths (read/write): ${paths}${sec.paths.denied.length > 10 ? ", …" : ""}.`);
103
+ }
104
+ if (sec.network?.enabled && sec.network.deniedDomains.length > 0) {
105
+ const domains = sec.network.deniedDomains.slice(0, 10).join(", ");
106
+ secLines.push(`Denied network domains: ${domains}${sec.network.deniedDomains.length > 10 ? ", …" : ""}.`);
107
+ }
108
+ if (secLines.length > 1) {
109
+ lines.push(...secLines);
110
+ }
111
+ }
112
+ const hasMCP = config.mcpServers && Object.values(config.mcpServers).some((s) => s.enabled !== false);
113
+ if (hasMCP) {
114
+ lines.push(`MCP servers are available — use the named MCP tools (prefixed mcp__) to query external services.`);
115
+ }
116
+ return lines.join("\n");
117
+ }
@@ -1,9 +1,11 @@
1
+ /** Версия MMA: читается из package.json в рантайме. */
1
2
  import { existsSync, readFileSync } from "fs";
2
3
  import { join, dirname } from "path";
3
4
  import { fileURLToPath } from "url";
4
5
  /**
5
- * Read the MMA version from package.json at runtime (never a static import —
6
- * bundlers inline a stale value at build time, see design rule #12).
6
+ * Читает версию MMA из package.json в рантайме (не статическим импортом —
7
+ * бандлеры инлайнят устаревшее значение на этапе сборки, см. правило №12).
8
+ * @returns версия из package.json либо "0.0.0", если файл не найден/бит.
7
9
  */
8
10
  export function readMmaVersion() {
9
11
  const here = dirname(fileURLToPath(import.meta.url));
@@ -16,11 +18,11 @@ export function readMmaVersion() {
16
18
  return raw.version;
17
19
  }
18
20
  catch {
19
- // Broken package.json — fall through to the next candidate
21
+ // Битый package.json — пробуем следующий кандидат
20
22
  }
21
23
  }
22
24
  }
23
25
  return "0.0.0";
24
26
  }
25
- /** Legacy alias — use readMmaVersion(). */
27
+ /** Устаревший алиас — используй readMmaVersion(). */
26
28
  export const readVersion = readMmaVersion;
@@ -0,0 +1,34 @@
1
+ import { defaultOutputBus } from "../output/bus";
2
+ /** Реализация хоста для `mma web` — без TTY, без LineEditor. */
3
+ export class WebHost {
4
+ deps;
5
+ constructor(deps) {
6
+ this.deps = deps;
7
+ }
8
+ isBusy() {
9
+ return this.deps.isBusy();
10
+ }
11
+ async submit(text, o) {
12
+ if (this.deps.isBusy())
13
+ throw new Error("agent is busy");
14
+ for (const url of o?.images ?? [])
15
+ this.deps.addPendingImage?.(url);
16
+ return this.deps.runAgent(text);
17
+ }
18
+ interrupt() {
19
+ if (this.deps.isBusy())
20
+ this.deps.interrupt();
21
+ }
22
+ subscribeOutput(fn) {
23
+ return defaultOutputBus.subscribe(fn);
24
+ }
25
+ async executeCommand(line) {
26
+ const trimmed = line.trim();
27
+ if (!trimmed.startsWith("/"))
28
+ return;
29
+ await this.deps.executeCommand(trimmed);
30
+ }
31
+ getState() {
32
+ return this.deps.getState();
33
+ }
34
+ }
@@ -1,6 +1,7 @@
1
+ /** Резолвинг путей workspace: поиск корня проекта по манифестам. */
1
2
  import { existsSync, readdirSync } from "fs";
2
3
  import { resolve, isAbsolute, join } from "path";
3
- /** Files/dirs that mark a directory as a project root. */
4
+ /** Файлы/каталоги — маркеры корня проекта. */
4
5
  const PROJECT_MANIFESTS = [
5
6
  "package.json",
6
7
  "bun.lockb",
@@ -14,7 +15,7 @@ const PROJECT_MANIFESTS = [
14
15
  "Gemfile",
15
16
  "composer.json",
16
17
  ];
17
- /** Immediate subdirectories that are never candidate project roots. */
18
+ /** Подкаталоги, никогда не кандидаты в корень проекта. */
18
19
  const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".hg", ".svn"]);
19
20
  const subdirCache = new Map();
20
21
  function scanSubdirs(baseDir) {
@@ -35,27 +36,30 @@ function scanSubdirs(baseDir) {
35
36
  subdirCache.set(baseDir, dirs);
36
37
  return dirs;
37
38
  }
39
+ /**
40
+ * Есть ли в каталоге манифест проекта.
41
+ * @param dir каталог для проверки.
42
+ * @returns true, если найден известный манифест.
43
+ */
38
44
  export function hasProjectManifest(dir) {
39
45
  return PROJECT_MANIFESTS.some((m) => existsSync(join(dir, m)));
40
46
  }
41
47
  /**
42
- * Resolve a file/dir path inside a workspace, tolerating the common case
43
- * where the agent runs from a parent directory while the actual project
44
- * lives in a single immediate subdirectory (e.g. baseDir=<repo>,
45
- * project=<repo>/app). Resolution order:
46
- * 1. absolute path as-is;
47
- * 2. baseDir/<p> тАФ project rooted directly at baseDir;
48
- * 3. <subdir>/<p> for each immediate project subdirectory of baseDir
49
- * (manifest-bearing subdirectories are preferred).
50
- * Returns baseDir/<p> as a fallback when nothing exists, so callers that
51
- * check existsSync() still correctly report the path as missing.
48
+ * Резолвит путь внутри workspace. Терпит запуск из родителя, когда проект
49
+ * лежит в одном подкаталоге (baseDir=<repo>, проект=<repo>/app). Порядок:
50
+ * 1. абсолютный путь как есть;
51
+ * 2. baseDir/<p> — проект прямо в baseDir;
52
+ * 3. <subdir>/<p> по подкаталогам (приоритет — с манифестом).
53
+ * Фолбэк — baseDir/<p>, чтобы вызывающий через existsSync() корректно
54
+ * определил отсутствие пути.
55
+ * @param baseDir корень workspace; @param p относительный/абсолютный путь.
52
56
  */
53
57
  export function resolveProjectPath(baseDir, p) {
54
58
  const abs = isAbsolute(p) ? p : resolve(baseDir, p);
55
59
  if (existsSync(abs))
56
60
  return abs;
57
- // baseDir itself is a project root тАФ paths are relative to it. Probing
58
- // subdirectories here would match files in unrelated nested packages.
61
+ // Сам baseDir — корень проекта: подкаталоги не сканируем, иначе
62
+ // зацепим файлы посторонних вложенных пакетов.
59
63
  if (hasProjectManifest(baseDir))
60
64
  return abs;
61
65
  let fallback = abs;
@@ -70,7 +74,7 @@ export function resolveProjectPath(baseDir, p) {
70
74
  }
71
75
  return fallback;
72
76
  }
73
- /** Clear the internal subdirectory cache (used by tests). */
77
+ /** Сбрасывает кэш подкаталогов (нужен тестам). */
74
78
  export function clearWorkspaceCache() {
75
79
  subdirCache.clear();
76
80
  }
@@ -1,3 +1,4 @@
1
+ // i18n: плоские словари en/ru, t(key, params) с фолбэком на английский.
1
2
  import en from "./en.json";
2
3
  import ru from "./ru.json";
3
4
  const locales = { en, ru };
@@ -22,25 +23,58 @@ const flat = {};
22
23
  for (const [name, dict] of Object.entries(locales)) {
23
24
  flat[name] = flatten(dict);
24
25
  }
26
+ /**
27
+ * Переключает текущую локаль. Неизвестное имя игнорируется.
28
+ * @param locale имя локали ("en" | "ru").
29
+ */
25
30
  export function setLocale(locale) {
26
31
  if (flat[locale]) {
27
32
  currentLocale = locale;
28
33
  }
29
34
  }
35
+ /**
36
+ * Плоские словари по именам локалей. Нужны веб-API, чтобы отдать все переводы
37
+ * в браузер для локализации его интерфейса. Только чтение — не мутировать.
38
+ */
39
+ export function getFlatDicts() {
40
+ return flat;
41
+ }
42
+ /**
43
+ * Подменяет/добавляет словарь локали в рантайме (плагины, тесты).
44
+ * @param name имя локали; @param dict вложенный словарь переводов.
45
+ */
30
46
  export function loadLocale(name, dict) {
31
47
  locales[name] = dict;
32
48
  flat[name] = flatten(dict);
33
49
  }
50
+ /**
51
+ * Ставит обработчик отсутствующих ключей (debug-диагностика). Подключается
52
+ * из bootstrap на logger.debug, чтобы не тянуть логер в i18n (циклы).
53
+ */
54
+ let missingKeyReporter;
55
+ export function setMissingKeyReporter(reporter) {
56
+ missingKeyReporter = reporter;
57
+ }
58
+ /**
59
+ * Переводит ключ с подстановкой `{params}`. Нет ключа — вернёт сам ключ.
60
+ * @param key точечный ключ; @param params значения подстановок.
61
+ */
34
62
  export function t(key, params) {
35
63
  const localeDict = flat[currentLocale];
36
- const enDict = flat["en"];
37
- let template = localeDict?.[key] ?? enDict?.[key] ?? key;
64
+ const enDict = flat.en;
65
+ const template = localeDict?.[key] ?? enDict?.[key];
66
+ if (template === undefined) {
67
+ missingKeyReporter?.(key);
68
+ return key;
69
+ }
38
70
  if (!template)
39
71
  return key;
40
72
  if (params) {
73
+ let result = template;
41
74
  for (const [k, v] of Object.entries(params)) {
42
- template = template.replaceAll(`{${k}}`, String(v));
75
+ result = result.replaceAll(`{${k}}`, String(v));
43
76
  }
77
+ return result;
44
78
  }
45
79
  return template;
46
80
  }
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Публичные экспорты пакета и библиотечный хелпер миграции конфига.
3
+ */
1
4
  export { DEFAULTS, loadConfig } from "./config/index";
2
5
  export { Logger } from "./logger/index";
3
6
  export { PromptBuilder } from "./core/index";
@@ -10,12 +13,13 @@ import { t } from "./i18n/index";
10
13
  import { defaultOutputBus, getDefaultChannel } from "./output";
11
14
  import { homedir } from "os";
12
15
  import { join } from "path";
16
+ import { MMA_DIR_NAME } from "./config/paths";
13
17
  export function checkMigration(configDir) {
14
- const dir = configDir || join(homedir(), ".mma");
18
+ const dir = configDir || join(homedir(), MMA_DIR_NAME);
15
19
  const detector = new MigrationDetector(dir);
16
20
  if (detector.needsMigration()) {
17
- // Ensure a terminal subscriber exists before emitting to the bus; library
18
- // consumers have no other eager channel creation.
21
+ // Терминальный подписчик должен существовать до отправки в шину: у
22
+ // библиотечных потребителей нет другого eager-создания канала.
19
23
  getDefaultChannel();
20
24
  const backup = new BackupManager(dir);
21
25
  backup.backupConfig();