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,25 +1,44 @@
1
1
  import { killByCallId } from "../modules/processes/runner";
2
2
  import { t } from "../i18n/index";
3
- const TOOL_EXECUTION_TIMEOUT_MS = 60000;
3
+ import { TOOL_EXECUTION_TIMEOUT_MS, TOOL_MEMO_TTL_MS as MEMO_TTL_MS, } from "../config/timeouts";
4
+ const MEMO_MAX_ENTRIES = 100;
5
+ /** Стабильная сериализация: сортировка ключей, порядок аргументов на ключ не влияет. */
6
+ function stableArgsKey(value) {
7
+ if (Array.isArray(value))
8
+ return value.map(stableArgsKey);
9
+ if (value && typeof value === "object") {
10
+ const entries = Object.keys(value)
11
+ .sort()
12
+ .map((k) => [k, stableArgsKey(value[k])]);
13
+ return Object.fromEntries(entries);
14
+ }
15
+ return value;
16
+ }
17
+ /** Исполнитель: реестр, контекст, плагины, сессионное мемо. */
4
18
  export class ToolExecutor {
5
19
  registry;
6
20
  ctx;
7
21
  pluginManager;
22
+ /**
23
+ * Сессионное мемо детерминированных read-only результатов
24
+ * (`ToolDefinition.cacheable` + ручные записи `manualMemo`-тулов вроде bash).
25
+ * Ключ: `sessionId::name::stableArgs`.
26
+ */
27
+ memo = new Map();
8
28
  constructor(registry, ctx, pluginManager) {
9
29
  this.registry = registry;
10
30
  this.ctx = ctx;
11
31
  this.pluginManager = pluginManager;
12
32
  }
13
33
  /**
14
- * Execute a tool by name with plain arguments (no call id, no timeout
15
- * wiring). Used internally — e.g. bash redirects a mistaken tool-call-as-
16
- * command into the real tool. Runs through the same registry and plugin
17
- * hooks as a normal call.
34
+ * Запуск тула по имени с plain-аргументами (без call id и таймаут-обвязки).
35
+ * Для внутреннего использования — напр. bash перенаправляет ошибочный
36
+ * tool-call-как-команду в настоящий тул. Идёт через те же реестр и хуки.
18
37
  *
19
- * **Reentrancy warning:** swaps `this.ctx` temporarily (line 30-39). Safe
20
- * for sequential callers but NOT for concurrent calls on the same executor
21
- * instance — they would race on the ctx swap. Today all callers are
22
- * sequential (bash redirect, single subagent tool); keep it that way.
38
+ * Внимание по реентрантности: временно подменяет `this.ctx`. Безопасно для
39
+ * последовательных вызовов, но НЕ для конкурентных на одном инстансе —
40
+ * будет гонка на подмене. Сейчас все вызовы последовательны (bash-редирект,
41
+ * один subagent-тул); так и держать.
23
42
  */
24
43
  async executeByName(name, args, ctx) {
25
44
  const prevCtx = this.ctx;
@@ -36,11 +55,59 @@ export class ToolExecutor {
36
55
  this.ctx = prevCtx;
37
56
  }
38
57
  }
39
- /** Whether a tool with this name is registered (used before redirects). */
58
+ /** Есть ли тул с таким именем (проверка перед редиректами). */
40
59
  hasTool(name) {
41
60
  return this.registry.has(name);
42
61
  }
62
+ memoKey(name, args) {
63
+ return `${this.ctx.sessionId ?? ""}::${name}::${JSON.stringify(stableArgsKey(args))}`;
64
+ }
65
+ /**
66
+ * Чтение мемо для `manualMemo`-тулов (bash). Возвращает откреплённую копию —
67
+ * `toolCallId` вызывающий ставит сам.
68
+ */
69
+ memoGet(name, args) {
70
+ const hit = this.memo.get(this.memoKey(name, args));
71
+ if (!hit)
72
+ return undefined;
73
+ if (Date.now() - hit.at > MEMO_TTL_MS) {
74
+ this.memo.delete(this.memoKey(name, args));
75
+ return undefined;
76
+ }
77
+ return { ...hit.result };
78
+ }
79
+ /** Запись в мемо для `manualMemo`-тулов. Хранит откреплённую копию. */
80
+ memoSet(name, args, result) {
81
+ const key = this.memoKey(name, args);
82
+ if (!this.memo.has(key) && this.memo.size >= MEMO_MAX_ENTRIES) {
83
+ const oldest = this.memo.keys().next();
84
+ if (!oldest.done)
85
+ this.memo.delete(oldest.value);
86
+ }
87
+ this.memo.set(key, { result: { ...result }, at: Date.now() });
88
+ }
89
+ /** Сброс мемо — после всего, что могло мутировать состояние. */
90
+ memoInvalidate() {
91
+ this.memo.clear();
92
+ }
93
+ /**
94
+ * Имена разрешённых тулов множеством, `null` — без ограничений.
95
+ * `undefined` — без ограничений; `[]` — запрещено всё.
96
+ */
97
+ allowedNames() {
98
+ const allow = this.ctx.toolAllowlist;
99
+ return allow ? new Set(allow) : null;
100
+ }
101
+ /** Выполнить tool call: allowlist → плагины → хендлер → post-хуки. */
43
102
  async execute(call, signal) {
103
+ const allow = this.allowedNames();
104
+ if (allow && !allow.has(call.name)) {
105
+ return {
106
+ success: false,
107
+ output: t("tool.not_allowed", { name: call.name }),
108
+ toolCallId: call.id,
109
+ };
110
+ }
44
111
  const tool = this.registry.get(call.name);
45
112
  if (!tool) {
46
113
  return {
@@ -72,23 +139,27 @@ export class ToolExecutor {
72
139
  }
73
140
  }
74
141
  let result;
142
+ // Мемоизированный повтор детерминированного чтения (cacheable): хендлер
143
+ // пропускается, но общий хвост (post-плагины, логирование) идёт как обычно,
144
+ // чтобы мемо-хит был неотличим от свежего прогона.
145
+ const memoHit = tool.cacheable ? this.memoGet(call.name, call.arguments) : undefined;
75
146
  try {
76
147
  this.ctx.activeCallId = call.id;
77
148
  if (tool.interactive) {
78
- // Interactive tools wait for user input — no timeout.
79
- result = await tool.handler(this.ctx, call.arguments);
149
+ // Интерактивные тулы ждут ввода — без таймаута.
150
+ result = memoHit ?? (await tool.handler(this.ctx, call.arguments));
80
151
  }
81
152
  else {
82
153
  const timeoutMs = tool.timeoutMs ?? TOOL_EXECUTION_TIMEOUT_MS;
83
154
  const failFast = (message) => {
84
- // Kill any long-running child process registered for this call.
155
+ // Убить долгоживущий дочерний процесс этого вызова.
85
156
  killByCallId(call.id);
86
- // Run tool-specific cleanup (e.g. subagent scope restore) before
87
- // rejecting so shared state is restored even on abandonment.
157
+ // Тул-специфичный cleanup (напр. восстановление скоупа subagent) —
158
+ // до reject, чтобы общее состояние восстановилось и при abandonment.
88
159
  const cleanup = this.ctx.cleanup;
89
160
  if (cleanup) {
90
161
  cleanup();
91
- // Clear only if no newer call has replaced it meanwhile.
162
+ // Сбрасывать, только если более новый вызов не подменил тем временем.
92
163
  if (this.ctx.cleanup === cleanup)
93
164
  this.ctx.cleanup = undefined;
94
165
  }
@@ -105,7 +176,7 @@ export class ToolExecutor {
105
176
  }, timeoutMs);
106
177
  });
107
178
  const racePromises = [
108
- tool.handler(this.ctx, call.arguments),
179
+ memoHit ? Promise.resolve(memoHit) : tool.handler(this.ctx, call.arguments),
109
180
  timeoutPromise,
110
181
  ];
111
182
  if (signal) {
@@ -124,16 +195,15 @@ export class ToolExecutor {
124
195
  result = await Promise.race(racePromises);
125
196
  }
126
197
  finally {
127
- // Stop the timer and detach the abort listener once settled so they
128
- // never fire against a DIFFERENT call's context/cleanup later.
198
+ // Остановить таймер и снять abort-слушатель, чтобы они не сработали
199
+ // против контекста/cleanup УЖЕ ДРУГОГО вызова.
129
200
  if (timer !== undefined)
130
201
  clearTimeout(timer);
131
202
  if (onAbort && signal)
132
203
  signal.removeEventListener("abort", onAbort);
133
- // Losing promises have no consumer after Promise.race settled
134
- // (their kill/cleanup effects were already applied in failFast).
135
- // Log the rejection, then detach it so it never surfaces as an
136
- // unhandled rejection.
204
+ // У проигравших промисов потребителя после settled race нет (их
205
+ // kill/cleanup-эффекты уже применены в failFast). Залогировать
206
+ // reject и отцепить, чтобы не всплыл как unhandled rejection.
137
207
  for (const p of racePromises.slice(1)) {
138
208
  p.catch((e) => {
139
209
  this.ctx.logger.debug(`Tool ${call.name}: discarded race loser: ${e.message}`);
@@ -150,10 +220,22 @@ export class ToolExecutor {
150
220
  toolCallId: call.id,
151
221
  };
152
222
  }
153
- // After an abort the tool's work is discarded (the agent loop breaks
154
- // without rendering or logging it), so skip the slow post-tool plugins
155
- // (LSP checks, lint, tsc) entirely. When the abort lands mid-plugin the
156
- // signal in the hook context lets the plugin kill its child processes.
223
+ if (!memoHit) {
224
+ // Успешные детерминированные чтения мемоизируются; всё остальное, что
225
+ // реально выполнялось, могло мутировать состояние (даже упавшая команда
226
+ // применяется наполовину) — мемо сбрасывается. `manualMemo` (bash) ведут
227
+ // свои записи сами.
228
+ if (tool.cacheable && result.success) {
229
+ this.memoSet(call.name, call.arguments, result);
230
+ }
231
+ else if (!tool.cacheable && !tool.manualMemo) {
232
+ this.memoInvalidate();
233
+ }
234
+ }
235
+ // После аборта работа тула отбрасывается (цикл агента завершается без
236
+ // рендера/логирования), поэтому медленные post-плагины (LSP, lint, tsc)
237
+ // пропускаются целиком. Аборт посреди плагина виден ему через signal
238
+ // в контексте хука — плагин убьёт свои дочерние процессы.
157
239
  if (signal?.aborted) {
158
240
  this.ctx.logger.debug(`Tool ${call.name}: abort, skipping onAfterTool plugins`);
159
241
  }
@@ -175,24 +257,47 @@ export class ToolExecutor {
175
257
  this.ctx.logger.debug(`Tool ${call.name}: ${result.success ? "OK" : "FAIL"}`);
176
258
  return result;
177
259
  }
260
+ /** Определения тулов с учётом allowlist (для схемы LLM). */
178
261
  getToolDefinitions(tags) {
262
+ const allow = this.allowedNames();
263
+ if (allow) {
264
+ return this.registry
265
+ .getAll()
266
+ .filter((t) => allow.has(t.name))
267
+ .map((t) => ({
268
+ name: t.name,
269
+ description: t.description,
270
+ parameters: t.parameters,
271
+ boundedOutput: t.boundedOutput,
272
+ }));
273
+ }
179
274
  return this.registry.getAllForLLM(tags);
180
275
  }
276
+ /** Доступ к реестру. */
181
277
  getRegistry() {
182
278
  return this.registry;
183
279
  }
280
+ /** Установить скоуп shared-исполнителя. */
184
281
  setScope(scope) {
185
282
  this.ctx.scope = scope;
186
283
  }
284
+ /** Текущий скоуп shared-исполнителя (для compare-and-restore субагентов). */
285
+ getScope() {
286
+ return this.ctx.scope;
287
+ }
288
+ /** Установить prompt-поверхность для интерактивных тулов. */
187
289
  setPromptIO(io) {
188
290
  this.ctx.promptIO = io;
189
291
  }
292
+ /** Текущая глубина рекурсии субагентов. */
190
293
  getRecursionDepth() {
191
294
  return this.ctx.recursionDepth ?? 0;
192
295
  }
296
+ /** Установить глубину рекурсии. */
193
297
  setRecursionDepth(depth) {
194
298
  this.ctx.recursionDepth = depth;
195
299
  }
300
+ /** Подменить LLM-провайдер в контексте. */
196
301
  updateProvider(provider) {
197
302
  this.ctx.llmProvider = provider;
198
303
  }
@@ -1,13 +1,16 @@
1
+ /** Тул метаданных файла/каталога (размер, даты; кешируемый). */
1
2
  import { statSync, existsSync } from "fs";
2
3
  import { t } from "../i18n/index";
3
4
  import { isPathInScope } from "../modules/security/path-validator";
4
5
  import { safeResolvePath } from "./path-utils";
6
+ /** Возвращает JSON с размером, типом, датами создания/изменения. */
5
7
  export const fileInfoTool = {
6
8
  name: "file_info",
7
9
  icon: "ℹ️",
8
10
  description: "Get metadata about a file or directory (size, creation date, modification date).",
9
11
  tags: ["file"],
10
12
  boundedOutput: true,
13
+ cacheable: true,
11
14
  parameters: {
12
15
  type: "object",
13
16
  properties: {
@@ -18,7 +21,6 @@ export const fileInfoTool = {
18
21
  handler: async (ctx, args) => {
19
22
  const path = String(args.path);
20
23
  const resolved = safeResolvePath(ctx.baseDir, path);
21
- // Check path permissions
22
24
  const scopeCheck = isPathInScope(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
23
25
  if (!scopeCheck.allowed) {
24
26
  return {
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Filter tools by a tag set, always keeping `alwaysOn` tools. Used by MoE
3
- * expert routing and subagent tool_tags so structural tools (plan/todo/verify,
4
- * project_map, lsp_check, enable_tools) survive narrowing.
2
+ * Фильтрует тулы по набору тегов, всегда оставляя `alwaysOn`. Для MoE-роутинга
3
+ * экспертов и subagent tool_tags — структурные тулы (plan/todo/verify,
4
+ * project_map, lsp_check, enable_tools) переживают сужение.
5
5
  */
6
6
  export function filterToolsByTags(tools, toolTags) {
7
7
  if (!toolTags || toolTags.length === 0)
@@ -1,11 +1,14 @@
1
+ /** Тул поиска файлов по glob-паттерну (кешируемый, jail в baseDir). */
1
2
  import { globSync } from "fs";
2
3
  import { t } from "../i18n/index";
3
4
  import { MAX_PREVIEW_LINES } from "./preview";
5
+ /** Ищет файлы по glob; выдача обрезана MAX_PREVIEW_LINES. */
4
6
  export const globTool = {
5
7
  name: "glob",
6
8
  icon: "🔎",
7
9
  description: `Search for files matching a glob pattern. Shows up to ${MAX_PREVIEW_LINES} results by default. Uses standard glob syntax (e.g., **/*.ts, src/**/*.test.ts).`,
8
10
  tags: ["file", "code", "research"],
11
+ cacheable: true,
9
12
  parameters: {
10
13
  type: "object",
11
14
  properties: {
@@ -15,7 +18,7 @@ export const globTool = {
15
18
  },
16
19
  handler: async (ctx, args) => {
17
20
  const pattern = String(args.pattern);
18
- // Security: absolute patterns (/etc/*) and ".." segments escape baseDir.
21
+ // Абсолютные паттерны (/etc/*) и ".." выводят за baseDir — запретить.
19
22
  if (/^([a-zA-Z]:)?[\\/]/.test(pattern) || pattern.split(/[\\/]/).includes("..")) {
20
23
  return {
21
24
  success: false,
@@ -1,10 +1,12 @@
1
- import { spawn } from "child_process";
1
+ /** Тул поиска по содержимому (rg с fallback на grep -r). */
2
+ import { spawn, } from "child_process";
2
3
  import { resolve } from "path";
3
4
  import { t } from "../i18n/index";
4
5
  import { logBashCommand, logSecurityBlock } from "../modules/security/audit-log";
5
6
  import { isPathInScope } from "../modules/security/path-validator";
6
7
  import { DEFAULT_SECURITY_CONFIG } from "../config/security";
7
8
  import { MAX_PREVIEW_LINES } from "./preview";
9
+ /** Обрезает вывод до MAX_PREVIEW_LINES строк. */
8
10
  function truncateLines(output) {
9
11
  const lines = output.split("\n");
10
12
  if (lines.length <= MAX_PREVIEW_LINES)
@@ -13,8 +15,8 @@ function truncateLines(output) {
13
15
  `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`);
14
16
  }
15
17
  /**
16
- * Run a search command asynchronously with kill-on-timeout.
17
- * Exit code 1 (no matches) resolves to an empty string, like execFileSync semantics.
18
+ * Поисковая команда асинхронно, с kill по таймауту. Exit code 1 (нет совпадений)
19
+ * резолвится в пустую строку. Возврат: stdout поиска.
18
20
  */
19
21
  export function runSearch(bin, args, cwd, timeoutMs = 30000) {
20
22
  return new Promise((resolvePromise, reject) => {
@@ -53,11 +55,13 @@ export function runSearch(bin, args, cwd, timeoutMs = 30000) {
53
55
  });
54
56
  });
55
57
  }
58
+ /** Ищет по содержимому через regex (rg, иначе grep -r); кешируемый. */
56
59
  export const grepTool = {
57
60
  name: "grep",
58
61
  icon: "🔍",
59
62
  description: `Search file contents using a regular expression. Shows up to ${MAX_PREVIEW_LINES} matching lines by default. Uses ripgrep (rg) if available, otherwise falls back to grep -r.`,
60
63
  tags: ["file", "code", "research"],
64
+ cacheable: true,
61
65
  parameters: {
62
66
  type: "object",
63
67
  properties: {
@@ -76,8 +80,8 @@ export const grepTool = {
76
80
  handler: async (ctx, args) => {
77
81
  const pattern = String(args.pattern);
78
82
  const searchPath = args.path ? resolve(ctx.baseDir, String(args.path)) : ctx.baseDir;
79
- // Security: the search directory must stay inside baseDir / scope /
80
- // allowed paths — `path: "../../.."` must not read outside the sandbox.
83
+ // Каталог поиска обязан оставаться внутри baseDir/скоупа: `path: "../../.."`
84
+ // не должен читать вне песочницы.
81
85
  const scopeCheck = isPathInScope(ctx.baseDir, searchPath, ctx.scope, ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths);
82
86
  if (!scopeCheck.allowed) {
83
87
  logSecurityBlock(ctx.sessionId || undefined, "file_read", scopeCheck.reason || "grep path not allowed", String(args.path ?? ctx.baseDir));
@@ -88,13 +92,12 @@ export const grepTool = {
88
92
  }),
89
93
  };
90
94
  }
91
- // Build rg arguments as an array to avoid shell interpretation of regex
92
- // metacharacters like |, (, ) — these are regex patterns, not shell operators.
95
+ // Аргументы rg массивом, чтобы шелл не трактовал метасимволы regex
96
+ // (|, (, )) как операторы — это паттерны, не команды шелла.
93
97
  const rgArgs = ["-n", "--with-filename", pattern, searchPath];
94
98
  if (args.include) {
95
99
  rgArgs.push("-g", String(args.include));
96
100
  }
97
- // Log the search (sanitized) for audit purposes
98
101
  logBashCommand(ctx.sessionId, `rg ${rgArgs.join(" ")}`, false, "grep-tool");
99
102
  try {
100
103
  const output = await runSearch("rg", rgArgs, ctx.baseDir);
@@ -113,8 +116,8 @@ export const grepTool = {
113
116
  else if (e.status === 1) {
114
117
  return { success: true, output: t("file.no_matches") };
115
118
  }
116
- // Fall back to plain grep -r when rg is not available or fails.
117
- // Using array args avoids shell injection.
119
+ // Fallback на plain grep -r, когда rg нет или упал. Массив аргументов —
120
+ // защита от shell injection.
118
121
  try {
119
122
  const grepArgs = ["-rn", pattern, searchPath];
120
123
  if (args.include) {
@@ -1,9 +1,9 @@
1
+ /** Блок системного промпта о скрытых tag-фильтром тулах. */
1
2
  import { t } from "../i18n/index";
2
3
  /**
3
- * Build a compact system-prompt block listing the tools hidden by the current
4
- * tag filter. The model must know these tools exist (and how to reach them)
5
- * even though they are absent from the LLM tool schema. Grouped by tag with
6
- * short purposes; capped to keep the block small.
4
+ * Компактный блок системного промпта со скрытыми тулами. Модель должна знать
5
+ * об их существовании (и пути доступа), хотя их нет в tool-схеме LLM.
6
+ * Группировка по тегам, размер ограничен.
7
7
  */
8
8
  export function buildHiddenToolsBlock(allTools, activeTags) {
9
9
  if (!allTools.length)
@@ -11,14 +11,14 @@ export function buildHiddenToolsBlock(allTools, activeTags) {
11
11
  const activeSet = new Set(activeTags);
12
12
  const visible = new Set();
13
13
  for (const tool of allTools) {
14
- if (tool.alwaysOn || (tool.tags && tool.tags.some((tag) => activeSet.has(tag)))) {
14
+ if (tool.alwaysOn || (tool.tags?.some((tag) => activeSet.has(tag)))) {
15
15
  visible.add(tool.name);
16
16
  }
17
17
  }
18
18
  const hidden = allTools.filter((tool) => !visible.has(tool.name));
19
19
  if (hidden.length === 0)
20
20
  return "";
21
- // Group by tag → tools, then by tool name alphabetically within a tag.
21
+ // Группировка по тегам, внутри тега — по имени тула.
22
22
  const byTag = new Map();
23
23
  for (const tool of hidden) {
24
24
  const tags = tool.tags && tool.tags.length > 0 ? tool.tags : ["other"];
@@ -1,3 +1,4 @@
1
+ /** Точка сборки каталога тулов: импорты, реэкспорты, регистрация. */
1
2
  import { ToolRegistry } from "./registry";
2
3
  import { ToolExecutor } from "./executor";
3
4
  import { readFileTool } from "./read-file";
@@ -14,29 +15,33 @@ import { bashTool } from "./bash";
14
15
  import { processListTool } from "./process-list";
15
16
  import { processLogTool } from "./process-log";
16
17
  import { processKillTool } from "./process-kill";
18
+ import { processWaitTool } from "./process-wait";
17
19
  import { subagentTool } from "./subagent";
18
20
  import { chunkQueryTool } from "./chunk-query";
19
21
  import { webSearchTool } from "./web-search";
20
22
  import { webFetchTool } from "./web-fetch";
21
23
  import { webBrowseTool } from "./web-browse";
22
24
  import { downloadFileTool } from "./download-file";
23
- // questionTool and approveTool removed — interactive menus are too disruptive for small models.
24
- // The model should ask questions as plain text in its response instead.
25
- // import { questionTool } from './question'
26
- // import { approveTool } from './approve'
25
+ // question/approve — основной набор: question покрывает выбор + свободный
26
+ // текст (с pre-check памяти), approve — явное Yes/No (reject даёт success:false,
27
+ // и цикл трактует это как сигнал сбоя, а не текст ответа).
28
+ import { questionTool } from "./question";
29
+ import { approveTool } from "./approve";
27
30
  import { createLoadSkillTool } from "./load-skill";
28
31
  import { pipelineRunTool } from "./pipeline-run";
29
32
  import { mcpCallTool } from "./mcp-call";
30
33
  import { searchHistoryTool } from "./search-history";
31
34
  import { rememberTool } from "./remember";
32
35
  import { recallTool } from "./recall";
36
+ import { meetDoneTool } from "./meet-done";
33
37
  import { createBrowserTool } from "./browser";
34
38
  import { attachImageTool } from "./attach-image";
35
39
  import { enableToolsTool } from "./enable-tools";
36
40
  import { sessionInfoTool } from "./session-info";
37
41
  export { ToolRegistry, ToolExecutor };
38
42
  export { filterToolsByTags } from "./filter-tools";
39
- export { readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, chunkQueryTool, processListTool, processLogTool, processKillTool, webSearchTool, webFetchTool, webBrowseTool, downloadFileTool, createLoadSkillTool, pipelineRunTool, mcpCallTool, searchHistoryTool, rememberTool, recallTool, createBrowserTool, attachImageTool, enableToolsTool, sessionInfoTool, };
43
+ export { readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, chunkQueryTool, processListTool, processLogTool, processKillTool, processWaitTool, webSearchTool, webFetchTool, webBrowseTool, downloadFileTool, createLoadSkillTool, pipelineRunTool, mcpCallTool, searchHistoryTool, rememberTool, recallTool, meetDoneTool, createBrowserTool, attachImageTool, enableToolsTool, sessionInfoTool, questionTool, approveTool, };
44
+ /** Регистрирует все тулы; опционально — on-demand тул и скиллы. */
40
45
  export function registerAllTools(registry, skillsModule, opts) {
41
46
  const tools = [
42
47
  readFileTool,
@@ -55,6 +60,7 @@ export function registerAllTools(registry, skillsModule, opts) {
55
60
  processListTool,
56
61
  processLogTool,
57
62
  processKillTool,
63
+ processWaitTool,
58
64
  webSearchTool,
59
65
  webFetchTool,
60
66
  webBrowseTool,
@@ -64,10 +70,12 @@ export function registerAllTools(registry, skillsModule, opts) {
64
70
  searchHistoryTool,
65
71
  attachImageTool,
66
72
  sessionInfoTool,
73
+ meetDoneTool,
74
+ questionTool,
75
+ approveTool,
67
76
  ];
68
- // On-demand tool activation is the default. When explicitly disabled
69
- // (enableOnDemand: false), the tool is not registered at all — combined
70
- // with an empty defaultTags this restores the legacy all-tools behavior.
77
+ // On-demand активация — дефолт. При явном enableOnDemand:false тул не
78
+ // регистрируется вовсе — с пустым defaultTags это legacy-режим «все тулы».
71
79
  if (opts?.enableOnDemand !== false) {
72
80
  tools.push(enableToolsTool);
73
81
  }
@@ -1,17 +1,20 @@
1
+ /** Тул листинга каталога (игнор мусора, cap вывода, кешируемый). */
1
2
  import { readdirSync, statSync, existsSync } from "fs";
2
3
  import { resolve } from "path";
3
4
  import { t } from "../i18n/index";
4
5
  import { isPathInScope } from "../modules/security/path-validator";
5
6
  import { safeResolvePath } from "./path-utils";
6
7
  import { MAX_PREVIEW_LINES } from "./preview";
7
- /** Directories hidden from list_dir output (consistent with indexer walker). */
8
+ /** Каталоги вне выдачи list_dir (едино с walker'ом индексёра). */
8
9
  const IGNORED_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage", ".mma"]);
10
+ /** Показывает файлы/подкаталоги (каталоги с `/`); мусор скрыт. */
9
11
  export const listDirTool = {
10
12
  name: "list_dir",
11
13
  icon: "📂",
12
14
  description: `List files and directories in a given path. Shows up to ${MAX_PREVIEW_LINES} entries by default.`,
13
15
  tags: ["file"],
14
16
  boundedOutput: true,
17
+ cacheable: true,
15
18
  parameters: {
16
19
  type: "object",
17
20
  properties: {
@@ -22,7 +25,6 @@ export const listDirTool = {
22
25
  handler: async (ctx, args) => {
23
26
  const path = String(args.path);
24
27
  const resolved = safeResolvePath(ctx.baseDir, path);
25
- // Check path permissions
26
28
  const scopeCheck = isPathInScope(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
27
29
  if (!scopeCheck.allowed) {
28
30
  return {
@@ -1,10 +1,17 @@
1
+ /** Фабрика тула загрузки скилла (инжект в системный промпт). */
1
2
  import { t } from "../i18n/index";
2
3
  import { estimateTokens } from "../llm/token-counter";
4
+ /**
5
+ * Фабрика тула load_skill. Контент скилла инжектится в системный промпт
6
+ * (не в историю) — переживает компакцию. Возврат: определение тула.
7
+ */
3
8
  export function createLoadSkillTool(skillsModule) {
4
9
  return {
5
10
  name: "load_skill",
6
11
  icon: "🎯",
7
12
  tags: ["core"],
13
+ // Роутинг скиллов — вне сужения (P5): модель использует только видимый скилл.
14
+ alwaysOn: true,
8
15
  description: "Load a skill by name. Skills provide specialized instructions and workflows. When loaded, the skill content is injected into the system prompt (not conversation history) so it persists through context compaction. Only use when the task clearly requires domain-specific knowledge.",
9
16
  parameters: {
10
17
  type: "object",
@@ -1,7 +1,10 @@
1
+ /** Тул вызова тула на MCP-сервере (ленивая регистрация из конфига). */
2
+ import { t } from "../i18n/index";
1
3
  import { MCPClient } from "../modules/mcp/client";
2
4
  import { MCPRegistry } from "../modules/mcp/registry";
3
5
  const registry = new MCPRegistry();
4
6
  let initialized = false;
7
+ /** Ленивая регистрация MCP-серверов из конфига (один раз на процесс). */
5
8
  function ensureInitialized(ctx) {
6
9
  if (initialized)
7
10
  return;
@@ -22,6 +25,7 @@ function ensureInitialized(ctx) {
22
25
  }
23
26
  }
24
27
  }
28
+ /** Вызывает тул на MCP-сервере. Параметры: server, tool, args. */
25
29
  export const mcpCallTool = {
26
30
  name: "mcp_call",
27
31
  icon: "🔗",
@@ -49,21 +53,28 @@ export const mcpCallTool = {
49
53
  const available = registry.list();
50
54
  return {
51
55
  success: false,
52
- output: `MCP server "${serverName}" not found. Available: ${available.join(", ") || "(none)"}`,
56
+ output: t("tool.mcp_server_not_found", {
57
+ server: serverName,
58
+ available: available.join(", ") || "(none)",
59
+ }),
53
60
  };
54
61
  }
62
+ const client = new MCPClient(serverConfig);
55
63
  try {
56
- const client = new MCPClient(serverConfig);
57
64
  await client.connect();
58
65
  const result = await client.callTool(toolName, toolArgs);
59
- await client.disconnect();
60
66
  return {
61
67
  success: true,
62
68
  output: `MCP call result:\n${JSON.stringify(result, null, 2)}`,
63
69
  };
64
70
  }
65
71
  catch (e) {
66
- return { success: false, output: `MCP call failed: ${e.message}` };
72
+ return { success: false, output: t("tool.mcp_call_failed", { error: e.message }) };
73
+ }
74
+ finally {
75
+ // Клиент закрывать всегда: раньше ошибка/таймаут в callTool пропускала
76
+ // disconnect и сиротила stdio-дочерний процесс на сессию.
77
+ await client.disconnect().catch(() => { });
67
78
  }
68
79
  },
69
80
  };
@@ -0,0 +1,14 @@
1
+ /** Сентинел завершения meet-интервью: ставит флаг, REPL выходит из режима. */
2
+ import { t } from "../i18n/index";
3
+ /** Модель зовёт по финальному саммари; вне онбординга — no-op по флагу. */
4
+ export const meetDoneTool = {
5
+ name: "meet_done",
6
+ description: "Signal that the get-acquainted interview is complete. Call only after the final summary.",
7
+ tags: ["memory"],
8
+ parameters: { type: "object", properties: {} },
9
+ handler: async (ctx) => {
10
+ if (ctx.onboarding)
11
+ ctx.onboarding.done = true;
12
+ return { success: true, output: t("tool.meet_done") };
13
+ },
14
+ };