@orion-agents/orion-code 0.1.1 → 0.1.2

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 (602) hide show
  1. package/README.md +93 -68
  2. package/README.zh-CN.md +181 -216
  3. package/dist/cli.js +13 -17
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/index.d.ts.map +1 -1
  6. package/dist/commands/index.js +213 -114
  7. package/dist/commands/index.js.map +1 -1
  8. package/dist/commands/target-command.d.ts.map +1 -1
  9. package/dist/commands/target-command.js +42 -8
  10. package/dist/commands/target-command.js.map +1 -1
  11. package/dist/core/agent.d.ts.map +1 -1
  12. package/dist/core/agent.js +2 -2
  13. package/dist/core/agent.js.map +1 -1
  14. package/dist/framework/external-assertion.d.ts +65 -0
  15. package/dist/framework/external-assertion.d.ts.map +1 -0
  16. package/dist/framework/external-assertion.js +571 -0
  17. package/dist/framework/external-assertion.js.map +1 -0
  18. package/dist/framework/index.d.ts +6 -4
  19. package/dist/framework/index.d.ts.map +1 -1
  20. package/dist/framework/index.js +4 -1
  21. package/dist/framework/index.js.map +1 -1
  22. package/dist/framework/query.d.ts +15 -1
  23. package/dist/framework/query.d.ts.map +1 -1
  24. package/dist/framework/query.js +42 -23
  25. package/dist/framework/query.js.map +1 -1
  26. package/dist/framework/tool-scheduler.d.ts +2 -0
  27. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  28. package/dist/framework/tool-scheduler.js +23 -9
  29. package/dist/framework/tool-scheduler.js.map +1 -1
  30. package/dist/framework/tool-serializer.d.ts.map +1 -1
  31. package/dist/framework/tool-serializer.js +20 -4
  32. package/dist/framework/tool-serializer.js.map +1 -1
  33. package/dist/framework/tool.d.ts +11 -5
  34. package/dist/framework/tool.d.ts.map +1 -1
  35. package/dist/framework/tool.js.map +1 -1
  36. package/dist/harness/safety.d.ts.map +1 -1
  37. package/dist/harness/safety.js +19 -3
  38. package/dist/harness/safety.js.map +1 -1
  39. package/dist/init.d.ts +1 -1
  40. package/dist/init.d.ts.map +1 -1
  41. package/dist/init.js +115 -34
  42. package/dist/init.js.map +1 -1
  43. package/dist/memory/storage.d.ts.map +1 -1
  44. package/dist/memory/storage.js +1 -0
  45. package/dist/memory/storage.js.map +1 -1
  46. package/dist/memory/store.d.ts.map +1 -1
  47. package/dist/memory/store.js +11 -5
  48. package/dist/memory/store.js.map +1 -1
  49. package/dist/print-ui/launch.d.ts +4 -0
  50. package/dist/print-ui/launch.d.ts.map +1 -1
  51. package/dist/print-ui/launch.js +102 -9
  52. package/dist/print-ui/launch.js.map +1 -1
  53. package/dist/product/identity.d.ts +2 -2
  54. package/dist/product/identity.d.ts.map +1 -1
  55. package/dist/product/identity.js +2 -2
  56. package/dist/product/identity.js.map +1 -1
  57. package/dist/product/index.d.ts +2 -1
  58. package/dist/product/index.d.ts.map +1 -1
  59. package/dist/product/index.js +5 -1
  60. package/dist/product/index.js.map +1 -1
  61. package/dist/product/version.d.ts +12 -0
  62. package/dist/product/version.d.ts.map +1 -0
  63. package/dist/product/version.js +31 -0
  64. package/dist/product/version.js.map +1 -0
  65. package/dist/runtime/agent-runtime-controller.d.ts +34 -0
  66. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  67. package/dist/runtime/agent-runtime-controller.js +953 -148
  68. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  69. package/dist/runtime/agent-runtime-protocol.d.ts +7 -2
  70. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  71. package/dist/runtime/agent-runtime-protocol.js +6 -0
  72. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  73. package/dist/runtime/chat-controller.d.ts +6 -0
  74. package/dist/runtime/chat-controller.d.ts.map +1 -1
  75. package/dist/runtime/chat-controller.js +200 -19
  76. package/dist/runtime/chat-controller.js.map +1 -1
  77. package/dist/runtime/goals/accounting.d.ts +28 -0
  78. package/dist/runtime/goals/accounting.d.ts.map +1 -1
  79. package/dist/runtime/goals/accounting.js +69 -0
  80. package/dist/runtime/goals/accounting.js.map +1 -1
  81. package/dist/runtime/goals/completion-audit.d.ts +13 -8
  82. package/dist/runtime/goals/completion-audit.d.ts.map +1 -1
  83. package/dist/runtime/goals/completion-audit.js +598 -16
  84. package/dist/runtime/goals/completion-audit.js.map +1 -1
  85. package/dist/runtime/goals/coordinator.d.ts +44 -5
  86. package/dist/runtime/goals/coordinator.d.ts.map +1 -1
  87. package/dist/runtime/goals/coordinator.js +1124 -58
  88. package/dist/runtime/goals/coordinator.js.map +1 -1
  89. package/dist/runtime/goals/evidence.d.ts +37 -0
  90. package/dist/runtime/goals/evidence.d.ts.map +1 -0
  91. package/dist/runtime/goals/evidence.js +300 -0
  92. package/dist/runtime/goals/evidence.js.map +1 -0
  93. package/dist/runtime/goals/presentation.d.ts +4 -0
  94. package/dist/runtime/goals/presentation.d.ts.map +1 -0
  95. package/dist/runtime/goals/presentation.js +50 -0
  96. package/dist/runtime/goals/presentation.js.map +1 -0
  97. package/dist/runtime/goals/prompt.d.ts.map +1 -1
  98. package/dist/runtime/goals/prompt.js +38 -8
  99. package/dist/runtime/goals/prompt.js.map +1 -1
  100. package/dist/runtime/goals/tools.d.ts +18 -1
  101. package/dist/runtime/goals/tools.d.ts.map +1 -1
  102. package/dist/runtime/goals/tools.js +308 -28
  103. package/dist/runtime/goals/tools.js.map +1 -1
  104. package/dist/runtime/goals/types.d.ts +241 -7
  105. package/dist/runtime/goals/types.d.ts.map +1 -1
  106. package/dist/runtime/goals/types.js +8 -2
  107. package/dist/runtime/goals/types.js.map +1 -1
  108. package/dist/runtime/subagents/child-executor-guard.d.ts +12 -4
  109. package/dist/runtime/subagents/child-executor-guard.d.ts.map +1 -1
  110. package/dist/runtime/subagents/child-executor-guard.js +397 -61
  111. package/dist/runtime/subagents/child-executor-guard.js.map +1 -1
  112. package/dist/runtime/subagents/presets.d.ts +4 -5
  113. package/dist/runtime/subagents/presets.d.ts.map +1 -1
  114. package/dist/runtime/subagents/presets.js +18 -33
  115. package/dist/runtime/subagents/presets.js.map +1 -1
  116. package/dist/runtime/subagents/production.d.ts +3 -1
  117. package/dist/runtime/subagents/production.d.ts.map +1 -1
  118. package/dist/runtime/subagents/production.js +37 -7
  119. package/dist/runtime/subagents/production.js.map +1 -1
  120. package/dist/runtime/subagents/runner.d.ts.map +1 -1
  121. package/dist/runtime/subagents/runner.js +24 -3
  122. package/dist/runtime/subagents/runner.js.map +1 -1
  123. package/dist/runtime/subagents/runtime-integration.d.ts +3 -0
  124. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  125. package/dist/runtime/subagents/runtime-integration.js +18 -7
  126. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  127. package/dist/runtime/subagents/supervisor.d.ts +4 -4
  128. package/dist/runtime/subagents/supervisor.d.ts.map +1 -1
  129. package/dist/runtime/subagents/supervisor.js +39 -19
  130. package/dist/runtime/subagents/supervisor.js.map +1 -1
  131. package/dist/runtime/subagents/types.d.ts +7 -0
  132. package/dist/runtime/subagents/types.d.ts.map +1 -1
  133. package/dist/runtime/subagents/types.js +12 -1
  134. package/dist/runtime/subagents/types.js.map +1 -1
  135. package/dist/runtime/ui-events.d.ts +6 -0
  136. package/dist/runtime/ui-events.d.ts.map +1 -1
  137. package/dist/runtime/ui-events.js +5 -5
  138. package/dist/runtime/ui-events.js.map +1 -1
  139. package/dist/services/config.d.ts +1 -1
  140. package/dist/services/config.d.ts.map +1 -1
  141. package/dist/services/config.js +53 -48
  142. package/dist/services/config.js.map +1 -1
  143. package/dist/services/doctor.d.ts.map +1 -1
  144. package/dist/services/doctor.js +163 -17
  145. package/dist/services/doctor.js.map +1 -1
  146. package/dist/services/goal-storage.d.ts +9 -4
  147. package/dist/services/goal-storage.d.ts.map +1 -1
  148. package/dist/services/goal-storage.js +1113 -75
  149. package/dist/services/goal-storage.js.map +1 -1
  150. package/dist/services/llm.d.ts +19 -0
  151. package/dist/services/llm.d.ts.map +1 -1
  152. package/dist/services/llm.js +153 -20
  153. package/dist/services/llm.js.map +1 -1
  154. package/dist/services/model-registry.d.ts +1 -1
  155. package/dist/services/model-registry.d.ts.map +1 -1
  156. package/dist/services/model-registry.js +69 -19
  157. package/dist/services/model-registry.js.map +1 -1
  158. package/dist/services/provider-resilience/coordinator.d.ts +2 -1
  159. package/dist/services/provider-resilience/coordinator.d.ts.map +1 -1
  160. package/dist/services/provider-resilience/coordinator.js +7 -6
  161. package/dist/services/provider-resilience/coordinator.js.map +1 -1
  162. package/dist/services/provider-resilience/error-classifier.d.ts.map +1 -1
  163. package/dist/services/provider-resilience/error-classifier.js +67 -17
  164. package/dist/services/provider-resilience/error-classifier.js.map +1 -1
  165. package/dist/services/provider-resilience/types.d.ts +1 -1
  166. package/dist/services/provider-resilience/types.d.ts.map +1 -1
  167. package/dist/services/provider-resilience/types.js.map +1 -1
  168. package/dist/services/redaction.d.ts.map +1 -1
  169. package/dist/services/redaction.js +9 -1
  170. package/dist/services/redaction.js.map +1 -1
  171. package/dist/services/session-storage.d.ts +12 -1
  172. package/dist/services/session-storage.d.ts.map +1 -1
  173. package/dist/services/session-storage.js +23 -7
  174. package/dist/services/session-storage.js.map +1 -1
  175. package/dist/services/storage-maintenance.d.ts +73 -3
  176. package/dist/services/storage-maintenance.d.ts.map +1 -1
  177. package/dist/services/storage-maintenance.js +771 -104
  178. package/dist/services/storage-maintenance.js.map +1 -1
  179. package/dist/services/task-manager.d.ts.map +1 -1
  180. package/dist/services/task-manager.js +2 -2
  181. package/dist/services/task-manager.js.map +1 -1
  182. package/dist/services/web-search-adapters.d.ts.map +1 -1
  183. package/dist/services/web-search-adapters.js +89 -49
  184. package/dist/services/web-search-adapters.js.map +1 -1
  185. package/dist/services/web-search-mcp.d.ts.map +1 -1
  186. package/dist/services/web-search-mcp.js +34 -37
  187. package/dist/services/web-search-mcp.js.map +1 -1
  188. package/dist/services/workspace-state.d.ts +4 -0
  189. package/dist/services/workspace-state.d.ts.map +1 -1
  190. package/dist/services/workspace-state.js +30 -9
  191. package/dist/services/workspace-state.js.map +1 -1
  192. package/dist/terminal-ui/launch.d.ts +2 -0
  193. package/dist/terminal-ui/launch.d.ts.map +1 -1
  194. package/dist/terminal-ui/launch.js +21 -7
  195. package/dist/terminal-ui/launch.js.map +1 -1
  196. package/dist/tools/git.d.ts.map +1 -1
  197. package/dist/tools/git.js +275 -27
  198. package/dist/tools/git.js.map +1 -1
  199. package/dist/tools/index.d.ts.map +1 -1
  200. package/dist/tools/index.js +23 -4
  201. package/dist/tools/index.js.map +1 -1
  202. package/dist/tools/mcp.d.ts.map +1 -1
  203. package/dist/tools/mcp.js +20 -12
  204. package/dist/tools/mcp.js.map +1 -1
  205. package/dist/tools/web.d.ts.map +1 -1
  206. package/dist/tools/web.js +38 -14
  207. package/dist/tools/web.js.map +1 -1
  208. package/dist/tui-ui/layout.d.ts.map +1 -1
  209. package/dist/tui-ui/layout.js +36 -23
  210. package/dist/tui-ui/layout.js.map +1 -1
  211. package/dist/tui-ui/pickers.d.ts.map +1 -1
  212. package/dist/tui-ui/pickers.js +4 -3
  213. package/dist/tui-ui/pickers.js.map +1 -1
  214. package/dist/tui-ui/runner.d.ts +1 -0
  215. package/dist/tui-ui/runner.d.ts.map +1 -1
  216. package/dist/tui-ui/runner.js +14 -0
  217. package/dist/tui-ui/runner.js.map +1 -1
  218. package/dist/tui-ui/state.d.ts +6 -0
  219. package/dist/tui-ui/state.d.ts.map +1 -1
  220. package/dist/tui-ui/state.js +60 -30
  221. package/dist/tui-ui/state.js.map +1 -1
  222. package/package.json +13 -9
  223. package/dist/core/state.d.ts +0 -94
  224. package/dist/core/state.d.ts.map +0 -1
  225. package/dist/core/state.js +0 -178
  226. package/dist/core/state.js.map +0 -1
  227. package/dist/ink/bidi.d.ts +0 -16
  228. package/dist/ink/bidi.d.ts.map +0 -1
  229. package/dist/ink/bidi.js +0 -124
  230. package/dist/ink/bidi.js.map +0 -1
  231. package/dist/ink/clearTerminal.d.ts +0 -14
  232. package/dist/ink/clearTerminal.d.ts.map +0 -1
  233. package/dist/ink/clearTerminal.js +0 -63
  234. package/dist/ink/clearTerminal.js.map +0 -1
  235. package/dist/ink/colorize.d.ts +0 -17
  236. package/dist/ink/colorize.d.ts.map +0 -1
  237. package/dist/ink/colorize.js +0 -208
  238. package/dist/ink/colorize.js.map +0 -1
  239. package/dist/ink/components/App.d.ts +0 -73
  240. package/dist/ink/components/App.d.ts.map +0 -1
  241. package/dist/ink/components/App.js +0 -646
  242. package/dist/ink/components/App.js.map +0 -1
  243. package/dist/ink/components/AppContext.d.ts +0 -12
  244. package/dist/ink/components/AppContext.d.ts.map +0 -1
  245. package/dist/ink/components/AppContext.js +0 -14
  246. package/dist/ink/components/AppContext.js.map +0 -1
  247. package/dist/ink/components/Box.d.ts +0 -44
  248. package/dist/ink/components/Box.d.ts.map +0 -1
  249. package/dist/ink/components/Box.js +0 -191
  250. package/dist/ink/components/Box.js.map +0 -1
  251. package/dist/ink/components/ClockContext.d.ts +0 -9
  252. package/dist/ink/components/ClockContext.d.ts.map +0 -1
  253. package/dist/ink/components/ClockContext.js +0 -146
  254. package/dist/ink/components/ClockContext.js.map +0 -1
  255. package/dist/ink/components/CursorDeclarationContext.d.ts +0 -23
  256. package/dist/ink/components/CursorDeclarationContext.d.ts.map +0 -1
  257. package/dist/ink/components/CursorDeclarationContext.js +0 -6
  258. package/dist/ink/components/CursorDeclarationContext.js.map +0 -1
  259. package/dist/ink/components/ErrorOverview.d.ts +0 -6
  260. package/dist/ink/components/ErrorOverview.d.ts.map +0 -1
  261. package/dist/ink/components/ErrorOverview.js +0 -27
  262. package/dist/ink/components/ErrorOverview.js.map +0 -1
  263. package/dist/ink/components/Newline.d.ts +0 -13
  264. package/dist/ink/components/Newline.d.ts.map +0 -1
  265. package/dist/ink/components/Newline.js +0 -36
  266. package/dist/ink/components/Newline.js.map +0 -1
  267. package/dist/ink/components/NoSelect.d.ts +0 -20
  268. package/dist/ink/components/NoSelect.d.ts.map +0 -1
  269. package/dist/ink/components/NoSelect.js +0 -63
  270. package/dist/ink/components/NoSelect.js.map +0 -1
  271. package/dist/ink/components/RawAnsi.d.ts +0 -17
  272. package/dist/ink/components/RawAnsi.d.ts.map +0 -1
  273. package/dist/ink/components/RawAnsi.js +0 -52
  274. package/dist/ink/components/RawAnsi.js.map +0 -1
  275. package/dist/ink/components/Spacer.d.ts +0 -6
  276. package/dist/ink/components/Spacer.d.ts.map +0 -1
  277. package/dist/ink/components/Spacer.js +0 -26
  278. package/dist/ink/components/Spacer.js.map +0 -1
  279. package/dist/ink/components/StdinContext.d.ts +0 -28
  280. package/dist/ink/components/StdinContext.d.ts.map +0 -1
  281. package/dist/ink/components/StdinContext.js +0 -19
  282. package/dist/ink/components/StdinContext.js.map +0 -1
  283. package/dist/ink/components/TerminalFocusContext.d.ts +0 -10
  284. package/dist/ink/components/TerminalFocusContext.d.ts.map +0 -1
  285. package/dist/ink/components/TerminalFocusContext.js +0 -81
  286. package/dist/ink/components/TerminalFocusContext.js.map +0 -1
  287. package/dist/ink/components/TerminalSizeContext.d.ts +0 -6
  288. package/dist/ink/components/TerminalSizeContext.d.ts.map +0 -1
  289. package/dist/ink/components/TerminalSizeContext.js +0 -6
  290. package/dist/ink/components/TerminalSizeContext.js.map +0 -1
  291. package/dist/ink/components/Text.d.ts +0 -56
  292. package/dist/ink/components/Text.d.ts.map +0 -1
  293. package/dist/ink/components/Text.js +0 -201
  294. package/dist/ink/components/Text.js.map +0 -1
  295. package/dist/ink/constants.d.ts +0 -2
  296. package/dist/ink/constants.d.ts.map +0 -1
  297. package/dist/ink/constants.js +0 -6
  298. package/dist/ink/constants.js.map +0 -1
  299. package/dist/ink/devtools.d.ts +0 -3
  300. package/dist/ink/devtools.d.ts.map +0 -1
  301. package/dist/ink/devtools.js +0 -5
  302. package/dist/ink/devtools.js.map +0 -1
  303. package/dist/ink/dom.d.ts +0 -77
  304. package/dist/ink/dom.d.ts.map +0 -1
  305. package/dist/ink/dom.js +0 -322
  306. package/dist/ink/dom.js.map +0 -1
  307. package/dist/ink/events/click-event.d.ts +0 -32
  308. package/dist/ink/events/click-event.d.ts.map +0 -1
  309. package/dist/ink/events/click-event.js +0 -30
  310. package/dist/ink/events/click-event.js.map +0 -1
  311. package/dist/ink/events/dispatcher.d.ts +0 -41
  312. package/dist/ink/events/dispatcher.d.ts.map +0 -1
  313. package/dist/ink/events/dispatcher.js +0 -181
  314. package/dist/ink/events/dispatcher.js.map +0 -1
  315. package/dist/ink/events/emitter.d.ts +0 -6
  316. package/dist/ink/events/emitter.d.ts.map +0 -1
  317. package/dist/ink/events/emitter.js +0 -36
  318. package/dist/ink/events/emitter.js.map +0 -1
  319. package/dist/ink/events/event-handlers.d.ts +0 -47
  320. package/dist/ink/events/event-handlers.d.ts.map +0 -1
  321. package/dist/ink/events/event-handlers.js +0 -34
  322. package/dist/ink/events/event-handlers.js.map +0 -1
  323. package/dist/ink/events/event.d.ts +0 -6
  324. package/dist/ink/events/event.d.ts.map +0 -1
  325. package/dist/ink/events/event.js +0 -16
  326. package/dist/ink/events/event.js.map +0 -1
  327. package/dist/ink/events/focus-event.d.ts +0 -14
  328. package/dist/ink/events/focus-event.d.ts.map +0 -1
  329. package/dist/ink/events/focus-event.js +0 -20
  330. package/dist/ink/events/focus-event.js.map +0 -1
  331. package/dist/ink/events/input-event.d.ts +0 -31
  332. package/dist/ink/events/input-event.d.ts.map +0 -1
  333. package/dist/ink/events/input-event.js +0 -163
  334. package/dist/ink/events/input-event.js.map +0 -1
  335. package/dist/ink/events/keyboard-event.d.ts +0 -20
  336. package/dist/ink/events/keyboard-event.d.ts.map +0 -1
  337. package/dist/ink/events/keyboard-event.js +0 -44
  338. package/dist/ink/events/keyboard-event.js.map +0 -1
  339. package/dist/ink/events/terminal-event.d.ts +0 -55
  340. package/dist/ink/events/terminal-event.d.ts.map +0 -1
  341. package/dist/ink/events/terminal-event.js +0 -79
  342. package/dist/ink/events/terminal-event.js.map +0 -1
  343. package/dist/ink/events/terminal-focus-event.d.ts +0 -14
  344. package/dist/ink/events/terminal-focus-event.d.ts.map +0 -1
  345. package/dist/ink/events/terminal-focus-event.js +0 -19
  346. package/dist/ink/events/terminal-focus-event.js.map +0 -1
  347. package/dist/ink/focus.d.ts +0 -44
  348. package/dist/ink/focus.d.ts.map +0 -1
  349. package/dist/ink/focus.js +0 -164
  350. package/dist/ink/focus.js.map +0 -1
  351. package/dist/ink/frame.d.ts +0 -95
  352. package/dist/ink/frame.d.ts.map +0 -1
  353. package/dist/ink/frame.js +0 -35
  354. package/dist/ink/frame.js.map +0 -1
  355. package/dist/ink/get-max-width.d.ts +0 -18
  356. package/dist/ink/get-max-width.d.ts.map +0 -1
  357. package/dist/ink/get-max-width.js +0 -26
  358. package/dist/ink/get-max-width.js.map +0 -1
  359. package/dist/ink/hit-test.d.ts +0 -34
  360. package/dist/ink/hit-test.d.ts.map +0 -1
  361. package/dist/ink/hit-test.js +0 -119
  362. package/dist/ink/hit-test.js.map +0 -1
  363. package/dist/ink/hooks/use-animation-frame.d.ts +0 -27
  364. package/dist/ink/hooks/use-animation-frame.d.ts.map +0 -1
  365. package/dist/ink/hooks/use-animation-frame.js +0 -52
  366. package/dist/ink/hooks/use-animation-frame.js.map +0 -1
  367. package/dist/ink/hooks/use-app.d.ts +0 -6
  368. package/dist/ink/hooks/use-app.d.ts.map +0 -1
  369. package/dist/ink/hooks/use-app.js +0 -13
  370. package/dist/ink/hooks/use-app.js.map +0 -1
  371. package/dist/ink/hooks/use-declared-cursor.d.ts +0 -27
  372. package/dist/ink/hooks/use-declared-cursor.d.ts.map +0 -1
  373. package/dist/ink/hooks/use-declared-cursor.js +0 -67
  374. package/dist/ink/hooks/use-declared-cursor.js.map +0 -1
  375. package/dist/ink/hooks/use-input.d.ts +0 -38
  376. package/dist/ink/hooks/use-input.d.ts.map +0 -1
  377. package/dist/ink/hooks/use-input.js +0 -86
  378. package/dist/ink/hooks/use-input.js.map +0 -1
  379. package/dist/ink/hooks/use-interval.d.ts +0 -19
  380. package/dist/ink/hooks/use-interval.d.ts.map +0 -1
  381. package/dist/ink/hooks/use-interval.js +0 -59
  382. package/dist/ink/hooks/use-interval.js.map +0 -1
  383. package/dist/ink/hooks/use-search-highlight.d.ts +0 -31
  384. package/dist/ink/hooks/use-search-highlight.d.ts.map +0 -1
  385. package/dist/ink/hooks/use-search-highlight.js +0 -39
  386. package/dist/ink/hooks/use-search-highlight.js.map +0 -1
  387. package/dist/ink/hooks/use-selection.d.ts +0 -40
  388. package/dist/ink/hooks/use-selection.d.ts.map +0 -1
  389. package/dist/ink/hooks/use-selection.js +0 -68
  390. package/dist/ink/hooks/use-selection.js.map +0 -1
  391. package/dist/ink/hooks/use-stdin.d.ts +0 -6
  392. package/dist/ink/hooks/use-stdin.d.ts.map +0 -1
  393. package/dist/ink/hooks/use-stdin.js +0 -13
  394. package/dist/ink/hooks/use-stdin.js.map +0 -1
  395. package/dist/ink/hooks/use-tab-status.d.ts +0 -14
  396. package/dist/ink/hooks/use-tab-status.d.ts.map +0 -1
  397. package/dist/ink/hooks/use-tab-status.js +0 -61
  398. package/dist/ink/hooks/use-tab-status.js.map +0 -1
  399. package/dist/ink/hooks/use-terminal-focus.d.ts +0 -11
  400. package/dist/ink/hooks/use-terminal-focus.d.ts.map +0 -1
  401. package/dist/ink/hooks/use-terminal-focus.js +0 -22
  402. package/dist/ink/hooks/use-terminal-focus.js.map +0 -1
  403. package/dist/ink/hooks/use-terminal-title.d.ts +0 -13
  404. package/dist/ink/hooks/use-terminal-title.d.ts.map +0 -1
  405. package/dist/ink/hooks/use-terminal-title.js +0 -36
  406. package/dist/ink/hooks/use-terminal-title.js.map +0 -1
  407. package/dist/ink/hooks/use-terminal-viewport.d.ts +0 -30
  408. package/dist/ink/hooks/use-terminal-viewport.d.ts.map +0 -1
  409. package/dist/ink/hooks/use-terminal-viewport.js +0 -81
  410. package/dist/ink/hooks/use-terminal-viewport.js.map +0 -1
  411. package/dist/ink/ink.d.ts +0 -371
  412. package/dist/ink/ink.d.ts.map +0 -1
  413. package/dist/ink/ink.js +0 -1686
  414. package/dist/ink/ink.js.map +0 -1
  415. package/dist/ink/instances.d.ts +0 -4
  416. package/dist/ink/instances.d.ts.map +0 -1
  417. package/dist/ink/instances.js +0 -10
  418. package/dist/ink/instances.js.map +0 -1
  419. package/dist/ink/line-width-cache.d.ts +0 -2
  420. package/dist/ink/line-width-cache.d.ts.map +0 -1
  421. package/dist/ink/line-width-cache.js +0 -23
  422. package/dist/ink/line-width-cache.js.map +0 -1
  423. package/dist/ink/log-update.d.ts +0 -18
  424. package/dist/ink/log-update.d.ts.map +0 -1
  425. package/dist/ink/log-update.js +0 -637
  426. package/dist/ink/log-update.js.map +0 -1
  427. package/dist/ink/measure-element.d.ts +0 -17
  428. package/dist/ink/measure-element.d.ts.map +0 -1
  429. package/dist/ink/measure-element.js +0 -11
  430. package/dist/ink/measure-element.js.map +0 -1
  431. package/dist/ink/measure-text.d.ts +0 -7
  432. package/dist/ink/measure-text.d.ts.map +0 -1
  433. package/dist/ink/measure-text.js +0 -38
  434. package/dist/ink/measure-text.js.map +0 -1
  435. package/dist/ink/node-cache.d.ts +0 -21
  436. package/dist/ink/node-cache.d.ts.map +0 -1
  437. package/dist/ink/node-cache.js +0 -36
  438. package/dist/ink/node-cache.js.map +0 -1
  439. package/dist/ink/optimizer.d.ts +0 -16
  440. package/dist/ink/optimizer.d.ts.map +0 -1
  441. package/dist/ink/optimizer.js +0 -85
  442. package/dist/ink/optimizer.js.map +0 -1
  443. package/dist/ink/output.d.ts +0 -120
  444. package/dist/ink/output.d.ts.map +0 -1
  445. package/dist/ink/output.js +0 -559
  446. package/dist/ink/output.js.map +0 -1
  447. package/dist/ink/parse-keypress.d.ts +0 -112
  448. package/dist/ink/parse-keypress.d.ts.map +0 -1
  449. package/dist/ink/parse-keypress.js +0 -719
  450. package/dist/ink/parse-keypress.js.map +0 -1
  451. package/dist/ink/parse-keypress.test.d.ts +0 -2
  452. package/dist/ink/parse-keypress.test.d.ts.map +0 -1
  453. package/dist/ink/parse-keypress.test.js +0 -37
  454. package/dist/ink/parse-keypress.test.js.map +0 -1
  455. package/dist/ink/reconciler.d.ts +0 -12
  456. package/dist/ink/reconciler.d.ts.map +0 -1
  457. package/dist/ink/reconciler.js +0 -456
  458. package/dist/ink/reconciler.js.map +0 -1
  459. package/dist/ink/render-border.d.ts +0 -25
  460. package/dist/ink/render-border.d.ts.map +0 -1
  461. package/dist/ink/render-border.js +0 -141
  462. package/dist/ink/render-border.js.map +0 -1
  463. package/dist/ink/render-node-to-output.d.ts +0 -46
  464. package/dist/ink/render-node-to-output.d.ts.map +0 -1
  465. package/dist/ink/render-node-to-output.js +0 -1257
  466. package/dist/ink/render-node-to-output.js.map +0 -1
  467. package/dist/ink/render-to-screen.d.ts +0 -47
  468. package/dist/ink/render-to-screen.d.ts.map +0 -1
  469. package/dist/ink/render-to-screen.js +0 -213
  470. package/dist/ink/render-to-screen.js.map +0 -1
  471. package/dist/ink/renderer.d.ts +0 -15
  472. package/dist/ink/renderer.d.ts.map +0 -1
  473. package/dist/ink/renderer.js +0 -169
  474. package/dist/ink/renderer.js.map +0 -1
  475. package/dist/ink/root.d.ts +0 -75
  476. package/dist/ink/root.d.ts.map +0 -1
  477. package/dist/ink/root.js +0 -89
  478. package/dist/ink/root.js.map +0 -1
  479. package/dist/ink/screen.d.ts +0 -270
  480. package/dist/ink/screen.d.ts.map +0 -1
  481. package/dist/ink/screen.js +0 -1194
  482. package/dist/ink/screen.js.map +0 -1
  483. package/dist/ink/searchHighlight.d.ts +0 -21
  484. package/dist/ink/searchHighlight.d.ts.map +0 -1
  485. package/dist/ink/searchHighlight.js +0 -82
  486. package/dist/ink/searchHighlight.js.map +0 -1
  487. package/dist/ink/selection.d.ts +0 -230
  488. package/dist/ink/selection.d.ts.map +0 -1
  489. package/dist/ink/selection.js +0 -814
  490. package/dist/ink/selection.js.map +0 -1
  491. package/dist/ink/squash-text-nodes.d.ts +0 -23
  492. package/dist/ink/squash-text-nodes.d.ts.map +0 -1
  493. package/dist/ink/squash-text-nodes.js +0 -60
  494. package/dist/ink/squash-text-nodes.js.map +0 -1
  495. package/dist/ink/stringWidth.d.ts +0 -2
  496. package/dist/ink/stringWidth.d.ts.map +0 -1
  497. package/dist/ink/stringWidth.js +0 -207
  498. package/dist/ink/stringWidth.js.map +0 -1
  499. package/dist/ink/styles.d.ts +0 -307
  500. package/dist/ink/styles.d.ts.map +0 -1
  501. package/dist/ink/styles.js +0 -302
  502. package/dist/ink/styles.js.map +0 -1
  503. package/dist/ink/supports-hyperlinks.d.ts +0 -14
  504. package/dist/ink/supports-hyperlinks.d.ts.map +0 -1
  505. package/dist/ink/supports-hyperlinks.js +0 -48
  506. package/dist/ink/supports-hyperlinks.js.map +0 -1
  507. package/dist/ink/tabstops.d.ts +0 -2
  508. package/dist/ink/tabstops.d.ts.map +0 -1
  509. package/dist/ink/tabstops.js +0 -43
  510. package/dist/ink/tabstops.js.map +0 -1
  511. package/dist/ink/terminal-focus-state.d.ts +0 -7
  512. package/dist/ink/terminal-focus-state.d.ts.map +0 -1
  513. package/dist/ink/terminal-focus-state.js +0 -43
  514. package/dist/ink/terminal-focus-state.js.map +0 -1
  515. package/dist/ink/terminal-querier.d.ts +0 -130
  516. package/dist/ink/terminal-querier.d.ts.map +0 -1
  517. package/dist/ink/terminal-querier.js +0 -184
  518. package/dist/ink/terminal-querier.js.map +0 -1
  519. package/dist/ink/terminal.d.ts +0 -56
  520. package/dist/ink/terminal.d.ts.map +0 -1
  521. package/dist/ink/terminal.js +0 -249
  522. package/dist/ink/terminal.js.map +0 -1
  523. package/dist/ink/termio/csi.d.ts +0 -166
  524. package/dist/ink/termio/csi.d.ts.map +0 -1
  525. package/dist/ink/termio/csi.js +0 -285
  526. package/dist/ink/termio/csi.js.map +0 -1
  527. package/dist/ink/termio/dec.d.ts +0 -38
  528. package/dist/ink/termio/dec.d.ts.map +0 -1
  529. package/dist/ink/termio/dec.js +0 -59
  530. package/dist/ink/termio/dec.js.map +0 -1
  531. package/dist/ink/termio.d.ts +0 -25
  532. package/dist/ink/termio.d.ts.map +0 -1
  533. package/dist/ink/termio.js +0 -32
  534. package/dist/ink/termio.js.map +0 -1
  535. package/dist/ink/useTerminalNotification.d.ts +0 -27
  536. package/dist/ink/useTerminalNotification.d.ts.map +0 -1
  537. package/dist/ink/useTerminalNotification.js +0 -62
  538. package/dist/ink/useTerminalNotification.js.map +0 -1
  539. package/dist/ink/warn.d.ts +0 -2
  540. package/dist/ink/warn.d.ts.map +0 -1
  541. package/dist/ink/warn.js +0 -14
  542. package/dist/ink/warn.js.map +0 -1
  543. package/dist/ink/widest-line.d.ts +0 -2
  544. package/dist/ink/widest-line.d.ts.map +0 -1
  545. package/dist/ink/widest-line.js +0 -18
  546. package/dist/ink/widest-line.js.map +0 -1
  547. package/dist/ink/wrap-text.d.ts +0 -3
  548. package/dist/ink/wrap-text.d.ts.map +0 -1
  549. package/dist/ink/wrap-text.js +0 -61
  550. package/dist/ink/wrap-text.js.map +0 -1
  551. package/dist/ink/wrapAnsi.d.ts +0 -8
  552. package/dist/ink/wrapAnsi.d.ts.map +0 -1
  553. package/dist/ink/wrapAnsi.js +0 -13
  554. package/dist/ink/wrapAnsi.js.map +0 -1
  555. package/dist/ink-ui/components/TerminalCursor.d.ts +0 -15
  556. package/dist/ink-ui/components/TerminalCursor.d.ts.map +0 -1
  557. package/dist/ink-ui/components/TerminalCursor.js +0 -49
  558. package/dist/ink-ui/components/TerminalCursor.js.map +0 -1
  559. package/dist/ink-ui/runtime/stdout.d.ts +0 -11
  560. package/dist/ink-ui/runtime/stdout.d.ts.map +0 -1
  561. package/dist/ink-ui/runtime/stdout.js +0 -82
  562. package/dist/ink-ui/runtime/stdout.js.map +0 -1
  563. package/dist/runtime/goal-coordinator.d.ts +0 -66
  564. package/dist/runtime/goal-coordinator.d.ts.map +0 -1
  565. package/dist/runtime/goal-coordinator.js +0 -200
  566. package/dist/runtime/goal-coordinator.js.map +0 -1
  567. package/dist/screens/REPL.d.ts +0 -21
  568. package/dist/screens/REPL.d.ts.map +0 -1
  569. package/dist/screens/REPL.js +0 -139
  570. package/dist/screens/REPL.js.map +0 -1
  571. package/dist/services/goal-sidecar.d.ts +0 -96
  572. package/dist/services/goal-sidecar.d.ts.map +0 -1
  573. package/dist/services/goal-sidecar.js +0 -145
  574. package/dist/services/goal-sidecar.js.map +0 -1
  575. package/dist/tools/goal.d.ts +0 -17
  576. package/dist/tools/goal.d.ts.map +0 -1
  577. package/dist/tools/goal.js +0 -191
  578. package/dist/tools/goal.js.map +0 -1
  579. package/dist/ui/ink/components/App.d.ts +0 -6
  580. package/dist/ui/ink/components/App.d.ts.map +0 -1
  581. package/dist/ui/ink/components/App.js +0 -38
  582. package/dist/ui/ink/components/App.js.map +0 -1
  583. package/dist/ui/ink/components/CommandPanel.d.ts +0 -13
  584. package/dist/ui/ink/components/CommandPanel.d.ts.map +0 -1
  585. package/dist/ui/ink/components/CommandPanel.js +0 -41
  586. package/dist/ui/ink/components/CommandPanel.js.map +0 -1
  587. package/dist/ui/ink/components/InputLine.d.ts +0 -8
  588. package/dist/ui/ink/components/InputLine.d.ts.map +0 -1
  589. package/dist/ui/ink/components/InputLine.js +0 -15
  590. package/dist/ui/ink/components/InputLine.js.map +0 -1
  591. package/dist/ui/ink/components/StatusBar.d.ts +0 -9
  592. package/dist/ui/ink/components/StatusBar.d.ts.map +0 -1
  593. package/dist/ui/ink/components/StatusBar.js +0 -18
  594. package/dist/ui/ink/components/StatusBar.js.map +0 -1
  595. package/dist/ui/ink/components/index.d.ts +0 -5
  596. package/dist/ui/ink/components/index.d.ts.map +0 -1
  597. package/dist/ui/ink/components/index.js +0 -5
  598. package/dist/ui/ink/components/index.js.map +0 -1
  599. package/dist/utils/chalk.d.ts +0 -9
  600. package/dist/utils/chalk.d.ts.map +0 -1
  601. package/dist/utils/chalk.js +0 -16
  602. package/dist/utils/chalk.js.map +0 -1
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AgentRuntimeController = void 0;
4
+ exports.goalProviderError = goalProviderError;
5
+ exports.goalTurnMadeProgress = goalTurnMadeProgress;
4
6
  const parser_1 = require("../commands/parser");
