micro-models-agent 2.16.1 → 2.17.1

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 (427) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/assets/app.js +1 -314
  3. package/dist/assets/hljs.js +5 -1244
  4. package/dist/assets/index.html +11 -0
  5. package/dist/assets/js/api.js +1 -28
  6. package/dist/assets/js/core/menus.js +1 -0
  7. package/dist/assets/js/core/store/feed.js +1 -0
  8. package/dist/assets/js/core/store/index.js +1 -0
  9. package/dist/assets/js/core/store/session.js +1 -0
  10. package/dist/assets/js/core/store/settings.js +1 -0
  11. package/dist/assets/js/core/store/tabs.js +1 -0
  12. package/dist/assets/js/dialog.js +1 -105
  13. package/dist/assets/js/dom.js +1 -178
  14. package/dist/assets/js/feed/core.js +1 -0
  15. package/dist/assets/js/feed/history.js +1 -0
  16. package/dist/assets/js/feed/plan.js +1 -0
  17. package/dist/assets/js/feed/question.js +3 -0
  18. package/dist/assets/js/feed/sidebar.js +1 -0
  19. package/dist/assets/js/feed/stream.js +1 -0
  20. package/dist/assets/js/feed/turn.js +5 -0
  21. package/dist/assets/js/feed.js +1 -1250
  22. package/dist/assets/js/format.js +2 -122
  23. package/dist/assets/js/i18n.js +1 -46
  24. package/dist/assets/js/rail.js +1 -114
  25. package/dist/assets/js/session/cmdk.js +1 -0
  26. package/dist/assets/js/session/composer.js +1 -0
  27. package/dist/assets/js/session/sessions-menu.js +1 -0
  28. package/dist/assets/js/session/tabs.js +1 -0
  29. package/dist/assets/js/settings/controls.js +1 -0
  30. package/dist/assets/js/settings/editors.js +1 -0
  31. package/dist/assets/js/settings/form.js +1 -0
  32. package/dist/assets/js/settings/modal.js +1 -0
  33. package/dist/assets/js/settings/panels.js +1 -0
  34. package/dist/assets/js/settings/providers.js +1 -0
  35. package/dist/assets/js/settings/search.js +1 -0
  36. package/dist/assets/js/settings/subdialog.js +1 -0
  37. package/dist/assets/js/settings/write.js +1 -0
  38. package/dist/assets/js/settings.js +1 -1914
  39. package/dist/assets/js/sidebar/core.js +1 -0
  40. package/dist/assets/js/sidebar/lsp.js +2 -0
  41. package/dist/assets/js/sidebar/map.js +1 -0
  42. package/dist/assets/js/sidebar/state.js +1 -0
  43. package/dist/assets/js/sidebar/trace.js +5 -0
  44. package/dist/assets/js/sidebar.js +1 -705
  45. package/dist/assets/js/store.js +1 -82
  46. package/dist/assets/js/subdialog.js +1 -0
  47. package/dist/assets/md.js +1 -291
  48. package/dist/assets/setup.js +3 -816
  49. package/dist/assets/smd.js +29 -1665
  50. package/dist/assets/styles.css +1 -1598
  51. package/dist/certification/certifications.json +1 -494
  52. package/dist/i18n/en.json +1 -1851
  53. package/dist/i18n/ru.json +1 -1854
  54. package/dist/main.js +815 -55797
  55. package/dist/modules/browser/bridge-server.mjs +2 -252
  56. package/package.json +10 -5
  57. package/dist/assets/js/settings-controls.js +0 -452
  58. package/dist/assets/js/tabs.js +0 -867
  59. package/dist/cli/cache-line.js +0 -31
  60. package/dist/cli/command-suggest.js +0 -39
  61. package/dist/cli/commands.js +0 -781
  62. package/dist/cli/completer.js +0 -268
  63. package/dist/cli/index.js +0 -3
  64. package/dist/cli/json-payload.js +0 -32
  65. package/dist/cli/main.js +0 -346
  66. package/dist/cli/onboarding-commands.js +0 -126
  67. package/dist/cli/plugin-commands.js +0 -38
  68. package/dist/cli/relaunch.js +0 -40
  69. package/dist/cli/repl-commands-lsp.js +0 -75
  70. package/dist/cli/repl-commands-skills.js +0 -105
  71. package/dist/cli/repl-commands.js +0 -1218
  72. package/dist/cli/repl.js +0 -1238
  73. package/dist/cli/run-result.js +0 -38
  74. package/dist/cli/security-commands.js +0 -177
  75. package/dist/cli/setup-order.js +0 -6
  76. package/dist/cli/setup-prompt.js +0 -50
  77. package/dist/cli/setup.js +0 -161
  78. package/dist/cli/web-command.js +0 -98
  79. package/dist/config/budget.js +0 -48
  80. package/dist/config/config.js +0 -345
  81. package/dist/config/defaults.js +0 -197
  82. package/dist/config/domains.js +0 -135
  83. package/dist/config/env.js +0 -46
  84. package/dist/config/experts.js +0 -17
  85. package/dist/config/index.js +0 -5
  86. package/dist/config/paths.js +0 -56
  87. package/dist/config/redact.js +0 -44
  88. package/dist/config/registry-extra.js +0 -334
  89. package/dist/config/registry.js +0 -584
  90. package/dist/config/security.js +0 -212
  91. package/dist/config/timeouts.js +0 -74
  92. package/dist/config/types.js +0 -1
  93. package/dist/config/utils.js +0 -57
  94. package/dist/core/agent/artifact-progress.js +0 -59
  95. package/dist/core/agent/artifact-resolve.js +0 -79
  96. package/dist/core/agent/audit-gate.js +0 -53
  97. package/dist/core/agent/compaction.js +0 -89
  98. package/dist/core/agent/constants.js +0 -90
  99. package/dist/core/agent/context-log.js +0 -454
  100. package/dist/core/agent/context-renderer.js +0 -45
  101. package/dist/core/agent/critic-gate.js +0 -132
  102. package/dist/core/agent/hallucination-gate.js +0 -197
  103. package/dist/core/agent/loop-state.js +0 -68
  104. package/dist/core/agent/prefix-monitor.js +0 -101
  105. package/dist/core/agent/reasoning-resolver.js +0 -65
  106. package/dist/core/agent/token-tracker.js +0 -99
  107. package/dist/core/agent/tool-batch.js +0 -312
  108. package/dist/core/agent/tool-images.js +0 -18
  109. package/dist/core/agent/tool-output.js +0 -116
  110. package/dist/core/agent/tool-pairing.js +0 -60
  111. package/dist/core/agent-moe.js +0 -248
  112. package/dist/core/agent.js +0 -1299
  113. package/dist/core/bootstrap/agent-phase.js +0 -66
  114. package/dist/core/bootstrap/config-phase.js +0 -86
  115. package/dist/core/bootstrap/modules-phase.js +0 -269
  116. package/dist/core/bootstrap/provider-phase.js +0 -183
  117. package/dist/core/bootstrap/session-phase.js +0 -204
  118. package/dist/core/bootstrap/state.js +0 -94
  119. package/dist/core/bootstrap/workspace-phase.js +0 -115
  120. package/dist/core/bootstrap.js +0 -112
  121. package/dist/core/crash-handler.js +0 -62
  122. package/dist/core/environment.js +0 -224
  123. package/dist/core/host-plan.js +0 -43
  124. package/dist/core/host.js +0 -23
  125. package/dist/core/index.js +0 -3
  126. package/dist/core/prompt-builder.js +0 -95
  127. package/dist/core/prompt-overflow.js +0 -306
  128. package/dist/core/session-logger.js +0 -375
  129. package/dist/core/system-prompt.js +0 -117
  130. package/dist/core/types.js +0 -1
  131. package/dist/core/version.js +0 -28
  132. package/dist/core/web-host.js +0 -34
  133. package/dist/core/workspace.js +0 -80
  134. package/dist/i18n/index.js +0 -80
  135. package/dist/index.js +0 -30
  136. package/dist/llm/cache-usage.js +0 -76
  137. package/dist/llm/image-utils.js +0 -138
  138. package/dist/llm/index.js +0 -4
  139. package/dist/llm/llm-errors.js +0 -50
  140. package/dist/llm/model-loader.js +0 -86
  141. package/dist/llm/models-dev.js +0 -121
  142. package/dist/llm/openai-compat.js +0 -756
  143. package/dist/llm/orchestrator.js +0 -294
  144. package/dist/llm/provider-budget.js +0 -68
  145. package/dist/llm/provider-context.js +0 -233
  146. package/dist/llm/provider.js +0 -9
  147. package/dist/llm/response.js +0 -44
  148. package/dist/llm/stream-state.js +0 -26
  149. package/dist/llm/token-counter.js +0 -83
  150. package/dist/llm/types.js +0 -1
  151. package/dist/logger/app-logger.js +0 -212
  152. package/dist/logger/file-log.js +0 -160
  153. package/dist/logger/index.js +0 -2
  154. package/dist/migration/backup.js +0 -56
  155. package/dist/migration/detect.js +0 -61
  156. package/dist/migration/index.js +0 -3
  157. package/dist/modules/artifacts/store.js +0 -62
  158. package/dist/modules/browser/actions.js +0 -79
  159. package/dist/modules/browser/bridge-client.js +0 -203
  160. package/dist/modules/browser/bridge-path.js +0 -10
  161. package/dist/modules/browser/console-buffer.js +0 -37
  162. package/dist/modules/browser/cookie-store.js +0 -29
  163. package/dist/modules/browser/driver-types.js +0 -1
  164. package/dist/modules/browser/driver.js +0 -186
  165. package/dist/modules/browser/index.js +0 -9
  166. package/dist/modules/browser/module.js +0 -30
  167. package/dist/modules/browser/session.js +0 -322
  168. package/dist/modules/browser/snapshot.js +0 -154
  169. package/dist/modules/browser/types.js +0 -13
  170. package/dist/modules/certification/cli.js +0 -264
  171. package/dist/modules/certification/fact-checker.js +0 -90
  172. package/dist/modules/certification/loader.js +0 -127
  173. package/dist/modules/certification/manifest.js +0 -231
  174. package/dist/modules/certification/runner.js +0 -233
  175. package/dist/modules/certification/scenarios.js +0 -586
  176. package/dist/modules/certification/syntax-scenarios.js +0 -50
  177. package/dist/modules/certification/types.js +0 -1
  178. package/dist/modules/context/chunk-query.js +0 -122
  179. package/dist/modules/context/fact-extractor.js +0 -170
  180. package/dist/modules/context/history.js +0 -15
  181. package/dist/modules/context/index.js +0 -2
  182. package/dist/modules/context/manager.js +0 -482
  183. package/dist/modules/critic/client.js +0 -176
  184. package/dist/modules/critic/evidence.js +0 -81
  185. package/dist/modules/critic/provider.js +0 -45
  186. package/dist/modules/critic/tools.js +0 -78
  187. package/dist/modules/critic/types.js +0 -1
  188. package/dist/modules/execution/audit-runners.js +0 -206
  189. package/dist/modules/execution/auditor.js +0 -222
  190. package/dist/modules/execution/execution-plugin.js +0 -478
  191. package/dist/modules/execution/index.js +0 -8
  192. package/dist/modules/execution/input-from.js +0 -70
  193. package/dist/modules/execution/module.js +0 -891
  194. package/dist/modules/execution/moe-executor.js +0 -443
  195. package/dist/modules/execution/plan-actions.js +0 -642
  196. package/dist/modules/execution/plan-checks.js +0 -35
  197. package/dist/modules/execution/plan-coverage.js +0 -71
  198. package/dist/modules/execution/plan-persister.js +0 -46
  199. package/dist/modules/execution/plan-steps.js +0 -31
  200. package/dist/modules/execution/plan-store.js +0 -274
  201. package/dist/modules/execution/plan-tool.js +0 -249
  202. package/dist/modules/execution/plan-validator.js +0 -197
  203. package/dist/modules/execution/planner.js +0 -103
  204. package/dist/modules/execution/stuck-detector.js +0 -834
  205. package/dist/modules/execution/tracker.js +0 -92
  206. package/dist/modules/execution/transient-error.js +0 -30
  207. package/dist/modules/execution/types.js +0 -1
  208. package/dist/modules/execution/verifier.js +0 -343
  209. package/dist/modules/execution/windows-commands.js +0 -52
  210. package/dist/modules/hallucination/artifact-claim.js +0 -93
  211. package/dist/modules/hallucination/confidence.js +0 -81
  212. package/dist/modules/hallucination/consistency.js +0 -37
  213. package/dist/modules/hallucination/detector.js +0 -57
  214. package/dist/modules/hallucination/factual.js +0 -205
  215. package/dist/modules/hallucination/index.js +0 -6
  216. package/dist/modules/hallucination/js-identifiers.js +0 -261
  217. package/dist/modules/hallucination/llm-judge.js +0 -116
  218. package/dist/modules/hallucination/success-claim.js +0 -59
  219. package/dist/modules/index.js +0 -5
  220. package/dist/modules/indexer/cache.js +0 -88
  221. package/dist/modules/indexer/index.js +0 -4
  222. package/dist/modules/indexer/map-command.js +0 -33
  223. package/dist/modules/indexer/map-select.js +0 -87
  224. package/dist/modules/indexer/module.js +0 -293
  225. package/dist/modules/indexer/project-profile.js +0 -184
  226. package/dist/modules/indexer/symbols.js +0 -189
  227. package/dist/modules/indexer/walker.js +0 -285
  228. package/dist/modules/lsp/check-tool.js +0 -61
  229. package/dist/modules/lsp/client.js +0 -406
  230. package/dist/modules/lsp/command.js +0 -53
  231. package/dist/modules/lsp/config.js +0 -189
  232. package/dist/modules/lsp/index.js +0 -4
  233. package/dist/modules/lsp/module.js +0 -295
  234. package/dist/modules/lsp/probe.js +0 -86
  235. package/dist/modules/lsp/project-root.js +0 -69
  236. package/dist/modules/lsp/startup-check.js +0 -154
  237. package/dist/modules/lsp/types.js +0 -1
  238. package/dist/modules/mcp/client.js +0 -445
  239. package/dist/modules/mcp/index.js +0 -4
  240. package/dist/modules/mcp/module.js +0 -295
  241. package/dist/modules/mcp/registry.js +0 -20
  242. package/dist/modules/memory/index.js +0 -2
  243. package/dist/modules/memory/module.js +0 -101
  244. package/dist/modules/memory/search.js +0 -45
  245. package/dist/modules/memory/store.js +0 -107
  246. package/dist/modules/onboarding/init.js +0 -47
  247. package/dist/modules/onboarding/meet.js +0 -30
  248. package/dist/modules/onboarding/mode.js +0 -30
  249. package/dist/modules/onboarding/prompts.js +0 -43
  250. package/dist/modules/pipelines/engine.js +0 -70
  251. package/dist/modules/pipelines/index.js +0 -4
  252. package/dist/modules/pipelines/parser.js +0 -59
  253. package/dist/modules/pipelines/template.js +0 -18
  254. package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
  255. package/dist/modules/plugins/builtin/notify.js +0 -11
  256. package/dist/modules/plugins/index.js +0 -2
  257. package/dist/modules/plugins/loader.js +0 -85
  258. package/dist/modules/plugins/manager.js +0 -244
  259. package/dist/modules/plugins/types.js +0 -1
  260. package/dist/modules/pricing/index.js +0 -70
  261. package/dist/modules/pricing/prices.js +0 -147
  262. package/dist/modules/processes/detect.js +0 -34
  263. package/dist/modules/processes/index.js +0 -4
  264. package/dist/modules/processes/kill-tree.js +0 -52
  265. package/dist/modules/processes/registry.js +0 -294
  266. package/dist/modules/processes/runner.js +0 -23
  267. package/dist/modules/providers/cache.js +0 -23
  268. package/dist/modules/providers/capabilities.js +0 -28
  269. package/dist/modules/providers/create.js +0 -21
  270. package/dist/modules/providers/factory.js +0 -28
  271. package/dist/modules/providers/fallback.js +0 -81
  272. package/dist/modules/providers/health.js +0 -47
  273. package/dist/modules/providers/index.js +0 -6
  274. package/dist/modules/providers/manager.js +0 -207
  275. package/dist/modules/providers/naming.js +0 -15
  276. package/dist/modules/providers/preflight.js +0 -71
  277. package/dist/modules/providers/presets.js +0 -232
  278. package/dist/modules/providers/registry.js +0 -27
  279. package/dist/modules/providers/types.js +0 -1
  280. package/dist/modules/reasoning/policy.js +0 -42
  281. package/dist/modules/reasoning/probe.js +0 -116
  282. package/dist/modules/registry.js +0 -48
  283. package/dist/modules/security/audit-log.js +0 -119
  284. package/dist/modules/security/audit-notifier.js +0 -267
  285. package/dist/modules/security/command-validator.js +0 -272
  286. package/dist/modules/security/content-scanner.js +0 -53
  287. package/dist/modules/security/data-sanitizer.js +0 -82
  288. package/dist/modules/security/encryption.js +0 -216
  289. package/dist/modules/security/index.js +0 -15
  290. package/dist/modules/security/network-validator.js +0 -236
  291. package/dist/modules/security/path-validator.js +0 -261
  292. package/dist/modules/security/protected-paths.js +0 -73
  293. package/dist/modules/security/rate-limiter.js +0 -98
  294. package/dist/modules/security/security-policies.js +0 -499
  295. package/dist/modules/security/session-encryption.js +0 -182
  296. package/dist/modules/security/session-isolation.js +0 -75
  297. package/dist/modules/session/auto-name.js +0 -63
  298. package/dist/modules/session/export.js +0 -98
  299. package/dist/modules/session/index.js +0 -4
  300. package/dist/modules/session/manager.js +0 -234
  301. package/dist/modules/session/module.js +0 -33
  302. package/dist/modules/session/store.js +0 -295
  303. package/dist/modules/session/types.js +0 -1
  304. package/dist/modules/setup/answers.js +0 -35
  305. package/dist/modules/setup/apply.js +0 -79
  306. package/dist/modules/setup/discovery.js +0 -72
  307. package/dist/modules/skills/index.js +0 -3
  308. package/dist/modules/skills/loader.js +0 -86
  309. package/dist/modules/skills/matcher.js +0 -27
  310. package/dist/modules/skills/module.js +0 -141
  311. package/dist/modules/types.js +0 -1
  312. package/dist/modules/updater/changelog-reader.js +0 -93
  313. package/dist/modules/updater/checker.js +0 -101
  314. package/dist/modules/updater/dev-detect.js +0 -18
  315. package/dist/modules/updater/index.js +0 -4
  316. package/dist/modules/updater/module.js +0 -129
  317. package/dist/modules/user-profile/compressor.js +0 -18
  318. package/dist/modules/user-profile/index.js +0 -2
  319. package/dist/modules/user-profile/profile.js +0 -77
  320. package/dist/modules/webui/answer-api.js +0 -29
  321. package/dist/modules/webui/assets.js +0 -50
  322. package/dist/modules/webui/body.js +0 -21
  323. package/dist/modules/webui/commands-settings.js +0 -209
  324. package/dist/modules/webui/commands-shared.js +0 -60
  325. package/dist/modules/webui/commands.js +0 -174
  326. package/dist/modules/webui/config-api.js +0 -134
  327. package/dist/modules/webui/file-api.js +0 -202
  328. package/dist/modules/webui/forward.js +0 -159
  329. package/dist/modules/webui/history-api.js +0 -112
  330. package/dist/modules/webui/host-state.js +0 -60
  331. package/dist/modules/webui/i18n-api.js +0 -9
  332. package/dist/modules/webui/limits.js +0 -6
  333. package/dist/modules/webui/log-forward.js +0 -20
  334. package/dist/modules/webui/memory-commands.js +0 -64
  335. package/dist/modules/webui/model-cache.js +0 -18
  336. package/dist/modules/webui/module.js +0 -148
  337. package/dist/modules/webui/persist.js +0 -24
  338. package/dist/modules/webui/prompt-io.js +0 -97
  339. package/dist/modules/webui/providers-api.js +0 -337
  340. package/dist/modules/webui/providers-view.js +0 -56
  341. package/dist/modules/webui/reasoning-view.js +0 -15
  342. package/dist/modules/webui/run-agent.js +0 -81
  343. package/dist/modules/webui/server.js +0 -284
  344. package/dist/modules/webui/sessions-api.js +0 -178
  345. package/dist/modules/webui/settings-api.js +0 -561
  346. package/dist/modules/webui/setup-api.js +0 -201
  347. package/dist/modules/webui/state.js +0 -74
  348. package/dist/modules/webui/util.js +0 -261
  349. package/dist/output/bus.js +0 -37
  350. package/dist/output/channel.js +0 -285
  351. package/dist/output/format.js +0 -15
  352. package/dist/output/index.js +0 -8
  353. package/dist/output/json-sink.js +0 -24
  354. package/dist/output/machine.js +0 -9
  355. package/dist/output/session-sink.js +0 -27
  356. package/dist/output/types.js +0 -1
  357. package/dist/tools/approve.js +0 -41
  358. package/dist/tools/attach-image.js +0 -102
  359. package/dist/tools/auto-fixer.js +0 -180
  360. package/dist/tools/auto-tags.js +0 -83
  361. package/dist/tools/bash.js +0 -649
  362. package/dist/tools/browser.js +0 -160
  363. package/dist/tools/chunk-query.js +0 -112
  364. package/dist/tools/create-dir.js +0 -60
  365. package/dist/tools/delete-file.js +0 -67
  366. package/dist/tools/download-file.js +0 -142
  367. package/dist/tools/edit-file.js +0 -136
  368. package/dist/tools/enable-tools.js +0 -59
  369. package/dist/tools/executor.js +0 -304
  370. package/dist/tools/file-info.js +0 -49
  371. package/dist/tools/filter-tools.js +0 -17
  372. package/dist/tools/glob-tool.js +0 -37
  373. package/dist/tools/grep-tool.js +0 -142
  374. package/dist/tools/hidden-tools-block.js +0 -37
  375. package/dist/tools/index.js +0 -88
  376. package/dist/tools/list-dir.js +0 -53
  377. package/dist/tools/load-skill.js +0 -51
  378. package/dist/tools/mcp-call.js +0 -80
  379. package/dist/tools/meet-done.js +0 -14
  380. package/dist/tools/move-file.js +0 -94
  381. package/dist/tools/path-utils.js +0 -108
  382. package/dist/tools/pipeline-run.js +0 -167
  383. package/dist/tools/preview.js +0 -2
  384. package/dist/tools/process-ids.js +0 -31
  385. package/dist/tools/process-kill.js +0 -33
  386. package/dist/tools/process-list.js +0 -38
  387. package/dist/tools/process-log.js +0 -49
  388. package/dist/tools/process-wait.js +0 -63
  389. package/dist/tools/prompt-io.js +0 -29
  390. package/dist/tools/question.js +0 -288
  391. package/dist/tools/read-file.js +0 -200
  392. package/dist/tools/recall.js +0 -124
  393. package/dist/tools/registry.js +0 -48
  394. package/dist/tools/remember.js +0 -72
  395. package/dist/tools/scope-check.js +0 -32
  396. package/dist/tools/scope-request.js +0 -96
  397. package/dist/tools/search-history.js +0 -88
  398. package/dist/tools/session-info.js +0 -46
  399. package/dist/tools/set-thinking.js +0 -75
  400. package/dist/tools/subagent.js +0 -257
  401. package/dist/tools/syntax-validator.js +0 -379
  402. package/dist/tools/types.js +0 -1
  403. package/dist/tools/user-input.js +0 -134
  404. package/dist/tools/web-browse.js +0 -93
  405. package/dist/tools/web-fetch.js +0 -108
  406. package/dist/tools/web-search.js +0 -107
  407. package/dist/tools/write-file.js +0 -128
  408. package/dist/ui/action-log.js +0 -13
  409. package/dist/ui/box.js +0 -78
  410. package/dist/ui/colors.js +0 -6
  411. package/dist/ui/diff.js +0 -189
  412. package/dist/ui/index.js +0 -7
  413. package/dist/ui/line-editor.js +0 -1083
  414. package/dist/ui/line-math.js +0 -98
  415. package/dist/ui/md-formatter.js +0 -322
  416. package/dist/ui/output.js +0 -19
  417. package/dist/ui/plan-view.js +0 -109
  418. package/dist/ui/renderer.js +0 -589
  419. package/dist/ui/spinner.js +0 -91
  420. package/dist/ui/table.js +0 -142
  421. package/dist/ui/theme.js +0 -143
  422. package/dist/utils/child-run.js +0 -20
  423. package/dist/utils/error.js +0 -4
  424. package/dist/utils/index.js +0 -6
  425. package/dist/utils/retry.js +0 -17
  426. package/dist/utils/sleep.js +0 -23
  427. package/dist/utils/truncate.js +0 -9
