micro-models-agent 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -6,25 +6,25 @@ import { toForwardSlash } from "../../tools/path-utils";
6
6
  import { platform } from "os";
7
7
  import { switchStepToDelete } from "./plan-steps";
8
8
  /**
9
- * Cooldown (in iterations) between stuck-recovery injections. Exported so the
10
- * ExecutionModule can seed its state's lastRecoveryIteration with the same
11
- * offset the plugin compares against.
9
+ * Кулдаун (в итерациях) между инжектами stuck-восстановления. Экспортируется,
10
+ * чтобы ExecutionModule сидировал state.lastRecoveryIteration тем же офсетом,
11
+ * с которым плагин сравнивает.
12
12
  */
13
13
  export const STUCK_RECOVERY_COOLDOWN = 5;
14
14
  const MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3;
15
15
  const FORCE_SKIP_THRESHOLD = 10;
16
- /** Re-log the SAME stuck-warning key at most every N iterations (a new key
17
- * always logs immediately). Prevents the identical warning from flooding
18
- * every iteration (ses_mt4fn58c: "Tool bash failed N times" after ~30
19
- * successful writes, "No write/exec" on every read). */
16
+ /** Один и тот же stuck-варнинг перелоггируем не чаще каждых N итераций (новый
17
+ * ключ — всегда сразу). Не даёт идентичному варнингу флудить каждую итерацию
18
+ * (было в ses_mt4fn58c: "Tool bash failed N times" после ~30 успешных записей,
19
+ * "No write/exec" на каждом чтении). */
20
20
  export const STUCK_WARN_REPEAT_EVERY = 5;
21
- /** How many file-mutating tool calls (write/edit/bash/download) without an
22
- * active plan trigger the plan-creation nudge. Evidence-based (rule #10):
23
- * the agent is clearly working on files, so a plan is warranted. */
21
+ /** Сколько файл-мутирующих вызовов (write/edit/bash/download) без активного
22
+ * плана триггерит nudge создания плана. Evidence-based (правило #10):
23
+ * агент явно работает с файлами — план оправдан. */
24
24
  const PLAN_NUDGE_THRESHOLD = 2;
