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
package/dist/cli/repl.js CHANGED
@@ -1,19 +1,27 @@
1
+ // Интерактивный REPL: ввод, прогоны агента, slash-команды, баннер, история.
1
2
  import * as readline from "readline";
2
3
  import { pc } from "../ui/colors";
3
4
  import { LineEditor } from "../ui/line-editor";
4
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
5
6
  import { dirname, join } from "path";
6
7
  import { homedir } from "os";
7
- import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, ProviderArgProvider, ModelArgProvider, } from "./completer";
8
+ import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, ProviderArgProvider, ModelArgProvider, FilePathProvider, ConfigKeyProvider, ConfigValueProvider, FlagProvider, CommandArgProvider, buildSlashCompletions, } from "./completer";
8
9
  import { Renderer } from "../ui/renderer";
10
+ import { resolveVerbosity, glyph, toolAccent } from "../ui/theme";
11
+ import { getLastAction } from "../ui/action-log";
9
12
  import { divider } from "../ui/box";
10
13
  import { t } from "../i18n/index";
14
+ import { createHostBridge } from "../core/host";
15
+ import { planSnapshotFromModule, adaptActivePlan } from "../core/host-plan";
11
16
  import { registerAllCommands } from "./repl-commands";
17
+ import { captureConfigSnapshot } from "../config/registry";
18
+ import { activeProviderEndpoint } from "../modules/providers/manager";
12
19
  import { probeLspServers } from "../modules/lsp/probe";
13
20
  import { FileOnlyLogger } from "../modules/lsp/client";
14
21
  import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
15
22
  import { errMsg } from "../utils";
16
23
  import { SessionLogger } from "../core/session-logger";
24
+ import { exitMeetMode } from "../modules/onboarding/mode";
17
25
  import { formatPlanChecklist, stepContextForTool } from "../ui/plan-view";
18
26
  import { formatCost } from "../modules/pricing/prices";
19
27
  import { formatCacheLine } from "./cache-line";
@@ -21,6 +29,9 @@ import { probeProviders, targetsFromProviders } from "../modules/providers/healt
21
29
  import { formatWarning } from "../ui/md-formatter";
22
30
  import { defaultOutputBus, getDefaultChannel, SessionOutputSink, writeNonTtyLine, } from "../output";
23
31
  import { createPromptIO } from "../tools/prompt-io";
32
+ import { MMA_DIR_NAME, projectMmaDir } from "../config/paths";
33
+ import { MMA_ENV, readEnv } from "../config/env";
34
+ /** Рендерит бар использования контекста с компакциями и качеством. */
24
35
  function formatContextBar(used, limit, compactions, quality) {
25
36
  const pct = Math.min(100, Math.round((used / limit) * 100));
26
37
  const barLen = 20;
@@ -37,32 +48,15 @@ function formatContextBar(used, limit, compactions, quality) {
37
48
  }
38
49
  return line;
39
50
  }
