micro-models-agent 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -1,19 +1,7 @@
1
+ // Корень LSP-workspace по маркерам и поиск локального tsc вверх по node_modules.
1
2
  import { existsSync } from "fs";
2
3
  import { dirname, join, relative, isAbsolute } from "path";
3
4
  import { platform } from "os";
4
- /**
5
- * Resolve the LSP workspace root for an edited file.
6
- *
7
- * The workspace root must be the nearest project of the *edited file*, not the
8
- * agent baseDir — tsserver resolves `typescript` relative to the workspace
9
- * root, so for a file in `bicycle-shop/src/x.ts` under baseDir `mma_tests` the
10
- * root must be `bicycle-shop` (observed: "Could not find a valid TypeScript
11
- * installation" on every write/edit because baseDir had no node_modules).
12
- *
13
- * Which files mark a project root is language-specific and is declared in the
14
- * LSP server config (`LspServerConfig.workspaceMarkers`) — this function takes
15
- * the markers as a parameter and performs no language detection itself.
16
- */
17
5
  /**
18
6
  * Находит локальный компилятор TypeScript, поднимаясь от `startDir` вверх по
19
7
  * `node_modules`. Возвращает готовую команду или null, если tsc не установлен.
@@ -47,16 +35,25 @@ export function resolveTscCommand(startDir, maxLevels = 8) {
47
35
  }
48
36
  return null;
49
37
  }
38
+ /**
39
+ * Корень LSP-workspace для редактируемого файла.
40
+ * Корень — ближайший проект *редактируемого файла*, не baseDir агента:
41
+ * tsserver резолвит `typescript` от корня workspace (наблюдалось: "Could not
42
+ * find a valid TypeScript installation" на каждый write/edit, когда у baseDir
43
+ * не было node_modules). Какие файлы маркируют корень — языко-специфично
44
+ * (`LspServerConfig.workspaceMarkers`); функция маркеры принимает параметром
45
+ * и детектом языка не занимается.
46
+ */
50
47
  export function findProjectRoot(filePath, baseDir, markers) {
51
48
  if (!markers || markers.length === 0)
52
49
  return baseDir;
53
50
  let dir = dirname(filePath);
54
51
  const root = baseDir.replace(/[\\/]+$/, "");
55
- // Clamp the walk to baseDir: callers that pass baseDir itself as filePath
56
- // (probes) start at dirname(baseDir) — ABOVE the sandbox — and would pick up
57
- // a marker directory from the parent (e.g. a package.json next to it).
52
+ // Обход клампим к baseDir: вызывающие с самим baseDir как filePath (пробы)
53
+ // стартуют от dirname(baseDir) — ВЫШЕ песочницы — и подобрали бы маркер
54
+ // родителя (напр. package.json рядом).
58
55
  const rel = relative(root, dir);
59
- if ((rel && rel.startsWith("..")) || isAbsolute(rel))
56
+ if ((rel?.startsWith("..")) || isAbsolute(rel))
60
57
  dir = root;
61
58
  while (true) {
62
59
  if (markers.some((m) => existsSync(join(dir, m)))) {
@@ -1,3 +1,4 @@
1
+ // Стартовый health-check проекта: предсуществующие ошибки в промпт до начала работы.
1
2
  import { existsSync } from "fs";
2
3
  import { join } from "path";
3
4
  import { spawn } from "child_process";
@@ -8,27 +9,27 @@ import { getServerForFile } from "./config";
8
9
  import { findProjectRoot, resolveTscCommand } from "./project-root";
9
10
  import { estimateTokens } from "../../llm/token-counter";
10
11
  import { errMsg } from "../../utils";
11
- /** Startup check never examines more than this many files via LSP. */
12
+ import { LSP_STARTUP_CHECK_TIMEOUT_MS as STARTUP_CHECK_TIMEOUT_MS } from "../../config/timeouts";
13
+ /** Стартовый чек смотрит не больше этого числа файлов через LSP. */
12
14
  const STARTUP_CHECK_FILE_CAP = 5;
13
- /** Overall cap for the whole startup check — never block session start longer. */
14
- const STARTUP_CHECK_TIMEOUT_MS = 60_000;
15
- /** Cap of tsc error lines surfaced in the prompt block. */
15
+ /** Кап строк ошибок tsc в промпт-блоке. */
16
16
  const STARTUP_CHECK_ERROR_CAP = 20;
17
17
  /**
18
- * Scan the project at session start for errors that already exist, so the
19
- * model knows the baseline BEFORE it starts working. Addresses interrupted
20
- * sessions: code left half-broken by a previous run is surfaced up front
21
- * instead of being discovered (or missed) reactively.
18
+ * Сканирует проект на старте сессии на уже существующие ошибки, чтобы модель
19
+ * знала baseline ДО начала работы. Закрывает прерванные сессии: недоломанный
20
+ * прошлым запуском код всплывает сразу, а не находится (или пропускается)
21
+ * реактивно.
22
22
  *
23
- * Strategy:
24
- * - If the project has a tsconfig.json → one `tsc --noEmit --skipLibCheck`
25
- * invocation (single process, covers the whole project).
26
- * - Otherwise → bounded LSP diagnostics on up to STARTUP_CHECK_FILE_CAP files
27
- * (handles CSS/JSON/plain-JS projects with no TS).
23
+ * Стратегия:
24
+ * - есть tsconfig.json → один `tsc --noEmit --skipLibCheck` (один процесс на
25
+ * весь проект);
26
+ * - иначе → ограниченные LSP-диагностики до STARTUP_CHECK_FILE_CAP файлов
27
+ * (CSS/JSON/чистый-JS проекты без TS).
28
28
  *
29
- * Always fail-open: on any error/timeout/clean result → returns null, so the
30
- * check can never slow down or break session startup.
29
+ * Всегда fail-open: любая ошибка/таймаут/чистый результат → null, чек не
30
+ * может замедлить или сломать старт сессии.
31
31
  */
32
+ /** Запустить стартовый health-check; нечего показать — null. */
32
33
  export async function runStartupHealthCheck(config, baseDir, deps = {}) {
33
34
  if (!config.enabled)
34
35
  return null;
@@ -81,7 +82,7 @@ async function runCheck(config, baseDir, deps) {
81
82
  }
82
83
  }
83
84
  catch (e) {
84
- // Per-file LSP failure → log and skip (fail-open).
85
+ // Упавший LSP на файле → лог и пропуск (fail-open).
85
86
  deps.logger?.debug(`Startup LSP check failed for ${file}: ${errMsg(e)}`);
86
87
  }
87
88
  }
@@ -124,6 +125,7 @@ async function runTscDefault(projectRoot, timeoutMs) {
124
125
  });
125
126
  });
