micro-models-agent 2.11.0 → 2.13.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 (356) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/assets/app.js +22 -2
  3. package/dist/assets/index.html +39 -9
  4. package/dist/assets/js/dom.js +15 -0
  5. package/dist/assets/js/feed.js +129 -10
  6. package/dist/assets/js/sidebar.js +512 -0
  7. package/dist/assets/js/tabs.js +30 -5
  8. package/dist/assets/setup.js +267 -8
  9. package/dist/assets/styles.css +168 -3
  10. package/dist/cli/cache-line.js +1 -0
  11. package/dist/cli/command-suggest.js +5 -4
  12. package/dist/cli/commands.js +231 -122
  13. package/dist/cli/completer.js +132 -31
  14. package/dist/cli/index.js +1 -0
  15. package/dist/cli/json-payload.js +4 -4
  16. package/dist/cli/main.js +166 -61
  17. package/dist/cli/onboarding-commands.js +126 -0
  18. package/dist/cli/plugin-commands.js +2 -1
  19. package/dist/cli/relaunch.js +16 -13
  20. package/dist/cli/repl-commands-lsp.js +75 -0
  21. package/dist/cli/repl-commands-skills.js +105 -0
  22. package/dist/cli/repl-commands.js +589 -266
  23. package/dist/cli/repl.js +436 -177
  24. package/dist/cli/run-result.js +5 -6
  25. package/dist/cli/security-commands.js +37 -28
  26. package/dist/cli/setup-order.js +4 -55
  27. package/dist/cli/setup-prompt.js +3 -2
  28. package/dist/cli/setup.js +27 -107
  29. package/dist/cli/web-command.js +98 -0
  30. package/dist/config/budget.js +5 -5
  31. package/dist/config/config.js +103 -96
  32. package/dist/config/defaults.js +38 -8
  33. package/dist/config/domains.js +24 -27
  34. package/dist/config/env.js +46 -0
  35. package/dist/config/experts.js +2 -0
  36. package/dist/config/index.js +1 -0
  37. package/dist/config/paths.js +56 -0
  38. package/dist/config/redact.js +44 -0
  39. package/dist/config/registry-extra.js +334 -0
  40. package/dist/config/registry.js +584 -0
  41. package/dist/config/security.js +35 -36
  42. package/dist/config/timeouts.js +74 -0
  43. package/dist/config/utils.js +16 -15
  44. package/dist/core/agent/artifact-progress.js +59 -0
  45. package/dist/core/agent/artifact-resolve.js +79 -0
  46. package/dist/core/agent/audit-gate.js +7 -3
  47. package/dist/core/agent/compaction.js +2 -2
  48. package/dist/core/agent/constants.js +33 -4
  49. package/dist/core/agent/context-log.js +454 -0
  50. package/dist/core/agent/context-renderer.js +5 -0
  51. package/dist/core/agent/critic-gate.js +132 -0
  52. package/dist/core/agent/hallucination-gate.js +148 -38
  53. package/dist/core/agent/loop-state.js +15 -0
  54. package/dist/core/agent/prefix-monitor.js +1 -1
  55. package/dist/core/agent/reasoning-resolver.js +10 -1
  56. package/dist/core/agent/token-tracker.js +7 -4
  57. package/dist/core/agent/tool-batch.js +93 -18
  58. package/dist/core/agent/tool-images.js +18 -0
  59. package/dist/core/agent/tool-output.js +65 -11
  60. package/dist/core/agent/tool-pairing.js +60 -0
  61. package/dist/core/agent-moe.js +25 -24
  62. package/dist/core/agent.js +429 -108
  63. package/dist/core/bootstrap/agent-phase.js +66 -0
  64. package/dist/core/bootstrap/config-phase.js +86 -0
  65. package/dist/core/bootstrap/modules-phase.js +269 -0
  66. package/dist/core/bootstrap/provider-phase.js +183 -0
  67. package/dist/core/bootstrap/session-phase.js +204 -0
  68. package/dist/core/bootstrap/state.js +94 -0
  69. package/dist/core/bootstrap/workspace-phase.js +115 -0
  70. package/dist/core/bootstrap.js +92 -660
  71. package/dist/core/crash-handler.js +17 -7
  72. package/dist/core/environment.js +37 -12
  73. package/dist/core/host-plan.js +43 -0
  74. package/dist/core/host.js +23 -0
  75. package/dist/core/index.js +1 -0
  76. package/dist/core/prompt-builder.js +17 -1
  77. package/dist/core/prompt-overflow.js +42 -43
  78. package/dist/core/session-logger.js +122 -30
  79. package/dist/core/system-prompt.js +117 -0
  80. package/dist/core/version.js +6 -4
  81. package/dist/core/web-host.js +34 -0
  82. package/dist/core/workspace.js +19 -15
  83. package/dist/i18n/en.json +89 -3
  84. package/dist/i18n/index.js +37 -3
  85. package/dist/i18n/ru.json +89 -3
  86. package/dist/index.js +7 -3
  87. package/dist/llm/image-utils.js +55 -64
  88. package/dist/llm/llm-errors.js +9 -0
  89. package/dist/llm/model-loader.js +10 -32
  90. package/dist/llm/models-dev.js +121 -0
  91. package/dist/llm/openai-compat.js +146 -73
  92. package/dist/llm/orchestrator.js +33 -11
  93. package/dist/llm/provider-budget.js +2 -2
  94. package/dist/llm/provider-context.js +233 -0
  95. package/dist/llm/provider.js +1 -1
  96. package/dist/llm/response.js +5 -0
  97. package/dist/llm/token-counter.js +27 -11
  98. package/dist/logger/app-logger.js +48 -22
  99. package/dist/logger/file-log.js +28 -19
  100. package/dist/logger/index.js +1 -0
  101. package/dist/main.js +888 -139
  102. package/dist/migration/backup.js +14 -3
  103. package/dist/migration/detect.js +12 -3
  104. package/dist/migration/index.js +1 -0
  105. package/dist/modules/artifacts/store.js +9 -8
  106. package/dist/modules/browser/actions.js +6 -0
  107. package/dist/modules/browser/bridge-client.js +14 -12
  108. package/dist/modules/browser/bridge-path.js +4 -4
  109. package/dist/modules/browser/console-buffer.js +37 -0
  110. package/dist/modules/browser/cookie-store.js +5 -0
  111. package/dist/modules/browser/driver-types.js +1 -0
  112. package/dist/modules/browser/driver.js +19 -11
  113. package/dist/modules/browser/index.js +3 -1
  114. package/dist/modules/browser/module.js +2 -1
  115. package/dist/modules/browser/session.js +10 -30
  116. package/dist/modules/browser/snapshot.js +19 -13
  117. package/dist/modules/browser/types.js +1 -0
  118. package/dist/modules/certification/cli.js +28 -20
  119. package/dist/modules/certification/fact-checker.js +9 -1
  120. package/dist/modules/certification/loader.js +9 -2
  121. package/dist/modules/certification/manifest.js +83 -21
  122. package/dist/modules/certification/runner.js +23 -13
  123. package/dist/modules/certification/scenarios.js +20 -20
  124. package/dist/modules/certification/syntax-scenarios.js +1 -2
  125. package/dist/modules/context/chunk-query.js +11 -9
  126. package/dist/modules/context/fact-extractor.js +37 -39
  127. package/dist/modules/context/index.js +1 -0
  128. package/dist/modules/context/manager.js +106 -80
  129. package/dist/modules/critic/client.js +176 -0
  130. package/dist/modules/critic/evidence.js +81 -0
  131. package/dist/modules/critic/provider.js +45 -0
  132. package/dist/modules/critic/tools.js +78 -0
  133. package/dist/modules/critic/types.js +1 -0
  134. package/dist/modules/execution/audit-runners.js +28 -34
  135. package/dist/modules/execution/auditor.js +53 -49
  136. package/dist/modules/execution/execution-plugin.js +142 -102
  137. package/dist/modules/execution/index.js +1 -1
  138. package/dist/modules/execution/input-from.js +37 -13
  139. package/dist/modules/execution/module.js +368 -191
  140. package/dist/modules/execution/moe-executor.js +53 -26
  141. package/dist/modules/execution/plan-actions.js +320 -202
  142. package/dist/modules/execution/plan-checks.js +35 -0
  143. package/dist/modules/execution/plan-coverage.js +36 -33
  144. package/dist/modules/execution/plan-steps.js +11 -3
  145. package/dist/modules/execution/plan-store.js +83 -17
  146. package/dist/modules/execution/plan-tool.js +89 -35
  147. package/dist/modules/execution/plan-validator.js +30 -10
  148. package/dist/modules/execution/planner.js +22 -13
  149. package/dist/modules/execution/stuck-detector.js +196 -93
  150. package/dist/modules/execution/tracker.js +22 -8
  151. package/dist/modules/execution/verifier.js +69 -48
  152. package/dist/modules/execution/windows-commands.js +12 -12
  153. package/dist/modules/hallucination/artifact-claim.js +93 -0
  154. package/dist/modules/hallucination/confidence.js +13 -11
  155. package/dist/modules/hallucination/consistency.js +10 -2
  156. package/dist/modules/hallucination/detector.js +10 -5
  157. package/dist/modules/hallucination/factual.js +43 -26
  158. package/dist/modules/hallucination/index.js +1 -0
  159. package/dist/modules/hallucination/js-identifiers.js +44 -45
  160. package/dist/modules/hallucination/llm-judge.js +17 -12
  161. package/dist/modules/hallucination/success-claim.js +59 -0
  162. package/dist/modules/indexer/cache.js +52 -4
  163. package/dist/modules/indexer/index.js +1 -0
  164. package/dist/modules/indexer/map-command.js +2 -4
  165. package/dist/modules/indexer/map-select.js +16 -16
  166. package/dist/modules/indexer/module.js +47 -12
  167. package/dist/modules/indexer/project-profile.js +8 -7
  168. package/dist/modules/indexer/symbols.js +15 -15
  169. package/dist/modules/indexer/walker.js +159 -29
  170. package/dist/modules/lsp/check-tool.js +7 -5
  171. package/dist/modules/lsp/client.js +39 -39
  172. package/dist/modules/lsp/command.js +17 -24
  173. package/dist/modules/lsp/config.js +12 -11
  174. package/dist/modules/lsp/index.js +1 -0
  175. package/dist/modules/lsp/module.js +48 -46
  176. package/dist/modules/lsp/probe.js +13 -14
  177. package/dist/modules/lsp/project-root.js +14 -17
  178. package/dist/modules/lsp/startup-check.js +18 -16
  179. package/dist/modules/mcp/client.js +59 -62
  180. package/dist/modules/mcp/index.js +1 -0
  181. package/dist/modules/mcp/module.js +66 -37
  182. package/dist/modules/mcp/registry.js +5 -0
  183. package/dist/modules/memory/index.js +1 -0
  184. package/dist/modules/memory/module.js +12 -8
  185. package/dist/modules/memory/search.js +4 -1
  186. package/dist/modules/memory/store.js +40 -2
  187. package/dist/modules/onboarding/init.js +47 -0
  188. package/dist/modules/onboarding/meet.js +30 -0
  189. package/dist/modules/onboarding/mode.js +30 -0
  190. package/dist/modules/onboarding/prompts.js +43 -0
  191. package/dist/modules/pipelines/engine.js +10 -0
  192. package/dist/modules/pipelines/index.js +1 -0
  193. package/dist/modules/pipelines/parser.js +4 -1
  194. package/dist/modules/pipelines/template.js +4 -0
  195. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  196. package/dist/modules/plugins/builtin/notify.js +2 -0
  197. package/dist/modules/plugins/index.js +1 -0
  198. package/dist/modules/plugins/loader.js +18 -3
  199. package/dist/modules/plugins/manager.js +37 -17
  200. package/dist/modules/pricing/index.js +10 -11
  201. package/dist/modules/pricing/prices.js +12 -12
  202. package/dist/modules/processes/index.js +1 -0
  203. package/dist/modules/processes/kill-tree.js +27 -31
  204. package/dist/modules/processes/registry.js +44 -25
  205. package/dist/modules/processes/runner.js +4 -4
  206. package/dist/modules/providers/capabilities.js +28 -0
  207. package/dist/modules/providers/create.js +3 -4
  208. package/dist/modules/providers/factory.js +7 -7
  209. package/dist/modules/providers/fallback.js +12 -12
  210. package/dist/modules/providers/health.js +4 -4
  211. package/dist/modules/providers/manager.js +50 -19
  212. package/dist/modules/providers/naming.js +15 -0
  213. package/dist/modules/providers/preflight.js +71 -0
  214. package/dist/modules/providers/presets.js +39 -8
  215. package/dist/modules/providers/registry.js +7 -2
  216. package/dist/modules/reasoning/policy.js +8 -6
  217. package/dist/modules/reasoning/probe.js +30 -25
  218. package/dist/modules/security/audit-log.js +19 -36
  219. package/dist/modules/security/audit-notifier.js +35 -75
  220. package/dist/modules/security/command-validator.js +99 -51
  221. package/dist/modules/security/content-scanner.js +8 -8
  222. package/dist/modules/security/data-sanitizer.js +9 -16
  223. package/dist/modules/security/encryption.js +32 -52
  224. package/dist/modules/security/index.js +2 -1
  225. package/dist/modules/security/network-validator.js +120 -43
  226. package/dist/modules/security/path-validator.js +61 -46
  227. package/dist/modules/security/protected-paths.js +73 -0
  228. package/dist/modules/security/rate-limiter.js +17 -49
  229. package/dist/modules/security/security-policies.js +25 -57
  230. package/dist/modules/security/session-encryption.js +32 -71
  231. package/dist/modules/security/session-isolation.js +15 -28
  232. package/dist/modules/session/auto-name.js +63 -0
  233. package/dist/modules/session/export.js +98 -0
  234. package/dist/modules/session/index.js +1 -0
  235. package/dist/modules/session/manager.js +79 -28
  236. package/dist/modules/session/module.js +4 -3
  237. package/dist/modules/session/store.js +57 -20
  238. package/dist/modules/setup/answers.js +35 -0
  239. package/dist/modules/setup/apply.js +79 -0
  240. package/dist/modules/setup/discovery.js +72 -0
  241. package/dist/modules/skills/index.js +1 -0
  242. package/dist/modules/skills/loader.js +9 -1
  243. package/dist/modules/skills/module.js +11 -0
  244. package/dist/modules/updater/changelog-reader.js +15 -16
  245. package/dist/modules/updater/checker.js +9 -4
  246. package/dist/modules/updater/dev-detect.js +7 -6
  247. package/dist/modules/updater/index.js +1 -0
  248. package/dist/modules/updater/module.js +17 -15
  249. package/dist/modules/user-profile/compressor.js +2 -0
  250. package/dist/modules/user-profile/index.js +1 -0
  251. package/dist/modules/user-profile/profile.js +10 -1
  252. package/dist/modules/webui/answer-api.js +29 -0
  253. package/dist/modules/webui/assets.js +50 -0
  254. package/dist/modules/webui/body.js +21 -0
  255. package/dist/modules/webui/commands-settings.js +209 -0
  256. package/dist/modules/webui/commands-shared.js +60 -0
  257. package/dist/modules/webui/commands.js +174 -0
  258. package/dist/modules/webui/config-api.js +134 -0
  259. package/dist/modules/webui/file-api.js +202 -0
  260. package/dist/modules/webui/forward.js +159 -0
  261. package/dist/modules/webui/history-api.js +112 -0
  262. package/dist/modules/webui/host-state.js +60 -0
  263. package/dist/modules/webui/i18n-api.js +9 -0
  264. package/dist/modules/webui/limits.js +6 -0
  265. package/dist/modules/webui/log-forward.js +20 -0
  266. package/dist/modules/webui/memory-commands.js +64 -0
  267. package/dist/modules/webui/model-cache.js +18 -0
  268. package/dist/modules/webui/module.js +148 -0
  269. package/dist/modules/webui/persist.js +24 -0
  270. package/dist/modules/webui/prompt-io.js +97 -0
  271. package/dist/modules/webui/providers-api.js +337 -0
  272. package/dist/modules/webui/providers-view.js +56 -0
  273. package/dist/modules/webui/reasoning-view.js +15 -0
  274. package/dist/modules/webui/run-agent.js +81 -0
  275. package/dist/modules/webui/server.js +284 -0
  276. package/dist/modules/webui/sessions-api.js +178 -0
  277. package/dist/modules/webui/settings-api.js +561 -0
  278. package/dist/modules/webui/setup-api.js +201 -0
  279. package/dist/modules/webui/state.js +74 -0
  280. package/dist/modules/webui/util.js +261 -0
  281. package/dist/output/bus.js +7 -2
  282. package/dist/output/channel.js +80 -28
  283. package/dist/output/format.js +2 -1
  284. package/dist/output/index.js +1 -0
  285. package/dist/output/json-sink.js +5 -3
  286. package/dist/output/machine.js +4 -3
  287. package/dist/output/session-sink.js +4 -4
  288. package/dist/tools/approve.js +4 -0
  289. package/dist/tools/attach-image.js +5 -4
  290. package/dist/tools/auto-fixer.js +26 -44
  291. package/dist/tools/auto-tags.js +83 -0
  292. package/dist/tools/bash.js +193 -116
  293. package/dist/tools/browser.js +76 -31
  294. package/dist/tools/chunk-query.js +14 -6
  295. package/dist/tools/create-dir.js +9 -5
  296. package/dist/tools/delete-file.js +9 -5
  297. package/dist/tools/download-file.js +32 -7
  298. package/dist/tools/edit-file.js +52 -14
  299. package/dist/tools/enable-tools.js +6 -6
  300. package/dist/tools/executor.js +132 -27
  301. package/dist/tools/file-info.js +3 -1
  302. package/dist/tools/filter-tools.js +3 -3
  303. package/dist/tools/glob-tool.js +4 -1
  304. package/dist/tools/grep-tool.js +13 -10
  305. package/dist/tools/hidden-tools-block.js +6 -6
  306. package/dist/tools/index.js +16 -8
  307. package/dist/tools/list-dir.js +4 -2
  308. package/dist/tools/load-skill.js +7 -0
  309. package/dist/tools/mcp-call.js +15 -4
  310. package/dist/tools/meet-done.js +14 -0
  311. package/dist/tools/move-file.js +14 -7
  312. package/dist/tools/path-utils.js +29 -29
  313. package/dist/tools/pipeline-run.js +29 -7
  314. package/dist/tools/preview.js +1 -1
  315. package/dist/tools/process-ids.js +31 -0
  316. package/dist/tools/process-kill.js +9 -16
  317. package/dist/tools/process-list.js +1 -0
  318. package/dist/tools/process-log.js +11 -16
  319. package/dist/tools/process-wait.js +63 -0
  320. package/dist/tools/prompt-io.js +4 -3
  321. package/dist/tools/question.js +172 -25
  322. package/dist/tools/read-file.js +55 -34
  323. package/dist/tools/recall.js +14 -8
  324. package/dist/tools/registry.js +6 -5
  325. package/dist/tools/remember.js +8 -4
  326. package/dist/tools/scope-request.js +11 -6
  327. package/dist/tools/search-history.js +5 -2
  328. package/dist/tools/session-info.js +2 -0
  329. package/dist/tools/set-thinking.js +8 -4
  330. package/dist/tools/subagent.js +59 -39
  331. package/dist/tools/syntax-validator.js +292 -90
  332. package/dist/tools/user-input.js +22 -18
  333. package/dist/tools/web-browse.js +9 -3
  334. package/dist/tools/web-fetch.js +12 -23
  335. package/dist/tools/web-search.js +7 -5
  336. package/dist/tools/write-file.js +45 -15
  337. package/dist/ui/action-log.js +13 -0
  338. package/dist/ui/box.js +4 -3
  339. package/dist/ui/colors.js +2 -0
  340. package/dist/ui/diff.js +6 -5
  341. package/dist/ui/index.js +1 -0
  342. package/dist/ui/line-editor.js +230 -122
  343. package/dist/ui/line-math.js +10 -2
  344. package/dist/ui/md-formatter.js +57 -37
  345. package/dist/ui/output.js +8 -3
  346. package/dist/ui/plan-view.js +21 -10
  347. package/dist/ui/renderer.js +393 -67
  348. package/dist/ui/spinner.js +16 -5
  349. package/dist/ui/table.js +15 -17
  350. package/dist/ui/theme.js +143 -0
  351. package/dist/utils/child-run.js +20 -0
  352. package/dist/utils/error.js +1 -1
  353. package/dist/utils/index.js +2 -0
  354. package/dist/utils/retry.js +2 -2
  355. package/dist/utils/truncate.js +1 -1
  356. package/package.json +1 -1