40
- /**
41
- * Build the HostBridge exposed to plugins as `ctx.hostBridge`. Web clients
42
- * (and any other plugin) submit messages through the SAME runAgent path as
43
- * terminal input — rendering, session logging and single-flight guard are
44
- * shared, never duplicated.
45
- */
46
- export function createHostBridge(opts) {
47
- return {
48
- isBusy: opts.isBusy,
49
- async submit(text, o) {
50
- if (opts.isBusy())
51
- throw new Error("agent is busy");
52
- for (const url of o?.images ?? [])
53
- opts.addPendingImage(url);
54
- return opts.runAgent(text);
55
- },
56
- interrupt() {
57
- if (opts.isBusy())
58
- opts.interruptRun();
59
- },
60
- subscribeOutput: (fn) => defaultOutputBus.subscribe(fn),
61
- };
62
- }
51
+ export { createHostBridge };
52
+ /** Интерактивный REPL терминала: владеет stdin, гоняет ходы агента, slash-команды. */
63
53
  export class Repl {
54
+ /** Глобальный SIGINT-хендлер, общий на все инстансы Repl (см. registerSigint). */
55
+ static sigintHandler;
64
56
  completer = new Completer();
65
57
  modelCache = [];
58
+ /** Boot-снапшот управляемых ключей: `/config reset` откатывает из него. */
59
+ configSnapshot = new Map();
66
60
  running = false;
67
61
  agentRunning = false;
68
62
  inputLocked = false;
@@ -76,17 +70,17 @@ export class Repl {
76
70
  noAgentsMd;
77
71
  pendingClipboardImage = null;
78
72
  exitOnClose = false;
79
- /** Owner of plan state (bootstrap). Null in tests — plan UI degrades. */
73
+ /** Владелец состояния плана (bootstrap). В тестах null — план-UI деградирует. */
80
74
  execModule;
81
75
  /**
82
- * True only once the input prompt frame is actually on screen. The channel's
83
- * prompt sink stays inactive until then, so the startup banner prints plainly
84
- * instead of erasing/redrawing a frame that was never drawn.
76
+ * True, лишь когда кадр промпта ввода реально на экране. Промпт-синк канала
77
+ * неактивен до тех пор, чтобы стартовый баннер печатался plain, а не стирал/
78
+ * перерисовывал никогда не рисованный кадр.
85
79
  */
86
80
  promptPresented = false;
87
81
  /**
88
- * Set by `runAgent` so the slash-command path knows the run already restored
89
- * the prompt and must not present it a second time (e.g. `/run`).
82
+ * Ставит `runAgent`, чтобы slash-путь знал: прогон уже вернул промпт и второй
83
+ * раз его показывать нельзя (напр. `/run`).
90
84
  */
91
85
  runHandledPrompt = false;
92
86
  rl;
@@ -98,23 +92,31 @@ export class Repl {
98
92
  logger;
99
93
  slog;
100
94
  /**
101
- * Persists `data.persist === true` output events to the active session.
102
- * Inert until a SessionManager exists; re-bound by `/reload`.
95
+ * Расшаренный инстанс HostBridge (для плагинов; заодно движок `Host`-поверхности).
96
+ * Лениво строится, если PluginManager не аттачили.
97
+ */
98
+ hostBridge;
99
+ /**
100
+ * Персистит события вывода с `data.persist === true` в активную сессию.
101
+ * Инертен, пока нет SessionManager; перепривязывается через `/reload`.
103
102
  */
104
103
  sessionOutput;
105
104
  envReport;
106
- /** Background probe of the actually loaded model context (bootstrap). */
105
+ /** Фоновый пробник реально загруженного контекста модели (bootstrap). */
107
106
  contextProbe;
108
107
  /**
109
- * Single terminal writer for this REPL. Public (not private) because
110
- * `ReplContext` exposes it to slash commands as `ctx.output`; a private
111
- * field cannot satisfy a public interface property.
108
+ * Единый писатель терминала этого REPL. Публичен (не private), потому что
109
+ * `ReplContext` отдаёт его slash-командам как `ctx.output`; private-поле не
110
+ * удовлетворило бы публичному свойству интерфейса.
112
111
  */
113
112
  output;
114
- /** Bridge that lets the channel draw above the live input frame. */
113
+ /** Мост, дающий каналу рисовать поверх живого кадра ввода. */
115
114
  promptSink;
116
- /** Terminal-aware PromptIO installed on the agent's tool executor. */
115
+ /** PromptIO с осознанием терминала, ставится на экзекутор тулов агента. */
117
116
  promptIO;
117
+ get toolCtx() {
118
+ return this.agent.toolContext;
119
+ }
118
120
  constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose, envReport, historyPath, execModule, contextProbe, output) {
119
121
  this.agent = agent;
120
122
  this.config = config;
@@ -128,7 +130,7 @@ export class Repl {
128
130
  this.execModule = execModule;
129
131
  this.slog = new SessionLogger(sessionManager, logger);
130
132
  this.bindSessionOutput();
131
- this.configDir = configDir || join(homedir(), ".mma");
133
+ this.configDir = configDir || join(homedir(), MMA_DIR_NAME);
132
134
  this.baseDir = baseDir || process.cwd();
133
135
  this.noAgentsMd = noAgentsMd === true;
134
136
  this.historyPath = historyPath ?? join(homedir(), ".mma", "repl-history");
@@ -139,6 +141,11 @@ export class Repl {
139
141
  return [matches, partial];
140
142
  return [[], line];
141
143
  };
144
+ // Комплитер node-readline понимает только plain-строковые совпадения.
145
+ const readlineCompleter = (line) => {
146
+ const [items, partial] = editorCompleter(line);
147
+ return [items.map((i) => i.value), partial];
148
+ };
142
149
  if (process.stdin.isTTY) {
143
150
  this.rl = new LineEditor({
144
151
  input: process.stdin,
@@ -157,7 +164,7 @@ export class Repl {
157
164
  history: this.history,
158
165
  historySize: this.maxHistory,
159
166
  tabSize: 2,
160
- completer: editorCompleter,
167
+ completer: readlineCompleter,
161
168
  });
162
169
  }
163
170
  this.output = output ?? getDefaultChannel();
@@ -171,9 +178,9 @@ export class Repl {
171
178
  this.rl.printAbove(text);
172
179
  return;
173
180
  }
174
- // Non-TTY fallback (readline / piped stdin): no input frame to redraw
175
- // above, so write directly through the sanctioned machine-output helper
176
- // — there is no frame for `printAbove` here.
181
+ // Не-TTY фолбэк (readline / пайпнутый stdin): кадра ввода для перерисовки
182
+ // сверху нет — пишем напрямую через санкционированный машинный хелпер:
183
+ // `printAbove` здесь не про что.
177
184
  writeNonTtyLine(text);
178
185
  },
179
186
  clearScreen: () => {
@@ -182,32 +189,42 @@ export class Repl {
182
189
  },
183
190
  };
184
191
  this.output.attachPrompt(this.promptSink);
185
- // Terminal-aware prompt surface for interactive tools (question/approve).
186
- // The REPL owns stdin and is in `streaming` mode during a run, so tools
187
- // must borrow the editor for a question instead of opening a competing
188
- // readline that would fight the LineEditor's raw mode.
192
+ // Промпт-поверхность с осознанием терминала для интерактивных тулов (question/approve).
193
+ // stdin владеет REPL и на время прогона он в `streaming`: тулы обязаны одалживать
194
+ // редактор под вопрос, а не открывать конкурирующий readline, который подерётся
195
+ // с raw-режимом LineEditor.
189
196
  this.promptIO = createPromptIO({
190
197
  ask: (prompt) => this.askViaEditor(prompt),
191
198
  print: (line) => this.output.writeLine(line),
192
199
  });
193
200
  this.agent.setPromptIO?.(this.promptIO);
194
201
  registerAllCommands(this);
202
+ this.configSnapshot = captureConfigSnapshot(this.config);
195
203
  this.setupCompleter();
196
204
  this.setupListeners();
197
- // Expose the agent to plugins (web UI etc.) through a narrow bridge.
205
+ // Отдаём агента плагинам (web UI и др.) через узкий мост.
198
206
  if (pluginManager) {
199
207
  this.attachHostBridge(pluginManager);
200
208
  }
201
209
  }
202
- /** (Re)attach the plugin HostBridge. Called at construction and after
203
- * /reload replaces the PluginManager — without this, a reloaded web-ui
204
- * plugin silently loses the ability to submit messages. */
210
+ /** (Пере)аттач плагинного HostBridge. Зовётся в конструкторе и после того, как
211
+ * /reload заменил PluginManager: без этого перезагруженный web-ui плагин
212
+ * молча теряет способность сабмитить сообщения. */
205
213
  attachHostBridge(pm) {
206
214
  const manager = pm ?? this.pluginManager;
207
215
  if (!manager)
208
216
  return;
209
- manager.setHostBridge(createHostBridge({
210
- isBusy: () => this.agentRunning,
217
+ this.hostBridge ??= this.buildHostBridge();
218
+ manager.setHostBridge(this.hostBridge);
219
+ }
220
+ /** Единый busy-предикат для `Host`-поверхности и моста. */
221
+ isAgentBusy() {
222
+ return this.agentRunning;
223
+ }
224
+ /** Строит HostBridge поверх обычного run-пути этого REPL. */
225
+ buildHostBridge() {
226
+ return createHostBridge({
227
+ isBusy: () => this.isAgentBusy(),
211
228
  addPendingImage: (url) => {
212
229
  this.agent.contextManager?.addPendingImage?.({
213
230
  type: "image_url",
@@ -216,9 +233,14 @@ export class Repl {
216
233
  },
217
234
  runAgent: (text) => this.runAgent(text),
218
235
  interruptRun: () => this.agent.shutdown(),
219
- }));
236
+ });
237
+ }
238
+ /** Лениво резолвит общий мост, чтобы `Host`-методы работали без плагинов. */
239
+ getHostBridge() {
240
+ this.hostBridge ??= this.buildHostBridge();
241
+ return this.hostBridge;
220
242
  }
221
- /** (Re)bind the session output sink after construction or `/reload`. */
243
+ /** (Пере)привязка синка вывода сессии после конструктора или `/reload`. */
222
244
  bindSessionOutput() {
223
245
  this.sessionOutput?.dispose();
224
246
  this.sessionOutput = this.sessionManager
@@ -242,19 +264,20 @@ export class Repl {
242
264
  Object.assign(this.config, freshConfig);
243
265
  const result = await bootstrap(this.configDir, this.baseDir, false, false);
244
266
  this.agent = result.agent;
245
- // Re-install the prompt surface on the freshly bootstrapped agent — the
246
- // REPL's `askViaEditor` closure still belongs to this instance.
267
+ // Переустанавливаем промпт-поверхность на свежезабутстрапленного агента: замыкание
268
+ // `askViaEditor` этого REPL принадлежит всё ещё этому инстансу.
247
269
  this.agent.setPromptIO?.(this.promptIO);
248
270
  this.sessionManager = result.sessionManager;
249
271
  this.skillsModule = result.skillsModule;
250
272
  this.pluginManager = result.pluginManager;
251
273
  this.execModule = result.execModule;
252
274
  this.bindSessionOutput();
253
- // Fresh PluginManager has no bridge — re-register it, otherwise plugins
254
- // like web-ui silently lose control of the agent.
275
+ // У свежего PluginManager моста нет — перерегистрируем, иначе плагины вроде
276
+ // web-ui молча теряют управление агентом.
255
277
  this.attachHostBridge(result.pluginManager);
256
278
  this.setupCompleter();
257
279
  }
280
+ /** Грузит историю ввода из файла. */
258
281
  loadHistory() {
259
282
  if (existsSync(this.historyPath)) {
260
283
  try {
@@ -266,10 +289,11 @@ export class Repl {
266
289
  }
267
290
  }
268
291
  }
292
+ /** Сохраняет историю ввода. Падение каталога переживает молча, не бросает. */
269
293
  saveHistory() {
270
294
  try {
271
- // The history dir may be gone (e.g. a temp dir removed during test or
272
- // session teardown). Recreate it and never let a save throw.
295
+ // Каталога истории может не быть (напр. temp снесли в тесте или при teardown
296
+ // сессии). Пересоздаём; сейв бросать не должен никогда.
273
297
  mkdirSync(dirname(this.historyPath), { recursive: true });
274
298
  const allHistory = this.history.slice(-this.maxHistory);
275
299
  writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
@@ -278,33 +302,102 @@ export class Repl {
278
302
  this.logger?.debug(`Failed to save REPL history: ${e.message}`);
279
303
  }
280
304
  }
305
+ /** Строит комплитер с нуля. */
281
306
  setupCompleter() {
282
- // Rebuild from scratch: /reload calls this again after bootstrap, and
283
- // re-registering without a reset would duplicate every provider.
307
+ // Пересборка с нуля: /reload зовёт снова после bootstrap, и перерегистрация
308
+ // без reset дублировала бы каждого провайдера.
284
309
  this.completer.reset();
285
- const slashCommands = Array.from(this.commands.keys());
310
+ // `this.commands` ключуется каноническими именами И алиасами; итерация даёт
311
+ // каждый дополняемый токен с описанием его команды.
312
+ const slashCommands = buildSlashCompletions(this.commands);
286
313
  this.completer.registerProvider(new SlashCommandProvider(slashCommands));
287
314
  if (this.sessionManager) {
288
- this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
315
+ this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name), [
316
+ "/resume",
317
+ "/delete",
318
+ "/export",
319
+ ]));
289
320
  }
290
321
  if (this.skillsModule) {
291
- this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
322
+ this.completer.registerProvider(new SubcommandProvider("skill", [
323
+ { name: "list", description: t("skill.sub_list") },
324
+ { name: "loaded", description: t("skill.sub_loaded") },
325
+ { name: "load", description: t("skill.sub_load") },
326
+ { name: "unload", description: t("skill.sub_unload") },
327
+ { name: "search", description: t("skill.sub_search") },
328
+ ]));
292
329
  this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
293
330
  }
294
- // Subcommand + argument completion for provider/model management.
295
- this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use", "add"]));
296
- this.completer.registerProvider(new SubcommandProvider("model", ["list", "use"]));
331
+ // Комплит субкоманд + аргументов управления provider/model.
332
+ this.completer.registerProvider(new SubcommandProvider("provider", [
333
+ { name: "list", description: t("provider.sub_list") },
334
+ { name: "use", description: t("provider.sub_use") },
335
+ { name: "add", description: t("provider.sub_add") },
336
+ ]));
337
+ this.completer.registerProvider(new SubcommandProvider("model", [
338
+ { name: "list", description: t("model.sub_list") },
339
+ { name: "use", description: t("model.sub_use") },
340
+ ]));
341
+ this.completer.registerProvider(new SubcommandProvider("map", [
342
+ { name: "summary", description: t("map.sub_summary") },
343
+ { name: "refresh", description: t("map.sub_refresh") },
344
+ { name: "find", description: t("map.sub_find") },
345
+ ]));
346
+ this.completer.registerProvider(new SubcommandProvider("context", [
347
+ { name: "system", description: t("context.sub_system") },
348
+ { name: "reserve", description: t("context.sub_reserve") },
349
+ ]));
350
+ this.completer.registerProvider(new SubcommandProvider("lsp", [
351
+ { name: "status", description: t("lsp.sub_status") },
352
+ { name: "restart", description: t("lsp.sub_restart") },
353
+ { name: "check", description: t("lsp.sub_check") },
354
+ ]));
355
+ this.completer.registerProvider(new SubcommandProvider("reasoning", [
356
+ { name: "show", description: t("reasoning.sub_show") },
357
+ { name: "hide", description: t("reasoning.sub_hide") },
358
+ { name: "level", description: t("reasoning.sub_level") },
359
+ ]));
360
+ this.completer.registerProvider(new SubcommandProvider("verbose", [
361
+ { name: "quiet", description: t("verbose.sub_quiet") },
362
+ { name: "normal", description: t("verbose.sub_normal") },
363
+ { name: "verbose", description: t("verbose.sub_verbose") },
364
+ ]));
365
+ this.completer.registerProvider(new SubcommandProvider("security", [
366
+ { name: "status", description: t("cli.security.status") },
367
+ ]));
368
+ this.completer.registerProvider(new ConfigKeyProvider());
369
+ this.completer.registerProvider(new ConfigValueProvider());
370
+ this.completer.registerProvider(new FlagProvider());
371
+ this.completer.registerProvider(new CommandArgProvider(() => Array.from(this.commands.entries())
372
+ .filter(([key, cmd]) => key === cmd.name)
373
+ .map(([, cmd]) => cmd.name)));
297
374
  const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
298
375
  if (entryLabels.length > 0) {
299
376
  this.completer.registerProvider(new ProviderArgProvider(entryLabels));
300
377
  }
301
378
  this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
302
- // Warm the model cache in the background (free listModels call).
379
+ // Комплит путей якорится на workspace агента, а не process.cwd().
380
+ this.completer.registerProvider(new FilePathProvider(this.baseDir));
381
+ // Греем кеш моделей фоном (бесплатный listModels-вызов).
303
382
  this.refreshModelCache();
304
383
  }
384
+ /** Перестройка реестра + комплитера — после смены `/config locale`. */
385
+ reregisterCommands() {
386
+ this.commands.clear();
387
+ registerAllCommands(this);
388
+ this.setupCompleter();
389
+ }
390
+ /** Boot-снапшот управляемых ключей для `/config reset`. */
391
+ getConfigSnapshot() {
392
+ return this.configSnapshot;
393
+ }
394
+ /** Кешированные id моделей активного провайдера (пусто, пока офлайн). */
395
+ knownModels() {
396
+ return this.modelCache;
397
+ }
305
398
  /**
306
- * Background refresh of the cached model list used by `/model use <Tab>`.
307
- * Best-effort: on failure the cache keeps its previous contents.
399
+ * Фоновое обновление кешированного списка моделей для `/model use <Tab>`.
400
+ * Best-effort: при провале кеш хранит прошлое содержимое.
308
401
  */
309
402
  refreshModelCache() {
310
403
  void (async () => {
@@ -346,7 +439,7 @@ export class Repl {
346
439
  }
347
440
  }
348
441
  if (inMultiLine) {
349
- multiLineBuffer += "\n" + line;
442
+ multiLineBuffer += `\n${line}`;
350
443
  if (!this.isMultiLineInput(multiLineBuffer)) {
351
444
  inMultiLine = false;
352
445
  const fullInput = multiLineBuffer.trim();
@@ -391,7 +484,7 @@ export class Repl {
391
484
  this.running = false;
392
485
  this.saveHistory();
393
486
  this.agent.shutdown();
394
- // Close session log when REPL exits
487
+ // Закрываем лог сессии на выходе из REPL.
395
488
  this.logger?.closeSessionLog();
396
489
  if (this.exitOnClose)
397
490
  process.exit(0);
@@ -401,7 +494,7 @@ export class Repl {
401
494
  this.running = false;
402
495
  this.saveHistory();
403
496
  this.agent.shutdown();
404
- // Close session log when REPL exits
497
+ // Закрываем лог сессии на выходе из REPL.
405
498
  this.logger?.closeSessionLog();
406
499
  process.exit(0);
407
500
  });
@@ -410,10 +503,12 @@ export class Repl {
410
503
  this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
411
504
  }
412
505
  let forceExitTimer = null;
413
- // Idempotent registration: a second Repl in the same process must not add
414
- // another global SIGINT handler. Leaked handlers from REPL instances
415
- // created in tests fired process.exit(0) on ANY process.emit("SIGINT")
416
- // elsewhere and silently terminated the whole bun test runner.
506
+ // Запас на очистку (сессия, MCP, дочерние процессы) после второго Ctrl+C.
507
+ const FORCE_EXIT_MS = 5000;
508
+ // Идемпотентная регистрация: второй Repl в том же процессе не добавляет
509
+ // ещё один глобальный SIGINT-хендлер. Протёкшие хендлеры инстансов REPL,
510
+ // созданные в тестах, стреляли process.exit(0) на ЛЮБОЙ process.emit("SIGINT")
511
+ // в другом месте и молча убивали весь bun-раннер тестов.
417
512
  if (typeof Repl.sigintHandler === "function") {
418
513
  process.removeListener("SIGINT", Repl.sigintHandler);
419
514
  }
@@ -424,7 +519,10 @@ export class Repl {
424
519
  this.agentRunning = false;
425
520
  if (forceExitTimer)
426
521
  clearTimeout(forceExitTimer);
427
- forceExitTimer = setTimeout(() => process.exit(1), 2000).unref();
522
+ forceExitTimer = setTimeout(() => {
523
+ this.output.writeLine(pc.yellow(t("repl.force_exit")));
524
+ process.exit(1);
525
+ }, FORCE_EXIT_MS).unref();
428
526
  }
429
527
  else {
430
528
  process.exit(0);
@@ -433,10 +531,10 @@ export class Repl {
433
531
  process.on("SIGINT", Repl.sigintHandler);
434
532
  }
435
533
  /**
436
- * Re-present the input prompt after a slash command. Commands do not own a
437
- * frame lifecycle, so they need this explicit redraw. The agent path is
438
- * deliberately excluded: `runAgent`'s finally calls `LineEditor.resumeFrame()`
439
- * as its single prompt presentation, so prompting here again would double-draw.
534
+ * Перепоказ промпта ввода после slash-команды. У команд нет владения жизненным
535
+ * циклом кадра, им нужна явная перерисовка. Путь агента осознанно исключён:
536
+ * finally `runAgent` зовёт `LineEditor.resumeFrame()` как единственный показ —
537
+ * промпт здесь дал бы двойную отрисовку.
440
538
  */
441
539
  presentPrompt() {
442
540
  if (!this.running)
@@ -445,13 +543,20 @@ export class Repl {
445
543
  this.rl.prompt();
446
544
  this.promptPresented = true;
447
545
  }
546
+ /** Возврат обычного промпта после стартового вопроса, если активен LineEditor. */
547
+ restoreStartupPrompt() {
548
+ if (this.rl instanceof LineEditor) {
549
+ this.rl.setPrompt(this.inputPrompt());
550
+ this.rl.prompt();
551
+ this.promptPresented = true;
552
+ }
553
+ }
448
554
  /**
449
- * Reasoning label shown in the input prompt. In `auto` mode the effective
450
- * level is normally decided per iteration by the policy, so we show only the
451
- * fact that reasoning is automatic. If the user set an explicit override
452
- * (`/reasoning <level>`, persistent) or the agent set a still-active
453
- * `set_thinking` override (transient), we show `auto→<level>`. In a fixed
454
- * mode we show the effective level.
555
+ * Ярлык reasoning в промпте ввода. В `auto` эффективный уровень обычно решает
556
+ * политика поитерационно, так что показываем лишь факт автоматики. При явном
557
+ * оверрайде юзера (`/reasoning <level>`, персистентный) или активном
558
+ * `set_thinking`-оверрайде агента (транзиентный) — `auto→<level>`. В фикс.
559
+ * режиме — эффективный уровень.
455
560
  */
456
561
  reasoningLabel() {
457
562
  const mode = this.config.reasoning?.mode ?? "default";
@@ -464,16 +569,15 @@ export class Repl {
464
569
  return `auto→${this.agent.reasoningLevel}`;
465
570
  return "auto";
466
571
  }
467
- /** Localized input prompt embedding the active reasoning mode/level. */
572
+ /** Локализованный промпт ввода со встроенным режимом/уровнем reasoning. */
468
573
  inputPrompt(continuation = false) {
469
574
  const key = continuation ? "repl.you_reasoning_cont" : "repl.you_reasoning";
470
575
  return pc.cyan(t(key, { level: this.reasoningLabel() }));
471
576
  }
472
577
  /**
473
- * Prompt bookkeeping after a slash command. A command that drove a run
474
- * (e.g. `/run`) already had its prompt restored by `runAgent`; presenting
475
- * again would double-render, so skip it exactly once. All other commands
476
- * present the prompt here.
578
+ * Учёт промпта после slash-команды. Команда, прогнавшая прогон (напр. `/run`),
579
+ * уже вернула себе промпт через `runAgent`: повторный показ двоил бы рендер,
580
+ * так что скипаем ровно раз. Все прочие команды показывают промпт здесь.
477
581
  */
478
582
  afterCommand() {
479
583
  if (this.runHandledPrompt) {
@@ -482,12 +586,12 @@ export class Repl {
482
586
  }
483
587
  this.presentPrompt();
484
588
  }
485
- handleSpecialKey(str, key) {
589
+ /** Спецклавиши: двойной Esc — аборт прогона, Ctrl+V — вставка картинки, Ctrl+O — детали. */
590
+ handleSpecialKey(_str, key) {
486
591
  if (key.name === "escape") {
487
- // Bun's keypress decoder collapses a fast double-Esc into a single
488
- // keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
489
- // Counting bytes (not events) catches both the collapsed case and
490
- // the case where two separate escape keypresses land in the window.
592
+ // Декодер keypress Bun схлопывает быстрый двойной Esc в один keypress, чей
593
+ // `sequence` содержит два ESC-байта ("\x1b\x1b"). Подсчёт байтов (не событий)
594
+ // ловит и схлопнутый случай, и два раздельных Esc в окне.
491
595
  const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
492
596
  const now = Date.now();
493
597
  const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
@@ -505,8 +609,34 @@ export class Repl {
505
609
  this.pasteClipboardImage();
506
610
  return true;
507
611
  }
612
+ if (key.ctrl && key.name === "o" && !this.agentRunning) {
613
+ this.reprintLastAction();
614
+ return true;
615
+ }
508
616
  return false;
509
617
  }
618
+ /** Ctrl+O — допечатка полных деталей последнего действия. */
619
+ reprintLastAction() {
620
+ const last = getLastAction();
621
+ if (!last) {
622
+ this.output.writeLine(pc.dim(t("repl.last_action_none")));
623
+ return;
624
+ }
625
+ const mark = last.status === "error"
626
+ ? pc.red(glyph.failure)
627
+ : last.status === "warn"
628
+ ? pc.yellow(glyph.warning)
629
+ : pc.green(glyph.success);
630
+ const accent = toolAccent(last.tool);
631
+ const head = `${accent(glyph.action)} ${accent(last.label)}${last.args ? ` ${pc.dim("·")} ${pc.dim(last.args)}` : ""} ${mark} ${pc.dim(`${last.durationMs}ms`)}`;
632
+ const lines = [head, ...last.body.map((l) => ` ${l}`)];
633
+ this.output.writeLine(`\n${lines.join("\n")}`);
634
+ if (this.rl instanceof LineEditor) {
635
+ this.rl.reset();
636
+ this.rl.prompt();
637
+ }
638
+ }
639
+ /** Вставка картинки из буфера обмена (Ctrl+V). */
510
640
  async pasteClipboardImage() {
511
641
  try {
512
642
  const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
@@ -528,9 +658,10 @@ export class Repl {
528
658
  }
529
659
  }
530
660
  catch {
531
- // clipboard read failed — ignore, let terminal paste text normally
661
+ // Чтение буфера не удалось — игнорим, пусть терминал вставит текст штатно.
532
662
  }
533
663
  }
664
+ /** Проверка незакрытого многострочного ввода (хвостовой `\` или `{` без `}`). */
534
665
  isMultiLineInput(line) {
535
666
  if (line.endsWith("\\"))
536
667
  return true;
@@ -540,6 +671,7 @@ export class Repl {
540
671
  return true;
541
672
  return false;
542
673
  }
674
+ /** Прогон хода агента: стрим в рендерер, футер, бары, возврат промпта. */
543
675
  async runAgent(input) {
544
676
  if (this.agentRunning)
545
677
  return;
@@ -556,27 +688,27 @@ export class Repl {
556
688
  this.pendingClipboardImage = null;
557
689
  }
558
690
  this.logger?.logREPL("user", input);
559
- // The prompt frame is about to be taken down; deactivate the sink before
560
- // any streamed output so nothing tries to draw above it.
691
+ // Кадр промпта сейчас снимется; гасим синк до любого стримленного вывода,
692
+ // чтобы ничего не пыталось рисовать поверх него.
561
693
  this.promptPresented = false;
562
- // Hand the terminal to the channel for the duration of the run: the
563
- // editor stops drawing its frame and ignores typed keys, so streamed
564
- // output cannot corrupt the input line. suspendFrame() runs before
565
- // reset(), which would otherwise draw a frame only to erase it.
694
+ // Отдаём терминал каналу на время прогона: редактор перестаёт рисовать кадр
695
+ // и игнорит клавиши, и стрим не портит строку ввода. suspendFrame() идёт до
696
+ // reset(), который иначе нарисовал бы кадр лишь затем, чтобы стереть.
566
697
  if (this.rl instanceof LineEditor) {
567
698
  this.rl.suspendFrame();
568
699
  this.rl.setInputEnabled(false);
569
700
  this.rl.reset();
570
701
  }
571
702
  this.output.beginStreaming();
572
- // Divider + agent label, written raw (no trailing newline): the next
573
- // Renderer.text() chunk adds its own leading "\n" ("- ..."), so a
574
- // writeLine here would insert a blank line ("Agent:\n\n- ...").
703
+ // Разделитель + ярлык агента, сырьём (без trailing newline): следующий чанк
704
+ // Renderer.text() добавляет свой ведущий "\n", и writeLine здесь вставил бы
705
+ // пустую строку ("Agent:\n\n...").
575
706
  this.output.writeRaw(`\r${divider()}\n${pc.green(t("repl.agent"))}`);
576
707
  const renderer = new Renderer({
577
708
  spinner: this.config.ui?.spinner ?? true,
578
709
  baseDir: this.baseDir,
579
710
  channel: this.output,
711
+ verbosity: resolveVerbosity(this.config.ui),
580
712
  });
581
713
  renderer.showLoader();
582
714
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
@@ -585,7 +717,7 @@ export class Repl {
585
717
  }
586
718
  else {
587
719
  renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd);
588
- if (ev.tool === "plan" || ev.tool === "todo") {
720
+ if (ev.tool === "plan" || ev.tool === "todo" || ev.tool === "plan_step") {
589
721
  this.renderPlan(renderer);
590
722
  }
591
723
  }
@@ -596,7 +728,7 @@ export class Repl {
596
728
  else {
597
729
  renderer.thinkingEnd();
598
730
  }
599
- });
731
+ }, (reasoning) => renderer.reasoning(reasoning));
600
732
  renderer.flush();
601
733
  if (result.provider && result.model) {
602
734
  renderer.footer(result.model, result.provider, result.llmDurationMs ?? result.durationMs ?? 0);
@@ -607,8 +739,8 @@ export class Repl {
607
739
  this.output.writeLine(pc.red(`${t("error.prefix")}${result.error}`), "stderr");
608
740
  }
609
741
  this.showContextBar(result);
610
- // Close the agent's turn: a divider before the next prompt keeps
611
- // consecutive user/agent messages visually separated.
742
+ // Закрываем ход агента: разделитель перед следующим промптом визуально
743
+ // разводит соседние сообщения юзера/агента.
612
744
  this.output.writeLine(`\n${divider()}`);
613
745
  }
614
746
  finally {
@@ -616,26 +748,32 @@ export class Repl {
616
748
  this.output.endStreaming();
617
749
  if (this.rl instanceof LineEditor) {
618
750
  this.rl.setInputEnabled(true);
619
- // Single prompt presentation for the agent path: resumeFrame()
620
- // redraws the frame, so the `line` listener must NOT prompt again
621
- // after runAgent() (it still does after slash commands).
622
- // Re-set the prompt first so a reasoning level changed by
623
- // set_thinking during the run is reflected on the resumed frame.
751
+ // Единственный показ промпта на пути агента: resumeFrame() перерисовывает
752
+ // кадр, так что слушатель `line` после runAgent() промпт НЕ показывает
753
+ // (после slash-команд — показывает). Промпт переставляем первым, чтобы
754
+ // уровень reasoning, сменённый set_thinking в прогоне, отразился на
755
+ // возобновлённом кадре.
624
756
  this.rl.setPrompt(this.inputPrompt());
625
757
  this.rl.resumeFrame();
626
758
  this.promptPresented = true;
627
759
  }
628
760
  else {
629
- // Piped/non-TTY fallback has no frame to resume; present the readline
630
- // prompt here to preserve the old post-run behavior.
761
+ // У пайпнутого/не-TTY фолбэка кадра для resume нет; показываем readline-
762
+ // промпт здесь ради старого пост-прогонного поведения.
631
763
  this.presentPrompt();
632
764
  }
765
+ const onboardingCtx = this.agent.tryToolContext();
766
+ if (onboardingCtx?.onboarding?.done) {
767
+ exitMeetMode(this);
768
+ this.slog.logOnboarding("meet_done", { model: this.config.model });
769
+ this.output.writeLine(pc.dim(t("repl.meet_done")));
770
+ }
633
771
  this.runHandledPrompt = true;
634
772
  }
635
773
  }
636
- /** Print the checklist of the plan currently tracked by ExecutionModule.
637
- * Called only on plan/todo tool events (a change event) — never polled,
638
- * so a disk-resident plan from a previous session cannot leak into chat. */
774
+ /** Печать чеклиста плана, трекаемого ExecutionModule.
775
+ * Зовётся только на plan/todo-событиях (событие изменения), не поллится:
776
+ * дисковый план прошлой сессии не протечёт в чат. */
639
777
  renderPlan(renderer) {
640
778
  const plan = this.execModule?.getActivePlan();
641
779
  if (!plan)
@@ -643,6 +781,7 @@ export class Repl {
643
781
  renderer.planBlock(formatPlanChecklist(plan));
644
782
  }
645
783
  commands = new Map();
784
+ /** Регистрирует slash-команду (имя + алиасы). */
646
785
  registerCommand(cmd) {
647
786
  this.commands.set(cmd.name, cmd);
648
787
  if (cmd.aliases) {
@@ -651,12 +790,12 @@ export class Repl {
651
790
  }
652
791
  }
653
792
  }
793
+ /** Эксклюзивный ввод: визард владеет терминалом через свои `rl.question()`. */
654
794
  async withExclusiveInput(fn) {
655
- // The wizard owns the terminal through its own `rl.question()` calls. Between
656
- // questions no frame is active, so its menus/scan results must be written
657
- // directly — bracketing with beginModal/endModal would QUEUE the whole
658
- // transcript and the user would only see bare `: ` prompts until completion.
659
- // `inputLocked` makes `promptSink.isActive()` false, routing writes direct.
795
+ // Между вопросами активного кадра нет, так что меню/результаты скана пишутся
796
+ // напрямую: обёртка beginModal/endModal сложила бы ВЕСЬ транскрипт в очередь,
797
+ // и юзер видел бы голые `: ` промпты до конца. `inputLocked` гасит
798
+ // `promptSink.isActive()`, пуская записи напрямую.
660
799
  this.inputLocked = true;
661
800
  try {
662
801
  await fn();
@@ -666,16 +805,14 @@ export class Repl {
666
805
  }
667
806
  }
668
807
  /**
669
- * Borrow the terminal for one interactive-tool question and hand it back.
670
- * During a run the LineEditor is suspended with input disabled; enabling
671
- * input + resuming the frame lets `question()` read keys while the agent
672
- * blocks, and the `finally` restores exactly the streaming state so the
673
- * agent's own renderer keeps working afterwards.
808
+ * Одалживает терминал под один вопрос интерактивного тула и возвращает обратно.
809
+ * На время прогона LineEditor suspended с выключенным вводом; включение ввода +
810
+ * resume кадра даёт `question()` читать клавиши, пока агент блокирован, а `finally`
811
+ * восстанавливает ровно стрим-состояние, и рендерер агента работает дальше как ни в чём.
674
812
  *
675
- * Order matters: `beginModal()` first flushes any output still buffered by
676
- * the streaming channel so it prints plainly before the frame returns;
677
- * `endModal()` runs only after the frame is suspended again, so events
678
- * queued during the question flush plainly instead of corrupting the frame.
813
+ * Порядок важен: `beginModal()` сначала сбрасывает вывод, ещё буферный у стрим-канала,
814
+ * чтобы напечатался plain до возврата кадра; `endModal()` идёт лишь после повторного
815
+ * suspend кадра, чтобы события, накопленные за вопрос, сбросились plain, а не побили кадр.
679
816
  */
680
817
  async askViaEditor(prompt) {
681
818
  const editor = this.rl instanceof LineEditor ? this.rl : null;
@@ -700,16 +837,60 @@ export class Repl {
700
837
  this.output.endModal();
701
838
  }
702
839
  }
703
- async executeCommand(input) {
840
+ /** Host/HostBridge: true, пока агент крутит ход. */
841
+ isBusy() {
842
+ return this.isAgentBusy();
843
+ }
844
+ /** Host/HostBridge: сабмит через общий мост (тот же run-путь). */
845
+ submit(text, opts) {
846
+ return this.getHostBridge().submit(text, opts);
847
+ }
848
+ /** Host/HostBridge: прерывание текущего прогона (в idle — no-op). */
849
+ interrupt() {
850
+ this.getHostBridge().interrupt();
851
+ }
852
+ /** Host/HostBridge: подписка на общую шину вывода. */
853
+ subscribeOutput(fn) {
854
+ return defaultOutputBus.subscribe(fn);
855
+ }
856
+ /** Host: прогон slash-команды через ту же handler-map, что и ручной ввод. */
857
+ async executeCommand(line) {
858
+ const trimmed = line.trim();
859
+ if (!trimmed.startsWith("/"))
860
+ return;
861
+ await this.dispatchCommand(trimmed);
862
+ }
863
+ /** Host: снапшот план/сессия/модель для UI-поверхностей. */
864
+ getState() {
865
+ const meta = this.sessionManager?.getActiveMeta();
866
+ return {
867
+ busy: this.agentRunning,
868
+ sessionId: meta?.id,
869
+ sessionName: meta?.name,
870
+ model: this.config.model,
871
+ provider: this.config.provider?.active,
872
+ plan: planSnapshotFromModule(adaptActivePlan(this.execModule?.getActivePlan())),
873
+ };
874
+ }
875
+ async dispatchCommand(input) {
704
876
  const parts = input.split(/\s+/);
705
877
  const name = parts[0].slice(1);
706
878
  const args = parts.slice(1);
707
- // Clear any stale run flag from a previous direct agent run so this
708
- // command's own presentPrompt() decision is based on its own action.
879
+ // Сбрасываем протухший run-флаг прошлого прямого прогона, чтобы решение
880
+ // presentPrompt() этой команды строилось на её собственном действии.
709
881
  this.runHandledPrompt = false;
710
882
  const cmd = this.commands.get(name);
711
883
  if (!cmd) {
712
- this.output.writeLine(`${pc.red(t("cli.unknown_cmd", { name }))} ${t("cli.help_hint")}`);
884
+ // Бывшие алиасы, уехавшие в субкоманду: указываем на новый синтаксис,
885
+ // а не на голое "unknown command".
886
+ const formerAliases = { use: "resume" };
887
+ const suggestion = formerAliases[name];
888
+ if (suggestion) {
889
+ this.output.writeLine(pc.red(t("cli.unknown_command", { input: `/${name}`, suggestion: `/${suggestion}` })));
890
+ }
891
+ else {
892
+ this.output.writeLine(`${pc.red(t("cli.unknown_cmd", { name }))} ${t("cli.help_hint")}`);
893
+ }
713
894
  return;
714
895
  }
715
896
  try {
@@ -721,8 +902,30 @@ export class Repl {
721
902
  })), "stderr");
722
903
  }
723
904
  }
724
- showHelp() {
905
+ /** Показ помощи: общая или по команде (`/help <команда>`). */
906
+ showHelp(command) {
725
907
  const { COMMAND_GROUPS } = require("./repl-commands");
908
+ if (command) {
909
+ const name = command.replace(/^\//, "");
910
+ const cmd = this.commands.get(name);
911
+ if (!cmd) {
912
+ this.output.writeLine(pc.red(t("repl.help_unknown", { name })));
913
+ return;
914
+ }
915
+ this.output.writeLine(pc.bold(pc.cyan(`/${cmd.name}`)));
916
+ this.output.writeLine(` ${cmd.description}`);
917
+ if (cmd.usage)
918
+ this.output.writeLine(` ${pc.dim(cmd.usage)}`);
919
+ if (cmd.aliases?.length) {
920
+ this.output.writeLine(pc.dim(` ${t("repl.help_aliases", { names: cmd.aliases.join(", ") })}`));
921
+ }
922
+ if (cmd.examples?.length) {
923
+ this.output.writeLine(pc.dim(` ${t("repl.help_examples")}`));
924
+ for (const ex of cmd.examples)
925
+ this.output.writeLine(pc.dim(` ${ex}`));
926
+ }
927
+ return;
928
+ }
726
929
  const order = ["general", "agent", "session", "skill"];
727
930
  const seen = new Set();
728
931
  for (const groupKey of order) {
@@ -743,7 +946,7 @@ export class Repl {
743
946
  this.output.writeLine(pc.bold(t(`repl.group.${groupKey}`)));
744
947
  for (const cmd of cmds) {
745
948
  const aliases = cmd.aliases?.length ? ` (${pc.dim(cmd.aliases.join(", "))})` : "";
746
- this.output.writeLine(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
949
+ this.output.writeLine(` ${pc.cyan(`/${cmd.name}`)}${aliases} ${pc.dim(cmd.description)}`);
747
950
  if (cmd.usage) {
748
951
  this.output.writeLine(` ${pc.dim(cmd.usage)}`);
749
952
  }
@@ -764,7 +967,11 @@ export class Repl {
764
967
  const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
765
968
  this.output.writeLine(ctxLine);
766
969
  if (result.totalTokens !== undefined && result.totalTokens > 0) {
767
- const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
970
+ const apiLine = pc.dim(t("repl.api_line", {
971
+ prompt: String(result.promptTokens),
972
+ completion: String(result.completionTokens),
973
+ total: String(result.totalTokens),
974
+ }));
768
975
  this.output.writeLine(apiLine);
769
976
  }
770
977
  if (result.totalCost !== undefined && result.totalCost > 0) {
@@ -778,7 +985,7 @@ export class Repl {
778
985
  else if (ui?.showCompaction && result.compactionCount !== undefined) {
779
986
  if (result.compactionCount > this.lastCompactionShown) {
780
987
  this.lastCompactionShown = result.compactionCount;
781
- this.output.writeLine(pc.dim(`\n ⟳ Context compacted (${result.compactionCount})`));
988
+ this.output.writeLine(pc.dim(`\n ⟳ ${t("repl.compacted", { count: String(result.compactionCount) })}`));
782
989
  }
783
990
  }
784
991
  }
@@ -793,13 +1000,18 @@ export class Repl {
793
1000
  const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
794
1001
  const histBudget = ctx - sysBudget - resBudget;
795
1002
  row(t("repl.model"), pc.white(this.config.model));
796
- row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
1003
+ // Плоские поля протухают после `/provider use` — истина в entries.
1004
+ const endpoint = activeProviderEndpoint(this.config.provider);
1005
+ row(t("repl.provider"), `${endpoint.type} → ${pc.dim(endpoint.baseUrl)}`);
797
1006
  row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
798
1007
  const si = this.agent.getSystemPromptInfo();
799
1008
  row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
800
1009
  if (this.skillsModule) {
801
1010
  const budget = this.skillsModule.getBudget();
802
- row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
1011
+ row(t("repl.skills_label"), t("repl.skills_available", {
1012
+ count: String(this.skillsModule.getAvailable().length),
1013
+ budget: String(budget.total),
1014
+ }));
803
1015
  }
804
1016
  if (this.pluginManager) {
805
1017
  const infos = this.pluginManager.getPluginInfos().filter(({ plugin }) => !plugin.isBuiltin);
@@ -811,7 +1023,7 @@ export class Repl {
811
1023
  return `${plugin.name}${version}${origin}`;
812
1024
  })
813
1025
  .join(", ");
814
- row(t("repl.plugins_label"), `${pc.white(String(infos.length))} active ${pc.dim(`(${pluginNames})`)}`);
1026
+ row(t("repl.plugins_label"), t("repl.plugins_active", { count: String(infos.length), names: pluginNames }));
815
1027
  }
816
1028
  }
817
1029
  const mcpServers = this.config.mcpServers || {};
@@ -820,8 +1032,8 @@ export class Repl {
820
1032
  const names = enabledServers.map(([name]) => name).join(", ");
821
1033
  row(t("repl.mcp_label"), `${pc.white(String(enabledServers.length))} ${pc.dim(`(${names})`)}`);
822
1034
  }
823
- // Show the agent's workspace (the `-d` dir), not the process cwd: `bun run`
824
- // sets cwd to the package root, which is NOT where the agent operates.
1035
+ // Показываем workspace агента (каталог `-d`), а не process cwd: `bun run`
1036
+ // ставит cwd в корень пакета, где агент НЕ работает.
825
1037
  row(t("repl.work_dir"), pc.dim(this.baseDir));
826
1038
  if (this.noAgentsMd) {
827
1039
  row(t("repl.agents_label"), pc.red(t("repl.disabled")));
@@ -829,7 +1041,7 @@ export class Repl {
829
1041
  else {
830
1042
  const agentsMdCandidates = [
831
1043
  join(this.baseDir, "AGENTS.md"),
832
- join(this.baseDir, ".mma", "AGENTS.md"),
1044
+ join(projectMmaDir(this.baseDir), "AGENTS.md"),
833
1045
  join(this.configDir, "AGENTS.md"),
834
1046
  ];
835
1047
  const foundAgents = agentsMdCandidates.filter((p) => existsSync(p));
@@ -845,17 +1057,17 @@ export class Repl {
845
1057
  const meta = this.sessionManager?.getActiveMeta();
846
1058
  if (meta) {
847
1059
  const sessionPath = join(this.configDir, "sessions", meta.id);
848
- row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
1060
+ row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} ${t("repl.msgs")} ${pc.dim(sessionPath)}`);
849
1061
  }
850
- // LSP status is probed in the background — it must never block the banner.
1062
+ // Статус LSP пробнится фоном — баннер блокировать нельзя.
851
1063
  const isTty = process.stdout.isTTY === true;
852
1064
  const lspEnabled = isTty && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
853
- // Plain banner without a surrounding box — line-based, wraps to width.
1065
+ // Plain-баннер без рамки: построчно, с переносом по ширине.
854
1066
  for (const line of info) {
855
1067
  this.output.writeLine(pc.dim(line).trimEnd());
856
1068
  }
857
1069
  this.output.writeLine("");
858
- // Legacy config hint
1070
+ // Хинт про legacy-конфиг.
859
1071
  try {
860
1072
  const { existsSync: exists } = await import("fs");
861
1073
  const { join: pathJoin } = await import("path");
@@ -866,26 +1078,76 @@ export class Repl {
866
1078
  this.output.writeLine("");
867
1079
  }
868
1080
  }
869
- catch { }
870
- // Single prompt presentation for startup: the banner above was written
871
- // plainly (promptPresented was false), so no frame needs erasing here.
1081
+ catch {
1082
+ // Хинт best-effort: ошибка чтения его просто скипает.
1083
+ }
1084
+ // Единственный показ промпта на старте: баннер выше печатался plain
1085
+ // (promptPresented был false), так что стирать кадр здесь нечего.
872
1086
  this.rl.prompt();
873
1087
  this.promptPresented = true;
1088
+ try {
1089
+ if (readEnv("START_MEET") === "1" || readEnv("START_INIT") === "1") {
1090
+ const mode = readEnv("START_MEET") === "1" ? "meet" : "init";
1091
+ delete process.env[MMA_ENV.START_MEET];
1092
+ delete process.env[MMA_ENV.START_INIT];
1093
+ this.slog.logOnboarding(`${mode}_start`, { model: this.config.model });
1094
+ if (mode === "meet") {
1095
+ const { runMeet } = await import("../modules/onboarding/meet");
1096
+ await runMeet(this);
1097
+ }
1098
+ else {
1099
+ const { runInit } = await import("../modules/onboarding/init");
1100
+ await runInit(this);
1101
+ this.slog.logOnboarding("init_done", { model: this.config.model });
1102
+ }
1103
+ }
1104
+ else if (readEnv("POST_SETUP") === "1" && process.stdout.isTTY === true) {
1105
+ const { preflightFromConfig } = await import("../modules/providers/preflight");
1106
+ const pre = await preflightFromConfig(this);
1107
+ if (pre.ok) {
1108
+ this.output.writeLine(pc.dim(`${t("repl.meet_offer")} ${t("repl.meet_offer_hint")}`));
1109
+ const answer = (await new Promise((resolve) => this.rl.question("[Y/n] ", resolve)))
1110
+ .trim()
1111
+ .toLowerCase();
1112
+ const yes = answer === "" ||
1113
+ answer === "y" ||
1114
+ answer === "yes" ||
1115
+ answer === "да" ||
1116
+ answer === "д";
1117
+ if (yes) {
1118
+ this.slog.logOnboarding("meet_start", { model: this.config.model });
1119
+ const { runMeet } = await import("../modules/onboarding/meet");
1120
+ await runMeet(this, { preflight: async () => pre });
1121
+ }
1122
+ else {
1123
+ this.restoreStartupPrompt();
1124
+ }
1125
+ }
1126
+ else {
1127
+ this.output.writeLine(pc.yellow(t("onboarding.provider_down", { cmd: "meet" })));
1128
+ this.output.writeLine(t("onboarding.provider_down_hint", { cmd: "meet" }));
1129
+ }
1130
+ }
1131
+ }
1132
+ catch (err) {
1133
+ this.logger?.debug(`Onboarding startup skipped: ${String(err)}`);
1134
+ this.restoreStartupPrompt();
1135
+ }
874
1136
  if (lspEnabled) {
875
1137
  this.probeLspBanner()
876
1138
  .then((lspSummary) => {
877
1139
  if (lspSummary) {
878
1140
  const line = `${pc.green(t("repl.agent"))}${pc.yellow(t("repl.lsp_label"))} ${lspSummary}`;
879
- // Channel owns the prompt row now: no manual erase + prompt() hack.
1141
+ // Строкой промпта владеет канал: ручной erase + prompt()-хак не нужен.
880
1142
  this.output.writeLine(`${divider()}\n${line}`);
881
1143
  }
882
1144
  })
883
1145
  .catch(() => { });
884
1146
  }
885
1147
  if ((this.config.provider.entries?.length ?? 0) > 1) {
886
- // Phase 4.4: background health probe of all entries (free listModels
887
- // calls). Failed providers are surfaced as warnings — failover (Phase
888
- // 4.2) would silently mask a dead primary.
1148
+ // Phase 4.4: фоновый health-пробник всех entries (бесплатные listModels).
1149
+ // Упавшие провайдеры — предупреждениями: фейловер (Phase 4.2) молча
1150
+ // замаскировал бы мёртвый primary.
889
1151
  void probeProviders(targetsFromProviders((this.config.provider.entries ?? [])
890
1152
  .map((e) => ({
891
1153
  name: e.label || e.type,
@@ -901,26 +1163,22 @@ export class Repl {
901
1163
  })
902
1164
  .catch(() => { });
903
1165
  }
904
- // Context probe (background): compare the context length the model is
905
- // actually loaded with against the configured window. Same non-blocking
906
- // banner pattern as the LSP probe above.
1166
+ // Контекст-пробник (фоном): сверяем контекст, с которым модель реально
1167
+ // загружена, против настроенного окна. Тот же неблокирующий баннер-паттерн,
1168
+ // что у LSP-пробника выше.
907
1169
  if (this.contextProbe) {
908
1170
  void this.contextProbe
909
1171
  .then((probe) => {
910
1172
  if (!probe)
911
1173
  return;
912
- this.sessionManager?.appendLog({
913
- ts: new Date().toISOString(),
914
- type: "context_probe",
915
- content: `model=${probe.model} actual=${probe.actual} configured=${this.config.contextWindow}`,
916
- });
1174
+ // Запись пробника персистит bootstrap (все режимы); здесь только строка баннера.
917
1175
  const line = probe.actual < this.config.contextWindow
918
1176
  ? `${pc.yellow("⚠")} ${t("repl.context_probe_small", { model: probe.model, actual: probe.actual, configured: this.config.contextWindow })}`
919
1177
  : probe.actual > this.config.contextWindow
920
1178
  ? `${pc.dim(t("repl.context_probe_big", { model: probe.model, actual: probe.actual }))}`
921
1179
  : null;
922
1180
  if (line) {
923
- // Channel owns the prompt row now: no manual erase + prompt() hack.
1181
+ // Строкой промпта владеет канал: ручной erase + prompt()-хак не нужен.
924
1182
  this.output.writeLine(`${divider()}\n${line}`);
925
1183
  }
926
1184
  })
@@ -928,16 +1186,16 @@ export class Repl {
928
1186
  }
929
1187
  }
930
1188
  /**
931
- * Probe the LSP servers applicable to the current project and format a
932
- * one-line banner summary, e.g. "✓ typescript (1.2s)" or "✗ css — timeout".
933
- * Returns null when LSP is disabled or no server applies.
1189
+ * Пробнит LSP-серверы, применимые к текущему проекту, и форматирует однострочную
1190
+ * сводку баннера, напр. "✓ typescript (1.2s)" или "✗ css — timeout".
1191
+ * Null, если LSP выключен или серверов нет.
934
1192
  */
935
1193
  async probeLspBanner() {
936
1194
  const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
937
1195
  try {
938
1196
  const lspOnlyLogger = this.logger ? new FileOnlyLogger(this.logger) : undefined;
939
1197
  const summary = await probeLspServers(config, this.baseDir, { logger: lspOnlyLogger });
940
- // Log LSP probe results to session for post-mortem visibility
1198
+ // Результаты LSP-пробника — в сессию, для пост-мортем видимости.
941
1199
  this.slog.logSessionStart({
942
1200
  environment: this.envReport,
943
1201
  lspProbe: {
@@ -966,13 +1224,14 @@ export class Repl {
966
1224
  return pc.yellow(t("repl.lsp_unknown"));
967
1225
  }
968
1226
  }
1227
+ /** Останов REPL: сейв истории, шатдаун агента, закрытие лога сессии. */
969
1228
  stop() {
970
1229
  this.running = false;
971
1230
  this.saveHistory();
972
1231
  this.agent.shutdown();
973
1232
  this.sessionOutput?.dispose();
974
1233
  this.sessionOutput = undefined;
975
- // Close session log when REPL stops
1234
+ // Закрываем лог сессии на останове REPL.
976
1235
  this.logger?.closeSessionLog();
977
1236
  this.rl.close();
978
1237
  }