@@ -1,1299 +0,0 @@
1
- import { ProviderManager, activeProviderEndpoint, reasoningStrategyForProvider } from "../modules/providers/manager";
2
- import { buildActiveProvider } from "../modules/providers/factory";
3
- import { t } from "../i18n/index";
4
- import { errMsg } from "../utils";
5
- import { resolveVerbosity } from "../ui/theme";
6
- import { join } from "path";
7
- import { projectMmaDir } from "../config/paths";
8
- import { PromptBuilder } from "./prompt-builder";
9
- import { processRegistry } from "../modules/processes";
10
- import { SessionLogger } from "./session-logger";
11
- import { runWithMoE } from "./agent-moe";
12
- import { CostTracker } from "../modules/pricing/index";
13
- import { createPolicyState } from "../modules/reasoning/policy";
14
- import { estimateTokens } from "../llm/token-counter";
15
- import { LlmError } from "../llm/llm-errors";
16
- import { createLoopState, clearIterationSignals } from "./agent/loop-state";
17
- import { CompactionService } from "./agent/compaction";
18
- import { TokenTracker } from "./agent/token-tracker";
19
- import { buildPromptSnapshot, diffPrompt } from "./agent/prefix-monitor";
20
- import { recordArtifactWrite, testChurnFile } from "./agent/artifact-progress";
21
- import { ReasoningEffortResolver } from "./agent/reasoning-resolver";
22
- import { repairToolCallPairing } from "./agent/tool-pairing";
23
- import { ContextRenderer } from "./agent/context-renderer";
24
- import { ToolBatchExecutor } from "./agent/tool-batch";
25
- import { HallucinationGate } from "./agent/hallucination-gate";
26
- import { resolveArtifactStates } from "./agent/artifact-resolve";
27
- import { AuditGate } from "./agent/audit-gate";
28
- import { CriticGate, gatherTurnToolEvents } from "./agent/critic-gate";
29
- import { seedToolTags } from "../tools/auto-tags";
30
- import { CriticClient } from "../modules/critic/client";
31
- import { buildCriticProvider } from "../modules/critic/provider";
32
- import { CRITIC_READONLY_TOOLS } from "../modules/critic/tools";
33
- import { buildEvidencePack, isWorkMutation } from "../modules/critic/evidence";
34
- import { deriveSessionName, generateSessionTitle, } from "../modules/session/auto-name";
35
- import { resolvePromptOverflow, overflowHint, HINT_BLOCK_TOKENS, } from "./prompt-overflow";
36
- import { MAX_EMPTY_RESPONSE_RETRIES, MAX_LLM_ERROR_RETRIES, MAX_REPEATED_TOOL_CALLS, MAX_REPEATED_TOOL_CALLS_INTERACTIVE, MUTATION_CYCLE_NUDGE_THRESHOLD, } from "./agent/constants";
37
- export { toolOutputCharLimit, compactToolError } from "./agent/tool-output";
38
- /**
39
- * Read-only тулты-интроспекции. Повтор одного с теми же аргументами прогресса
40
- * не даст никогда, поэтому интерактивный цикл жёстко стопает после нескольких
41
- * повторов (наблюдалось: `session_info` в тугом цикле после готовой задачи).
42
- * Мутирующие — только nudge, чтобы легитимный edit/build/read-цикл не резать.
43
- */
44
- const LOOP_PRONE_STATUS_TOOLS = new Set([
45
- "session_info",
46
- "process_list",
47
- "process_log",
48
- "file_info",
49
- "project_map",
50
- ]);
51
- function probeIdentityOf(config) {
52
- return {
53
- model: config.model,
54
- baseUrl: activeProviderEndpoint(config.provider).baseUrl,
55
- active: config.provider?.active,
56
- };
57
- }
58
- /**
59
- * Ключ целевого объекта мутации для детекции «перфекционизм-цикла».
60
- * edit_file/write_file → путь файла; bash → нормализованная команда.
61
- * Остальные тула (read-only, bookkeeping) не считаются мутациями.
62
- * Внимание: в цикле toolCall.arguments — сырая JSON-строка от провайдера.
63
- */
64
- export function mutationTargetKey(name, rawArgs) {
65
- let a = null;
66
- if (typeof rawArgs === "string") {
67
- try {
68
- a = JSON.parse(rawArgs);
69
- }
70
- catch {
71
- return null;
72
- }
73
- }
74
- else if (rawArgs && typeof rawArgs === "object") {
75
- a = rawArgs;
76
- }
77
- if (!a)
78
- return null;
79
- if ((name === "edit_file" || name === "write_file") && typeof a.path === "string") {
80
- return `write:${a.path.replace(/\\/g, "/").toLowerCase()}`;
81
- }
82
- if (name === "bash" && typeof a.command === "string") {
83
- const cmd = a.command.replace(/\s+/g, " ").trim().slice(0, 80).toLowerCase();
84
- return cmd ? `bash:${cmd}` : null;
85
- }
86
- // Bookkeeping-churn: повторы plan/todo над тем же действием с чуть разными
87
- // аргументами сожгли 10+ итераций в ses_mthn3c2a после отклонения аудита
88
- // (модель «чинила» план вместо работы).
89
- if ((name === "plan" || name === "todo") && typeof a.action === "string") {
90
- return `book:${name}:${a.action.toLowerCase()}`;
91
- }
92
- // У plan_step нет action — ключ по status, чтобы повторные апдейты
93
- // били в тот же гард подряд-дубликатов.
94
- if (name === "plan_step" && typeof a.status === "string") {
95
- return `book:${name}:${a.status.toLowerCase()}`;
96
- }
97
- return null;
98
- }
99
- /**
100
- * Агент: владеет циклом, конфигом, стоимостью, гейтами. Состояние хода —
101
- * в LoopState, шаги — в extracted-модулях.
102
- */
103
- export class Agent {
104
- deps;
105
- systemPromptAdded = false;
106
- shutdownRequested = false;
107
- abortController = null;
108
- costTracker;
109
- /** Рендер UI-индикаторов контекста (stats-бар/компакция). Экземпляр
110
- * переживает несколько запусков цикла — хранит lastCompactionShown. */
111
- contextRenderer;
112
- policyState;
113
- /** Итог reasoning-пробы (B2): старт из deps, лениво обновляется в run(). */
114
- probePassed;
115
- /** Общее мутабельное reasoning-состояние (владелец — агент, делит с set_thinking). */
116
- reasoningState;
117
- /** Счётчик текущей итерации (для кулдаунов тулов). */
118
- _currentIteration = 0;
119
- /** Kind → блок-замена для instructions/project-map сверх бюджета (резолв раз до первого LLM-вызова). */
120
- promptOverrides = new Map();
121
- /** Идентичность, против которой гонялась проба. Сравнение ПО ЗНАЧЕНИЮ: все
122
- * write-пути мутят живой конфиг in place и передают ТОТ ЖЕ объект в
123
- * `reconfigure` — сравнение объектов изменения бы не увидело. Сидируется
124
- * в конструкторе (строки до мутаций). */
125
- lastProbeIdentity;
126
- /** Малый всегда-включённый блок про сжатые инструкции. */
127
- overflowHintBlock = null;
128
- /** Гард одноразового асинхронного резолва переполнения в run(). */
129
- overflowResolved = false;
130
- constructor(deps) {
131
- this.deps = deps;
132
- this.costTracker = new CostTracker(deps.config.model, deps.config.pricing);
133
- this.contextRenderer = new ContextRenderer();
134
- this.policyState = createPolicyState();
135
- this.probePassed = deps.reasoningProbePassed ?? false;
136
- this.lastProbeIdentity = probeIdentityOf(deps.config);
137
- const initialLevel = (deps.config.reasoning?.mode !== "auto" ? deps.config.reasoning?.mode : undefined) ?? "medium";
138
- this.reasoningState = { level: initialLevel, overrideIteration: -100 };
139
- }
140
- /** Менеджер контекста наружу (аттач картинок в REPL и др. прямой доступ). */
141
- get contextManager() {
142
- return this.deps.contextManager;
143
- }
144
- /** Текущий эффективный уровень reasoning. */
145
- get reasoningLevel() {
146
- return this.reasoningState.level;
147
- }
148
- /** Ставит уровень reasoning (REPL/CLI). "auto" снимает ручной override. */
149
- setReasoningLevel(level) {
150
- if (level === "auto") {
151
- this.reasoningState.manual = false;
152
- return;
153
- }
154
- this.reasoningState.level = level;
155
- this.reasoningState.manual = true;
156
- }
157
- /** Номер текущей итерации (для кулдаунов тулов). */
158
- get currentIteration() {
159
- return this._currentIteration;
160
- }
161
- /**
162
- * Провенанс вызывающего: живой провайдер и модель (для стоимости/логов).
163
- * @returns {provider, model} реально обслужившие вызов.
164
- */
165
- callerProvenance() {
166
- // Сначала живое имя: sticky failover переключает активную запись
167
- // менеджера, не трогая config.provider.active.
168
- const live = this.deps.getProviderName?.();
169
- if (live)
170
- return { provider: live, model: this.deps.config.model };
171
- const p = this.deps.config.provider;
172
- let providerName = p?.type || "unknown";
173
- // При записях провайдера — метка активной, чтобы провенанс стоимости
174
- // различался (напр. "lmstudio" vs "ollama").
175
- if (p?.entries && p.entries.length > 0) {
176
- const entry = p.active
177
- ? p.entries.find((e) => e.label === p.active || e.type === p.active)
178
- : p.entries[0];
179
- if (entry)
180
- providerName = entry.label || entry.type || providerName;
181
- }
182
- return { provider: providerName, model: this.deps.config.model };
183
- }
184
- /** Типизированный доступ к модулям для CLI/REPL (напр. /lsp). */
185
- getModule(name) {
186
- return this.deps.moduleRegistry?.get(name);
187
- }
188
- /**
189
- * Запуск тула по имени из CLI/REPL (например, lsp_check). Обходит
190
- * инкапсуляцию: команды не должны лезть в приватные `deps`.
191
- */
192
- runTool(name, args) {
193
- return this.deps.toolExecutor.executeByName(name, args);
194
- }
195
- setScope() {
196
- if (this.deps.scope) {
197
- this.deps.toolExecutor.setScope(this.deps.scope);
198
- }
199
- }
200
- /**
201
- * Ставит (или снимает) терминальную prompt-поверхность для интерактивных
202
- * тулов. Делегирует исполнителю, чтобы общий ToolContext — тот, что реально
203
- * получают тулы — нёс тот же инстанс.
204
- */
205
- setPromptIO(io) {
206
- this.deps.toolExecutor.setPromptIO(io);
207
- }
208
- /**
209
- * Дописывает системный блок на текущий прогон (гайд онбординга).
210
- * Удаление по идентичности через `removePromptBlock`. Блоки читаются при
211
- * каждой `buildSystemPrompt`, эффект — со следующего хода.
212
- */
213
- pushPromptBlock(block) {
214
- this.deps.promptBlocks.push(block);
215
- }
216
- /** Убирает ранее запушенный блок (по ссылке). */
217
- removePromptBlock(block) {
218
- const i = this.deps.promptBlocks.indexOf(block);
219
- if (i >= 0)
220
- this.deps.promptBlocks.splice(i, 1);
221
- }
222
- /** Тестовый вид текущих блоков промпта. */
223
- getPromptBlocksForTest() {
224
- return this.deps.promptBlocks;
225
- }
226
- /** Общий ToolContext (бросает, если bootstrap его не дал). */
227
- get toolContext() {
228
- if (!this.deps.toolCtx)
229
- throw new Error("toolCtx not provided to Agent");
230
- return this.deps.toolCtx;
231
- }
232
- /** Небросающийся вариант `toolContext` для опциональных/lifecycle-проверок. */
233
- tryToolContext() {
234
- return this.deps.toolCtx;
235
- }
236
- buildSystemPrompt() {
237
- const systemBudget = Math.floor(this.deps.config.contextWindow * this.deps.config.contextBudget.systemPrompt);
238
- const builder = new PromptBuilder(systemBudget);
239
- // Замены переполнения (сжатые инструкции/карта) приоритетнее исходных
240
- // негабаритных блоков.
241
- const applyOverrides = (blocks) => blocks.map((b) => (b.kind && this.promptOverrides.has(b.kind) ? this.promptOverrides.get(b.kind) : b));
242
- builder.addBlocks(applyOverrides(this.deps.promptBlocks));
243
- const dynamic = this.deps.getDynamicPromptBlocks?.() ?? [];
244
- if (dynamic.length > 0) {
245
- builder.addBlocks(applyOverrides(dynamic));
246
- }
247
- const pluginBlocks = (this.deps.pluginManager.runOnBuildPrompt?.() ?? []).flatMap((content) => content && content.trim() !== ""
248
- ? [
249
- {
250
- content,
251
- priority: "low",
252
- essential: false,
253
- estimatedTokens: this.deps.llmProvider.countTokens(content),
254
- },
255
- ]
256
- : []);
257
- if (pluginBlocks.length > 0) {
258
- builder.addBlocks(pluginBlocks);
259
- }
260
- if (this.overflowHintBlock) {
261
- builder.addBlock(this.overflowHintBlock);
262
- }
263
- const result = builder.build();
264
- return {
265
- prompt: result.prompt,
266
- excluded: result.excluded,
267
- excludedBlocks: result.excludedBlocks,
268
- blocks: result.blocks,
269
- };
270
- }
271
- /**
272
- * Пишет состояние контекста в лог сессии. Каждую итерацию — полный таймлайн
273
- * роста/компакции/состава; `start`-снимок дополнительно несёт разбивку
274
- * системных блоков (что, приоритеты, оценки, что срезал бюджет).
275
- */
276
- logContextStat(kind, iteration, slog, blocks) {
277
- const cm = this.deps.contextManager;
278
- if (typeof cm.getSnapshot !== "function")
279
- return;
280
- const snap = cm.getSnapshot();
281
- const history = cm.getActiveHistory();
282
- const systemMsg = history.find((m) => m.role === "system");
283
- slog.logContext({
284
- kind,
285
- iteration,
286
- window: snap.window,
287
- systemBudget: snap.budget.systemPrompt,
288
- reserveBudget: snap.budget.responseReserve,
289
- historyBudget: snap.budget.history,
290
- systemTokens: kind === "start" && systemMsg && typeof systemMsg.content === "string"
291
- ? this.deps.llmProvider.countTokens(systemMsg.content)
292
- : undefined,
293
- toolTokens: snap.toolTokens,
294
- tokens: snap.tokens,
295
- quality: snap.quality,
296
- messageCount: snap.messageCount,
297
- compactionCount: snap.compactionCount,
298
- iterationsSinceCompaction: snap.iterationsSinceCompaction,
299
- blocks,
300
- });
301
- }
302
- /**
303
- * Текущий системный промпт с подсчётом токенов (для /sysprompt, диагностики).
304
- * @returns текст, число токенов, исключённые блоки.
305
- */
306
- getSystemPromptInfo() {
307
- const { prompt, excluded } = this.buildSystemPrompt();
308
- const tokenCount = this.deps.llmProvider.countTokens(prompt);
309
- return { text: prompt, tokenCount, excluded };
310
- }
311
- /**
312
- * Одноразовая (первый прогон) обработка instructions/project-map сверх
313
- * бюджета: LLM суммаризирует в остаток (диск-кэш) или усекает, а малый
314
- * hint-блок говорит модели что случилось и как читать полный источник.
315
- */
316
- async resolvePromptOverflowOnce() {
317
- const cfg = this.deps.config;
318
- const dry = this.buildSystemPrompt();
319
- const overflow = dry.excludedBlocks.filter((b) => Boolean(b.kind));
320
- if (overflow.length === 0)
321
- return;
322
- const systemBudget = Math.floor(cfg.contextWindow * cfg.contextBudget.systemPrompt);
323
- const res = await resolvePromptOverflow({
324
- overflow,
325
- includedTokens: dry.blocks.filter((b) => b.included).reduce((s, b) => s + b.tokens, 0),
326
- systemBudget,
327
- provider: cfg.instructions?.summarize === false ? null : this.deps.llmProvider,
328
- cacheDir: join(projectMmaDir(this.deps.baseDir), "cache", "prompt-summaries"),
329
- logger: this.deps.logger,
330
- });
331
- for (const r of res.replacements) {
332
- if (r.kind)
333
- this.promptOverrides.set(r.kind, r);
334
- }
335
- this.overflowHintBlock = res.hintBlock;
336
- const needed = dry.blocks.filter((b) => b.included).reduce((s, b) => s + b.tokens, 0) +
337
- overflow.reduce((s, b) => s + b.estimatedTokens, 0) +
338
- HINT_BLOCK_TOKENS;
339
- const hint = overflowHint(cfg, this.deps.configDir, needed);
340
- for (const w of res.warnings) {
341
- this.deps.logger.warn(t("prompt.overflow.exceeded", {
342
- label: w.label,
343
- original: String(w.originalTokens),
344
- needed: String(needed),
345
- budget: String(systemBudget),
346
- mode: w.mode === "summary" ? "summarized" : "truncated",
347
- resolved: String(w.resolvedTokens),
348
- hint,
349
- }));
350
- }
351
- }
352
- refreshSystemPrompt() {
353
- const { prompt } = this.buildSystemPrompt();
354
- const current = this.deps.contextManager.getActiveHistory().find((m) => m.role === "system");
355
- if (!current || current.content !== prompt) {
356
- this.deps.contextManager.updateSystemPrompt?.(prompt);
357
- }
358
- }
359
- emitPhase(iteration, phase, onPhase) {
360
- this.deps.pluginManager.runOnPhase?.({
361
- iteration,
362
- logger: this.deps.logger,
363
- contextManager: this.deps.contextManager,
364
- }, phase);
365
- onPhase?.(phase);
366
- }
367
- /**
368
- * Прогоняет ход агента: MoE-путь или одиночный цикл, с автонеймингом сессии.
369
- * @param input сообщение пользователя; @param onChunk/onMeta/onTool/onPhase/onReasoning колбэки вывода;
370
- * @returns итог прогона.
371
- */
372
- async run(input, onChunk, onMeta, onTool, onPhase, onReasoning) {
373
- // Сброс флага shutdown от прошлого прерывания
374
- this.shutdownRequested = false;
375
- // Свежий контроллер на ход — MoE-путь полагается на него для Esc
376
- // (executeSingleAgentLoop пересоздаёт его же для одиночного пути).
377
- this.abortController = new AbortController();
378
- this.setScope();
379
- // P5 автосужение: детерминированно пред-посеваем теги тулов из сырого
380
- // сообщения ДО первого LLM-вызова (системный промпт + схемы тулов).
381
- // Только добавление — mid-session гранты enable_tools не снимаются.
382
- // Пропуск при выключенном сужении (enableOnDemand: false → все тулы).
383
- if (this.deps.config.tools?.enableOnDemand !== false && this.deps.toolTags) {
384
- const added = seedToolTags(this.deps.toolTags, input);
385
- if (added.length > 0) {
386
- this.deps.logger.debug(`Auto-narrowing: pre-seeded tool tags [${added.join(", ")}]`);
387
- }
388
- }
389
- const { config, llmProvider, toolExecutor, pluginManager, contextManager, logger, sessionManager, } = this.deps;
390
- const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
391
- if (sessionManager && !sessionManager.getActive()) {
392
- sessionManager.create();
393
- logger.debug(`Session started: ${sessionManager.getActive()}`);
394
- }
395
- const activeMeta = sessionManager?.getActiveMeta();
396
- const isFirstMessage = !!activeMeta && activeMeta.messageCount === 0;
397
- if (!this.systemPromptAdded &&
398
- !contextManager.getActiveHistory().some((m) => m.role === "system")) {
399
- // Стартовую диагностику сессии (окружение, плагины, скиллы) — первой
400
- // записью в session.jsonl, чтобы файл был самоописываем для пост-анализа.
401
- // LSP-проба логируется отдельно из repl.ts (может завершиться позже).
402
- // Дедуп: logSessionStart проверяет существующий session_start от дублей.
403
- slog.logSessionStart({
404
- environment: this.deps.envReport,
405
- });
406
- const lazy = this.deps.lazyPromptBlocks ? await this.deps.lazyPromptBlocks() : [];
407
- if (lazy.length > 0) {
408
- this.deps.promptBlocks.push(...lazy);
409
- }
410
- // Одноразовый резолв instructions/project-map сверх бюджета: суммаризация
411
- // (кэш) или усечение ДО первой сборки, чтобы модель инструкции получила.
412
- if (!this.overflowResolved) {
413
- this.overflowResolved = true;
414
- try {
415
- await this.resolvePromptOverflowOnce();
416
- }
417
- catch (err) {
418
- this.deps.logger.warn(t("prompt.overflow.failed", { error: String(err?.message ?? err) }));
419
- }
420
- }
421
- // B2: ждём фоновую reasoning-пробу рядом с ленивыми блоками, чтобы ПЕРВЫЙ
422
- // LLM-вызов уже знал, держит ли бэкенд механизм (bootstrap больше не ждёт).
423
- if (this.deps.reasoningProbe) {
424
- this.probePassed = await this.deps.reasoningProbe();
425
- }
426
- const { prompt: systemPrompt, excluded } = this.buildSystemPrompt();
427
- contextManager.addMessage({ role: "system", content: systemPrompt });
428
- this.systemPromptAdded = true;
429
- slog.logSystem(systemPrompt.slice(0, 2000));
430
- if (excluded.length > 0) {
431
- slog.logSystem(`[Excluded prompt blocks: ${excluded.length}]`);
432
- }
433
- pluginManager.runOnSessionStart({
434
- logger,
435
- sessionManager: sessionManager?.getActiveMeta(),
436
- contextManager,
437
- });
438
- }
439
- // Новое сообщение — новый интервал компакции: итерации прошлого хода не
440
- // должны триггерить компакцию на итерации 1 этого (наблюдалось: ход 2
441
- // скомпактился на итерации 8, снеся только что присланную задачу с 40+ ходами).
442
- if (typeof contextManager.resetUserTurn === "function") {
443
- contextManager.resetUserTurn();
444
- }
445
- contextManager.addMessage({ role: "user", content: input });
446
- slog.logUser(input);
447
- if (isFirstMessage && activeMeta && sessionManager) {
448
- const meta = sessionManager.getActiveMeta();
449
- if (meta && (meta.nameSource ?? "default") !== "manual") {
450
- sessionManager.rename(meta.id, deriveSessionName(input), "auto");
451
- if (config.session.autoName !== false) {
452
- const chat = async (prompt, signal) => {
453
- let text = "";
454
- for await (const chunk of llmProvider.chat([{ role: "user", content: prompt }], undefined, signal, { maxTokens: 48, reasoningEffort: "none" })) {
455
- if (chunk.type === "text" && chunk.content)
456
- text += chunk.content;
457
- }
458
- return text;
459
- };
460
- void generateSessionTitle(input, chat)
461
- .then((title) => {
462
- if (!title)
463
- return;
464
- const cur = sessionManager.getActiveMeta();
465
- if (cur && cur.id === meta.id && (cur.nameSource ?? "default") === "auto") {
466
- sessionManager.rename(meta.id, title, "auto");
467
- }
468
- })
469
- .catch(() => { });
470
- }
471
- }
472
- }
473
- // Жизненный цикл хода для плагинов: считаем тул-события вне цикла,
474
- // onTurnEnd — ровно один раз (успех, ошибка или прерывание).
475
- const startedAt = Date.now();
476
- let toolCalls = 0;
477
- const countTool = (ev) => {
478
- if (ev.type === "end")
479
- toolCalls++;
480
- onTool?.(ev);
481
- };
482
- const emitTurnEnd = (result, err) => {
483
- const interrupted = (err instanceof Error && err.name === "AbortError") || this.shutdownRequested;
484
- pluginManager.runOnTurnEnd?.({
485
- logger,
486
- sessionManager: sessionManager?.getActiveMeta(),
487
- contextManager,
488
- }, {
489
- success: !!result && result.success !== false && !err,
490
- durationMs: Date.now() - startedAt,
491
- textLength: (result?.text || "").length,
492
- toolCalls,
493
- interrupted: interrupted || undefined,
494
- });
495
- };
496
- try {
497
- const moeEnabled = config.moe?.enabled === true;
498
- const moeRunner = this.deps.runWithMoEOverride ?? runWithMoE;
499
- const result = moeEnabled
500
- ? await moeRunner({
501
- config,
502
- llmProvider,
503
- toolExecutor,
504
- logger,
505
- baseDir: this.deps.baseDir,
506
- }, input, () => this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning), {
507
- onMeta,
508
- onTool: countTool,
509
- onPhase,
510
- signal: this.abortController.signal,
511
- onEvent: (event) => slog.logMoE(event.type, event.data),
512
- sessionId: this.deps.sessionManager?.getActiveMeta()?.id,
513
- })
514
- : await this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning);
515
- // В MoE-режиме одиночный цикл (единственный писатель ходов ассистента)
516
- // не бежит — без этого история схлопнется user→user между ходами, а в
517
- // session.jsonl не будет ответа. Пишем итог один раз и только когда MoE
518
- // реально вёл ход (фолбэк уже записал сам).
519
- if (moeEnabled && result.moeHandled) {
520
- if (result.text) {
521
- contextManager.addMessage({ role: "assistant", content: result.text });
522
- if (config.session.autoSave) {
523
- slog.saveAssistantMessage(result.text);
524
- }
525
- slog.logAssistant(result.text, "", undefined, 0);
526
- }
527
- if (!result.success && result.error) {
528
- slog.logError(result.error);
529
- }
530
- }
531
- const provenance = this.callerProvenance();
532
- result.provider = provenance.provider;
533
- result.model = provenance.model;
534
- result.durationMs = Date.now() - startedAt;
535
- emitTurnEnd(result);
536
- return result;
537
- }
538
- catch (err) {
539
- emitTurnEnd(null, err);
540
- throw err;
541
- }
542
- }
543
- async executeSingleAgentLoop(input, slog, onChunk, onMeta, onTool, onPhase, onReasoning) {
544
- const { config, llmProvider, toolExecutor, pluginManager, contextManager, hallucinationDetector, logger, } = this.deps;
545
- this.abortController = new AbortController();
546
- // Единый контейнер состояния цикла — заменяет ~24 разрозненных `let`.
547
- const state = createLoopState();
548
- // Модули цикла: каждый инкапсулирует одну ответственность и получает
549
- // зависимости через конструктор (никаких глобальных синглтонов).
550
- const compactionService = new CompactionService({
551
- contextManager,
552
- logger,
553
- slog,
554
- hallucinationDetector,
555
- });
556
- const tokenTracker = new TokenTracker(this.costTracker, () => this.callerProvenance());
557
- const reasoningResolver = new ReasoningEffortResolver();
558
- const toolBatch = new ToolBatchExecutor({
559
- config,
560
- toolExecutor,
561
- pluginManager,
562
- contextManager,
563
- hallucinationDetector,
564
- compactionService,
565
- logger,
566
- memoryStore: this.deps.memoryStore,
567
- costTracker: this.costTracker,
568
- planSummary: this.deps.planSummary,
569
- getAbortSignal: () => this.abortController?.signal,
570
- isShutdownRequested: () => this.shutdownRequested,
571
- setScope: () => this.setScope(),
572
- });
573
- const hallucinationGate = new HallucinationGate(hallucinationDetector);
574
- const auditGate = new AuditGate(this.deps.finalAudit);
575
- // Защита: частичные/легаси конфиги (тесты, старый config.json) могут не иметь
576
- // домена `critic` — считаем выключенным, а не роняем цикл.
577
- const criticConfig = {
578
- enabled: config.critic?.enabled ?? false,
579
- tools: config.critic?.tools ?? true,
580
- maxCalls: config.critic?.maxCalls ?? 4,
581
- maxRepairCycles: config.critic?.maxRepairCycles ?? 1,
582
- timeoutMs: config.critic?.timeoutMs ?? 15000,
583
- };
584
- // Phase 2: выделенные провайдер/модель, если настроены, иначе активный
585
- // провайдер (v1). Битый оверрайд — warn + фолбэк, никогда не молча.
586
- const criticProvider = buildCriticProvider(config.critic, config, this.deps.llmProvider, {
587
- logger,
588
- getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
589
- });
590
- const criticClient = new CriticClient({
591
- provider: criticProvider,
592
- timeoutMs: criticConfig.timeoutMs,
593
- });
594
- const criticGate = new CriticGate({
595
- enabled: criticConfig.enabled,
596
- maxCalls: criticConfig.maxCalls,
597
- maxRepairCycles: criticConfig.maxRepairCycles,
598
- verify: this.deps.criticOverride?.verify ?? ((pack, signal) => criticClient.verify(pack, signal, {
599
- // Phase 2 read-only цикл: проверки бегут последовательно здесь (гейт
600
- // await'ится между пакетами тулов), через общий executor с read-only
601
- // allowlist — записи/shell недостижимы.
602
- runCheck: criticConfig.tools && this.deps.toolCtx
603
- ? async (tool, args) => {
604
- const res = await toolExecutor.executeByName(tool, args, {
605
- ...this.deps.toolCtx,
606
- toolAllowlist: [...CRITIC_READONLY_TOOLS],
607
- });
608
- return { ok: res.success, output: res.output ?? "" };
609
- }
610
- : undefined,
611
- })),
612
- buildPack: (d) => buildEvidencePack({
613
- task: d.task,
614
- planSummary: d.planSummary,
615
- toolEvents: d.toolEvents,
616
- baseDir: this.deps.baseDir,
617
- }),
618
- signal: this.abortController?.signal,
619
- });
620
- // Определения тулов учитываем в бюджете контекста (идут через body.tools, не messages).
621
- // Пересчитываем каждую итерацию, чтобы `enable_tools` (мутит общий
622
- // activeToolTags) мог растить видимый модели набор прямо в прогоне.
623
- // Привязка к итерации держит оценку бюджета и boundedOutput в синхроне
624
- // с реально отправляемым.
625
- let allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
626
- let boundedToolNames = new Set(allToolsForBudget.filter((t) => t.boundedOutput).map((t) => t.name));
627
- let toolTokenEstimate = allToolsForBudget.reduce((sum, t) => sum + estimateTokens(t.description + JSON.stringify(t.parameters)), 0);
628
- contextManager.setToolTokens(toolTokenEstimate);
629
- while (state.iteration < config.maxToolIterations && !this.shutdownRequested) {
630
- state.iteration++;
631
- this._currentIteration = state.iteration;
632
- contextManager.noteIteration();
633
- // Перечитываем мутабельный набор тегов — вдруг вызывали enable_tools.
634
- allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
635
- boundedToolNames = new Set(allToolsForBudget.filter((t) => t.boundedOutput).map((t) => t.name));
636
- toolTokenEstimate = allToolsForBudget.reduce((sum, t) => sum + Math.ceil((t.description.length + JSON.stringify(t.parameters).length) / 4), 0);
637
- contextManager.setToolTokens(toolTokenEstimate);
638
- pluginManager.runOnBeforeThink({
639
- iteration: state.iteration,
640
- logger,
641
- lastUserMessage: input,
642
- contextManager,
643
- onMeta,
644
- sessionLog: {
645
- plan: (event, detail, iter) => slog.logPlan(event, detail, iter),
646
- },
647
- });
648
- // Единая точка компакции: интервал → качество (с cooldown) → переполнение.
649
- compactionService.compactIfNeeded(state);
650
- this.refreshSystemPrompt();
651
- // Позиционная нормализация истории перед отправкой: вклинившийся user
652
- // (step-gate/auto-advance плана) или осиротевший tool ломает контракт
653
- // OpenAI и даёт 400 у строгих провайдеров. Новые прогоны порядок не
654
- // портят (pendingMessages), проход лечит уже испорченные resume-истории.
655
- const history = repairToolCallPairing(contextManager.getActiveHistory());
656
- // Монитор стабильности префикса: считаем до запроса, пишем в llm_usage
657
- // после. Char-оценка (chars/4) достаточна — важна динамика ratio и
658
- // причина расхождения, а не абсолютная точность токенов.
659
- const promptSnapshot = buildPromptSnapshot(history, allToolsForBudget);
660
- state.prefixDelta = diffPrompt(state.prevPrompt, promptSnapshot, estimateTokens);
661
- state.prevPrompt = promptSnapshot;
662
- // Opt-in git-style context log: snapshot отправляемого контекста.
663
- if (this.deps.contextLogger) {
664
- try {
665
- this.deps.contextLogger.record(history, allToolsForBudget, {
666
- iteration: state.iteration,
667
- ts: new Date().toISOString(),
668
- systemTokens: estimateTokens(promptSnapshot.system),
669
- toolTokens: estimateTokens(promptSnapshot.tools),
670
- historyTokens: estimateTokens(promptSnapshot.history),
671
- totalTokens: contextManager.getEstimatedTokens(),
672
- contextWindow: config.contextWindow,
673
- });
674
- }
675
- catch {
676
- // Логирование контекста не должно ронять агент.
677
- }
678
- }
679
- if (state.prefixDelta.cause === "system" ||
680
- state.prefixDelta.cause === "tools" ||
681
- state.prefixDelta.cause === "history" ||
682
- state.prefixDelta.cause === "volatile") {
683
- state.lastPrefixBreak = state.prefixDelta;
684
- }
685
- slog.logToolDefs(allToolsForBudget.length, allToolsForBudget.map((t) => t.name), state.iteration);
686
- if (state.iteration === 1) {
687
- const { blocks } = this.buildSystemPrompt();
688
- this.logContextStat("start", state.iteration, slog, blocks);
689
- }
690
- else {
691
- this.logContextStat("iteration", state.iteration, slog);
692
- }
693
- let textContent = "";
694
- let reasoningContent = "";
695
- const toolCalls = [];
696
- let sawToolCall = false;
697
- let emittedReasoning = false;
698
- let malformedToolArgs = 0;
699
- this.emitPhase(state.iteration, "thinking", onPhase);
700
- const llmStart = Date.now();
701
- const baseline = tokenTracker.beginIteration(state, contextManager.getEstimatedTokens());
702
- logger.logLLMRequest(config.model, history.length, input, "agent");
703
- // Оценка reasoning-effort на итерацию. Сигналы описывают события
704
- // ПРЕДЫДУЩЕЙ итерации (выставляются на retry-путях перед continue),
705
- // поэтому политика оценивается ДО очистки сигналов — иначе raise-ветка
706
- // недостижима.
707
- const reasoning = reasoningResolver.resolve({
708
- state,
709
- policyState: this.policyState,
710
- reasoningConfig: config.reasoning,
711
- probePassed: this.probePassed,
712
- reasoningStrategy: this.deps.reasoningStrategy,
713
- reasoningState: this.reasoningState,
714
- });
715
- // Очистка per-итерационных сигналов после потребления политикой.
716
- clearIterationSignals(state);
717
- if (reasoning.level) {
718
- slog.logReasoningControl({
719
- iteration: state.iteration,
720
- level: reasoning.level,
721
- strategy: reasoning.strategy,
722
- probePassed: this.probePassed,
723
- source: reasoning.source,
724
- });
725
- contextManager.thinkingLevel = reasoning.level;
726
- }
727
- try {
728
- for await (const chunk of llmProvider.chat(history, allToolsForBudget, this.abortController?.signal, reasoning.level ? { reasoningEffort: reasoning.level, reasoningStrategy: reasoning.strategy } : undefined)) {
729
- if (this.shutdownRequested)
730
- break;
731
- if (chunk.type === "text" && chunk.content) {
732
- if (emittedReasoning && !textContent) {
733
- onReasoning?.("\n\n");
734
- }
735
- textContent += chunk.content;
736
- const textOut = pluginManager.runOnText({ iteration: state.iteration, logger, contextManager }, chunk.content);
737
- onChunk?.(textOut);
738
- }
739
- if (chunk.type === "reasoning" && chunk.content) {
740
- reasoningContent += chunk.content;
741
- if (config.showReasoning) {
742
- const metaOut = pluginManager.runOnMeta({ iteration: state.iteration, logger, contextManager }, chunk.content);
743
- if (metaOut) {
744
- onReasoning?.(metaOut);
745
- }
746
- emittedReasoning = true;
747
- }
748
- }
749
- if (chunk.type === "tool_call" && chunk.toolCall) {
750
- sawToolCall = true;
751
- let parsedArgs;
752
- try {
753
- parsedArgs = JSON.parse(chunk.toolCall.arguments);
754
- }
755
- catch {
756
- // Битый JSON аргументов раньше молча превращался в `{}` —
757
- // модель звала тул с пустыми параметрами и не понимала причины.
758
- // Логируем и сообщаем модели, чтобы она повторила вызов.
759
- parsedArgs = {};
760
- malformedToolArgs++;
761
- logger.diagnostic("warn", `Invalid JSON in tool_call arguments for "${chunk.toolCall.name}" (${chunk.toolCall.arguments.length} chars) — passing {}`);
762
- slog.logError(`tool_args_parse_error: tool="${chunk.toolCall.name}" tool_call_id="${chunk.toolCall.id}" chars=${chunk.toolCall.arguments.length}`);
763
- }
764
- toolCalls.push({
765
- id: chunk.toolCall.id,
766
- name: chunk.toolCall.name,
767
- arguments: parsedArgs,
768
- });
769
- }
770
- if (chunk.type === "done" && chunk.usage) {
771
- // chat() вызывается ОДИН раз за итерацию — каждый `done` здесь
772
- // относится к тому же логическому запросу (usage-only чанк +
773
- // non-streaming fallback дают два done). ЗАМЕНЯЕМ running-суммы
774
- // вместо аккумуляции, иначе один реальный запрос посчитается дважды.
775
- tokenTracker.recordApiUsage(state, baseline, chunk.usage);
776
- }
777
- if (chunk.type === "warning" && chunk.content) {
778
- // Нефатальное уведомление провайдера (напр., ответ срезан cap'ом).
779
- // Рендерим через текстовый стрим, чтобы легло после частичной строки,
780
- // не ломая markdown; в контекст/историю ассистента НЕ добавляем.
781
- logger.diagnostic("warn", `LLM warning: ${chunk.content}`);
782
- onChunk?.(`\n\n> ⚠️ ${chunk.content}\n`);
783
- }
784
- }
785
- }
786
- catch (err) {
787
- if (this.shutdownRequested || err?.name === "AbortError") {
788
- logger.diagnostic("info", "LLM call aborted (interrupt)");
789
- break;
790
- }
791
- // Восстановимые ошибки LLM (например, токен-лимит на середине
792
- // tool_call): отдаём локализованную причину обратно в контекст, чтобы
793
- // модель адаптировалась (разбить запись на куски) вместо смерти всей
794
- // сессии. Ограничено во избежание бесконечного цикла.
795
- if (err instanceof LlmError && err.recoverable && state.llmErrorRetries < MAX_LLM_ERROR_RETRIES) {
796
- state.iterRecoverableLlmError = true;
797
- state.llmErrorRetries++;
798
- logger.diagnostic("warn", `Recoverable LLM error, feeding back (${state.llmErrorRetries}/${MAX_LLM_ERROR_RETRIES}): ${err.message}`);
799
- slog.logError(err.message);
800
- pluginManager.runOnError({ iteration: state.iteration, logger, contextManager }, err);
801
- contextManager.addMessage({
802
- role: "user",
803
- content: `<system-summary>${err.message}</system-summary>`,
804
- });
805
- continue;
806
- }
807
- logger.logLLMResponse(config.model, textContent.length, Date.now() - llmStart, err.message, "agent");
808
- logger.error(`LLM call failed: ${err.message}`);
809
- slog.logError(err.message);
810
- pluginManager.runOnError({ iteration: state.iteration, logger, contextManager }, err);
811
- return {
812
- success: false,
813
- text: state.lastText,
814
- error: t("error.llm", { message: err.message }),
815
- iterationCount: state.iteration,
816
- };
817
- }
818
- finally {
819
- this.emitPhase(state.iteration, "done", onPhase);
820
- }
821
- // Совокупная длина ответа — метрики токенов остаются валидными, даже
822
- // когда бэкенд опускает `usage`.
823
- tokenTracker.addCompletionChars(state, (textContent || reasoningContent).length);
824
- logger.logLLMResponse(config.model, (textContent || reasoningContent).length, Date.now() - llmStart, undefined, "agent");
825
- state.totalLlmDuration += Date.now() - llmStart;
826
- // Per-call лог usage + стоимость. llama.cpp в стриминге часто опускает
827
- // `usage` — фолбэк на локальные оценки (контекст + chars/4) с пометкой
828
- // источника: лог должен отличать реальные API-цифры от эвристики.
829
- tokenTracker.logUsage(state, baseline, textContent || reasoningContent, contextManager.getEstimatedTokens(), state.iteration, Date.now() - llmStart, slog);
830
- if (this.shutdownRequested) {
831
- break;
832
- }
833
- // Сообщаем модели, что её аргументы тула были невалидным JSON: иначе она
834
- // видит лишь «пустой» вызов и не знает, что нужно повторить корректно.
835
- if (malformedToolArgs > 0) {
836
- contextManager.addMessage({
837
- role: "user",
838
- content: `<system-summary>${malformedToolArgs} tool call(s) in your last response had invalid JSON arguments and were executed with empty parameters. Re-issue them with valid JSON.</system-summary>`,
839
- });
840
- }
841
- let llmResponse = null;
842
- if (sawToolCall) {
843
- llmResponse = { type: "tool_call", calls: toolCalls };
844
- }
845
- else if (textContent) {
846
- llmResponse = { type: "text", content: textContent };
847
- }
848
- else if (reasoningContent) {
849
- llmResponse = { type: "reasoning", content: reasoningContent };
850
- }
851
- pluginManager.runOnAfterThink({ iteration: state.iteration, logger, contextManager }, llmResponse);
852
- // Аннотация вывода модели в context-log (reasoning/текст/tool_calls).
853
- // НЕ входит в отправляемый контекст — пишется отдельным [OUTPUT]-блоком.
854
- if (this.deps.contextLogger && (reasoningContent || textContent || toolCalls.length > 0)) {
855
- try {
856
- this.deps.contextLogger.recordOutput({
857
- iteration: state.iteration,
858
- ts: new Date().toISOString(),
859
- reasoning: reasoningContent || undefined,
860
- text: textContent || undefined,
861
- toolCalls: toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments })),
862
- });
863
- }
864
- catch {
865
- // Логирование контекста не должно ронять агент.
866
- }
867
- }
868
- // Детекция повторных одинаковых тулов (структурная, не по ключевым
869
- // словам). Сигнал питает reasoning policy для понижения effort на рутине.
870
- if (sawToolCall) {
871
- const signature = toolCalls
872
- .map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
873
- .join("|");
874
- if (signature && signature === state.lastToolSignature) {
875
- state.iterRepetitive = true;
876
- // Nudge во ВСЕХ режимах: идентичный (тул,аргументы) повтор — пустая
877
- // итерация (наблюдалось: qwen3.5-9b гонял ту же verify-команду подряд,
878
- // когда ответ уже был в результате). Мутирующие в интерактиве —
879
- // только nudge (контроль у пользователя); status-тулы, которым повтор
880
- // прогресса не даст никогда, — жёсткий стоп после нескольких повторов.
881
- logger.diagnostic("info", `Repeated identical tool call detected (iteration ${state.iteration}) — nudging model to finish or change approach`);
882
- contextManager.addMessage({
883
- role: "user",
884
- content: `<system-summary>You just called the same tool with identical arguments. If the task is done, answer with a final text response NOW. If the command failed, try a different approach.</system-summary>`,
885
- });
886
- state.repeatedToolCount++;
887
- // Жёсткий стоп — только для status-тулов, которые не могут
888
- // прогрессировать от повтора. Мутирующие/побочные тулы (например,
889
- // bash) остаются nudge-only: пользователь мог явно попросить
890
- // выполнить команду несколько раз, и обрыв дал бы пустой ответ.
891
- const allStatusTools = toolCalls.every((tc) => LOOP_PRONE_STATUS_TOOLS.has(tc.name));
892
- if (this.deps.exitOnComplete) {
893
- if (state.repeatedToolCount >= MAX_REPEATED_TOOL_CALLS && allStatusTools) {
894
- logger.debug("Exit-on-complete: repeated identical status tool call, stopping");
895
- break;
896
- }
897
- }
898
- else if (state.repeatedToolCount >= MAX_REPEATED_TOOL_CALLS_INTERACTIVE &&
899
- allStatusTools) {
900
- logger.diagnostic("warn", `Repeated identical status tool ${state.repeatedToolCount}× (iteration ${state.iteration}) — stopping to avoid an infinite loop`);
901
- break;
902
- }
903
- }
904
- else {
905
- state.repeatedToolCount = 0;
906
- }
907
- state.lastToolSignature = signature;
908
- }
909
- // Детекция «перфекционизм-цикла»: многократные правки ОДНОГО файла или
910
- // запуски одной команды через не-подряд итерации (edit → build → read →
911
- // edit…). Аргументы каждый раз новые, поэтому подряд-детекция дубликатов
912
- // выше этот паттерн не видит. Nudge уходит ПОСЛЕ tool-результатов, чтобы
913
- // не ломать парность assistant(tool_calls) → tool(results).
914
- const cycleNudges = [];
915
- if (sawToolCall) {
916
- for (const tc of toolCalls) {
917
- // Прогресс тест/спек-артефактов (обобщённо, по пути файла).
918
- if ((tc.name === "write_file" || tc.name === "edit_file") &&
919
- typeof tc.arguments.path === "string") {
920
- recordArtifactWrite(state.artifactChurn, tc.arguments.path);
921
- }
922
- const target = mutationTargetKey(tc.name, tc.arguments);
923
- if (!target)
924
- continue;
925
- const count = (state.toolTargetCounts.get(target) ?? 0) + 1;
926
- state.toolTargetCounts.set(target, count);
927
- if (count >= MUTATION_CYCLE_NUDGE_THRESHOLD &&
928
- !state.cycleNudgedKeys.has(target)) {
929
- state.cycleNudgedKeys.add(target);
930
- cycleNudges.push(target);
931
- }
932
- }
933
- for (const tc of toolCalls) {
934
- if (isWorkMutation(tc.name))
935
- state.mutationsSinceCritic++;
936
- }
937
- }
938
- if (sawToolCall) {
939
- const batch = await toolBatch.execute(state, toolCalls, boundedToolNames, textContent, reasoningContent, input, onTool, onMeta, slog);
940
- if (batch.interrupted) {
941
- break;
942
- }
943
- if (cycleNudges.length > 0) {
944
- logger.diagnostic("info", `Perfectionism cycle detected (${cycleNudges.length} target(s) at ${MUTATION_CYCLE_NUDGE_THRESHOLD}+ mutations) — nudging model to finalize`);
945
- contextManager.addMessage({
946
- role: "user",
947
- content: `<system-summary>You have already modified or re-run the same target many times (e.g. ${cycleNudges[0]}). If the latest output is correct, STOP polishing and answer with a final text response. More identical edits will not change the result.</system-summary>`,
948
- });
949
- }
950
- // Тест/спек-churn: серия правок тестов без единой production-правки
951
- // (обобщённо — см. artifact-progress.ts).
952
- const churnFile = testChurnFile(state.artifactChurn);
953
- if (churnFile) {
954
- state.artifactChurn.nudged = true;
955
- logger.diagnostic("info", `Test/spec artifact churn: "${churnFile}" repeatedly edited with no production changes — nudging to implement or change approach`);
956
- contextManager.addMessage({
957
- role: "user",
958
- content: `<system-summary>${t("exec.test_churn_nudge", { file: churnFile })}</system-summary>`,
959
- });
960
- }
961
- this.contextRenderer.render(this.deps.config.ui, contextManager, onMeta);
962
- // Тулы означают, что модель продолжила работать, а не переотвечает —
963
- // послабление на повтор после аудита больше не действует.
964
- state.suppressRepetitionRetry = false;
965
- continue;
966
- }
967
- const verdict = await hallucinationGate.evaluate({
968
- state,
969
- textContent,
970
- input,
971
- exitOnComplete: this.deps.exitOnComplete ?? false,
972
- hasFinalAudit: !!this.deps.finalAudit,
973
- logger,
974
- contextManager,
975
- onMeta,
976
- onChunk,
977
- // Строка о неуверенности — диагностика: только verbose-режим.
978
- verbose: resolveVerbosity(this.deps.config.ui) === "verbose",
979
- // Заземление заявлений на диск (отсутствующие/непарсящиеся файлы,
980
- // поданные как созданные/валидные). Резолвится здесь — у гейта нет fs.
981
- artifacts: resolveArtifactStates(this.deps.baseDir, textContent),
982
- });
983
- if (verdict.action === "block") {
984
- return {
985
- success: false,
986
- text: state.lastText,
987
- error: t("error.response_blocked", { reason: verdict.reason }),
988
- iterationCount: state.iteration,
989
- };
990
- }
991
- if (verdict.action === "retry") {
992
- continue;
993
- }
994
- if (verdict.action === "exit") {
995
- // lastText/finalAnswerAccepted уже выставлены вентилем.
996
- break;
997
- }
998
- // warn/accept → принятие ответа
999
- if (textContent) {
1000
- contextManager.addMessage({ role: "assistant", content: textContent });
1001
- if (config.session.autoSave) {
1002
- slog.saveAssistantMessage(textContent);
1003
- }
1004
- slog.logAssistant(textContent, reasoningContent, undefined, state.iteration);
1005
- }
1006
- state.lastText = textContent;
1007
- {
1008
- const decisionPatterns = [
1009
- ...textContent.matchAll(/(?:plan|decided|decision|решено|план|решение):\s*(.+?)(?:\n|$)/gi),
1010
- ];
1011
- for (const match of decisionPatterns) {
1012
- hallucinationDetector
1013
- .getConsistencyCheck()
1014
- .trackDecision(match[1].trim(), "agent_response");
1015
- }
1016
- }
1017
- // Guard: пустой финальный ответ (текста нет, тулов нет — часто только
1018
- // reasoning после компакции). Подталкиваем к реальному ответу вместо
1019
- // молчаливого завершения с text: "".
1020
- if (!textContent?.trim()) {
1021
- if (state.emptyResponseRetries < MAX_EMPTY_RESPONSE_RETRIES) {
1022
- state.emptyResponseRetries++;
1023
- logger.diagnostic("warn", `Empty response on iteration ${state.iteration} (retry ${state.emptyResponseRetries}/${MAX_EMPTY_RESPONSE_RETRIES})`);
1024
- contextManager.addMessage({
1025
- role: "user",
1026
- content: `<system-summary>Your previous response was empty. Answer the user's task now with a final text response or call a tool. Do not reply with reasoning only.</system-summary>`,
1027
- });
1028
- continue;
1029
- }
1030
- state.emptyResponseExhausted = true;
1031
- logger.diagnostic("warn", `Empty response retries exhausted after ${MAX_EMPTY_RESPONSE_RETRIES} attempts`);
1032
- }
1033
- const criticAction = await criticGate.evaluate({
1034
- state,
1035
- iteration: state.iteration,
1036
- logger,
1037
- contextManager,
1038
- slog,
1039
- task: input,
1040
- planSummary: this.deps.planSummary?.() ?? undefined,
1041
- toolEvents: gatherTurnToolEvents(contextManager),
1042
- beforeFinish: true,
1043
- });
1044
- if (criticAction === "retry") {
1045
- state.suppressRepetitionRetry = true;
1046
- continue;
1047
- }
1048
- const auditVerdict = await auditGate.evaluate({
1049
- state,
1050
- iteration: state.iteration,
1051
- maxIterations: config.maxToolIterations,
1052
- logger,
1053
- contextManager,
1054
- slog,
1055
- });
1056
- if (auditVerdict === null || auditVerdict.action === "pass") {
1057
- state.finalAnswerAccepted = true;
1058
- break;
1059
- }
1060
- if (auditVerdict.action === "exhausted") {
1061
- // auditFailed уже выставлен вентилем.
1062
- break;
1063
- }
1064
- }
1065
- const tokensUsed = contextManager.getEstimatedTokens();
1066
- const budget = contextManager.getBudget();
1067
- const usageTokens = tokenTracker.resolveFinal(state);
1068
- const cacheStats = this.buildCacheStats(state);
1069
- if (state.iteration >= config.maxToolIterations && !state.finalAnswerAccepted) {
1070
- return {
1071
- success: false,
1072
- text: state.lastText,
1073
- error: t("error.max_iters", { max: config.maxToolIterations }),
1074
- iterationCount: state.iteration,
1075
- contextUsed: tokensUsed,
1076
- contextLimit: budget.history,
1077
- promptTokens: usageTokens.prompt,
1078
- completionTokens: usageTokens.completion,
1079
- totalTokens: usageTokens.total,
1080
- totalCost: this.costTracker.total,
1081
- costBreakdown: this.costTracker.breakdown(),
1082
- cache: cacheStats,
1083
- compactionCount: contextManager.getCompactionCount(),
1084
- contextQuality: contextManager.getQuality(),
1085
- };
1086
- }
1087
- return {
1088
- success: !(state.emptyResponseExhausted || state.auditFailed || state.retryLoopStopped),
1089
- text: state.lastText,
1090
- error: state.emptyResponseExhausted
1091
- ? t("error.empty_response")
1092
- : state.auditFailed
1093
- ? t("error.audit_failed", { summary: state.lastAuditSummary })
1094
- : state.retryLoopStopped
1095
- ? t("error.retry_loop_stopped", { count: state.consecutiveFailedBatches })
1096
- : undefined,
1097
- iterationCount: state.iteration,
1098
- contextUsed: tokensUsed,
1099
- contextLimit: budget.history,
1100
- promptTokens: usageTokens.prompt,
1101
- completionTokens: usageTokens.completion,
1102
- totalTokens: usageTokens.total,
1103
- totalCost: this.costTracker.total,
1104
- costBreakdown: this.costTracker.breakdown(),
1105
- cache: cacheStats,
1106
- compactionCount: contextManager.getCompactionCount(),
1107
- contextQuality: contextManager.getQuality(),
1108
- llmDurationMs: state.totalLlmDuration,
1109
- };
1110
- }
1111
- /**
1112
- * Собирает статистику кеша промпта за прогон: провайдерские токены (если
1113
- * были) плюс client-side стабильность префикса. Возвращает undefined, когда
1114
- * нет ни данных провайдера, ни префикс-монитора — чтобы не засорять вывод.
1115
- */
1116
- buildCacheStats(state) {
1117
- const hasTokens = state.cacheCachedTotal > 0 || state.cacheUncachedTotal > 0 || state.cacheWriteTotal > 0;
1118
- // Приоритет — последнему реальному разрыву, иначе состояние текущей
1119
- // итерации (нормальная дописка / первая итерация).
1120
- const prefix = state.lastPrefixBreak ?? state.prefixDelta;
1121
- if (!hasTokens && prefix?.stableRatio === undefined)
1122
- return undefined;
1123
- const denom = state.cacheCachedTotal + state.cacheUncachedTotal;
1124
- return {
1125
- cachedTokens: state.cacheCachedTotal,
1126
- uncachedTokens: state.cacheUncachedTotal,
1127
- cacheWriteTokens: state.cacheWriteTotal,
1128
- hitRate: denom > 0 ? state.cacheCachedTotal / denom : 0,
1129
- saved: this.costTracker.saved,
1130
- prefixStable: prefix?.stableRatio,
1131
- prefixCause: prefix?.cause,
1132
- source: state.cacheSource,
1133
- };
1134
- }
1135
- /** Сбрасывает контекст (следующий прогон пересоберёт системный промпт). */
1136
- clearContext() {
1137
- this.deps.contextManager.clear();
1138
- this.systemPromptAdded = false;
1139
- }
1140
- /**
1141
- * Применяет новый конфиг живьём: provider-first проба контекста, пересборка
1142
- * провайдера, ресайз бюджетов, обновление стоимости.
1143
- * @param config новый конфиг приложения.
1144
- */
1145
- async reconfigure(config) {
1146
- const { TokenCounter } = await import("../llm/token-counter");
1147
- // Provider-first контекст: при смене модели/бэкенда спрашиваем новый
1148
- // провайдер о реально отдаваемом окне ДО пересборки бюджетов вокруг
1149
- // (возможно протухшего) настроенного значения. Ручные правки
1150
- // `contextWindow` без смены идентичности уважаются как есть.
1151
- // Fail-open: ошибки пробы хранят настроенное значение.
1152
- try {
1153
- const nextId = probeIdentityOf(config);
1154
- const prevId = this.lastProbeIdentity;
1155
- const identityChanged = prevId.model !== nextId.model ||
1156
- prevId.baseUrl !== nextId.baseUrl ||
1157
- prevId.active !== nextId.active;
1158
- if (identityChanged) {
1159
- const nextEndpoint = activeProviderEndpoint(config.provider);
1160
- const { probeModel, probeProviderContext } = await import("../llm/model-loader");
1161
- const lmProbe = await probeModel(nextEndpoint.baseUrl, config.model);
1162
- const detected = (lmProbe ? { model: lmProbe.model, actual: lmProbe.contextLength } : null) ??
1163
- (await probeProviderContext(nextEndpoint.baseUrl, config.model, {
1164
- apiKey: nextEndpoint.apiKey,
1165
- providerType: nextEndpoint.type,
1166
- }));
1167
- if (detected && detected.actual !== config.contextWindow) {
1168
- this.deps.logger?.info(`Context probe: auto-set contextWindow from provider — model "${detected.model}" serves ${detected.actual} tokens (configured was ${config.contextWindow}).`);
1169
- config.contextWindow = detected.actual;
1170
- }
1171
- this.lastProbeIdentity = nextId;
1172
- }
1173
- }
1174
- catch {
1175
- // Проба — best-effort, переключение модели/провайдера не блокируем никогда.
1176
- }
1177
- const built = buildActiveProvider(config, this.deps.logger, {
1178
- getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
1179
- });
1180
- const newProvider = built.provider;
1181
- this.deps.llmProvider = newProvider;
1182
- this.deps.getProviderName = () => built.manager.activeName;
1183
- this.deps.toolExecutor.updateProvider(newProvider);
1184
- const newTokenCounter = new TokenCounter(config.model);
1185
- this.deps.contextManager.resize(config.contextWindow, config.contextBudget, newTokenCounter);
1186
- this.deps.config = config;
1187
- if (this.deps.configRef)
1188
- this.deps.configRef.current = config;
1189
- this.costTracker.setModel(config.model);
1190
- this.applyReasoningStrategy(config.provider);
1191
- }
1192
- /**
1193
- * Пересчитывает стратегию reasoning из АКТИВНОЙ записи провайдера. Без этого
1194
- * `/provider use` и смена модели оставляли старую стратегию (prompt-tag против
1195
- * openai-effort) на весь сеанс. При "none" гасит probe-флаг, чтобы auto-режим
1196
- * не полагался на устаревшую пробу.
1197
- * @param provider конфиг провайдера (entries/active).
1198
- */
1199
- applyReasoningStrategy(provider) {
1200
- const strategy = reasoningStrategyForProvider(provider);
1201
- this.deps.reasoningStrategy = strategy;
1202
- if (strategy === "none")
1203
- this.probePassed = false;
1204
- }
1205
- /**
1206
- * Переключает активного провайдера (и модель опционально).
1207
- * @param name имя записи провайдера; @param model новая модель.
1208
- */
1209
- setProvider(name, model) {
1210
- const { manager, provider } = buildActiveProvider(this.deps.config, this.deps.logger, {
1211
- getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
1212
- });
1213
- manager.switch(name, model);
1214
- const providerCfg = manager.toConfig();
1215
- this.deps.config.provider = providerCfg;
1216
- this.applyReasoningStrategy(providerCfg);
1217
- this.deps.llmProvider = provider;
1218
- this.deps.getProviderName = () => manager.activeName;
1219
- this.deps.toolExecutor.updateProvider(provider);
1220
- }
1221
- /**
1222
- * Список записей провайдеров с флагом активности.
1223
- * @returns записи с label и active.
1224
- */
1225
- listProviders() {
1226
- const manager = new ProviderManager(this.deps.config.provider, {
1227
- contextWindow: this.deps.config.contextWindow,
1228
- retry: this.deps.config.retry,
1229
- rateLimits: this.deps.config.security?.rateLimits,
1230
- getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
1231
- });
1232
- const activeName = this.deps.config.provider.active;
1233
- return manager.list().map((e) => ({
1234
- ...e,
1235
- active: e.label === activeName || e.type === activeName,
1236
- }));
1237
- }
1238
- /** Строит (или берёт кэшированный) провайдер именованной записи — health-чеки, роутинг. */
1239
- getProviderFor(name) {
1240
- try {
1241
- const manager = new ProviderManager(this.deps.config.provider, {
1242
- contextWindow: this.deps.config.contextWindow,
1243
- retry: this.deps.config.retry,
1244
- rateLimits: this.deps.config.security?.rateLimits,
1245
- getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
1246
- logger: this.deps.logger,
1247
- });
1248
- manager.setModel(this.deps.config.model);
1249
- manager.switch(name);
1250
- return manager.active;
1251
- }
1252
- catch (err) {
1253
- this.deps.logger.warn(t("error.provider_build", { name, error: errMsg(err) }));
1254
- return undefined;
1255
- }
1256
- }
1257
- /**
1258
- * Заменяет контекст историей (resume): системный промпт + сообщения без дубля system.
1259
- * @param messages сообщения для восстановления.
1260
- */
1261
- setContext(messages) {
1262
- const { contextManager } = this.deps;
1263
- contextManager.clear();
1264
- const { prompt: systemPrompt } = this.buildSystemPrompt();
1265
- contextManager.addMessage({ role: "system", content: systemPrompt });
1266
- this.systemPromptAdded = true;
1267
- for (const msg of messages) {
1268
- if (msg.role === "system")
1269
- continue;
1270
- contextManager.addMessage({
1271
- role: msg.role,
1272
- content: msg.content,
1273
- name: msg.name,
1274
- });
1275
- }
1276
- }
1277
- /**
1278
- * Останавливает прогон: abort, kill фоновых процессов, onSessionEnd.
1279
- * Лог сессии НЕ закрываем — сессия ещё активна (закрытие только на
1280
- * реальном конце: exit, delete).
1281
- */
1282
- shutdown() {
1283
- this.shutdownRequested = true;
1284
- this.abortController?.abort();
1285
- const { pluginManager, logger, sessionManager, contextManager } = this.deps;
1286
- contextManager.onCompact = null;
1287
- const killed = processRegistry.killAll();
1288
- if (killed > 0) {
1289
- logger.info(t("env.killed_processes", { count: String(killed) }));
1290
- }
1291
- // Сессионный лог тут НЕ закрываем — сессия ещё активна.
1292
- // Закрытие только на реальном конце (exit, удаление сессии).
1293
- pluginManager.runOnSessionEnd({
1294
- logger,
1295
- sessionManager: sessionManager?.getActiveMeta(),
1296
- contextManager,
1297
- });
1298
- }
1299
- }