attocode 0.2.2 → 0.2.4

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 (382) hide show
  1. package/CHANGELOG.md +169 -3
  2. package/README.md +65 -5
  3. package/dist/src/adapters.d.ts.map +1 -1
  4. package/dist/src/adapters.js +15 -11
  5. package/dist/src/adapters.js.map +1 -1
  6. package/dist/src/agent.d.ts +44 -98
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +716 -2648
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/cli.d.ts.map +1 -1
  11. package/dist/src/cli.js +25 -3
  12. package/dist/src/cli.js.map +1 -1
  13. package/dist/src/commands/handler.d.ts.map +1 -1
  14. package/dist/src/commands/handler.js +11 -3
  15. package/dist/src/commands/handler.js.map +1 -1
  16. package/dist/src/commands/init-commands.d.ts.map +1 -1
  17. package/dist/src/commands/init-commands.js +16 -1
  18. package/dist/src/commands/init-commands.js.map +1 -1
  19. package/dist/src/commands/init.d.ts.map +1 -1
  20. package/dist/src/commands/init.js +31 -0
  21. package/dist/src/commands/init.js.map +1 -1
  22. package/dist/src/config/base-types.d.ts +45 -0
  23. package/dist/src/config/base-types.d.ts.map +1 -0
  24. package/dist/src/config/base-types.js +9 -0
  25. package/dist/src/config/base-types.js.map +1 -0
  26. package/dist/src/config/config-manager.d.ts +35 -0
  27. package/dist/src/config/config-manager.d.ts.map +1 -0
  28. package/dist/src/config/config-manager.js +108 -0
  29. package/dist/src/config/config-manager.js.map +1 -0
  30. package/dist/src/config/index.d.ts +4 -0
  31. package/dist/src/config/index.d.ts.map +1 -0
  32. package/dist/src/config/index.js +3 -0
  33. package/dist/src/config/index.js.map +1 -0
  34. package/dist/src/config/schema.d.ts +1546 -0
  35. package/dist/src/config/schema.d.ts.map +1 -0
  36. package/dist/src/config/schema.js +268 -0
  37. package/dist/src/config/schema.js.map +1 -0
  38. package/dist/src/config.d.ts +4 -1
  39. package/dist/src/config.d.ts.map +1 -1
  40. package/dist/src/config.js +8 -12
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/core/agent-state-machine.d.ts +131 -0
  43. package/dist/src/core/agent-state-machine.d.ts.map +1 -0
  44. package/dist/src/core/agent-state-machine.js +302 -0
  45. package/dist/src/core/agent-state-machine.js.map +1 -0
  46. package/dist/src/core/base-manager.d.ts +79 -0
  47. package/dist/src/core/base-manager.d.ts.map +1 -0
  48. package/dist/src/core/base-manager.js +170 -0
  49. package/dist/src/core/base-manager.js.map +1 -0
  50. package/dist/src/core/completion-analyzer.d.ts +15 -0
  51. package/dist/src/core/completion-analyzer.d.ts.map +1 -0
  52. package/dist/src/core/completion-analyzer.js +53 -0
  53. package/dist/src/core/completion-analyzer.js.map +1 -0
  54. package/dist/src/core/execution-loop.d.ts +46 -0
  55. package/dist/src/core/execution-loop.d.ts.map +1 -0
  56. package/dist/src/core/execution-loop.js +1258 -0
  57. package/dist/src/core/execution-loop.js.map +1 -0
  58. package/dist/src/core/index.d.ts +7 -0
  59. package/dist/src/core/index.d.ts.map +1 -1
  60. package/dist/src/core/index.js +9 -0
  61. package/dist/src/core/index.js.map +1 -1
  62. package/dist/src/core/process-handlers.d.ts.map +1 -1
  63. package/dist/src/core/process-handlers.js +14 -0
  64. package/dist/src/core/process-handlers.js.map +1 -1
  65. package/dist/src/core/protocol/types.d.ts +4 -4
  66. package/dist/src/core/response-handler.d.ts +16 -0
  67. package/dist/src/core/response-handler.d.ts.map +1 -0
  68. package/dist/src/core/response-handler.js +234 -0
  69. package/dist/src/core/response-handler.js.map +1 -0
  70. package/dist/src/core/subagent-spawner.d.ts +43 -0
  71. package/dist/src/core/subagent-spawner.d.ts.map +1 -0
  72. package/dist/src/core/subagent-spawner.js +966 -0
  73. package/dist/src/core/subagent-spawner.js.map +1 -0
  74. package/dist/src/core/tool-executor.d.ts +59 -0
  75. package/dist/src/core/tool-executor.d.ts.map +1 -0
  76. package/dist/src/core/tool-executor.js +677 -0
  77. package/dist/src/core/tool-executor.js.map +1 -0
  78. package/dist/src/core/types.d.ts +133 -0
  79. package/dist/src/core/types.d.ts.map +1 -0
  80. package/dist/src/core/types.js +12 -0
  81. package/dist/src/core/types.js.map +1 -0
  82. package/dist/src/defaults.d.ts +8 -3
  83. package/dist/src/defaults.d.ts.map +1 -1
  84. package/dist/src/defaults.js +65 -3
  85. package/dist/src/defaults.js.map +1 -1
  86. package/dist/src/integrations/agent-registry.d.ts +11 -0
  87. package/dist/src/integrations/agent-registry.d.ts.map +1 -1
  88. package/dist/src/integrations/agent-registry.js.map +1 -1
  89. package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
  90. package/dist/src/integrations/auto-compaction.js +8 -3
  91. package/dist/src/integrations/auto-compaction.js.map +1 -1
  92. package/dist/src/integrations/bash-policy.d.ts +33 -0
  93. package/dist/src/integrations/bash-policy.d.ts.map +1 -0
  94. package/dist/src/integrations/bash-policy.js +142 -0
  95. package/dist/src/integrations/bash-policy.js.map +1 -0
  96. package/dist/src/integrations/budget-pool.d.ts +7 -0
  97. package/dist/src/integrations/budget-pool.d.ts.map +1 -1
  98. package/dist/src/integrations/budget-pool.js +43 -0
  99. package/dist/src/integrations/budget-pool.js.map +1 -1
  100. package/dist/src/integrations/codebase-ast.d.ts +52 -0
  101. package/dist/src/integrations/codebase-ast.d.ts.map +1 -0
  102. package/dist/src/integrations/codebase-ast.js +457 -0
  103. package/dist/src/integrations/codebase-ast.js.map +1 -0
  104. package/dist/src/integrations/codebase-context.d.ts +23 -0
  105. package/dist/src/integrations/codebase-context.d.ts.map +1 -1
  106. package/dist/src/integrations/codebase-context.js +230 -17
  107. package/dist/src/integrations/codebase-context.js.map +1 -1
  108. package/dist/src/integrations/compaction.d.ts.map +1 -1
  109. package/dist/src/integrations/compaction.js +14 -6
  110. package/dist/src/integrations/compaction.js.map +1 -1
  111. package/dist/src/integrations/context-engineering.d.ts +8 -0
  112. package/dist/src/integrations/context-engineering.d.ts.map +1 -1
  113. package/dist/src/integrations/context-engineering.js +19 -0
  114. package/dist/src/integrations/context-engineering.js.map +1 -1
  115. package/dist/src/integrations/delegation-protocol.js +2 -2
  116. package/dist/src/integrations/delegation-protocol.js.map +1 -1
  117. package/dist/src/integrations/economics.d.ts +67 -1
  118. package/dist/src/integrations/economics.d.ts.map +1 -1
  119. package/dist/src/integrations/economics.js +328 -33
  120. package/dist/src/integrations/economics.js.map +1 -1
  121. package/dist/src/integrations/edit-validator.d.ts +30 -0
  122. package/dist/src/integrations/edit-validator.d.ts.map +1 -0
  123. package/dist/src/integrations/edit-validator.js +85 -0
  124. package/dist/src/integrations/edit-validator.js.map +1 -0
  125. package/dist/src/integrations/file-cache.d.ts +7 -0
  126. package/dist/src/integrations/file-cache.d.ts.map +1 -1
  127. package/dist/src/integrations/file-cache.js +54 -0
  128. package/dist/src/integrations/file-cache.js.map +1 -1
  129. package/dist/src/integrations/health-check.d.ts.map +1 -1
  130. package/dist/src/integrations/health-check.js +3 -2
  131. package/dist/src/integrations/health-check.js.map +1 -1
  132. package/dist/src/integrations/hierarchical-config.d.ts +3 -0
  133. package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
  134. package/dist/src/integrations/hierarchical-config.js +20 -0
  135. package/dist/src/integrations/hierarchical-config.js.map +1 -1
  136. package/dist/src/integrations/hooks.d.ts +2 -0
  137. package/dist/src/integrations/hooks.d.ts.map +1 -1
  138. package/dist/src/integrations/hooks.js +99 -15
  139. package/dist/src/integrations/hooks.js.map +1 -1
  140. package/dist/src/integrations/index.d.ts +10 -1
  141. package/dist/src/integrations/index.d.ts.map +1 -1
  142. package/dist/src/integrations/index.js +12 -2
  143. package/dist/src/integrations/index.js.map +1 -1
  144. package/dist/src/integrations/logger.d.ts +104 -0
  145. package/dist/src/integrations/logger.d.ts.map +1 -0
  146. package/dist/src/integrations/logger.js +219 -0
  147. package/dist/src/integrations/logger.js.map +1 -0
  148. package/dist/src/integrations/lsp.d.ts.map +1 -1
  149. package/dist/src/integrations/lsp.js +5 -4
  150. package/dist/src/integrations/lsp.js.map +1 -1
  151. package/dist/src/integrations/mcp-client.d.ts.map +1 -1
  152. package/dist/src/integrations/mcp-client.js +8 -7
  153. package/dist/src/integrations/mcp-client.js.map +1 -1
  154. package/dist/src/integrations/observability.d.ts.map +1 -1
  155. package/dist/src/integrations/observability.js +5 -4
  156. package/dist/src/integrations/observability.js.map +1 -1
  157. package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
  158. package/dist/src/integrations/openrouter-pricing.js +4 -3
  159. package/dist/src/integrations/openrouter-pricing.js.map +1 -1
  160. package/dist/src/integrations/persistence.d.ts.map +1 -1
  161. package/dist/src/integrations/persistence.js +5 -4
  162. package/dist/src/integrations/persistence.js.map +1 -1
  163. package/dist/src/integrations/planning.d.ts.map +1 -1
  164. package/dist/src/integrations/planning.js +5 -4
  165. package/dist/src/integrations/planning.js.map +1 -1
  166. package/dist/src/integrations/policy-engine.d.ts +55 -0
  167. package/dist/src/integrations/policy-engine.d.ts.map +1 -0
  168. package/dist/src/integrations/policy-engine.js +247 -0
  169. package/dist/src/integrations/policy-engine.js.map +1 -0
  170. package/dist/src/integrations/retry.d.ts +1 -0
  171. package/dist/src/integrations/retry.d.ts.map +1 -1
  172. package/dist/src/integrations/retry.js.map +1 -1
  173. package/dist/src/integrations/routing.d.ts.map +1 -1
  174. package/dist/src/integrations/routing.js +2 -1
  175. package/dist/src/integrations/routing.js.map +1 -1
  176. package/dist/src/integrations/safety.d.ts +5 -4
  177. package/dist/src/integrations/safety.d.ts.map +1 -1
  178. package/dist/src/integrations/safety.js +45 -20
  179. package/dist/src/integrations/safety.js.map +1 -1
  180. package/dist/src/integrations/sandbox/basic.d.ts +7 -0
  181. package/dist/src/integrations/sandbox/basic.d.ts.map +1 -1
  182. package/dist/src/integrations/sandbox/basic.js +27 -2
  183. package/dist/src/integrations/sandbox/basic.js.map +1 -1
  184. package/dist/src/integrations/sandbox/docker.d.ts.map +1 -1
  185. package/dist/src/integrations/sandbox/docker.js +2 -1
  186. package/dist/src/integrations/sandbox/docker.js.map +1 -1
  187. package/dist/src/integrations/sandbox/index.d.ts +6 -0
  188. package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
  189. package/dist/src/integrations/sandbox/index.js +8 -4
  190. package/dist/src/integrations/sandbox/index.js.map +1 -1
  191. package/dist/src/integrations/sandbox/landlock.d.ts.map +1 -1
  192. package/dist/src/integrations/sandbox/landlock.js +3 -0
  193. package/dist/src/integrations/sandbox/landlock.js.map +1 -1
  194. package/dist/src/integrations/self-improvement.d.ts.map +1 -1
  195. package/dist/src/integrations/self-improvement.js +12 -0
  196. package/dist/src/integrations/self-improvement.js.map +1 -1
  197. package/dist/src/integrations/session-store.d.ts +1 -0
  198. package/dist/src/integrations/session-store.d.ts.map +1 -1
  199. package/dist/src/integrations/session-store.js +1 -0
  200. package/dist/src/integrations/session-store.js.map +1 -1
  201. package/dist/src/integrations/shared-blackboard.d.ts +3 -0
  202. package/dist/src/integrations/shared-blackboard.d.ts.map +1 -1
  203. package/dist/src/integrations/shared-blackboard.js +47 -0
  204. package/dist/src/integrations/shared-blackboard.js.map +1 -1
  205. package/dist/src/integrations/smart-decomposer.d.ts +45 -1
  206. package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
  207. package/dist/src/integrations/smart-decomposer.js +486 -30
  208. package/dist/src/integrations/smart-decomposer.js.map +1 -1
  209. package/dist/src/integrations/sqlite-store.d.ts +2 -0
  210. package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
  211. package/dist/src/integrations/sqlite-store.js +18 -6
  212. package/dist/src/integrations/sqlite-store.js.map +1 -1
  213. package/dist/src/integrations/swarm/failure-classifier.d.ts +11 -0
  214. package/dist/src/integrations/swarm/failure-classifier.d.ts.map +1 -0
  215. package/dist/src/integrations/swarm/failure-classifier.js +95 -0
  216. package/dist/src/integrations/swarm/failure-classifier.js.map +1 -0
  217. package/dist/src/integrations/swarm/index.d.ts +1 -1
  218. package/dist/src/integrations/swarm/index.d.ts.map +1 -1
  219. package/dist/src/integrations/swarm/index.js.map +1 -1
  220. package/dist/src/integrations/swarm/model-selector.d.ts +15 -0
  221. package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
  222. package/dist/src/integrations/swarm/model-selector.js +100 -20
  223. package/dist/src/integrations/swarm/model-selector.js.map +1 -1
  224. package/dist/src/integrations/swarm/swarm-budget.d.ts +4 -0
  225. package/dist/src/integrations/swarm/swarm-budget.d.ts.map +1 -1
  226. package/dist/src/integrations/swarm/swarm-budget.js +6 -0
  227. package/dist/src/integrations/swarm/swarm-budget.js.map +1 -1
  228. package/dist/src/integrations/swarm/swarm-config-loader.d.ts +8 -0
  229. package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
  230. package/dist/src/integrations/swarm/swarm-config-loader.js +249 -7
  231. package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
  232. package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +86 -1
  233. package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
  234. package/dist/src/integrations/swarm/swarm-event-bridge.js +207 -23
  235. package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
  236. package/dist/src/integrations/swarm/swarm-events.d.ts +58 -1
  237. package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
  238. package/dist/src/integrations/swarm/swarm-events.js +22 -5
  239. package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
  240. package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +147 -8
  241. package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
  242. package/dist/src/integrations/swarm/swarm-orchestrator.js +2179 -132
  243. package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
  244. package/dist/src/integrations/swarm/swarm-quality-gate.d.ts +83 -2
  245. package/dist/src/integrations/swarm/swarm-quality-gate.d.ts.map +1 -1
  246. package/dist/src/integrations/swarm/swarm-quality-gate.js +278 -19
  247. package/dist/src/integrations/swarm/swarm-quality-gate.js.map +1 -1
  248. package/dist/src/integrations/swarm/swarm-state-store.d.ts +4 -1
  249. package/dist/src/integrations/swarm/swarm-state-store.d.ts.map +1 -1
  250. package/dist/src/integrations/swarm/swarm-state-store.js +8 -1
  251. package/dist/src/integrations/swarm/swarm-state-store.js.map +1 -1
  252. package/dist/src/integrations/swarm/task-queue.d.ts +54 -0
  253. package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
  254. package/dist/src/integrations/swarm/task-queue.js +310 -12
  255. package/dist/src/integrations/swarm/task-queue.js.map +1 -1
  256. package/dist/src/integrations/swarm/types.d.ts +251 -13
  257. package/dist/src/integrations/swarm/types.d.ts.map +1 -1
  258. package/dist/src/integrations/swarm/types.js +70 -8
  259. package/dist/src/integrations/swarm/types.js.map +1 -1
  260. package/dist/src/integrations/swarm/worker-pool.d.ts +21 -4
  261. package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
  262. package/dist/src/integrations/swarm/worker-pool.js +223 -44
  263. package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
  264. package/dist/src/integrations/task-manager.d.ts +33 -1
  265. package/dist/src/integrations/task-manager.d.ts.map +1 -1
  266. package/dist/src/integrations/task-manager.js +78 -4
  267. package/dist/src/integrations/task-manager.js.map +1 -1
  268. package/dist/src/integrations/tool-recommendation.d.ts +7 -4
  269. package/dist/src/integrations/tool-recommendation.d.ts.map +1 -1
  270. package/dist/src/integrations/tool-recommendation.js +58 -5
  271. package/dist/src/integrations/tool-recommendation.js.map +1 -1
  272. package/dist/src/integrations/work-log.js +4 -4
  273. package/dist/src/integrations/work-log.js.map +1 -1
  274. package/dist/src/main.js +106 -30
  275. package/dist/src/main.js.map +1 -1
  276. package/dist/src/modes/repl.d.ts.map +1 -1
  277. package/dist/src/modes/repl.js +50 -12
  278. package/dist/src/modes/repl.js.map +1 -1
  279. package/dist/src/modes/tui.d.ts.map +1 -1
  280. package/dist/src/modes/tui.js +41 -6
  281. package/dist/src/modes/tui.js.map +1 -1
  282. package/dist/src/modes.d.ts.map +1 -1
  283. package/dist/src/modes.js +4 -27
  284. package/dist/src/modes.js.map +1 -1
  285. package/dist/src/observability/tracer.d.ts.map +1 -1
  286. package/dist/src/observability/tracer.js +2 -1
  287. package/dist/src/observability/tracer.js.map +1 -1
  288. package/dist/src/persistence/schema.d.ts.map +1 -1
  289. package/dist/src/persistence/schema.js +11 -0
  290. package/dist/src/persistence/schema.js.map +1 -1
  291. package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
  292. package/dist/src/providers/adapters/anthropic.js +3 -2
  293. package/dist/src/providers/adapters/anthropic.js.map +1 -1
  294. package/dist/src/providers/adapters/openai.d.ts.map +1 -1
  295. package/dist/src/providers/adapters/openai.js +3 -2
  296. package/dist/src/providers/adapters/openai.js.map +1 -1
  297. package/dist/src/providers/adapters/openrouter.d.ts.map +1 -1
  298. package/dist/src/providers/adapters/openrouter.js +11 -11
  299. package/dist/src/providers/adapters/openrouter.js.map +1 -1
  300. package/dist/src/providers/circuit-breaker.d.ts +1 -0
  301. package/dist/src/providers/circuit-breaker.d.ts.map +1 -1
  302. package/dist/src/providers/circuit-breaker.js.map +1 -1
  303. package/dist/src/providers/provider.d.ts.map +1 -1
  304. package/dist/src/providers/provider.js +2 -1
  305. package/dist/src/providers/provider.js.map +1 -1
  306. package/dist/src/providers/resilient-provider.d.ts.map +1 -1
  307. package/dist/src/providers/resilient-provider.js +2 -1
  308. package/dist/src/providers/resilient-provider.js.map +1 -1
  309. package/dist/src/session-picker.d.ts.map +1 -1
  310. package/dist/src/session-picker.js +40 -5
  311. package/dist/src/session-picker.js.map +1 -1
  312. package/dist/src/shared/budget-tracker.d.ts +65 -0
  313. package/dist/src/shared/budget-tracker.d.ts.map +1 -0
  314. package/dist/src/shared/budget-tracker.js +128 -0
  315. package/dist/src/shared/budget-tracker.js.map +1 -0
  316. package/dist/src/shared/context-engine.d.ts +64 -0
  317. package/dist/src/shared/context-engine.d.ts.map +1 -0
  318. package/dist/src/shared/context-engine.js +117 -0
  319. package/dist/src/shared/context-engine.js.map +1 -0
  320. package/dist/src/shared/index.d.ts +12 -0
  321. package/dist/src/shared/index.d.ts.map +1 -0
  322. package/dist/src/shared/index.js +12 -0
  323. package/dist/src/shared/index.js.map +1 -0
  324. package/dist/src/shared/persistence.d.ts +57 -0
  325. package/dist/src/shared/persistence.d.ts.map +1 -0
  326. package/dist/src/shared/persistence.js +168 -0
  327. package/dist/src/shared/persistence.js.map +1 -0
  328. package/dist/src/shared/shared-context-state.d.ts +89 -0
  329. package/dist/src/shared/shared-context-state.d.ts.map +1 -0
  330. package/dist/src/shared/shared-context-state.js +175 -0
  331. package/dist/src/shared/shared-context-state.js.map +1 -0
  332. package/dist/src/shared/shared-economics-state.d.ts +61 -0
  333. package/dist/src/shared/shared-economics-state.d.ts.map +1 -0
  334. package/dist/src/shared/shared-economics-state.js +100 -0
  335. package/dist/src/shared/shared-economics-state.js.map +1 -0
  336. package/dist/src/tools/agent.d.ts.map +1 -1
  337. package/dist/src/tools/agent.js +11 -2
  338. package/dist/src/tools/agent.js.map +1 -1
  339. package/dist/src/tools/bash.d.ts +1 -1
  340. package/dist/src/tools/bash.d.ts.map +1 -1
  341. package/dist/src/tools/bash.js +2 -1
  342. package/dist/src/tools/bash.js.map +1 -1
  343. package/dist/src/tools/coercion.d.ts +6 -0
  344. package/dist/src/tools/coercion.d.ts.map +1 -1
  345. package/dist/src/tools/coercion.js +13 -0
  346. package/dist/src/tools/coercion.js.map +1 -1
  347. package/dist/src/tools/file.d.ts +5 -5
  348. package/dist/src/tools/file.js +2 -2
  349. package/dist/src/tools/file.js.map +1 -1
  350. package/dist/src/tools/permission.d.ts.map +1 -1
  351. package/dist/src/tools/permission.js +10 -116
  352. package/dist/src/tools/permission.js.map +1 -1
  353. package/dist/src/tools/types.d.ts +1 -0
  354. package/dist/src/tools/types.d.ts.map +1 -1
  355. package/dist/src/tools/types.js.map +1 -1
  356. package/dist/src/tracing/trace-collector.d.ts +292 -0
  357. package/dist/src/tracing/trace-collector.d.ts.map +1 -1
  358. package/dist/src/tracing/trace-collector.js +249 -5
  359. package/dist/src/tracing/trace-collector.js.map +1 -1
  360. package/dist/src/tracing/types.d.ts +200 -1
  361. package/dist/src/tracing/types.d.ts.map +1 -1
  362. package/dist/src/tracing/types.js.map +1 -1
  363. package/dist/src/tricks/failure-evidence.d.ts.map +1 -1
  364. package/dist/src/tricks/failure-evidence.js +2 -1
  365. package/dist/src/tricks/failure-evidence.js.map +1 -1
  366. package/dist/src/tui/app.d.ts +13 -0
  367. package/dist/src/tui/app.d.ts.map +1 -1
  368. package/dist/src/tui/app.js +162 -19
  369. package/dist/src/tui/app.js.map +1 -1
  370. package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -1
  371. package/dist/src/tui/components/ErrorBoundary.js +3 -2
  372. package/dist/src/tui/components/ErrorBoundary.js.map +1 -1
  373. package/dist/src/tui/event-display.d.ts.map +1 -1
  374. package/dist/src/tui/event-display.js +36 -62
  375. package/dist/src/tui/event-display.js.map +1 -1
  376. package/dist/src/tui/index.d.ts +4 -0
  377. package/dist/src/tui/index.d.ts.map +1 -1
  378. package/dist/src/tui/index.js +17 -0
  379. package/dist/src/tui/index.js.map +1 -1
  380. package/dist/src/types.d.ts +214 -1
  381. package/dist/src/types.d.ts.map +1 -1
  382. package/package.json +18 -3