5
7
  const target_command_1 = require("../commands/target-command");
6
8
  const agent_runtime_protocol_1 = require("./agent-runtime-protocol");
@@ -8,10 +10,46 @@ const agent_status_1 = require("./agent-status");
8
10
  const chat_controller_1 = require("./chat-controller");
9
11
  const ui_events_1 = require("./ui-events");
10
12
  const turn_controller_1 = require("./turn-controller");
13
+ const tools_1 = require("./goals/tools");
14
+ const accounting_1 = require("./goals/accounting");
15
+ const crypto_1 = require("crypto");
16
+ const workspace_state_1 = require("../services/workspace-state");
17
+ const redaction_1 = require("../services/redaction");
18
+ const evidence_1 = require("./goals/evidence");
19
+ const session_storage_1 = require("../services/session-storage");
20
+ const external_assertion_1 = require("../framework/external-assertion");
11
21
  function isExitInput(input) {
12
22
  const parsed = (0, parser_1.parseInput)(input.trim());
13
23
  return parsed.isCommand && ['exit', 'quit', 'q'].includes(parsed.name);
14
24
  }
25
+ function goalProviderError(finishReason, errorType) {
26
+ if (finishReason !== 'failed' || !errorType)
27
+ return undefined;
28
+ switch (errorType) {
29
+ case 'quota_or_credit_exhausted':
30
+ return { kind: 'usage_limit', retryable: false };
31
+ case 'rate_limit':
32
+ return { kind: 'rate_limit', retryable: true };
33
+ case 'provider_busy':
34
+ return { kind: 'provider_busy', retryable: true };
35
+ case 'auth_failed':
36
+ return { kind: 'auth', retryable: false };
37
+ case 'connect_timeout':
38
+ case 'read_timeout':
39
+ case 'connection_reset':
40
+ case 'network_error':
41
+ return { kind: 'network', retryable: true };
42
+ default:
43
+ return { kind: 'unknown', retryable: false };
44
+ }
45
+ }
46
+ function goalTurnMadeProgress(input) {
47
+ const passedEvidence = input.evidenceRecords?.some(record => record.result === 'passed') ?? false;
48
+ const workspaceChanged = Boolean(input.workspaceFingerprintBefore &&
49
+ input.workspaceFingerprintAfter &&
50
+ input.workspaceFingerprintBefore !== input.workspaceFingerprintAfter);
51
+ return passedEvidence || workspaceChanged;
52
+ }
15
53
  function submittedEntry(input) {
16
54
  const parsed = (0, parser_1.parseInput)(input.trim());
17
55
  return {
@@ -41,10 +79,7 @@ class AgentRuntimeController {
41
79
  /** v0.2.24: set the goal coordinator for /target mode. */
42
80
  setGoalCoordinator(coord) {
43
81
  this.goalCoordinator = coord;
44
- this.goalCoordinatorSessionId = '*';
45
- // Wire goal tools to the coordinator so model can call get_goal/create_goal/update_goal.
46
- const { setGoalToolCoordinator } = require('./goals/tools');
47
- setGoalToolCoordinator(coord);
82
+ this.goalCoordinatorSessionId = coord.boundSessionId;
48
83
  // Wire goal prompt injection into the chat controller.
49
84
  if ('setGoalCoordinator' in this.runner) {
50
85
  this.runner.setGoalCoordinator(coord);
@@ -55,6 +90,9 @@ class AgentRuntimeController {
55
90
  this.pendingPermissions = new Map();
56
91
  this.activeRun = null;
57
92
  this.stopping = false;
93
+ this.continuationScheduleEpoch = 0;
94
+ /** Conservative reservations for every provider attempt in the current root turn. */
95
+ this.goalProviderReservedTokens = 0;
58
96
  this.nextPermissionRequestId = 1;
59
97
  /** v0.2.24: optional goal coordinator for /target mode. */
60
98
  this.goalCoordinator = null;
@@ -63,9 +101,20 @@ class AgentRuntimeController {
63
101
  throw new Error('AgentRuntimeController requires either events or eventSink');
64
102
  }
65
103
  this.turnController = new turn_controller_1.TurnController(options);
66
- this.eventSink = options.eventSink ?? (0, agent_runtime_protocol_1.createAgentRuntimeEventSinkFromUiEvents)(options.events);
67
- const events = options.events ?? (0, agent_runtime_protocol_1.createUiEventSinkFromAgentRuntimeEvents)(this.eventSink);
68
- this.runner = options.runner ?? new chat_controller_1.AgentChatController(options.runtime, events, this.createChatOptions());
104
+ const downstream = options.eventSink ?? (0, agent_runtime_protocol_1.createAgentRuntimeEventSinkFromUiEvents)(options.events);
105
+ this.eventSink = {
106
+ emit: event => {
107
+ this.captureGoalEvidence(event);
108
+ const result = downstream.emit(event);
109
+ if (event.type === 'session_restored') {
110
+ this.restoreGoalForSession(event.event.sessionId, event.event.projectPath);
111
+ }
112
+ return result;
113
+ },
114
+ };
115
+ const events = (0, agent_runtime_protocol_1.createUiEventSinkFromAgentRuntimeEvents)(this.eventSink);
116
+ this.runner =
117
+ options.runner ?? new chat_controller_1.AgentChatController(options.runtime, events, this.createChatOptions());
69
118
  }
70
119
  hasActiveTurn() {
71
120
  return this.turnController.hasActiveTurn();
@@ -80,86 +129,162 @@ class AgentRuntimeController {
80
129
  content: parsed.error,
81
130
  errorLayer: 'runtime',
82
131
  });
83
- return { handled: true, statusText: parsed.error, runtimeResult: { type: 'command_handled' } };
132
+ return {
133
+ handled: true,
134
+ statusText: parsed.error,
135
+ runtimeResult: { type: 'command_handled' },
136
+ };
84
137
  }
85
138
  const input = parsed.input;
86
139
  const coord = this.ensureGoalCoordinator(input.action !== 'show');
87
140
  if (!coord) {
88
141
  const statusText = 'Target unavailable: no active session.';
89
- this.emitAppend({ role: 'error', title: 'target', content: statusText, errorLayer: 'session' });
142
+ this.emitAppend({
143
+ role: 'error',
144
+ title: 'target',
145
+ content: statusText,
146
+ errorLayer: 'session',
147
+ });
90
148
  return { handled: true, statusText, runtimeResult: { type: 'command_handled' } };
91
149
  }
92
- if (this.turnController.hasActiveTurn()
93
- && !['pause', 'show', 'clear'].includes(input.action)) {
150
+ if (this.turnController.hasActiveTurn() && !['pause', 'show', 'clear'].includes(input.action)) {
94
151
  const statusText = 'Target command ignored while the agent is running. Use /target pause or interrupt first.';
95
152
  this.emitStatus(statusText);
96
153
  return { handled: true, statusText, runtimeResult: { type: 'command_ignored' } };
97
154
  }
98
155
  let success = true;
99
156
  let error;
100
- switch (input.action) {
101
- case 'show':
102
- break;
103
- case 'create': {
104
- const result = coord.create(input.payload?.objective ?? '');
105
- success = result.ok;
106
- if (!result.ok)
107
- error = result.error;
108
- break;
109
- }
110
- case 'pause':
111
- success = coord.pause();
112
- if (success && this.turnController.hasActiveTurn()) {
113
- this.turnController.interruptActiveTurn();
157
+ let executionRevoked = false;
158
+ const previousGoalId = coord.goal?.goalId;
159
+ try {
160
+ switch (input.action) {
161
+ case 'show':
162
+ break;
163
+ case 'create': {
164
+ const result = coord.create(input.payload?.objective ?? '');
165
+ success = result.ok;
166
+ if (!result.ok)
167
+ error = result.error;
168
+ break;
114
169
  }
115
- if (!success)
116
- error = 'Target is not active.';
117
- break;
118
- case 'resume':
119
- success = coord.resume();
120
- if (!success)
121
- error = 'Target cannot be resumed from its current state.';
122
- break;
123
- case 'edit':
124
- success = coord.edit(input.payload?.objective ?? '');
125
- if (!success)
126
- error = 'Target objective could not be updated.';
127
- break;
128
- case 'replace':
129
- success = coord.replace(input.payload?.objective ?? '');
130
- if (!success)
131
- error = 'Target could not be replaced.';
132
- break;
133
- case 'set_budget':
134
- success = coord.setBudget(input.payload?.tokenBudget ?? null);
135
- if (!success)
136
- error = 'Create or resume a target before setting a budget.';
137
- break;
138
- case 'clear':
139
- if (!input.payload?.confirmed) {
140
- success = false;
141
- error = 'Clearing a target requires confirmation: /target clear --yes';
142
- }
143
- else {
144
- success = coord.clear();
170
+ case 'pause':
171
+ if (coord.isActive) {
172
+ this.abortGoalOwnedExecution();
173
+ executionRevoked = true;
174
+ }
175
+ success = coord.pause();
145
176
  if (!success)
146
- error = 'No target exists to clear.';
147
- }
148
- break;
177
+ error = 'Target is not active.';
178
+ break;
179
+ case 'resume':
180
+ success = coord.resume({
181
+ confirmBoundary: true,
182
+ expectedGoalId: coord.goal?.goalId,
183
+ expectedRevision: coord.goal?.revision,
184
+ });
185
+ if (!success)
186
+ error = 'Target cannot be resumed from its current state.';
187
+ break;
188
+ case 'confirm':
189
+ success = coord.confirmCriterion(input.payload?.criterionId ?? '');
190
+ if (!success) {
191
+ error = 'Criterion cannot be confirmed. It must exist and require user evidence.';
192
+ }
193
+ break;
194
+ case 'edit':
195
+ success = coord.edit(input.payload?.objective ?? '');
196
+ if (!success)
197
+ error = 'Target objective could not be updated.';
198
+ break;
199
+ case 'replace':
200
+ success = coord.replace(input.payload?.objective ?? '');
201
+ if (!success) {
202
+ error = 'Target could not be replaced.';
203
+ if (input.payload?.objective?.trim()) {
204
+ error = this.failClosedGoalMutation(coord, input.action, new Error(error), executionRevoked, previousGoalId);
205
+ executionRevoked = true;
206
+ }
207
+ }
208
+ break;
209
+ case 'set_budget':
210
+ success = coord.setBudget(input.payload?.tokenBudget ?? null);
211
+ if (!success)
212
+ error = 'Create or resume a target before setting a budget.';
213
+ break;
214
+ case 'clear':
215
+ if (!input.payload?.confirmed) {
216
+ success = false;
217
+ error = 'Clearing a target requires confirmation: /target clear --yes';
218
+ }
219
+ else {
220
+ const hadGoal = coord.goal !== null;
221
+ if (hadGoal) {
222
+ this.abortGoalOwnedExecution();
223
+ executionRevoked = true;
224
+ }
225
+ success = coord.clear();
226
+ if (hadGoal && !success) {
227
+ error = this.failClosedGoalMutation(coord, input.action, new Error('Target clear did not remove the active Goal.'), executionRevoked, previousGoalId);
228
+ }
229
+ if (!success && !hadGoal)
230
+ error = 'No target exists to clear.';
231
+ }
232
+ break;
233
+ }
234
+ }
235
+ catch (cause) {
236
+ success = false;
237
+ error =
238
+ input.action === 'show'
239
+ ? cause instanceof Error
240
+ ? cause.message
241
+ : String(cause)
242
+ : this.failClosedGoalMutation(coord, input.action, cause, executionRevoked, previousGoalId);
149
243
  }
150
244
  if (!success && error) {
151
245
  this.emitAppend({ role: 'error', title: 'target', content: error, errorLayer: 'runtime' });
152
246
  }
247
+ if (success &&
248
+ input.action === 'resume' &&
249
+ coord.goal?.status === 'active' &&
250
+ coord.goal.tokenBudget !== undefined) {
251
+ const resumedBudget = (0, accounting_1.budgetPreflight)(coord.goal.tokensUsed, coord.goal.tokenBudget, 0);
252
+ if (!resumedBudget.available) {
253
+ try {
254
+ coord.limitBudget(resumedBudget.reason ?? 'Token budget unavailable after resuming the target.');
255
+ }
256
+ catch (cause) {
257
+ success = false;
258
+ error = this.failClosedGoalMutation(coord, 'resume_budget_stop', cause, executionRevoked, previousGoalId);
259
+ this.emitGoalMutationError(error);
260
+ }
261
+ }
262
+ }
263
+ if (success) {
264
+ const { updateSessionGoalBinding } = require('../services/session-storage');
265
+ updateSessionGoalBinding(coord.boundSessionId, coord.goal);
266
+ if (input.action === 'clear' && previousGoalId) {
267
+ this.emitGoalEvent({ type: 'goal_cleared', goalId: previousGoalId, reason: 'user_clear' });
268
+ }
269
+ else {
270
+ const snapshot = coord.snapshot();
271
+ if (snapshot) {
272
+ this.emitGoalEvent({
273
+ type: 'goal_updated',
274
+ goal: snapshot,
275
+ reason: `target_${input.action}`,
276
+ });
277
+ }
278
+ }
279
+ }
153
280
  const statusText = this.formatTargetStatus(coord);
154
281
  this.emitAppend({ role: 'system', title: 'target', content: statusText });
155
- if (success
156
- && (input.action === 'create' || input.action === 'resume' || input.action === 'replace')
157
- && coord.isActive) {
282
+ if (success &&
283
+ (input.action === 'create' || input.action === 'resume' || input.action === 'replace') &&
284
+ coord.isActive) {
158
285
  const req = coord.buildContinuationRequest();
159
286
  if (req) {
160
- const runtimeResult = this.submit(req.goal?.continuationIndex
161
- ? `[goal continuation #${req.goal.continuationIndex}]`
162
- : 'Continue pursuing the goal.');
287
+ const runtimeResult = this.submitGoalContinuation(req, `target_${input.action}`, true);
163
288
  return { handled: true, statusText, runtimeResult };
164
289
  }
165
290
  }
@@ -184,6 +309,7 @@ class AgentRuntimeController {
184
309
  }
185
310
  /** v0.1.1: emit shutdown_requested event through the renderer protocol. */
186
311
  emitShutdownRequested(reason) {
312
+ this.deferGoalContinuation(reason ?? 'shutdown requested', true);
187
313
  this.eventSink.emit({ type: 'shutdown_requested', reason });
188
314
  }
189
315
  setVerificationState(state) {
@@ -232,22 +358,61 @@ class AgentRuntimeController {
232
358
  if (this.turnController.hasActiveTurn()) {
233
359
  const parsed = (0, parser_1.parseInput)(submitted);
234
360
  if (parsed.isCommand) {
235
- this.emitStatus(this.options.commandWhileRunningStatus ?? 'Command ignored while agent is running. Press Ctrl+C to interrupt first.');
361
+ this.emitStatus(this.options.commandWhileRunningStatus ??
362
+ 'Command ignored while agent is running. Press Ctrl+C to interrupt first.');
236
363
  return { type: 'command_ignored' };
237
364
  }
238
365
  // v0.2.26: user steering input during active goal — update constraints
239
366
  // without replacing the root objective.
240
367
  const gc = this.goalCoordinator;
241
368
  if (gc?.goal && !(0, target_command_1.isTargetCommand)(submitted)) {
242
- // Record the steering as a constraint update without changing objective.
243
- gc.edit(gc.goal.objective); // no-op edit to bump revision, steering is via prompt
369
+ const steeringGoalId = gc.goal.goalId;
370
+ try {
371
+ gc.addConstraint(submitted);
372
+ }
373
+ catch (cause) {
374
+ const message = this.failClosedGoalMutation(gc, 'steering', cause, false, steeringGoalId);
375
+ this.emitAppend({
376
+ role: 'error',
377
+ title: 'target steering',
378
+ content: message,
379
+ errorLayer: 'runtime',
380
+ });
381
+ this.emitStatus(message);
382
+ return { type: 'command_ignored' };
383
+ }
244
384
  }
245
385
  this.turnController.clearExitIntent();
246
386
  this.turnController.requestRevision(submitted);
247
387
  this.emitStatus(this.options.revisionStatus ?? 'Revision received. Interrupting current response...');
248
388
  return { type: 'revision_requested' };
249
389
  }
250
- this.activeRun = this.runTurn(submitted)
390
+ const session = this.options.runtime.getSession() ?? this.options.runtime.ensureSession();
391
+ const coord = this.ensureGoalCoordinator(false);
392
+ const goal = coord?.goal?.status === 'active' ? coord.goal : undefined;
393
+ const request = {
394
+ inputKind: 'user',
395
+ text: submitted,
396
+ sessionId: session?.id ?? 'pending-session',
397
+ goal: goal
398
+ ? {
399
+ goalId: goal.goalId,
400
+ revision: goal.revision,
401
+ continuationIndex: goal.continuationCount,
402
+ }
403
+ : undefined,
404
+ persistAsUserMessage: true,
405
+ echoToTranscript: true,
406
+ generation: coord?.generation ?? 0,
407
+ };
408
+ return this.submitTurnRequest(request);
409
+ }
410
+ submitTurnRequest(request) {
411
+ if (this.turnController.hasActiveTurn())
412
+ return { type: 'command_ignored' };
413
+ if (!this.requestIsCurrent(request))
414
+ return { type: 'command_ignored' };
415
+ this.activeRun = this.runTurn(request)
251
416
  .catch(error => {
252
417
  this.handleRunLoopError(error);
253
418
  })
@@ -258,58 +423,326 @@ class AgentRuntimeController {
258
423
  });
259
424
  return { type: 'started' };
260
425
  }
426
+ submitGoalContinuation(request, reason, emitScheduled) {
427
+ const goalId = request.goal?.goalId;
428
+ if (emitScheduled && goalId) {
429
+ this.emitGoalEvent({ type: 'goal_continuation', goalId, phase: 'scheduled', reason });
430
+ }
431
+ const result = this.submitTurnRequest(request);
432
+ if (goalId) {
433
+ this.emitGoalEvent({
434
+ type: 'goal_continuation',
435
+ goalId,
436
+ phase: result.type === 'started' ? 'started' : 'deferred',
437
+ reason: result.type === 'started' ? reason : `request rejected: ${reason}`,
438
+ });
439
+ }
440
+ return result;
441
+ }
261
442
  // --- v0.2.26: goal continuation scheduling ---
262
443
  scheduleGoalContinuation() {
263
444
  const coord = this.goalCoordinator;
264
445
  if (!coord || !coord.isActive || coord.goal?.status !== 'active')
265
446
  return;
266
- if (!coord.canContinue)
267
- return; // don't continue if deferred, budget exceeded, etc.
447
+ if (!coord.canContinue) {
448
+ this.emitGoalEvent({
449
+ type: 'goal_continuation',
450
+ goalId: coord.goal.goalId,
451
+ phase: 'deferred',
452
+ reason: 'coordinator is not eligible to continue',
453
+ });
454
+ return;
455
+ }
456
+ const scheduleEpoch = ++this.continuationScheduleEpoch;
457
+ const scheduledGoalId = coord.goal.goalId;
268
458
  // Defer to next tick to let the current turn's events settle.
269
459
  setImmediate(() => {
270
- if (this.stopping || this.turnController.hasActiveTurn())
460
+ if (scheduleEpoch !== this.continuationScheduleEpoch) {
461
+ this.emitGoalEvent({
462
+ type: 'goal_continuation',
463
+ goalId: scheduledGoalId,
464
+ phase: 'deferred',
465
+ reason: 'scheduled continuation was invalidated',
466
+ });
271
467
  return;
272
- if (!coord.isActive || coord.goal?.status !== 'active')
273
- return; // re-check after tick
468
+ }
469
+ if (this.stopping ||
470
+ this.turnController.hasActiveTurn() ||
471
+ this.pendingPermissions.size > 0) {
472
+ this.emitGoalEvent({
473
+ type: 'goal_continuation',
474
+ goalId: scheduledGoalId,
475
+ phase: 'deferred',
476
+ reason: this.stopping
477
+ ? 'runtime is stopping'
478
+ : this.pendingPermissions.size > 0
479
+ ? 'tool permission is pending'
480
+ : 'another turn is active',
481
+ });
482
+ return;
483
+ }
484
+ if (!coord.isActive || coord.goal?.status !== 'active') {
485
+ this.emitGoalEvent({
486
+ type: 'goal_continuation',
487
+ goalId: scheduledGoalId,
488
+ phase: 'deferred',
489
+ reason: 'goal is no longer active',
490
+ });
491
+ return;
492
+ }
274
493
  const req = coord.buildContinuationRequest();
275
494
  if (req) {
276
- this.handle({
277
- type: 'submit',
278
- text: `[goal continuation #${coord.goal.continuationCount + 1}]`,
279
- source: 'programmatic',
495
+ const projected = coord.goal?.lastTurn?.totalTokens ?? 0;
496
+ const preflight = (0, accounting_1.budgetPreflight)(coord.goal?.tokensUsed ?? 0, coord.goal?.tokenBudget, projected);
497
+ if (!preflight.available) {
498
+ try {
499
+ coord.limitBudget(preflight.reason ?? 'Token budget unavailable for another turn.');
500
+ }
501
+ catch (cause) {
502
+ const message = this.failClosedGoalMutation(coord, 'budget_stop', cause, false, scheduledGoalId);
503
+ this.emitGoalMutationError(message);
504
+ return;
505
+ }
506
+ this.emitGoalEvent({
507
+ type: 'goal_continuation',
508
+ goalId: coord.goal.goalId,
509
+ phase: 'stopped',
510
+ reason: preflight.reason ?? 'budget preflight failed',
511
+ });
512
+ return;
513
+ }
514
+ this.submitGoalContinuation(req, `continuation ${req.goal.continuationIndex}`, true);
515
+ }
516
+ else {
517
+ this.emitGoalEvent({
518
+ type: 'goal_continuation',
519
+ goalId: scheduledGoalId,
520
+ phase: 'deferred',
521
+ reason: 'coordinator did not produce a continuation request',
280
522
  });
281
523
  }
282
524
  });
283
525
  }
284
526
  /** v0.2.26: finalize goal turn with usage data from the last loop. */
285
- finalizeGoalTurn(turnId) {
527
+ finalizeGoalTurn(turnId, request, toolContext, startedAt, workspaceFingerprintBefore, turnAborted) {
286
528
  const coord = this.goalCoordinator;
287
- if (!coord?.goal || coord.goal.status !== 'active')
529
+ const requestGoal = request.goal;
530
+ const currentSessionId = this.options.runtime.getSession()?.id;
531
+ if (!coord?.goal ||
532
+ !requestGoal ||
533
+ currentSessionId !== request.sessionId ||
534
+ coord.boundSessionId !== request.sessionId ||
535
+ coord.generation !== request.generation ||
536
+ coord.goal.goalId !== requestGoal.goalId) {
537
+ if (request.inputKind === 'goal_continuation' && requestGoal) {
538
+ this.emitGoalEvent({
539
+ type: 'goal_continuation',
540
+ goalId: requestGoal.goalId,
541
+ phase: 'deferred',
542
+ reason: 'turn outcome rejected because its session, goal, revision, or generation is stale',
543
+ });
544
+ }
288
545
  return;
546
+ }
289
547
  const usage = this.options.runtime.store.getSnapshot().tokenUsage;
290
548
  const loopStats = this.options.runtime.store.getSnapshot().lastLoopStats;
549
+ const subagentPromptTokens = loopStats?.subagentPromptTokens ?? 0;
550
+ const subagentCompletionTokens = loopStats?.subagentCompletionTokens ?? 0;
551
+ const subagentTokens = loopStats?.subagentTotalTokens ?? 0;
552
+ const workspaceFingerprintAfter = (0, workspace_state_1.captureWorkspaceFingerprint)(this.activeProjectPath());
553
+ const workspaceChanged = Boolean(workspaceFingerprintBefore &&
554
+ workspaceFingerprintAfter &&
555
+ workspaceFingerprintBefore !== workspaceFingerprintAfter);
556
+ const usageAccountingComplete = loopStats
557
+ ?.usageAccountingComplete !== false;
291
558
  const outcome = {
292
559
  turnId: String(turnId),
293
- sessionId: this.options.runtime.getSession()?.id ?? '',
294
- goalId: coord.goal.goalId,
295
- goalRevision: coord.goal.revision,
296
- startedAt: coord.goal.updatedAt,
560
+ sessionId: request.sessionId,
561
+ goalId: requestGoal.goalId,
562
+ goalRevision: requestGoal.revision,
563
+ goalGeneration: request.generation,
564
+ startedAt,
297
565
  endedAt: Date.now(),
298
566
  finishReason: loopStats?.finishReason ?? 'unknown',
299
567
  usage: {
300
- promptTokens: usage?.promptTokens ?? 0,
301
- completionTokens: usage?.completionTokens ?? 0,
302
- subagentTokens: 0,
303
- totalTokens: (usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0),
568
+ promptTokens: Math.max(0, (usage?.promptTokens ?? 0) - subagentPromptTokens),
569
+ completionTokens: Math.max(0, (usage?.completionTokens ?? 0) - subagentCompletionTokens),
570
+ subagentTokens,
571
+ totalTokens: usage !== null
572
+ ? (usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0)
573
+ : subagentTokens,
304
574
  },
305
- madeProgress: loopStats ? loopStats.toolCalls > 0 : true,
575
+ madeProgress: goalTurnMadeProgress({
576
+ evidenceRecords: toolContext?.evidenceRecords,
577
+ pendingPlanUpdate: toolContext?.pendingPlanUpdate,
578
+ workspaceFingerprintBefore,
579
+ workspaceFingerprintAfter,
580
+ }),
581
+ workspaceChanged,
582
+ providerError: goalProviderError(loopStats?.finishReason, loopStats?.providerLastRetryErrorType),
583
+ blocker: toolContext?.pendingBlocker,
584
+ pendingTerminalRequest: toolContext?.pendingTerminalRequest,
585
+ pendingPlanUpdate: toolContext?.pendingPlanUpdate,
586
+ evidenceRecords: toolContext?.evidenceRecords,
587
+ workspaceFingerprint: workspaceFingerprintAfter,
588
+ evidenceRefs: toolContext?.evidenceRecords.map(record => record.id),
589
+ verificationSummary: this.verificationSummary(loopStats),
590
+ usageComplete: usageAccountingComplete &&
591
+ usage !== null &&
592
+ loopStats?.finishReason !== 'failed' &&
593
+ (loopStats?.providerRetryCount ?? 0) === 0 &&
594
+ (loopStats?.providerFallbackCount ?? 0) === 0,
306
595
  };
307
- coord.finalizeTurn(outcome);
596
+ const semanticOutcomeIsCurrent = coord.goal.status === 'active' &&
597
+ !turnAborted &&
598
+ coord.goal.revision === requestGoal.revision;
599
+ if (!semanticOutcomeIsCurrent) {
600
+ const accountingOnlyEligible = turnAborted || coord.goal.status === 'paused' || coord.goal.revision > requestGoal.revision;
601
+ if (!accountingOnlyEligible) {
602
+ if (request.inputKind === 'goal_continuation') {
603
+ this.emitGoalEvent({
604
+ type: 'goal_continuation',
605
+ goalId: requestGoal.goalId,
606
+ phase: 'deferred',
607
+ reason: 'turn outcome rejected because the Goal lifecycle no longer accepts this turn',
608
+ });
609
+ }
610
+ return;
611
+ }
612
+ // accountStaleTurn is the coordinator's accounting-only, idempotent
613
+ // entry point. An interrupt can leave the Goal revision unchanged, so
614
+ // freeze this outcome immediately before the current revision without
615
+ // admitting any semantic payload from the cancelled turn.
616
+ const accountingOutcome = {
617
+ ...outcome,
618
+ goalRevision: Math.min(outcome.goalRevision ?? requestGoal.revision, coord.goal.revision - 1),
619
+ };
620
+ let accounted = false;
621
+ try {
622
+ const alreadyAccounted = coord.goal.lastTurn?.turnId === String(turnId);
623
+ accounted = alreadyAccounted ? false : coord.accountStaleTurn(accountingOutcome);
624
+ // Missing/partial provider usage is fail-closed. Known lower-bound
625
+ // tokens have already been retained by accountStaleTurn.
626
+ if (outcome.usageComplete === false && coord.goal?.status === 'active') {
627
+ coord.pause();
628
+ }
629
+ }
630
+ catch (error) {
631
+ this.failClosedGoalPersistence(coord, requestGoal.goalId, error, 'stale_accounting');
632
+ return;
633
+ }
634
+ const snapshot = coord.snapshot();
635
+ if (accounted && snapshot) {
636
+ this.emitGoalEvent({
637
+ type: 'goal_updated',
638
+ goal: snapshot,
639
+ reason: 'interrupted or stale turn usage accounted without semantic finalization',
640
+ });
641
+ this.recordGoalTraceState(turnId, request, snapshot);
642
+ }
643
+ if (request.inputKind === 'goal_continuation') {
644
+ this.emitGoalEvent({
645
+ type: 'goal_continuation',
646
+ goalId: requestGoal.goalId,
647
+ phase: 'deferred',
648
+ reason: 'turn semantic outcome rejected; incurred usage was accounted without continuation',
649
+ });
650
+ }
651
+ return;
652
+ }
653
+ // Live steering intentionally bumps the Goal revision before the aborted
654
+ // provider turn settles. The semantic payload is stale, but the provider
655
+ // usage and elapsed time were still incurred and must be counted once.
656
+ const planRevisionBefore = coord.goal.contract?.planSnapshot?.revision;
657
+ try {
658
+ coord.finalizeTurn(outcome);
659
+ }
660
+ catch (error) {
661
+ this.failClosedGoalPersistence(coord, requestGoal.goalId, error, 'turn_finalize');
662
+ return;
663
+ }
664
+ const snapshot = coord.snapshot();
665
+ if (snapshot) {
666
+ this.recordGoalTraceState(turnId, request, snapshot);
667
+ if (coord.goal?.contract?.planSnapshot &&
668
+ coord.goal.contract.planSnapshot.revision !== planRevisionBefore) {
669
+ const plan = coord.goal.contract.planSnapshot;
670
+ this.emitGoalEvent({
671
+ type: 'goal_plan_updated',
672
+ goalId: snapshot.goalId,
673
+ planRevision: plan.revision,
674
+ phase: plan.phase,
675
+ nextAction: plan.nextAction,
676
+ });
677
+ }
678
+ let terminalEvent;
679
+ if (snapshot.status === 'complete' && coord.goal?.completionAudit) {
680
+ terminalEvent = {
681
+ type: 'goal_completed',
682
+ goal: snapshot,
683
+ audit: coord.goal.completionAudit,
684
+ };
685
+ }
686
+ else if (coord.goal?.completionAudit && !coord.goal.completionAudit.passed) {
687
+ terminalEvent = {
688
+ type: 'goal_audit_failed',
689
+ goalId: snapshot.goalId,
690
+ audit: 'completion',
691
+ summary: coord.goal.completionAudit.remainingRequirements.join(' '),
692
+ };
693
+ }
694
+ else if (toolContext?.pendingTerminalRequest?.requestedStatus === 'blocked' &&
695
+ snapshot.status !== 'blocked') {
696
+ const blocker = coord.goal?.blocker;
697
+ terminalEvent = {
698
+ type: 'goal_audit_failed',
699
+ goalId: snapshot.goalId,
700
+ audit: 'blocked',
701
+ summary: blocker
702
+ ? `Blocker is not terminal: ${blocker.consecutiveTurns}/3 consecutive turns; no-progress ${coord.goal?.noProgressCount ?? 0}/3.`
703
+ : 'Blocked request did not include a valid non-retryable user, permission, or external-state blocker.',
704
+ };
705
+ }
706
+ // Project the fresh snapshot first, then the specific audit/completion
707
+ // event. This keeps renderer state current without a generic update
708
+ // overwriting the user-facing terminal result.
709
+ this.emitGoalEvent({ type: 'goal_updated', goal: snapshot, reason: 'turn_finalized' });
710
+ if (terminalEvent)
711
+ this.emitGoalEvent(terminalEvent);
712
+ }
713
+ }
714
+ failClosedGoalPersistence(coord, goalId, error, operation) {
715
+ const message = this.failClosedGoalMutation(coord, operation, error, false, goalId);
716
+ this.emitAppend({
717
+ role: 'error',
718
+ title: 'goal persistence',
719
+ content: message,
720
+ errorLayer: 'session',
721
+ });
722
+ this.emitStatus(message);
723
+ }
724
+ recordGoalTraceState(turnId, request, snapshot) {
725
+ if (!request.goal)
726
+ return;
727
+ const trace = (0, session_storage_1.appendSessionTraceEvent)(request.sessionId, {
728
+ turnId: String(turnId),
729
+ type: 'goal_state',
730
+ goalId: request.goal.goalId,
731
+ goalRevision: request.goal.revision,
732
+ goalInputKind: request.inputKind,
733
+ goalStopReason: snapshot.stopReason ?? (snapshot.status === 'complete' ? 'completed' : 'continue'),
734
+ note: `status=${snapshot.status}; snapshotRevision=${snapshot.revision}`,
735
+ });
736
+ if (trace)
737
+ this.eventSink.emit({ type: 'trace_event_recorded', event: trace });
308
738
  }
309
739
  interrupt() {
310
- const shouldExit = this.turnController.registerExitIntent();
311
740
  // v0.2.26: pause active goal on interrupt to prevent immediate restart.
312
- this.goalCoordinator?.deferContinuation();
741
+ this.deferGoalContinuation('user interrupt', true);
742
+ // Register after any synchronous persistence recovery. A lock wait can be
743
+ // longer than the confirmation window, but must not consume the user's
744
+ // first Ctrl+C before this call even returns.
745
+ const shouldExit = this.turnController.registerExitIntent();
313
746
  if (this.turnController.hasActiveTurn()) {
314
747
  this.turnController.interruptActiveTurn();
315
748
  if (shouldExit)
@@ -324,6 +757,7 @@ class AgentRuntimeController {
324
757
  }
325
758
  async stopActiveTurn() {
326
759
  this.stopping = true;
760
+ this.deferGoalContinuation('runtime stopping');
327
761
  this.turnController.interruptActiveTurn();
328
762
  this.rejectPendingPermissions();
329
763
  if (this.activeRun) {
@@ -332,24 +766,136 @@ class AgentRuntimeController {
332
766
  // Reset stopping flag so subsequent turns can execute.
333
767
  this.stopping = false;
334
768
  }
769
+ deferGoalContinuation(reason, preserveExitIntent = false) {
770
+ this.continuationScheduleEpoch += 1;
771
+ const coord = this.goalCoordinator;
772
+ const goalId = coord?.goal?.status === 'active' ? coord.goal.goalId : undefined;
773
+ if (!coord || !goalId)
774
+ return;
775
+ try {
776
+ coord.deferContinuation();
777
+ }
778
+ catch (cause) {
779
+ const message = this.failClosedGoalMutation(coord, 'pause', cause, false, goalId, preserveExitIntent);
780
+ this.emitGoalMutationError(message);
781
+ return;
782
+ }
783
+ this.emitGoalEvent({
784
+ type: 'goal_continuation',
785
+ goalId,
786
+ phase: 'deferred',
787
+ reason,
788
+ });
789
+ }
790
+ /** Revoke all Goal-owned execution before mutating a lifecycle boundary. */
791
+ abortGoalOwnedExecution(preserveExitIntent = false) {
792
+ this.continuationScheduleEpoch += 1;
793
+ if (!preserveExitIntent)
794
+ this.turnController.clearExitIntent();
795
+ this.turnController.interruptActiveTurn();
796
+ this.rejectPendingPermissions();
797
+ }
798
+ /**
799
+ * Contain a failed Goal mutation without writing the restored disk state back.
800
+ * The coordinator advances its generation so every in-flight request becomes
801
+ * stale, then preserves complete or deleted disk authority as-is.
802
+ */
803
+ failClosedGoalMutation(coord, operation, cause, executionAlreadyRevoked = false, goalIdBeforeMutation, preserveExitIntent = false) {
804
+ if (!executionAlreadyRevoked)
805
+ this.abortGoalOwnedExecution(preserveExitIntent);
806
+ const detail = (0, redaction_1.redactTraceText)(cause instanceof Error ? cause.message : String(cause)).slice(0, 600);
807
+ const operationSubject = operation === 'steering'
808
+ ? 'Steering'
809
+ : operation === 'turn_finalize'
810
+ ? 'Goal turn finalization'
811
+ : operation === 'stale_accounting'
812
+ ? 'Stale Goal accounting'
813
+ : `Target ${operation}`;
814
+ const goalIdBeforeRecovery = coord.goal?.goalId ?? goalIdBeforeMutation;
815
+ const failureReason = `${operationSubject} was not saved; Goal-owned execution was revoked fail-closed. ${detail}`;
816
+ coord.failClosedAfterPersistenceError(failureReason);
817
+ const snapshot = coord.snapshot();
818
+ const message = !snapshot
819
+ ? `${operationSubject} was not saved; disk authority reports that the Goal was deleted. No continuation was started. ${detail}`
820
+ : coord.goal?.status === 'complete'
821
+ ? `${operationSubject} was not saved; the restored completed Goal remains terminal. No continuation was started. ${detail}`
822
+ : `${operationSubject} was not saved; execution was paused fail-closed. Resolve the storage error, then use /target resume. ${detail}`;
823
+ if (snapshot) {
824
+ this.emitGoalEvent({
825
+ type: 'goal_updated',
826
+ goal: snapshot,
827
+ reason: coord.goal?.status === 'complete'
828
+ ? `target_${operation} persistence failed; completed disk authority preserved`
829
+ : `target_${operation} persistence failed; continuation paused fail-closed`,
830
+ });
831
+ }
832
+ else if (goalIdBeforeRecovery) {
833
+ this.emitGoalEvent({
834
+ type: 'goal_cleared',
835
+ goalId: goalIdBeforeRecovery,
836
+ reason: `target_${operation} failed after the Goal was deleted`,
837
+ });
838
+ }
839
+ const deferredGoalId = snapshot?.goalId ?? goalIdBeforeRecovery;
840
+ if (deferredGoalId) {
841
+ this.emitGoalEvent({
842
+ type: 'goal_continuation',
843
+ goalId: deferredGoalId,
844
+ phase: 'deferred',
845
+ reason: `target_${operation} persistence failed`,
846
+ });
847
+ }
848
+ return message;
849
+ }
335
850
  waitForIdle() {
336
851
  return this.activeRun ?? Promise.resolve();
337
852
  }
338
- async runTurn(firstInput) {
339
- let nextInput = firstInput;
340
- while (nextInput?.trim() && !this.stopping) {
341
- if (this.options.echoSubmittedInput ?? true) {
853
+ async runTurn(firstRequest) {
854
+ let nextRequest = firstRequest;
855
+ while (nextRequest && !this.stopping) {
856
+ this.goalProviderReservedTokens = 0;
857
+ if (!this.requestIsCurrent(nextRequest))
858
+ break;
859
+ const request = nextRequest;
860
+ const nextInput = request.text?.trim() ||
861
+ 'Continue pursuing the active goal from its persisted plan and evidence.';
862
+ if ((this.options.echoSubmittedInput ?? true) && request.echoToTranscript) {
342
863
  this.emitAppend(submittedEntry(nextInput));
343
864
  }
344
865
  this.options.beforeTurn?.(nextInput);
345
866
  const turn = this.turnController.beginTurn(nextInput);
867
+ const startedAt = Date.now();
868
+ const workspaceFingerprintBefore = (0, workspace_state_1.captureWorkspaceFingerprint)(this.activeProjectPath());
869
+ const coord = this.ensureGoalCoordinator(false);
870
+ const toolContext = coord
871
+ ? { coordinator: coord, request, turnId: String(turn.id), evidenceRecords: [] }
872
+ : undefined;
346
873
  this.options.runtime.store.setProcessing(true);
347
874
  this.emitProcessing(true);
348
875
  const runningStatus = statusText(this.options.runningStatus, nextInput);
349
876
  if (runningStatus)
350
877
  this.emitStatus(runningStatus);
351
878
  try {
352
- await this.runner.runInput(nextInput, { abortSignal: turn.abortSignal, turnId: turn.id });
879
+ const execute = () => {
880
+ if (this.runner.runRequest) {
881
+ return this.runner.runRequest(request, {
882
+ abortSignal: turn.abortSignal,
883
+ turnId: turn.id,
884
+ });
885
+ }
886
+ return this.runner.runInput(nextInput, {
887
+ abortSignal: turn.abortSignal,
888
+ turnId: turn.id,
889
+ persistAsUserMessage: request.persistAsUserMessage,
890
+ inputKind: request.inputKind,
891
+ });
892
+ };
893
+ if (toolContext) {
894
+ await (0, tools_1.runWithGoalToolContext)(toolContext, execute);
895
+ }
896
+ else {
897
+ await execute();
898
+ }
353
899
  }
354
900
  catch (error) {
355
901
  if (!turn.abortSignal.aborted) {
@@ -365,13 +911,35 @@ class AgentRuntimeController {
365
911
  finally {
366
912
  const revision = this.turnController.finishTurn(turn.id);
367
913
  // v0.2.26: finalize goal turn with usage data.
368
- this.finalizeGoalTurn(turn.id);
914
+ this.finalizeGoalTurn(turn.id, request, toolContext, startedAt, workspaceFingerprintBefore, turn.abortSignal.aborted);
369
915
  if (revision?.trim()) {
370
- this.emitStatus(this.options.restartingStatus ?? 'Restarting with latest instruction...');
371
- nextInput = revision;
916
+ const currentCoord = this.ensureGoalCoordinator(false);
917
+ const currentGoal = currentCoord?.goal?.status === 'active' ? currentCoord.goal : undefined;
918
+ if (request.goal && currentCoord?.goal && !currentGoal) {
919
+ this.emitStatus('Latest instruction was saved, but the Goal is paused because usage accounting is incomplete. Use /target resume after reviewing the budget.');
920
+ nextRequest = undefined;
921
+ }
922
+ else {
923
+ this.emitStatus(this.options.restartingStatus ?? 'Restarting with latest instruction...');
924
+ nextRequest = {
925
+ inputKind: 'revision',
926
+ text: revision,
927
+ sessionId: this.options.runtime.getSession()?.id ?? request.sessionId,
928
+ goal: currentGoal
929
+ ? {
930
+ goalId: currentGoal.goalId,
931
+ revision: currentGoal.revision,
932
+ continuationIndex: currentGoal.continuationCount,
933
+ }
934
+ : undefined,
935
+ persistAsUserMessage: true,
936
+ echoToTranscript: true,
937
+ generation: currentCoord?.generation ?? 0,
938
+ };
939
+ }
372
940
  }
373
941
  else {
374
- nextInput = undefined;
942
+ nextRequest = undefined;
375
943
  }
376
944
  }
377
945
  }
@@ -392,7 +960,11 @@ class AgentRuntimeController {
392
960
  }
393
961
  else {
394
962
  const message = error instanceof Error ? error.message : String(error);
395
- this.emitAppend({ role: 'error', content: `[RUNTIME] Error: ${message}`, errorLayer: 'runtime' });
963
+ this.emitAppend({
964
+ role: 'error',
965
+ content: `[RUNTIME] Error: ${message}`,
966
+ errorLayer: 'runtime',
967
+ });
396
968
  }
397
969
  this.options.runtime.store.setProcessing(false);
398
970
  this.emitProcessing(false);
@@ -412,6 +984,131 @@ class AgentRuntimeController {
412
984
  emitProcessing(processing) {
413
985
  this.eventSink.emit({ type: 'processing_changed', processing });
414
986
  }
987
+ emitGoalEvent(event) {
988
+ this.eventSink.emit({ type: 'goal_event', event });
989
+ }
990
+ requestIsCurrent(request) {
991
+ const activeSessionId = this.options.runtime.getSession()?.id;
992
+ if (activeSessionId && request.sessionId !== activeSessionId)
993
+ return false;
994
+ if (!request.goal)
995
+ return true;
996
+ const coord = this.goalCoordinator;
997
+ const goal = coord?.goal;
998
+ return Boolean(coord &&
999
+ goal &&
1000
+ coord.boundSessionId === request.sessionId &&
1001
+ coord.generation === request.generation &&
1002
+ goal.goalId === request.goal.goalId &&
1003
+ goal.revision === request.goal.revision &&
1004
+ goal.status === 'active');
1005
+ }
1006
+ verificationSummary(loopStats) {
1007
+ if (!loopStats)
1008
+ return 'No loop statistics were recorded.';
1009
+ const passed = loopStats.verificationPassedCommands ?? [];
1010
+ const failed = loopStats.verificationFailedCommands ?? [];
1011
+ const missing = loopStats.verificationMissingCommands ?? [];
1012
+ return [
1013
+ `finish=${loopStats.finishReason}`,
1014
+ passed.length ? `passed=${passed.join(', ')}` : '',
1015
+ failed.length ? `failed=${failed.join(', ')}` : '',
1016
+ missing.length ? `missing=${missing.join(', ')}` : '',
1017
+ ]
1018
+ .filter(Boolean)
1019
+ .join('; ');
1020
+ }
1021
+ captureGoalEvidence(event) {
1022
+ if (event.type !== 'tool_finished')
1023
+ return;
1024
+ const context = (0, tools_1.currentGoalToolContext)();
1025
+ const goal = context?.coordinator.goal;
1026
+ if (!context || !goal || goal.status !== 'active')
1027
+ return;
1028
+ const capturedAt = Date.now();
1029
+ const claimedAssertion = event.event.externalAssertion;
1030
+ const assertion = claimedAssertion &&
1031
+ (0, external_assertion_1.externalAssertionMatchesInvocation)({
1032
+ assertion: claimedAssertion,
1033
+ name: event.event.name,
1034
+ args: event.event.args,
1035
+ success: event.event.success,
1036
+ skipped: event.event.skipped,
1037
+ now: capturedAt,
1038
+ })
1039
+ ? claimedAssertion
1040
+ : undefined;
1041
+ const kind = claimedAssertion
1042
+ ? 'external'
1043
+ : (0, evidence_1.classifyGoalEvidenceKind)(event.event.name, event.event.args);
1044
+ if (!kind)
1045
+ return;
1046
+ const classifiedResult = (0, evidence_1.classifyGoalEvidenceResult)({
1047
+ kind,
1048
+ success: event.event.success,
1049
+ skipped: event.event.skipped,
1050
+ summary: event.event.summary,
1051
+ error: event.event.error,
1052
+ });
1053
+ // A successful transport plus display text is never sufficient proof of
1054
+ // an external mutation/state. Only runtime-derived typed assertions may
1055
+ // promote successful external tool output to passed evidence. Explicit
1056
+ // tool failures remain failed so the ledger still records negative proof.
1057
+ const result = assertion
1058
+ ? assertion.status
1059
+ : kind === 'external' && classifiedResult === 'passed'
1060
+ ? 'inconclusive'
1061
+ : classifiedResult;
1062
+ const assertionSubject = assertion
1063
+ ? [
1064
+ `external ${assertion.action} ${assertion.status}`,
1065
+ `provider=${assertion.provider}`,
1066
+ `target=${assertion.target}`,
1067
+ assertion.observedValue ? `observed=${assertion.observedValue}` : '',
1068
+ ]
1069
+ .filter(Boolean)
1070
+ .join(' ')
1071
+ : undefined;
1072
+ const record = {
1073
+ id: `evidence:${(0, crypto_1.randomUUID)()}`,
1074
+ goalId: goal.goalId,
1075
+ goalRevision: goal.revision,
1076
+ objectiveRevision: goal.contract?.objectiveRevision ?? 0,
1077
+ turnId: context.turnId,
1078
+ kind,
1079
+ subject: (0, redaction_1.redactTraceText)(assertionSubject || event.event.summary || event.event.error || event.event.name).slice(0, 512),
1080
+ result,
1081
+ sourceRef: `tool:${event.event.callId}:${event.event.name}`,
1082
+ capturedAt,
1083
+ workspaceFingerprint: (0, workspace_state_1.captureWorkspaceFingerprint)(this.activeProjectPath()),
1084
+ expiresAt: kind === 'external' ? capturedAt + 5 * 60000 : undefined,
1085
+ ...(assertion ? { externalAssertion: assertion } : {}),
1086
+ redacted: true,
1087
+ };
1088
+ context.evidenceRecords.push(record);
1089
+ this.emitGoalEvent({
1090
+ type: 'goal_evidence_recorded',
1091
+ goalId: goal.goalId,
1092
+ evidence: {
1093
+ id: record.id,
1094
+ kind: record.kind,
1095
+ result: record.result,
1096
+ subject: record.subject,
1097
+ },
1098
+ });
1099
+ }
1100
+ restoreGoalForSession(sessionId, projectPath) {
1101
+ // A continuation queued by the previously active session must not survive a
1102
+ // session switch. It captured the old coordinator and would otherwise be
1103
+ // able to start after /resume has rebound the runtime.
1104
+ this.continuationScheduleEpoch += 1;
1105
+ this.goalCoordinator = null;
1106
+ this.goalCoordinatorSessionId = null;
1107
+ const coord = this.bindGoalCoordinator(sessionId, true, projectPath);
1108
+ const snapshot = coord?.snapshot();
1109
+ if (snapshot)
1110
+ this.emitGoalEvent({ type: 'goal_restored', goal: snapshot });
1111
+ }
415
1112
  async requestToolPermission(request) {
416
1113
  if (request.abortSignal?.aborted || this.stopping)
417
1114
  return false;
@@ -441,72 +1138,112 @@ class AgentRuntimeController {
441
1138
  const coord = this.goalCoordinator;
442
1139
  if (!coord)
443
1140
  return { type: 'empty' };
444
- switch (input.action) {
445
- case 'show':
446
- // Just shows status — already handled by the renderer format function.
447
- return { type: 'empty' };
448
- case 'create': {
449
- const obj = input.payload?.objective;
450
- if (!obj)
1141
+ let executionRevoked = false;
1142
+ const goalIdBeforeMutation = coord.goal?.goalId;
1143
+ try {
1144
+ switch (input.action) {
1145
+ case 'show':
1146
+ // Just shows status — already handled by the renderer format function.
451
1147
  return { type: 'empty' };
452
- const result = coord.create(obj);
453
- return { type: result.ok ? 'interrupted' : 'empty' };
454
- }
455
- case 'pause':
456
- coord.pause();
457
- return { type: 'interrupted' };
458
- case 'resume':
459
- coord.resume();
460
- return { type: 'interrupted' };
461
- case 'edit': {
462
- const obj = input.payload?.objective;
463
- if (obj)
464
- coord.edit(obj);
465
- return { type: 'interrupted' };
466
- }
467
- case 'replace': {
468
- const obj = input.payload?.objective;
469
- if (obj)
470
- coord.replace(obj);
471
- return { type: 'interrupted' };
472
- }
473
- case 'set_budget':
474
- coord.setBudget(input.payload?.tokenBudget ?? null);
475
- return { type: 'interrupted' };
476
- case 'clear':
477
- if (input.payload?.confirmed) {
478
- coord.clear();
1148
+ case 'create': {
1149
+ const obj = input.payload?.objective;
1150
+ if (!obj)
1151
+ return { type: 'empty' };
1152
+ const result = coord.create(obj);
1153
+ return { type: result.ok ? 'interrupted' : 'empty' };
479
1154
  }
480
- return { type: 'interrupted' };
481
- default:
482
- return { type: 'empty' };
1155
+ case 'pause':
1156
+ if (coord.isActive) {
1157
+ this.abortGoalOwnedExecution();
1158
+ executionRevoked = true;
1159
+ }
1160
+ return { type: coord.pause() ? 'interrupted' : 'empty' };
1161
+ case 'resume':
1162
+ return { type: coord.resume() ? 'interrupted' : 'empty' };
1163
+ case 'confirm':
1164
+ coord.confirmCriterion(input.payload?.criterionId ?? '');
1165
+ return { type: 'interrupted' };
1166
+ case 'edit': {
1167
+ const obj = input.payload?.objective;
1168
+ if (obj)
1169
+ coord.edit(obj);
1170
+ return { type: 'interrupted' };
1171
+ }
1172
+ case 'replace': {
1173
+ const obj = input.payload?.objective;
1174
+ if (obj && !coord.replace(obj)) {
1175
+ const message = this.failClosedGoalMutation(coord, input.action, new Error('Target could not be replaced.'), executionRevoked, goalIdBeforeMutation);
1176
+ this.emitGoalMutationError(message);
1177
+ }
1178
+ return { type: 'interrupted' };
1179
+ }
1180
+ case 'set_budget':
1181
+ coord.setBudget(input.payload?.tokenBudget ?? null);
1182
+ return { type: 'interrupted' };
1183
+ case 'clear':
1184
+ if (input.payload?.confirmed && coord.goal !== null) {
1185
+ this.abortGoalOwnedExecution();
1186
+ executionRevoked = true;
1187
+ if (!coord.clear()) {
1188
+ const message = this.failClosedGoalMutation(coord, input.action, new Error('Target clear did not remove the active Goal.'), executionRevoked, goalIdBeforeMutation);
1189
+ this.emitGoalMutationError(message);
1190
+ }
1191
+ }
1192
+ return { type: 'interrupted' };
1193
+ default:
1194
+ return { type: 'empty' };
1195
+ }
1196
+ }
1197
+ catch (cause) {
1198
+ const message = this.failClosedGoalMutation(coord, input.action, cause, executionRevoked, goalIdBeforeMutation);
1199
+ this.emitGoalMutationError(message);
1200
+ return { type: 'interrupted' };
483
1201
  }
484
1202
  }
1203
+ emitGoalMutationError(message) {
1204
+ this.emitAppend({
1205
+ role: 'error',
1206
+ title: 'target',
1207
+ content: message,
1208
+ errorLayer: 'runtime',
1209
+ });
1210
+ this.emitStatus(message);
1211
+ }
485
1212
  ensureGoalCoordinator(ensureSession) {
486
- if (this.goalCoordinator && this.goalCoordinatorSessionId === '*') {
487
- return this.goalCoordinator;
488
- }
489
1213
  if (ensureSession) {
490
1214
  this.options.runtime.ensureSession();
491
1215
  }
492
- const sessionId = this.options.runtime.getSession()?.id;
1216
+ const session = this.options.runtime.getSession();
1217
+ const sessionId = session?.id;
493
1218
  if (!sessionId)
494
1219
  return this.goalCoordinator;
495
1220
  if (this.goalCoordinator && this.goalCoordinatorSessionId === sessionId) {
496
1221
  return this.goalCoordinator;
497
1222
  }
1223
+ return this.bindGoalCoordinator(sessionId, true, session.projectPath || this.options.runtime.cwd);
1224
+ }
1225
+ bindGoalCoordinator(sessionId, recoverActive, projectPath) {
498
1226
  const { GoalCoordinator } = require('./goals/coordinator');
499
- const coord = new GoalCoordinator(this.options.runtime.cwd, sessionId);
500
- coord.load();
1227
+ const coord = new GoalCoordinator(projectPath, sessionId);
1228
+ coord.load(recoverActive);
1229
+ if (coord.lastLoadIssue) {
1230
+ this.emitAppend({
1231
+ role: 'error',
1232
+ title: 'target recovery',
1233
+ content: `${coord.lastLoadIssue.code}: ${coord.lastLoadIssue.message}. Run orion doctor for recovery guidance.`,
1234
+ errorLayer: 'runtime',
1235
+ });
1236
+ }
501
1237
  this.goalCoordinator = coord;
502
1238
  this.goalCoordinatorSessionId = sessionId;
503
- const { setGoalToolCoordinator } = require('./goals/tools');
504
- setGoalToolCoordinator(coord);
505
1239
  if ('setGoalCoordinator' in this.runner) {
506
1240
  this.runner.setGoalCoordinator(coord);
507
1241
  }
508
1242
  return coord;
509
1243
  }
1244
+ activeProjectPath() {
1245
+ return this.options.runtime.getSession()?.projectPath || this.options.runtime.cwd;
1246
+ }
510
1247
  formatTargetStatus(coord) {
511
1248
  const goal = coord.goal;
512
1249
  if (!goal) {
@@ -514,7 +1251,33 @@ class AgentRuntimeController {
514
1251
  }
515
1252
  const objective = goal.objective.length > 60 ? `${goal.objective.slice(0, 57)}...` : goal.objective;
516
1253
  const tokens = goal.tokensUsed >= 1000 ? `${(goal.tokensUsed / 1000).toFixed(1)}K` : String(goal.tokensUsed);
517
- return `Target: [${goal.status}] ${objective} | ${goal.continuationCount} turns | ${tokens} tokens`;
1254
+ const lines = [
1255
+ `Target: [${goal.status}] ${objective} | ${goal.continuationCount} turns | ${tokens} tokens${goal.tokenBudget ? ` | budget ${tokens}/${goal.tokenBudget}` : ''}`,
1256
+ ];
1257
+ const contract = goal.contract;
1258
+ const plan = contract?.planSnapshot;
1259
+ if (plan) {
1260
+ lines.push(`Plan: r${plan.revision} ${plan.phase}${plan.nextAction ? ` | next: ${plan.nextAction}` : ''}`);
1261
+ }
1262
+ if (contract?.successCriteria.length) {
1263
+ const passed = contract.successCriteria.filter(criterion => criterion.status === 'passed').length;
1264
+ const failed = contract.successCriteria.filter(criterion => criterion.status === 'failed').length;
1265
+ const stale = contract.successCriteria.filter(criterion => criterion.status === 'stale').length;
1266
+ const pending = contract.successCriteria.length - passed - failed - stale;
1267
+ lines.push(`Criteria: ${pending} pending | ${passed}/${contract.successCriteria.length} passed | ${failed} failed | ${stale} stale`);
1268
+ }
1269
+ if (goal.completionAudit?.remainingRequirements.length) {
1270
+ lines.push(`Audit: ${goal.completionAudit.remainingRequirements.slice(0, 3).join(' | ')}`);
1271
+ }
1272
+ if (goal.stopReason)
1273
+ lines.push(`Resume: ${goal.stopReason.message}`);
1274
+ const recentEvidence = (goal.evidenceLedger ?? []).slice(-3);
1275
+ if (recentEvidence.length > 0) {
1276
+ lines.push(`Evidence: ${recentEvidence
1277
+ .map(item => `${item.id} ${item.kind}/${item.result} ${item.subject}`)
1278
+ .join(' | ')}`);
1279
+ }
1280
+ return lines.join('\n');
518
1281
  }
519
1282
  recordPermissionDecision(requestId, approved) {
520
1283
  const resolve = this.pendingPermissions.get(requestId);
@@ -545,7 +1308,9 @@ class AgentRuntimeController {
545
1308
  };
546
1309
  chatOptions.uiCapabilities = uiCapabilities;
547
1310
  chatOptions.uiRenderer = chatOptions.uiRenderer ?? resolvedRenderer;
548
- chatOptions.onVerificationStateChange = chatOptions.onVerificationStateChange ?? (state => this.turnController.setVerificationState(state));
1311
+ chatOptions.onVerificationStateChange =
1312
+ chatOptions.onVerificationStateChange ??
1313
+ (state => this.turnController.setVerificationState(state));
549
1314
  if (this.options.useRuntimeToolPermissions && !chatOptions.confirmToolUse) {
550
1315
  chatOptions.confirmToolUse = request => this.requestToolPermission(request);
551
1316
  }
@@ -572,6 +1337,46 @@ class AgentRuntimeController {
572
1337
  });
573
1338
  };
574
1339
  }
1340
+ const configuredPreflight = chatOptions.beforeProviderRequest;
1341
+ chatOptions.beforeProviderRequest = async (context) => {
1342
+ if (configuredPreflight) {
1343
+ const configuredDecision = await configuredPreflight(context);
1344
+ if (!configuredDecision.available)
1345
+ return configuredDecision;
1346
+ }
1347
+ const coord = this.goalCoordinator;
1348
+ const goal = coord?.goal;
1349
+ if (!coord || !goal || goal.status !== 'active' || !goal.tokenBudget) {
1350
+ return { available: true };
1351
+ }
1352
+ const projectedTokens = Math.max(1, context.estimatedPromptTokens, goal.lastTurn?.totalTokens ?? 0);
1353
+ const decision = (0, accounting_1.budgetPreflight)(goal.tokensUsed + this.goalProviderReservedTokens, goal.tokenBudget, projectedTokens);
1354
+ if (!decision.available) {
1355
+ try {
1356
+ coord.limitBudget(decision.reason ?? 'Token budget unavailable for provider request.');
1357
+ }
1358
+ catch (cause) {
1359
+ const message = this.failClosedGoalMutation(coord, 'provider_budget_stop', cause, false, goal.goalId);
1360
+ this.emitGoalMutationError(message);
1361
+ return { available: false, reason: message };
1362
+ }
1363
+ const snapshot = coord.snapshot();
1364
+ if (snapshot) {
1365
+ this.emitGoalEvent({
1366
+ type: 'goal_updated',
1367
+ goal: snapshot,
1368
+ reason: `provider request ${context.operation} attempt ${context.attempt} rejected by token budget`,
1369
+ });
1370
+ }
1371
+ return decision;
1372
+ }
1373
+ // Reserve the projected cost before the network call. Failed retries and
1374
+ // fallbacks keep their reservation because providers may charge attempts
1375
+ // that fail before returning usage. Actual successful usage is persisted
1376
+ // at turn finalization; reservations reset only when the next root turn starts.
1377
+ this.goalProviderReservedTokens += projectedTokens;
1378
+ return decision;
1379
+ };
575
1380
  return Object.keys(chatOptions).length > 0 ? chatOptions : undefined;
576
1381
  }
577
1382
  }