micro-models-agent 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/app.js +22 -4
  3. package/dist/assets/index.html +29 -0
  4. package/dist/assets/js/dom.js +2 -1
  5. package/dist/assets/js/feed.js +38 -26
  6. package/dist/assets/js/format.js +1 -1
  7. package/dist/assets/js/rail.js +114 -0
  8. package/dist/assets/js/settings-controls.js +2 -2
  9. package/dist/assets/js/settings.js +246 -51
  10. package/dist/assets/js/store.js +3 -0
  11. package/dist/assets/js/tabs.js +104 -2
  12. package/dist/assets/styles.css +215 -66
  13. package/dist/cli/cache-line.js +1 -0
  14. package/dist/cli/command-suggest.js +5 -4
  15. package/dist/cli/commands.js +231 -122
  16. package/dist/cli/completer.js +132 -31
  17. package/dist/cli/index.js +1 -0
  18. package/dist/cli/json-payload.js +4 -4
  19. package/dist/cli/main.js +166 -61
  20. package/dist/cli/onboarding-commands.js +126 -0
  21. package/dist/cli/plugin-commands.js +2 -1
  22. package/dist/cli/relaunch.js +16 -13
  23. package/dist/cli/repl-commands-lsp.js +75 -0
  24. package/dist/cli/repl-commands-skills.js +105 -0
  25. package/dist/cli/repl-commands.js +589 -266
  26. package/dist/cli/repl.js +436 -177
  27. package/dist/cli/run-result.js +5 -6
  28. package/dist/cli/security-commands.js +37 -28
  29. package/dist/cli/setup-order.js +4 -55
  30. package/dist/cli/setup-prompt.js +3 -2
  31. package/dist/cli/setup.js +27 -107
  32. package/dist/cli/web-command.js +98 -0
  33. package/dist/config/budget.js +5 -5
  34. package/dist/config/config.js +103 -96
  35. package/dist/config/defaults.js +38 -8
  36. package/dist/config/domains.js +24 -27
  37. package/dist/config/env.js +46 -0
  38. package/dist/config/experts.js +2 -0
  39. package/dist/config/index.js +1 -0
  40. package/dist/config/paths.js +56 -0
  41. package/dist/config/redact.js +44 -0
  42. package/dist/config/registry-extra.js +334 -0
  43. package/dist/config/registry.js +584 -0
  44. package/dist/config/security.js +35 -36
  45. package/dist/config/timeouts.js +74 -0
  46. package/dist/config/utils.js +16 -15
  47. package/dist/core/agent/artifact-progress.js +59 -0
  48. package/dist/core/agent/artifact-resolve.js +79 -0
  49. package/dist/core/agent/audit-gate.js +7 -3
  50. package/dist/core/agent/compaction.js +2 -2
  51. package/dist/core/agent/constants.js +33 -4
  52. package/dist/core/agent/context-log.js +454 -0
  53. package/dist/core/agent/context-renderer.js +5 -0
  54. package/dist/core/agent/critic-gate.js +132 -0
  55. package/dist/core/agent/hallucination-gate.js +148 -38
  56. package/dist/core/agent/loop-state.js +15 -0
  57. package/dist/core/agent/prefix-monitor.js +1 -1
  58. package/dist/core/agent/reasoning-resolver.js +10 -1
  59. package/dist/core/agent/token-tracker.js +7 -4
  60. package/dist/core/agent/tool-batch.js +93 -18
  61. package/dist/core/agent/tool-images.js +18 -0
  62. package/dist/core/agent/tool-output.js +65 -11
  63. package/dist/core/agent/tool-pairing.js +60 -0
  64. package/dist/core/agent-moe.js +25 -24
  65. package/dist/core/agent.js +429 -108
  66. package/dist/core/bootstrap/agent-phase.js +66 -0
  67. package/dist/core/bootstrap/config-phase.js +86 -0
  68. package/dist/core/bootstrap/modules-phase.js +269 -0
  69. package/dist/core/bootstrap/provider-phase.js +183 -0
  70. package/dist/core/bootstrap/session-phase.js +204 -0
  71. package/dist/core/bootstrap/state.js +94 -0
  72. package/dist/core/bootstrap/workspace-phase.js +115 -0
  73. package/dist/core/bootstrap.js +92 -660
  74. package/dist/core/crash-handler.js +17 -7
  75. package/dist/core/environment.js +37 -12
  76. package/dist/core/host-plan.js +43 -0
  77. package/dist/core/host.js +23 -0
  78. package/dist/core/index.js +1 -0
  79. package/dist/core/prompt-builder.js +17 -1
  80. package/dist/core/prompt-overflow.js +42 -43
  81. package/dist/core/session-logger.js +122 -30
  82. package/dist/core/system-prompt.js +117 -0
  83. package/dist/core/version.js +6 -4
  84. package/dist/core/web-host.js +34 -0
  85. package/dist/core/workspace.js +19 -15
  86. package/dist/i18n/en.json +106 -21
  87. package/dist/i18n/index.js +37 -3
  88. package/dist/i18n/ru.json +109 -21
  89. package/dist/index.js +7 -3
  90. package/dist/llm/image-utils.js +55 -64
  91. package/dist/llm/llm-errors.js +9 -0
  92. package/dist/llm/model-loader.js +10 -32
  93. package/dist/llm/models-dev.js +121 -0
  94. package/dist/llm/openai-compat.js +146 -73
  95. package/dist/llm/orchestrator.js +33 -11
  96. package/dist/llm/provider-budget.js +2 -2
  97. package/dist/llm/provider-context.js +233 -0
  98. package/dist/llm/provider.js +1 -1
  99. package/dist/llm/response.js +5 -0
  100. package/dist/llm/token-counter.js +27 -11
  101. package/dist/logger/app-logger.js +48 -22
  102. package/dist/logger/file-log.js +28 -19
  103. package/dist/logger/index.js +1 -0
  104. package/dist/main.js +24285 -22447
  105. package/dist/migration/backup.js +14 -3
  106. package/dist/migration/detect.js +12 -3
  107. package/dist/migration/index.js +1 -0
  108. package/dist/modules/artifacts/store.js +9 -8
  109. package/dist/modules/browser/actions.js +6 -0
  110. package/dist/modules/browser/bridge-client.js +14 -12
  111. package/dist/modules/browser/bridge-path.js +4 -4
  112. package/dist/modules/browser/console-buffer.js +37 -0
  113. package/dist/modules/browser/cookie-store.js +5 -0
  114. package/dist/modules/browser/driver-types.js +1 -0
  115. package/dist/modules/browser/driver.js +19 -11
  116. package/dist/modules/browser/index.js +3 -1
  117. package/dist/modules/browser/module.js +2 -1
  118. package/dist/modules/browser/session.js +10 -30
  119. package/dist/modules/browser/snapshot.js +19 -13
  120. package/dist/modules/browser/types.js +1 -0
  121. package/dist/modules/certification/cli.js +28 -20
  122. package/dist/modules/certification/fact-checker.js +9 -1
  123. package/dist/modules/certification/loader.js +9 -2
  124. package/dist/modules/certification/manifest.js +83 -21
  125. package/dist/modules/certification/runner.js +23 -13
  126. package/dist/modules/certification/scenarios.js +20 -20
  127. package/dist/modules/certification/syntax-scenarios.js +1 -2
  128. package/dist/modules/context/chunk-query.js +11 -9
  129. package/dist/modules/context/fact-extractor.js +37 -39
  130. package/dist/modules/context/index.js +1 -0
  131. package/dist/modules/context/manager.js +106 -80
  132. package/dist/modules/critic/client.js +176 -0
  133. package/dist/modules/critic/evidence.js +81 -0
  134. package/dist/modules/critic/provider.js +45 -0
  135. package/dist/modules/critic/tools.js +78 -0
  136. package/dist/modules/critic/types.js +1 -0
  137. package/dist/modules/execution/audit-runners.js +28 -34
  138. package/dist/modules/execution/auditor.js +53 -49
  139. package/dist/modules/execution/execution-plugin.js +142 -102
  140. package/dist/modules/execution/index.js +1 -1
  141. package/dist/modules/execution/input-from.js +37 -13
  142. package/dist/modules/execution/module.js +368 -191
  143. package/dist/modules/execution/moe-executor.js +53 -26
  144. package/dist/modules/execution/plan-actions.js +320 -202
  145. package/dist/modules/execution/plan-checks.js +35 -0
  146. package/dist/modules/execution/plan-coverage.js +36 -33
  147. package/dist/modules/execution/plan-steps.js +11 -3
  148. package/dist/modules/execution/plan-store.js +83 -17
  149. package/dist/modules/execution/plan-tool.js +89 -35
  150. package/dist/modules/execution/plan-validator.js +30 -10
  151. package/dist/modules/execution/planner.js +22 -13
  152. package/dist/modules/execution/stuck-detector.js +196 -93
  153. package/dist/modules/execution/tracker.js +22 -8
  154. package/dist/modules/execution/verifier.js +69 -48
  155. package/dist/modules/execution/windows-commands.js +12 -12
  156. package/dist/modules/hallucination/artifact-claim.js +93 -0
  157. package/dist/modules/hallucination/confidence.js +13 -11
  158. package/dist/modules/hallucination/consistency.js +10 -2
  159. package/dist/modules/hallucination/detector.js +10 -5
  160. package/dist/modules/hallucination/factual.js +43 -26
  161. package/dist/modules/hallucination/index.js +1 -0
  162. package/dist/modules/hallucination/js-identifiers.js +44 -45
  163. package/dist/modules/hallucination/llm-judge.js +17 -12
  164. package/dist/modules/hallucination/success-claim.js +59 -0
  165. package/dist/modules/indexer/cache.js +52 -4
  166. package/dist/modules/indexer/index.js +1 -0
  167. package/dist/modules/indexer/map-command.js +2 -4
  168. package/dist/modules/indexer/map-select.js +16 -16
  169. package/dist/modules/indexer/module.js +47 -12
  170. package/dist/modules/indexer/project-profile.js +8 -7
  171. package/dist/modules/indexer/symbols.js +15 -15
  172. package/dist/modules/indexer/walker.js +159 -29
  173. package/dist/modules/lsp/check-tool.js +7 -5
  174. package/dist/modules/lsp/client.js +39 -39
  175. package/dist/modules/lsp/command.js +17 -24
  176. package/dist/modules/lsp/config.js +12 -11
  177. package/dist/modules/lsp/index.js +1 -0
  178. package/dist/modules/lsp/module.js +48 -46
  179. package/dist/modules/lsp/probe.js +13 -14
  180. package/dist/modules/lsp/project-root.js +14 -17
  181. package/dist/modules/lsp/startup-check.js +18 -16
  182. package/dist/modules/mcp/client.js +59 -62
  183. package/dist/modules/mcp/index.js +1 -0
  184. package/dist/modules/mcp/module.js +66 -37
  185. package/dist/modules/mcp/registry.js +5 -0
  186. package/dist/modules/memory/index.js +1 -0
  187. package/dist/modules/memory/module.js +12 -8
  188. package/dist/modules/memory/search.js +4 -1
  189. package/dist/modules/memory/store.js +40 -2
  190. package/dist/modules/onboarding/init.js +47 -0
  191. package/dist/modules/onboarding/meet.js +30 -0
  192. package/dist/modules/onboarding/mode.js +30 -0
  193. package/dist/modules/onboarding/prompts.js +43 -0
  194. package/dist/modules/pipelines/engine.js +10 -0
  195. package/dist/modules/pipelines/index.js +1 -0
  196. package/dist/modules/pipelines/parser.js +4 -1
  197. package/dist/modules/pipelines/template.js +4 -0
  198. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  199. package/dist/modules/plugins/builtin/notify.js +2 -0
  200. package/dist/modules/plugins/index.js +1 -0
  201. package/dist/modules/plugins/loader.js +18 -3
  202. package/dist/modules/plugins/manager.js +37 -17
  203. package/dist/modules/pricing/index.js +10 -11
  204. package/dist/modules/pricing/prices.js +12 -12
  205. package/dist/modules/processes/index.js +1 -0
  206. package/dist/modules/processes/kill-tree.js +27 -31
  207. package/dist/modules/processes/registry.js +44 -25
  208. package/dist/modules/processes/runner.js +4 -4
  209. package/dist/modules/providers/capabilities.js +28 -0
  210. package/dist/modules/providers/create.js +3 -4
  211. package/dist/modules/providers/factory.js +7 -7
  212. package/dist/modules/providers/fallback.js +12 -12
  213. package/dist/modules/providers/health.js +4 -4
  214. package/dist/modules/providers/manager.js +50 -19
  215. package/dist/modules/providers/naming.js +15 -0
  216. package/dist/modules/providers/preflight.js +71 -0
  217. package/dist/modules/providers/presets.js +39 -8
  218. package/dist/modules/providers/registry.js +7 -2
  219. package/dist/modules/reasoning/policy.js +8 -6
  220. package/dist/modules/reasoning/probe.js +30 -25
  221. package/dist/modules/security/audit-log.js +19 -36
  222. package/dist/modules/security/audit-notifier.js +35 -75
  223. package/dist/modules/security/command-validator.js +99 -51
  224. package/dist/modules/security/content-scanner.js +8 -8
  225. package/dist/modules/security/data-sanitizer.js +9 -16
  226. package/dist/modules/security/encryption.js +32 -52
  227. package/dist/modules/security/index.js +2 -1
  228. package/dist/modules/security/network-validator.js +120 -43
  229. package/dist/modules/security/path-validator.js +61 -46
  230. package/dist/modules/security/protected-paths.js +73 -0
  231. package/dist/modules/security/rate-limiter.js +17 -49
  232. package/dist/modules/security/security-policies.js +25 -57
  233. package/dist/modules/security/session-encryption.js +32 -71
  234. package/dist/modules/security/session-isolation.js +15 -28
  235. package/dist/modules/session/auto-name.js +63 -0
  236. package/dist/modules/session/export.js +98 -0
  237. package/dist/modules/session/index.js +1 -0
  238. package/dist/modules/session/manager.js +79 -28
  239. package/dist/modules/session/module.js +4 -3
  240. package/dist/modules/session/store.js +57 -20
  241. package/dist/modules/setup/answers.js +35 -0
  242. package/dist/modules/setup/apply.js +79 -0
  243. package/dist/modules/setup/discovery.js +72 -0
  244. package/dist/modules/skills/index.js +1 -0
  245. package/dist/modules/skills/loader.js +9 -1
  246. package/dist/modules/skills/module.js +11 -0
  247. package/dist/modules/updater/changelog-reader.js +15 -16
  248. package/dist/modules/updater/checker.js +9 -4
  249. package/dist/modules/updater/dev-detect.js +7 -6
  250. package/dist/modules/updater/index.js +1 -0
  251. package/dist/modules/updater/module.js +17 -15
  252. package/dist/modules/user-profile/compressor.js +2 -0
  253. package/dist/modules/user-profile/index.js +1 -0
  254. package/dist/modules/user-profile/profile.js +10 -1
  255. package/dist/modules/webui/answer-api.js +29 -0
  256. package/dist/modules/webui/assets.js +50 -0
  257. package/dist/modules/webui/body.js +21 -0
  258. package/dist/modules/webui/commands-settings.js +209 -0
  259. package/dist/modules/webui/commands-shared.js +60 -0
  260. package/dist/modules/webui/commands.js +174 -0
  261. package/dist/modules/webui/config-api.js +134 -0
  262. package/dist/modules/webui/file-api.js +202 -0
  263. package/dist/modules/webui/forward.js +159 -0
  264. package/dist/modules/webui/history-api.js +112 -0
  265. package/dist/modules/webui/host-state.js +60 -0
  266. package/dist/modules/webui/i18n-api.js +9 -0
  267. package/dist/modules/webui/limits.js +6 -0
  268. package/dist/modules/webui/log-forward.js +20 -0
  269. package/dist/modules/webui/memory-commands.js +64 -0
  270. package/dist/modules/webui/model-cache.js +18 -0
  271. package/dist/modules/webui/module.js +148 -0
  272. package/dist/modules/webui/persist.js +24 -0
  273. package/dist/modules/webui/prompt-io.js +97 -0
  274. package/dist/modules/webui/providers-api.js +337 -0
  275. package/dist/modules/webui/providers-view.js +56 -0
  276. package/dist/modules/webui/reasoning-view.js +15 -0
  277. package/dist/modules/webui/run-agent.js +81 -0
  278. package/dist/modules/webui/server.js +284 -0
  279. package/dist/modules/webui/sessions-api.js +178 -0
  280. package/dist/modules/webui/settings-api.js +561 -0
  281. package/dist/modules/webui/setup-api.js +201 -0
  282. package/dist/modules/webui/state.js +74 -0
  283. package/dist/modules/webui/util.js +261 -0
  284. package/dist/output/bus.js +7 -2
  285. package/dist/output/channel.js +80 -28
  286. package/dist/output/format.js +2 -1
  287. package/dist/output/index.js +1 -0
  288. package/dist/output/json-sink.js +5 -3
  289. package/dist/output/machine.js +4 -3
  290. package/dist/output/session-sink.js +4 -4
  291. package/dist/tools/approve.js +4 -0
  292. package/dist/tools/attach-image.js +5 -4
  293. package/dist/tools/auto-fixer.js +26 -44
  294. package/dist/tools/auto-tags.js +83 -0
  295. package/dist/tools/bash.js +193 -116
  296. package/dist/tools/browser.js +76 -31
  297. package/dist/tools/chunk-query.js +14 -6
  298. package/dist/tools/create-dir.js +9 -5
  299. package/dist/tools/delete-file.js +9 -5
  300. package/dist/tools/download-file.js +32 -7
  301. package/dist/tools/edit-file.js +52 -14
  302. package/dist/tools/enable-tools.js +6 -6
  303. package/dist/tools/executor.js +132 -27
  304. package/dist/tools/file-info.js +3 -1
  305. package/dist/tools/filter-tools.js +3 -3
  306. package/dist/tools/glob-tool.js +4 -1
  307. package/dist/tools/grep-tool.js +13 -10
  308. package/dist/tools/hidden-tools-block.js +6 -6
  309. package/dist/tools/index.js +16 -8
  310. package/dist/tools/list-dir.js +4 -2
  311. package/dist/tools/load-skill.js +7 -0
  312. package/dist/tools/mcp-call.js +15 -4
  313. package/dist/tools/meet-done.js +14 -0
  314. package/dist/tools/move-file.js +14 -7
  315. package/dist/tools/path-utils.js +29 -29
  316. package/dist/tools/pipeline-run.js +29 -7
  317. package/dist/tools/preview.js +1 -1
  318. package/dist/tools/process-ids.js +31 -0
  319. package/dist/tools/process-kill.js +9 -16
  320. package/dist/tools/process-list.js +1 -0
  321. package/dist/tools/process-log.js +11 -16
  322. package/dist/tools/process-wait.js +63 -0
  323. package/dist/tools/prompt-io.js +4 -3
  324. package/dist/tools/question.js +172 -25
  325. package/dist/tools/read-file.js +55 -34
  326. package/dist/tools/recall.js +14 -8
  327. package/dist/tools/registry.js +6 -5
  328. package/dist/tools/remember.js +8 -4
  329. package/dist/tools/scope-request.js +11 -6
  330. package/dist/tools/search-history.js +5 -2
  331. package/dist/tools/session-info.js +2 -0
  332. package/dist/tools/set-thinking.js +8 -4
  333. package/dist/tools/subagent.js +59 -39
  334. package/dist/tools/syntax-validator.js +292 -90
  335. package/dist/tools/user-input.js +22 -18
  336. package/dist/tools/web-browse.js +9 -3
  337. package/dist/tools/web-fetch.js +12 -23
  338. package/dist/tools/web-search.js +7 -5
  339. package/dist/tools/write-file.js +45 -15
  340. package/dist/ui/action-log.js +13 -0
  341. package/dist/ui/box.js +4 -3
  342. package/dist/ui/colors.js +2 -0
  343. package/dist/ui/diff.js +6 -5
  344. package/dist/ui/index.js +1 -0
  345. package/dist/ui/line-editor.js +230 -122
  346. package/dist/ui/line-math.js +10 -2
  347. package/dist/ui/md-formatter.js +57 -37
  348. package/dist/ui/output.js +8 -3
  349. package/dist/ui/plan-view.js +21 -10
  350. package/dist/ui/renderer.js +393 -67
  351. package/dist/ui/spinner.js +16 -5
  352. package/dist/ui/table.js +15 -17
  353. package/dist/ui/theme.js +143 -0
  354. package/dist/utils/child-run.js +20 -0
  355. package/dist/utils/error.js +1 -1
  356. package/dist/utils/index.js +2 -0
  357. package/dist/utils/retry.js +2 -2
  358. package/dist/utils/truncate.js +1 -1
  359. package/package.json +1 -1