@@ -0,0 +1,1258 @@
1
+ /**
2
+ * Execution Loop Module (Phase 2.1)
3
+ *
4
+ * Extracted from ProductionAgent.executeDirectly().
5
+ * Contains the main ReAct while(true) loop: cancellation checks, resource checks,
6
+ * economics/budget checks with compaction recovery, loop detection, context
7
+ * engineering injection (recitation, failure evidence), LLM call dispatch,
8
+ * resilience handling, tool execution, and compaction.
9
+ */
10
+ import { isFeatureEnabled } from '../defaults.js';
11
+ import { callLLM } from './response-handler.js';
12
+ import { executeToolCalls } from './tool-executor.js';
13
+ import { TIMEOUT_WRAPUP_PROMPT, stableStringify, } from '../integrations/index.js';
14
+ import { detectIncompleteActionResponse } from './completion-analyzer.js';
15
+ export { detectIncompleteActionResponse } from './completion-analyzer.js';
16
+ import { createComponentLogger } from '../integrations/logger.js';
17
+ import { validateSyntax } from '../integrations/edit-validator.js';
18
+ import * as fs from 'node:fs';
19
+ const log = createComponentLogger('ExecutionLoop');
20
+ // =============================================================================
21
+ // HELPER FUNCTIONS (extracted from ProductionAgent private methods)
22
+ // =============================================================================
23
+ /**
24
+ * Estimate total tokens in a message array.
25
+ * Uses ~4 chars per token heuristic for fast estimation.
26
+ */
27
+ export function estimateContextTokens(messages) {
28
+ let totalChars = 0;
29
+ for (const msg of messages) {
30
+ if (msg.content) {
31
+ totalChars += msg.content.length;
32
+ }
33
+ if (msg.toolCalls) {
34
+ for (const tc of msg.toolCalls) {
35
+ totalChars += tc.name.length;
36
+ totalChars += JSON.stringify(tc.arguments).length;
37
+ }
38
+ }
39
+ }
40
+ return Math.ceil(totalChars / 4);
41
+ }
42
+ /**
43
+ * Compact tool outputs to save context.
44
+ */
45
+ export function compactToolOutputs(messages) {
46
+ const COMPACT_PREVIEW_LENGTH = 200;
47
+ const MAX_PRESERVED_EXPENSIVE_RESULTS = 6;
48
+ let compactedCount = 0;
49
+ let savedChars = 0;
50
+ const preservedExpensiveIndexes = messages
51
+ .map((msg, index) => ({ msg, index }))
52
+ .filter(({ msg }) => msg.role === 'tool' && msg.metadata?.preserveFromCompaction === true)
53
+ .map(({ index }) => index);
54
+ const preserveSet = new Set(preservedExpensiveIndexes.slice(-MAX_PRESERVED_EXPENSIVE_RESULTS));
55
+ for (let i = 0; i < messages.length; i++) {
56
+ const msg = messages[i];
57
+ if (msg.role === 'tool' && msg.content && msg.content.length > COMPACT_PREVIEW_LENGTH * 2) {
58
+ if (msg.metadata?.preserveFromCompaction === true && preserveSet.has(i)) {
59
+ continue;
60
+ }
61
+ const originalLength = msg.content.length;
62
+ const preview = msg.content.slice(0, COMPACT_PREVIEW_LENGTH).replace(/\n/g, ' ');
63
+ msg.content = `[${preview}...] (${originalLength} chars, compacted)`;
64
+ savedChars += originalLength - msg.content.length;
65
+ compactedCount++;
66
+ }
67
+ }
68
+ if (compactedCount > 0 && process.env.DEBUG) {
69
+ log.debug('Compacted tool outputs', { compactedCount, savedTokens: Math.round(savedChars / 4) });
70
+ }
71
+ }
72
+ /**
73
+ * Extract a requested markdown artifact filename from a task prompt.
74
+ */
75
+ export function extractRequestedArtifact(task) {
76
+ const markdownArtifactMatch = task.match(/(?:write|save|create)[^.\n]{0,120}\b([A-Za-z0-9._/-]+\.md)\b/i);
77
+ return markdownArtifactMatch?.[1] ?? null;
78
+ }
79
+ /**
80
+ * Check whether a requested artifact appears to be missing.
81
+ */
82
+ export function isRequestedArtifactMissing(requestedArtifact, executedToolNames) {
83
+ if (!requestedArtifact)
84
+ return false;
85
+ const artifactWriteTools = ['write_file', 'edit_file', 'apply_patch', 'append_file'];
86
+ return !artifactWriteTools.some(toolName => executedToolNames.has(toolName));
87
+ }
88
+ function getOpenTaskSummary(ctx) {
89
+ if (!ctx.taskManager)
90
+ return undefined;
91
+ const tasks = ctx.taskManager.list();
92
+ const pending = tasks.filter(t => t.status === 'pending').length;
93
+ const inProgress = tasks.filter(t => t.status === 'in_progress').length;
94
+ const blocked = tasks.filter(t => t.status === 'pending' && ctx.taskManager?.isBlocked(t.id)).length;
95
+ return { pending, inProgress, blocked };
96
+ }
97
+ function getPendingWithOwnerCount(ctx) {
98
+ if (!ctx.taskManager)
99
+ return 0;
100
+ return ctx.taskManager.list().filter(t => t.status === 'pending' && !!t.owner).length;
101
+ }
102
+ // =============================================================================
103
+ // MAIN EXECUTION LOOP
104
+ // =============================================================================
105
+ /**
106
+ * Execute a task directly without planning.
107
+ * This is the main ReAct loop extracted from ProductionAgent.executeDirectly().
108
+ *
109
+ * @param task - The user task to execute
110
+ * @param ctx - Agent context (managers, config, state)
111
+ * @param mutators - Functions to update mutable agent state
112
+ */
113
+ export async function executeDirectly(task, messages, ctx, mutators) {
114
+ // Reset economics for new task
115
+ ctx.economics?.reset();
116
+ const taskLeaseStaleMs = typeof ctx.config.resilience === 'object'
117
+ ? (ctx.config.resilience.taskLeaseStaleMs ?? 5 * 60 * 1000)
118
+ : 5 * 60 * 1000;
119
+ // Recover orphaned in-progress tasks left behind by interrupted runs.
120
+ if (ctx.taskManager) {
121
+ const recovered = ctx.taskManager.reconcileStaleInProgress({
122
+ staleAfterMs: taskLeaseStaleMs,
123
+ reason: 'execution_loop_start',
124
+ });
125
+ if (recovered.reconciled > 0) {
126
+ log.info('Recovered stale in-progress tasks at execution start', {
127
+ recovered: recovered.reconciled,
128
+ });
129
+ }
130
+ }
131
+ // Reflection configuration
132
+ const reflectionConfig = ctx.config.reflection;
133
+ const reflectionEnabled = isFeatureEnabled(reflectionConfig);
134
+ const autoReflect = reflectionEnabled && reflectionConfig.autoReflect;
135
+ const maxReflectionAttempts = reflectionEnabled
136
+ ? (reflectionConfig.maxAttempts || 3)
137
+ : 1;
138
+ const confidenceThreshold = reflectionEnabled
139
+ ? (reflectionConfig.confidenceThreshold || 0.8)
140
+ : 0.8;
141
+ let reflectionAttempt = 0;
142
+ let lastResponse = '';
143
+ let incompleteActionRetries = 0;
144
+ const requestedArtifact = extractRequestedArtifact(task);
145
+ const executedToolNames = new Set();
146
+ let result = {
147
+ success: true,
148
+ terminationReason: 'completed',
149
+ };
150
+ // Outer loop for reflection (if enabled)
151
+ while (reflectionAttempt < maxReflectionAttempts) {
152
+ reflectionAttempt++;
153
+ // Agent loop - uses economics-based budget checking
154
+ while (true) {
155
+ ctx.state.iteration++;
156
+ ctx.emit({ type: 'iteration.before', iteration: ctx.state.iteration });
157
+ // Record iteration start for tracing
158
+ ctx.traceCollector?.record({
159
+ type: 'iteration.start',
160
+ data: { iterationNumber: ctx.state.iteration },
161
+ });
162
+ // =======================================================================
163
+ // CANCELLATION CHECK
164
+ // =======================================================================
165
+ if (ctx.cancellation?.isCancelled) {
166
+ ctx.cancellation.token.throwIfCancellationRequested();
167
+ }
168
+ // =======================================================================
169
+ // RESOURCE CHECK
170
+ // =======================================================================
171
+ if (ctx.resourceManager) {
172
+ const resourceCheck = ctx.resourceManager.check();
173
+ if (!resourceCheck.canContinue) {
174
+ ctx.observability?.logger?.warn('Resource limit reached', {
175
+ status: resourceCheck.status,
176
+ message: resourceCheck.message,
177
+ });
178
+ const reason = resourceCheck.message || 'Resource limit exceeded';
179
+ ctx.emit({ type: 'error', error: reason });
180
+ result = {
181
+ success: false,
182
+ terminationReason: 'resource_limit',
183
+ failureReason: reason,
184
+ };
185
+ break;
186
+ }
187
+ if (resourceCheck.status === 'warning' || resourceCheck.status === 'critical') {
188
+ ctx.observability?.logger?.info(`Resource status: ${resourceCheck.status}`, {
189
+ message: resourceCheck.message,
190
+ });
191
+ }
192
+ }
193
+ // =======================================================================
194
+ // ECONOMICS CHECK (Token Budget)
195
+ // =======================================================================
196
+ let forceTextOnly = false;
197
+ let budgetInjectedPrompt;
198
+ if (ctx.economics) {
199
+ const budgetCheck = ctx.economics.checkBudget();
200
+ forceTextOnly = budgetCheck.forceTextOnly ?? false;
201
+ budgetInjectedPrompt = budgetCheck.injectedPrompt;
202
+ if (!budgetCheck.canContinue) {
203
+ // RECOVERY ATTEMPT: Try emergency context reduction
204
+ const isTokenLimit = budgetCheck.budgetType === 'tokens' || budgetCheck.budgetType === 'cost';
205
+ const alreadyTriedRecovery = ctx.state._recoveryAttempted === true;
206
+ if (isTokenLimit && !alreadyTriedRecovery) {
207
+ ctx.observability?.logger?.info('Budget limit reached, attempting recovery via context reduction', {
208
+ reason: budgetCheck.reason,
209
+ percentUsed: budgetCheck.percentUsed,
210
+ });
211
+ ctx.emit({
212
+ type: 'resilience.retry',
213
+ reason: 'budget_limit_compaction',
214
+ attempt: 1,
215
+ maxAttempts: 1,
216
+ });
217
+ ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
218
+ ctx.state._recoveryAttempted = true;
219
+ const tokensBefore = estimateContextTokens(messages);
220
+ // Step 1: Compact tool outputs aggressively
221
+ compactToolOutputs(ctx.state.messages);
222
+ // Step 2: Emergency truncation - keep system + last N messages
223
+ const PRESERVE_RECENT = 10;
224
+ if (messages.length > PRESERVE_RECENT + 2) {
225
+ const systemMessage = messages.find(m => m.role === 'system');
226
+ const recentMessages = messages.slice(-(PRESERVE_RECENT));
227
+ messages.length = 0;
228
+ if (systemMessage) {
229
+ messages.push(systemMessage);
230
+ }
231
+ messages.push({
232
+ role: 'system',
233
+ content: `[CONTEXT REDUCED: Earlier messages were removed to stay within budget. Conversation continues from recent context.]`,
234
+ });
235
+ messages.push(...recentMessages);
236
+ // Inject work log after emergency truncation
237
+ if (ctx.workLog?.hasContent()) {
238
+ const workLogMessage = {
239
+ role: 'user',
240
+ content: ctx.workLog.toCompactString(),
241
+ };
242
+ messages.push(workLogMessage);
243
+ }
244
+ // Update state messages too
245
+ ctx.state.messages.length = 0;
246
+ ctx.state.messages.push(...messages);
247
+ }
248
+ const tokensAfter = estimateContextTokens(messages);
249
+ const reduction = Math.round((1 - tokensAfter / tokensBefore) * 100);
250
+ if (tokensAfter < tokensBefore * 0.8) {
251
+ ctx.observability?.logger?.info('Context reduction successful, continuing execution', {
252
+ tokensBefore,
253
+ tokensAfter,
254
+ reduction,
255
+ });
256
+ ctx.emit({
257
+ type: 'resilience.recovered',
258
+ reason: 'budget_limit_compaction',
259
+ attempts: 1,
260
+ });
261
+ ctx.emit({
262
+ type: 'compaction.auto',
263
+ tokensBefore,
264
+ tokensAfter,
265
+ messagesCompacted: tokensBefore - tokensAfter,
266
+ });
267
+ continue;
268
+ }
269
+ ctx.observability?.logger?.warn('Context reduction insufficient', {
270
+ tokensBefore,
271
+ tokensAfter,
272
+ reduction,
273
+ });
274
+ }
275
+ // Hard limit reached and recovery failed
276
+ ctx.observability?.logger?.warn('Budget limit reached', {
277
+ reason: budgetCheck.reason,
278
+ budgetType: budgetCheck.budgetType,
279
+ });
280
+ if (budgetCheck.budgetType === 'iterations') {
281
+ const totalIter = ctx.getTotalIterations();
282
+ const iterMsg = ctx.parentIterations > 0
283
+ ? `${ctx.state.iteration} + ${ctx.parentIterations} parent = ${totalIter}`
284
+ : `${ctx.state.iteration}`;
285
+ const reason = `Max iterations reached (${iterMsg})`;
286
+ ctx.emit({ type: 'error', error: reason });
287
+ result = {
288
+ success: false,
289
+ terminationReason: 'max_iterations',
290
+ failureReason: reason,
291
+ };
292
+ }
293
+ else {
294
+ const reason = budgetCheck.reason || 'Budget exceeded';
295
+ ctx.emit({ type: 'error', error: reason });
296
+ result = {
297
+ success: false,
298
+ terminationReason: 'budget_limit',
299
+ failureReason: reason,
300
+ };
301
+ }
302
+ break;
303
+ }
304
+ // Check for soft limits and potential extension
305
+ if (budgetCheck.isSoftLimit && budgetCheck.suggestedAction === 'request_extension') {
306
+ ctx.observability?.logger?.info('Approaching budget limit', {
307
+ reason: budgetCheck.reason,
308
+ percentUsed: budgetCheck.percentUsed,
309
+ });
310
+ }
311
+ }
312
+ else {
313
+ // Fallback to simple iteration check
314
+ if (ctx.getTotalIterations() >= ctx.config.maxIterations) {
315
+ const totalIter = ctx.getTotalIterations();
316
+ const reason = `Max iterations reached (${totalIter})`;
317
+ ctx.observability?.logger?.warn('Max iterations reached', {
318
+ iteration: ctx.state.iteration,
319
+ parentIterations: ctx.parentIterations,
320
+ total: totalIter,
321
+ });
322
+ ctx.emit({ type: 'error', error: reason });
323
+ result = {
324
+ success: false,
325
+ terminationReason: 'max_iterations',
326
+ failureReason: reason,
327
+ };
328
+ break;
329
+ }
330
+ }
331
+ // =======================================================================
332
+ // GRACEFUL WRAPUP CHECK
333
+ // =======================================================================
334
+ if (ctx.wrapupRequested && !forceTextOnly) {
335
+ forceTextOnly = true;
336
+ budgetInjectedPrompt = TIMEOUT_WRAPUP_PROMPT;
337
+ mutators.setWrapupRequested(false);
338
+ }
339
+ // =======================================================================
340
+ // EXTERNAL CANCELLATION CHECK (deferred)
341
+ // =======================================================================
342
+ if (ctx.externalCancellationToken?.isCancellationRequested && !forceTextOnly) {
343
+ ctx.externalCancellationToken.throwIfCancellationRequested();
344
+ }
345
+ // =======================================================================
346
+ // INTELLIGENT LOOP DETECTION & NUDGE INJECTION
347
+ // =======================================================================
348
+ if (ctx.economics && budgetInjectedPrompt) {
349
+ messages.push({
350
+ role: 'user',
351
+ content: budgetInjectedPrompt,
352
+ });
353
+ const loopState = ctx.economics.getLoopState();
354
+ const phaseState = ctx.economics.getPhaseState();
355
+ ctx.observability?.logger?.info('Loop detection - injecting guidance', {
356
+ iteration: ctx.state.iteration,
357
+ doomLoop: loopState.doomLoopDetected,
358
+ phase: phaseState.phase,
359
+ filesRead: phaseState.uniqueFilesRead,
360
+ filesModified: phaseState.filesModified,
361
+ shouldTransition: phaseState.shouldTransition,
362
+ forceTextOnly,
363
+ });
364
+ }
365
+ // =======================================================================
366
+ // RECITATION INJECTION (Trick Q)
367
+ // =======================================================================
368
+ if (ctx.contextEngineering) {
369
+ if (process.env.DEBUG_LLM) {
370
+ if (process.env.DEBUG)
371
+ log.debug('Recitation before', { messageCount: messages.length });
372
+ }
373
+ const enrichedMessages = ctx.contextEngineering.injectRecitation(messages, {
374
+ goal: task,
375
+ plan: ctx.state.plan ? {
376
+ description: ctx.state.plan.goal || task,
377
+ tasks: ctx.state.plan.tasks.map(t => ({
378
+ id: t.id,
379
+ description: t.description,
380
+ status: t.status,
381
+ })),
382
+ currentTaskIndex: ctx.state.plan.tasks.findIndex(t => t.status === 'in_progress'),
383
+ } : undefined,
384
+ activeFiles: ctx.economics?.getProgress().filesModified
385
+ ? [`${ctx.economics.getProgress().filesModified} files modified`]
386
+ : undefined,
387
+ recentErrors: ctx.contextEngineering.getFailureInsights().slice(0, 2),
388
+ });
389
+ if (process.env.DEBUG_LLM) {
390
+ if (process.env.DEBUG)
391
+ log.debug('Recitation after', { messageCount: enrichedMessages?.length ?? 'null/undefined' });
392
+ }
393
+ if (enrichedMessages && enrichedMessages !== messages && enrichedMessages.length > 0) {
394
+ messages.length = 0;
395
+ messages.push(...enrichedMessages);
396
+ }
397
+ else if (!enrichedMessages || enrichedMessages.length === 0) {
398
+ log.warn('Recitation returned empty/null messages, keeping original');
399
+ }
400
+ const contextTokens = messages.reduce((sum, m) => sum + (m.content?.length || 0) / 4, 0);
401
+ ctx.contextEngineering.updateRecitationFrequency(contextTokens);
402
+ }
403
+ // =======================================================================
404
+ // FAILURE CONTEXT INJECTION (Trick S)
405
+ // =======================================================================
406
+ if (ctx.contextEngineering) {
407
+ const failureContext = ctx.contextEngineering.getFailureContext(5);
408
+ if (failureContext) {
409
+ let lastUserIdx = -1;
410
+ for (let i = messages.length - 1; i >= 0; i--) {
411
+ if (messages[i].role === 'user') {
412
+ lastUserIdx = i;
413
+ break;
414
+ }
415
+ }
416
+ if (lastUserIdx > 0) {
417
+ messages.splice(lastUserIdx, 0, {
418
+ role: 'system',
419
+ content: failureContext,
420
+ });
421
+ }
422
+ }
423
+ }
424
+ // =======================================================================
425
+ // RESUME ORIENTATION — after compaction, nudge the agent to act, not re-summarize
426
+ // =======================================================================
427
+ const hasCompactionSummary = messages.some(m => m.role === 'system' && typeof m.content === 'string' && m.content.includes('[Conversation Summary'));
428
+ if (hasCompactionSummary && ctx.state.iteration <= 2) {
429
+ messages.push({
430
+ role: 'user',
431
+ content: `[System] Context was compacted. Review the summary above for what's been done and what remains. Do NOT repeat the summary — start working on the next task immediately using your tools.`,
432
+ });
433
+ }
434
+ // =======================================================================
435
+ // INJECTION BUDGET ANALYSIS (Phase 2 - monitoring mode)
436
+ // =======================================================================
437
+ if (ctx.injectionBudget) {
438
+ const proposals = [];
439
+ if (budgetInjectedPrompt) {
440
+ proposals.push({ name: 'budget_warning', priority: 0, maxTokens: 500, content: budgetInjectedPrompt });
441
+ }
442
+ if (ctx.contextEngineering) {
443
+ const failureCtx = ctx.contextEngineering.getFailureContext(5);
444
+ if (failureCtx) {
445
+ proposals.push({ name: 'failure_context', priority: 2, maxTokens: 300, content: failureCtx });
446
+ }
447
+ }
448
+ if (proposals.length > 0) {
449
+ const accepted = ctx.injectionBudget.allocate(proposals);
450
+ const stats = ctx.injectionBudget.getLastStats();
451
+ if (stats && stats.droppedNames.length > 0 && process.env.DEBUG) {
452
+ log.debug('Injection budget dropped items', { droppedNames: stats.droppedNames.join(', '), proposedTokens: stats.proposedTokens, acceptedTokens: stats.acceptedTokens });
453
+ }
454
+ if (stats && process.env.DEBUG_LLM) {
455
+ log.debug('Injection budget summary', { iteration: ctx.state.iteration, accepted: accepted.length, total: proposals.length, tokens: stats.acceptedTokens });
456
+ }
457
+ }
458
+ }
459
+ // =======================================================================
460
+ // RESILIENT LLM CALL
461
+ // =======================================================================
462
+ const resilienceConfig = typeof ctx.config.resilience === 'object'
463
+ ? ctx.config.resilience
464
+ : {};
465
+ const resilienceEnabled = isFeatureEnabled(ctx.config.resilience);
466
+ const MAX_EMPTY_RETRIES = resilienceConfig.maxEmptyRetries ?? 2;
467
+ const MAX_CONTINUATIONS = resilienceConfig.maxContinuations ?? 3;
468
+ const AUTO_CONTINUE = resilienceConfig.autoContinue ?? true;
469
+ const MIN_CONTENT_LENGTH = resilienceConfig.minContentLength ?? 1;
470
+ const INCOMPLETE_ACTION_RECOVERY = resilienceConfig.incompleteActionRecovery ?? true;
471
+ const MAX_INCOMPLETE_ACTION_RETRIES = resilienceConfig.maxIncompleteActionRetries ?? 2;
472
+ const ENFORCE_REQUESTED_ARTIFACTS = resilienceConfig.enforceRequestedArtifacts ?? true;
473
+ // PRE-FLIGHT BUDGET CHECK
474
+ if (ctx.economics && !forceTextOnly) {
475
+ const estimatedInputTokens = estimateContextTokens(messages);
476
+ const currentUsage = ctx.economics.getUsage();
477
+ const budget = ctx.economics.getBudget();
478
+ // Use proportional output estimate: 10% of remaining budget, capped at 4096, floored at 512.
479
+ // The old hardcoded 4096 caused premature wrapup for subagents with smaller budgets.
480
+ const remainingTokens = budget.maxTokens - currentUsage.tokens - estimatedInputTokens;
481
+ const estimatedOutputTokens = Math.min(4096, Math.max(512, Math.floor(remainingTokens * 0.1)));
482
+ const projectedTotal = currentUsage.tokens + estimatedInputTokens + estimatedOutputTokens;
483
+ if (projectedTotal > budget.maxTokens) {
484
+ ctx.observability?.logger?.warn('Pre-flight budget check: projected overshoot', {
485
+ currentTokens: currentUsage.tokens,
486
+ estimatedInput: estimatedInputTokens,
487
+ projectedTotal,
488
+ maxTokens: budget.maxTokens,
489
+ });
490
+ if (!budgetInjectedPrompt) {
491
+ messages.push({
492
+ role: 'user',
493
+ content: '[System] BUDGET CRITICAL: This is your LAST response. Summarize findings concisely and stop. Do NOT call tools.',
494
+ });
495
+ ctx.state.messages.push({
496
+ role: 'user',
497
+ content: '[System] BUDGET CRITICAL: This is your LAST response. Summarize findings concisely and stop. Do NOT call tools.',
498
+ });
499
+ }
500
+ forceTextOnly = true;
501
+ }
502
+ }
503
+ let response = await callLLM(messages, ctx);
504
+ let emptyRetries = 0;
505
+ let continuations = 0;
506
+ // Phase 1: Handle empty responses with retry
507
+ while (resilienceEnabled && emptyRetries < MAX_EMPTY_RETRIES) {
508
+ const hasContent = response.content && response.content.length >= MIN_CONTENT_LENGTH;
509
+ const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
510
+ const hasThinking = response.thinking && response.thinking.length > 0;
511
+ if (hasContent || hasToolCalls) {
512
+ if (emptyRetries > 0) {
513
+ ctx.emit({
514
+ type: 'resilience.recovered',
515
+ reason: 'empty_response',
516
+ attempts: emptyRetries,
517
+ });
518
+ ctx.observability?.logger?.info('Recovered from empty response', {
519
+ retries: emptyRetries,
520
+ });
521
+ }
522
+ break;
523
+ }
524
+ if (hasThinking && !hasContent && !hasToolCalls) {
525
+ if (emptyRetries === 0) {
526
+ emptyRetries++;
527
+ ctx.emit({
528
+ type: 'resilience.retry',
529
+ reason: 'thinking_only_response',
530
+ attempt: emptyRetries,
531
+ maxAttempts: MAX_EMPTY_RETRIES,
532
+ });
533
+ ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
534
+ ctx.observability?.logger?.warn('Thinking-only response (no visible content), nudging', {
535
+ thinkingLength: response.thinking.length,
536
+ });
537
+ const thinkingNudge = {
538
+ role: 'user',
539
+ content: '[System: You produced reasoning but no visible response. Please provide your answer based on your analysis.]',
540
+ };
541
+ messages.push(thinkingNudge);
542
+ ctx.state.messages.push(thinkingNudge);
543
+ response = await callLLM(messages, ctx);
544
+ continue;
545
+ }
546
+ ctx.observability?.logger?.info('Accepting thinking as content after nudge failed', {
547
+ thinkingLength: response.thinking.length,
548
+ });
549
+ response = { ...response, content: response.thinking };
550
+ break;
551
+ }
552
+ emptyRetries++;
553
+ ctx.emit({
554
+ type: 'resilience.retry',
555
+ reason: 'empty_response',
556
+ attempt: emptyRetries,
557
+ maxAttempts: MAX_EMPTY_RETRIES,
558
+ });
559
+ ctx.state.metrics.retryCount = (ctx.state.metrics.retryCount ?? 0) + 1;
560
+ ctx.observability?.logger?.warn('Empty LLM response, retrying', {
561
+ attempt: emptyRetries,
562
+ maxAttempts: MAX_EMPTY_RETRIES,
563
+ });
564
+ const nudgeMessage = {
565
+ role: 'user',
566
+ content: '[System: Your previous response was empty. Please provide a response or use a tool.]',
567
+ };
568
+ messages.push(nudgeMessage);
569
+ ctx.state.messages.push(nudgeMessage);
570
+ response = await callLLM(messages, ctx);
571
+ }
572
+ // Phase 2: Handle max_tokens truncation with continuation
573
+ if (resilienceEnabled && AUTO_CONTINUE && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
574
+ let accumulatedContent = response.content || '';
575
+ while (continuations < MAX_CONTINUATIONS && response.stopReason === 'max_tokens') {
576
+ continuations++;
577
+ ctx.emit({
578
+ type: 'resilience.continue',
579
+ reason: 'max_tokens',
580
+ continuation: continuations,
581
+ maxContinuations: MAX_CONTINUATIONS,
582
+ accumulatedLength: accumulatedContent.length,
583
+ });
584
+ ctx.observability?.logger?.info('Response truncated at max_tokens, continuing', {
585
+ continuation: continuations,
586
+ accumulatedLength: accumulatedContent.length,
587
+ });
588
+ const continuationMessage = {
589
+ role: 'assistant',
590
+ content: accumulatedContent,
591
+ };
592
+ const continueRequest = {
593
+ role: 'user',
594
+ content: '[System: Please continue from where you left off. Do not repeat what you already said.]',
595
+ };
596
+ messages.push(continuationMessage, continueRequest);
597
+ ctx.state.messages.push(continuationMessage, continueRequest);
598
+ response = await callLLM(messages, ctx);
599
+ if (response.content) {
600
+ accumulatedContent += response.content;
601
+ }
602
+ }
603
+ if (continuations > 0) {
604
+ response = { ...response, content: accumulatedContent };
605
+ ctx.emit({
606
+ type: 'resilience.completed',
607
+ reason: 'max_tokens_continuation',
608
+ continuations,
609
+ finalLength: accumulatedContent.length,
610
+ });
611
+ }
612
+ }
613
+ // Phase 2b: Handle truncated tool calls
614
+ if (resilienceEnabled && response.stopReason === 'max_tokens' && response.toolCalls?.length) {
615
+ ctx.emit({
616
+ type: 'resilience.truncated_tool_call',
617
+ toolNames: response.toolCalls.map(tc => tc.name),
618
+ });
619
+ ctx.observability?.logger?.warn('Tool call truncated at max_tokens', {
620
+ toolNames: response.toolCalls.map(tc => tc.name),
621
+ outputTokens: response.usage?.outputTokens,
622
+ });
623
+ const truncatedResponse = response;
624
+ response = { ...response, toolCalls: undefined };
625
+ const recoveryMessage = {
626
+ role: 'user',
627
+ content: '[System: Your previous tool call was truncated because the output exceeded the token limit. ' +
628
+ 'The tool call arguments were cut off and could not be parsed. ' +
629
+ 'Please retry with a smaller approach: for write_file, break the content into smaller chunks ' +
630
+ 'or use edit_file for targeted changes instead of rewriting entire files.]',
631
+ };
632
+ messages.push({ role: 'assistant', content: truncatedResponse.content || '' });
633
+ messages.push(recoveryMessage);
634
+ ctx.state.messages.push({ role: 'assistant', content: truncatedResponse.content || '' });
635
+ ctx.state.messages.push(recoveryMessage);
636
+ response = await callLLM(messages, ctx);
637
+ }
638
+ // Record LLM usage for economics
639
+ if (ctx.economics && response.usage) {
640
+ ctx.economics.recordLLMUsage(response.usage.inputTokens, response.usage.outputTokens, ctx.config.model, response.usage.cost);
641
+ // POST-LLM BUDGET CHECK
642
+ if (!forceTextOnly) {
643
+ const postCheck = ctx.economics.checkBudget();
644
+ if (!postCheck.canContinue) {
645
+ ctx.observability?.logger?.warn('Budget exceeded after LLM call, skipping tool execution', {
646
+ reason: postCheck.reason,
647
+ });
648
+ forceTextOnly = true;
649
+ }
650
+ }
651
+ }
652
+ // Add assistant message
653
+ const assistantMessage = {
654
+ role: 'assistant',
655
+ content: response.content,
656
+ toolCalls: response.toolCalls,
657
+ ...(response.thinking ? { metadata: { thinking: response.thinking } } : {}),
658
+ };
659
+ messages.push(assistantMessage);
660
+ ctx.state.messages.push(assistantMessage);
661
+ lastResponse = response.content || (response.thinking ? response.thinking : '');
662
+ // Plan mode: capture exploration findings
663
+ if (ctx.modeManager.getMode() === 'plan' && response.content && response.content.length > 50) {
664
+ const hasReadOnlyTools = response.toolCalls?.every(tc => ['read_file', 'list_files', 'glob', 'grep', 'search', 'mcp_'].some(prefix => tc.name.startsWith(prefix) || tc.name === prefix));
665
+ if (hasReadOnlyTools && !response.content.match(/^(Let me|I'll|I will|I need to|First,)/i)) {
666
+ ctx.pendingPlanManager.appendExplorationFinding(response.content.slice(0, 1000));
667
+ }
668
+ }
669
+ // Check for tool calls
670
+ const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
671
+ if (!hasToolCalls || forceTextOnly) {
672
+ if (forceTextOnly && hasToolCalls) {
673
+ ctx.observability?.logger?.info('Ignoring tool calls due to forceTextOnly (max steps reached)', {
674
+ toolCallCount: response.toolCalls?.length,
675
+ iteration: ctx.state.iteration,
676
+ });
677
+ }
678
+ // Track text-only turns for summary-loop detection (skip forceTextOnly — that's expected)
679
+ if (!hasToolCalls && !forceTextOnly) {
680
+ ctx.economics?.recordTextOnlyTurn();
681
+ }
682
+ const incompleteAction = detectIncompleteActionResponse(response.content || '');
683
+ const missingRequiredArtifact = ENFORCE_REQUESTED_ARTIFACTS
684
+ ? isRequestedArtifactMissing(requestedArtifact, executedToolNames)
685
+ : false;
686
+ const shouldRecoverIncompleteAction = resilienceEnabled
687
+ && INCOMPLETE_ACTION_RECOVERY
688
+ && !forceTextOnly
689
+ && (incompleteAction || missingRequiredArtifact);
690
+ if (shouldRecoverIncompleteAction) {
691
+ ctx.emit({
692
+ type: 'completion.before',
693
+ reason: missingRequiredArtifact && requestedArtifact
694
+ ? `missing_requested_artifact:${requestedArtifact}`
695
+ : 'future_intent_without_action',
696
+ attempt: incompleteActionRetries + 1,
697
+ maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
698
+ });
699
+ if (incompleteActionRetries < MAX_INCOMPLETE_ACTION_RETRIES) {
700
+ incompleteActionRetries++;
701
+ const reason = missingRequiredArtifact && requestedArtifact
702
+ ? `missing_requested_artifact:${requestedArtifact}`
703
+ : 'future_intent_without_action';
704
+ ctx.emit({
705
+ type: 'recovery.before',
706
+ reason,
707
+ attempt: incompleteActionRetries,
708
+ maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
709
+ });
710
+ ctx.emit({
711
+ type: 'resilience.incomplete_action_detected',
712
+ reason,
713
+ attempt: incompleteActionRetries,
714
+ maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
715
+ requiresArtifact: missingRequiredArtifact,
716
+ });
717
+ ctx.observability?.logger?.warn('Incomplete action detected, retrying with nudge', {
718
+ reason,
719
+ attempt: incompleteActionRetries,
720
+ maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
721
+ });
722
+ const nudgeMessage = {
723
+ role: 'user',
724
+ content: missingRequiredArtifact && requestedArtifact
725
+ ? `[System: You said you would complete the next action, but no tool call was made. The task requires creating or updating "${requestedArtifact}". Execute the required tool now, or explicitly explain why it cannot be produced.]`
726
+ : '[System: You described a next action but did not execute it. If work remains, call the required tool now. If the task is complete, provide a final answer with no pending action language.]',
727
+ };
728
+ messages.push(nudgeMessage);
729
+ ctx.state.messages.push(nudgeMessage);
730
+ ctx.emit({
731
+ type: 'iteration.after',
732
+ iteration: ctx.state.iteration,
733
+ hadToolCalls: false,
734
+ completionCandidate: false,
735
+ });
736
+ continue;
737
+ }
738
+ const failureReason = missingRequiredArtifact && requestedArtifact
739
+ ? `incomplete_action_missing_artifact:${requestedArtifact}`
740
+ : 'incomplete_action_unresolved';
741
+ ctx.emit({
742
+ type: 'resilience.incomplete_action_failed',
743
+ reason: failureReason,
744
+ attempts: incompleteActionRetries,
745
+ maxAttempts: MAX_INCOMPLETE_ACTION_RETRIES,
746
+ });
747
+ ctx.emit({
748
+ type: 'recovery.after',
749
+ reason: failureReason,
750
+ recovered: false,
751
+ attempts: incompleteActionRetries,
752
+ });
753
+ const reason = `LLM failed to complete requested action after ${incompleteActionRetries} retries (${failureReason})`;
754
+ result = {
755
+ success: false,
756
+ terminationReason: 'incomplete_action',
757
+ failureReason: reason,
758
+ };
759
+ ctx.emit({
760
+ type: 'completion.after',
761
+ success: false,
762
+ reason: 'incomplete_action',
763
+ details: reason,
764
+ });
765
+ throw new Error(reason);
766
+ }
767
+ if (incompleteActionRetries > 0) {
768
+ ctx.emit({
769
+ type: 'resilience.incomplete_action_recovered',
770
+ reason: 'incomplete_action',
771
+ attempts: incompleteActionRetries,
772
+ });
773
+ ctx.emit({
774
+ type: 'recovery.after',
775
+ reason: 'incomplete_action',
776
+ recovered: true,
777
+ attempts: incompleteActionRetries,
778
+ });
779
+ incompleteActionRetries = 0;
780
+ }
781
+ // Verification gate
782
+ if (ctx.verificationGate && !forceTextOnly) {
783
+ const vResult = ctx.verificationGate.check();
784
+ if (!vResult.satisfied && !vResult.forceAllow && vResult.nudge) {
785
+ const nudgeMessage = {
786
+ role: 'user',
787
+ content: vResult.nudge,
788
+ };
789
+ messages.push(nudgeMessage);
790
+ ctx.state.messages.push(nudgeMessage);
791
+ ctx.observability?.logger?.info('Verification gate nudge', {
792
+ missing: vResult.missing,
793
+ nudgeCount: ctx.verificationGate.getState().nudgeCount,
794
+ });
795
+ ctx.emit({
796
+ type: 'iteration.after',
797
+ iteration: ctx.state.iteration,
798
+ hadToolCalls: false,
799
+ completionCandidate: false,
800
+ });
801
+ continue;
802
+ }
803
+ }
804
+ // No tool calls — agent is done
805
+ compactToolOutputs(ctx.state.messages);
806
+ // Plan mode: capture exploration summary
807
+ if (ctx.modeManager.getMode() === 'plan' && ctx.pendingPlanManager.hasPendingPlan()) {
808
+ const explorationContent = response.content || '';
809
+ if (explorationContent.length > 0) {
810
+ ctx.pendingPlanManager.setExplorationSummary(explorationContent);
811
+ }
812
+ }
813
+ // Final validation
814
+ if (!response.content || response.content.length === 0) {
815
+ ctx.observability?.logger?.error('Agent finished with empty response after all retries', {
816
+ emptyRetries,
817
+ continuations,
818
+ iteration: ctx.state.iteration,
819
+ });
820
+ ctx.emit({
821
+ type: 'resilience.failed',
822
+ reason: 'empty_final_response',
823
+ emptyRetries,
824
+ continuations,
825
+ });
826
+ }
827
+ ctx.emit({
828
+ type: 'completion.after',
829
+ success: true,
830
+ reason: 'completed',
831
+ });
832
+ ctx.emit({
833
+ type: 'iteration.after',
834
+ iteration: ctx.state.iteration,
835
+ hadToolCalls: false,
836
+ completionCandidate: true,
837
+ });
838
+ // Record iteration end for tracing (no tool calls case)
839
+ ctx.traceCollector?.record({
840
+ type: 'iteration.end',
841
+ data: { iterationNumber: ctx.state.iteration },
842
+ });
843
+ // =====================================================================
844
+ // TASK EXECUTION LOOP — pick up next available task before exiting
845
+ // =====================================================================
846
+ if (ctx.taskManager) {
847
+ // Reconcile stale in-progress tasks before deciding there is no more work.
848
+ ctx.taskManager.reconcileStaleInProgress({
849
+ staleAfterMs: taskLeaseStaleMs,
850
+ reason: 'completion_gate',
851
+ });
852
+ const pendingWithOwner = getPendingWithOwnerCount(ctx);
853
+ const availableTasks = ctx.taskManager.getAvailableTasks();
854
+ if (!forceTextOnly && availableTasks.length > 0) {
855
+ const nextTask = availableTasks[0];
856
+ ctx.taskManager.claim(nextTask.id, ctx.agentId);
857
+ log.info('Picking up next task from task list', {
858
+ taskId: nextTask.id,
859
+ subject: nextTask.subject,
860
+ });
861
+ const taskPrompt = {
862
+ role: 'user',
863
+ content: `[System] Previous work is done. Now work on the next task:\n\n**Task ${nextTask.id}: ${nextTask.subject}**\n${nextTask.description}\n\nStart working on this task now using your tools.`,
864
+ };
865
+ messages.push(taskPrompt);
866
+ ctx.state.messages.push(taskPrompt);
867
+ ctx.emit({
868
+ type: 'iteration.after',
869
+ iteration: ctx.state.iteration,
870
+ hadToolCalls: false,
871
+ completionCandidate: false,
872
+ });
873
+ continue; // Re-enter the main loop for the next task
874
+ }
875
+ const openTasks = getOpenTaskSummary(ctx);
876
+ if (openTasks && (openTasks.inProgress > 0 || openTasks.pending > 0)) {
877
+ if (forceTextOnly) {
878
+ const reason = `Task continuation suppressed by forceTextOnly mode: ${openTasks.pending} pending, ${openTasks.inProgress} in_progress`;
879
+ ctx.emit({
880
+ type: 'completion.blocked',
881
+ reasons: [reason],
882
+ openTasks,
883
+ diagnostics: {
884
+ forceTextOnly: true,
885
+ availableTasks: availableTasks.length,
886
+ pendingWithOwner,
887
+ },
888
+ });
889
+ result = {
890
+ success: false,
891
+ terminationReason: 'budget_limit',
892
+ failureReason: reason,
893
+ openTasks,
894
+ };
895
+ break;
896
+ }
897
+ const reasons = [
898
+ `Open tasks remain: ${openTasks.pending} pending, ${openTasks.inProgress} in_progress`,
899
+ openTasks.blocked > 0 ? `${openTasks.blocked} pending tasks are currently blocked` : '',
900
+ ].filter(Boolean);
901
+ ctx.emit({
902
+ type: 'completion.blocked',
903
+ reasons,
904
+ openTasks,
905
+ diagnostics: {
906
+ forceTextOnly: false,
907
+ availableTasks: availableTasks.length,
908
+ pendingWithOwner,
909
+ },
910
+ });
911
+ result = {
912
+ success: false,
913
+ terminationReason: 'open_tasks',
914
+ failureReason: reasons.join('; '),
915
+ openTasks,
916
+ };
917
+ }
918
+ }
919
+ break;
920
+ }
921
+ // Execute tool calls
922
+ const toolCalls = response.toolCalls;
923
+ // SAFEGUARD: Hard cap on tool calls per LLM response
924
+ const maxToolCallsPerResponse = ctx.economics?.getBudget()?.tuning?.maxToolCallsPerResponse ?? 25;
925
+ if (toolCalls.length > maxToolCallsPerResponse) {
926
+ log.warn('Tool call explosion detected — capping', {
927
+ requested: toolCalls.length,
928
+ cap: maxToolCallsPerResponse,
929
+ toolNames: [...new Set(toolCalls.map(tc => tc.name))],
930
+ });
931
+ ctx.emit({
932
+ type: 'safeguard.tool_call_cap',
933
+ requested: toolCalls.length,
934
+ cap: maxToolCallsPerResponse,
935
+ droppedCount: toolCalls.length - maxToolCallsPerResponse,
936
+ });
937
+ toolCalls.splice(maxToolCallsPerResponse);
938
+ }
939
+ const toolResults = await executeToolCalls(toolCalls, ctx);
940
+ ctx.emit({
941
+ type: 'iteration.after',
942
+ iteration: ctx.state.iteration,
943
+ hadToolCalls: true,
944
+ completionCandidate: false,
945
+ });
946
+ // Record tool calls for economics/progress tracking + work log
947
+ for (let i = 0; i < toolCalls.length; i++) {
948
+ const toolCall = toolCalls[i];
949
+ const result = toolResults[i];
950
+ executedToolNames.add(toolCall.name);
951
+ ctx.economics?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
952
+ ctx.stateMachine?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
953
+ // Record in work log
954
+ const toolOutput = result?.result && typeof result.result === 'object' && 'output' in result.result
955
+ ? String(result.result.output)
956
+ : typeof result?.result === 'string' ? result.result : undefined;
957
+ ctx.workLog?.recordToolExecution(toolCall.name, toolCall.arguments, toolOutput);
958
+ // Record in verification gate
959
+ if (ctx.verificationGate) {
960
+ if (toolCall.name === 'bash') {
961
+ const toolRes = result?.result;
962
+ const output = toolRes && typeof toolRes === 'object' && 'output' in toolRes
963
+ ? String(toolRes.output)
964
+ : typeof toolRes === 'string' ? toolRes : '';
965
+ const exitCode = toolRes && typeof toolRes === 'object' && toolRes.metadata
966
+ ? toolRes.metadata.exitCode ?? null
967
+ : null;
968
+ ctx.verificationGate.recordBashExecution(String(toolCall.arguments.command || ''), output, exitCode);
969
+ }
970
+ if (['write_file', 'edit_file'].includes(toolCall.name)) {
971
+ ctx.verificationGate.recordFileChange();
972
+ }
973
+ }
974
+ // Phase 5.1: Post-edit syntax validation
975
+ if (['write_file', 'edit_file'].includes(toolCall.name) && result?.result && result.result.success) {
976
+ const filePath = String(toolCall.arguments.path || '');
977
+ if (filePath) {
978
+ try {
979
+ const content = toolCall.name === 'write_file'
980
+ ? String(toolCall.arguments.content || '')
981
+ : await fs.promises.readFile(filePath, 'utf-8');
982
+ const validation = validateSyntax(content, filePath);
983
+ if (!validation.valid && result.result && typeof result.result === 'object') {
984
+ const errorSummary = validation.errors
985
+ .slice(0, 3)
986
+ .map(e => ` L${e.line}:${e.column}: ${e.message}`)
987
+ .join('\n');
988
+ result.result.output += `\n\n⚠ Syntax validation warning:\n${errorSummary}`;
989
+ }
990
+ }
991
+ catch {
992
+ // Validation failure is non-blocking
993
+ }
994
+ }
995
+ }
996
+ }
997
+ // Add tool results to messages (with truncation and proactive budget management)
998
+ const MAX_TOOL_OUTPUT_CHARS = 8000;
999
+ // PROACTIVE BUDGET CHECK
1000
+ const currentContextTokens = estimateContextTokens(messages);
1001
+ if (ctx.autoCompactionManager) {
1002
+ const compactionResult = await ctx.autoCompactionManager.checkAndMaybeCompact({
1003
+ currentTokens: currentContextTokens,
1004
+ messages: messages,
1005
+ });
1006
+ if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
1007
+ if (!ctx.compactionPending) {
1008
+ mutators.setCompactionPending(true);
1009
+ const preCompactionMsg = {
1010
+ role: 'user',
1011
+ content: '[SYSTEM] Context compaction is imminent. Summarize your current progress, key findings, and next steps into a single concise message. This will be preserved after compaction.',
1012
+ };
1013
+ messages.push(preCompactionMsg);
1014
+ ctx.state.messages.push(preCompactionMsg);
1015
+ ctx.observability?.logger?.info('Pre-compaction agentic turn: injected summary request');
1016
+ }
1017
+ else {
1018
+ mutators.setCompactionPending(false);
1019
+ // Pre-compaction checkpoint
1020
+ // NOTE: autoCheckpoint is called via the agent's method, not directly here
1021
+ // The agent wires this through the mutators pattern
1022
+ // Replace messages with compacted version
1023
+ messages.length = 0;
1024
+ messages.push(...compactionResult.compactedMessages);
1025
+ ctx.state.messages.length = 0;
1026
+ ctx.state.messages.push(...compactionResult.compactedMessages);
1027
+ // Inject work log after compaction
1028
+ if (ctx.workLog?.hasContent()) {
1029
+ const workLogMessage = {
1030
+ role: 'user',
1031
+ content: ctx.workLog.toCompactString(),
1032
+ };
1033
+ messages.push(workLogMessage);
1034
+ ctx.state.messages.push(workLogMessage);
1035
+ }
1036
+ // Context recovery
1037
+ const recoveryParts = [];
1038
+ if (ctx.store) {
1039
+ const goalsSummary = ctx.store.getGoalsSummary();
1040
+ if (goalsSummary && goalsSummary !== 'No active goals.' && goalsSummary !== 'Goals feature not available.') {
1041
+ recoveryParts.push(goalsSummary);
1042
+ }
1043
+ }
1044
+ if (ctx.store) {
1045
+ const juncturesSummary = ctx.store.getJuncturesSummary(undefined, 5);
1046
+ if (juncturesSummary) {
1047
+ recoveryParts.push(juncturesSummary);
1048
+ }
1049
+ }
1050
+ if (ctx.learningStore) {
1051
+ const learnings = ctx.learningStore.getLearningContext({ maxLearnings: 3 });
1052
+ if (learnings) {
1053
+ recoveryParts.push(learnings);
1054
+ }
1055
+ }
1056
+ if (recoveryParts.length > 0) {
1057
+ const recoveryMessage = {
1058
+ role: 'user',
1059
+ content: `[CONTEXT RECOVERY — Re-injected after compaction]\n\n${recoveryParts.join('\n\n')}`,
1060
+ };
1061
+ messages.push(recoveryMessage);
1062
+ ctx.state.messages.push(recoveryMessage);
1063
+ }
1064
+ // Emit compaction event
1065
+ const compactionTokensAfter = estimateContextTokens(messages);
1066
+ const compactionRecoveryInjected = recoveryParts.length > 0;
1067
+ const compactionEvent = {
1068
+ type: 'context.compacted',
1069
+ tokensBefore: currentContextTokens,
1070
+ tokensAfter: compactionTokensAfter,
1071
+ recoveryInjected: compactionRecoveryInjected,
1072
+ };
1073
+ ctx.emit(compactionEvent);
1074
+ if (ctx.traceCollector) {
1075
+ ctx.traceCollector.record({
1076
+ type: 'context.compacted',
1077
+ data: {
1078
+ tokensBefore: currentContextTokens,
1079
+ tokensAfter: compactionTokensAfter,
1080
+ recoveryInjected: compactionRecoveryInjected,
1081
+ },
1082
+ });
1083
+ }
1084
+ }
1085
+ }
1086
+ else if (compactionResult.status === 'hard_limit') {
1087
+ const reason = `Context hard limit reached (${Math.round(compactionResult.ratio * 100)}% of max tokens)`;
1088
+ ctx.emit({ type: 'error', error: reason });
1089
+ result = {
1090
+ success: false,
1091
+ terminationReason: 'hard_context_limit',
1092
+ failureReason: reason,
1093
+ };
1094
+ break;
1095
+ }
1096
+ }
1097
+ else if (ctx.economics) {
1098
+ // Fallback to simple compaction
1099
+ const currentUsage = ctx.economics.getUsage();
1100
+ const budget = ctx.economics.getBudget();
1101
+ const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
1102
+ if (percentUsed >= 70) {
1103
+ ctx.observability?.logger?.info('Proactive compaction triggered', {
1104
+ percentUsed: Math.round(percentUsed),
1105
+ currentTokens: currentUsage.tokens,
1106
+ maxTokens: budget.maxTokens,
1107
+ });
1108
+ compactToolOutputs(ctx.state.messages);
1109
+ }
1110
+ }
1111
+ // SAFEGUARD: Aggregate context guard — prevent mass small results from overflowing
1112
+ if (ctx.economics && toolResults.length > 10) {
1113
+ const preAccumTokens = estimateContextTokens(messages);
1114
+ const budget = ctx.economics.getBudget();
1115
+ const availableTokens = budget.maxTokens * 0.90 - preAccumTokens;
1116
+ // Estimate total result tokens
1117
+ let totalResultTokens = 0;
1118
+ for (const r of toolResults) {
1119
+ const c = typeof r.result === 'string' ? r.result : stableStringify(r.result);
1120
+ totalResultTokens += Math.ceil(Math.min(c.length, MAX_TOOL_OUTPUT_CHARS) / 4);
1121
+ }
1122
+ if (totalResultTokens > availableTokens && availableTokens > 0) {
1123
+ log.warn('Tool results would exceed context budget — truncating batch', {
1124
+ resultCount: toolResults.length,
1125
+ estimatedTokens: totalResultTokens,
1126
+ availableTokens: Math.round(availableTokens),
1127
+ });
1128
+ let tokenBudget = availableTokens;
1129
+ for (let i = 0; i < toolResults.length; i++) {
1130
+ const c = typeof toolResults[i].result === 'string'
1131
+ ? toolResults[i].result
1132
+ : stableStringify(toolResults[i].result);
1133
+ const tokens = Math.ceil(Math.min(c.length, MAX_TOOL_OUTPUT_CHARS) / 4);
1134
+ if (tokens > tokenBudget) {
1135
+ const skipped = toolResults.length - i;
1136
+ for (let j = i; j < toolResults.length; j++) {
1137
+ toolResults[j] = {
1138
+ callId: toolResults[j].callId,
1139
+ result: `[Result omitted: context overflow guard — ${skipped} of ${toolResults.length} results skipped]`,
1140
+ };
1141
+ }
1142
+ ctx.emit({
1143
+ type: 'safeguard.context_overflow_guard',
1144
+ estimatedTokens: totalResultTokens,
1145
+ maxTokens: budget.maxTokens,
1146
+ toolResultsSkipped: skipped,
1147
+ });
1148
+ break;
1149
+ }
1150
+ tokenBudget -= tokens;
1151
+ }
1152
+ }
1153
+ }
1154
+ const toolCallNameById = new Map(toolCalls.map(tc => [tc.id, tc.name]));
1155
+ for (const result of toolResults) {
1156
+ let content = typeof result.result === 'string' ? result.result : stableStringify(result.result);
1157
+ const sourceToolName = toolCallNameById.get(result.callId);
1158
+ const isExpensiveResult = sourceToolName === 'spawn_agent' || sourceToolName === 'spawn_agents_parallel';
1159
+ const effectiveMaxChars = isExpensiveResult ? MAX_TOOL_OUTPUT_CHARS * 2 : MAX_TOOL_OUTPUT_CHARS;
1160
+ if (content.length > effectiveMaxChars) {
1161
+ content = content.slice(0, effectiveMaxChars) + `\n\n... [truncated ${content.length - effectiveMaxChars} chars]`;
1162
+ }
1163
+ // Check if adding this result would exceed budget
1164
+ if (ctx.economics) {
1165
+ const estimatedNewTokens = Math.ceil(content.length / 4);
1166
+ const currentCtxTokens = estimateContextTokens(messages);
1167
+ const budget = ctx.economics.getBudget();
1168
+ if (currentCtxTokens + estimatedNewTokens > budget.maxTokens * 0.95) {
1169
+ ctx.observability?.logger?.warn('Skipping tool result to stay within budget', {
1170
+ toolCallId: result.callId,
1171
+ estimatedTokens: estimatedNewTokens,
1172
+ currentContext: currentCtxTokens,
1173
+ limit: budget.maxTokens,
1174
+ });
1175
+ const toolMessage = {
1176
+ role: 'tool',
1177
+ content: `[Result omitted to stay within token budget. Original size: ${content.length} chars]`,
1178
+ toolCallId: result.callId,
1179
+ };
1180
+ messages.push(toolMessage);
1181
+ ctx.state.messages.push(toolMessage);
1182
+ continue;
1183
+ }
1184
+ }
1185
+ const toolMessage = {
1186
+ role: 'tool',
1187
+ content,
1188
+ toolCallId: result.callId,
1189
+ ...(isExpensiveResult
1190
+ ? {
1191
+ metadata: {
1192
+ preserveFromCompaction: true,
1193
+ costToRegenerate: 'high',
1194
+ source: sourceToolName,
1195
+ },
1196
+ }
1197
+ : {}),
1198
+ };
1199
+ messages.push(toolMessage);
1200
+ ctx.state.messages.push(toolMessage);
1201
+ }
1202
+ // Emit context health
1203
+ const currentTokenEstimate = estimateContextTokens(messages);
1204
+ const contextLimit = ctx.getMaxContextTokens();
1205
+ const percentUsed = Math.round((currentTokenEstimate / contextLimit) * 100);
1206
+ const avgTokensPerExchange = currentTokenEstimate / Math.max(1, ctx.state.iteration);
1207
+ const remainingTokens = contextLimit - currentTokenEstimate;
1208
+ const estimatedExchanges = Math.floor(remainingTokens / Math.max(1, avgTokensPerExchange));
1209
+ ctx.emit({
1210
+ type: 'context.health',
1211
+ currentTokens: currentTokenEstimate,
1212
+ maxTokens: contextLimit,
1213
+ estimatedExchanges,
1214
+ percentUsed,
1215
+ });
1216
+ // Record iteration end
1217
+ ctx.traceCollector?.record({
1218
+ type: 'iteration.end',
1219
+ data: { iterationNumber: ctx.state.iteration },
1220
+ });
1221
+ }
1222
+ // =======================================================================
1223
+ // REFLECTION (Lesson 16)
1224
+ // =======================================================================
1225
+ if (!result.success) {
1226
+ break;
1227
+ }
1228
+ if (autoReflect && ctx.planning && reflectionAttempt < maxReflectionAttempts) {
1229
+ ctx.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: false });
1230
+ const reflectionResult = await ctx.planning.reflect(task, lastResponse, ctx.provider);
1231
+ ctx.state.metrics.reflectionAttempts = reflectionAttempt;
1232
+ if (reflectionResult.satisfied && reflectionResult.confidence >= confidenceThreshold) {
1233
+ ctx.emit({ type: 'reflection', attempt: reflectionAttempt, satisfied: true });
1234
+ break;
1235
+ }
1236
+ const feedbackMessage = {
1237
+ role: 'user',
1238
+ content: `[Reflection feedback]\nThe previous output needs improvement:\n- Critique: ${reflectionResult.critique}\n- Suggestions: ${reflectionResult.suggestions.join(', ')}\n\nPlease improve the output.`,
1239
+ };
1240
+ messages.push(feedbackMessage);
1241
+ ctx.state.messages.push(feedbackMessage);
1242
+ ctx.observability?.logger?.info('Reflection not satisfied, retrying', {
1243
+ attempt: reflectionAttempt,
1244
+ confidence: reflectionResult.confidence,
1245
+ critique: reflectionResult.critique,
1246
+ });
1247
+ }
1248
+ else {
1249
+ break;
1250
+ }
1251
+ }
1252
+ // Store conversation in memory
1253
+ ctx.memory?.storeConversation(ctx.state.messages);
1254
+ // Memory stats update (hook point)
1255
+ ctx.memory?.getStats();
1256
+ return result;
1257
+ }
1258
+ //# sourceMappingURL=execution-loop.js.map