micro-models-agent 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/app.js +22 -4
  3. package/dist/assets/index.html +29 -0
  4. package/dist/assets/js/dom.js +2 -1
  5. package/dist/assets/js/feed.js +38 -26
  6. package/dist/assets/js/format.js +1 -1
  7. package/dist/assets/js/rail.js +114 -0
  8. package/dist/assets/js/settings-controls.js +2 -2
  9. package/dist/assets/js/settings.js +246 -51
  10. package/dist/assets/js/store.js +3 -0
  11. package/dist/assets/js/tabs.js +104 -2
  12. package/dist/assets/styles.css +215 -66
  13. package/dist/cli/cache-line.js +1 -0
  14. package/dist/cli/command-suggest.js +5 -4
  15. package/dist/cli/commands.js +231 -122
  16. package/dist/cli/completer.js +132 -31
  17. package/dist/cli/index.js +1 -0
  18. package/dist/cli/json-payload.js +4 -4
  19. package/dist/cli/main.js +166 -61
  20. package/dist/cli/onboarding-commands.js +126 -0
  21. package/dist/cli/plugin-commands.js +2 -1
  22. package/dist/cli/relaunch.js +16 -13
  23. package/dist/cli/repl-commands-lsp.js +75 -0
  24. package/dist/cli/repl-commands-skills.js +105 -0
  25. package/dist/cli/repl-commands.js +589 -266
  26. package/dist/cli/repl.js +436 -177
  27. package/dist/cli/run-result.js +5 -6
  28. package/dist/cli/security-commands.js +37 -28
  29. package/dist/cli/setup-order.js +4 -55
  30. package/dist/cli/setup-prompt.js +3 -2
  31. package/dist/cli/setup.js +27 -107
  32. package/dist/cli/web-command.js +98 -0
  33. package/dist/config/budget.js +5 -5
  34. package/dist/config/config.js +103 -96
  35. package/dist/config/defaults.js +38 -8
  36. package/dist/config/domains.js +24 -27
  37. package/dist/config/env.js +46 -0
  38. package/dist/config/experts.js +2 -0
  39. package/dist/config/index.js +1 -0
  40. package/dist/config/paths.js +56 -0
  41. package/dist/config/redact.js +44 -0
  42. package/dist/config/registry-extra.js +334 -0
  43. package/dist/config/registry.js +584 -0
  44. package/dist/config/security.js +35 -36
  45. package/dist/config/timeouts.js +74 -0
  46. package/dist/config/utils.js +16 -15
  47. package/dist/core/agent/artifact-progress.js +59 -0
  48. package/dist/core/agent/artifact-resolve.js +79 -0
  49. package/dist/core/agent/audit-gate.js +7 -3
  50. package/dist/core/agent/compaction.js +2 -2
  51. package/dist/core/agent/constants.js +33 -4
  52. package/dist/core/agent/context-log.js +454 -0
  53. package/dist/core/agent/context-renderer.js +5 -0
  54. package/dist/core/agent/critic-gate.js +132 -0
  55. package/dist/core/agent/hallucination-gate.js +148 -38
  56. package/dist/core/agent/loop-state.js +15 -0
  57. package/dist/core/agent/prefix-monitor.js +1 -1
  58. package/dist/core/agent/reasoning-resolver.js +10 -1
  59. package/dist/core/agent/token-tracker.js +7 -4
  60. package/dist/core/agent/tool-batch.js +93 -18
  61. package/dist/core/agent/tool-images.js +18 -0
  62. package/dist/core/agent/tool-output.js +65 -11
  63. package/dist/core/agent/tool-pairing.js +60 -0
  64. package/dist/core/agent-moe.js +25 -24
  65. package/dist/core/agent.js +429 -108
  66. package/dist/core/bootstrap/agent-phase.js +66 -0
  67. package/dist/core/bootstrap/config-phase.js +86 -0
  68. package/dist/core/bootstrap/modules-phase.js +269 -0
  69. package/dist/core/bootstrap/provider-phase.js +183 -0
  70. package/dist/core/bootstrap/session-phase.js +204 -0
  71. package/dist/core/bootstrap/state.js +94 -0
  72. package/dist/core/bootstrap/workspace-phase.js +115 -0
  73. package/dist/core/bootstrap.js +92 -660
  74. package/dist/core/crash-handler.js +17 -7
  75. package/dist/core/environment.js +37 -12
  76. package/dist/core/host-plan.js +43 -0
  77. package/dist/core/host.js +23 -0
  78. package/dist/core/index.js +1 -0
  79. package/dist/core/prompt-builder.js +17 -1
  80. package/dist/core/prompt-overflow.js +42 -43
  81. package/dist/core/session-logger.js +122 -30
  82. package/dist/core/system-prompt.js +117 -0
  83. package/dist/core/version.js +6 -4
  84. package/dist/core/web-host.js +34 -0
  85. package/dist/core/workspace.js +19 -15
  86. package/dist/i18n/en.json +106 -21
  87. package/dist/i18n/index.js +37 -3
  88. package/dist/i18n/ru.json +109 -21
  89. package/dist/index.js +7 -3
  90. package/dist/llm/image-utils.js +55 -64
  91. package/dist/llm/llm-errors.js +9 -0
  92. package/dist/llm/model-loader.js +10 -32
  93. package/dist/llm/models-dev.js +121 -0
  94. package/dist/llm/openai-compat.js +146 -73
  95. package/dist/llm/orchestrator.js +33 -11
  96. package/dist/llm/provider-budget.js +2 -2
  97. package/dist/llm/provider-context.js +233 -0
  98. package/dist/llm/provider.js +1 -1
  99. package/dist/llm/response.js +5 -0
  100. package/dist/llm/token-counter.js +27 -11
  101. package/dist/logger/app-logger.js +48 -22
  102. package/dist/logger/file-log.js +28 -19
  103. package/dist/logger/index.js +1 -0
  104. package/dist/main.js +24285 -22447
  105. package/dist/migration/backup.js +14 -3
  106. package/dist/migration/detect.js +12 -3
  107. package/dist/migration/index.js +1 -0
  108. package/dist/modules/artifacts/store.js +9 -8
  109. package/dist/modules/browser/actions.js +6 -0
  110. package/dist/modules/browser/bridge-client.js +14 -12
  111. package/dist/modules/browser/bridge-path.js +4 -4
  112. package/dist/modules/browser/console-buffer.js +37 -0
  113. package/dist/modules/browser/cookie-store.js +5 -0
  114. package/dist/modules/browser/driver-types.js +1 -0
  115. package/dist/modules/browser/driver.js +19 -11
  116. package/dist/modules/browser/index.js +3 -1
  117. package/dist/modules/browser/module.js +2 -1
  118. package/dist/modules/browser/session.js +10 -30
  119. package/dist/modules/browser/snapshot.js +19 -13
  120. package/dist/modules/browser/types.js +1 -0
  121. package/dist/modules/certification/cli.js +28 -20
  122. package/dist/modules/certification/fact-checker.js +9 -1
  123. package/dist/modules/certification/loader.js +9 -2
  124. package/dist/modules/certification/manifest.js +83 -21
  125. package/dist/modules/certification/runner.js +23 -13
  126. package/dist/modules/certification/scenarios.js +20 -20
  127. package/dist/modules/certification/syntax-scenarios.js +1 -2
  128. package/dist/modules/context/chunk-query.js +11 -9
  129. package/dist/modules/context/fact-extractor.js +37 -39
  130. package/dist/modules/context/index.js +1 -0
  131. package/dist/modules/context/manager.js +106 -80
  132. package/dist/modules/critic/client.js +176 -0
  133. package/dist/modules/critic/evidence.js +81 -0
  134. package/dist/modules/critic/provider.js +45 -0
  135. package/dist/modules/critic/tools.js +78 -0
  136. package/dist/modules/critic/types.js +1 -0
  137. package/dist/modules/execution/audit-runners.js +28 -34
  138. package/dist/modules/execution/auditor.js +53 -49
  139. package/dist/modules/execution/execution-plugin.js +142 -102
  140. package/dist/modules/execution/index.js +1 -1
  141. package/dist/modules/execution/input-from.js +37 -13
  142. package/dist/modules/execution/module.js +368 -191
  143. package/dist/modules/execution/moe-executor.js +53 -26
  144. package/dist/modules/execution/plan-actions.js +320 -202
  145. package/dist/modules/execution/plan-checks.js +35 -0
  146. package/dist/modules/execution/plan-coverage.js +36 -33
  147. package/dist/modules/execution/plan-steps.js +11 -3
  148. package/dist/modules/execution/plan-store.js +83 -17
  149. package/dist/modules/execution/plan-tool.js +89 -35
  150. package/dist/modules/execution/plan-validator.js +30 -10
  151. package/dist/modules/execution/planner.js +22 -13
  152. package/dist/modules/execution/stuck-detector.js +196 -93
  153. package/dist/modules/execution/tracker.js +22 -8
  154. package/dist/modules/execution/verifier.js +69 -48
  155. package/dist/modules/execution/windows-commands.js +12 -12
  156. package/dist/modules/hallucination/artifact-claim.js +93 -0
  157. package/dist/modules/hallucination/confidence.js +13 -11
  158. package/dist/modules/hallucination/consistency.js +10 -2
  159. package/dist/modules/hallucination/detector.js +10 -5
  160. package/dist/modules/hallucination/factual.js +43 -26
  161. package/dist/modules/hallucination/index.js +1 -0
  162. package/dist/modules/hallucination/js-identifiers.js +44 -45
  163. package/dist/modules/hallucination/llm-judge.js +17 -12
  164. package/dist/modules/hallucination/success-claim.js +59 -0
  165. package/dist/modules/indexer/cache.js +52 -4
  166. package/dist/modules/indexer/index.js +1 -0
  167. package/dist/modules/indexer/map-command.js +2 -4
  168. package/dist/modules/indexer/map-select.js +16 -16
  169. package/dist/modules/indexer/module.js +47 -12
  170. package/dist/modules/indexer/project-profile.js +8 -7
  171. package/dist/modules/indexer/symbols.js +15 -15
  172. package/dist/modules/indexer/walker.js +159 -29
  173. package/dist/modules/lsp/check-tool.js +7 -5
  174. package/dist/modules/lsp/client.js +39 -39
  175. package/dist/modules/lsp/command.js +17 -24
  176. package/dist/modules/lsp/config.js +12 -11
  177. package/dist/modules/lsp/index.js +1 -0
  178. package/dist/modules/lsp/module.js +48 -46
  179. package/dist/modules/lsp/probe.js +13 -14
  180. package/dist/modules/lsp/project-root.js +14 -17
  181. package/dist/modules/lsp/startup-check.js +18 -16
  182. package/dist/modules/mcp/client.js +59 -62
  183. package/dist/modules/mcp/index.js +1 -0
  184. package/dist/modules/mcp/module.js +66 -37
  185. package/dist/modules/mcp/registry.js +5 -0
  186. package/dist/modules/memory/index.js +1 -0
  187. package/dist/modules/memory/module.js +12 -8
  188. package/dist/modules/memory/search.js +4 -1
  189. package/dist/modules/memory/store.js +40 -2
  190. package/dist/modules/onboarding/init.js +47 -0
  191. package/dist/modules/onboarding/meet.js +30 -0
  192. package/dist/modules/onboarding/mode.js +30 -0
  193. package/dist/modules/onboarding/prompts.js +43 -0
  194. package/dist/modules/pipelines/engine.js +10 -0
  195. package/dist/modules/pipelines/index.js +1 -0
  196. package/dist/modules/pipelines/parser.js +4 -1
  197. package/dist/modules/pipelines/template.js +4 -0
  198. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  199. package/dist/modules/plugins/builtin/notify.js +2 -0
  200. package/dist/modules/plugins/index.js +1 -0
  201. package/dist/modules/plugins/loader.js +18 -3
  202. package/dist/modules/plugins/manager.js +37 -17
  203. package/dist/modules/pricing/index.js +10 -11
  204. package/dist/modules/pricing/prices.js +12 -12
  205. package/dist/modules/processes/index.js +1 -0
  206. package/dist/modules/processes/kill-tree.js +27 -31
  207. package/dist/modules/processes/registry.js +44 -25
  208. package/dist/modules/processes/runner.js +4 -4
  209. package/dist/modules/providers/capabilities.js +28 -0
  210. package/dist/modules/providers/create.js +3 -4
  211. package/dist/modules/providers/factory.js +7 -7
  212. package/dist/modules/providers/fallback.js +12 -12
  213. package/dist/modules/providers/health.js +4 -4
  214. package/dist/modules/providers/manager.js +50 -19
  215. package/dist/modules/providers/naming.js +15 -0
  216. package/dist/modules/providers/preflight.js +71 -0
  217. package/dist/modules/providers/presets.js +39 -8
  218. package/dist/modules/providers/registry.js +7 -2
  219. package/dist/modules/reasoning/policy.js +8 -6
  220. package/dist/modules/reasoning/probe.js +30 -25
  221. package/dist/modules/security/audit-log.js +19 -36
  222. package/dist/modules/security/audit-notifier.js +35 -75
  223. package/dist/modules/security/command-validator.js +99 -51
  224. package/dist/modules/security/content-scanner.js +8 -8
  225. package/dist/modules/security/data-sanitizer.js +9 -16
  226. package/dist/modules/security/encryption.js +32 -52
  227. package/dist/modules/security/index.js +2 -1
  228. package/dist/modules/security/network-validator.js +120 -43
  229. package/dist/modules/security/path-validator.js +61 -46
  230. package/dist/modules/security/protected-paths.js +73 -0
  231. package/dist/modules/security/rate-limiter.js +17 -49
  232. package/dist/modules/security/security-policies.js +25 -57
  233. package/dist/modules/security/session-encryption.js +32 -71
  234. package/dist/modules/security/session-isolation.js +15 -28
  235. package/dist/modules/session/auto-name.js +63 -0
  236. package/dist/modules/session/export.js +98 -0
  237. package/dist/modules/session/index.js +1 -0
  238. package/dist/modules/session/manager.js +79 -28
  239. package/dist/modules/session/module.js +4 -3
  240. package/dist/modules/session/store.js +57 -20
  241. package/dist/modules/setup/answers.js +35 -0
  242. package/dist/modules/setup/apply.js +79 -0
  243. package/dist/modules/setup/discovery.js +72 -0
  244. package/dist/modules/skills/index.js +1 -0
  245. package/dist/modules/skills/loader.js +9 -1
  246. package/dist/modules/skills/module.js +11 -0
  247. package/dist/modules/updater/changelog-reader.js +15 -16
  248. package/dist/modules/updater/checker.js +9 -4
  249. package/dist/modules/updater/dev-detect.js +7 -6
  250. package/dist/modules/updater/index.js +1 -0
  251. package/dist/modules/updater/module.js +17 -15
  252. package/dist/modules/user-profile/compressor.js +2 -0
  253. package/dist/modules/user-profile/index.js +1 -0
  254. package/dist/modules/user-profile/profile.js +10 -1
  255. package/dist/modules/webui/answer-api.js +29 -0
  256. package/dist/modules/webui/assets.js +50 -0
  257. package/dist/modules/webui/body.js +21 -0
  258. package/dist/modules/webui/commands-settings.js +209 -0
  259. package/dist/modules/webui/commands-shared.js +60 -0
  260. package/dist/modules/webui/commands.js +174 -0
  261. package/dist/modules/webui/config-api.js +134 -0
  262. package/dist/modules/webui/file-api.js +202 -0
  263. package/dist/modules/webui/forward.js +159 -0
  264. package/dist/modules/webui/history-api.js +112 -0
  265. package/dist/modules/webui/host-state.js +60 -0
  266. package/dist/modules/webui/i18n-api.js +9 -0
  267. package/dist/modules/webui/limits.js +6 -0
  268. package/dist/modules/webui/log-forward.js +20 -0
  269. package/dist/modules/webui/memory-commands.js +64 -0
  270. package/dist/modules/webui/model-cache.js +18 -0
  271. package/dist/modules/webui/module.js +148 -0
  272. package/dist/modules/webui/persist.js +24 -0
  273. package/dist/modules/webui/prompt-io.js +97 -0
  274. package/dist/modules/webui/providers-api.js +337 -0
  275. package/dist/modules/webui/providers-view.js +56 -0
  276. package/dist/modules/webui/reasoning-view.js +15 -0
  277. package/dist/modules/webui/run-agent.js +81 -0
  278. package/dist/modules/webui/server.js +284 -0
  279. package/dist/modules/webui/sessions-api.js +178 -0
  280. package/dist/modules/webui/settings-api.js +561 -0
  281. package/dist/modules/webui/setup-api.js +201 -0
  282. package/dist/modules/webui/state.js +74 -0
  283. package/dist/modules/webui/util.js +261 -0
  284. package/dist/output/bus.js +7 -2
  285. package/dist/output/channel.js +80 -28
  286. package/dist/output/format.js +2 -1
  287. package/dist/output/index.js +1 -0
  288. package/dist/output/json-sink.js +5 -3
  289. package/dist/output/machine.js +4 -3
  290. package/dist/output/session-sink.js +4 -4
  291. package/dist/tools/approve.js +4 -0
  292. package/dist/tools/attach-image.js +5 -4
  293. package/dist/tools/auto-fixer.js +26 -44
  294. package/dist/tools/auto-tags.js +83 -0
  295. package/dist/tools/bash.js +193 -116
  296. package/dist/tools/browser.js +76 -31
  297. package/dist/tools/chunk-query.js +14 -6
  298. package/dist/tools/create-dir.js +9 -5
  299. package/dist/tools/delete-file.js +9 -5
  300. package/dist/tools/download-file.js +32 -7
  301. package/dist/tools/edit-file.js +52 -14
  302. package/dist/tools/enable-tools.js +6 -6
  303. package/dist/tools/executor.js +132 -27
  304. package/dist/tools/file-info.js +3 -1
  305. package/dist/tools/filter-tools.js +3 -3
  306. package/dist/tools/glob-tool.js +4 -1
  307. package/dist/tools/grep-tool.js +13 -10
  308. package/dist/tools/hidden-tools-block.js +6 -6
  309. package/dist/tools/index.js +16 -8
  310. package/dist/tools/list-dir.js +4 -2
  311. package/dist/tools/load-skill.js +7 -0
  312. package/dist/tools/mcp-call.js +15 -4
  313. package/dist/tools/meet-done.js +14 -0
  314. package/dist/tools/move-file.js +14 -7
  315. package/dist/tools/path-utils.js +29 -29
  316. package/dist/tools/pipeline-run.js +29 -7
  317. package/dist/tools/preview.js +1 -1
  318. package/dist/tools/process-ids.js +31 -0
  319. package/dist/tools/process-kill.js +9 -16
  320. package/dist/tools/process-list.js +1 -0
  321. package/dist/tools/process-log.js +11 -16
  322. package/dist/tools/process-wait.js +63 -0
  323. package/dist/tools/prompt-io.js +4 -3
  324. package/dist/tools/question.js +172 -25
  325. package/dist/tools/read-file.js +55 -34
  326. package/dist/tools/recall.js +14 -8
  327. package/dist/tools/registry.js +6 -5
  328. package/dist/tools/remember.js +8 -4
  329. package/dist/tools/scope-request.js +11 -6
  330. package/dist/tools/search-history.js +5 -2
  331. package/dist/tools/session-info.js +2 -0
  332. package/dist/tools/set-thinking.js +8 -4
  333. package/dist/tools/subagent.js +59 -39
  334. package/dist/tools/syntax-validator.js +292 -90
  335. package/dist/tools/user-input.js +22 -18
  336. package/dist/tools/web-browse.js +9 -3
  337. package/dist/tools/web-fetch.js +12 -23
  338. package/dist/tools/web-search.js +7 -5
  339. package/dist/tools/write-file.js +45 -15
  340. package/dist/ui/action-log.js +13 -0
  341. package/dist/ui/box.js +4 -3
  342. package/dist/ui/colors.js +2 -0
  343. package/dist/ui/diff.js +6 -5
  344. package/dist/ui/index.js +1 -0
  345. package/dist/ui/line-editor.js +230 -122
  346. package/dist/ui/line-math.js +10 -2
  347. package/dist/ui/md-formatter.js +57 -37
  348. package/dist/ui/output.js +8 -3
  349. package/dist/ui/plan-view.js +21 -10
  350. package/dist/ui/renderer.js +393 -67
  351. package/dist/ui/spinner.js +16 -5
  352. package/dist/ui/table.js +15 -17
  353. package/dist/ui/theme.js +143 -0
  354. package/dist/utils/child-run.js +20 -0
  355. package/dist/utils/error.js +1 -1
  356. package/dist/utils/index.js +2 -0
  357. package/dist/utils/retry.js +2 -2
  358. package/dist/utils/truncate.js +1 -1
  359. package/package.json +1 -1