@@ -1,36 +1,72 @@
1
1
  import { BrowserSession } from "../modules/browser/session";
2
2
  import { DEFAULT_BROWSER_CONFIG } from "../modules/browser/types";
3
+ import { bufferToDataUrl } from "../llm/image-utils";
3
4
  import { t } from "../i18n/index";
5
+ import { defaultOutputBus } from "../output";
6
+ import { errMsg } from "../utils";
4
7
  import { join } from "path";
8
+ import { mkdirSync } from "fs";
9
+ import { projectMmaDir } from "../config/paths";
5
10
  let session = null;
11
+ /**
12
+ * Тестовый seam: подмена фабрики сессии без `vi.mock` (в Bun он глобален на
13
+ * процесс). Используется только в тестах сериализации действий.
14
+ */
15
+ let sessionFactory;
16
+ /** @internal */
17
+ export function setBrowserSessionFactoryForTest(factory) {
18
+ sessionFactory = factory;
19
+ session = null;
20
+ }
21
+ /**
22
+ * Сериализация действий. В MoE параллельные волны делят module-level сессию:
23
+ * одновременный `execute()` перетирает состояние страницы, а `close()` одного
24
+ * субагента убивает сессию остальных. Promise-chain гарантирует, что в браузере
25
+ * одновременно выполняется ровно одно действие.
26
+ */
27
+ let actionChain = Promise.resolve();
28
+ /** Сериализует действия: одновременно в браузере ровно одно. */
29
+ function serialize(fn) {
30
+ const run = actionChain.then(fn, fn);
31
+ actionChain = run.then(() => undefined, () => undefined);
32
+ return run;
33
+ }
34
+ /** Ленивая module-level сессия (cookie-dir в project .mma). */
6
35
  function getSession(ctx) {
7
36
  if (!session) {
8
- const cookieDir = join(ctx.baseDir, ".mma", "browser");
9
- session = new BrowserSession({
10
- ...DEFAULT_BROWSER_CONFIG,
11
- headless: ctx.config.browser?.headless ?? true,
12
- maxElements: ctx.config.browser?.maxElements ?? 30,
13
- maxContentChars: ctx.config.browser?.maxContentChars ?? 2500,
14
- maxConsoleEntries: ctx.config.browser?.maxConsoleEntries ?? 40,
15
- maxConsoleLineChars: ctx.config.browser?.maxConsoleLineChars ?? 400,
16
- navigationTimeout: ctx.config.browser?.navigationTimeout ?? 15000,
17
- viewportWidth: ctx.config.browser?.viewportWidth ?? 1280,
18
- viewportHeight: ctx.config.browser?.viewportHeight ?? 720,
19
- cookieDir,
20
- });
37
+ const cookieDir = join(projectMmaDir(ctx.baseDir), "browser");
38
+ mkdirSync(cookieDir, { recursive: true });
39
+ session = sessionFactory
40
+ ? sessionFactory(ctx)
41
+ : new BrowserSession({
42
+ ...DEFAULT_BROWSER_CONFIG,
43
+ headless: ctx.config.browser?.headless ?? true,
44
+ maxElements: ctx.config.browser?.maxElements ?? 30,
45
+ maxContentChars: ctx.config.browser?.maxContentChars ?? 2500,
46
+ maxConsoleEntries: ctx.config.browser?.maxConsoleEntries ?? 40,
47
+ maxConsoleLineChars: ctx.config.browser?.maxConsoleLineChars ?? 400,
48
+ navigationTimeout: ctx.config.browser?.navigationTimeout ?? 15000,
49
+ viewportWidth: ctx.config.browser?.viewportWidth ?? 1280,
50
+ viewportHeight: ctx.config.browser?.viewportHeight ?? 720,
51
+ cookieDir,
52
+ });
21
53
  }
22
54
  return session;
23
55
  }