126
127
  }
128
+ /** Распарсить "error TS" строки из вывода tsc (дедуп). */
127
129
  export function parseTscErrors(output) {
128
130
  const lines = [];
129
131
  for (const raw of output.split("\n")) {
@@ -1,27 +1,32 @@
1
+ // MCP-клиент: JSON-RPC поверх stdio/sse/http, listTools/callTool, таймауты.
1
2
  import { spawn } from "child_process";
2
3
  import { defaultOutputBus } from "../../output";
3
- /** Default timeout for MCP HTTP requests (connect + tool calls). */
4
- const HTTP_TIMEOUT_MS = 10_000;
4
+ import { MCP_HTTP_TIMEOUT_MS as HTTP_TIMEOUT_MS } from "../../config/timeouts";
5
5
  /**
6
- * Accept header required by the MCP Streamable HTTP transport — the server
7
- * responds with 406 if the client doesn't accept both media types.
6
+ * Accept-заголовок для MCP Streamable HTTP: сервер отвечает 406, если клиент
7
+ * не принимает оба media type.
8
8
  */
9
9
  const MCP_ACCEPT_HEADER = "application/json, text/event-stream";
10
+ /** MCP-клиент: коннект по транспорту, listTools, callTool. */
10
11
  export class MCPClient {
11
12
  serverName;
12
13
  config;
13
14
  process = null;
14
15
  _connected = false;
15
16
  abortController = null;
16
- pendingResolve = null;
17
- pendingReject = null;
17
+ /** In-flight SSE-вызовы по JSON-RPC id (один слот мешал конкурентные
18
+ * вызовы и матчил ответы по любому id — теперь слот на вызов). */
19
+ ssePending = new Map();
20
+ sseSeq = 0;
18
21
  constructor(config) {
19
22
  this.config = config;
20
23
  this.serverName = config.name;
21
24
  }
25
+ /** Подключён ли клиент. */
22
26
  isConnected() {
23
27
  return this._connected;
24
28
  }
29
+ /** Подключиться по транспорту из конфига (http/sse/stdio). */
25
30
  async connect() {
26
31
  if (this._connected)
27
32
  return;
@@ -56,7 +61,7 @@ export class MCPClient {
56
61
  this._connected = true;
57
62
  const reader = response.body.getReader();
58
63
  const decoder = new TextDecoder();
59
- let buffer = "";
64
+ const buffer = "";
60
65
  this.readSSE(reader, decoder, buffer);
61
66
  }
62
67
  catch (err) {
@@ -102,31 +107,30 @@ export class MCPClient {
102
107
  handleSSEMessage(data) {
103
108
  try {
104
109
  const msg = JSON.parse(data);
105
- if (this.pendingResolve && msg.id) {
106
- this.clearSseTimer();
107
- if (msg.error) {
108
- this.pendingReject?.(new Error(msg.error.message));
110
+ if (msg.id !== undefined) {
111
+ const pending = this.ssePending.get(msg.id);
112
+ if (pending) {
113
+ this.ssePending.delete(msg.id);
114
+ clearTimeout(pending.timer);
115
+ if (msg.error)
116
+ pending.reject(new Error(msg.error.message));
117
+ else
118
+ pending.resolve(msg.result);
109
119
  }
110
- else {
111
- this.pendingResolve(msg.result);
112
- }
113
- this.pendingResolve = null;
114
- this.pendingReject = null;
115
120
  }
116
121
  }
117
122
  catch (e) {
118
- // Heartbeats/comments are not JSON — expected noise, but still logged
119
- // so a genuinely broken stream is visible in diagnostics.
123
+ // Хартбиты/комменты — не JSON: ожидаемый шум, но логируем, чтобы
124
+ // реально битый стрим был виден в диагностике.
120
125
  defaultOutputBus.log("error", "mcp", `[MCPClient] Unparseable SSE message: ${String(data).slice(0, 120)} ${e instanceof Error ? e.message : String(e)}`, { persist: true });
121
126
  }
122
127
  }
123
128
  handleSSEError(err) {
124
- if (this.pendingReject) {
125
- this.clearSseTimer();
126
- this.pendingReject(err);
127
- this.pendingResolve = null;
128
- this.pendingReject = null;
129
+ for (const [, pending] of this.ssePending) {
130
+ clearTimeout(pending.timer);
131
+ pending.reject(err);
129
132
  }
133
+ this.ssePending.clear();
130
134
  }
131
135
  connectStdio() {
132
136
  return new Promise((resolve, reject) => {
@@ -152,15 +156,15 @@ export class MCPClient {
152
156
  child.on("spawn", () => {
153
157
  this._connected = true;
154
158
  this.process = child;
155
- // MCP handshake: real servers require `initialize` before any
156
- // tools/list or tools/call request.
159
+ // MCP-handshake: настоящие серверы требуют `initialize` перед
160
+ // tools/list и tools/call.
157
161
  this.stdioRequest("initialize", {
158
162
  protocolVersion: "2024-11-05",
159
163
  capabilities: {},
160
164
  clientInfo: { name: "mma", version: "1.0.0" },
161
165
  }, 10000)
162
166
  .then(() => {
163
- child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
167
+ child.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}\n`);
164
168
  })
165
169
  .then(() => {
166
170
  _resolved = true;
@@ -176,16 +180,18 @@ export class MCPClient {
176
180
  });
177
181
  });
178
182
  }
179
- /** Unparsed trailing bytes from stdio responses (partial line). */
183
+ /** Неразобранный хвост stdio-ответов (частичная строка). */
180
184
  stdioBuffer = "";
185
+ /** Монотонный id stdio-запросов (Date.now() коллизил на вызовах в одну мс). */
186
+ stdioRequestSeq = 0;
181
187
  /**
182
- * JSON-RPC request over stdio with timeout, exit handling and
183
- * buffer-aware line parsing (notifications and stale responses between
184
- * our request and its response no longer break matching).
188
+ * JSON-RPC запрос поверх stdio: таймаут, обработка exit и построчный парсинг
189
+ * с учётом буфера (нотификации и чужие ответы между запросом и ответом
190
+ * матчинг больше не ломают).
185
191
  */
186
192
  stdioRequest(method, params, timeoutMs) {
187
193
  const proc = this.process;
188
- if (!proc || !proc.stdout || !proc.stdin) {
194
+ if (!proc?.stdout || !proc.stdin) {
189
195
  return Promise.reject(new Error("Not connected to MCP server"));
190
196
  }
191
197
  const stdout = proc.stdout;
@@ -193,7 +199,7 @@ export class MCPClient {
193
199
  return new Promise((resolve, reject) => {
194
200
  const request = {
195
201
  jsonrpc: "2.0",
196
- id: Date.now(),
202
+ id: ++this.stdioRequestSeq,
197
203
  method,
198
204
  ...(params !== undefined ? { params } : {}),
199
205
  };
@@ -225,7 +231,7 @@ export class MCPClient {
225
231
  continue;
226
232
  }
227
233
  if (msg.id !== request.id)
228
- continue; // notification or foreign response
234
+ continue; // нотификация или чужой ответ
229
235
  finish(() => {
230
236
  if (msg.error) {
231
237
  reject(new Error(msg.error.message));
@@ -245,9 +251,10 @@ export class MCPClient {
245
251
  timer = setTimeout(() => {
246
252
  finish(() => reject(new Error(`MCP ${method} timeout`)));
247
253
  }, timeout);
248
- proc.stdin.write(JSON.stringify(request) + "\n");
254
+ proc.stdin.write(`${JSON.stringify(request)}\n`);
249
255
  });
250
256
  }
257
+ /** Список тулов сервера (автоконнект; транспорт выбирается из конфига). */
251
258
  async listTools() {
252
259
  if (!this._connected) {
253
260
  await this.connect();
@@ -319,6 +326,7 @@ export class MCPClient {
319
326
  const result = await this.stdioRequest("tools/list");
320
327
  return result?.tools || [];
321
328
  }
329
+ /** Отключиться: http — флаг, sse — abort, stdio — kill процесса. */
322
330
  async disconnect() {
323
331
  if (this.config.transport === "http") {
324
332
  this._connected = false;
@@ -334,6 +342,7 @@ export class MCPClient {
334
342
  }
335
343
  this._connected = false;
336
344
  }
345
+ /** Вызвать тул (без коннекта — ошибка; транспорт из конфига). */
337
346
  async callTool(toolName, args) {
338
347
  if (!this._connected) {
339
348
  throw new Error("Not connected to MCP server");
@@ -376,12 +385,11 @@ export class MCPClient {
376
385
  }
377
386
  return result.result || result;
378
387
  }
379
- /** Timer of the in-flight SSE request — cleared when the reply arrives via the stream. */
380
- sseTimer;
381
388
  async callToolSSE(toolName, args) {
389
+ const id = ++this.sseSeq;
382
390
  const request = {
383
391
  jsonrpc: "2.0",
384
- id: Date.now(),
392
+ id,
385
393
  method: "tools/call",
386
394
  params: {
387
395
  name: toolName,
@@ -389,14 +397,17 @@ export class MCPClient {
389
397
  },
390
398
  };
391
399
  return new Promise((resolve, reject) => {
392
- this.pendingResolve = resolve;
393
- this.pendingReject = reject;
400
+ const settle = (fn) => {
401
+ const pending = this.ssePending.get(id);
402
+ if (!pending)
403
+ return;
404
+ this.ssePending.delete(id);
405
+ clearTimeout(pending.timer);
406
+ fn();
407
+ };
394
408
  const timeout = this.config.timeout || 30000;
395
- this.sseTimer = setTimeout(() => {
396
- this.pendingResolve = null;
397
- this.pendingReject = null;
398
- reject(new Error("MCP call timeout"));
399
- }, timeout);
409
+ const timer = setTimeout(() => settle(() => reject(new Error("MCP call timeout"))), timeout);
410
+ this.ssePending.set(id, { resolve, reject, timer });
400
411
  fetch(this.config.url, {
401
412
  method: "POST",
402
413
  headers: {
@@ -408,34 +419,20 @@ export class MCPClient {
408
419
  signal: this.abortController?.signal,
409
420
  })
410
421
  .then(async (response) => {
411
- this.clearSseTimer();
412
422
  if (!response.ok) {
413
423
  const text = await response.text().catch(() => "");
414
- reject(new Error(`MCP POST failed: ${response.status} ${text}`));
424
+ settle(() => reject(new Error(`MCP POST failed: ${response.status} ${text}`)));
415
425
  return;
416
426
  }
417
427
  const result = await response.json();
418
- if (result.error) {
419
- reject(new Error(result.error.message));
420
- }
421
- else {
422
- resolve(result.result || result);
423
- }
428
+ settle(() => result.error ? reject(new Error(result.error.message)) : resolve(result.result || result));
424
429
  })
425
430
  .catch((err) => {
426
- this.clearSseTimer();
427
- if (err.name !== "AbortError") {
428
- reject(err);
429
- }
431
+ if (err.name !== "AbortError")
432
+ settle(() => reject(err));
430
433
  });
431
434
  });
432
435
  }
433
- clearSseTimer() {
434
- if (this.sseTimer !== undefined) {
435
- clearTimeout(this.sseTimer);
436
- this.sseTimer = undefined;
437
- }
438
- }
439
436
  callToolStdio(toolName, args) {
440
437
  if (!this.process) {
441
438
  throw new Error("Not connected to MCP server");
@@ -1,3 +1,4 @@
1
+ // Публичный фасад модуля MCP: клиент, реестр, модуль.
1
2
  export { MCPClient } from "./client";
2
3
  export { MCPRegistry } from "./registry";
3
4
  export { MCPModule } from "./module";
@@ -1,12 +1,15 @@
1
1
  import { MCPClient } from "./client";
2
2
  import { MCPRegistry } from "./registry";
3
3
  import { Logger } from "../../logger/app-logger";
4
+ import { MCP_DISCOVERY_TIMEOUT_MS as DISCOVERY_TIMEOUT_MS } from "../../config/timeouts";
4
5
  const logger = new Logger("warn", "mcp");
6
+ /** Безопасное имя тула: mcp__server__tool. */
5
7
  function sanitizeToolName(server, tool) {
6
8
  const safeServer = server.replace(/[^a-zA-Z0-9_-]/g, "_");
7
9
  const safeTool = tool.replace(/[^a-zA-Z0-9_-]/g, "_");
8
10
  return `mcp__${safeServer}__${safeTool}`;
9
11
  }
12
+ /** Привести input-схему MCP к форме дефиниций тулов. */
10
13
  function convertInputSchema(schema) {
11
14
  if (schema && typeof schema === "object" && "properties" in schema) {
12
15
  const required = Array.isArray(schema.required) ? schema.required : [];
@@ -18,26 +21,23 @@ function convertInputSchema(schema) {
18
21
  }
19
22
  return { type: "object", properties: {} };
20
23
  }
21
- /** Timeout for the initial tool discovery call (avoids hanging startup). */
22
- const DISCOVERY_TIMEOUT_MS = 5_000;
24
+ /** Модуль MCP: конфиги при init, коннект лениво, тул-дефиниции из найденного. */
23
25
  export class MCPModule {
24
26
  config;
25
27
  name = "mcp";
26
28
  connections = new Map();
27
- /** Server configs registered at init, not yet connected. */
29
+ /** Конфиги серверов с init, ещё без коннекта. */
28
30
  serverConfigs = new Map();
29
31
  discovered = [];
30
32
  initialized = false;
31
- initError = null;
32
- /** Servers that failed initial discovery — will not be retried. */
33
+ /** Серверы с упавшим первичным discovery — не ретраятся. */
33
34
  failedServers = new Set();
34
35
  constructor(config) {
35
36
  this.config = config;
36
37
  }
37
38
  /**
38
- * Register MCP server configs WITHOUT connecting. Actual connection and tool
39
- * discovery happens lazily on first tool call — keeps startup fast and
40
- * offline-safe.
39
+ * Зарегистрировать конфиги MCP-серверов БЕЗ коннекта. Коннект и discovery
40
+ * тулов — лениво, на первом вызове: старт быстрый и офлайн-безопасный.
41
41
  */
42
42
  async initialize() {
43
43
  if (this.initialized)
@@ -61,30 +61,36 @@ export class MCPModule {
61
61
  };
62
62
  registry.register(serverConfig);
63
63
  }
64
- // Store configs for lazy connection — no network calls here.
64
+ // Конфиги — для ленивого коннекта, сетевых вызовов тут нет.
65
65
  for (const serverName of registry.list()) {
66
66
  this.serverConfigs.set(serverName, registry.get(serverName));
67
67
  }
68
- // Attempt fast discovery in background (best-effort, timeout-bounded).
69
- // If it fails, tools are still registered — they'll connect on first call.
70
- this.discoverAllTools();
68
+ // Discovery ДО снапшота тул-дефиниций вызывающим. Раньше был
69
+ // fire-and-forget, и `collectToolDefinitions()` при bootstrap отрабатывал
70
+ // раньше, чем серверы успевали подключиться, — настоящие MCP-тулы не
71
+ // регистрировались. Discovery ограничен (таймаут на сервер, серверы
72
+ // параллельно) и best-effort: падения оставляют ленивый плейсхолдер "connect".
73
+ await this.discoverAllTools();
71
74
  }
72
75
  /**
73
- * Try to connect and discover tools for all registered servers.
74
- * Runs in background, never blocks the caller. Failures are silent —
75
- * tools still get registered and will retry connection on first call.
76
+ * Подключиться и найти тулы всех зарегистрированных серверов параллельно,
77
+ * каждый ограничен DISCOVERY_TIMEOUT_MS. Отменённая по таймауту попытка:
78
+ * её позднее завершение не должно регистрировать сервер, уже помеченный упавшим.
76
79
  */
77
80
  async discoverAllTools() {
78
- for (const [serverName, serverConfig] of this.serverConfigs) {
81
+ await Promise.all([...this.serverConfigs].map(async ([serverName, serverConfig]) => {
79
82
  if (this.failedServers.has(serverName))
80
- continue;
83
+ return;
81
84
  const client = new MCPClient(serverConfig);
85
+ let timedOut = false;
86
+ let timer;
82
87
  try {
83
- // Race discovery against timeout — never let a dead server block startup.
84
88
  await Promise.race([
85
89
  (async () => {
86
90
  await client.connect();
87
91
  const tools = await client.listTools();
92
+ if (timedOut)
93
+ return; // проиграл гонку — состояние не мутировать
88
94
  this.connections.set(serverName, client);
89
95
  for (const tool of tools) {
90
96
  this.discovered.push({
@@ -95,28 +101,35 @@ export class MCPModule {
95
101
  });
96
102
  }
97
103
  })(),
98
- new Promise((_, reject) => setTimeout(() => reject(new Error("discovery timeout")), DISCOVERY_TIMEOUT_MS)),
104
+ new Promise((_, reject) => {
105
+ timer = setTimeout(() => reject(new Error("discovery timeout")), DISCOVERY_TIMEOUT_MS);
106
+ }),
99
107
  ]);
100
108
  }
101
109
  catch (e) {
110
+ timedOut = true;
102
111
  this.failedServers.add(serverName);
103
- this.initError = `MCP server "${serverName}" init failed: ${e.message}`;
112
+ logger.warn(`MCP server "${serverName}" init failed: ${e.message}`);
104
113
  await client.disconnect().catch((err) => {
105
114
  logger.warn(`cleanup disconnect for "${serverName}" failed`, { error: String(err) });
106
115
  });
107
116
  }
108
- }
117
+ finally {
118
+ if (timer)
119
+ clearTimeout(timer);
120
+ }
121
+ }));
109
122
  }
110
123
  /**
111
- * Ensure a specific server is connected and its tools are discovered.
112
- * Called lazily on first tool invocation for that server.
124
+ * Гарантировать коннект конкретного сервера и discovery его тулов.
125
+ * Лениво вызывается на первом обращении к тулу сервера.
113
126
  */
114
127
  async ensureConnected(serverName) {
115
- // Already connected?
128
+ // Уже подключён?
116
129
  const existing = this.connections.get(serverName);
117
130
  if (existing?.isConnected())
118
131
  return existing;
119
- // Failed during init — don't retry.
132
+ // Упал при init — не ретраить.
120
133
  if (this.failedServers.has(serverName))
121
134
  return null;
122
135
  const serverConfig = this.serverConfigs.get(serverName);
@@ -127,7 +140,7 @@ export class MCPModule {
127
140
  await client.connect();
128
141
  const tools = await client.listTools();
129
142
  this.connections.set(serverName, client);
130
- // Add newly discovered tools (if not already present).
143
+ // Добавить новонайденные тулы (если ещё нет).
131
144
  for (const tool of tools) {
132
145
  const alreadyDiscovered = this.discovered.some((d) => d.serverName === serverName && d.toolName === tool.name);
133
146
  if (!alreadyDiscovered) {
@@ -169,7 +182,7 @@ export class MCPModule {
169
182
  }
170
183
  }
171
184
  else {
172
- lines.push(` (not yet connected — use mcp__${serverName}__connect to discover tools)`);
185
+ lines.push(` (not yet connected — use ${sanitizeToolName(serverName, "connect")} to discover tools)`);
173
186
  }
174
187
  }
175
188
  lines.push("---");
@@ -181,13 +194,30 @@ export class MCPModule {
181
194
  };
182
195
  }
183
196
  getToolDefinitions() {
184
- // Build definitions from discovered tools AND from registered-but-not-yet-discovered
185
- // servers (so the LLM sees all configured MCP tools even if discovery failed at startup).
197
+ // Дефиниции из найденных тулов И из зарегистрированных, но ещё не
198
+ // найденных серверов (модель видит все настроенные MCP-тулы, даже если
199
+ // discovery при старте упал).
186
200
  const defs = [];
187
- // Tools already discovered (connected at startup).
201
+ // Санитизация может схлопнуть разные имена в один id (`a.b`/`a-b`,
202
+ // `x.y`/`x-y`); реестр на дубле бросал, что роняло bootstrap.
203
+ // Разруливаем числовым суффиксом до регистрации.
204
+ const usedNames = new Set();
205
+ const uniqueName = (base) => {
206
+ if (!usedNames.has(base)) {
207
+ usedNames.add(base);
208
+ return base;
209
+ }
210
+ let i = 2;
211
+ while (usedNames.has(`${base}_${i}`))
212
+ i++;
213
+ const name = `${base}_${i}`;
214
+ usedNames.add(name);
215
+ return name;
216
+ };
217
+ // Уже найденные тулы (подключены при старте).
188
218
  for (const d of this.discovered) {
189
219
  defs.push({
190
- name: sanitizeToolName(d.serverName, d.toolName),
220
+ name: uniqueName(sanitizeToolName(d.serverName, d.toolName)),
191
221
  description: `${d.description} [MCP server: ${d.serverName}]`,
192
222
  parameters: convertInputSchema(d.inputSchema),
193
223
  tags: ["code", "research"],
@@ -212,17 +242,16 @@ export class MCPModule {
212
242
  },
213
243
  });
214
244
  }
215
- // Servers that haven't been discovered yet — register placeholder tools
216
- // that will connect lazily on first call.
245
+ // Ещё не найденные серверы — плейсхолдеры, коннект лениво на первом вызове.
217
246
  for (const [serverName] of this.serverConfigs) {
218
247
  if (this.failedServers.has(serverName))
219
248
  continue;
220
249
  const alreadyDiscovered = this.discovered.some((d) => d.serverName === serverName);
221
250
  if (alreadyDiscovered)
222
251
  continue;
223
- // Register a single "connect" tool so the LLM can trigger discovery.
252
+ // Один тул "connect", чтобы модель могла триггернуть discovery.
224
253
  defs.push({
225
- name: sanitizeToolName(serverName, "connect"),
254
+ name: uniqueName(sanitizeToolName(serverName, "connect")),
226
255
  description: `Connect to MCP server "${serverName}" and discover its tools`,
227
256
  parameters: { type: "object", properties: {} },
228
257
  tags: ["code", "research"],
@@ -255,8 +284,8 @@ export class MCPModule {
255
284
  await client.disconnect();
256
285
  }
257
286
  catch (e) {
258
- // Best-effort cleanup at session end — a failed disconnect must
259
- // not crash the shutdown path, but must stay diagnosable.
287
+ // Best-effort cleanup на конце сессии: упавший disconnect не
288
+ // должен ронять shutdown, но обязан остаться в диагностике.
260
289
  logger.warn(`disconnect from "${serverName}" failed`, { error: String(e?.message ?? e) });
261
290
  }
262
291
  }
@@ -1,14 +1,19 @@
1
+ /** Реестр конфигов MCP-серверов по именам. */
1
2
  export class MCPRegistry {
2
3
  servers = new Map();
4
+ /** Зарегистрировать конфиг сервера. */
3
5
  register(config) {
4
6
  this.servers.set(config.name, config);
5
7
  }
8
+ /** Конфиг по имени (undefined — нет). */
6
9
  get(name) {
7
10
  return this.servers.get(name);
8
11
  }
12
+ /** Имена серверов по алфавиту. */
9
13
  list() {
10
14
  return Array.from(this.servers.keys()).sort();
11
15
  }
16
+ /** Удалить сервер. */
12
17
  remove(name) {
13
18
  this.servers.delete(name);
14
19
  }
@@ -1 +1,2 @@
1
+ // Публичная поверхность модуля памяти: хранилище категорий и преференсов.
1
2
  export { MemoryStore } from "./store";