@@ -3,14 +3,13 @@ import { pc } from "../ui/colors";
3
3
  import { formatCost } from "../modules/pricing/prices";
4
4
  import { formatCacheLine } from "./cache-line";
5
5
  import { getDefaultChannel } from "../output";
6
+ // Печать результата one-shot прогона: стоимость/токены/кеш, код выхода.
6
7
  /**
7
- * Render a single-run AgentResult to the terminal (non-JSON mode) and return
8
- * the process exit code.
8
+ * Рендерит one-shot AgentResult в терминал (не-JSON режим) и возвращает код выхода.
9
9
  *
10
- * On failure only the error is printed. The model's `text` (which is the last
11
- * *accepted* message, i.e. possibly stale after an audit/hallucination error)
12
- * was already streamed live during the run — re-printing it here misleadingly
13
- * suggests the task succeeded.
10
+ * При провале печатается только ошибка. `text` модели (последнее *принятое*
11
+ * сообщение — возможно, протухшее после ошибки аудита/галлюцинаций) уже
12
+ * стримлено живьём во время прогона: допечатка здесь ложно намекнула бы на успех.
14
13
  */
15
14
  export function printRunResult(result, flush, channel = getDefaultChannel()) {
16
15
  flush();
@@ -1,15 +1,16 @@
1
1
  import { bootstrap } from "../core/bootstrap";
2
2
  import { saveConfig } from "../config/config";
3
- import { join, dirname } from "path";
4
- import { homedir } from "os";
3
+ import { dirname } from "path";
5
4
  import { SECURITY_POLICIES, applySecurityPolicy, getSecurityPolicy, } from "../modules/security/security-policies";
6
5
  import { globalAuditNotifier } from "../modules/security/audit-notifier";
6
+ import { DEFAULT_SECURITY_CONFIG } from "../config/security";
7
7
  import { t } from "../i18n/index";
8
8
  import { getDefaultChannel } from "../output";
9
+ import { globalConfigFile } from "../config/paths";
9
10
  /**
10
- * Toggle session-file encryption in place: flips `enabled` (and the two
11
- * per-file flags) while PRESERVING user-set fields already stored in the
12
- * config (keyPath etc.). Mutates the passed security object.
11
+ * Переключает шифрование файлов сессии на месте: флипает `enabled` (и оба
12
+ * пофайловых флага), СОХРАНЯЯ уже лежащие в конфиге юзер-поля (keyPath и др.).
13
+ * Мутирует переданный объект security.
13
14
  */
14
15
  export function toggleSessionEncryption(security, enabled) {
15
16
  const enc = (security.sessionEncryption ?? {});
@@ -19,8 +20,8 @@ export function toggleSessionEncryption(security, enabled) {
19
20
  security.sessionEncryption = enc;
20
21
  }
21
22
  /**
22
- * Toggle audit notifications in place: flips `enabled` only, preserving
23
- * webhookUrl / filePath / minSeverity / eventTypes set by the user.
23
+ * Переключает аудит-нотификации на месте: флипает только `enabled`, сохраняя
24
+ * юзерские webhookUrl / filePath / minSeverity / eventTypes.
24
25
  */
25
26
  export function toggleAuditNotifier(security, enabled) {
26
27
  const notifier = (security.auditNotifier ?? {});
@@ -28,17 +29,17 @@ export function toggleAuditNotifier(security, enabled) {
28
29
  security.auditNotifier = notifier;
29
30
  }
30
31
  /**
31
- * Create security subcommand
32
+ * Создаёт субкоманду security.
32
33
  */
33
34
  export function createSecurityCommand(program) {
34
35
  const securityCmd = program.command("security").description(t("cli.security.description"));
35
- // security status - show current security configuration
36
+ // security status — текущий конфиг безопасности.
36
37
  securityCmd
37
38
  .command("status")
38
39
  .description(t("cli.security.status"))
39
40
  .action(async () => {
40
41
  const { config } = await bootstrap();
41
- const security = config.security || {};
42
+ const security = config.security ?? DEFAULT_SECURITY_CONFIG;
42
43
  const bash = security.bash || {};
43
44
  const paths = security.paths || {};
44
45
  const network = security.network || {};
@@ -48,7 +49,7 @@ export function createSecurityCommand(program) {
48
49
  const auditNotifier = security.auditNotifier || {};
49
50
  getDefaultChannel().writeLine(t("cli.security.current_policy"));
50
51
  getDefaultChannel().writeLine(` ${t("cli.security.bash_enabled")}: ${bash.blacklist?.length > 0 ? t("cli.yes") : t("cli.no")}`);
51
- getDefaultChannel().writeLine(` ${t("cli.security.path_validation")}: ${paths.denied?.length > 0 ? t("cli.yes") : t("cli.no")}`);
52
+ getDefaultChannel().writeLine(` ${t("cli.security.path_validation")}: ${paths.enabled ? t("cli.yes") : t("cli.no")}`);
52
53
  getDefaultChannel().writeLine(` ${t("cli.security.network_validation")}: ${network.deniedDomains?.length > 0 || network.allowedDomains?.length > 0 ? t("cli.yes") : t("cli.no")}`);
53
54
  getDefaultChannel().writeLine(` ${t("cli.security.content_scanning")}: ${contentScan.enabled ? t("cli.yes") : t("cli.no")}`);
54
55
  getDefaultChannel().writeLine(` ${t("cli.security.max_recursion")}: ${security.maxRecursionDepth ?? 3}`);
@@ -57,7 +58,7 @@ export function createSecurityCommand(program) {
57
58
  getDefaultChannel().writeLine(` ${t("cli.security.session_encryption")}: ${sessionEncryption.enabled ? t("cli.yes") : t("cli.no")}`);
58
59
  getDefaultChannel().writeLine(` ${t("cli.security.audit_notifier")}: ${auditNotifier.enabled ? t("cli.yes") : t("cli.no")}`);
59
60
  });
60
- // security policies - manage security policies
61
+ // security policies — список политик.
61
62
  securityCmd
62
63
  .command("policies")
63
64
  .description(t("cli.security.policies"))
@@ -74,13 +75,13 @@ export function createSecurityCommand(program) {
74
75
  getDefaultChannel().writeLine("");
75
76
  }
76
77
  });
77
- // security set-policy - apply a security policy
78
+ // security set-policy — применить политику.
78
79
  securityCmd
79
80
  .command("set-policy")
80
81
  .argument("<preset>", t("cli.security.preset"))
81
82
  .description(t("cli.security.set_policy"))
82
83
  .action(async (preset) => {
83
- const configPath = join(homedir(), ".mma", "config.json");
84
+ const configPath = globalConfigFile();
84
85
  const { config: appConfig } = await bootstrap();
85
86
  const validPresets = ["strict", "balanced", "permissive"];
86
87
  if (!validPresets.includes(preset)) {
@@ -89,61 +90,69 @@ export function createSecurityCommand(program) {
89
90
  }
90
91
  const policy = getSecurityPolicy(preset);
91
92
  const newSecurityConfig = applySecurityPolicy(preset);
92
- // Merge with existing config
93
+ // Мерж с существующим конфигом.
93
94
  appConfig.security = newSecurityConfig;
94
95
  saveConfig(appConfig, configPath, dirname(configPath));
95
96
  getDefaultChannel().writeLine(t("cli.security.policy_applied", { name: policy.name }));
96
97
  getDefaultChannel().writeLine(t("cli.security.policy_description", { description: policy.description }));
97
98
  });
98
- // security enable-encryption - enable session file encryption
99
+ // security enable-encryption — включить шифрование файлов сессии.
99
100
  securityCmd
100
101
  .command("enable-encryption")
101
102
  .description(t("cli.security.enable_encryption"))
102
103
  .action(async () => {
103
- const configPath = join(homedir(), ".mma", "config.json");
104
+ const configPath = globalConfigFile();
104
105
  const { config: appConfig } = await bootstrap();
105
- const security = (appConfig.security = appConfig.security || {});
106
+ if (!appConfig.security)
107
+ appConfig.security = structuredClone(DEFAULT_SECURITY_CONFIG);
108
+ const security = appConfig.security;
106
109
  toggleSessionEncryption(security, true);
107
110
  saveConfig(appConfig, configPath, dirname(configPath));
108
111
  getDefaultChannel().writeLine(t("cli.security.encryption_enabled"));
109
112
  });
110
- // security disable-encryption - disable session file encryption
113
+ // security disable-encryption — выключить шифрование файлов сессии.
111
114
  securityCmd
112
115
  .command("disable-encryption")
113
116
  .description(t("cli.security.disable_encryption"))
114
117
  .action(async () => {
115
- const configPath = join(homedir(), ".mma", "config.json");
118
+ const configPath = globalConfigFile();
116
119
  const { config: appConfig } = await bootstrap();
117
- const security = (appConfig.security = appConfig.security || {});
120
+ if (!appConfig.security)
121
+ appConfig.security = structuredClone(DEFAULT_SECURITY_CONFIG);
122
+ const security = appConfig.security;
118
123
  toggleSessionEncryption(security, false);
119
124
  saveConfig(appConfig, configPath, dirname(configPath));
120
125
  getDefaultChannel().writeLine(t("cli.security.encryption_disabled"));
121
126
  });
122
- // security enable-audit - enable audit notifications
127
+ // security enable-audit — включить аудит-нотификации.
123
128
  securityCmd
124
129
  .command("enable-audit")
125
130
  .description(t("cli.security.enable_audit"))
126
131
  .action(async () => {
127
- const configPath = join(homedir(), ".mma", "config.json");
132
+ const configPath = globalConfigFile();
128
133
  const { config: appConfig } = await bootstrap();
129
- const security = (appConfig.security = appConfig.security || {});
134
+ if (!appConfig.security)
135
+ appConfig.security = structuredClone(DEFAULT_SECURITY_CONFIG);
136
+ const security = appConfig.security;
130
137
  toggleAuditNotifier(security, true);
131
138
  saveConfig(appConfig, configPath, dirname(configPath));
132
139
  getDefaultChannel().writeLine(t("cli.security.audit_enabled"));
133
140
  });
134
- // security disable-audit - disable audit notifications
141
+ // security disable-audit — выключить аудит-нотификации.
135
142
  securityCmd
136
143
  .command("disable-audit")
137
144
  .description(t("cli.security.disable_audit"))
138
145
  .action(async () => {
139
- const configPath = join(homedir(), ".mma", "config.json");
146
+ const configPath = globalConfigFile();
140
147
  const { config: appConfig } = await bootstrap();
141
- const security = (appConfig.security = appConfig.security || {});
148
+ if (!appConfig.security)
149
+ appConfig.security = structuredClone(DEFAULT_SECURITY_CONFIG);
150
+ const security = appConfig.security;
142
151
  toggleAuditNotifier(security, false);
143
152
  saveConfig(appConfig, configPath, dirname(configPath));
144
153
  getDefaultChannel().writeLine(t("cli.security.audit_disabled"));
145
154
  });
146
- // security audit-stats - show audit notification statistics
155
+ // security audit-stats — статистика аудит-нотификаций.
147
156
  securityCmd
148
157
  .command("audit-stats")
149
158
  .description(t("cli.security.audit_stats"))
@@ -1,57 +1,6 @@
1
- /** Extra commands added to the bash blacklist when the wizard enables it. */
2
- const SECURITY_BASH_BLOCKLIST = [
3
- "rm",
4
- "dd",
5
- "chmod",
6
- "wget",
7
- "curl",
8
- "scp",
9
- "ssh",
10
- "nc",
11
- "netcat",
12
- "sudo",
13
- "su",
14
- "kill",
15
- "pkill",
16
- "killall",
17
- "shutdown",
18
- "reboot",
19
- ];
20
1
  /**
21
- * Apply first-run wizard answers onto a loaded config.
22
- *
23
- * The provider is replaced with a **fresh object** rather than mutated in
24
- * place: `loadConfig` may register a session-scoped `MMA_PROVIDER_*` env
25
- * override whose `envValue` points at the loaded `config.provider` object.
26
- * `saveConfig`'s `stripEnvOverrides` then matches that object by identity and
27
- * would revert the whole provider to the env/default value, silently losing
28
- * the wizard's provider/URL/key. Replacing the object keeps the override's
29
- * stale identity intact, so only the env key is stripped and our answers are
30
- * persisted.
2
+ * Шим совместимости: логика записи конфига визарда живёт в
3
+ * `src/modules/setup/apply.ts`, чтобы её делил браузерный визард. Реэкспорт
4
+ * отсюда сохраняет терминальный путь импорта (и его тесты).
31
5
  */
32
- export function applyAnswersToConfig(config, a) {
33
- config.provider = {
34
- ...config.provider,
35
- type: a.provider,
36
- baseUrl: a.apiBase,
37
- apiKey: a.apiKey,
38
- };
39
- config.model = a.model;
40
- config.contextWindow = a.contextWindow;
41
- config.maxToolIterations = a.maxToolIterations;
42
- config.locale = a.locale;
43
- if (config.security) {
44
- config.security.enabled = true;
45
- config.security.bash.enabled = true;
46
- config.security.bash.blockDangerousFlags =
47
- a.securityFlagsBlock || config.security.bash.blockDangerousFlags;
48
- if (a.securityBashBlock) {
49
- config.security.bash.blacklist = [
50
- ...new Set([...config.security.bash.blacklist, ...SECURITY_BASH_BLOCKLIST]),
51
- ];
52
- }
53
- if (!a.securityPathsDeny)
54
- config.security.paths.denied = [];
55
- }
56
- return config;
57
- }
6
+ export { applyAnswersToConfig, SECURITY_BASH_BLOCKLIST } from "../modules/setup/apply";
@@ -1,3 +1,4 @@
1
+ /** Cooked-режимный промпт визарда: эхо/правка на терминале, мы копим до newline. */
1
2
  export class SetupPrompt {
2
3
  input;
3
4
  output;
@@ -8,7 +9,7 @@ export class SetupPrompt {
8
9
  constructor(input = process.stdin, output = process.stdout) {
9
10
  this.input = input;
10
11
  this.output = output;
11
- // Cooked mode: the console handles line editing and echo.
12
+ // Cooked-режим: правка строки и эхо — на консоли.
12
13
  if (typeof this.input.setRawMode === "function")
13
14
  this.input.setRawMode(false);
14
15
  this.onData = (buf) => this.consume(buf.toString("utf-8"));
@@ -41,7 +42,7 @@ export class SetupPrompt {
41
42
  this.buffer = "";
42
43
  const cb = this.pending;
43
44
  this.pending = null;
44
- // A TTY already echoes the Enter as a newline; only piped input needs one.
45
+ // TTY сам эхует Enter переводом строки; дописываем его только пайпам.
45
46
  if (!this.output.isTTY)
46
47
  this.output.write("\n");
47
48
  cb(answer);
package/dist/cli/setup.js CHANGED
@@ -1,9 +1,12 @@
1
+ // Визард первого запуска: язык → провайдер → ключ → модель → контекст → security.
1
2
  import { pc } from "../ui/colors";
2
3
  import { t, setLocale } from "../i18n/index";
3
4
  import { Spinner } from "../ui/spinner";
4
5
  import { BUILTIN_PROVIDERS, HOSTED_BASE_URLS } from "../modules/providers/presets";
5
6
  import { box } from "../ui/box";
6
7
  import { getDefaultChannel } from "../output";
8
+ import { SETUP_DEFAULTS, SETUP_LANGUAGES, } from "../modules/setup/answers";
9
+ import { fetchProviderModels, scanLocalServers, testProviderChat, } from "../modules/setup/discovery";
7
10
  import { SetupPrompt } from "./setup-prompt";
8
11
  async function withSpinner(message, fn) {
9
12
  const spinner = new Spinner({ channel: getDefaultChannel() });
@@ -15,12 +18,13 @@ async function withSpinner(message, fn) {
15
18
  spinner.stop();
16
19
  }
17
20
  }
18
- /** Render a numbered list inside a box. */
21
+ /** Рендерит нумерованный список в рамке. */
19
22
  function menuList(items) {
20
23
  const lines = items.map((it, i) => ` ${pc.cyan(`[${i + 1}]`)} ${it.label}${it.url ? ` ${pc.dim(it.url)}` : ""}`);
21
24
  for (const l of box(lines, { width: 72 }))
22
25
  getDefaultChannel().writeLine(l);
23
26
  }
27
+ /** Спрашивает строку; пустой ответ — defaultValue. */
24
28
  function ask(rl, question, defaultValue) {
25
29
  return new Promise((resolve) => {
26
30
  const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
@@ -29,7 +33,7 @@ function ask(rl, question, defaultValue) {
29
33
  });
30
34
  });
31
35
  }
32
- /** Parse a positive integer answer, falling back to the prompt's default. */
36
+ /** Парсит положительный int-ответ, иначе — дефолт промпта. */
33
37
  function askInt(rl, question, defaultValue) {
34
38
  return ask(rl, question, String(defaultValue)).then((raw) => {
35
39
  const n = parseInt(raw, 10);
@@ -40,113 +44,28 @@ const PROVIDER_TYPES = BUILTIN_PROVIDERS.map((spec) => ({
40
44
  value: spec.type,
41
45
  label: spec.label,
42
46
  }));
43
- /** UI languages offered by the wizard (labels stay in their own language). */
44
- const LANGUAGES = [
45
- { value: "en", label: "English" },
46
- { value: "ru", label: "Русский" },
47
- ];
48
- const KNOWN_PORTS = [
49
- { port: 1234, label: "LM Studio" },
50
- { port: 11434, label: "Ollama" },
51
- { port: 8080, label: "vLLM / custom" },
52
- { port: 4891, label: "GPT4All" },
53
- ];
54
- async function scanPorts() {
55
- const found = [];
56
- for (const { port, label } of KNOWN_PORTS) {
57
- try {
58
- const controller = new AbortController();
59
- const timeout = setTimeout(() => controller.abort(), 2000);
60
- const resp = await fetch(`http://localhost:${port}/v1/models`, {
61
- signal: controller.signal,
62
- });
63
- clearTimeout(timeout);
64
- if (resp.ok) {
65
- found.push({ label, url: `http://localhost:${port}/v1` });
66
- }
67
- }
68
- catch {
69
- /* port not available */
70
- }
71
- }
72
- return found;
73
- }
74
- async function fetchModels(apiBase, apiKey) {
75
- try {
76
- const url = `${apiBase.replace(/\/+$/, "")}/models`;
77
- const controller = new AbortController();
78
- const timeout = setTimeout(() => controller.abort(), 5000);
79
- const resp = await fetch(url, {
80
- headers: {
81
- Authorization: `Bearer ${apiKey}`,
82
- "Content-Type": "application/json",
83
- },
84
- signal: controller.signal,
85
- });
86
- clearTimeout(timeout);
87
- if (!resp.ok)
88
- return [];
89
- const data = (await resp.json());
90
- const models = (data.data || data || [])
91
- .map((m) => m.id || m.name || m.model || "")
92
- .filter(Boolean);
93
- return models;
94
- }
95
- catch {
96
- return [];
97
- }
98
- }
99
- async function testChat(apiBase, apiKey, model) {
100
- try {
101
- const url = `${apiBase.replace(/\/+$/, "")}/chat/completions`;
102
- const controller = new AbortController();
103
- const timeout = setTimeout(() => controller.abort(), 10000);
104
- const resp = await fetch(url, {
105
- method: "POST",
106
- headers: {
107
- Authorization: `Bearer ${apiKey}`,
108
- "Content-Type": "application/json",
109
- },
110
- body: JSON.stringify({
111
- model,
112
- messages: [{ role: "user", content: 'Say "ok"' }],
113
- max_tokens: 20,
114
- }),
115
- signal: controller.signal,
116
- });
117
- clearTimeout(timeout);
118
- if (!resp.ok)
119
- return false;
120
- const data = (await resp.json());
121
- const msg = data.choices?.[0]?.message;
122
- const text = msg?.content || msg?.reasoning_content || "";
123
- return text.length > 0;
124
- }
125
- catch {
126
- return false;
127
- }
128
- }
47
+ /** Прогоняет визард первого запуска (язык → провайдер → … → review). Возвращает ответы. */
129
48
  export async function runSetup(externalRl) {
130
49
  getDefaultChannel().writeLine(t("setup.title"));
131
50
  const rl = externalRl ?? new SetupPrompt();
132
51
  const ownRl = !externalRl;
133
52
  getDefaultChannel().writeLine(t("setup.language"));
134
- menuList(LANGUAGES);
135
- const languageChoice = await ask(rl, t("setup.select_language_num", { max: LANGUAGES.length }), "1");
53
+ menuList([...SETUP_LANGUAGES]);
54
+ const languageChoice = await ask(rl, t("setup.select_language_num", { max: SETUP_LANGUAGES.length }), "1");
136
55
  const typedLang = languageChoice.trim().toLowerCase();
137
- const byCode = LANGUAGES.find((l) => l.value === typedLang);
56
+ const byCode = SETUP_LANGUAGES.find((l) => l.value === typedLang);
138
57
  const locale = byCode
139
58
  ? byCode.value
140
- : LANGUAGES[Math.max(0, Math.min(LANGUAGES.length - 1, (parseInt(languageChoice) || 1) - 1))].value;
59
+ : SETUP_LANGUAGES[Math.max(0, Math.min(SETUP_LANGUAGES.length - 1, (parseInt(languageChoice, 10) || 1) - 1))].value;
141
60
  setLocale(locale);
142
61
  getDefaultChannel().writeLine(t("setup.select_provider"));
143
62
  menuList(PROVIDER_TYPES);
144
63
  const providerChoice = await ask(rl, t("setup.select_provider_num", { max: PROVIDER_TYPES.length }), "1");
145
- const providerIdx = Math.max(0, Math.min(PROVIDER_TYPES.length - 1, (parseInt(providerChoice) || 1) - 1));
64
+ const providerIdx = Math.max(0, Math.min(PROVIDER_TYPES.length - 1, (parseInt(providerChoice, 10) || 1) - 1));
146
65
  const provider = PROVIDER_TYPES[providerIdx].value;
147
66
  let apiBase = "";
148
- if (provider === "openai-compat") {
149
- const found = await withSpinner(t("setup.scanning_spinner"), async () => scanPorts());
67
+ if (provider === "openai-compat" || provider === "lmstudio") {
68
+ const found = await withSpinner(t("setup.scanning_spinner"), async () => scanLocalServers());
150
69
  if (found.length > 0) {
151
70
  getDefaultChannel().writeLine(t("setup.found_servers", { count: found.length }));
152
71
  menuList([
@@ -154,7 +73,7 @@ export async function runSetup(externalRl) {
154
73
  { label: t("setup.custom_url") },
155
74
  ]);
156
75
  const choice = await ask(rl, t("setup.select_server", { max: found.length + 1 }), "1");
157
- const idx = (parseInt(choice) || 1) - 1;
76
+ const idx = (parseInt(choice, 10) || 1) - 1;
158
77
  if (idx >= found.length) {
159
78
  apiBase = await ask(rl, t("setup.api_base"), "http://localhost:1234/v1");
160
79
  }
@@ -170,21 +89,21 @@ export async function runSetup(externalRl) {
170
89
  else {
171
90
  apiBase = HOSTED_BASE_URLS[provider] ?? "";
172
91
  }
173
- const defaultKey = provider === "openai-compat" ? "not-needed" : "";
92
+ const defaultKey = provider === "openai-compat" || provider === "lmstudio" ? "not-needed" : "";
174
93
  const apiKey = await ask(rl, t("setup.api_key"), defaultKey);
175
- const models = await withSpinner(t("setup.fetching_spinner"), () => fetchModels(apiBase, apiKey));
94
+ const models = await withSpinner(t("setup.fetching_spinner"), () => fetchProviderModels(apiBase, apiKey));
176
95
  let model = "";
177
96
  if (models.length > 0) {
178
97
  getDefaultChannel().writeLine(t("setup.available_models"));
179
98
  menuList(models.slice(0, 10).map((m) => ({ label: m })));
180
99
  const choice = await ask(rl, t("setup.select_model", { max: Math.min(models.length, 10) }), "1");
181
- const idx = Math.max(0, Math.min(models.length - 1, (parseInt(choice) || 1) - 1));
100
+ const idx = Math.max(0, Math.min(models.length - 1, (parseInt(choice, 10) || 1) - 1));
182
101
  model = models[idx];
183
102
  }
184
103
  else {
185
104
  model = await ask(rl, t("setup.model_name"), "qwen/qwen3.5-9b");
186
105
  }
187
- const connected = await withSpinner(t("setup.testing_spinner", { model }), () => testChat(apiBase, apiKey, model));
106
+ const connected = await withSpinner(t("setup.testing_spinner", { model }), () => testProviderChat(apiBase, apiKey, model));
188
107
  if (connected) {
189
108
  getDefaultChannel().writeLine(pc.green(t("setup.ok")));
190
109
  }
@@ -192,24 +111,25 @@ export async function runSetup(externalRl) {
192
111
  getDefaultChannel().writeLine(pc.yellow(t("setup.warning")));
193
112
  }
194
113
  getDefaultChannel().writeLine(t("setup.agent_settings"));
195
- const contextWindow = await askInt(rl, t("setup.context_window"), 32768);
196
- const maxIterations = await askInt(rl, t("setup.max_iters"), 1000);
114
+ const contextWindow = await askInt(rl, t("setup.context_window"), SETUP_DEFAULTS.contextWindow);
115
+ const maxIterations = await askInt(rl, t("setup.max_iters"), SETUP_DEFAULTS.maxToolIterations);
197
116
  // Security settings
198
117
  getDefaultChannel().writeLine(t("setup.security_header"));
199
118
  getDefaultChannel().writeLine(pc.dim(t("setup.security_status_on")));
200
119
  const configureSecurity = await ask(rl, t("setup.security_configure"), "n");
201
120
  let securityBashBlock = false;
202
121
  let securityFlagsBlock = false;
203
- let securityPathsDeny = true;
122
+ let securityPathsRestrict = false;
204
123
  if (configureSecurity.toLowerCase() === "y" || configureSecurity.toLowerCase() === "yes") {
205
124
  securityBashBlock = (await ask(rl, t("setup.security_bash_block"), "n")).toLowerCase() === "y";
206
125
  securityFlagsBlock =
207
126
  (await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() === "y";
208
- securityPathsDeny = (await ask(rl, t("setup.security_paths_deny"), "Y")).toLowerCase() !== "n";
127
+ const pathsAnswer = (await ask(rl, t("setup.security_paths_restrict"), "Y")).toLowerCase();
128
+ securityPathsRestrict = pathsAnswer !== "n" && pathsAnswer !== "no";
209
129
  }
210
130
  if (ownRl) {
211
- // Detach the prompt reader and pause stdin so the REPL's raw-mode
212
- // LineEditor can take over cleanly (no readline/raw-mode state left).
131
+ // Деттачим ридер промпта и ставим stdin на паузу, чтобы raw-режимный
132
+ // LineEditor REPL захватил его чисто (без leftover readline/raw-состояния).
213
133
  rl.close();
214
134
  }
215
135
  const answers = {
@@ -222,7 +142,7 @@ export async function runSetup(externalRl) {
222
142
  locale,
223
143
  securityBashBlock,
224
144
  securityFlagsBlock,
225
- securityPathsDeny,
145
+ securityPathsRestrict,
226
146
  };
227
147
  getDefaultChannel().writeLine(pc.green(pc.bold(t("setup.complete"))));
228
148
  const summary = [
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Точка входа `mma web`: bootstrap, старт браузерного UI, жизнь процесса до
3
+ * SIGINT/SIGTERM, затем чистое завершение.
4
+ */
5
+ import { existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { bootstrap } from "../core/bootstrap";
8
+ import { agentsMdPaths } from "../core/bootstrap";
9
+ import { getDefaultChannel } from "../output";
10
+ import { t } from "../i18n/index";
11
+ import { pc } from "../ui/colors";
12
+ import { startWebUi } from "../modules/webui/module";
13
+ import { tryOpenBrowser } from "../modules/webui/server";
14
+ import { globalMmaDir } from "../config/paths";
15
+ import { MMA_ENV } from "../config/env";
16
+ /**
17
+ * Парсит и валидирует флаг `--port`. `undefined`/пусто → `undefined` (рандомный
18
+ * порт). Нечисловые и вне диапазона бросают throw, а не молча откатываются на
19
+ * рандом: `--port abc` не должен маскироваться под рабочий bind.
20
+ */
21
+ export function parsePort(raw) {
22
+ if (raw === undefined || raw.trim() === "")
23
+ return undefined;
24
+ const n = Number(raw);
25
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
26
+ throw new Error(t("cli.web_invalid_port", { port: raw }));
27
+ }
28
+ return n;
29
+ }
30
+ /**
31
+ * Аргументы bootstrap для `runWeb`. Вынесены, чтобы форвардинг CLI-флагов
32
+ * (особенно `--no-agents-md`) был юнит-тестируем без подъёма настоящего агента:
33
+ * Bun-моки модулей процесс-глобальны, спай на `bootstrap` потёк бы.
34
+ */
35
+ export function webBootstrapArgs(opts) {
36
+ return [undefined, opts.projectDir, opts.noAgentsMd, true];
37
+ }
38
+ /** Запуск веб-режима: bootstrap → старт UI → ожидание SIGINT/SIGTERM. */
39
+ export async function runWeb(opts) {
40
+ // Транспорт читает их на старте (server/state): у порта нет поля конфига,
41
+ // так что CLI-флаг маппится на env, которую сервер уже уважает.
42
+ if (opts.port !== undefined)
43
+ process.env[MMA_ENV.WEBUI_PORT] = String(opts.port);
44
+ if (opts.host)
45
+ process.env[MMA_ENV.WEBUI_HOST] = opts.host;
46
+ if (opts.token)
47
+ process.env[MMA_ENV.WEBUI_TOKEN] = opts.token;
48
+ if (opts.noOpen)
49
+ process.env[MMA_ENV.WEBUI_OPEN] = "0";
50
+ const mmaDir = globalMmaDir();
51
+ const { hasDomainFiles } = await import("../config/domains");
52
+ const virgin = !existsSync(join(mmaDir, "config.json")) && !hasDomainFiles(mmaDir);
53
+ // exitOnComplete=true: в веб-режиме интерактивный промпт stdin не владеет.
54
+ const { agent, config, sessionManager, execModule, configDir, baseDir, noAgentsMd, indexerModule, memoryStore, skillsModule, pluginManager, toolRegistry, toolCtx, contextProbe, } = await bootstrap(...webBootstrapArgs(opts));
55
+ if (virgin) {
56
+ getDefaultChannel().writeLine(pc.yellow(` ${t("cli.web_setup_required")}`), "stderr");
57
+ }
58
+ const web = await startWebUi({
59
+ agent,
60
+ config,
61
+ sessionManager,
62
+ execModule,
63
+ memory: memoryStore,
64
+ skills: skillsModule,
65
+ plugins: pluginManager,
66
+ toolRegistry,
67
+ toolCtx,
68
+ configDir,
69
+ baseDir,
70
+ agentsMd: {
71
+ disabled: noAgentsMd,
72
+ files: noAgentsMd ? [] : agentsMdPaths(baseDir, configDir).filter((p) => existsSync(p)),
73
+ },
74
+ indexer: indexerModule,
75
+ contextProbe,
76
+ host: opts.host,
77
+ token: opts.token,
78
+ setupRequired: virgin,
79
+ });
80
+ const url = await web.url;
81
+ // Всегда показываем резолвленный workspace: shell-нарезка `-d` пути (пробелы,
82
+ // хвостовой бэкслэш) может молча указать не туда, и эта строка — единственное
83
+ // место, где веб-режим его репортит (зеркало баннера REPL).
84
+ getDefaultChannel().writeLine(t("cli.web_started", { url, dir: baseDir }));
85
+ if (!opts.noOpen)
86
+ tryOpenBrowser(url);
87
+ await new Promise((resolve) => {
88
+ const shutdown = () => {
89
+ process.off("SIGINT", shutdown);
90
+ process.off("SIGTERM", shutdown);
91
+ web.stop();
92
+ agent.shutdown();
93
+ resolve();
94
+ };
95
+ process.on("SIGINT", shutdown);
96
+ process.on("SIGTERM", shutdown);
97
+ });
98
+ }
@@ -1,9 +1,9 @@
1
1
  import { t } from "../i18n/index";
2
2
  const MIN_SHARE = 0.05;
3
3
  const MAX_SHARE = 0.9;
4
- /** system + reserve must leave room for history. */
4
+ /** system + reserve обязаны оставить место под историю. */
5
5
  const MAX_COMBINED = 0.95;
6
- /** Split the context window into its system / reserve / history parts. */
6
+ /** Режет окно контекста на части system / reserve / history. */
7
7
  export function budgetBreakdown(config) {
8
8
  const window = config.contextWindow;
9
9
  const budget = config.contextBudget;
@@ -21,8 +21,8 @@ export function budgetBreakdown(config) {
21
21
  };
22
22
  }
23
23
  /**
24
- * Validate and apply a share change. Returns an error reason on failure
25
- * (`"range"` or `"combined"`), or null on success. Callers localize the reason.
24
+ * Проверяет и применяет смену доли. При неуспехе возвращает причину
25
+ * (`"range"` или `"combined"`), при успехе — null. Причину локализует вызывающий.
26
26
  */
27
27
  export function setBudgetShare(config, key, value) {
28
28
  if (!Number.isFinite(value) || value < MIN_SHARE || value > MAX_SHARE)
@@ -35,7 +35,7 @@ export function setBudgetShare(config, key, value) {
35
35
  budget[key === "system" ? "systemPrompt" : "responseReserve"] = value;
36
36
  return null;
37
37
  }
38
- /** Localized budget breakdown lines (shared by `mma context` and `/context`). */
38
+ /** Локализованные строки разбивки бюджета (общие для `mma context` и `/context`). */
39
39
  export function budgetBreakdownLines(config) {
40
40
  const bd = budgetBreakdown(config);
41
41
  const pct = (f) => Math.round(f * 100);