56
+ /** Скриншот для текстовой модели: пометка + текстовый снапшот. */
24
57
  export function formatScreenshotForTextModel(snapshot) {
25
58
  return `${t("tool.screenshot_unavailable")}\n\n${snapshot}`;
26
59
  }
27
- export function formatScreenshotResult(snapshot, screenshot, supportsVision) {
28
- if (!supportsVision) {
29
- return formatScreenshotForTextModel(snapshot);
30
- }
31
- const base64 = screenshot.toString("base64");
32
- return `${snapshot}\n\n[Screenshot: data:image/png;base64,${base64}]`;
60
+ /**
61
+ * Превращает PNG-буфер скриншота в image-part для доставки модели. Размер
62
+ * ограничивается `bufferToDataUrl` (ресайз ≤800px, JPEG) — иначе base64
63
+ * раздул бы контекст малыша (32K).
64
+ */
65
+ export async function screenshotToImagePart(screenshot) {
66
+ const { dataUrl } = await bufferToDataUrl(screenshot);
67
+ return { type: "image_url", image_url: { url: dataUrl } };
33
68
  }
69
+ /** Фабрика browser-тула (сессия ленивая, module-level). */
34
70
  export function createBrowserTool() {
35
71
  return {
36
72
  name: "browser",
@@ -93,23 +129,32 @@ export function createBrowserTool() {
93
129
  return { success: false, output: t("tool.action_required") };
94
130
  }
95
131
  const s = getSession(ctx);
96
- const result = await s.execute(action, args);
97
- const supportsVision = ctx.config.model.includes("vision") ||
98
- ctx.config.model.includes("gpt-4o") ||
99
- ctx.config.model.includes("claude");
132
+ const result = await serialize(() => s.execute(action, args));
100
133
  if (result.screenshot) {
101
- return {
102
- success: result.success,
103
- output: formatScreenshotResult(result.output, result.screenshot, supportsVision),
104
- };
134
+ // vision берётся из резолвера возможностей активной модели
135
+ // (пресет + native-probe + конфиг), а не из эвристики по имени.
136
+ // Без vision отдаём текстовый snapshot — base64 в контекст не льём.
137
+ if (ctx.capabilities?.vision) {
138
+ return {
139
+ success: result.success,
140
+ output: result.output,
141
+ images: [await screenshotToImagePart(result.screenshot)],
142
+ };
143
+ }
144
+ return { success: result.success, output: formatScreenshotForTextModel(result.output) };
105
145
  }
106
146
  return { success: result.success, output: result.output };
107
147
  },
108
148
  };
109
149
  }