25
- /** Tools whose success can have changed the filesystem — the plan auto-advance
26
- * re-checks the current step's deliverables after them. Read-only tools are
27
- * deliberately excluded (see the onAfterTool advance call below). */
25
+ /** Тулы, чей успех мог поменять ФС — после них перепроверяем deliverables
26
+ * текущего шага для автопродвижения. Read-only осознанно исключены (см. вызов
27
+ * advance в onAfterTool ниже). */
28
28
  const FS_MUTATING_TOOLS = new Set([
29
29
  "write_file",
30
30
  "edit_file",
@@ -41,8 +41,8 @@ const FS_MUTATING_TOOLS = new Set([
41
41
  function normalizeBrokenPath(p) {
42
42
  return toForwardSlash(p).replace(/^\.\//, "");
43
43
  }
44
- /** Parse a tsc / bun build / node --check error line into its file path.
45
- * Returns null when the line carries no file anchor (e.g. `error: Could not
44
+ /** Парсит строку ошибки tsc / bun build / node --check в путь файла.
45
+ * Null, если в строке нет привязки к файлу (напр. `error: Could not
46
46
  * resolve "..."`). */
47
47
  export function parseBrokenFile(line) {
48
48
  const trimmed = line.trim();
@@ -56,9 +56,9 @@ export function parseBrokenFile(line) {
56
56
  return normalizeBrokenPath(bn[1]);
57
57
  return null;
58
58
  }
59
- /** Extract per-file compile failures from a write/edit result output. Only
60
- * the marker lines are inspected (`[Project typecheck failed]: <line>` /
61
- * `[Syntax check failed]: <line>`) so a diff body can never false-positive. */
59
+ /** Вытаскивает пофайловые compile-фейлы из вывода write/edit. Смотрит только
60
+ * строки-маркеры (`[Project typecheck failed]: <line>` /
61
+ * `[Syntax check failed]: <line>`), чтобы тело диффа не давало ложных. */
62
62
  export function extractBrokenFiles(output) {
63
63
  const out = [];
64
64
  for (const line of output.split("\n")) {
@@ -75,6 +75,7 @@ function resetStepFlags(state, stepId) {
75
75
  state.consecutivePlanWarnings = 0;
76
76
  state.lastStepId = stepId;
77
77
  state.stuckNotified = false;
78
+ state.escalationDirectiveSent = false;
78
79
  }
79
80
  /** Полный сброс состояния плана (завершён или его нет). */
80
81
  function resetPlanState(state) {
@@ -83,14 +84,14 @@ function resetPlanState(state) {
83
84
  state.stuckNotified = false;
84
85
  state.mutationsWithoutPlan = 0;
85
86
  state.planNudgeSent = false;
87
+ state.escalationDirectiveSent = false;
86
88
  }
87
89
  /**
88
- * Gate for `plan update status=done`: refuse while the last write/edit still
89
- * reports a compile error. A failure with no file anchor blocks the whole
90
- * project; otherwise a step's named file tokens are matched against the
91
- * broken files (path-suffix match, case-insensitive). A step naming no files
92
- * cannot be blamed — its done stays vacuous. Returns the first error to show
93
- * the model, or null to allow the done.
90
+ * Гейт для `plan update status=done`: реджектит, пока последний write/edit
91
+ * репортит compile-ошибку. Фейл без привязки к файлу блокирует весь проект;
92
+ * иначе именованные токены файлов шага матчатся против битых (суффикс пути,
93
+ * регистронезависимо). Шаг без файлов обвинить не в чем — его done остаётся
94
+ * vacuous. Возвращает первую ошибку для показа модели, или null чтобы пустить.
94
95
  */
95
96
  export function stepTypecheckGate(failures, stepDescription) {
96
97
  if (failures.size === 0)
@@ -109,28 +110,27 @@ export function stepTypecheckGate(failures, stepDescription) {
109
110
  return null;
110
111
  }
111
112
  /**
112
- * The execution plugin hooks, extracted verbatim from ExecutionModule.getPlugin().
113
- * All shared mutable state (pendingMessages, forbiddenBashFailures, state,
114
- * tracker) is owned by the module and mutated by reference through `deps`.
113
+ * Хуки execution-плагина, вынесенные дословно из ExecutionModule.getPlugin().
114
+ * Всё разделяемое мутабельное состояние (pendingMessages, forbiddenBashFailures,
115
+ * state, tracker) принадлежит модулю и мутируется по ссылке через `deps`.
115
116
  */
116
117
  export function createExecutionPlugin(deps) {
117
118
  return {
118
119
  name: "execution",
119
120
  onBeforeThink: (ctx) => {
120
- // Flush deferred <system-summary> messages first so they are visible
121
- // to the next model call but never interleave tool messages.
121
+ // Отложенные <system-summary> флашим первыми: видны следующему вызову
122
+ // модели, но не interleav'ят tool-сообщения.
122
123
  if (ctx.contextManager && deps.pendingMessages.length > 0) {
123
124
  for (const m of deps.pendingMessages.splice(0)) {
124
125
  ctx.contextManager.addMessage(m);
125
126
  }
126
127
  }
127
128
  if (deps.trackerRef.current?.isComplete()) {
128
- // All steps done/skipped — stop counting "no progress" against
129
- // the last step and silence step-stuck warnings for a finished
130
- // plan, BUT keep the tool-call history so a read-without-write
131
- // loop is still caught (observed: plan complete + false-positive
132
- // audit → the model re-read files for 50+ iterations with zero
133
- // writes and no recovery hint).
129
+ // Все шаги done/skipped — перестаём считать "no progress" против
130
+ // последнего шага и глушим step-stuck варнинги готового плана, НО
131
+ // историю вызовов храним, чтобы петля чтения-без-записи ловилась
132
+ // (было: план готов + ложноположительный аудит → модель 50+ итераций
133
+ // перечитывала файлы с нулем записей без recovery-хинта).
134
134
  deps.stuckDetector.resetStepProgress();
135
135
  resetPlanState(deps.state);
136
136
  }
@@ -147,12 +147,15 @@ export function createExecutionPlugin(deps) {
147
147
  deps.state.planNudgeSent = false;
148
148
  }
149
149
  else {
150
- deps.stuckDetector.reset();
150
+ // До-плана: историю цикла (свежие вызовы, read-only стрик) храним,
151
+ // чтобы петли повторов/read-only ловились и без плана. `reset()`
152
+ // тёр её каждую итерацию, отключая детекцию.
153
+ deps.stuckDetector.resetStepProgress();
151
154
  resetStepFlags(deps.state, -1);
152
- // Evidence-based plan nudge (rule #10 — no bare iteration counter,
153
- // no keyword task classification): only after the agent actually
154
- // made file-mutating tool calls (write/edit/bash/download) without
155
- // a plan does the agent need a plan.
155
+ // Evidence-based nudge плана (правило #10 — без голого счётчика
156
+ // итераций, без keyword-классификации задач): только когда агент
157
+ // реально сделал файл-мутирующие вызовы (write/edit/bash/download)
158
+ // без плана, ему нужен план.
156
159
  const mutations = deps.state.mutationsWithoutPlan;
157
160
  if (mutations >= PLAN_NUDGE_THRESHOLD &&
158
161
  !deps.state.planNudgeSent &&
@@ -169,10 +172,9 @@ export function createExecutionPlugin(deps) {
169
172
  }
170
173
  const stuckReason = deps.stuckDetector.getStuckReason();
171
174
  if (stuckReason) {
172
- // Log once per warning episode instead of spamming every iteration:
173
- // a new warning key logs immediately; the SAME key re-logs only every
174
- // STUCK_WARN_REPEAT_EVERY iterations. The step-stuck reason keeps its
175
- // dedicated once-per-episode flag.
175
+ // Логируем раз на эпизод варнинга, а не спамим каждую итерацию: новый
176
+ // ключ — сразу; ТОТ ЖЕ ключ — только каждые STUCK_WARN_REPEAT_EVERY
177
+ // итераций. Step-stuck причина хранит свой once-per-episode флаг.
176
178
  const isStuck = deps.stuckDetector.isStuck();
177
179
  const warnKey = deps.stuckDetector.getWarningKey();
178
180
  const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
@@ -194,12 +196,14 @@ export function createExecutionPlugin(deps) {
194
196
  }
195
197
  if (deps.stuckDetector.isStuck() ||
196
198
  deps.stuckDetector.hasRepetitiveToolCalls() ||
197
- deps.stuckDetector.hasReadOnlyLoop()) {
199
+ deps.stuckDetector.hasReadOnlyLoop() ||
200
+ deps.stuckDetector.hasBashFlailing() ||
201
+ deps.stuckDetector.hasConsecutiveFailures()) {
198
202
  const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
199
203
  if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
200
204
  const recovery = deps.stuckDetector.getRecoveryMessage();
201
205
  if (recovery && ctx.contextManager) {
202
- // Include tsc error context so the model knows WHY it is stuck.
206
+ // Прикладываем контекст tsc-ошибки, чтобы модель знала, ПОЧЕМУ застряла.
203
207
  let tscContext = "";
204
208
  if (deps.state.typecheckFailures.size > 0) {
205
209
  const currentStep = deps.trackerRef.current?.getCurrentStep();
@@ -218,21 +222,35 @@ export function createExecutionPlugin(deps) {
218
222
  }
219
223
  }
220
224
  }
225
+ // Префикс стабильной машиночитаемой классификации: модель видит,
226
+ // ПОЧЕМУ застряла, до локализованного recovery-гайданса.
227
+ const diagnostic = deps.stuckDetector.getStuckDiagnostic(currentIter);
221
228
  ctx.contextManager.addMessage({
222
229
  role: "user",
223
- content: `<system-summary>${recovery}${tscContext}</system-summary>`,
230
+ content: `<system-summary>${diagnostic ? `${diagnostic}\n` : ""}${recovery}${tscContext}</system-summary>`,
224
231
  });
225
232
  }
226
233
  deps.stuckDetector.recordEscalation();
227
234
  deps.state.lastRecoveryIteration = currentIter;
228
- if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
235
+ if (deps.stuckDetector.shouldEscalate()) {
229
236
  const escalation = t("exec.escalation", {
230
237
  stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
231
238
  description: deps.trackerRef.current?.getCurrentStep()?.description ?? "",
232
239
  });
233
- ctx.onMeta(escalation);
240
+ // Эскалируем МОДЕЛИ, а не только UI-баннеру: после нескольких попыток
241
+ // восстановления даём явные общие опции (сменить подход, поискать
242
+ // ошибку, спросить юзера, re-plan/skip) вместо повтора того же
243
+ // фейлящегося действия. Инжектится раз на эпизод.
244
+ if (!deps.state.escalationDirectiveSent && ctx.contextManager) {
245
+ deps.state.escalationDirectiveSent = true;
246
+ ctx.contextManager.addMessage({
247
+ role: "user",
248
+ content: `<system-summary>${t("exec.escalation_directive")}</system-summary>`,
249
+ });
250
+ }
251
+ ctx.onMeta?.(escalation);
234
252
  }
235
- // Force skip after too many iterations on the same step
253
+ // Форс-скип после слишком долгого сидения на одном шаге
236
254
  if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD &&
237
255
  ctx.contextManager) {
238
256
  const step = deps.trackerRef.current?.getCurrentStep();
@@ -261,8 +279,8 @@ export function createExecutionPlugin(deps) {
261
279
  role: "user",
262
280
  content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`,
263
281
  });
264
- // Block with an explicit reason so the model sees WHY the call
265
- // was blocked (executor shows it via tool.blocked_reason).
282
+ // Блок с явной причиной, чтобы модель видела, ПОЧЕМУ вызов
283
+ // заблокирован (executor показывает через tool.blocked_reason).
266
284
  return t("exec.plan_blocked", {
267
285
  step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
268
286
  max: MAX_PLAN_WARNINGS_BEFORE_BLOCK,
@@ -279,8 +297,8 @@ export function createExecutionPlugin(deps) {
279
297
  const args = ctx?.args;
280
298
  if (toolName && args) {
281
299
  deps.stuckDetector.recordToolCall(toolName, args);
282
- // Count file-mutating calls made with NO active plan — feeds the
283
- // evidence-based plan-creation nudge in onBeforeThink.
300
+ // Считаем файл-мутирующие вызовы БЕЗ активного плана — питают
301
+ // evidence-based nudge создания плана в onBeforeThink.
284
302
  if (!deps.trackerRef.current &&
285
303
  (toolName === "write_file" ||
286
304
  toolName === "edit_file" ||
@@ -291,14 +309,14 @@ export function createExecutionPlugin(deps) {
291
309
  }
292
310
  },
293
311
  onAfterTool: (ctx, call, result) => {
294
- // Remember every bash invocation (successful or not) so stuck
295
- // detection can reason about e.g. repeated runs with empty output.
312
+ // Запоминаем каждый вызов bash (успех или нет): детекция рассуждает,
313
+ // напр., о повторных прогонах с пустым выводом.
296
314
  if (call.name === "bash") {
297
315
  deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
298
316
  }
299
- // Auto-detect delete intent from model actions: when delete_file is
300
- // called, find the matching plan step and set kind="delete". This is
301
- // language-agnostic — based on what the model DID, not what it said.
317
+ // Авто-детект delete-намерения из действий модели: при вызове delete_file
318
+ // находим совпадающий шаг плана и ставим kind="delete". Языконезависимо —
319
+ // по СДЕЛАННОМУ моделью, не по сказанному.
302
320
  if (call.name === "delete_file" && result.success && deps.trackerRef.current) {
303
321
  const deletedPath = String(call.arguments?.path ?? "");
304
322
  if (deletedPath) {
@@ -316,21 +334,20 @@ export function createExecutionPlugin(deps) {
316
334
  }
317
335
  }
318
336
  }
319
- // A write/edit/bash that reports success but still carries a
320
- // type/syntax error in its output is NOT a success — the model must
321
- // fix the actual error, not keep rewriting the file blindly. Feed it
322
- // to the stuck detector so the rewrite-loop hint fires.
337
+ // write/edit/bash с успехом, но type/syntax-ошибкой в выводе — НЕ успех:
338
+ // модель чинит реальную ошибку, а не переписывает файл вслепую. Кормим
339
+ // stuck-детектор, чтобы сработал хинт rewrite-петли.
323
340
  const toolText = String(result.output ?? "");
324
341
  const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
325
342
  if (hasTypeError) {
326
343
  deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
327
344
  }
328
- // Track compile failures per-file so the plan done-gate can refuse
329
- // "done" while the last edit still doesn't compile (observed: the agent
330
- // marked plan steps done while the project typecheck failed on every
331
- // write — a "fix it later" cascade). A CLEAN result clears the map: the
332
- // debounced project tsc re-ran and passed (or no tsconfig applies), so
333
- // old failures must not block completion forever.
345
+ // Трекаем compile-фейлы пофайлово: done-gate плана реджектит "done", пока
346
+ // последняя правка не компилируется (было: агент маркил шаги done, а
347
+ // проектный typecheck фейлился на каждой записи — каскад "починю позже").
348
+ // ЧИСТЫЙ результат чистит карту: дебаунснутый проектный tsc перегнал и
349
+ // прошёл (или tsconfig неприменим) — старые фейлы завершение вечно не
350
+ // блокируют.
334
351
  if (call.name === "write_file" || call.name === "edit_file") {
335
352
  const broken = extractBrokenFiles(toolText);
336
353
  if (broken.length > 0) {
@@ -338,22 +355,34 @@ export function createExecutionPlugin(deps) {
338
355
  deps.state.typecheckFailures.set(b.file, b.error);
339
356
  }
340
357
  else if (result.success) {
341
- deps.state.typecheckFailures.clear();
358
+ // Чистим только записи только что писанного/правленного файла (и
359
+ // проектную безадресную запись, которую чистая правка вероятно
360
+ // закрыла). Сплошной `.clear()` стирал несвязанный успешной правкой
361
+ // чужой фейл, обходя done-gate.
362
+ const edited = normalizeBrokenPath(String(call.arguments?.path ?? ""));
363
+ for (const key of [...deps.state.typecheckFailures.keys()]) {
364
+ if (key === "" ||
365
+ !edited ||
366
+ key === edited ||
367
+ key.endsWith(edited) ||
368
+ edited.endsWith(key)) {
369
+ deps.state.typecheckFailures.delete(key);
370
+ }
371
+ }
342
372
  }
343
373
  }
344
374
  if (!result.success) {
345
- // Feed the bash attempt outcome into the flailing window BEFORE the
346
- // per-command handling — rotation across different failing commands
347
- // (wc → cat → Get-Content → …) must still trip the detector.
375
+ // Исход bash-попытки кормим во флейлинг-окно ДО пер-командной обработки —
376
+ // ротация разных фейлящихся команд (wc → cat → Get-Content → …) обязана
377
+ // триггерить детектор.
348
378
  if (call.name === "bash") {
349
379
  deps.stuckDetector.recordBashAttempt(false);
350
380
  }
351
- // Hard stop for forbidden Windows commands: after the second
352
- // failure of the same forbidden command (grep/sed/ls/find/…),
353
- // inject a STOP message so the session stops burning bash calls
354
- // on commands cmd.exe does not have. Mirrors the bash tool's own
355
- // win32-gated hard-stop (src/tools/bash.ts) — on macOS/Linux
356
- // these commands exist and fail for legit reasons.
381
+ // Hard stop для запрещённых Windows-команд: после второго фейла той же
382
+ // запрещённой команды (grep/sed/ls/find/…) инжектим STOP, чтобы сессия
383
+ // не жгла bash-вызовы на командах без cmd.exe. Зеркалит собственный
384
+ // win32-gated hard-stop тула bash (src/tools/bash.ts) — на macOS/Linux
385
+ // эти команды есть и фейлятся по легитимным причинам.
357
386
  if (call.name === "bash" && platform() === "win32") {
358
387
  const cmd = String(call.arguments?.command ?? "");
359
388
  const forbidden = forbiddenWindowsCommand(cmd);
@@ -368,36 +397,48 @@ export function createExecutionPlugin(deps) {
368
397
  }
369
398
  }
370
399
  }
371
- // A type/syntax error in an otherwise "successful" result was already
372
- // recorded above. A failing call (result.success === false) would
373
- // otherwise double-count the same error (typecheck branch + failure
374
- // branch), inflating the error-signature count and triggering the
375
- // web search at half the intended threshold.
400
+ // Type/syntax-ошибка в иначе "успешном" результате уже записана выше.
401
+ // Фейлящийся вызов (result.success === false) иначе посчитал бы ту же
402
+ // ошибку дважды (typecheck-ветка + failure-ветка), раздув счётчик
403
+ // error-сигнатур и триггернув веб-поиск на половине порога.
376
404
  if (!hasTypeError) {
377
405
  deps.stuckDetector.recordToolError(call.name, result.output);
378
406
  }
379
407
  }
380
408
  else {
381
- deps.stuckDetector.recordToolSuccess();
409
+ // "Успешная" запись/правка может нести type/syntax-ошибку (lint-on-write
410
+ // хук аппендит её, оставляя success:true). Скормленный здесь
411
+ // recordToolSuccess() отменял recordToolError выше на том же вызове —
412
+ // rewrite-loop сигнал не накапливался.
413
+ if (!hasTypeError)
414
+ deps.stuckDetector.recordToolSuccess();
382
415
  if (call.name === "bash") {
383
416
  deps.stuckDetector.recordBashAttempt(true);
384
417
  }
385
- // When a bash command runs code successfully, suggest marking the
386
- // step done — unless the output shows failing tests, which must
387
- // never be reported as a clean success.
418
+ // Успешно отработавшая команда bash с кодом — предлагаем маркить шаг
419
+ // done, если только вывод не показывает упавшие тесты (их нельзя
420
+ // репортить чистым успехом).
388
421
  if (call.name === "bash" && result.success) {
389
422
  const cmd = String(call.arguments?.command ?? "");
390
423
  const testRun = detectTestResults(String(result.output ?? ""));
391
424
  if (testRun && testRun.failed > 0) {
392
425
  deps.pendingMessages.push({
393
426
  role: "user",
394
- content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`,
427
+ content: `<system-summary>${t("exec.test_fail_summary", {
428
+ framework: testRun.framework,
429
+ failed: String(testRun.failed),
430
+ passed: String(testRun.passed),
431
+ })}</system-summary>`,
395
432
  });
396
433
  }
397
434
  else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
398
435
  deps.pendingMessages.push({
399
436
  role: "user",
400
- content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`,
437
+ content: `<system-summary>${t("exec.test_pass_summary", {
438
+ framework: testRun.framework,
439
+ passed: String(testRun.passed),
440
+ cmd,
441
+ })}</system-summary>`,
401
442
  });
402
443
  }
403
444
  }
@@ -418,20 +459,19 @@ export function createExecutionPlugin(deps) {
418
459
  }
419
460
  }
420
461
  }
421
- // Auto-advance fires after every successful FILESYSTEM-MUTATING tool —
422
- // not just write_file/edit_file. Small models often create files through
423
- // bash (`echo ... > f`, heredocs) or delete/move via shell instead of the
424
- // dedicated tools, and the plan then never advances past the step even
425
- // though its deliverables exist (certification 3.5/3.8: files on disk,
426
- // plan stuck at 1/4 and 4/5). Read-only tools MUST stay excluded: a
427
- // successful read/glob would otherwise complete an "Update X" step whose
428
- // target file already exists from an earlier step — without any edit.
429
- // advancePlanIfStepComplete is a no-op when tokens don't resolve.
462
+ // Автопродвижение срабатывает после каждого успешного ФС-МУТИРУЮЩЕГО
463
+ // тула, не только write_file/edit_file. Мелкие модели часто создают файлы
464
+ // через bash (`echo ... > f`, heredoc'и) или delete/move через shell
465
+ // вместо выделенных тулов — план тогда не двигается с шага, хотя
466
+ // deliverables на диске (сертификация 3.5/3.8: файлы на месте, план
467
+ // висит на 1/4 и 4/5). Read-only тулы исключены: успешный read/glob иначе
468
+ // закрыл бы шаг "Update X", чей файл существует с раннего шага, — без
469
+ // единой правки. advancePlanIfStepComplete — no-op, если токены не резолвятся.
430
470
  if (result.success && FS_MUTATING_TOOLS.has(call.name)) {
431
- deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
471
+ deps.advancePlanIfStepComplete(ctx.sessionLog);
432
472
  }
433
- // Automatic web search for a repeatedly failing error (fire-and-forget,
434
- // results land in pendingMessages on the next onBeforeThink flush).
473
+ // Автопоиск в вебе повторяющейся ошибки (fire-and-forget, результаты
474
+ // падают в pendingMessages на следующем флаше onBeforeThink).
435
475
  deps.maybeSearchError(ctx, call);
436
476
  },
437
477
  };
@@ -1,8 +1,8 @@
1
+ // Модуль исполнения: реэкспорт планов, трекера, верификатора, stuck-детектора, аудитора.
1
2
  export { ExecutionModule } from "./module";
2
3
  export { PlanCreator } from "./planner";
3
4
  export { PlanTracker } from "./tracker";
4
5
  export { StepVerifier } from "./verifier";
5
6
  export { StuckDetector } from "./stuck-detector";
6
7
  export { Auditor } from "./auditor";
7
- export { PlanPersister } from "./plan-persister";
8
8
  export { PlanStore } from "./plan-store";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Accept both compact (`["task-1"]`) and detailed
3
- * (`[{"subtaskId": "task-1", "field": "json"}]`) forms of input_from.
2
+ * Принимает обе формы input_from: компактную (`["task-1"]`) и детальную
3
+ * (`[{"subtaskId": "task-1", "field": "json"}]`).
4
4
  */
5
5
  export function normalizeInputFrom(input) {
6
6
  if (!Array.isArray(input))
@@ -10,8 +10,14 @@ export function normalizeInputFrom(input) {
10
10
  if (typeof entry === "string") {
11
11
  refs.push({ subtaskId: entry });
12
12
  }
13
- else if (entry && typeof entry === "object" && typeof entry.subtaskId === "string") {
14
- refs.push({ subtaskId: entry.subtaskId, field: entry.field });
13
+ else if (entry && typeof entry === "object") {
14
+ const obj = entry;
15
+ if (typeof obj.subtaskId === "string") {
16
+ refs.push({
17
+ subtaskId: obj.subtaskId,
18
+ field: typeof obj.field === "string" ? obj.field : undefined,
19
+ });
20
+ }
15
21
  }
16
22
  }
17
23
  return refs;
@@ -23,24 +29,42 @@ function truncate(text, cap) {
23
29
  return `${flat.slice(0, cap)}…`;
24
30
  }
25
31
  /**
26
- * Render the "Input from previous tasks" section injected into a dependent
27
- * subtask's prompt. Failed dependencies are marked explicitly so the reader
28
- * cannot build logic on missing data.
32
+ * Рендерит секцию "Input from previous tasks" для промпта зависимой
33
+ * подзадачи. Упавшие зависимости помечаются явно, чтобы читатель не строил
34
+ * логику на отсутствующих данных.
29
35
  */
30
36
  export function buildInputSection(subtask, depResults, maxChars) {
31
37
  const refs = normalizeInputFrom(subtask.input_from);
32
38
  if (refs.length === 0)
33
39
  return "";
34
- const lines = refs.map((ref) => {
40
+ // Агрегатный кап: без него 10 зависимостей × maxChars раздувают промпт
41
+ // выше бюджета контекста маленькой модели.
42
+ const totalCap = maxChars * 4;
43
+ const lines = [];
44
+ let used = 0;
45
+ let omitted = 0;
46
+ for (const ref of refs) {
35
47
  const dep = depResults.get(ref.subtaskId);
36
48
  if (!dep) {
37
- return `- ${ref.subtaskId}: (no result available)`;
49
+ const line = `- ${ref.subtaskId}: (no result available)`;
50
+ lines.push(line);
51
+ used += line.length;
52
+ continue;
38
53
  }
39
54
  const label = ref.field ? `${ref.subtaskId}.${ref.field}` : ref.subtaskId;
40
- if (!dep.success) {
41
- return `- ${label}: [FAILED] ${truncate(dep.error || dep.summary || "unknown error", maxChars)}`;
55
+ const body = dep.success
56
+ ? truncate(dep.result || dep.summary, maxChars)
57
+ : `[FAILED] ${truncate(dep.error || dep.summary || "unknown error", maxChars)}`;
58
+ const line = `- ${label}: ${body}`;
59
+ if (used + line.length > totalCap) {
60
+ omitted++;
61
+ continue;
42
62
  }
43
- return `- ${label}: ${truncate(dep.result || dep.summary, maxChars)}`;
44
- });
63
+ lines.push(line);
64
+ used += line.length;
65
+ }
66
+ if (omitted > 0) {
67
+ lines.push(`- (${omitted} more input(s) omitted: input budget reached)`);
68
+ }
45
69
  return `\n\nInput from previous tasks:\n${lines.join("\n")}`;
46
70
  }