@@ -1,6 +1,9 @@
1
+ /** Вентиль халлюцинаций: проверка финального ответа перед принятием. */
1
2
  import { pc } from "../../ui/colors";
2
3
  import { t } from "../../i18n/index";
3
- import { MAX_HALLUCINATION_RETRIES } from "./constants";
4
+ import { checkSuccessClaim } from "../../modules/hallucination/success-claim";
5
+ import { checkArtifactClaims, checkUnverifiedOutput, } from "../../modules/hallucination/artifact-claim";
6
+ import { MAX_CLAIM_CHECK_RETRIES, MAX_HALLUCINATION_RETRIES } from "./constants";
4
7
  /**
5
8
  * Вентиль проверки халлюцинаций финального текстового ответа.
6
9
  *
@@ -12,11 +15,38 @@ import { MAX_HALLUCINATION_RETRIES } from "./constants";
12
15
  * Возвращает структурированный вердикт; решение о `break`/`continue`/`return`
13
16
  * принимает цикл. Вся мутация состояния живёт здесь — цикл остаётся тонким.
14
17
  */
18
+ /**
19
+ * Свежие выводы тулов для проверки заявлений: последние 10 сообщений
20
+ * role:"tool" (текст урезан; чекер режет повторно). Тот же источник
21
+ * getActiveHistory, что у вентиля критика.
22
+ */
23
+ function lastToolEvidence(deps) {
24
+ let history;
25
+ try {
26
+ history = deps.contextManager.getActiveHistory() ?? [];
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ return history
32
+ .filter((m) => m.role === "tool")
33
+ .slice(-10)
34
+ .map((m) => ({
35
+ name: m.name,
36
+ ok: m.success,
37
+ text: typeof m.content === "string" ? m.content : JSON.stringify(m.content) ?? "",
38
+ }))
39
+ .filter((m) => m.text.trim().length > 0);
40
+ }
15
41
  export class HallucinationGate {
16
42
  detector;
17
43
  constructor(detector) {
18
44
  this.detector = detector;
19
45
  }
46
+ /**
47
+ * Проверяет финальный текст ответа (детектор + заявления об успехе).
48
+ * @param deps состояние, текст, контекст; @returns структурированный вердикт.
49
+ */
20
50
  async evaluate(deps) {
21
51
  const { state, textContent, logger } = deps;
22
52
  // Обновляем базу сравнения ДО validate: confidence-чек смотрит на
@@ -26,56 +56,136 @@ export class HallucinationGate {
26
56
  state.lastModelText = textContent;
27
57
  const result = await this.detector.validate(textContent);
28
58
  if (result.status === "block") {
29
- logger.warn(`Response blocked: ${result.reason}`);
59
+ logger.diagnostic("warn", `Response blocked: ${result.reason}`);
30
60
  return { action: "block", reason: result.reason || "" };
31
61
  }
32
62
  if (result.status === "warn") {
33
- logger.warn(`Hallucination warning: ${result.reason}`);
63
+ logger.diagnostic("warn", `Hallucination warning: ${result.reason}`);
34
64
  this.displayWarnLine(result.reason, deps);
35
65
  return { action: "warn" };
36
66
  }
37
67
  if (result.status === "retry") {
38
- state.iterHallucinationRetry = true;
39
- // Exit-on-complete fast-exit НЕ должен обходить финальный аудит: при
40
- // активном плане модель может ответить "done" с невыполненными шагами
41
- // (регрессия сертификации: модель ответила, не запустив свой скрипт).
42
- // Короткое замыкание — только когда аудитить нечего.
43
- if (deps.exitOnComplete && textContent?.trim() && !deps.hasFinalAudit) {
44
- logger.debug("Exit-on-complete: stopping on first response");
45
- // Сохраняем ДО break — lastText всё ещё текст предыдущей (туловой)
46
- // итерации, и без этого итоговый ответ теряется (reported text: "").
47
- state.lastText = textContent;
48
- state.finalAnswerAccepted = true;
49
- return { action: "exit" };
50
- }
51
- if (state.suppressRepetitionRetry && result.kind === "repetition") {
52
- // Аудит отклонил предыдущий ответ и переспросил; его переформулировка
53
- // выполненной задачи естественно "повторяющаяся" — это ожидаемо, а не
54
- // деградация. Снижаем до предупреждения и пропускаем в acceptance path
55
- // (сам аудит ограничен MAX_AUDIT_RETRIES).
56
- state.suppressRepetitionRetry = false;
57
- logger.warn(`Audit-triggered re-answer repetition — not counted as hallucination retry`);
58
- this.displayWarnLine(result.reason, deps);
59
- return { action: "accept" };
68
+ return this.applyRetry(deps, textContent, result);
69
+ }
70
+ // Проверка заявлений (ses_muih0ah5): сильное заявление о готовности
71
+ // («done», «published», «готово»), когда свежие результаты тулов — с ошибками.
72
+ // Одна ограниченная перепроверка — затем fail-open (принять как есть), не блок.
73
+ const claimRetry = this.checkSuccessClaim(deps, textContent);
74
+ if (claimRetry)
75
+ return claimRetry;
76
+ return { action: "accept" };
77
+ }
78
+ /**
79
+ * Общий инжект повтора: фиксирует отклонённый ответ, дописывает причину
80
+ * как `<system-summary>` и просит цикл о следующем ходе.
81
+ */
82
+ applyRetry(deps, textContent, result) {
83
+ const { state, logger } = deps;
84
+ state.iterHallucinationRetry = true;
85
+ // Exit-on-complete fast-exit НЕ должен обходить финальный аудит: при
86
+ // активном плане модель может ответить "done" с невыполненными шагами
87
+ // (регрессия сертификации: модель ответила, не запустив свой скрипт).
88
+ // Короткое замыкание — только когда аудитить нечего.
89
+ if (deps.exitOnComplete && textContent?.trim() && !deps.hasFinalAudit) {
90
+ logger.debug("Exit-on-complete: stopping on first response");
91
+ // Сохраняем ДО break — lastText всё ещё текст предыдущей (туловой)
92
+ // итерации, и без этого итоговый ответ теряется (reported text: "").
93
+ state.lastText = textContent;
94
+ state.finalAnswerAccepted = true;
95
+ return { action: "exit" };
96
+ }
97
+ if (state.suppressRepetitionRetry && result.kind === "repetition") {
98
+ // Аудит отклонил предыдущий ответ и переспросил; его переформулировка
99
+ // выполненной задачи естественно "повторяющаяся" — это ожидаемо, а не
100
+ // деградация. Снижаем до предупреждения и пропускаем в acceptance path
101
+ // (сам аудит ограничен MAX_AUDIT_RETRIES).
102
+ state.suppressRepetitionRetry = false;
103
+ logger.diagnostic("warn", `Audit-triggered re-answer repetition — not counted as hallucination retry`);
104
+ this.displayWarnLine(result.reason, deps);
105
+ return { action: "accept" };
106
+ }
107
+ if (state.hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
108
+ logger.diagnostic("warn", `Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
109
+ return { action: "block", reason: t("hall.max_retries_exhausted") };
110
+ }
111
+ state.hallucinationRetries++;
112
+ logger.diagnostic("warn", `Hallucination retry (${state.hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${result.reason}`);
113
+ if (textContent) {
114
+ deps.contextManager.addMessage({ role: "assistant", content: textContent });
115
+ }
116
+ deps.contextManager.addMessage({
117
+ role: "user",
118
+ content: `<system-summary>[Retry context: ${result.reason}. Original task: "${deps.input}". You must either call a needed tool or provide a substantive response. Empty replies are not allowed.]</system-summary>`,
119
+ });
120
+ return { action: "retry" };
121
+ }
122
+ /**
123
+ * Детерминированные проверки заявлений против улик (без LLM): исходная
124
+ * проверка success-claim плюс заземление артефактов (файл отсутствует/
125
+ * не парсится) и провенанс вывода (вывод подан как исполненный без
126
+ * исполняющего тула). У каждого класса свой ограниченный бюджет перепроверок
127
+ * (fail-open): съеденный бюджет одной проверки не должен экранировать другую
128
+ * ложь в том же ответе (наблюдалось: retry success-claim съел единый бюджет,
129
+ * и «output written to fizzbuzz_output.txt» прошёл при отсутствующем файле).
130
+ */
131
+ checkSuccessClaim(deps, textContent) {
132
+ const { state } = deps;
133
+ const text = textContent ?? "";
134
+ const evidence = lastToolEvidence(deps);
135
+ if (state.claimCheckRetries < MAX_CLAIM_CHECK_RETRIES) {
136
+ const contradiction = checkSuccessClaim(text, evidence);
137
+ if (contradiction) {
138
+ state.claimCheckRetries++;
139
+ return this.retryClaim(deps, textContent, t("hall.claim_contradiction", {
140
+ tool: contradiction.tool,
141
+ snippet: contradiction.snippet,
142
+ }), `Success-claim contradiction: ${contradiction.tool}: ${contradiction.snippet}`);
60
143
  }
61
- if (state.hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
62
- logger.warn(`Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
63
- return { action: "block", reason: t("hall.max_retries_exhausted") };
144
+ }
145
+ if (state.artifactClaimRetries < MAX_CLAIM_CHECK_RETRIES &&
146
+ deps.artifacts &&
147
+ deps.artifacts.length > 0) {
148
+ const artifact = checkArtifactClaims(text, deps.artifacts);
149
+ if (artifact) {
150
+ state.artifactClaimRetries++;
151
+ return this.retryClaim(deps, textContent, t("hall.claim_artifact", { path: artifact.path, detail: artifact.snippet }), `Artifact-claim contradiction: ${artifact.snippet}`);
64
152
  }
65
- state.hallucinationRetries++;
66
- logger.warn(`Hallucination retry (${state.hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${result.reason}`);
67
- if (textContent) {
68
- deps.contextManager.addMessage({ role: "assistant", content: textContent });
153
+ }
154
+ if (state.outputClaimRetries < MAX_CLAIM_CHECK_RETRIES) {
155
+ const unverified = checkUnverifiedOutput(text, evidence);
156
+ if (unverified) {
157
+ state.outputClaimRetries++;
158
+ return this.retryClaim(deps, textContent, t("hall.claim_output_unverified", { detail: unverified.snippet }), `Unverified output: ${unverified.snippet}`);
69
159
  }
70
- deps.contextManager.addMessage({
71
- role: "user",
72
- content: `<system-summary>[Retry context: ${result.reason}. Original task: "${deps.input}". You must either call a needed tool or provide a substantive response. Empty replies are not allowed.]</system-summary>`,
73
- });
74
- return { action: "retry" };
75
160
  }
76
- return { action: "accept" };
161
+ return null;
162
+ }
163
+ /**
164
+ * Общий инжект claim-повтора: фиксирует отклонённый ответ, дописывает
165
+ * причину как `<system-summary>` и просит цикл о следующем ходе.
166
+ */
167
+ retryClaim(deps, textContent, reason, logLine) {
168
+ const { state, logger } = deps;
169
+ state.iterHallucinationRetry = true;
170
+ // Переответ пересказывает выполненную задачу и естественно повторяется —
171
+ // бюджет халлюцинаций на его форму не жжём (та же логика, что даунгрейд
172
+ // в applyRetry после отклонения аудита).
173
+ state.suppressRepetitionRetry = true;
174
+ logger.diagnostic("warn", logLine);
175
+ if (textContent) {
176
+ deps.contextManager.addMessage({ role: "assistant", content: textContent });
177
+ }
178
+ deps.contextManager.addMessage({
179
+ role: "user",
180
+ content: `<system-summary>${reason} Original task: "${deps.input}".</system-summary>`,
181
+ });
182
+ return { action: "retry" };
77
183
  }
78
184
  displayWarnLine(reason, deps) {
185
+ // Диагностика: в чате только пользовательские уведомления; модель всё равно
186
+ // получает retry-контекст, вердикт остаётся в логах сессии/приложения.
187
+ if (!deps.verbose)
188
+ return;
79
189
  const warnLine = `${t("hall.uncertainty_prefix").trim()} ${reason ?? ""}`;
80
190
  if (deps.onMeta) {
81
191
  deps.onMeta(`\n${pc.yellow(warnLine)}\n`);
@@ -1,4 +1,6 @@
1
+ /** Состояние одного запуска агентного цикла: счётчики, бюджеты, сигналы. */
1
2
  import { FORCED_COMPACTION_COOLDOWN } from "./constants";
3
+ import { createArtifactChurnState } from "./artifact-progress";
2
4
  /** Фабрика начального состояния цикла. */
3
5
  export function createLoopState() {
4
6
  return {
@@ -7,14 +9,22 @@ export function createLoopState() {
7
9
  lastModelText: "",
8
10
  lastForcedCompactionIteration: -FORCED_COMPACTION_COOLDOWN,
9
11
  hallucinationRetries: 0,
12
+ claimCheckRetries: 0,
13
+ artifactClaimRetries: 0,
14
+ outputClaimRetries: 0,
10
15
  lastToolSignature: "",
11
16
  consecutiveToolFailures: 0,
17
+ consecutiveFailedBatches: 0,
18
+ honestStopNudged: false,
12
19
  consecutiveToolSuccesses: 0,
13
20
  repeatedToolCount: 0,
14
21
  toolFailureCounts: new Map(),
15
22
  memoryRuleRecorded: new Set(),
16
23
  toolTargetCounts: new Map(),
17
24
  cycleNudgedKeys: new Set(),
25
+ artifactChurn: createArtifactChurnState(),
26
+ sawSecurityBlock: false,
27
+ scriptWorkaroundNudged: false,
18
28
  apiPromptTokens: 0,
19
29
  apiCompletionTokens: 0,
20
30
  apiCompletionChars: 0,
@@ -26,8 +36,13 @@ export function createLoopState() {
26
36
  cacheSource: "none",
27
37
  auditRetries: 0,
28
38
  lastAuditSummary: "",
39
+ criticCalls: 0,
40
+ criticRepairCycles: 0,
41
+ mutationsSinceCritic: 0,
42
+ seenCriticIssues: new Set(),
29
43
  emptyResponseRetries: 0,
30
44
  emptyResponseExhausted: false,
45
+ retryLoopStopped: false,
31
46
  auditFailed: false,
32
47
  finalAnswerAccepted: false,
33
48
  suppressRepetitionRetry: false,
@@ -1,4 +1,4 @@
1
- // src/core/agent/prefix-monitor
1
+ /** Монитор стабильности префикса промпта (клиентская оценка кеша). */
2
2
  /**
3
3
  * Клиентский монитор стабильности префикса промпта.
4
4
  *
@@ -1,3 +1,4 @@
1
+ /** Резолвер уровня reasoning на итерацию: override → политика → фикс. */
1
2
  import { evaluateReasoningPolicy } from "../../modules/reasoning/policy";
2
3
  /**
3
4
  * Резолвер уровня reasoning на итерацию.
@@ -15,6 +16,11 @@ import { evaluateReasoningPolicy } from "../../modules/reasoning/policy";
15
16
  * clearIterationSignals сразу после resolve).
16
17
  */
17
18
  export class ReasoningEffortResolver {
19
+ /**
20
+ * Вычисляет уровень reasoning на итерацию (override → политика → фикс).
21
+ * @param deps состояние цикла, политика, конфиг, probe, стратегия.
22
+ * @returns уровень, стратегия и источник решения.
23
+ */
18
24
  resolve(deps) {
19
25
  const { state, reasoningConfig, reasoningState } = deps;
20
26
  let level;
@@ -42,7 +48,10 @@ export class ReasoningEffortResolver {
42
48
  planCreated: state.iterPlanCreated,
43
49
  consecutiveSuccesses: state.consecutiveToolSuccesses,
44
50
  isRepetitive: state.iterRepetitive,
45
- baseline: reasoningConfig?.baseline ?? "medium",
51
+ // Baseline — настроенный минимум (`min`, по умолчанию "low"),
52
+ // чтобы конфиг без явного baseline не жёг "medium" на пустяках.
53
+ // Политика всё равно повышает уровень по сигналам.
54
+ baseline: reasoningConfig.baseline ?? reasoningConfig.min,
46
55
  }, deps.policyState);
47
56
  level = policyResult.level;
48
57
  source = "policy";
@@ -14,10 +14,10 @@ import { estimateTokens } from "../../llm/token-counter";
14
14
  */
15
15
  export class TokenTracker {
16
16
  costTracker;
17
- getProviderName;
18
- constructor(costTracker, getProviderName) {
17
+ getProvenance;
18
+ constructor(costTracker, getProvenance) {
19
19
  this.costTracker = costTracker;
20
- this.getProviderName = getProviderName;
20
+ this.getProvenance = getProvenance;
21
21
  }
22
22
  /**
23
23
  * Начало итерации: фиксирует baseline API-токенов и аккумулирует оценку
@@ -64,7 +64,10 @@ export class TokenTracker {
64
64
  state.cacheWriteTotal += state.cacheUsage.cacheWriteTokens;
65
65
  state.cacheSource = state.cacheUsage.source;
66
66
  }
67
+ const prov = this.getProvenance();
67
68
  slog.logLlmUsage(iteration, {
69
+ provider: prov.provider,
70
+ model: prov.model,
68
71
  promptTokens: prompt,
69
72
  completionTokens: completion,
70
73
  totalTokens: prompt + completion,
@@ -75,7 +78,7 @@ export class TokenTracker {
75
78
  });
76
79
  // Кеш учитываем только когда цифры пришли от API: оценка chars/4 о кеше
77
80
  // ничего не знает и не должна менять тарификацию.
78
- this.costTracker.record(prompt, completion, this.getProviderName(), source === "api" ? state.cacheUsage : undefined);
81
+ this.costTracker.record(prompt, completion, prov.provider, source === "api" ? state.cacheUsage : undefined);
79
82
  }
80
83
  /**
81
84
  * Финальные метрики результата. API-токены в приоритете; иначе — сумма
@@ -1,8 +1,10 @@
1
+ /** Исполнитель пакета тулов: жизненный цикл вызовов за итерацию. */
1
2
  import { pc } from "../../ui/colors";
2
3
  import { t } from "../../i18n/index";
3
- import { summarizeToolArgs, truncateToolOutput, compactToolError } from "./tool-output";
4
+ import { summarizeToolArgs, truncateToolOutput, toolFailureSummary, isExecutableScriptPath, } from "./tool-output";
5
+ import { queueToolImages } from "./tool-images";
4
6
  import { errMsg } from "../../utils";
5
- import { MAX_CONSECUTIVE_TOOL_FAILURES, MIN_REPEATED_TOOL_FAILURES, } from "./constants";
7
+ import { MAX_CONSECUTIVE_TOOL_FAILURES, MIN_REPEATED_TOOL_FAILURES, MAX_CONSECUTIVE_FAILED_BATCHES, HARD_STOP_FAILED_BATCHES } from "./constants";
6
8
  /**
7
9
  * Выполнение пакета тулов, полученных от модели за одну итерацию.
8
10
  *
@@ -24,6 +26,13 @@ export class ToolBatchExecutor {
24
26
  constructor(deps) {
25
27
  this.deps = deps;
26
28
  }
29
+ /**
30
+ * Выполняет пакет tool_calls одной итерации (контекст, UI, учёт неудач).
31
+ * @param state состояние цикла; @param toolCalls вызовы от модели;
32
+ * @param boundedToolNames тулы с собственным лимитом вывода;
33
+ * @param textContent/reasoningContent/input тексты хода;
34
+ * @returns флаг прерывания по shutdown.
35
+ */
27
36
  async execute(state, toolCalls, boundedToolNames, textContent, reasoningContent, input, onTool, onMeta, slog) {
28
37
  const { config, pluginManager, contextManager, logger } = this.deps;
29
38
  const iteration = state.iteration;
@@ -62,9 +71,13 @@ export class ToolBatchExecutor {
62
71
  };
63
72
  const summaries = [];
64
73
  let anyToolFailed = false;
74
+ let batchHasSuccess = false;
65
75
  // Информационные read-only тулы, чей "фейл" — ожидаемое поведение
66
76
  // (file_info "Not found" после delete_file) — исключены из учёта неудач.
67
77
  const infoTools = new Set(["file_info"]);
78
+ // Флаг блока безопасности действует в пределах текущего пакета вызовов:
79
+ // иначе старый блок влиял бы на nudge через десятки итераций.
80
+ state.sawSecurityBlock = false;
68
81
  for (const call of toolCalls) {
69
82
  this.deps.setScope();
70
83
  const startTime = Date.now();
@@ -77,6 +90,9 @@ export class ToolBatchExecutor {
77
90
  onTool?.({ type: "start", tool: call.name, args: call.arguments, icon: toolIcon });
78
91
  slog?.logToolCall(call, iteration);
79
92
  const tokensBeforeTool = contextManager.getEstimatedTokens();
93
+ // Снимок стоимости ДО тула: рендеру нужна стоимость именно этого вызова
94
+ // (subagent/mcp/pipeline тратят токены), а не накопленная за сессию.
95
+ const costBeforeTool = this.deps.costTracker.total;
80
96
  let result;
81
97
  try {
82
98
  result = await this.deps.toolExecutor.execute(call, this.deps.getAbortSignal());
@@ -89,7 +105,7 @@ export class ToolBatchExecutor {
89
105
  synthesizePendingToolResults();
90
106
  throw err;
91
107
  }
92
- logger.warn(`Tool ${call.name} threw: ${errMsg(err)}`);
108
+ logger.diagnostic("warn", `Tool ${call.name} threw: ${errMsg(err)}`);
93
109
  result = {
94
110
  success: false,
95
111
  output: `${t("error.prefix")}${errMsg(err)}`,
@@ -104,6 +120,8 @@ export class ToolBatchExecutor {
104
120
  const duration = Date.now() - startTime;
105
121
  if (!result.success && !result.blocked && !infoTools.has(call.name))
106
122
  anyToolFailed = true;
123
+ if (result.success)
124
+ batchHasSuccess = true;
107
125
  if (result.success && call.name === "plan") {
108
126
  const action = String(call.arguments.action ?? "");
109
127
  if (action === "create" || action === "re-plan")
@@ -122,28 +140,34 @@ export class ToolBatchExecutor {
122
140
  // Проверка наличия, а не истинности: read_file возвращает display ""
123
141
  // на полном чтении (заголовок уже показывает путь) — фолбэк на пустую
124
142
  // строку вывалил бы всё содержимое файла в REPL.
143
+ // kind="tool": тела тулов model-facing, в quiet-чатах скрываются
144
+ // (статусы контекста/компакции идут с kind по умолчанию и всегда видны).
145
+ const TOOL_META = "tool";
125
146
  if (result.display !== undefined) {
126
147
  if (result.display.length > 0) {
127
- onMeta?.("\n" + result.display + "\n");
148
+ onMeta?.(`\n${result.display}\n`, TOOL_META);
128
149
  }
129
150
  }
130
151
  else if (result.success) {
131
152
  const metaOut = pluginManager.runOnMeta({ iteration, logger, contextManager }, result.output);
132
- onMeta?.("\n" + pc.dim(metaOut) + "\n");
153
+ onMeta?.(`\n${pc.dim(metaOut)}\n`, TOOL_META);
133
154
  }
134
155
  else {
135
- // Failed tool: its `output` is model-facing (Hints, recovery
136
- // directives, "Do NOT …"). The user only needs the gist — the first
137
- // line — not the instructions written for the agent.
138
- const line = compactToolError(result.output);
139
- const metaOut = line
140
- ? pluginManager.runOnMeta({ iteration, logger, contextManager }, line)
156
+ // Упавший тул: `output` — model-facing (хинты, recovery-директивы,
157
+ // «Do NOT …»). Пользователю нужна спокойная суть — первая строка,
158
+ // а не инструкции для агента. Мягкие гарды — варнинги, не ошибки.
159
+ const failure = toolFailureSummary(result.output);
160
+ const metaOut = failure.text
161
+ ? pluginManager.runOnMeta({ iteration, logger, contextManager }, failure.text)
141
162
  : "";
142
- if (metaOut)
143
- onMeta?.("\n" + pc.red(metaOut) + "\n");
163
+ if (metaOut) {
164
+ const color = failure.severity === "warn" ? pc.yellow : pc.red;
165
+ const glyph = failure.severity === "warn" ? "⚠" : "✗";
166
+ onMeta?.(`\n${color(`${glyph} ${metaOut}`)}\n`, TOOL_META);
167
+ }
144
168
  }
145
169
  if (result.diff) {
146
- onMeta?.("\n" + result.diff + "\n");
170
+ onMeta?.(`\n${result.diff}\n`, TOOL_META);
147
171
  }
148
172
  const currentTokens = contextManager.getEstimatedTokens();
149
173
  const budget = contextManager.getBudget();
@@ -158,17 +182,40 @@ export class ToolBatchExecutor {
158
182
  arguments: call.arguments,
159
183
  });
160
184
  answeredToolCallIds.add(call.id);
185
+ // Изображения тула (browser screenshot и т.п.) встают в очередь и
186
+ // прицепятся к следующему user-сообщению — обычно к <system-summary>
187
+ // в конце этого же batch'а. В role:"tool" строку base64 не пишем.
188
+ queueToolImages(contextManager, result.images);
161
189
  const tokensAfterTool = contextManager.getEstimatedTokens();
190
+ const costAfterTool = this.deps.costTracker.total;
162
191
  onTool?.({
163
192
  type: "end",
164
193
  tool: call.name,
165
194
  args: call.arguments,
166
195
  duration,
167
196
  error: !result.success,
197
+ output: typeof result.output === "string" ? result.output : undefined,
168
198
  ctxDelta: tokensAfterTool - tokensBeforeTool,
169
- costUsd: this.deps.costTracker.total,
199
+ costUsd: costBeforeTool !== undefined && costAfterTool !== undefined
200
+ ? costAfterTool - costBeforeTool
201
+ : undefined,
170
202
  });
171
203
  summaries.push(`[Tool: ${call.name}${summarizeToolArgs(call.arguments)} → ${truncatedOutput.slice(0, 200)}]`);
204
+ // Видели блок безопасности? Запоминаем для эвристики скрипта-обёртки.
205
+ if (typeof result.output === "string" && result.output.includes("[SECURITY BLOCKED]")) {
206
+ state.sawSecurityBlock = true;
207
+ }
208
+ // Скрипт-обход: после блока безопасности запись shell-скрипта
209
+ // (.bat/.cmd/.sh/.ps1) почти всегда — попытка обойти заблокированный
210
+ // оператор; nudge один раз, обобщённо (без привязки к тулу/языку).
211
+ if (state.sawSecurityBlock &&
212
+ !state.scriptWorkaroundNudged &&
213
+ call.name === "write_file" &&
214
+ typeof call.arguments.path === "string" &&
215
+ isExecutableScriptPath(call.arguments.path)) {
216
+ state.scriptWorkaroundNudged = true;
217
+ summaries.push(t("exec.script_workaround_nudge"));
218
+ }
172
219
  if (config.session.autoSave) {
173
220
  slog?.logToolResult(call, result, duration, iteration);
174
221
  }
@@ -188,7 +235,7 @@ export class ToolBatchExecutor {
188
235
  const memStore = this.deps.memoryStore;
189
236
  if (memStore) {
190
237
  memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
191
- logger.warn(`Recorded repeated ${key} failures to memory (${prev.count}x)`);
238
+ logger.diagnostic("warn", `Recorded repeated ${key} failures to memory (${prev.count}x)`);
192
239
  }
193
240
  }
194
241
  }
@@ -210,8 +257,8 @@ export class ToolBatchExecutor {
210
257
  const recoveryMsg = t("exec.consecutive_failures_recovery", {
211
258
  count: state.consecutiveToolFailures,
212
259
  });
213
- logger.warn(`Consecutive tool failures: ${state.consecutiveToolFailures}`);
214
- const taskSnippet = input.length > 200 ? input.slice(0, 200) + "..." : input;
260
+ logger.diagnostic("warn", `Consecutive tool failures: ${state.consecutiveToolFailures}`);
261
+ const taskSnippet = input.length > 200 ? `${input.slice(0, 200)}...` : input;
215
262
  const taskReminder = t("exec.task_reminder", { task: taskSnippet });
216
263
  contextManager.addMessage({
217
264
  role: "user",
@@ -222,6 +269,34 @@ export class ToolBatchExecutor {
222
269
  memStore.appendRule("errors", `${state.consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
223
270
  }
224
271
  }
272
+ // Honest-stop полностью провальных пакетов: каждый вызов пакета — неудача
273
+ // или tool-level блок (в отличие от consecutiveToolFailures выше, блоки
274
+ // ВХОДЯТ в учёт — перебор вариантов заблокированной команды это та же
275
+ // петля). Смешанные пакеты и успехи сбрасывают стрик: связка
276
+ // «упал → поправил → упал» (дебаг) под него не попадает.
277
+ // Plugin-level блоки (result.blocked, guard-rails) нейтральны по контракту
278
+ // ToolResult.blocked — не успех и не неудача.
279
+ if (anyToolFailed && !batchHasSuccess) {
280
+ state.consecutiveFailedBatches++;
281
+ }
282
+ else if (batchHasSuccess) {
283
+ state.consecutiveFailedBatches = 0;
284
+ state.honestStopNudged = false;
285
+ }
286
+ if (state.consecutiveFailedBatches >= MAX_CONSECUTIVE_FAILED_BATCHES &&
287
+ !state.honestStopNudged) {
288
+ state.honestStopNudged = true;
289
+ logger.diagnostic("warn", `Consecutive failed tool batches: ${state.consecutiveFailedBatches} — honest-stop directive`);
290
+ contextManager.addMessage({
291
+ role: "user",
292
+ content: `<system-summary>${t("exec.honest_stop", { count: state.consecutiveFailedBatches })}</system-summary>`,
293
+ });
294
+ }
295
+ if (state.consecutiveFailedBatches >= HARD_STOP_FAILED_BATCHES) {
296
+ state.retryLoopStopped = true;
297
+ logger.diagnostic("warn", `Retry loop hard-stopped after ${state.consecutiveFailedBatches} consecutive failed batches`);
298
+ return { interrupted: true };
299
+ }
225
300
  // Статусная строка плана после каждого пакета тулов. Заменяет динамический
226
301
  // plan-блок, который жил в system prompt (удалён ради KV-cache — план
227
302
  // меняется каждую итерацию).
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Ставит изображения, произведённые тулом, в очередь pending-изображений
3
+ * контекста. Они прикрепятся к следующему user-сообщению (обычно это
4
+ * `<system-summary>` с итогами пакета тулов в конце batch'а) — так картинка
5
+ * доходит до модели как валидный image-part, а не как base64-текст в
6
+ * `role:"tool"` (который и не портируем, и усекается бюджетом).
7
+ *
8
+ * Намеренно не складывает изображения в промежуточные tool-сообщения:
9
+ * вставка user-сообщения между результатами одного assistant(tool_calls)
10
+ * нарушает порядок, требуемый Jinja-шаблонами (см. architecture.md).
11
+ */
12
+ export function queueToolImages(sink, images) {
13
+ if (!sink || !images || images.length === 0)
14
+ return;
15
+ for (const image of images) {
16
+ sink.addPendingImage(image);
17
+ }
18
+ }