150
+ /** Закрывает сессию после текущего действия (не посреди навигации). */
110
151
  export function closeBrowserSession() {
111
- if (session) {
112
- session.close().catch(() => { });
113
- session = null;
114
- }
152
+ const s = session;
153
+ session = null;
154
+ if (!s)
155
+ return;
156
+ // Дожидаемся текущего действия, чтобы не закрыть браузер во время навигации.
157
+ actionChain = actionChain.then(() => s.close().catch((err) => {
158
+ defaultOutputBus.log("warn", "browser", `Failed to close browser session: ${errMsg(err)}`);
159
+ }));
115
160
  }
@@ -1,15 +1,17 @@
1
+ /** Тул ответа по большому тексту: чанкинг + параллельные запросы + синтез. */
1
2
  import { readFileSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
4
  import { t } from "../i18n/index";
4
5
  import { isPathInScope } from "../modules/security/path-validator";
5
6
  import { DEFAULT_SECURITY_CONFIG } from "../config/security";
6
7
  import { runChunkQuery } from "../modules/context/chunk-query";
8
+ /** Отвечает на вопрос по тексту вне контекстного окна (чанк-параллелизм). */
7
9
  export const chunkQueryTool = {
8
10
  name: "chunk_query",
9
11
  icon: "🧩",
10
12
  description: "Answer a query over a large text (file or inline) by splitting it into chunks and querying the model per chunk in parallel, then optionally synthesizing a final answer. Use when the input is too large for the context window.",
11
13
  tags: ["research"],
12
- /** Multiple LLM calls (parallel chunk queries + synthesis) need a longer budget. */
14
+ /** Несколько LLM-вызовов (чанк-запросы + синтез) — нужен больший бюджет. */
13
15
  timeoutMs: 300000,
14
16
  parameters: {
15
17
  type: "object",
@@ -64,22 +66,28 @@ export const chunkQueryTool = {
64
66
  return { success: false, output: t("tool.chunk_query_no_input") };
65
67
  let content = text || "";
66
68
  if (inputPath) {
67
- // Same policy as read_file: global security paths (denied patterns
68
- // like .env/keys) + subagent scope.
69
+ // Та же политика, что у read_file: глобальные security-пути (.env/ключи)
70
+ // + скоуп субагента.
69
71
  const resolvedInput = resolve(ctx.baseDir, inputPath);
70
72
  const check = isPathInScope(ctx.baseDir, resolvedInput, ctx.scope, ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths);
71
73
  if (!check.allowed) {
72
- return { success: false, output: `[SCOPE] ${check.reason || "Path not allowed"}` };
74
+ return {
75
+ success: false,
76
+ output: t("tool.chunk_query_path_not_allowed", { reason: check.reason || "Path not allowed" }),
77
+ };
73
78
  }
74
79
  try {
75
80
  content = readFileSync(resolvedInput, "utf8");
76
81
  }
77
82
  catch (e) {
78
- return { success: false, output: `Cannot read ${inputPath}: ${e.message}` };
83
+ return {
84
+ success: false,
85
+ output: t("tool.chunk_query_read_failed", { path: inputPath, error: e.message }),
86
+ };
79
87
  }
80
88
  }
81
89
  if (!ctx.llmProvider) {
82
- return { success: false, output: "chunk_query: missing llmProvider in context" };
90
+ return { success: false, output: t("tool.chunk_query_no_provider") };
83
91
  }
84
92
  const maxParallel = typeof args.max_parallel === "number"
85
93
  ? args.max_parallel
@@ -1,9 +1,12 @@
1
+ /** Тул создания каталога (рекурсивно, с security-проверками). */
1
2
  import { mkdirSync, existsSync } from "fs";
2
3
  import { t } from "../i18n/index";
3
4
  import { isPathWritable } from "../modules/security/path-validator";
4
5
  import { logFileWrite, logSecurityBlock } from "../modules/security/audit-log";
5
6
  import { getSessionSecurityConfig } from "../modules/security/session-isolation";
6
7
  import { safeResolvePath } from "./path-utils";
8
+ import { protectedPathBlock } from "../modules/security/protected-paths";
9
+ /** Создаёт каталог со всеми промежуточными. */
7
10
  export const createDirTool = {
8
11
  name: "create_dir",
9
12
  icon: "📁",
@@ -19,11 +22,10 @@ export const createDirTool = {
19
22
  handler: async (ctx, args) => {
20
23
  const path = String(args.path);
21
24
  const resolved = safeResolvePath(ctx.baseDir, path);
22
- // Get session-specific security config
23
25
  const securityConfig = ctx.sessionContext
24
26
  ? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
25
27
  : ctx.config.security;
26
- // Check file operations limit
28
+ // Лимит файловых операций.
27
29
  const maxFileOps = securityConfig?.maxFileOperations ?? 100;
28
30
  const currentCount = ctx.fileOperationsCount ?? 0;
29
31
  if (currentCount >= maxFileOps) {
@@ -33,7 +35,7 @@ export const createDirTool = {
33
35
  output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
34
36
  };
35
37
  }
36
- // Check path permissions
38
+ // Проверка прав на путь.
37
39
  const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
38
40
  if (!scopeCheck.allowed) {
39
41
  logSecurityBlock(ctx.sessionId, "file_write", scopeCheck.reason || "Path not allowed", path);
@@ -44,12 +46,14 @@ export const createDirTool = {
44
46
  }),
45
47
  };
46
48
  }
49
+ // Защищённые пути (.git/, .env-каталоги, …) — спросить юзера.
50
+ const protectedBlock = protectedPathBlock(ctx, resolved, path);
51
+ if (protectedBlock)
52
+ return protectedBlock;
47
53
  if (!existsSync(resolved)) {
48
54
  mkdirSync(resolved, { recursive: true });
49
55
  }
50
- // Increment file operations counter
51
56
  ctx.fileOperationsCount = currentCount + 1;
52
- // Log directory creation
53
57
  logFileWrite(ctx.sessionId, path, true, "Directory created");
54
58
  return { success: true, output: t("file.created", { path }) };
55
59
  },
@@ -1,3 +1,4 @@
1
+ /** Тул удаления файла (с diff удалённого, security-проверки). */
1
2
  import { unlinkSync, existsSync, statSync, readFileSync } from "fs";
2
3
  import { t } from "../i18n/index";
3
4
  import { isPathWritable } from "../modules/security/path-validator";
@@ -5,6 +6,8 @@ import { logFileDelete, logSecurityBlock } from "../modules/security/audit-log";
5
6
  import { getSessionSecurityConfig } from "../modules/security/session-isolation";
6
7
  import { generateDeleteDiff } from "../ui/diff";
7
8
  import { safeResolvePath } from "./path-utils";
9
+ import { protectedPathBlock } from "../modules/security/protected-paths";
10
+ /** Удаляет файл; каталоги отклоняет. Возвращает diff удалённого. */
8
11
  export const deleteFileTool = {
9
12
  name: "delete_file",
10
13
  icon: "🗑️",
@@ -20,11 +23,10 @@ export const deleteFileTool = {
20
23
  handler: async (ctx, args) => {
21
24
  const path = String(args.path);
22
25
  const resolved = safeResolvePath(ctx.baseDir, path);
23
- // Get session-specific security config
24
26
  const securityConfig = ctx.sessionContext
25
27
  ? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
26
28
  : ctx.config.security;
27
- // Check file operations limit
29
+ // Лимит файловых операций.
28
30
  const maxFileOps = securityConfig?.maxFileOperations ?? 100;
29
31
  const currentCount = ctx.fileOperationsCount ?? 0;
30
32
  if (currentCount >= maxFileOps) {
@@ -34,7 +36,7 @@ export const deleteFileTool = {
34
36
  output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
35
37
  };
36
38
  }
37
- // Check path permissions
39
+ // Проверка прав на путь.
38
40
  const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
39
41
  if (!scopeCheck.allowed) {
40
42
  logSecurityBlock(ctx.sessionId, "file_delete", scopeCheck.reason || "Path not allowed", path);
@@ -45,6 +47,10 @@ export const deleteFileTool = {
45
47
  }),
46
48
  };
47
49
  }
50
+ // Защищённые пути (.git/, .env, ключи) — спросить юзера.
51
+ const protectedBlock = protectedPathBlock(ctx, resolved, path, "file_delete");
52
+ if (protectedBlock)
53
+ return protectedBlock;
48
54
  if (!existsSync(resolved)) {
49
55
  return { success: false, output: t("file.notfound", { path }) };
50
56
  }
@@ -54,9 +60,7 @@ export const deleteFileTool = {
54
60
  const content = readFileSync(resolved, "utf-8");
55
61
  unlinkSync(resolved);
56
62
  const diff = generateDeleteDiff(content);
57
- // Increment file operations counter
58
63
  ctx.fileOperationsCount = currentCount + 1;
59
- // Log successful file deletion
60
64
  logFileDelete(ctx.sessionId, path, true);
61
65
  return { success: true, output: t("file.deleted", { path }), diff };
62
66
  },
@@ -1,15 +1,18 @@
1
+ /** Тул скачивания URL в файл (байты, SSRF-защита, cap размера). */
1
2
  import { writeFileSync, mkdirSync } from "fs";
2
- import { dirname } from "path";
3
+ import { dirname, resolve } from "path";
3
4
  import { t } from "../i18n/index";
4
- import { isUrlAllowed, sanitizeUrl } from "../modules/security/network-validator";
5
+ import { BlockedRedirectError, fetchValidated, isUrlAllowed, sanitizeUrl, } from "../modules/security/network-validator";
5
6
  import { isPathWritable } from "../modules/security/path-validator";
7
+ import { protectedPathBlock } from "../modules/security/protected-paths";
6
8
  import { logNetworkRequest, logFileWrite, logSecurityBlock } from "../modules/security/audit-log";
7
9
  import { getSessionSecurityConfig } from "../modules/security/session-isolation";
8
10
  import { DEFAULT_SECURITY_CONFIG } from "../config/security";
9
11
  import { safeResolvePath } from "./path-utils";
10
- /** Default cap for a single download (bytes). Chunked responses without a
11
- * content-length header are checked after the body is read. */
12
+ /** Дефолтный cap скачивания (байты). Чанковые ответы без content-length
13
+ * проверяются после чтения тела. */
12
14
  export const MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
15
+ /** Качает бинарник в файл; возвращает путь/размер/тип. Параметры: url, path, max_bytes. */
13
16
  export const downloadFileTool = {
14
17
  name: "download_file",
15
18
  icon: "⬇️",
@@ -36,7 +39,7 @@ export const downloadFileTool = {
36
39
  if (!url || !path) {
37
40
  return { success: false, output: t("tool.invalid_params") };
38
41
  }
39
- // Network security: same rules as web_fetch/web_browse.
42
+ // Сеть: те же правила, что у web_fetch/web_browse.
40
43
  const securityConfig = ctx.sessionContext
41
44
  ? getSessionSecurityConfig(ctx.config, ctx.sessionContext).network
42
45
  : ctx.config.security?.network || DEFAULT_SECURITY_CONFIG.network;
@@ -48,7 +51,7 @@ export const downloadFileTool = {
48
51
  output: `[SECURITY BLOCKED] URL is not allowed: ${validation.reason}`,
49
52
  };
50
53
  }
51
- // Path safety: resolve against baseDir and verify writability.
54
+ // Путь: резолв от baseDir + проверка записи.
52
55
  const resolved = safeResolvePath(ctx.baseDir, path);
53
56
  const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
54
57
  if (!scopeCheck.allowed) {
@@ -60,9 +63,27 @@ export const downloadFileTool = {
60
63
  }),
61
64
  };
62
65
  }
66
+ // Тот же guard записи, что у write/edit: онбординг-allowlist и always-on
67
+ // проверка защищённых путей (иначе скачиванием перезаписали бы .env/ключи,
68
+ // которые остальные тулы записи отклоняют).
69
+ if (ctx.writePathAllowlist) {
70
+ const ok = ctx.writePathAllowlist.some((p) => resolve(p).toLowerCase() === resolve(resolved).toLowerCase());
71
+ if (!ok) {
72
+ return {
73
+ success: false,
74
+ output: t("file.path_not_allowed", {
75
+ path: `${path} — ${t("onboarding.write_restricted")}`,
76
+ }),
77
+ };
78
+ }
79
+ }
80
+ const protectedBlock = protectedPathBlock(ctx, resolved, path);
81
+ if (protectedBlock)
82
+ return protectedBlock;
63
83
  const maxBytes = Number(args.max_bytes) > 0 ? Number(args.max_bytes) : MAX_DOWNLOAD_BYTES;
64
84
  try {
65
- const response = await fetch(url, {
85
+ // Редиректы перевадидируются (SSRF) внутри хелпера.
86
+ const { response } = await fetchValidated(url, securityConfig, {
66
87
  signal: AbortSignal.timeout(securityConfig?.requestTimeout || 15000),
67
88
  });
68
89
  if (!response.ok) {
@@ -107,6 +128,10 @@ export const downloadFileTool = {
107
128
  };
108
129
  }
109
130
  catch (e) {
131
+ if (e instanceof BlockedRedirectError) {
132
+ logSecurityBlock(ctx.sessionId, "network_request", e.reason, sanitizeUrl(url));
133
+ return { success: false, output: `[SECURITY BLOCKED] redirect: ${e.reason}` };
134
+ }
110
135
  logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${e.message}`);
111
136
  return {
112
137
  success: false,
@@ -1,4 +1,6 @@
1
+ /** Тул точечной правки: exact-match замена, скан, валидация, diff. */
1
2
  import { readFileSync, writeFileSync } from "fs";
3
+ import { resolve } from "path";
2
4
  import { t } from "../i18n/index";
3
5
  import { isPathWritable } from "../modules/security/path-validator";
4
6
  import { scanContent } from "../modules/security/content-scanner";
@@ -7,6 +9,9 @@ import { getSessionSecurityConfig } from "../modules/security/session-isolation"
7
9
  import { generateDiff } from "../ui/diff";
8
10
  import { safeResolvePath } from "./path-utils";
9
11
  import { preValidateSyntax, detectImportConflicts } from "./syntax-validator";
12
+ import { errMsg } from "../utils";
13
+ import { protectedPathBlock } from "../modules/security/protected-paths";
14
+ /** Заменяет точный фрагмент в существующем файле (не regex). */
10
15
  export const editFileTool = {
11
16
  name: "edit_file",
12
17
  icon: "✏️",
@@ -24,11 +29,23 @@ export const editFileTool = {
24
29
  handler: async (ctx, args) => {
25
30
  const path = String(args.path);
26
31
  const resolved = safeResolvePath(ctx.baseDir, path);
27
- // Get session-specific security config
32
+ if (ctx.writePathAllowlist) {
33
+ const ok = ctx.writePathAllowlist.some((p) => resolve(p).toLowerCase() === resolve(resolved).toLowerCase());
34
+ if (!ok) {
35
+ return {
36
+ success: false,
37
+ output: t("file.path_not_allowed", {
38
+ path: `${path} — ${t("onboarding.write_restricted")}`,
39
+ reason: t("onboarding.write_restricted"),
40
+ }),
41
+ };
42
+ }
43
+ }
44
+ // Сессионный security-конфиг.
28
45
  const securityConfig = ctx.sessionContext
29
46
  ? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
30
47
  : ctx.config.security;
31
- // Check file operations limit
48
+ // Лимит файловых операций.
32
49
  const maxFileOps = securityConfig?.maxFileOperations ?? 100;
33
50
  const currentCount = ctx.fileOperationsCount ?? 0;
34
51
  if (currentCount >= maxFileOps) {
@@ -38,7 +55,7 @@ export const editFileTool = {
38
55
  output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
39
56
  };
40
57
  }
41
- // Check path permissions
58
+ // Проверка прав на путь.
42
59
  const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
43
60
  if (!scopeCheck.allowed) {
44
61
  logSecurityBlock(ctx.sessionId, "file_write", scopeCheck.reason || "Path not allowed", path);
@@ -49,19 +66,43 @@ export const editFileTool = {
49
66
  }),
50
67
  };
51
68
  }
52
- const content = readFileSync(resolved, "utf-8");
53
- const oldStr = String(args.old);
54
- const newStr = String(args.new);
69
+ // Защищённые пути (.git/, .env, ключи) — всегда спросить юзера.
70
+ const protectedBlock = protectedPathBlock(ctx, resolved, path);
71
+ if (protectedBlock)
72
+ return protectedBlock;
73
+ // Строковые аргументы обязательны: String(null) дало бы "null" и молча
74
+ // испортило файл. Явно отклоняем не-строки с понятной ошибкой.
75
+ if (typeof args.old !== "string" || typeof args.new !== "string") {
76
+ return {
77
+ success: false,
78
+ output: t("file.edit_args_invalid", {
79
+ oldType: typeof args.old,
80
+ newType: typeof args.new,
81
+ }),
82
+ };
83
+ }
84
+ const oldStr = args.old;
85
+ const newStr = args.new;
86
+ let content;
87
+ try {
88
+ content = readFileSync(resolved, "utf-8");
89
+ }
90
+ catch (err) {
91
+ return {
92
+ success: false,
93
+ output: t("file.read_failed", { path, error: errMsg(err) }),
94
+ };
95
+ }
55
96
  if (!content.includes(oldStr)) {
56
97
  return {
57
98
  success: false,
58
99
  output: t("file.string_not_found", { str: oldStr, path }),
59
100
  };
60
101
  }
61
- // Replacement via function — otherwise newStr is treated as a template
62
- // and `$&`, `$1` etc. would silently corrupt the file content.
102
+ // Замена через функцию — иначе newStr трактуется как шаблон и `$&`, `$1`
103
+ // молча испортят содержимое файла.
63
104
  const updated = content.replace(oldStr, () => newStr);
64
- // Check new content for dangerous patterns — only when security is enabled
105
+ // Скан нового контента — только при включённой безопасности.
65
106
  if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
66
107
  const scanResult = scanContent(updated, path, securityConfig.contentScan);
67
108
  if (!scanResult.allowed) {
@@ -72,7 +113,7 @@ export const editFileTool = {
72
113
  };
73
114
  }
74
115
  }
75
- // Pre-validate syntax BEFORE writing — prevents "file written" + syntax error loop
116
+ // Превалидация синтаксиса ДО записи — иначе петля "файл записан" + syntax error.
76
117
  const validation = await preValidateSyntax(resolved, updated, ctx.baseDir);
77
118
  if (!validation.valid) {
78
119
  return {
@@ -80,15 +121,12 @@ export const editFileTool = {
80
121
  output: t("file.syntax_error", { path, error: validation.error || "Unknown syntax error" }),
81
122
  };
82
123
  }
83
- // Detect import/export conflicts — warning only (code may be valid)
124
+ // Конфликты import/export — только warning.
84
125
  const conflicts = detectImportConflicts(updated);
85
126
  writeFileSync(resolved, updated, "utf-8");
86
127
  const diff = generateDiff(content, updated);
87
- // Increment file operations counter
88
128
  ctx.fileOperationsCount = currentCount + 1;
89
- // Log successful file edit
90
129
  logFileWrite(ctx.sessionId, path, true, "File edited");
91
- // Add conflict warning to output if any
92
130
  let output = t("file.replaced_in", { path });
93
131
  if (conflicts.length > 0) {
94
132
  output += `\n[WARNING] Import/export conflicts: ${conflicts.join(", ")}`;
@@ -1,11 +1,11 @@
1
+ /** On-demand тул: включает скрытые категории тулов на сессию. */
1
2
  import { t } from "../i18n/index";
2
3
  /**
3
- * On-demand tool activation. The main loop exposes only `config.tools.defaultTags`
4
- * (+ `alwaysOn` structural tools). Specialized tools (web, browser, memory,
5
- * process) are hidden from the schema to shrink the token footprint; the model
6
- * activates them by calling this tool with the desired tag. The handler pushes
7
- * the tags into the shared mutable `ctx.activeToolTags` array, which the agent
8
- * loop re-reads each iteration to recompute the LLM-visible tool set.
4
+ * On-demand активация тулов. Главный цикл показывает только
5
+ * `config.tools.defaultTags` (+ структурные `alwaysOn`); спецтулы (web,
6
+ * browser, memory, process) скрыты из схемы ради экономии токенов. Модель
7
+ * включает их этим тулом; хендлер дописывает теги в общий мутабельный
8
+ * `ctx.activeToolTags`, который цикл перечитывает каждую итерацию.
9
9
  */
10
10
  export const enableToolsTool = {
11
11
  name: "enable_tools",