@caupulican/pi-adaptative 0.81.39 → 0.81.40

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 (667) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +2 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +2 -0
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -1
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +2 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -1
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +3 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +9 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +290 -107
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +357 -122
  13. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +2 -2
  14. package/dist/cli/args.d.ts +2 -0
  15. package/dist/cli/args.d.ts.map +1 -1
  16. package/dist/cli/args.js +9 -0
  17. package/dist/cli/args.js.map +1 -1
  18. package/dist/core/agent-directory-layout.d.ts +15 -0
  19. package/dist/core/agent-directory-layout.d.ts.map +1 -0
  20. package/dist/core/agent-directory-layout.js +72 -0
  21. package/dist/core/agent-directory-layout.js.map +1 -0
  22. package/dist/core/agent-paths.d.ts +19 -2
  23. package/dist/core/agent-paths.d.ts.map +1 -1
  24. package/dist/core/agent-paths.js +67 -4
  25. package/dist/core/agent-paths.js.map +1 -1
  26. package/dist/core/agent-session-runtime.d.ts +52 -8
  27. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  28. package/dist/core/agent-session-runtime.js +397 -79
  29. package/dist/core/agent-session-runtime.js.map +1 -1
  30. package/dist/core/agent-session-services.d.ts +1 -0
  31. package/dist/core/agent-session-services.d.ts.map +1 -1
  32. package/dist/core/agent-session-services.js +1 -0
  33. package/dist/core/agent-session-services.js.map +1 -1
  34. package/dist/core/agent-session.d.ts +71 -136
  35. package/dist/core/agent-session.d.ts.map +1 -1
  36. package/dist/core/agent-session.js +477 -1318
  37. package/dist/core/agent-session.js.map +1 -1
  38. package/dist/core/autonomy/approval-gate.d.ts +3 -2
  39. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  40. package/dist/core/autonomy/approval-gate.js +6 -28
  41. package/dist/core/autonomy/approval-gate.js.map +1 -1
  42. package/dist/core/autonomy/bounded-completion.d.ts +1 -1
  43. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  44. package/dist/core/autonomy/bounded-completion.js +1 -1
  45. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  46. package/dist/core/autonomy/contracts.d.ts +20 -12
  47. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  48. package/dist/core/autonomy/contracts.js.map +1 -1
  49. package/dist/core/autonomy/foreground-envelope.d.ts +8 -2
  50. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  51. package/dist/core/autonomy/foreground-envelope.js +8 -25
  52. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  53. package/dist/core/autonomy/gates.d.ts.map +1 -1
  54. package/dist/core/autonomy/gates.js +8 -10
  55. package/dist/core/autonomy/gates.js.map +1 -1
  56. package/dist/core/autonomy/lane-private-paths.d.ts +2 -0
  57. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -0
  58. package/dist/core/autonomy/lane-private-paths.js +16 -0
  59. package/dist/core/autonomy/lane-private-paths.js.map +1 -0
  60. package/dist/core/autonomy/lane-tool-surface.d.ts +10 -0
  61. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  62. package/dist/core/autonomy/lane-tool-surface.js +46 -8
  63. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  64. package/dist/core/autonomy/lane-tracker.d.ts +14 -0
  65. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  66. package/dist/core/autonomy/lane-tracker.js +38 -0
  67. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  68. package/dist/core/autonomy/risk-assessment.d.ts +1 -2
  69. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  70. package/dist/core/autonomy/risk-assessment.js +1 -123
  71. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  72. package/dist/core/autonomy/session-lane-record.d.ts +4 -4
  73. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  74. package/dist/core/autonomy/session-lane-record.js +16 -29
  75. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  76. package/dist/core/autonomy/telemetry-events.d.ts +1 -1
  77. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -1
  78. package/dist/core/autonomy/telemetry-events.js +1 -1
  79. package/dist/core/autonomy/telemetry-events.js.map +1 -1
  80. package/dist/core/autonomy-telemetry.d.ts +7 -7
  81. package/dist/core/autonomy-telemetry.d.ts.map +1 -1
  82. package/dist/core/autonomy-telemetry.js +9 -9
  83. package/dist/core/autonomy-telemetry.js.map +1 -1
  84. package/dist/core/background-lane-controller.d.ts +54 -236
  85. package/dist/core/background-lane-controller.d.ts.map +1 -1
  86. package/dist/core/background-lane-controller.js +140 -1273
  87. package/dist/core/background-lane-controller.js.map +1 -1
  88. package/dist/core/capability-contract.d.ts +4 -0
  89. package/dist/core/capability-contract.d.ts.map +1 -0
  90. package/dist/core/capability-contract.js +28 -0
  91. package/dist/core/capability-contract.js.map +1 -0
  92. package/dist/core/compaction-controller.d.ts +90 -0
  93. package/dist/core/compaction-controller.d.ts.map +1 -0
  94. package/dist/core/compaction-controller.js +452 -0
  95. package/dist/core/compaction-controller.js.map +1 -0
  96. package/dist/core/compaction-support.d.ts +12 -11
  97. package/dist/core/compaction-support.d.ts.map +1 -1
  98. package/dist/core/compaction-support.js.map +1 -1
  99. package/dist/core/context/context-artifacts.d.ts +6 -2
  100. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  101. package/dist/core/context/context-artifacts.js +34 -2
  102. package/dist/core/context/context-artifacts.js.map +1 -1
  103. package/dist/core/context/context-store-retention.d.ts +8 -2
  104. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  105. package/dist/core/context/context-store-retention.js +158 -35
  106. package/dist/core/context/context-store-retention.js.map +1 -1
  107. package/dist/core/context/okf-memory.d.ts.map +1 -1
  108. package/dist/core/context/okf-memory.js +9 -11
  109. package/dist/core/context/okf-memory.js.map +1 -1
  110. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  111. package/dist/core/context/sqlite-runtime-index.js +6 -8
  112. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  113. package/dist/core/context-gc.d.ts +3 -0
  114. package/dist/core/context-gc.d.ts.map +1 -1
  115. package/dist/core/context-gc.js +10 -5
  116. package/dist/core/context-gc.js.map +1 -1
  117. package/dist/core/context-pipeline.d.ts.map +1 -1
  118. package/dist/core/context-pipeline.js +9 -4
  119. package/dist/core/context-pipeline.js.map +1 -1
  120. package/dist/core/default-tool-surface.d.ts.map +1 -1
  121. package/dist/core/default-tool-surface.js +1 -0
  122. package/dist/core/default-tool-surface.js.map +1 -1
  123. package/dist/core/delegation/managed-lane-controller.d.ts +34 -0
  124. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -0
  125. package/dist/core/delegation/managed-lane-controller.js +217 -0
  126. package/dist/core/delegation/managed-lane-controller.js.map +1 -0
  127. package/dist/core/delegation/session-worker-claim.d.ts +44 -0
  128. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -0
  129. package/dist/core/delegation/session-worker-claim.js +98 -0
  130. package/dist/core/delegation/session-worker-claim.js.map +1 -0
  131. package/dist/core/delegation/worker-actions.d.ts +7 -5
  132. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  133. package/dist/core/delegation/worker-actions.js +20 -4
  134. package/dist/core/delegation/worker-actions.js.map +1 -1
  135. package/dist/core/delegation/{worker-result.d.ts → worker-claim.d.ts} +13 -13
  136. package/dist/core/delegation/worker-claim.d.ts.map +1 -0
  137. package/dist/core/delegation/{worker-result.js → worker-claim.js} +61 -51
  138. package/dist/core/delegation/worker-claim.js.map +1 -0
  139. package/dist/core/delegation/worker-delegation-controller.d.ts +110 -0
  140. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -0
  141. package/dist/core/delegation/worker-delegation-controller.js +861 -0
  142. package/dist/core/delegation/worker-delegation-controller.js.map +1 -0
  143. package/dist/core/delegation/worker-delegation-request.d.ts +11 -0
  144. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -0
  145. package/dist/core/delegation/worker-delegation-request.js +2 -0
  146. package/dist/core/delegation/worker-delegation-request.js.map +1 -0
  147. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +40 -0
  148. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -0
  149. package/dist/core/delegation/worker-dispatch-scheduler.js +93 -0
  150. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -0
  151. package/dist/core/delegation/worker-execution-policy.d.ts +62 -0
  152. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -0
  153. package/dist/core/delegation/worker-execution-policy.js +160 -0
  154. package/dist/core/delegation/worker-execution-policy.js.map +1 -0
  155. package/dist/core/delegation/worker-lane-projection.d.ts +14 -0
  156. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -0
  157. package/dist/core/delegation/worker-lane-projection.js +100 -0
  158. package/dist/core/delegation/worker-lane-projection.js.map +1 -0
  159. package/dist/core/delegation/worker-lifecycle.d.ts +97 -0
  160. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -0
  161. package/dist/core/delegation/worker-lifecycle.js +339 -0
  162. package/dist/core/delegation/worker-lifecycle.js.map +1 -0
  163. package/dist/core/delegation/worker-notification-coordinator.d.ts +36 -0
  164. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -0
  165. package/dist/core/delegation/worker-notification-coordinator.js +90 -0
  166. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -0
  167. package/dist/core/delegation/worker-profile-resolver.d.ts +55 -0
  168. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -0
  169. package/dist/core/delegation/worker-profile-resolver.js +113 -0
  170. package/dist/core/delegation/worker-profile-resolver.js.map +1 -0
  171. package/dist/core/delegation/worker-runner.d.ts +25 -14
  172. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  173. package/dist/core/delegation/worker-runner.js +122 -53
  174. package/dist/core/delegation/worker-runner.js.map +1 -1
  175. package/dist/core/delegation/worker-task-label.d.ts +3 -0
  176. package/dist/core/delegation/worker-task-label.d.ts.map +1 -0
  177. package/dist/core/delegation/worker-task-label.js +11 -0
  178. package/dist/core/delegation/worker-task-label.js.map +1 -0
  179. package/dist/core/doctor.d.ts +8 -0
  180. package/dist/core/doctor.d.ts.map +1 -1
  181. package/dist/core/doctor.js +32 -1
  182. package/dist/core/doctor.js.map +1 -1
  183. package/dist/core/extensions/index.d.ts +1 -1
  184. package/dist/core/extensions/index.d.ts.map +1 -1
  185. package/dist/core/extensions/index.js.map +1 -1
  186. package/dist/core/extensions/loader.d.ts +5 -1
  187. package/dist/core/extensions/loader.d.ts.map +1 -1
  188. package/dist/core/extensions/loader.js +50 -15
  189. package/dist/core/extensions/loader.js.map +1 -1
  190. package/dist/core/extensions/runner.d.ts.map +1 -1
  191. package/dist/core/extensions/runner.js +6 -0
  192. package/dist/core/extensions/runner.js.map +1 -1
  193. package/dist/core/extensions/storage.d.ts +8 -0
  194. package/dist/core/extensions/storage.d.ts.map +1 -0
  195. package/dist/core/extensions/storage.js +28 -0
  196. package/dist/core/extensions/storage.js.map +1 -0
  197. package/dist/core/extensions/types.d.ts +70 -21
  198. package/dist/core/extensions/types.d.ts.map +1 -1
  199. package/dist/core/extensions/types.js.map +1 -1
  200. package/dist/core/failure-corpus.d.ts +1 -15
  201. package/dist/core/failure-corpus.d.ts.map +1 -1
  202. package/dist/core/failure-corpus.js +9 -37
  203. package/dist/core/failure-corpus.js.map +1 -1
  204. package/dist/core/foreground-recovery-controller.d.ts +52 -0
  205. package/dist/core/foreground-recovery-controller.d.ts.map +1 -0
  206. package/dist/core/foreground-recovery-controller.js +111 -0
  207. package/dist/core/foreground-recovery-controller.js.map +1 -0
  208. package/dist/core/gateways/channel-provider.d.ts +3 -0
  209. package/dist/core/gateways/channel-provider.d.ts.map +1 -1
  210. package/dist/core/gateways/channel-provider.js +31 -6
  211. package/dist/core/gateways/channel-provider.js.map +1 -1
  212. package/dist/core/goals/goal-acceptance.d.ts +11 -0
  213. package/dist/core/goals/goal-acceptance.d.ts.map +1 -0
  214. package/dist/core/goals/goal-acceptance.js +25 -0
  215. package/dist/core/goals/goal-acceptance.js.map +1 -0
  216. package/dist/core/goals/goal-auto-continue-controller.d.ts +27 -0
  217. package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -0
  218. package/dist/core/goals/goal-auto-continue-controller.js +93 -0
  219. package/dist/core/goals/goal-auto-continue-controller.js.map +1 -0
  220. package/dist/core/goals/goal-continuation-controller.d.ts +1 -1
  221. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  222. package/dist/core/goals/goal-continuation-controller.js +10 -0
  223. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  224. package/dist/core/goals/goal-continuation-prompt.d.ts +1 -1
  225. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  226. package/dist/core/goals/goal-continuation-prompt.js +6 -6
  227. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  228. package/dist/core/goals/goal-lifecycle.d.ts +20 -0
  229. package/dist/core/goals/goal-lifecycle.d.ts.map +1 -0
  230. package/dist/core/goals/goal-lifecycle.js +17 -0
  231. package/dist/core/goals/goal-lifecycle.js.map +1 -0
  232. package/dist/core/goals/goal-runtime-snapshot.d.ts +11 -9
  233. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  234. package/dist/core/goals/goal-runtime-snapshot.js +10 -8
  235. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  236. package/dist/core/goals/goal-state.d.ts +1 -0
  237. package/dist/core/goals/goal-state.d.ts.map +1 -1
  238. package/dist/core/goals/goal-state.js +7 -10
  239. package/dist/core/goals/goal-state.js.map +1 -1
  240. package/dist/core/goals/goal-tool-core.d.ts +8 -6
  241. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  242. package/dist/core/goals/goal-tool-core.js +11 -41
  243. package/dist/core/goals/goal-tool-core.js.map +1 -1
  244. package/dist/core/goals/session-goal-state.d.ts +2 -4
  245. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  246. package/dist/core/goals/session-goal-state.js +10 -33
  247. package/dist/core/goals/session-goal-state.js.map +1 -1
  248. package/dist/core/human-input.d.ts +100 -0
  249. package/dist/core/human-input.d.ts.map +1 -0
  250. package/dist/core/human-input.js +362 -0
  251. package/dist/core/human-input.js.map +1 -0
  252. package/dist/core/index.d.ts +9 -1
  253. package/dist/core/index.d.ts.map +1 -1
  254. package/dist/core/index.js +9 -1
  255. package/dist/core/index.js.map +1 -1
  256. package/dist/core/{telemetry.d.ts → install-telemetry.d.ts} +1 -1
  257. package/dist/core/install-telemetry.d.ts.map +1 -0
  258. package/dist/core/{telemetry.js → install-telemetry.js} +1 -1
  259. package/dist/core/install-telemetry.js.map +1 -0
  260. package/dist/core/keybindings.d.ts +17 -1
  261. package/dist/core/keybindings.d.ts.map +1 -1
  262. package/dist/core/keybindings.js +24 -6
  263. package/dist/core/keybindings.js.map +1 -1
  264. package/dist/core/learning/learning-audit.d.ts +2 -4
  265. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  266. package/dist/core/learning/learning-audit.js +9 -29
  267. package/dist/core/learning/learning-audit.js.map +1 -1
  268. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  269. package/dist/core/learning/learning-gate.js +1 -6
  270. package/dist/core/learning/learning-gate.js.map +1 -1
  271. package/dist/core/learning/session-learning-decision.d.ts +2 -4
  272. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  273. package/dist/core/learning/session-learning-decision.js +9 -29
  274. package/dist/core/learning/session-learning-decision.js.map +1 -1
  275. package/dist/core/memory-controller.d.ts.map +1 -1
  276. package/dist/core/memory-controller.js +4 -4
  277. package/dist/core/memory-controller.js.map +1 -1
  278. package/dist/core/model-capability.d.ts.map +1 -1
  279. package/dist/core/model-capability.js +1 -0
  280. package/dist/core/model-capability.js.map +1 -1
  281. package/dist/core/model-resolver.d.ts +1 -1
  282. package/dist/core/model-resolver.d.ts.map +1 -1
  283. package/dist/core/model-resolver.js +6 -9
  284. package/dist/core/model-resolver.js.map +1 -1
  285. package/dist/core/model-router/intent-classifier.d.ts +1 -4
  286. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  287. package/dist/core/model-router/intent-classifier.js +1 -156
  288. package/dist/core/model-router/intent-classifier.js.map +1 -1
  289. package/dist/core/model-router-controller.d.ts.map +1 -1
  290. package/dist/core/model-router-controller.js +2 -2
  291. package/dist/core/model-router-controller.js.map +1 -1
  292. package/dist/core/model-selection-controller.d.ts +4 -4
  293. package/dist/core/model-selection-controller.d.ts.map +1 -1
  294. package/dist/core/model-selection-controller.js.map +1 -1
  295. package/dist/core/model-tool-protocol.d.ts +23 -0
  296. package/dist/core/model-tool-protocol.d.ts.map +1 -0
  297. package/dist/core/model-tool-protocol.js +43 -0
  298. package/dist/core/model-tool-protocol.js.map +1 -0
  299. package/dist/core/models/adaptation-store.d.ts +4 -6
  300. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  301. package/dist/core/models/adaptation-store.js +95 -83
  302. package/dist/core/models/adaptation-store.js.map +1 -1
  303. package/dist/core/models/fitness-store.d.ts +2 -12
  304. package/dist/core/models/fitness-store.d.ts.map +1 -1
  305. package/dist/core/models/fitness-store.js +84 -57
  306. package/dist/core/models/fitness-store.js.map +1 -1
  307. package/dist/core/models/host-state-store.d.ts +34 -0
  308. package/dist/core/models/host-state-store.d.ts.map +1 -0
  309. package/dist/core/models/host-state-store.js +86 -0
  310. package/dist/core/models/host-state-store.js.map +1 -0
  311. package/dist/core/orchestration/agent-resume.d.ts +24 -0
  312. package/dist/core/orchestration/agent-resume.d.ts.map +1 -0
  313. package/dist/core/orchestration/agent-resume.js +90 -0
  314. package/dist/core/orchestration/agent-resume.js.map +1 -0
  315. package/dist/core/orchestration/capability-gateway.d.ts +61 -0
  316. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -0
  317. package/dist/core/orchestration/capability-gateway.js +152 -0
  318. package/dist/core/orchestration/capability-gateway.js.map +1 -0
  319. package/dist/core/orchestration/contracts.d.ts +343 -0
  320. package/dist/core/orchestration/contracts.d.ts.map +1 -0
  321. package/dist/core/orchestration/contracts.js +135 -0
  322. package/dist/core/orchestration/contracts.js.map +1 -0
  323. package/dist/core/orchestration/delegation-ledger.d.ts +56 -0
  324. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -0
  325. package/dist/core/orchestration/delegation-ledger.js +197 -0
  326. package/dist/core/orchestration/delegation-ledger.js.map +1 -0
  327. package/dist/core/orchestration/event-store.d.ts +83 -0
  328. package/dist/core/orchestration/event-store.d.ts.map +1 -0
  329. package/dist/core/orchestration/event-store.js +560 -0
  330. package/dist/core/orchestration/event-store.js.map +1 -0
  331. package/dist/core/orchestration/lane-tool-manifests.d.ts +9 -0
  332. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -0
  333. package/dist/core/orchestration/lane-tool-manifests.js +37 -0
  334. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -0
  335. package/dist/core/orchestration/model-binding.d.ts +15 -0
  336. package/dist/core/orchestration/model-binding.d.ts.map +1 -0
  337. package/dist/core/orchestration/model-binding.js +29 -0
  338. package/dist/core/orchestration/model-binding.js.map +1 -0
  339. package/dist/core/orchestration/policy-compiler.d.ts +49 -0
  340. package/dist/core/orchestration/policy-compiler.d.ts.map +1 -0
  341. package/dist/core/orchestration/policy-compiler.js +191 -0
  342. package/dist/core/orchestration/policy-compiler.js.map +1 -0
  343. package/dist/core/orchestration/policy-gate.d.ts +18 -0
  344. package/dist/core/orchestration/policy-gate.d.ts.map +1 -0
  345. package/dist/core/orchestration/policy-gate.js +24 -0
  346. package/dist/core/orchestration/policy-gate.js.map +1 -0
  347. package/dist/core/orchestration/profile-registry.d.ts +51 -0
  348. package/dist/core/orchestration/profile-registry.d.ts.map +1 -0
  349. package/dist/core/orchestration/profile-registry.js +393 -0
  350. package/dist/core/orchestration/profile-registry.js.map +1 -0
  351. package/dist/core/orchestration/profile-store.d.ts +31 -0
  352. package/dist/core/orchestration/profile-store.d.ts.map +1 -0
  353. package/dist/core/orchestration/profile-store.js +136 -0
  354. package/dist/core/orchestration/profile-store.js.map +1 -0
  355. package/dist/core/orchestration/risk-budget.d.ts +9 -0
  356. package/dist/core/orchestration/risk-budget.d.ts.map +1 -0
  357. package/dist/core/orchestration/risk-budget.js +48 -0
  358. package/dist/core/orchestration/risk-budget.js.map +1 -0
  359. package/dist/core/orchestration/task-runtime.d.ts +168 -0
  360. package/dist/core/orchestration/task-runtime.d.ts.map +1 -0
  361. package/dist/core/orchestration/task-runtime.js +1600 -0
  362. package/dist/core/orchestration/task-runtime.js.map +1 -0
  363. package/dist/core/orchestration/work-state-projection.d.ts +32 -0
  364. package/dist/core/orchestration/work-state-projection.d.ts.map +1 -0
  365. package/dist/core/orchestration/work-state-projection.js +116 -0
  366. package/dist/core/orchestration/work-state-projection.js.map +1 -0
  367. package/dist/core/orchestration/worker-execution-contract.d.ts +21 -0
  368. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -0
  369. package/dist/core/orchestration/worker-execution-contract.js +179 -0
  370. package/dist/core/orchestration/worker-execution-contract.js.map +1 -0
  371. package/dist/core/orchestration/worker-result-adapter.d.ts +21 -0
  372. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -0
  373. package/dist/core/orchestration/worker-result-adapter.js +109 -0
  374. package/dist/core/orchestration/worker-result-adapter.js.map +1 -0
  375. package/dist/core/process-identity.d.ts +11 -0
  376. package/dist/core/process-identity.d.ts.map +1 -0
  377. package/dist/core/process-identity.js +26 -0
  378. package/dist/core/process-identity.js.map +1 -0
  379. package/dist/core/process-matrix/codes.d.ts +14 -3
  380. package/dist/core/process-matrix/codes.d.ts.map +1 -1
  381. package/dist/core/process-matrix/codes.js.map +1 -1
  382. package/dist/core/process-matrix/resume-launcher.d.ts +26 -0
  383. package/dist/core/process-matrix/resume-launcher.d.ts.map +1 -0
  384. package/dist/core/process-matrix/resume-launcher.js +62 -0
  385. package/dist/core/process-matrix/resume-launcher.js.map +1 -0
  386. package/dist/core/process-matrix/runtime.d.ts +37 -22
  387. package/dist/core/process-matrix/runtime.d.ts.map +1 -1
  388. package/dist/core/process-matrix/runtime.js +399 -76
  389. package/dist/core/process-matrix/runtime.js.map +1 -1
  390. package/dist/core/process-matrix/store.d.ts +5 -2
  391. package/dist/core/process-matrix/store.d.ts.map +1 -1
  392. package/dist/core/process-matrix/store.js +123 -14
  393. package/dist/core/process-matrix/store.js.map +1 -1
  394. package/dist/core/process-matrix/supervisor.d.ts +10 -7
  395. package/dist/core/process-matrix/supervisor.d.ts.map +1 -1
  396. package/dist/core/process-matrix/supervisor.js +45 -14
  397. package/dist/core/process-matrix/supervisor.js.map +1 -1
  398. package/dist/core/profile-registry.d.ts.map +1 -1
  399. package/dist/core/profile-registry.js +6 -8
  400. package/dist/core/profile-registry.js.map +1 -1
  401. package/dist/core/reflection-controller.d.ts +4 -4
  402. package/dist/core/reflection-controller.d.ts.map +1 -1
  403. package/dist/core/reflection-controller.js +2 -1
  404. package/dist/core/reflection-controller.js.map +1 -1
  405. package/dist/core/reload-blockers.d.ts.map +1 -1
  406. package/dist/core/reload-blockers.js +6 -8
  407. package/dist/core/reload-blockers.js.map +1 -1
  408. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  409. package/dist/core/research/evidence-bundle.js +1 -6
  410. package/dist/core/research/evidence-bundle.js.map +1 -1
  411. package/dist/core/research/lane-model-resolver.d.ts +33 -0
  412. package/dist/core/research/lane-model-resolver.d.ts.map +1 -0
  413. package/dist/core/research/lane-model-resolver.js +65 -0
  414. package/dist/core/research/lane-model-resolver.js.map +1 -0
  415. package/dist/core/research/model-fitness-controller.d.ts +38 -0
  416. package/dist/core/research/model-fitness-controller.d.ts.map +1 -0
  417. package/dist/core/research/model-fitness-controller.js +106 -0
  418. package/dist/core/research/model-fitness-controller.js.map +1 -0
  419. package/dist/core/research/model-fitness.d.ts +1 -1
  420. package/dist/core/research/model-fitness.d.ts.map +1 -1
  421. package/dist/core/research/model-fitness.js +4 -4
  422. package/dist/core/research/model-fitness.js.map +1 -1
  423. package/dist/core/research/research-gate.d.ts.map +1 -1
  424. package/dist/core/research/research-gate.js +7 -7
  425. package/dist/core/research/research-gate.js.map +1 -1
  426. package/dist/core/research/research-lane-controller.d.ts +62 -0
  427. package/dist/core/research/research-lane-controller.d.ts.map +1 -0
  428. package/dist/core/research/research-lane-controller.js +292 -0
  429. package/dist/core/research/research-lane-controller.js.map +1 -0
  430. package/dist/core/research/session-evidence-bundle.d.ts +2 -4
  431. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  432. package/dist/core/research/session-evidence-bundle.js +10 -46
  433. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  434. package/dist/core/resource-loader.d.ts.map +1 -1
  435. package/dist/core/resource-loader.js +7 -2
  436. package/dist/core/resource-loader.js.map +1 -1
  437. package/dist/core/risk-classifier.d.ts +6 -0
  438. package/dist/core/risk-classifier.d.ts.map +1 -0
  439. package/dist/core/risk-classifier.js +225 -0
  440. package/dist/core/risk-classifier.js.map +1 -0
  441. package/dist/core/runtime-builder.d.ts +26 -20
  442. package/dist/core/runtime-builder.d.ts.map +1 -1
  443. package/dist/core/runtime-builder.js +237 -178
  444. package/dist/core/runtime-builder.js.map +1 -1
  445. package/dist/core/sdk.d.ts +6 -3
  446. package/dist/core/sdk.d.ts.map +1 -1
  447. package/dist/core/sdk.js +61 -24
  448. package/dist/core/sdk.js.map +1 -1
  449. package/dist/core/session-analytics.d.ts.map +1 -1
  450. package/dist/core/session-analytics.js +28 -1
  451. package/dist/core/session-analytics.js.map +1 -1
  452. package/dist/core/session-image-store.d.ts +33 -0
  453. package/dist/core/session-image-store.d.ts.map +1 -0
  454. package/dist/core/session-image-store.js +283 -0
  455. package/dist/core/session-image-store.js.map +1 -0
  456. package/dist/core/session-role.d.ts +6 -4
  457. package/dist/core/session-role.d.ts.map +1 -1
  458. package/dist/core/session-role.js +10 -4
  459. package/dist/core/session-role.js.map +1 -1
  460. package/dist/core/session-snapshot.d.ts +34 -0
  461. package/dist/core/session-snapshot.d.ts.map +1 -0
  462. package/dist/core/session-snapshot.js +68 -0
  463. package/dist/core/session-snapshot.js.map +1 -0
  464. package/dist/core/session-supervision-runtime.d.ts +31 -0
  465. package/dist/core/session-supervision-runtime.d.ts.map +1 -0
  466. package/dist/core/session-supervision-runtime.js +97 -0
  467. package/dist/core/session-supervision-runtime.js.map +1 -0
  468. package/dist/core/settings-manager.d.ts +9 -4
  469. package/dist/core/settings-manager.d.ts.map +1 -1
  470. package/dist/core/settings-manager.js +80 -28
  471. package/dist/core/settings-manager.js.map +1 -1
  472. package/dist/core/tasks/session-task-state.d.ts +2 -4
  473. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  474. package/dist/core/tasks/session-task-state.js +10 -28
  475. package/dist/core/tasks/session-task-state.js.map +1 -1
  476. package/dist/core/tasks/task-projection.d.ts +20 -0
  477. package/dist/core/tasks/task-projection.d.ts.map +1 -0
  478. package/dist/core/tasks/task-projection.js +47 -0
  479. package/dist/core/tasks/task-projection.js.map +1 -0
  480. package/dist/core/tasks/task-state.d.ts +4 -0
  481. package/dist/core/tasks/task-state.d.ts.map +1 -1
  482. package/dist/core/tasks/task-state.js +28 -11
  483. package/dist/core/tasks/task-state.js.map +1 -1
  484. package/dist/core/tool-capability-policy.d.ts +13 -0
  485. package/dist/core/tool-capability-policy.d.ts.map +1 -0
  486. package/dist/core/tool-capability-policy.js +85 -0
  487. package/dist/core/tool-capability-policy.js.map +1 -0
  488. package/dist/core/tool-protocol-controller.d.ts +91 -0
  489. package/dist/core/tool-protocol-controller.d.ts.map +1 -0
  490. package/dist/core/tool-protocol-controller.js +512 -0
  491. package/dist/core/tool-protocol-controller.js.map +1 -0
  492. package/dist/core/tool-recovery-log-records.d.ts +2 -3
  493. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  494. package/dist/core/tool-recovery-log-records.js +9 -35
  495. package/dist/core/tool-recovery-log-records.js.map +1 -1
  496. package/dist/core/tool-selection/tool-performance-store.d.ts +3 -7
  497. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  498. package/dist/core/tool-selection/tool-performance-store.js +97 -138
  499. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  500. package/dist/core/tools/ask-question.d.ts +124 -0
  501. package/dist/core/tools/ask-question.d.ts.map +1 -0
  502. package/dist/core/tools/ask-question.js +693 -0
  503. package/dist/core/tools/ask-question.js.map +1 -0
  504. package/dist/core/tools/delegate-status.d.ts +31 -3
  505. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  506. package/dist/core/tools/delegate-status.js +149 -29
  507. package/dist/core/tools/delegate-status.js.map +1 -1
  508. package/dist/core/tools/delegate.d.ts +15 -13
  509. package/dist/core/tools/delegate.d.ts.map +1 -1
  510. package/dist/core/tools/delegate.js +108 -23
  511. package/dist/core/tools/delegate.js.map +1 -1
  512. package/dist/core/tools/extensionify.d.ts.map +1 -1
  513. package/dist/core/tools/extensionify.js +1 -0
  514. package/dist/core/tools/extensionify.js.map +1 -1
  515. package/dist/core/tools/goal.d.ts +5 -5
  516. package/dist/core/tools/goal.d.ts.map +1 -1
  517. package/dist/core/tools/goal.js +10 -10
  518. package/dist/core/tools/goal.js.map +1 -1
  519. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  520. package/dist/core/tools/model-fitness.js +2 -2
  521. package/dist/core/tools/model-fitness.js.map +1 -1
  522. package/dist/core/tools/orchestration-panel.d.ts +43 -0
  523. package/dist/core/tools/orchestration-panel.d.ts.map +1 -0
  524. package/dist/core/tools/orchestration-panel.js +168 -0
  525. package/dist/core/tools/orchestration-panel.js.map +1 -0
  526. package/dist/core/tools/run-process.d.ts +26 -0
  527. package/dist/core/tools/run-process.d.ts.map +1 -0
  528. package/dist/core/tools/run-process.js +138 -0
  529. package/dist/core/tools/run-process.js.map +1 -0
  530. package/dist/core/tools/task-steps.d.ts +2 -0
  531. package/dist/core/tools/task-steps.d.ts.map +1 -1
  532. package/dist/core/tools/task-steps.js +102 -0
  533. package/dist/core/tools/task-steps.js.map +1 -1
  534. package/dist/core/tools/tmux-dispatch.d.ts +3 -5
  535. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -1
  536. package/dist/core/tools/tmux-dispatch.js +4 -4
  537. package/dist/core/tools/tmux-dispatch.js.map +1 -1
  538. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -1
  539. package/dist/core/tools/windows-shell-engine.js +4 -6
  540. package/dist/core/tools/windows-shell-engine.js.map +1 -1
  541. package/dist/core/util/bounded-jsonl.d.ts +10 -0
  542. package/dist/core/util/bounded-jsonl.d.ts.map +1 -0
  543. package/dist/core/util/bounded-jsonl.js +75 -0
  544. package/dist/core/util/bounded-jsonl.js.map +1 -0
  545. package/dist/core/util/value-guards.d.ts +5 -0
  546. package/dist/core/util/value-guards.d.ts.map +1 -0
  547. package/dist/core/util/value-guards.js +12 -0
  548. package/dist/core/util/value-guards.js.map +1 -0
  549. package/dist/core/worktree-sync/store.d.ts +3 -3
  550. package/dist/core/worktree-sync/store.d.ts.map +1 -1
  551. package/dist/core/worktree-sync/store.js +10 -5
  552. package/dist/core/worktree-sync/store.js.map +1 -1
  553. package/dist/index.d.ts +13 -3
  554. package/dist/index.d.ts.map +1 -1
  555. package/dist/index.js +12 -2
  556. package/dist/index.js.map +1 -1
  557. package/dist/main.d.ts.map +1 -1
  558. package/dist/main.js +128 -44
  559. package/dist/main.js.map +1 -1
  560. package/dist/migrations.d.ts +4 -6
  561. package/dist/migrations.d.ts.map +1 -1
  562. package/dist/migrations.js +186 -28
  563. package/dist/migrations.js.map +1 -1
  564. package/dist/modes/interactive/clipboard-input.d.ts +7 -1
  565. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -1
  566. package/dist/modes/interactive/clipboard-input.js +44 -6
  567. package/dist/modes/interactive/clipboard-input.js.map +1 -1
  568. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  569. package/dist/modes/interactive/components/settings-selector.js +2 -2
  570. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  571. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  572. package/dist/modes/interactive/config-backup.js +5 -4
  573. package/dist/modes/interactive/config-backup.js.map +1 -1
  574. package/dist/modes/interactive/extension-ui-host.d.ts +14 -0
  575. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  576. package/dist/modes/interactive/extension-ui-host.js +76 -15
  577. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  578. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  579. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  580. package/dist/modes/interactive/interactive-mode.js +63 -1
  581. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  582. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  583. package/dist/modes/interactive/key-handlers.js +1 -1
  584. package/dist/modes/interactive/key-handlers.js.map +1 -1
  585. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  586. package/dist/modes/interactive/profile-menu-controller.js +7 -7
  587. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  588. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  589. package/dist/modes/interactive/report-commands.js +21 -0
  590. package/dist/modes/interactive/report-commands.js.map +1 -1
  591. package/dist/modes/interactive/session-flow-commands.d.ts +17 -2
  592. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  593. package/dist/modes/interactive/session-flow-commands.js +50 -17
  594. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  595. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  596. package/dist/modes/interactive/session-io-commands.js +16 -6
  597. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  598. package/dist/modes/interactive/session-replacement-errors.d.ts +7 -0
  599. package/dist/modes/interactive/session-replacement-errors.d.ts.map +1 -0
  600. package/dist/modes/interactive/session-replacement-errors.js +15 -0
  601. package/dist/modes/interactive/session-replacement-errors.js.map +1 -0
  602. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  603. package/dist/modes/interactive/startup-checks.js +1 -1
  604. package/dist/modes/interactive/startup-checks.js.map +1 -1
  605. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  606. package/dist/modes/rpc/rpc-mode.js +82 -23
  607. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  608. package/dist/modes/rpc/rpc-types.d.ts +15 -0
  609. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  610. package/dist/modes/rpc/rpc-types.js +138 -1
  611. package/dist/modes/rpc/rpc-types.js.map +1 -1
  612. package/dist/utils/clipboard-image.d.ts.map +1 -1
  613. package/dist/utils/clipboard-image.js +2 -13
  614. package/dist/utils/clipboard-image.js.map +1 -1
  615. package/dist/utils/clipboard-native.d.ts +1 -0
  616. package/dist/utils/clipboard-native.d.ts.map +1 -1
  617. package/dist/utils/clipboard-native.js.map +1 -1
  618. package/dist/utils/clipboard.d.ts +1 -0
  619. package/dist/utils/clipboard.d.ts.map +1 -1
  620. package/dist/utils/clipboard.js +52 -1
  621. package/dist/utils/clipboard.js.map +1 -1
  622. package/dist/utils/platform.d.ts +3 -0
  623. package/dist/utils/platform.d.ts.map +1 -0
  624. package/dist/utils/platform.js +8 -0
  625. package/dist/utils/platform.js.map +1 -0
  626. package/dist/utils/work-directory.d.ts +11 -0
  627. package/dist/utils/work-directory.d.ts.map +1 -1
  628. package/dist/utils/work-directory.js +30 -9
  629. package/dist/utils/work-directory.js.map +1 -1
  630. package/docs/UI_POLISH_ORCHESTRATION.md +44 -0
  631. package/docs/UI_QUALITY_ORCHESTRATION.md +30 -0
  632. package/docs/docs.json +4 -0
  633. package/docs/extensions.md +62 -2
  634. package/docs/harness-architecture.md +156 -0
  635. package/docs/keybindings.md +11 -1
  636. package/docs/process-matrix.md +59 -26
  637. package/docs/rpc.md +45 -2
  638. package/docs/sessions.md +15 -0
  639. package/docs/settings.md +125 -11
  640. package/docs/task-steps.md +8 -0
  641. package/docs/telemetry.md +19 -0
  642. package/docs/tmux-agent-manager.md +10 -3
  643. package/docs/ui-reference/README.md +9 -0
  644. package/docs/ui-reference/orchestration-after.txt +7 -0
  645. package/docs/ui-reference/orchestration-before.txt +7 -0
  646. package/docs/ui-reference/orchestration-reference.txt +6 -0
  647. package/docs/usage.md +14 -2
  648. package/docs/work-directory.md +19 -8
  649. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  650. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  651. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  652. package/examples/extensions/question.ts +34 -245
  653. package/examples/extensions/questionnaire.ts +41 -407
  654. package/examples/extensions/sandbox/package-lock.json +2 -2
  655. package/examples/extensions/sandbox/package.json +1 -1
  656. package/examples/extensions/with-deps/package-lock.json +2 -2
  657. package/examples/extensions/with-deps/package.json +1 -1
  658. package/npm-shrinkwrap.json +12 -12
  659. package/package.json +4 -4
  660. package/dist/core/delegation/session-worker-result.d.ts +0 -46
  661. package/dist/core/delegation/session-worker-result.d.ts.map +0 -1
  662. package/dist/core/delegation/session-worker-result.js +0 -113
  663. package/dist/core/delegation/session-worker-result.js.map +0 -1
  664. package/dist/core/delegation/worker-result.d.ts.map +0 -1
  665. package/dist/core/delegation/worker-result.js.map +0 -1
  666. package/dist/core/telemetry.d.ts.map +0 -1
  667. package/dist/core/telemetry.js.map +0 -1
@@ -1,13 +1,10 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, join } from "node:path";
4
- import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
5
- import { calculateContextTokens, compact, createDeterministicCompaction, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, runCompactionLoop, shouldCompact, } from "@caupulican/pi-agent-core/node";
6
- import { cleanupSessionResources, formatToolRepairStandingRule, formatVariantEnvelope, generateTextToolProtocolPrimer, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, parseTextToolCalls, streamSimple, } from "@caupulican/pi-ai";
7
- import { Type } from "typebox";
2
+ import { readFileSync } from "node:fs";
3
+ import { basename, dirname } from "node:path";
4
+ import { compactToolResultDetailsForRetention, createCustomMessage, DEFAULT_STREAM_IDLE, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
5
+ import { cleanupSessionResources, getSupportedThinkingLevels, modelsAreEqual, streamSimple } from "@caupulican/pi-ai";
8
6
  import { getAgentDir } from "../config.js";
9
7
  import { stripFrontmatter } from "../utils/frontmatter.js";
10
- import { getProcessWorkRun } from "../utils/work-directory.js";
11
8
  import { resourceDir, stateFile } from "./agent-paths.js";
12
9
  import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
13
10
  import { buildForegroundEnvelope, formatForegroundEnvelopeObservation } from "./autonomy/foreground-envelope.js";
@@ -15,19 +12,21 @@ import { evaluateToolGate } from "./autonomy/gates.js";
15
12
  import { AutonomyTelemetry } from "./autonomy-telemetry.js";
16
13
  import { BackgroundLaneController } from "./background-lane-controller.js";
17
14
  import { BashExecutionController } from "./bash-execution-controller.js";
18
- import { BillingFailoverController, ExhaustedProviderRegistry } from "./billing-failover-controller.js";
15
+ import { CompactionController } from "./compaction-controller.js";
19
16
  import { CompactionSupport } from "./compaction-support.js";
20
17
  import { buildContextCompositionReport, formatContextCompositionDashboard, } from "./context/context-composition.js";
21
18
  import { ContextPipeline } from "./context-pipeline.js";
22
19
  import { downgradeReasoning, estimateTurnCostUsd, evaluateCostGuard } from "./cost-guard.js";
23
- import { appendWorkerResultSnapshot, getWorkerResultSnapshots } from "./delegation/session-worker-result.js";
20
+ import { appendWorkerClaimSnapshot, getWorkerClaimSnapshots } from "./delegation/session-worker-claim.js";
24
21
  import { FailureCorpusRecorder } from "./failure-corpus.js";
22
+ import { ForegroundRecoveryController } from "./foreground-recovery-controller.js";
25
23
  import { GatewayRegistry } from "./gateways/channel-provider.js";
26
24
  import { GoalLoopController } from "./goal-loop-controller.js";
27
25
  import { buildGoalRuntimeSnapshot, } from "./goals/goal-runtime-snapshot.js";
28
26
  import { applyGoalEvent } from "./goals/goal-state.js";
29
27
  import { appendGoalStateSnapshot, getLatestGoalStateSnapshot } from "./goals/session-goal-state.js";
30
28
  import { constrainStreamIdleToHttpTimeout } from "./http-dispatcher.js";
29
+ import { beginHumanInputRequest, createHumanInputRequest, formatHumanInputAnswerText, getLatestHumanInputSnapshots, getResumableHumanInputSnapshot, getWorkerHumanInputsRequiringDelivery, HUMAN_INPUT_WORKER_RESPONSE_CUSTOM_TYPE, resolveHumanInput, } from "./human-input.js";
31
30
  import { appendLearningDecisionSnapshot, getLearningDecisionSnapshots } from "./learning/session-learning-decision.js";
32
31
  import { isPromotedFrontmatter, SkillCurator } from "./learning/skill-curator.js";
33
32
  import { LocalRuntimeController } from "./local-runtime-controller.js";
@@ -36,9 +35,11 @@ import { deriveModelCapabilityProfile, evaluateLaneWorkerRefusal, filterToolName
36
35
  import { isLocalOrManagedRouterModel } from "./model-router/tool-escalation.js";
37
36
  import { formatModelRouterModel, ModelRouterController } from "./model-router-controller.js";
38
37
  import { ModelSelectionController } from "./model-selection-controller.js";
39
- import { ModelAdaptationStore, } from "./models/adaptation-store.js";
38
+ import { ModelAdaptationStore } from "./models/adaptation-store.js";
40
39
  import { HF_TRANSFORMERS_PROVIDER, OLLAMA_PROVIDER } from "./models/local-registration.js";
41
40
  import { DEFAULT_ADAPTIVE_STREAM_IDLE_CEILING_MS, estimateContextPromptTokens, resolveAdaptiveStreamIdleOptions, withModelPerfProfile, } from "./models/perf-profile.js";
41
+ import { resolveConfiguredOrchestrationModel } from "./orchestration/model-binding.js";
42
+ import { validateOrchestrationProfile } from "./orchestration/profile-registry.js";
42
43
  import { ProfileFilterController } from "./profile-filter-controller.js";
43
44
  import { expandPromptTemplate } from "./prompt-templates.js";
44
45
  import { ReflectionController } from "./reflection-controller.js";
@@ -47,15 +48,16 @@ import { collectWorkspaceSources } from "./research/workspace-collector.js";
47
48
  import { stripResourceProfileBlocks } from "./resource-profile-blocks.js";
48
49
  import { RuntimeBuilder } from "./runtime-builder.js";
49
50
  import { SessionAnalytics } from "./session-analytics.js";
51
+ import { SessionImageStore } from "./session-image-store.js";
52
+ import { getActiveSessionBranchEntries } from "./session-snapshot.js";
50
53
  import { SessionTreeNavigator } from "./session-tree-navigator.js";
51
54
  import { SystemPromptBuilder } from "./system-prompt-builder.js";
52
55
  import { appendTaskStepsStateSnapshot, getLatestTaskStepsStateSnapshot } from "./tasks/session-task-state.js";
53
56
  import { formatTaskStepsContext } from "./tasks/task-state.js";
54
57
  import { ToolGateController } from "./tool-gate-controller.js";
58
+ import { ToolProtocolController } from "./tool-protocol-controller.js";
55
59
  import { TOOL_RECOVERY_EVENT_LOG_FILE } from "./tool-recovery-log-records.js";
56
60
  import { ToolRecoveryLogger } from "./tool-recovery-logger.js";
57
- import { formatToolRepairHealthReport } from "./tool-repair-health.js";
58
- import { resolveCurrentToolRepairSettings } from "./tool-repair-settings.js";
59
61
  import { ToolPerformanceStore } from "./tool-selection/tool-performance-store.js";
60
62
  import { formatToolSelectionReport, ToolSelectionController } from "./tool-selection/tool-selection-controller.js";
61
63
  import { disposePersistentShellSession } from "./tools/shell-session.js";
@@ -72,39 +74,6 @@ import { disposePersistentShellSession } from "./tools/shell-session.js";
72
74
  * key stable regardless of how many times this module is evaluated.
73
75
  */
74
76
  const RAW_STREAM_MARKER = Symbol.for("pi.rawStreamSimple");
75
- const MODEL_ADAPTATION_REPAIR_THRESHOLD = 3;
76
- const TEXT_TOOL_PROTOCOL_VERSION = 1;
77
- const TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT = 2;
78
- const TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD = 3;
79
- /** How often the one-line envelope-format corrective steer (see
80
- * {@link AgentSession._maybeInjectTextProtocolCorrectiveSteer}) re-fires after the first parse
81
- * failure this session — every Nth failure thereafter, not every single one, so a model that keeps
82
- * missing the envelope doesn't get the reminder spliced into every turn (the breaker's same-signature
83
- * counter is what actually demotes it, at {@link TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD}). */
84
- const TEXT_TOOL_PROTOCOL_STEER_INTERVAL = 5;
85
- /** Anti-loop cooldown: the evidence-gated auto-probe (see {@link
86
- * AgentSession._maybeAutoProbeOnValidationEscalation}) skips a model whose persisted `toolProbe`
87
- * verdict was written within this window, so a burst of validation-escalation events for a still-
88
- * failing model — or a very recent explicit `/toolprobe` run, or a future circuit-breaker demote — does
89
- * not re-fire the (multi-completion) probe every time. Independent of, and complementary to, the
90
- * per-session `_autoProbedModels` latch. */
91
- const AUTO_TOOL_PROBE_FRESHNESS_MS = 15 * 60 * 1000;
92
- const TEXT_TOOL_PROTOCOL_VARIANTS = [
93
- "tool-tag",
94
- "tool-call",
95
- "fenced-json",
96
- "function-xml",
97
- ];
98
- const TEXT_TOOL_PROTOCOL_ECHO_TOOL = {
99
- name: "echo",
100
- description: "Echo calibration data",
101
- parameters: Type.Object({ data: Type.String() }),
102
- };
103
- const NATIVE_TOOL_PROBE_READ_TOOL = {
104
- name: "read",
105
- description: "Read file contents",
106
- parameters: Type.Object({ path: Type.String() }),
107
- };
108
77
  /** Test-only override of the stream-idle bounds. Read per-request by the wiring's resolver. */
109
78
  let streamIdleOptionsOverride;
110
79
  /**
@@ -199,17 +168,16 @@ export class AgentSession {
199
168
  _queuedExtensionCommands = [];
200
169
  _pendingNextTurnMessages = [];
201
170
  _streamingPromptSubmissionTail = Promise.resolve();
171
+ /** Serializes owner-input overlays so concurrent worker terminals cannot replace each other. */
172
+ _humanInputTail = Promise.resolve();
173
+ _activeHumanInputRequestIds = new Set();
202
174
  /**
203
175
  * The last tool set requested via setActiveToolsByName BEFORE model-capability filtering, so
204
176
  * switching from a small-window model back to a large one restores the full requested set.
205
177
  */
206
178
  _requestedActiveToolNames;
207
- _compactionAbortController = undefined;
208
- _autoCompactionAbortController = undefined;
209
- _overflowRecoveryAttempted = false;
210
179
  _unboundToolGrantWarnings = [];
211
180
  _branchSummaryAbortController = undefined;
212
- _retryController;
213
181
  _modelSelection;
214
182
  _bash;
215
183
  _profileFilter;
@@ -226,31 +194,16 @@ export class AgentSession {
226
194
  _agentDir;
227
195
  _collectWorkspaceSources;
228
196
  _localRuntimeController;
229
- _modelAdaptationStore;
197
+ _toolProtocol;
230
198
  _prefixWarmer;
231
199
  _completedPrefixWarms = new Set();
232
- _repairModeSessionCounts = new Map();
233
- _textProtocolParseFailures = new Map();
234
- _textProtocolParseObservedThisTurn = false;
235
- /** Total text-protocol parse-failure events observed this session, backing the
236
- * {@link TEXT_TOOL_PROTOCOL_STEER_INTERVAL} throttle on {@link _maybeInjectTextProtocolCorrectiveSteer}. */
237
- _textProtocolCorrectiveSteerCount = 0;
238
- /** Monotonic counter backing tool-probe/text-protocol-calibration spawned-usage reportIds
239
- * (see {@link _nextProbeUsageReportId}); guarantees each probe/calibration completion in the
240
- * session lands in the cost ledger exactly once, even across repeated /toolprobe invocations. */
241
- _toolProbeUsageReportSeq = 0;
242
- /** Anti-loop: modelKeys the evidence-gated auto-probe has already fired for THIS session (see
243
- * {@link _maybeAutoProbeOnValidationEscalation}), so a burst of validation-escalation events for
244
- * the same still-failing model never re-fires the probe more than once per session. */
245
- _autoProbedModels = new Set();
246
- _textProtocolValidationOutcomeThisTurn;
247
200
  /** Assembles the session's base system prompt from live session state (see
248
201
  * system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
249
202
  _systemPromptBuilder;
250
203
  /** Autonomy telemetry sink + status/diagnostic snapshots (see autonomy-telemetry.ts); owns
251
204
  * the latest gate outcome and the bounded gate-outcome history. */
252
205
  _autonomyTelemetry;
253
- /** Goal auto-continue + research lane + scout-worker delegation + model-fitness probe (see
206
+ /** Goal auto-continue + research lane + profile-bound worker delegation + model-fitness probe (see
254
207
  * background-lane-controller.ts); owns the lane timers/guards, the last research-lane skip
255
208
  * reason, the live LaneTracker, and the in-flight research/worker abort controllers. */
256
209
  _backgroundLanes;
@@ -259,6 +212,7 @@ export class AgentSession {
259
212
  * effectiveness tracker, and the extension-contributed pending providers. */
260
213
  _memory;
261
214
  _compactionSupport;
215
+ _compaction;
262
216
  /** Per-turn context-shaping subsystem (see context-pipeline.ts); owns the latest
263
217
  * audit/policy/correlation/enforcement/gc reports, the brain-curation sidecar + its skip reasons,
264
218
  * and the tool-output artifact store. Invoked stage-by-stage from the context transform. */
@@ -288,12 +242,13 @@ export class AgentSession {
288
242
  * used by the status report. Its parallel routed drive path delegates every turn back to
289
243
  * {@link _runAgentPrompt} so the drive loop stays host-side. */
290
244
  _modelRouter;
291
- _billingFailover;
245
+ _foregroundRecovery;
292
246
  _failureCorpus;
293
247
  _toolRecoveryLogger;
294
248
  _toolRecoveryEventLogPath;
295
249
  _skillCuratorInstance;
296
250
  _disposed = false;
251
+ _disposeCompletion;
297
252
  _reflectionAbort = new AbortController();
298
253
  /** Native reflection engine + learning-apply/rollback path (see reflection-controller.ts); owns no
299
254
  * session state, applies durable writes through the bundled memory tool and the session log. */
@@ -323,7 +278,7 @@ export class AgentSession {
323
278
  this.agent = config.agent;
324
279
  // Bound every provider stream this session starts against a silently dead connection: a
325
280
  // stall aborts the inner request and surfaces as a retryable "stream stalled" error, which
326
- // _isRetryableError routes into the existing auto-retry path. Wrapped exactly once, here.
281
+ // ForegroundRecoveryController routes into the existing auto-retry path. Wrapped exactly once, here.
327
282
  // The wrapper reports the stall immediately and aborts the inner request; releasing the
328
283
  // inner pump relies on the provider ending its stream after abort (real providers do — see
329
284
  // withStreamIdleWatchdog's contract), so no extra drain is added at this wiring site.
@@ -367,31 +322,46 @@ export class AgentSession {
367
322
  }), baseStreamFn === streamSimple);
368
323
  this.sessionManager = config.sessionManager;
369
324
  this.settingsManager = config.settingsManager;
370
- // Auto-retry rides the reliability kernel: the controller owns the attempt counter and the
371
- // abortable backoff. This session maps its retry settings onto a RetryPolicy, bridges the
372
- // controller's events onto the session event stream, and supplies the live context window so
373
- // the overflow-aware classifier can route overflow to compaction instead of a pointless retry.
374
- this._retryController = new RetryController(this.agent, () => {
375
- const retry = this.settingsManager.getRetrySettings();
376
- return {
377
- enabled: retry.enabled,
378
- maxAttempts: retry.maxRetries,
379
- baseDelayMs: retry.baseDelayMs,
380
- maxDelayMs: DEFAULT_RETRY_POLICY.maxDelayMs,
381
- jitterRatio: 0,
382
- };
383
- }, {
384
- onRetryStart: (info) => this._emit({ type: "auto_retry_start", ...info }),
385
- onRetryEnd: (info) => this._emit({ type: "auto_retry_end", ...info }),
386
- }, () => this.model?.contextWindow ?? 0);
387
- this._scopedModels = config.scopedModels ?? [];
325
+ if (config.orchestrationProfile) {
326
+ validateOrchestrationProfile(config.orchestrationProfile);
327
+ const resolved = resolveConfiguredOrchestrationModel(config.orchestrationProfile, config.modelRegistry);
328
+ if (!resolved) {
329
+ throw new TypeError(`Orchestration profile '${config.orchestrationProfile.profileId}' has no configured, authenticated model that supports its exact thinking level.`);
330
+ }
331
+ if (!this.agent.state.model || !modelsAreEqual(this.agent.state.model, resolved.model)) {
332
+ this.sessionManager.appendModelChange(resolved.model.provider, resolved.model.id);
333
+ }
334
+ if (this.agent.state.thinkingLevel !== resolved.binding.thinkingLevel) {
335
+ this.sessionManager.appendThinkingLevelChange(resolved.binding.thinkingLevel);
336
+ }
337
+ this.agent.state.model = resolved.model;
338
+ this.agent.state.thinkingLevel = resolved.binding.thinkingLevel;
339
+ this.settingsManager.setRuntimeResourceProfiles([...config.orchestrationProfile.resourceProfileNames]);
340
+ }
341
+ this._scopedModels = config.orchestrationProfile
342
+ ? [{ model: this.agent.state.model, thinkingLevel: this.agent.state.thinkingLevel }]
343
+ : (config.scopedModels ?? []);
388
344
  this._resourceLoader = config.resourceLoader;
389
345
  this._customTools = config.customTools ?? [];
390
346
  this._cwd = config.cwd;
391
347
  this._agentDir = agentDir;
392
- this._modelAdaptationStore = modelAdaptationStore;
393
- this.agent.onTextToolProtocolParse = (event) => this._handleTextToolProtocolParse(event);
394
- this._applyToolRepairLayerSettings();
348
+ this._toolProtocol = new ToolProtocolController({
349
+ agent: this.agent,
350
+ agentDir: this._agentDir,
351
+ settingsManager: this.settingsManager,
352
+ getModelRegistry: () => this._modelRegistry,
353
+ adaptationStore: modelAdaptationStore,
354
+ isRawStreamSimple: (fn) => this._isRawStreamSimple(fn),
355
+ getRequiredRequestAuth: (model) => this._getRequiredRequestAuth(model),
356
+ addSpawnedUsage: (usage, opts) => this.addSpawnedUsage(usage, opts),
357
+ emitWarning: (message) => this._emit({ type: "warning", message }),
358
+ sendCorrectiveSteer: (message) => this.sendCustomMessage({ customType: "text-protocol-corrective-steer", content: message, display: false }, { deliverAs: "nextTurn" }),
359
+ findLastAssistantMessage: () => this._findLastAssistantMessage(),
360
+ isDisposed: () => this._disposed,
361
+ probeForAuto: (model) => this._probeToolCallingForModel(model),
362
+ });
363
+ this.agent.onTextToolProtocolParse = (event) => this._toolProtocol.handleTextProtocolParse(event);
364
+ this._toolProtocol.applyRepairLayerSettings();
395
365
  this._collectWorkspaceSources = config.collectWorkspaceSources ?? collectWorkspaceSources;
396
366
  this._localRuntimeController = new LocalRuntimeController({
397
367
  agentDir: this._agentDir,
@@ -410,7 +380,7 @@ export class AgentSession {
410
380
  hasTool: (name) => this._runtimeBuilder.hasTool(name),
411
381
  getToolPromptSnippet: (name) => this._runtimeBuilder.getToolPromptSnippet(name),
412
382
  getToolPromptGuidelines: (name) => this._runtimeBuilder.getToolPromptGuidelines(name),
413
- getModelAdaptationRules: () => this._getModelAdaptationRulesForPrompt(),
383
+ getModelAdaptationRules: () => this._toolProtocol.getAdaptationRulesForPrompt(),
414
384
  getActiveExtensions: () => this._extensionRunner.activeExtensions,
415
385
  getContextWindow: () => this.model?.contextWindow,
416
386
  getThinkingLevel: () => this.thinkingLevel,
@@ -425,9 +395,9 @@ export class AgentSession {
425
395
  getCostSummary: () => this.getCostSummary(),
426
396
  getGoalStateSnapshot: () => this.getGoalStateSnapshot(),
427
397
  getActiveLaneCount: () => this._backgroundLanes.getActiveLaneCount(),
428
- getEvidenceBundleSnapshots: () => this.getEvidenceBundleSnapshots(),
429
- getWorkerResultSnapshots: () => this.getWorkerResultSnapshots(),
430
- getLearningDecisionSnapshots: () => this.getLearningDecisionSnapshots(),
398
+ getSessionEvidenceBundleHistory: () => getEvidenceBundleSnapshots(this.sessionManager.getEntries()),
399
+ getSessionWorkerClaimHistory: () => getWorkerClaimSnapshots(this.sessionManager.getEntries()),
400
+ getSessionLearningDecisionHistory: () => getLearningDecisionSnapshots(this.sessionManager.getEntries()),
431
401
  getLearningAuditRecords: () => this.getLearningAuditRecords(),
432
402
  });
433
403
  this._modelRegistry = config.modelRegistry;
@@ -439,8 +409,9 @@ export class AgentSession {
439
409
  getAgentDir: () => this._agentDir,
440
410
  getSessionManager: () => this.sessionManager,
441
411
  getSettingsManager: () => this.settingsManager,
412
+ getActiveOrchestrationProfile: () => config.orchestrationProfile,
442
413
  getModelRegistry: () => this._modelRegistry,
443
- isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
414
+ isModelExhausted: (model) => this._foregroundRecovery.isModelExhausted(`${model.provider}/${model.id}`),
444
415
  getModel: () => this.model ?? undefined,
445
416
  isDelegateToolActive: () => this.getActiveToolNames().includes("delegate"),
446
417
  isGoalToolActive: () => this.getActiveToolNames().includes("goal"),
@@ -453,7 +424,8 @@ export class AgentSession {
453
424
  getGoalRuntimeSnapshot: (settings) => this.getGoalRuntimeSnapshot(settings),
454
425
  getEvidenceBundleSnapshot: () => this.getEvidenceBundleSnapshot(),
455
426
  saveEvidenceBundleSnapshot: (bundle) => this.saveEvidenceBundleSnapshot(bundle),
456
- saveWorkerResultSnapshot: (result, request) => this.saveWorkerResultSnapshot(result, request),
427
+ saveWorkerClaimSnapshot: (claim, request) => this.saveWorkerClaimSnapshot(claim, request),
428
+ queueWorkerHumanInput: (request) => this._queueWorkerHumanInput(request),
457
429
  readMemoryForLane: (query) => this._memory.readMemoryForLane(query),
458
430
  addSpawnedUsage: (usage, opts) => this.addSpawnedUsage(usage, opts),
459
431
  runIsolatedCompletion: (opts) => this.runIsolatedCompletion(opts),
@@ -480,7 +452,7 @@ export class AgentSession {
480
452
  getModelRegistry: () => this._modelRegistry,
481
453
  isRawStream: () => this._isRawStreamSimple(this.agent.streamFn),
482
454
  getRequiredRequestAuth: (model) => this._getRequiredRequestAuth(model),
483
- isModelExhausted: (ref) => this._billingFailover.isExhausted(ref),
455
+ isModelExhausted: (ref) => this._foregroundRecovery.isModelExhausted(ref),
484
456
  getStoredFitnessReport: (ref) => this.getStoredFitnessReports().find((entry) => entry.model === ref)?.report,
485
457
  // Live context is an over-estimate of the span to summarize (includes the kept tail) —
486
458
  // conservative in the safe direction for the summarizer capacity check.
@@ -507,23 +479,51 @@ export class AgentSession {
507
479
  });
508
480
  const failureCorpusPath = stateFile(this._agentDir, "failure-corpus.jsonl");
509
481
  this._toolRecoveryEventLogPath = stateFile(this._agentDir, TOOL_RECOVERY_EVENT_LOG_FILE);
510
- const toolRepairSettings = this._toolRepairSettings();
482
+ const toolRepairSettings = this._toolProtocol.getRepairSettings();
511
483
  this._failureCorpus = new FailureCorpusRecorder({
512
484
  filePath: failureCorpusPath,
513
485
  });
486
+ this._compaction = new CompactionController({
487
+ agent: this.agent,
488
+ sessionManager: this.sessionManager,
489
+ settingsManager: this.settingsManager,
490
+ getModel: () => this.model,
491
+ getAdaptedSettings: () => this._getAdaptedCompactionSettings(),
492
+ getRequestAuth: (model) => this._getCompactionRequestAuth(model),
493
+ resolveModelAndAuth: (compactionModel, sessionModel) => this._resolveCompactionModelAndAuth(compactionModel, sessionModel),
494
+ resolveModel: (sessionModel) => this._resolveCompactionModel(sessionModel),
495
+ getSelectionReason: () => this._getLastCompactionSelectionReason(),
496
+ resolveThinkingLevel: (compactionModel, sessionModel) => this._resolveCompactionThinkingLevel(compactionModel, sessionModel),
497
+ describeSummarizer: () => this._describeCompactionSummarizer(),
498
+ getExtensionRunner: () => this._extensionRunner,
499
+ isRawStream: () => this._isRawStreamSimple(this.agent.streamFn),
500
+ disconnectAgent: () => this._disconnectFromAgent(),
501
+ reconnectAgent: () => this._reconnectToAgent(),
502
+ abortForeground: () => this.abort(),
503
+ emit: (event) => this._emit(event),
504
+ estimateCurrentContextTokens: (messages) => this._pipeline.estimateCurrentContextTokens(messages),
505
+ buildPreDigest: () => this._buildCompactionPreDigest(),
506
+ refreshAfterCompaction: () => this._refreshAfterCompaction(),
507
+ getFailureCorpus: () => this._failureCorpus,
508
+ measureLiveContextTokens: () => this._measureLiveContextTokensForCompaction(),
509
+ runAutoCompaction: (reason, willRetry) => this._runAutoCompaction(reason, willRetry),
510
+ compactWithRetry: (run, signal, provider) => this._compactWithRetry(run, signal, provider),
511
+ });
514
512
  this._toolRecoveryLogger = new ToolRecoveryLogger({
515
513
  enabled: toolRepairSettings.logging,
516
514
  sessionId: this.sessionManager.getSessionId(),
517
515
  eventLogPath: this._toolRecoveryEventLogPath,
518
516
  failureCorpusPath,
519
517
  });
520
- this._billingFailover = new BillingFailoverController({
518
+ this._foregroundRecovery = new ForegroundRecoveryController({
521
519
  agent: this.agent,
522
520
  modelRegistry: this._modelRegistry,
523
- exhausted: new ExhaustedProviderRegistry(),
524
- subscriptionHop: this.settingsManager.getFailoverSettings().subscriptionHop,
521
+ settingsManager: this.settingsManager,
522
+ failureCorpus: this._failureCorpus,
523
+ getContextWindow: () => this.model?.contextWindow ?? 0,
525
524
  emit: (event) => this._emit(event),
526
- recordFailure: (args) => this._failureCorpus.record(args),
525
+ checkCompaction: (message) => this._checkCompaction(message),
526
+ onSuccessfulAssistant: () => this._compaction.resetOverflowRecovery(),
527
527
  });
528
528
  this._modelRouter = new ModelRouterController({
529
529
  getAgent: () => this.agent,
@@ -531,8 +531,11 @@ export class AgentSession {
531
531
  getSettingsManager: () => this.settingsManager,
532
532
  getSessionManager: () => this.sessionManager,
533
533
  getModelRegistry: () => this._modelRegistry,
534
- isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
535
- getFailoverStatus: () => ({ ...this._billingFailover.getStatus(), failureStats: this._failureCorpus.stats() }),
534
+ isModelExhausted: (model) => this._foregroundRecovery.isModelExhausted(`${model.provider}/${model.id}`),
535
+ getFailoverStatus: () => ({
536
+ ...this._foregroundRecovery.getFailoverStatus(),
537
+ failureStats: this._failureCorpus.stats(),
538
+ }),
536
539
  getAgentDir: () => this._agentDir,
537
540
  getReflectionSignal: () => this._reflectionAbort.signal,
538
541
  getBaseSystemPrompt: () => this._baseSystemPrompt,
@@ -545,7 +548,7 @@ export class AgentSession {
545
548
  emitAutonomyTelemetry: (event) => this._emitAutonomyTelemetry(event),
546
549
  resolveLaneModel: (pattern) => this._backgroundLanes.resolveLaneModel(pattern),
547
550
  resolveCurationModelIfFit: () => this._resolveCurationModelIfFit(),
548
- getToolProbeVerdict: (model) => this._toolProbeVerdict(model),
551
+ getToolProbeVerdict: (model) => this._toolProtocol.getToolProbeVerdict(model),
549
552
  });
550
553
  this._reflection = new ReflectionController({
551
554
  getModel: () => this.model,
@@ -559,7 +562,7 @@ export class AgentSession {
559
562
  isChildSession: () => this._isChildSession,
560
563
  isDisposed: () => this._disposed,
561
564
  getReflectionSignal: () => this._reflectionAbort.signal,
562
- resolveTextToolCallProtocol: (model) => this._textProtocolFlag(model),
565
+ resolveTextToolCallProtocol: (model) => this._resolveModelToolProtocol(model).protocol,
563
566
  archivePromotedSkill: (name) => this.archivePromotedSkill(name),
564
567
  emitAutonomyTelemetry: (event) => this._emitAutonomyTelemetry(event),
565
568
  ensureModelReady: (model) => this._localRuntimeController.ensureIsolatedModelReady(model),
@@ -572,9 +575,19 @@ export class AgentSession {
572
575
  recordGoalContinuationPass: (pass) => this.recordGoalContinuationPass(pass),
573
576
  });
574
577
  this._extensionRunnerRef = config.extensionRunnerRef;
575
- this._initialActiveToolNames = config.initialActiveToolNames;
576
- this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
577
- this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
578
+ this._initialActiveToolNames = config.orchestrationProfile
579
+ ? [...config.orchestrationProfile.toolNames]
580
+ : config.initialActiveToolNames;
581
+ this._allowedToolNames = config.orchestrationProfile
582
+ ? new Set(config.orchestrationProfile.toolNames)
583
+ : config.allowedToolNames
584
+ ? new Set(config.allowedToolNames)
585
+ : undefined;
586
+ this._excludedToolNames = config.orchestrationProfile
587
+ ? undefined
588
+ : config.excludedToolNames
589
+ ? new Set(config.excludedToolNames)
590
+ : undefined;
578
591
  this._toolProfileFilter = config.toolProfileFilter
579
592
  ? { allow: config.toolProfileFilter.allow ?? [], block: config.toolProfileFilter.block ?? [] }
580
593
  : undefined;
@@ -591,7 +604,7 @@ export class AgentSession {
591
604
  getSessionManager: () => this.sessionManager,
592
605
  getSettingsManager: () => this.settingsManager,
593
606
  getModelRegistry: () => this._modelRegistry,
594
- isModelExhausted: (model) => this._billingFailover.isExhausted(`${model.provider}/${model.id}`),
607
+ isModelExhausted: (model) => this._foregroundRecovery.isModelExhausted(`${model.provider}/${model.id}`),
595
608
  getResourceLoader: () => this._resourceLoader,
596
609
  getExtensionRunner: () => this._extensionRunner,
597
610
  setExtensionRunner: (runner) => {
@@ -615,6 +628,7 @@ export class AgentSession {
615
628
  this._toolProfileFilter = filter;
616
629
  },
617
630
  getAllowedToolNames: () => this._allowedToolNames,
631
+ getOrchestrationProfile: () => config.orchestrationProfile,
618
632
  getExcludedToolNames: () => this._excludedToolNames,
619
633
  deriveToolProfileFilter: () => this._profileFilter.deriveToolProfileFilter(),
620
634
  isToolOrCommandAllowedByProfile: (name) => this._profileFilter.isToolOrCommandAllowedByProfile(name),
@@ -635,6 +649,7 @@ export class AgentSession {
635
649
  reapplyActiveProfileModelSettings: () => this._profileFilter.reapplyActiveProfileModelSettings(),
636
650
  notifyExtensionsChanged: () => this._notifyExtensionsChanged(),
637
651
  getToolArtifactStore: () => this._getToolArtifactStore(),
652
+ getSessionImageStore: () => this._getSessionImageStore(),
638
653
  getMemoryManager: () => this._memory.getMemoryManager(),
639
654
  getMemoryAuditDiagnostics: () => this._memory.getMemoryAuditDiagnostics(),
640
655
  clearPendingMemoryProviders: () => this._memory.clearPendingProviders(),
@@ -647,8 +662,9 @@ export class AgentSession {
647
662
  saveTaskStepsStateSnapshot: (state) => this.saveTaskStepsStateSnapshot(state),
648
663
  getContextGcReport: (messages) => this.getContextGcReport(messages),
649
664
  startWorkerDelegation: (request) => this._backgroundLanes.startWorkerDelegation(request),
665
+ getOrchestrationProfileCatalog: () => this._backgroundLanes.getOrchestrationProfileCatalog(),
650
666
  getWorkerLaneRecords: () => this._backgroundLanes.getLaneRecords(),
651
- getWorkerResultSnapshots: () => this.getWorkerResultSnapshots(),
667
+ getWorkerClaimSnapshots: () => this.getWorkerClaimSnapshots(),
652
668
  resolveManagedLaneId: (id) => this._backgroundLanes.resolveManagedLaneId(id),
653
669
  runWorkerDelegationOnce: (request) => this.runWorkerDelegationOnce(request),
654
670
  runModelFitness: (args) => this.runModelFitness(args),
@@ -680,7 +696,7 @@ export class AgentSession {
680
696
  });
681
697
  const previousToolArgumentValidation = this.agent.onToolArgumentValidation;
682
698
  this.agent.onToolArgumentValidation = (event) => {
683
- const taggedEvent = this._tagModelAdaptationRuleTeaching(event);
699
+ const taggedEvent = this._toolProtocol.tagAdaptationRuleTeaching(event);
684
700
  if (taggedEvent.outcome === "repaired" || taggedEvent.outcome === "bounced") {
685
701
  this._toolSelection.recordValidation(taggedEvent.tool, taggedEvent.outcome);
686
702
  }
@@ -689,8 +705,8 @@ export class AgentSession {
689
705
  if (!logRecord)
690
706
  return;
691
707
  this._analytics.recordToolArgumentValidation(logRecord);
692
- this._handleTextToolProtocolValidationOutcome(taggedEvent);
693
- this._handleModelAdaptationTelemetry(taggedEvent);
708
+ this._toolProtocol.handleValidationOutcome(taggedEvent);
709
+ this._toolProtocol.handleAdaptationTelemetry(taggedEvent);
694
710
  };
695
711
  this._treeNavigator = new SessionTreeNavigator({
696
712
  getSessionManager: () => this.sessionManager,
@@ -940,35 +956,9 @@ export class AgentSession {
940
956
  const authoritativeMessages = this.agent.state.messages.length > 0 ? this.agent.state.messages : transformed;
941
957
  let currentMessages = authoritativeMessages;
942
958
  try {
943
- const settings = this._getAdaptedCompactionSettings();
944
- const contextWindow = this.model?.contextWindow ?? 0;
945
- if (settings.enabled && contextWindow > 0 && !this.isCompacting) {
946
- const triggerTokens = this.model?.autoCompactionTriggerTokens;
947
- const contextTokens = this._estimateCurrentContextTokens(authoritativeMessages);
948
- if (shouldCompact(contextTokens, contextWindow, settings, triggerTokens)) {
949
- // This pre-check runs BEFORE context-gc (below, same transform pass), so the
950
- // raw estimate above can't see this turn's own GC packing. Since context-gc later
951
- // packs the SAME messages before they're ever sent, a raw-over-threshold turn that
952
- // GC alone would bring back under threshold doesn't actually need compaction.
953
- // Project this turn's GC pass read-only (writePayloads=false -- no digest/curation
954
- // enqueue, no disk write, no artifact-reference release; see
955
- // ContextPipeline.applyContextGc) to get the same packed output the real pass would
956
- // produce for these messages, then re-check against ITS estimate. Packing only ever
957
- // shrinks (never grows) a message, so the projected estimate is never higher than
958
- // the raw one: this can only SUPPRESS an unnecessary compaction, never skip a
959
- // genuinely needed one -- the hard near-full trigger inside shouldCompact still
960
- // fires whenever the projected estimate itself remains over threshold.
961
- const gcProjection = this._applyContextGc(authoritativeMessages, false);
962
- const projectedContextTokens = this._estimateCurrentContextTokens(gcProjection.messages);
963
- if (shouldCompact(projectedContextTokens, contextWindow, settings, triggerTokens)) {
964
- const latestBefore = getLatestCompactionEntry(this.sessionManager.getBranch())?.id;
965
- await this._runAutoCompaction("threshold", false);
966
- const latestAfter = getLatestCompactionEntry(this.sessionManager.getBranch())?.id;
967
- if (latestAfter && latestAfter !== latestBefore) {
968
- currentMessages = this.agent.state.messages.slice();
969
- }
970
- }
971
- }
959
+ if (!this.isCompacting &&
960
+ (await this._compaction.maybeCompactBeforeContextTransform(authoritativeMessages, (messages) => this._applyContextGc(messages, false).messages))) {
961
+ currentMessages = this.agent.state.messages.slice();
972
962
  }
973
963
  }
974
964
  catch {
@@ -1005,7 +995,8 @@ export class AgentSession {
1005
995
  * The ceiling is turn-cumulative: the next foreground call's projection is folded together with
1006
996
  * background/research/worker/reflection spend recorded SINCE THIS TURN BEGAN — {@link getSpawnedUsage}'s
1007
997
  * already-recorded rollup (the same read-side-deduped total the footer's SUBAGENTS line uses) minus the
1008
- * baseline snapshotted at the top of `_promptUnserialized` ({@link _costGuardTurnBaselineUsd}) — so a
998
+ * baseline snapshotted before a new turn enters routing in `_promptUnserialized`
999
+ * ({@link _costGuardTurnBaselineUsd}) — so a
1009
1000
  * turn that is cheap in the foreground but has spent heavily via background lanes THIS turn still trips
1010
1001
  * the warning, while a prior turn's background spend does not keep every later turn's guard stuck
1011
1002
  * "over". A background lane that finishes mid-turn is attributed to whichever turn it completes in.
@@ -1106,520 +1097,32 @@ export class AgentSession {
1106
1097
  tools: this.agent.state.tools.slice(),
1107
1098
  };
1108
1099
  }
1109
- _modelAdaptationKeyFor(model) {
1110
- return model ? formatModelRouterModel(model) : undefined;
1111
- }
1112
- _toolRepairSettings() {
1113
- return resolveCurrentToolRepairSettings(this.settingsManager.settings);
1114
- }
1115
- _applyToolRepairLayerSettings() {
1116
- const settings = this._toolRepairSettings();
1117
- this.agent.toolArgumentTeachEnabled = settings.teach;
1118
- }
1119
- _getModelAdaptationRulesForPrompt() {
1120
- if (!this._toolRepairSettings().teach)
1121
- return [];
1122
- const modelKey = this._modelAdaptationKeyFor(this.agent.state.model);
1123
- return modelKey ? this._modelAdaptationStore.get(modelKey).rules : [];
1124
- }
1125
- _textProtocolFlag(model) {
1126
- // Phase 7 gating hierarchy: PI_TEXT_TOOL_CALL_PROTOCOL_DISABLED is resolved
1127
- // in _toolRepairSettings() as the env kill switch, then settings.toolRepair.textProtocol
1128
- // force-enables/disables globally, then Model.textToolCallProtocol opts in per model,
1129
- // then a persisted /toolprobe text-protocol verdict opts in that exact model. The
1130
- // calibration store is consulted after this flag; native provider tool calls still
1131
- // win when emitted, and this only enables the text-protocol fallback lane.
1132
- const override = this._toolRepairSettings().textProtocol;
1133
- if (override !== undefined)
1134
- return override;
1135
- if (model?.textToolCallProtocol === true)
1136
- return true;
1137
- const modelKey = this._modelAdaptationKeyFor(model);
1138
- return !!modelKey && this._modelAdaptationStore.get(modelKey).toolProbe?.status === "text-protocol";
1139
- }
1140
- async _streamForToolProbe(model, context, options) {
1141
- let requestOptions = options;
1142
- if (this._isRawStreamSimple(this.agent.streamFn)) {
1143
- const auth = await this._getRequiredRequestAuth(model);
1144
- requestOptions = {
1145
- ...options,
1146
- apiKey: auth.apiKey,
1147
- headers: auth.headers || options.headers ? { ...auth.headers, ...options.headers } : undefined,
1148
- };
1149
- }
1150
- return this.agent.streamFn(model, context, requestOptions);
1151
- }
1152
- /** Builds a reportId that uniquely identifies one probe/calibration completion within the
1153
- * session: the model, a caller-supplied `kind` (e.g. "read-task", "echo", or
1154
- * "text-protocol:<variant>"), and a monotonic sequence number. The sequence number (not
1155
- * wall-clock time or randomness) is what guarantees no collision even when the same model+kind
1156
- * is probed again later in the session (e.g. a repeated /toolprobe run), so genuine repeat spend
1157
- * is never silently deduped away. */
1158
- _nextProbeUsageReportId(model, kind) {
1159
- return `tool-probe:${this._modelRef(model)}:${kind}:${this._toolProbeUsageReportSeq++}`;
1160
- }
1161
- /** Awaits a tool-probe/text-protocol-calibration stream's result and, if the resolved message
1162
- * carries reportable usage, rolls it into spawned usage under `reportId` so the turn-scoped cost
1163
- * guard and daily usage see it. These trial completions previously spent tokens with no
1164
- * addSpawnedUsage call, making them invisible to both. `addSpawnedUsage` dedups on `reportId`, so
1165
- * a re-report of the same id is a no-op. */
1166
- async _resolveProbeStreamCountingUsage(stream, label, reportId) {
1167
- const message = await stream.result();
1168
- const usage = message.usage;
1169
- if (usage && (usage.cost.total > 0 || usage.totalTokens > 0)) {
1170
- this.addSpawnedUsage(usage, { label, reportId });
1171
- }
1172
- return message;
1173
- }
1174
- _textProtocolCalibrationContext(variant, token) {
1175
- const primer = generateTextToolProtocolPrimer([TEXT_TOOL_PROTOCOL_ECHO_TOOL], { variant });
1176
- const instruction = `Text tool protocol calibration trial. Using the protocol above, call echo with data exactly "${token}". Output only the tool-call envelope.`;
1177
- return {
1178
- systemPrompt: `${primer}\n\n${instruction}`,
1179
- messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
1180
- };
1181
- }
1182
- _messageHasToolCallWithStringArgument(message, toolName, argName, argValue) {
1183
- return message.content.some((block) => {
1184
- if (block.type !== "toolCall" || block.name !== toolName)
1185
- return false;
1186
- const args = block.arguments;
1187
- return (typeof args === "object" &&
1188
- args !== null &&
1189
- !Array.isArray(args) &&
1190
- args[argName] === argValue);
1191
- });
1192
- }
1193
- _nativeToolProbeSystemPrompt(instruction) {
1194
- const base = (this.agent.state.systemPrompt ?? "").trim();
1195
- return base ? `${base}\n\n${instruction}` : instruction;
1196
- }
1197
- async _runNativeReadTaskProbeTrial(model, path) {
1198
- const instruction = `Native tool-call capability probe: task-scale read. Use provider-native tool calling, not prose. ` +
1199
- `Call read exactly once with path exactly "${path}".`;
1200
- const stream = await this._streamForToolProbe(model, {
1201
- systemPrompt: this._nativeToolProbeSystemPrompt(instruction),
1202
- messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
1203
- tools: [NATIVE_TOOL_PROBE_READ_TOOL],
1204
- }, { textToolCallProtocol: false, maxRetries: 0, temperature: 0, maxTokens: 768 });
1205
- const message = await this._resolveProbeStreamCountingUsage(stream, "tool-probe", this._nextProbeUsageReportId(model, "read-task"));
1206
- return this._messageHasToolCallWithStringArgument(message, "read", "path", path);
1207
- }
1208
- async _runNativeEchoToolProbeTrial(model, token) {
1209
- const instruction = `Native tool-call capability probe: echo-only. Use provider-native tool calling, not prose. ` +
1210
- `Call echo with data exactly "${token}".`;
1211
- const stream = await this._streamForToolProbe(model, {
1212
- systemPrompt: this._nativeToolProbeSystemPrompt(instruction),
1213
- messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
1214
- tools: [TEXT_TOOL_PROTOCOL_ECHO_TOOL],
1215
- }, { textToolCallProtocol: false, maxRetries: 0, temperature: 0, maxTokens: 256 });
1216
- const message = await this._resolveProbeStreamCountingUsage(stream, "tool-probe", this._nextProbeUsageReportId(model, "echo"));
1217
- return this._messageHasToolCallWithStringArgument(message, "echo", "data", token);
1218
- }
1219
- async _gradeNativeToolCallingForModel(model, token) {
1220
- const path = join(getProcessWorkRun(this._agentDir, "probes", "native-tools").path, `pi-native-probe-${process.pid}-${Date.now()}.txt`);
1221
- writeFileSync(path, token, "utf-8");
1222
- try {
1223
- const taskPassed = await this._runNativeReadTaskProbeTrial(model, path);
1224
- if (taskPassed)
1225
- return "task";
1226
- const echoPassed = await this._runNativeEchoToolProbeTrial(model, token);
1227
- if (echoPassed)
1228
- return "echo-only";
1229
- return "absent";
1230
- }
1231
- finally {
1232
- rmSync(path, { force: true });
1233
- }
1234
- }
1235
- async _runTextProtocolTrial(model, variant, token) {
1236
- const stream = await this._streamForToolProbe(model, this._textProtocolCalibrationContext(variant, token), {
1237
- textToolCallProtocol: false,
1238
- maxRetries: 0,
1239
- temperature: 0,
1240
- maxTokens: 256,
1241
- });
1242
- const message = await this._resolveProbeStreamCountingUsage(stream, "text-protocol-calibration", this._nextProbeUsageReportId(model, `text-protocol:${variant}`));
1243
- const text = message.content
1244
- .filter((block) => block.type === "text")
1245
- .map((block) => block.text)
1246
- .join("\n")
1247
- .trim();
1248
- if (!text)
1249
- return false;
1250
- const parsed = parseTextToolCalls(text, [TEXT_TOOL_PROTOCOL_ECHO_TOOL]);
1251
- return parsed.calls.some((call) => call.name === "echo" && call.arguments.data === token);
1252
- }
1253
- async _calibrateTextToolProtocolForModel(model, modelKey, options) {
1254
- const variantsTried = [];
1255
- for (const variant of TEXT_TOOL_PROTOCOL_VARIANTS) {
1256
- variantsTried.push(variant);
1257
- let passed = true;
1258
- for (let trial = 0; trial < TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT; trial++) {
1259
- const ok = await this._runTextProtocolTrial(model, variant, `pi-calibration-${trial + 1}`);
1260
- if (!ok) {
1261
- passed = false;
1262
- break;
1263
- }
1264
- }
1265
- if (passed) {
1266
- const calibratedAt = new Date().toISOString();
1267
- if (modelKey) {
1268
- this._modelAdaptationStore.setProtocol(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "calibrated", variant, calibratedAt }, calibratedAt);
1269
- }
1270
- return { status: "calibrated", variant, calibratedAt };
1271
- }
1272
- }
1273
- const attemptedAt = new Date().toISOString();
1274
- if (modelKey && options.persistFailure) {
1275
- this._modelAdaptationStore.setProtocol(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "failed", attemptedAt, variantsTried }, attemptedAt);
1276
- }
1277
- return { status: "failed", attemptedAt, variantsTried };
1278
- }
1279
- /**
1280
- * A PURE READER of the persisted calibration — never calibrates inline and
1281
- * never throws out of the prompt path. A model whose flag is on but has no valid current-version
1282
- * protocol on record falls back to native for this turn (with a warning pointing at /toolprobe)
1283
- * instead of blocking the user's message behind up to 8 inline calibration completions or
1284
- * aborting the turn entirely. Calibration now only ever happens off the hot path: explicit
1285
- * /toolprobe, or the capability-gate spine's evidence-gated auto-probe.
1286
- */
1287
- async _ensureTextToolProtocolForActiveModel() {
1288
- const model = this.agent.state.model;
1289
- if (!this._textProtocolFlag(model)) {
1290
- this.agent.textToolCallProtocol = undefined;
1291
- return;
1292
- }
1293
- // Force-enable (a global settings override or Model.textToolCallProtocol) wins regardless of
1294
- // whether this model has ever been graded-probed — that is the point of an explicit override.
1295
- // Preserve it exactly as before: default to the tool-tag variant, no inline calibration. Only
1296
- // the graded-evidence path below (flag on solely because of a persisted toolProbe verdict)
1297
- // needs a valid persisted protocol to proceed.
1298
- const forceEnabled = this._toolRepairSettings().textProtocol === true || model?.textToolCallProtocol === true;
1299
- const modelKey = this._modelAdaptationKeyFor(model);
1300
- if (!modelKey || forceEnabled) {
1301
- this.agent.textToolCallProtocol = true;
1302
- return;
1303
- }
1304
- const profile = this._modelAdaptationStore.get(modelKey);
1305
- if (profile.protocol?.version === TEXT_TOOL_PROTOCOL_VERSION && profile.protocol.status !== "failed") {
1306
- this.agent.textToolCallProtocol = { variant: profile.protocol.variant };
1307
- return;
1308
- }
1309
- this.agent.textToolCallProtocol = undefined;
1310
- this._emit({
1311
- type: "warning",
1312
- message: profile.protocol?.status === "failed"
1313
- ? `Text tool protocol calibration for ${modelKey} previously failed (variants tried: ${profile.protocol.variantsTried.join(", ")}); falling back to native tool calls this turn. Run /toolprobe ${modelKey} to recalibrate.`
1314
- : `Text tool protocol for ${modelKey} has no valid calibration on record; falling back to native tool calls this turn. Run /toolprobe ${modelKey} to calibrate.`,
1315
- });
1100
+ /** Compatibility seam retained for focused auto-probe regressions. */
1101
+ _probeToolCallingForModel(model) {
1102
+ return this._toolProtocol.probeToolCallingForModel(model);
1316
1103
  }
1317
- _modelRef(model) {
1318
- return `${model.provider}/${model.id}`;
1319
- }
1320
- /** The persisted `/toolprobe` verdict on record for this model, if any (undefined = unprobed). */
1321
- _toolProbeVerdict(model) {
1322
- return this._modelAdaptationStore.get(this._modelRef(model)).toolProbe?.status;
1323
- }
1324
- _formatToolProbeReport(results) {
1325
- const lines = [
1326
- "Tool probe results:",
1327
- "Model | Verdict | Variant | Native grade | Diagnostic",
1328
- "--- | --- | --- | --- | ---",
1329
- ];
1330
- for (const result of results) {
1331
- lines.push([
1332
- result.model,
1333
- result.verdict,
1334
- result.variant ?? "-",
1335
- result.nativeGrade ?? "-",
1336
- result.diagnostic ? result.diagnostic.replace(/\s+/g, " ").slice(0, 160) : "-",
1337
- ].join(" | "));
1338
- }
1339
- return lines.join("\n");
1340
- }
1341
- _storeToolProbe(modelKey, probe) {
1342
- this._modelAdaptationStore.setToolProbe(modelKey, probe, probe.probedAt);
1343
- }
1344
- async _probeToolCallingForModel(model) {
1345
- const modelKey = this._modelRef(model);
1346
- const probedAt = new Date().toISOString();
1347
- let nativeGrade = "absent";
1348
- let diagnostic;
1349
- try {
1350
- nativeGrade = await this._gradeNativeToolCallingForModel(model, "pi-native-probe");
1351
- if (nativeGrade === "task") {
1352
- this._storeToolProbe(modelKey, {
1353
- version: TEXT_TOOL_PROTOCOL_VERSION,
1354
- status: "native",
1355
- probedAt,
1356
- nativeGrade,
1357
- });
1358
- return { model: modelKey, verdict: "native", nativeGrade };
1359
- }
1360
- diagnostic =
1361
- nativeGrade === "echo-only"
1362
- ? "Native echo probe passed but task-scale read probe failed."
1363
- : "Native task-scale read and echo probes did not produce provider-native tool calls.";
1364
- }
1365
- catch (error) {
1366
- diagnostic = error instanceof Error ? error.message : String(error);
1367
- }
1368
- try {
1369
- const calibrated = await this._calibrateTextToolProtocolForModel(model, modelKey, { persistFailure: false });
1370
- if (calibrated.status === "calibrated") {
1371
- this._storeToolProbe(modelKey, {
1372
- version: TEXT_TOOL_PROTOCOL_VERSION,
1373
- status: "text-protocol",
1374
- probedAt: calibrated.calibratedAt,
1375
- variant: calibrated.variant,
1376
- nativeGrade,
1377
- diagnostic,
1378
- });
1379
- return { model: modelKey, verdict: "text-protocol", variant: calibrated.variant, nativeGrade, diagnostic };
1380
- }
1381
- diagnostic = `${diagnostic ? `${diagnostic} ` : ""}Text protocol variants failed: ${calibrated.variantsTried.join(", ")}`;
1382
- }
1383
- catch (error) {
1384
- diagnostic = error instanceof Error ? error.message : String(error);
1385
- }
1386
- this._storeToolProbe(modelKey, {
1387
- version: TEXT_TOOL_PROTOCOL_VERSION,
1388
- status: "none",
1389
- probedAt,
1390
- nativeGrade,
1391
- diagnostic,
1392
- });
1393
- return { model: modelKey, verdict: "none", nativeGrade, diagnostic };
1394
- }
1395
- async _resolveToolProbeModels(target) {
1396
- const trimmed = target?.trim();
1397
- if (!trimmed)
1398
- return this._modelRegistry.getAvailable();
1399
- const [provider, ...modelParts] = trimmed.split("/");
1400
- const modelId = modelParts.join("/");
1401
- if (!provider || !modelId)
1402
- throw new Error("Usage: /toolprobe [provider/model]");
1403
- const exact = this._modelRegistry.find(provider, modelId);
1404
- if (exact)
1405
- return [exact];
1406
- const current = this.agent.state.model;
1407
- if (current?.provider === provider && current.id === modelId)
1408
- return [current];
1409
- throw new Error(`Model not found: ${trimmed}`);
1104
+ /** Compatibility seam retained for focused protocol-selection doctrine regressions. */
1105
+ _resolveModelToolProtocol(model) {
1106
+ return this._toolProtocol.resolveModelToolProtocol(model);
1410
1107
  }
1411
1108
  async probeToolCalling(target) {
1412
- const models = await this._resolveToolProbeModels(target);
1413
- if (models.length === 0)
1414
- throw new Error("No available models to probe.");
1415
- const results = [];
1416
- for (const model of models) {
1417
- results.push(await this._probeToolCallingForModel(model));
1418
- }
1419
- return { results, table: this._formatToolProbeReport(results) };
1420
- }
1421
- /**
1422
- * The text-protocol circuit breaker. Every parse failure (from either the
1423
- * live per-completion {@link Agent.onTextToolProtocolParse} callback or the post-hoc detection in
1424
- * {@link _recordTextToolProtocolParseOutcomeFromLastAssistant}) gets a throttled one-line
1425
- * corrective steer for the next turn. On the 3rd consecutive SAME-SIGNATURE failure — graded
1426
- * evidence a model genuinely cannot speak this dialect, not a single bad turn — the breaker also
1427
- * demotes the persisted tool-probe verdict to "none" so {@link _textProtocolFlag} reads false next
1428
- * turn and native is attempted instead of thrashing on a protocol this model has proven it can't
1429
- * follow.
1430
- */
1431
- _handleTextToolProtocolParse(event) {
1432
- this._textProtocolParseObservedThisTurn = true;
1433
- const modelKey = `${event.provider}/${event.model}`;
1434
- if (event.status === "parsed")
1435
- return;
1436
- this._maybeInjectTextProtocolCorrectiveSteer(event.variant);
1437
- const signature = `${event.variant}:${event.reason ?? "failed"}`;
1438
- const previous = this._textProtocolParseFailures.get(modelKey);
1439
- const repeats = previous?.signature === signature ? previous.repeats + 1 : 1;
1440
- this._textProtocolParseFailures.set(modelKey, { signature, repeats });
1441
- if (repeats < TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD)
1442
- return;
1443
- const profile = this._modelAdaptationStore.get(modelKey);
1444
- if (profile.protocol?.version === TEXT_TOOL_PROTOCOL_VERSION && profile.protocol.status !== "failed") {
1445
- this._modelAdaptationStore.removeProtocol(modelKey);
1446
- this.agent.textToolCallProtocol = undefined;
1447
- }
1448
- this._textProtocolParseFailures.delete(modelKey);
1449
- // Demote on graded evidence -- TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD identical-signature
1450
- // parse failures in a row -- rather than thrashing every turn. The fresh probedAt doubles as
1451
- // the capability-gate spine's auto-probe freshness gate, so this demotion doesn't immediately
1452
- // trigger a re-probe/re-phone loop.
1453
- const probedAt = new Date().toISOString();
1454
- this._modelAdaptationStore.setToolProbe(modelKey, {
1455
- version: TEXT_TOOL_PROTOCOL_VERSION,
1456
- status: "none",
1457
- probedAt,
1458
- nativeGrade: profile.toolProbe?.nativeGrade,
1459
- diagnostic: `Text protocol parsing failed ${TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD}x in a row with signature "${signature}".`,
1460
- }, probedAt);
1461
- this._emit({
1462
- type: "warning",
1463
- message: `Text tool protocol for ${modelKey} stopped parsing after ${TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD} attempts; demoted to native fallback. Run /toolprobe ${modelKey} to recalibrate.`,
1464
- });
1465
- }
1466
- /**
1467
- * A phone model whose envelope fails to parse gets no corrective guidance today —
1468
- * the runaway-loop backstop only trips on repeated PARSED tool-call signatures (agent-loop.ts), so
1469
- * an every-turn unparseable-prose model never trips it. Inject a one-line reminder of the envelope
1470
- * shape as a nextTurn message, reusing {@link formatVariantEnvelope} so the reminder can never
1471
- * drift from the grammar the primer actually teaches. Throttled to the first failure this session
1472
- * and then every {@link TEXT_TOOL_PROTOCOL_STEER_INTERVAL}th after — the breaker above is what
1473
- * actually demotes a genuinely-failing model at the 3rd same-signature failure; this is guidance,
1474
- * not a second counter.
1475
- */
1476
- _maybeInjectTextProtocolCorrectiveSteer(variant) {
1477
- this._textProtocolCorrectiveSteerCount++;
1478
- if (this._textProtocolCorrectiveSteerCount !== 1 &&
1479
- this._textProtocolCorrectiveSteerCount % TEXT_TOOL_PROTOCOL_STEER_INTERVAL !== 0) {
1480
- return;
1481
- }
1482
- const reminder = `Reminder: to call a tool, emit exactly this envelope shape: ${formatVariantEnvelope(variant, "TOOL", '{"arg":"value"}')} — no other format is recognized. Reasoning may appear as prose before the envelope, never inside it.`;
1483
- this.sendCustomMessage({ customType: "text-protocol-corrective-steer", content: reminder, display: false }, { deliverAs: "nextTurn" }).catch(() => {
1484
- // Best-effort steer; a failure to queue it must not break parse-failure handling.
1485
- });
1486
- }
1487
- _handleTextToolProtocolValidationOutcome(event) {
1488
- if (event.source !== "text-protocol")
1489
- return;
1490
- const protocol = this.agent.textToolCallProtocol;
1491
- const variant = protocol === true ? "tool-tag" : protocol ? protocol.variant : undefined;
1492
- if (!variant)
1493
- return;
1494
- const status = event.outcome === "bounced" ? "failed" : "parsed";
1495
- if (this._textProtocolValidationOutcomeThisTurn?.status === "parsed" && status === "failed")
1496
- return;
1497
- this._textProtocolValidationOutcomeThisTurn = {
1498
- provider: event.provider ?? this.agent.state.model.provider,
1499
- model: event.model ?? this.agent.state.model.id,
1500
- variant,
1501
- status,
1502
- callCount: 1,
1503
- textLength: 0,
1504
- ...(status === "failed" && {
1505
- reason: event.errorKeywords?.includes("unknown_tool") ? "unknown-tool" : "validation-failed",
1506
- }),
1507
- };
1508
- }
1509
- _recordTextToolProtocolParseOutcomeFromLastAssistant() {
1510
- const validationOutcome = this._textProtocolValidationOutcomeThisTurn;
1511
- this._textProtocolValidationOutcomeThisTurn = undefined;
1512
- if (validationOutcome?.status === "parsed") {
1513
- this._textProtocolParseObservedThisTurn = true;
1514
- this._textProtocolParseFailures.delete(`${validationOutcome.provider}/${validationOutcome.model}`);
1515
- return;
1516
- }
1517
- if (validationOutcome) {
1518
- this._handleTextToolProtocolParse(validationOutcome);
1519
- return;
1520
- }
1521
- if (this._textProtocolParseObservedThisTurn)
1522
- return;
1523
- const protocol = this.agent.textToolCallProtocol;
1524
- if (protocol === false || protocol === true || !protocol?.variant)
1525
- return;
1526
- const response = this._findLastAssistantMessage();
1527
- if (!response)
1528
- return;
1529
- const responseText = response.content
1530
- .filter((content) => content.type === "text")
1531
- .map((content) => content.text)
1532
- .join("\n");
1533
- if (!responseText)
1534
- return;
1535
- const parsed = parseTextToolCalls(responseText, this.agent.state.tools);
1536
- const attempted = parsed.attempted || this._looksLikeTextToolProtocolAttempt(responseText);
1537
- if (!attempted)
1538
- return;
1539
- this._handleTextToolProtocolParse({
1540
- provider: this.agent.state.model.provider,
1541
- model: this.agent.state.model.id,
1542
- variant: protocol.variant,
1543
- status: parsed.calls.length > 0 ? "parsed" : "failed",
1544
- reason: parsed.failure,
1545
- callCount: parsed.calls.length,
1546
- textLength: responseText.length,
1547
- });
1548
- }
1549
- _looksLikeTextToolProtocolAttempt(text) {
1550
- return /<pi:call\b|<tool_call\b|```(?:tool|tool_call)[\s\S]*"name"\s*:/i.test(text);
1551
- }
1552
- _tagModelAdaptationRuleTeaching(event) {
1553
- if (!this._toolRepairSettings().teach || event.taught !== "none")
1554
- return event;
1555
- const modelKey = event.provider && event.model
1556
- ? `${event.provider}/${event.model}`
1557
- : this._modelAdaptationKeyFor(this.agent.state.model);
1558
- if (!modelKey)
1559
- return event;
1560
- try {
1561
- const rules = this._modelAdaptationStore.get(modelKey).rules;
1562
- const modes = new Set([...event.failureModes, ...event.repairsApplied]);
1563
- if (rules.some((rule) => modes.has(rule.mode))) {
1564
- return { ...event, taught: "rule" };
1565
- }
1566
- }
1567
- catch {
1568
- // Adaptation telemetry tagging is best-effort; leave the original event unchanged.
1569
- }
1570
- return event;
1571
- }
1572
- _repairSessionCount(modelKey, mode) {
1573
- const key = `${modelKey}\0${mode}`;
1574
- const count = (this._repairModeSessionCounts.get(key) ?? 0) + 1;
1575
- this._repairModeSessionCounts.set(key, count);
1576
- return count;
1577
- }
1578
- _handleModelAdaptationTelemetry(event) {
1579
- if (!this._toolRepairSettings().teach || event.outcome !== "repaired" || event.repairsApplied.length === 0)
1580
- return;
1581
- const modelKey = event.provider && event.model
1582
- ? `${event.provider}/${event.model}`
1583
- : this._modelAdaptationKeyFor(this.agent.state.model);
1584
- if (!modelKey)
1585
- return;
1586
- try {
1587
- for (const mode of [...new Set(event.repairsApplied)]) {
1588
- const profile = this._modelAdaptationStore.get(modelKey);
1589
- const stats = profile.teachStats[mode] ?? { taught: 0, recurrenceBefore: 0, recurrenceAfter: 0 };
1590
- if (profile.rules.some((rule) => rule.mode === mode)) {
1591
- this._modelAdaptationStore.markRuleFired(modelKey, mode);
1592
- this._modelAdaptationStore.setTeachStats(modelKey, mode, {
1593
- ...stats,
1594
- recurrenceAfter: stats.recurrenceAfter + 1,
1595
- });
1596
- continue;
1597
- }
1598
- const recurrenceBefore = stats.recurrenceBefore + 1;
1599
- this._modelAdaptationStore.setTeachStats(modelKey, mode, { ...stats, recurrenceBefore });
1600
- const sessionCount = this._repairSessionCount(modelKey, mode);
1601
- if (sessionCount >= MODEL_ADAPTATION_REPAIR_THRESHOLD ||
1602
- recurrenceBefore >= MODEL_ADAPTATION_REPAIR_THRESHOLD) {
1603
- this._modelAdaptationStore.addRule(modelKey, {
1604
- mode,
1605
- text: formatToolRepairStandingRule(mode),
1606
- });
1607
- this._modelAdaptationStore.setTeachStats(modelKey, mode, {
1608
- ...stats,
1609
- taught: stats.taught + 1,
1610
- recurrenceBefore,
1611
- });
1612
- }
1613
- }
1614
- }
1615
- catch {
1616
- // Model adaptation is best-effort; failed telemetry persistence must not affect the turn.
1617
- }
1109
+ return this._toolProtocol.probeToolCalling(target);
1618
1110
  }
1619
1111
  /** Tool-build call-site delegation to {@link ContextPipeline.getToolArtifactStore}. */
1620
1112
  _getToolArtifactStore() {
1621
1113
  return this._pipeline.getToolArtifactStore();
1622
1114
  }
1115
+ _getSessionImageStore() {
1116
+ const directory = this.settingsManager.getClipboardImageDirectory();
1117
+ if (!this.sessionManager.isPersisted() && !directory)
1118
+ return undefined;
1119
+ return new SessionImageStore({
1120
+ agentDir: this._agentDir,
1121
+ cwd: this._cwd,
1122
+ sessionId: this.sessionId,
1123
+ directory,
1124
+ });
1125
+ }
1623
1126
  /**
1624
1127
  * Context-transform hot-path delegation to {@link ContextPipeline.runContextAudit}. Kept as a
1625
1128
  * one-line method (not inlined) so the transform stays the single owner of the pass ordering.
@@ -1767,7 +1270,7 @@ export class AgentSession {
1767
1270
  }
1768
1271
  formatToolRepairHealthReport() {
1769
1272
  return [
1770
- formatToolRepairHealthReport(this._modelAdaptationStore, new Date(), this._toolRecoveryLogger.getStats()),
1273
+ this._toolProtocol.formatRepairHealth(this._toolRecoveryLogger.getStats()),
1771
1274
  formatToolSelectionReport(this._toolSelection.getReport()),
1772
1275
  ].join("\n\n");
1773
1276
  }
@@ -1775,12 +1278,10 @@ export class AgentSession {
1775
1278
  await this._toolRecoveryLogger.flush(timeoutMs);
1776
1279
  }
1777
1280
  removeToolRepairRule(model, mode) {
1778
- return this._modelAdaptationStore.removeRule(model, mode);
1281
+ return this._toolProtocol.removeRepairRule(model, mode);
1779
1282
  }
1780
1283
  resetToolProtocolCalibration(model) {
1781
- const removed = this._modelAdaptationStore.removeProtocol(model);
1782
- this._textProtocolParseFailures.delete(model);
1783
- return removed;
1284
+ return this._toolProtocol.resetProtocolCalibration(model);
1784
1285
  }
1785
1286
  /** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
1786
1287
  /** Curation status for diagnostics/dashboard (delegates to {@link ContextPipeline.getContextCurationStatus}). */
@@ -1825,14 +1326,6 @@ export class AgentSession {
1825
1326
  getContextGcReport(messages) {
1826
1327
  return this._pipeline.getContextGcReport(messages);
1827
1328
  }
1828
- /**
1829
- * Context-transform hot-path delegation to {@link ContextPipeline.estimateCurrentContextTokens}. Kept
1830
- * as a one-line method (not inlined) so the transform stays the single owner of the pass ordering;
1831
- * also feeds the per-turn cost guard.
1832
- */
1833
- _estimateCurrentContextTokens(messages) {
1834
- return this._pipeline.estimateCurrentContextTokens(messages);
1835
- }
1836
1329
  _installAgentToolHooks() {
1837
1330
  this.agent.beforeToolCall = this._toolGate.beforeToolCall;
1838
1331
  this.agent.afterToolCall = this._toolGate.afterToolCall;
@@ -1859,17 +1352,6 @@ export class AgentSession {
1859
1352
  message: `Stopped: the model repeated the same tool call ${info.repeats} times in a row without making progress. Review the last tool result and steer or retry with a different approach.`,
1860
1353
  });
1861
1354
  }
1862
- /** Anti-loop: true when this model's persisted tool-probe verdict was written within the
1863
- * auto-probe freshness window ({@link AUTO_TOOL_PROBE_FRESHNESS_MS}) — covers a very recent
1864
- * explicit `/toolprobe` run or a fresh circuit-breaker demote from an EARLIER session, complementing
1865
- * the in-session {@link _autoProbedModels} latch. */
1866
- _hasFreshToolProbeVerdict(modelKey) {
1867
- const probedAt = this._modelAdaptationStore.get(modelKey).toolProbe?.probedAt;
1868
- if (!probedAt)
1869
- return false;
1870
- const age = Date.now() - new Date(probedAt).getTime();
1871
- return Number.isFinite(age) && age >= 0 && age < AUTO_TOOL_PROBE_FRESHNESS_MS;
1872
- }
1873
1355
  /**
1874
1356
  * Evidence-gated native→phone auto-probe for a LOCAL/MANAGED model (never cloud — see
1875
1357
  * {@link isLocalOrManagedRouterModel}) that just crossed the tool-argument-validation escalation
@@ -1880,36 +1362,11 @@ export class AgentSession {
1880
1362
  * "native" and is never phoned) entirely OFF the hot path — fired here but never awaited by the
1881
1363
  * caller, so a slow or failing probe can never block or throw the user's in-flight turn.
1882
1364
  * Anti-loop: skipped when this session already auto-probed this model, or a fresh persisted
1883
- * verdict already exists ({@link _hasFreshToolProbeVerdict}) — otherwise a model that keeps
1365
+ * verdict already exists (enforced by {@link ToolProtocolController}) — otherwise a model that keeps
1884
1366
  * failing validation every turn would re-fire the (multi-completion) probe every single turn.
1885
1367
  */
1886
1368
  _maybeAutoProbeOnValidationEscalation(model) {
1887
- const modelKey = this._modelRef(model);
1888
- if (this._autoProbedModels.has(modelKey) || this._hasFreshToolProbeVerdict(modelKey))
1889
- return;
1890
- this._autoProbedModels.add(modelKey);
1891
- void this._probeToolCallingForModel(model)
1892
- .then((result) => {
1893
- if (this._disposed)
1894
- return;
1895
- const detail = result.verdict === "text-protocol"
1896
- ? ` (variant ${result.variant}); it will use the text tool protocol starting next turn`
1897
- : result.verdict === "none"
1898
- ? " — no working tool-call path was found; run /toolprobe for details"
1899
- : "; native tool calls stay in use";
1900
- this._emit({
1901
- type: "warning",
1902
- message: `Auto-probed ${modelKey} after repeated native tool-call validation failures: verdict "${result.verdict}"${detail}.`,
1903
- });
1904
- })
1905
- .catch((error) => {
1906
- if (this._disposed)
1907
- return;
1908
- this._emit({
1909
- type: "warning",
1910
- message: `Auto-probe for ${modelKey} (triggered by repeated tool-call validation failures) did not complete: ${error instanceof Error ? error.message : String(error)}.`,
1911
- });
1912
- });
1369
+ this._toolProtocol.maybeAutoProbe(model);
1913
1370
  }
1914
1371
  /**
1915
1372
  * A repeated identical tool-argument-validation failure crossed the escalation threshold
@@ -1960,11 +1417,23 @@ export class AgentSession {
1960
1417
  }
1961
1418
  }
1962
1419
  async _notifyWorkerTerminalHandoff(records) {
1963
- if (this._disposed || records.length === 0)
1420
+ if (records.length === 0)
1964
1421
  return;
1422
+ if (this._disposed)
1423
+ throw new Error("Session disposed before worker terminal handoff was persisted");
1424
+ // sendCustomMessage only queues in process memory while the foreground is streaming. Wait for
1425
+ // the owning run to settle so a successful return proves the handoff reached session storage
1426
+ // before the durable notification outbox marks it delivered.
1427
+ await this.agent.waitForIdle();
1428
+ if (this._disposed)
1429
+ throw new Error("Session disposed before worker terminal handoff was persisted");
1965
1430
  const included = records.slice(0, 8);
1966
1431
  const sanitize = (value) => value.replace(/[\r\n]+/g, " ").slice(0, 120);
1967
1432
  const omitted = records.length - included.length;
1433
+ const workerRequestsAwaitingDelivery = new Set(getWorkerHumanInputsRequiringDelivery(this.sessionManager).flatMap((snapshot) => snapshot.request.workerRequestId ? [snapshot.request.workerRequestId] : []));
1434
+ const ownerQuestionWillWakeParent = this._extensionUIContext !== undefined &&
1435
+ this._extensionMode !== "print" &&
1436
+ records.every((record) => workerRequestsAwaitingDelivery.has(record.laneId));
1968
1437
  const content = [
1969
1438
  "Background worker terminal handoff:",
1970
1439
  ...included.map((record) => {
@@ -1979,7 +1448,7 @@ export class AgentSession {
1979
1448
  content,
1980
1449
  display: true,
1981
1450
  details: { records: included },
1982
- }, { triggerTurn: true, deliverAs: "followUp" });
1451
+ }, { triggerTurn: !ownerQuestionWillWakeParent, deliverAs: "followUp" });
1983
1452
  }
1984
1453
  _emitQueueUpdate() {
1985
1454
  this._emit({
@@ -1989,8 +1458,6 @@ export class AgentSession {
1989
1458
  commands: [...this._queuedExtensionCommands],
1990
1459
  });
1991
1460
  }
1992
- // Track last assistant message for auto-compaction check
1993
- _lastAssistantMessage = undefined;
1994
1461
  /**
1995
1462
  * User messages already painted to the UI by an early, synthetic `message_start` fired from
1996
1463
  * `_promptUnserialized` — before the model-router judge's bounded LLM call — so the prompt
@@ -2006,7 +1473,7 @@ export class AgentSession {
2006
1473
  // When a user message starts, check if it's from either queue and remove it BEFORE emitting
2007
1474
  // This ensures the UI sees the updated queue state
2008
1475
  if (event.type === "message_start" && event.message.role === "user") {
2009
- this._overflowRecoveryAttempted = false;
1476
+ this._compaction.resetOverflowRecovery();
2010
1477
  const messageText = this._getUserMessageText(event.message);
2011
1478
  if (messageText) {
2012
1479
  // Check steering queue first
@@ -2065,41 +1532,21 @@ export class AgentSession {
2065
1532
  messagePersisted = true;
2066
1533
  }
2067
1534
  // Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
2068
- // Track assistant message for auto-compaction (checked on agent_end)
1535
+ // Track the response for ordered retry/failover/compaction handling after agent_end.
2069
1536
  if (event.message.role === "assistant") {
2070
- this._lastAssistantMessage = event.message;
2071
1537
  const assistantMsg = event.message;
2072
1538
  if (messagePersisted) {
2073
1539
  this._pipeline.observeProviderUsage(this.agent.state.messages, assistantMsg);
2074
1540
  }
2075
- if (assistantMsg.stopReason !== "error") {
2076
- this._overflowRecoveryAttempted = false;
2077
- }
2078
- // Reset retry counter immediately on successful assistant response
2079
- // This prevents accumulation across multiple LLM calls within a turn
2080
- if (assistantMsg.stopReason !== "error" && this._retryController.attempt > 0) {
2081
- this._emit({
2082
- type: "auto_retry_end",
2083
- success: true,
2084
- attempt: this._retryController.attempt,
2085
- });
2086
- this._retryController.reset();
2087
- }
1541
+ this._foregroundRecovery.observeAssistant(assistantMsg);
2088
1542
  }
2089
1543
  }
1544
+ if (event.type === "agent_end" && !this._willRetryAfterAgentEnd(event)) {
1545
+ this._schedulePendingWorkerHumanInputs();
1546
+ }
2090
1547
  };
2091
1548
  _willRetryAfterAgentEnd(event) {
2092
- const settings = this.settingsManager.getRetrySettings();
2093
- if (!settings.enabled || this._retryController.attempt >= settings.maxRetries) {
2094
- return false;
2095
- }
2096
- for (let i = event.messages.length - 1; i >= 0; i--) {
2097
- const message = event.messages[i];
2098
- if (message.role === "assistant") {
2099
- return this._isRetryableError(message);
2100
- }
2101
- }
2102
- return false;
1549
+ return this._foregroundRecovery.willRetryAfterAgentEnd(event);
2103
1550
  }
2104
1551
  /** Extract text content from a message */
2105
1552
  _getUserMessageText(message) {
@@ -2287,45 +1734,50 @@ export class AgentSession {
2287
1734
  * Call this when completely done with the session.
2288
1735
  */
2289
1736
  dispose() {
2290
- try {
2291
- this._backgroundLanes.clearGoalAutoContinueTimer();
2292
- this._backgroundLanes.clearResearchLaneTimer();
2293
- this.abortRetry();
2294
- this.abortCompaction();
2295
- this.abortBranchSummary();
2296
- this.abortBash();
2297
- disposePersistentShellSession(this._shellSessionKey);
2298
- this._cancelPrefixWarm();
2299
- this.agent.abort();
2300
- // Stop any deployment-registered gateway channels / schedulers.
2301
- void this._gatewayRegistry.stop().catch(() => { });
2302
- // Abort any in-flight background reflection so it cannot keep spending tokens or
2303
- // write memory/skills against this now-disposed session.
2304
- this._disposed = true;
2305
- this._reflectionAbort.abort();
2306
- // Abort any in-flight research pass or delegated worker for the same reason: a disposed
2307
- // session must not keep spending tokens or persist evidence against dead state.
2308
- this._backgroundLanes.abortInFlightLanes();
2309
- // Clear the hooks this session installed on the shared agent so their closures stop
2310
- // pinning this (deactivated) session — and all its history/maps — in memory if the agent
2311
- // instance outlives the session.
1737
+ if (this._disposed)
1738
+ return;
1739
+ this._disposed = true;
1740
+ const shutdowns = [];
1741
+ const safely = (action) => {
1742
+ try {
1743
+ action();
1744
+ }
1745
+ catch {
1746
+ // Every independent cleanup remains best-effort; one failure cannot skip the rest.
1747
+ }
1748
+ };
1749
+ const track = (action) => {
1750
+ try {
1751
+ shutdowns.push(action());
1752
+ }
1753
+ catch {
1754
+ // A synchronously throwing async-resource adapter cannot skip the remaining shutdowns.
1755
+ }
1756
+ };
1757
+ safely(() => this._backgroundLanes.clearGoalAutoContinueTimer());
1758
+ safely(() => this._backgroundLanes.clearResearchLaneTimer());
1759
+ safely(() => this.abortRetry());
1760
+ safely(() => this.abortCompaction());
1761
+ safely(() => this.abortBranchSummary());
1762
+ safely(() => this.abortBash());
1763
+ safely(() => disposePersistentShellSession(this._shellSessionKey));
1764
+ safely(() => this._cancelPrefixWarm());
1765
+ safely(() => this.agent.abort());
1766
+ track(() => this._gatewayRegistry.stop());
1767
+ safely(() => this._reflectionAbort.abort());
1768
+ safely(() => this._backgroundLanes.abortInFlightLanes());
1769
+ safely(() => {
2312
1770
  this.agent.afterToolCall = undefined;
2313
1771
  this.agent.transformContext = undefined;
2314
- }
2315
- catch {
2316
- // Dispose must succeed even if an abort hook throws.
2317
- }
2318
- this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
2319
- this._disconnectFromAgent();
1772
+ });
1773
+ safely(() => this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload()."));
1774
+ safely(() => this._disconnectFromAgent());
2320
1775
  this._eventListeners = [];
2321
1776
  // Best-effort memory cleanup (release locks/handles). Write-side onSessionEnd is wired on a
2322
1777
  // true session-end hook; file-store shutdown is a no-op.
2323
- void this._memory
2324
- .getMemoryManager()
2325
- .shutdownAll()
2326
- .catch(() => { });
2327
- void this._toolRecoveryLogger.shutdown().catch(() => { });
2328
- cleanupSessionResources(this.sessionId);
1778
+ track(() => this._memory.getMemoryManager().shutdownAll());
1779
+ track(() => this._toolRecoveryLogger.shutdown());
1780
+ safely(() => cleanupSessionResources(this.sessionId));
2329
1781
  // Best-effort final sweep for any grep/find artifact already released (reference
2330
1782
  // count zero) but not yet reclaimed -- e.g. a release whose cleanup() call failed
2331
1783
  // transiently. This is conservative: it never releases a still-referenced
@@ -2333,12 +1785,13 @@ export class AgentSession {
2333
1785
  // short to cross preserveRecentMessages) correctly leaves that artifact in place,
2334
1786
  // resolvable if the same session is resumed later. It does not sweep OTHER
2335
1787
  // sessions' artifact directories.
2336
- try {
2337
- this._pipeline.cleanupToolArtifactStoreOnDispose();
2338
- }
2339
- catch {
2340
- // Best-effort; dispose must succeed regardless.
2341
- }
1788
+ safely(() => this._pipeline.cleanupToolArtifactStoreOnDispose());
1789
+ this._disposeCompletion = Promise.allSettled(shutdowns).then(() => undefined);
1790
+ }
1791
+ /** Dispose synchronously-visible state, then await all session-owned asynchronous shutdowns. */
1792
+ async disposeAndWait() {
1793
+ this.dispose();
1794
+ await this._disposeCompletion;
2342
1795
  }
2343
1796
  // =========================================================================
2344
1797
  // Read-only State Access
@@ -2365,7 +1818,7 @@ export class AgentSession {
2365
1818
  }
2366
1819
  /** Current retry attempt (0 if not retrying) */
2367
1820
  get retryAttempt() {
2368
- return this._retryController.attempt;
1821
+ return this._foregroundRecovery.attempt;
2369
1822
  }
2370
1823
  /**
2371
1824
  * Get the names of currently active tools.
@@ -2415,7 +1868,7 @@ export class AgentSession {
2415
1868
  * Changes take effect on the next agent turn.
2416
1869
  *
2417
1870
  * artifact_retrieve is auto-activated as a companion whenever an artifact-producing tool
2418
- * (grep, find, or run_toolkit_script) ends up in the resulting active set and artifact_retrieve
1871
+ * (grep, find, run_toolkit_script, or ask_question) ends up in the resulting active set and artifact_retrieve
2419
1872
  * is registered (i.e. not excluded/
2420
1873
  * blocked/outside an allowlist -- the registry itself is built with that same filter,
2421
1874
  * so registry presence already tracks "allowed"). This is enforced here, not just in
@@ -2449,7 +1902,8 @@ export class AgentSession {
2449
1902
  }
2450
1903
  if (validToolNames.includes("grep") ||
2451
1904
  validToolNames.includes("find") ||
2452
- validToolNames.includes("run_toolkit_script")) {
1905
+ validToolNames.includes("run_toolkit_script") ||
1906
+ validToolNames.includes("ask_question")) {
2453
1907
  addIfRegistered("artifact_retrieve");
2454
1908
  }
2455
1909
  if (validToolNames.includes("delegate")) {
@@ -2463,9 +1917,7 @@ export class AgentSession {
2463
1917
  }
2464
1918
  /** Whether compaction or branch summarization is currently running */
2465
1919
  get isCompacting() {
2466
- return (this._autoCompactionAbortController !== undefined ||
2467
- this._compactionAbortController !== undefined ||
2468
- this._branchSummaryAbortController !== undefined);
1920
+ return this._compaction.isRunning() || this._branchSummaryAbortController !== undefined;
2469
1921
  }
2470
1922
  /** All messages including custom types like BashExecutionMessage */
2471
1923
  get messages() {
@@ -2536,7 +1988,7 @@ export class AgentSession {
2536
1988
  try {
2537
1989
  const maxGoalLoopRounds = this.settingsManager.getAutonomySettings().maxStallTurns;
2538
1990
  this.agent.maxStallTurns = maxGoalLoopRounds;
2539
- await this._ensureTextToolProtocolForActiveModel();
1991
+ await this._toolProtocol.ensureActiveModelProtocol();
2540
1992
  let goalLoopRounds = 1;
2541
1993
  await this.agent.prompt(messages);
2542
1994
  while ((maxGoalLoopRounds === 0 || goalLoopRounds < maxGoalLoopRounds) && (await this._handlePostAgentRun())) {
@@ -2549,6 +2001,187 @@ export class AgentSession {
2549
2001
  await this._drainQueuedExtensionCommands();
2550
2002
  }
2551
2003
  }
2004
+ _queueWorkerHumanInput(request) {
2005
+ const existing = getLatestHumanInputSnapshots(this.sessionManager).find((snapshot) => snapshot.request.workerRequestId === request.workerRequestId);
2006
+ if (existing)
2007
+ return;
2008
+ const blockerText = request.blockers
2009
+ .slice(0, 4)
2010
+ .map((blocker) => blocker.slice(0, 500))
2011
+ .join("; ");
2012
+ const question = createHumanInputRequest({
2013
+ source: "worker",
2014
+ workerRequestId: request.workerRequestId,
2015
+ questions: [
2016
+ {
2017
+ id: "worker_review",
2018
+ header: "Worker review",
2019
+ question: `${request.message.slice(0, 1_500)}${blockerText ? ` Blockers: ${blockerText}` : ""}`,
2020
+ options: [
2021
+ {
2022
+ label: "Review now",
2023
+ description: "Have the parent inspect the worker claim and evidence before reconciling it.",
2024
+ },
2025
+ {
2026
+ label: "Keep blocked",
2027
+ description: "Leave the worker result blocked and continue without accepting its output.",
2028
+ },
2029
+ ],
2030
+ },
2031
+ ],
2032
+ acceptsImages: false,
2033
+ });
2034
+ beginHumanInputRequest(this.sessionManager, question);
2035
+ if (!this.isStreaming)
2036
+ this._scheduleWorkerHumanInput(question);
2037
+ }
2038
+ _schedulePendingWorkerHumanInputs() {
2039
+ for (const snapshot of getWorkerHumanInputsRequiringDelivery(this.sessionManager)) {
2040
+ this._scheduleWorkerHumanInput(snapshot);
2041
+ }
2042
+ }
2043
+ _scheduleWorkerHumanInput(input) {
2044
+ const snapshot = "status" in input ? input : { request: input, status: "pending", answers: [], updatedAt: input.createdAt };
2045
+ const requestId = snapshot.request.requestId;
2046
+ if (this._activeHumanInputRequestIds.has(requestId))
2047
+ return;
2048
+ this._activeHumanInputRequestIds.add(requestId);
2049
+ this._humanInputTail = this._humanInputTail
2050
+ .then(async () => {
2051
+ await this._resolveWorkerHumanInput(snapshot);
2052
+ })
2053
+ .catch((error) => {
2054
+ this._emit({
2055
+ type: "warning",
2056
+ message: `Worker owner-input request failed: ${error instanceof Error ? error.message : String(error)}`,
2057
+ });
2058
+ })
2059
+ .finally(() => {
2060
+ this._activeHumanInputRequestIds.delete(requestId);
2061
+ });
2062
+ }
2063
+ async _resolveWorkerHumanInput(initial) {
2064
+ const ui = this._extensionUIContext;
2065
+ if (!ui || this._extensionMode === "print")
2066
+ return false;
2067
+ // This path may be scheduled by agent_end. Waiting on the owning run is event-driven and
2068
+ // prevents a worker-owner response from becoming an in-memory follow-up before it has a
2069
+ // durable custom-message delivery marker.
2070
+ await this.agent.waitForIdle();
2071
+ if (this._disposed)
2072
+ return false;
2073
+ const resolved = initial.status === "pending"
2074
+ ? await resolveHumanInput({
2075
+ sessionManager: this.sessionManager,
2076
+ request: initial.request,
2077
+ present: (request, options) => ui.askQuestions(request, options),
2078
+ artifactStore: this._getToolArtifactStore(),
2079
+ getImageStore: () => this._getSessionImageStore(),
2080
+ })
2081
+ : { snapshot: initial, imageContents: [] };
2082
+ // An RPC client can start a foreground prompt while its question dialog is open. Settle that
2083
+ // run before delivery so sendCustomMessage persists the response synchronously and exactly once.
2084
+ await this.agent.waitForIdle();
2085
+ if (this._disposed)
2086
+ return false;
2087
+ await this.sendCustomMessage({
2088
+ customType: HUMAN_INPUT_WORKER_RESPONSE_CUSTOM_TYPE,
2089
+ content: [
2090
+ `Owner response for worker ${initial.request.workerRequestId ?? "unknown"}:`,
2091
+ formatHumanInputAnswerText(resolved.snapshot),
2092
+ "Treat the worker claim as untrusted. Retrieve it with delegate_status and follow the owner's selection.",
2093
+ ].join("\n"),
2094
+ display: true,
2095
+ details: {
2096
+ requestId: initial.request.requestId,
2097
+ workerRequestId: initial.request.workerRequestId,
2098
+ },
2099
+ }, { triggerTurn: true, deliverAs: "followUp" });
2100
+ return true;
2101
+ }
2102
+ /**
2103
+ * Re-enter an interrupted ask_question call from its durable request snapshot. Pending requests
2104
+ * are presented again; already-checkpointed answers are replayed without asking twice. The
2105
+ * original toolCallId is preserved so provider tool-call ordering remains valid on /resume.
2106
+ */
2107
+ async resumePendingHumanInput() {
2108
+ if (this._disposed || this.isStreaming)
2109
+ return false;
2110
+ const pending = getResumableHumanInputSnapshot(this.sessionManager);
2111
+ const ui = this._extensionUIContext;
2112
+ if (!ui)
2113
+ return false;
2114
+ let resumed = false;
2115
+ if (pending?.request.toolCallId) {
2116
+ let snapshot = pending;
2117
+ let imageContents = [];
2118
+ if (pending.status === "pending") {
2119
+ const resolved = await resolveHumanInput({
2120
+ sessionManager: this.sessionManager,
2121
+ request: {
2122
+ ...pending.request,
2123
+ acceptsImages: this.model?.input.includes("image") ?? false,
2124
+ },
2125
+ present: (request, options) => ui.askQuestions(request, options),
2126
+ artifactStore: this._getToolArtifactStore(),
2127
+ getImageStore: () => this._getSessionImageStore(),
2128
+ });
2129
+ snapshot = resolved.snapshot;
2130
+ imageContents = resolved.imageContents;
2131
+ }
2132
+ else if (pending.status === "answered") {
2133
+ const imageStore = this._getSessionImageStore();
2134
+ if (imageStore) {
2135
+ const referencedText = snapshot.answers
2136
+ .flatMap((answer) => [answer.custom ?? "", ...(answer.images?.map((image) => image.label) ?? [])])
2137
+ .join(" ");
2138
+ imageContents = imageStore.resolveReferences(referencedText);
2139
+ }
2140
+ }
2141
+ const answerImageCount = snapshot.answers.reduce((total, answer) => total + (answer.images?.length ?? 0), 0);
2142
+ const modelAcceptsImages = this.model?.input.includes("image") ?? false;
2143
+ const missingImageNotice = answerImageCount > imageContents.length
2144
+ ? `\n\n[${answerImageCount - imageContents.length} attached image(s) could not be restored from durable storage.]`
2145
+ : "";
2146
+ const unsupportedImageNotice = imageContents.length > 0 && !modelAcceptsImages
2147
+ ? "\n\n[Attached images were retained but not sent because the selected model does not accept image input.]"
2148
+ : "";
2149
+ const toolResult = {
2150
+ role: "toolResult",
2151
+ toolCallId: pending.request.toolCallId,
2152
+ toolName: pending.request.toolName ?? "ask_question",
2153
+ content: [
2154
+ {
2155
+ type: "text",
2156
+ text: `${formatHumanInputAnswerText(snapshot)}${missingImageNotice}${unsupportedImageNotice}`,
2157
+ },
2158
+ ...(modelAcceptsImages ? imageContents : []),
2159
+ ],
2160
+ details: {
2161
+ questions: snapshot.request.questions,
2162
+ answers: snapshot.answers,
2163
+ cancelled: snapshot.status === "cancelled",
2164
+ ...(snapshot.reason ? { reason: snapshot.reason } : {}),
2165
+ },
2166
+ isError: false,
2167
+ timestamp: Date.now(),
2168
+ };
2169
+ await this._runAgentPrompt(toolResult);
2170
+ resumed = true;
2171
+ }
2172
+ for (const workerInput of getWorkerHumanInputsRequiringDelivery(this.sessionManager)) {
2173
+ if (this._activeHumanInputRequestIds.has(workerInput.request.requestId))
2174
+ continue;
2175
+ this._activeHumanInputRequestIds.add(workerInput.request.requestId);
2176
+ try {
2177
+ resumed = (await this._resolveWorkerHumanInput(workerInput)) || resumed;
2178
+ }
2179
+ finally {
2180
+ this._activeHumanInputRequestIds.delete(workerInput.request.requestId);
2181
+ }
2182
+ }
2183
+ return resumed;
2184
+ }
2552
2185
  /**
2553
2186
  * Shared {@link OllamaRuntime} for a given server, lazily created and cached by baseUrl so every
2554
2187
  * caller — the router's readiness gate and any host UI's own model-lifecycle commands (e.g.
@@ -2606,40 +2239,7 @@ export class AgentSession {
2606
2239
  return this._modelRouter.getStatus(formatLabel);
2607
2240
  }
2608
2241
  async _handlePostAgentRun() {
2609
- const msg = this._lastAssistantMessage;
2610
- this._lastAssistantMessage = undefined;
2611
- if (!msg) {
2612
- return false;
2613
- }
2614
- const classified = this._classifyAssistantError(msg);
2615
- if (classified) {
2616
- this._failureCorpus.record({
2617
- provider: msg.provider,
2618
- modelId: msg.model,
2619
- message: msg.errorMessage ?? "",
2620
- classified,
2621
- });
2622
- }
2623
- if (classified?.retryable && (await this._prepareRetry(msg))) {
2624
- return true;
2625
- }
2626
- if (await this._billingFailover.handleAssistantError(msg, classified))
2627
- return false;
2628
- if (msg.stopReason === "error" && this._retryController.attempt > 0) {
2629
- this._emit({
2630
- type: "auto_retry_end",
2631
- success: false,
2632
- attempt: this._retryController.attempt,
2633
- finalError: msg.errorMessage,
2634
- });
2635
- this._retryController.reset();
2636
- }
2637
- if (await this._checkCompaction(msg)) {
2638
- return true;
2639
- }
2640
- // The agent loop drains both queues before emitting agent_end. Any messages
2641
- // here were queued by agent_end extension handlers and need a continuation.
2642
- return this.agent.hasQueuedMessages();
2242
+ return this._foregroundRecovery.handlePostAgentRun();
2643
2243
  }
2644
2244
  /**
2645
2245
  * Send a prompt to the agent.
@@ -2662,11 +2262,7 @@ export class AgentSession {
2662
2262
  return this._promptUnserialized(text, options);
2663
2263
  }
2664
2264
  async _promptUnserialized(text, options) {
2665
- // Start of a new foreground prompt cycle -- rebaseline the cost guard's background-spend
2666
- // window so a PRIOR turn's background/spawned spend doesn't keep this turn's guard permanently
2667
- // tripped. Every round trip within this same turn (tool-call iterations) shares this baseline.
2668
- this._costGuardTurnBaselineUsd = this.getSpawnedUsage().cost;
2669
- this._applyToolRepairLayerSettings();
2265
+ this._toolProtocol.applyRepairLayerSettings();
2670
2266
  this._cancelPrefixWarm();
2671
2267
  const expandPromptTemplates = options?.expandPromptTemplates ?? true;
2672
2268
  const processSlashCommands = options?.processSlashCommands ?? expandPromptTemplates;
@@ -2678,6 +2274,8 @@ export class AgentSession {
2678
2274
  // selected/authenticated — can un-register it from _earlyDisplayedUserMessages instead of
2679
2275
  // leaking the reference forever.
2680
2276
  let userMessage;
2277
+ let routingStarted = false;
2278
+ let pendingNextTurnCount = 0;
2681
2279
  // Effectiveness feedback: remember the recall page + the query so we can score, after the
2682
2280
  // response, whether the agent actually used the recalled context.
2683
2281
  let injectedRecall = "";
@@ -2738,6 +2336,9 @@ export class AgentSession {
2738
2336
  preflightResult?.(true);
2739
2337
  return;
2740
2338
  }
2339
+ // This submission is starting a new foreground turn. Queued steer/follow-up messages above
2340
+ // remain part of the already-active turn and must not erase its spawned-cost baseline.
2341
+ this._costGuardTurnBaselineUsd = this.getSpawnedUsage().cost;
2741
2342
  // Flush any pending bash messages before the new prompt
2742
2343
  this._flushPendingBashMessages();
2743
2344
  // Build the user message now — before the router judge — and paint it to the UI
@@ -2761,6 +2362,7 @@ export class AgentSession {
2761
2362
  // prompt with nothing happening for however long the judge takes. routing_end is emitted
2762
2363
  // exactly once below: either in the catch block (this phase failed) or right after the try
2763
2364
  // block (this phase succeeded, whether or not it produced a turn to run).
2365
+ routingStarted = true;
2764
2366
  this._emit({ type: "routing_start" });
2765
2367
  const resolvedRouteInfo = await this._modelRouter.resolveTurnRouteJudged(expandedText, {
2766
2368
  // Internally generated turns (goal continuation, lane follow-ups) never consult the judge:
@@ -2777,6 +2379,9 @@ export class AgentSession {
2777
2379
  if (!requestModel) {
2778
2380
  throw new Error(formatNoModelSelectedMessage());
2779
2381
  }
2382
+ if (currentImages && currentImages.length > 0 && !requestModel.input.includes("image")) {
2383
+ throw new Error(`Model "${requestModel.provider}/${requestModel.id}" does not accept image input. Select an image-capable model or route the inspection to a vision worker.`);
2384
+ }
2780
2385
  // A manual/default local model has no RouteDecision, so the router readiness gate above is
2781
2386
  // intentionally a no-op. It still needs the same managed-runtime boot/residency guarantee.
2782
2387
  if (!resolvedRouteInfo) {
@@ -2826,10 +2431,10 @@ export class AgentSession {
2826
2431
  // immediately — see the early message_start emit above).
2827
2432
  messages.push(userMessage);
2828
2433
  // Inject any pending "nextTurn" messages as context alongside the user message
2829
- for (const msg of this._pendingNextTurnMessages) {
2434
+ pendingNextTurnCount = this._pendingNextTurnMessages.length;
2435
+ for (const msg of this._pendingNextTurnMessages.slice(0, pendingNextTurnCount)) {
2830
2436
  messages.push(msg);
2831
2437
  }
2832
- this._pendingNextTurnMessages = [];
2833
2438
  const taskStepsState = this.getTaskStepsStateSnapshot();
2834
2439
  const taskStepsContext = taskStepsState ? formatTaskStepsContext(taskStepsState) : undefined;
2835
2440
  if (taskStepsState && taskStepsContext) {
@@ -2858,6 +2463,9 @@ export class AgentSession {
2858
2463
  // Ensure we're using the base prompt (in case previous turn had modifications)
2859
2464
  this.agent.state.systemPrompt = this._baseSystemPrompt;
2860
2465
  }
2466
+ // Commit consumption only after all fallible preflight hooks have succeeded. Messages added
2467
+ // while the hook was running remain queued for the following turn.
2468
+ this._pendingNextTurnMessages.splice(0, pendingNextTurnCount);
2861
2469
  }
2862
2470
  catch (error) {
2863
2471
  // The turn never reached _runAgentPrompt, so the authoritative message_start that would
@@ -2868,22 +2476,31 @@ export class AgentSession {
2868
2476
  }
2869
2477
  // The routing/prep phase (routing_start above) failed before ever reaching the turn — end
2870
2478
  // it here, or the UI's "working" indicator for it spins forever with nothing behind it.
2871
- this._emit({ type: "routing_end" });
2479
+ if (routingStarted)
2480
+ this._emit({ type: "routing_end" });
2872
2481
  preflightResult?.(false);
2873
2482
  throw error;
2874
2483
  }
2875
2484
  // The routing/prep phase is over — either we're about to hand off into the turn (which emits
2876
2485
  // its own agent_start/streaming events right after), or messages is unexpectedly unset and we
2877
2486
  // bail below. Either way nothing is left "routing" past this point.
2878
- this._emit({ type: "routing_end" });
2487
+ if (routingStarted)
2488
+ this._emit({ type: "routing_end" });
2879
2489
  if (!messages) {
2880
2490
  return;
2881
2491
  }
2882
2492
  preflightResult?.(true);
2883
- this._textProtocolParseObservedThisTurn = false;
2884
- this._textProtocolValidationOutcomeThisTurn = undefined;
2885
- await this._modelRouter.runRoutedTurn(messages, routedTurnModel, routedTurnRouteDecision);
2886
- this._recordTextToolProtocolParseOutcomeFromLastAssistant();
2493
+ try {
2494
+ this._toolProtocol.resetTurnState();
2495
+ await this._modelRouter.runRoutedTurn(messages, routedTurnModel, routedTurnRouteDecision);
2496
+ this._toolProtocol.recordParseOutcomeFromLastAssistant();
2497
+ }
2498
+ finally {
2499
+ // Normally consumed by the authoritative message_start. If execution failed before that
2500
+ // event, do not retain the early-painted message identity indefinitely.
2501
+ if (userMessage)
2502
+ this._earlyDisplayedUserMessages.delete(userMessage);
2503
+ }
2887
2504
  // Score whether the agent actually used the recalled context, so the recall gate can adapt.
2888
2505
  if (injectedRecall) {
2889
2506
  const response = this._findLastAssistantMessage();
@@ -3251,166 +2868,13 @@ export class AgentSession {
3251
2868
  * @param customInstructions Optional instructions for the compaction summary
3252
2869
  */
3253
2870
  async compact(customInstructions) {
3254
- this._disconnectFromAgent();
3255
- await this.abort();
3256
- this._compactionAbortController = new AbortController();
3257
- this._emit({ type: "compaction_start", reason: "manual" });
3258
- try {
3259
- if (!this.model) {
3260
- throw new Error(formatNoModelSelectedMessage());
3261
- }
3262
- const sessionModel = this.model;
3263
- const selectedCompactionModel = this._resolveCompactionModel(sessionModel);
3264
- if (this._isRawStreamSimple(this.agent.streamFn)) {
3265
- await this._getCompactionRequestAuth(selectedCompactionModel);
3266
- }
3267
- const selectionReason = this._getLastCompactionSelectionReason() ?? "unknown";
3268
- const settings = this._getAdaptedCompactionSettings();
3269
- const initialBranch = this.sessionManager.getBranch();
3270
- const initialPreparation = prepareCompaction(initialBranch, settings);
3271
- if (!initialPreparation) {
3272
- const lastEntry = initialBranch[initialBranch.length - 1];
3273
- if (lastEntry?.type === "compaction") {
3274
- throw new Error("Already compacted");
3275
- }
3276
- throw new Error("Nothing to compact (session too small)");
3277
- }
3278
- let extensionCompaction;
3279
- let fromExtension = false;
3280
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
3281
- const result = (await this._extensionRunner.emit({
3282
- type: "session_before_compact",
3283
- preparation: initialPreparation,
3284
- branchEntries: initialBranch,
3285
- customInstructions,
3286
- signal: this._compactionAbortController.signal,
3287
- }));
3288
- if (result?.cancel)
3289
- throw new Error("Compaction cancelled");
3290
- if (result?.compaction) {
3291
- extensionCompaction = result.compaction;
3292
- fromExtension = true;
3293
- }
3294
- }
3295
- if (extensionCompaction) {
3296
- this.sessionManager.appendCompaction(extensionCompaction.summary, extensionCompaction.firstKeptEntryId, extensionCompaction.tokensBefore, extensionCompaction.details, true);
3297
- this._refreshAfterCompaction();
3298
- const savedCompactionEntry = this.sessionManager
3299
- .getEntries()
3300
- .find((entry) => entry.type === "compaction" && entry.summary === extensionCompaction.summary);
3301
- if (this._extensionRunner && savedCompactionEntry) {
3302
- await this._extensionRunner.emit({
3303
- type: "session_compact",
3304
- compactionEntry: savedCompactionEntry,
3305
- fromExtension: true,
3306
- });
3307
- }
3308
- this._emit({
3309
- type: "compaction_end",
3310
- reason: "manual",
3311
- result: extensionCompaction,
3312
- aborted: false,
3313
- willRetry: false,
3314
- });
3315
- return extensionCompaction;
3316
- }
3317
- let appliedResult;
3318
- const signal = this._compactionAbortController.signal;
3319
- const outcome = await runCompactionLoop({
3320
- measureLiveTokens: () => Math.max(this._estimateCurrentContextTokens(this.agent.state.messages), 1),
3321
- shouldCompact: () => true,
3322
- getPostApplyMargin: () => 0,
3323
- getBranch: () => this.sessionManager.getBranch(),
3324
- getBaseKeepRecentTokens: () => settings.keepRecentTokens,
3325
- resolveModelAndAuth: async (modelTier) => {
3326
- const model = modelTier === "cheap" ? selectedCompactionModel : sessionModel;
3327
- // Return the resolution result AS-IS: it may have fallen back to a different model
3328
- // (e.g. session model, when the tier's model failed auth or the readiness gate),
3329
- // and `failure` must reach the retry loop's auth-failed escalation rather than be
3330
- // dropped — dropping it would pair the wrong model with the fallback's credentials
3331
- // and silently skip the loop's visible failure/escalation handling.
3332
- return this._resolveCompactionModelAndAuth(model, sessionModel);
3333
- },
3334
- summarizeAndVerify: async (params, model, apiKey, headers, branch) => {
3335
- const preparation = prepareCompaction(branch, {
3336
- ...settings,
3337
- keepRecentTokens: params.keepRecentTokens,
3338
- }, { allowTrailingCompactionAsPrevious: true });
3339
- if (!preparation)
3340
- throw new Error("Nothing to compact (session too small)");
3341
- if (extensionCompaction)
3342
- return { result: extensionCompaction };
3343
- const compactionThinkingLevel = this._resolveCompactionThinkingLevel(model, sessionModel);
3344
- const result = await this._compactWithRetry(() => compact(preparation, model, apiKey, headers, customInstructions, signal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest(), { chunked: params.chunked }), signal, model.provider);
3345
- return { result };
3346
- },
3347
- buildDeterministicCheckpoint: () => ({ result: createDeterministicCompaction(initialPreparation) }),
3348
- apply: async (result) => {
3349
- if (signal.aborted)
3350
- throw new Error("Compaction cancelled");
3351
- this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, fromExtension);
3352
- this._refreshAfterCompaction();
3353
- const savedCompactionEntry = this.sessionManager
3354
- .getEntries()
3355
- .find((entry) => entry.type === "compaction" && entry.summary === result.summary);
3356
- if (this._extensionRunner && savedCompactionEntry) {
3357
- await this._extensionRunner.emit({
3358
- type: "session_compact",
3359
- compactionEntry: savedCompactionEntry,
3360
- fromExtension,
3361
- });
3362
- }
3363
- appliedResult = result;
3364
- },
3365
- verifyPostApplyEffect: () => false,
3366
- onTransition: ({ cycle, cause, detail }) => {
3367
- this._emit({
3368
- type: "warning",
3369
- message: `manual compaction cycle ${cycle}: ${cause}${detail ? ` (${detail})` : ""} — retrying from step 0 (${this._describeCompactionSummarizer()})`,
3370
- });
3371
- },
3372
- signal,
3373
- });
3374
- if (outcome.kind === "failed") {
3375
- if (outcome.reason === "aborted")
3376
- throw new Error("Compaction cancelled");
3377
- throw new Error(`manual compaction failed after retry ladder using ${selectedCompactionModel.provider}/${selectedCompactionModel.id} (${selectionReason}); first failure: ${outcome.reason}`);
3378
- }
3379
- if (outcome.kind === "skip" || !appliedResult)
3380
- throw new Error(outcome.kind === "skip" ? outcome.reason : "Compaction failed");
3381
- this._emit({
3382
- type: "compaction_end",
3383
- reason: "manual",
3384
- result: appliedResult,
3385
- aborted: false,
3386
- willRetry: false,
3387
- });
3388
- return appliedResult;
3389
- }
3390
- catch (error) {
3391
- const message = error instanceof Error ? error.message : String(error);
3392
- const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
3393
- this._emit({
3394
- type: "compaction_end",
3395
- reason: "manual",
3396
- result: undefined,
3397
- aborted,
3398
- willRetry: false,
3399
- errorMessage: aborted ? undefined : `Compaction failed: ${message}`,
3400
- });
3401
- throw error;
3402
- }
3403
- finally {
3404
- this._compactionAbortController = undefined;
3405
- this._reconnectToAgent();
3406
- }
2871
+ return this._compaction.compact(customInstructions);
3407
2872
  }
3408
2873
  /**
3409
2874
  * Cancel in-progress compaction (manual or auto).
3410
2875
  */
3411
2876
  abortCompaction() {
3412
- this._compactionAbortController?.abort();
3413
- this._autoCompactionAbortController?.abort();
2877
+ this._compaction.abort();
3414
2878
  }
3415
2879
  /**
3416
2880
  * Cancel in-progress branch summarization.
@@ -3433,280 +2897,19 @@ export class AgentSession {
3433
2897
  return this._compactionSupport.getAdaptedSettings();
3434
2898
  }
3435
2899
  _checkContextWindowUsageWarning() {
3436
- if (!this.model)
3437
- return;
3438
- const contextWindow = this.model.contextWindow ?? 0;
3439
- if (contextWindow <= 0)
3440
- return;
3441
- const systemPromptTokens = Math.ceil((this.agent.state.systemPrompt ?? "").length / 4);
3442
- let toolsChars = 0;
3443
- for (const tool of this.agent.state.tools || []) {
3444
- toolsChars += tool.name.length;
3445
- toolsChars += tool.description?.length ?? 0;
3446
- if (tool.parameters) {
3447
- toolsChars += JSON.stringify(tool.parameters).length;
3448
- }
3449
- }
3450
- const toolsTokens = Math.ceil(toolsChars / 4);
3451
- const baseTokens = systemPromptTokens + toolsTokens;
3452
- if (baseTokens >= contextWindow) {
3453
- this._emit({
3454
- type: "warning",
3455
- message: `Base configuration (system prompt and active tools) consumes ${baseTokens} tokens, which exceeds the model's context window of ${contextWindow} tokens. The model cannot process any prompts in this state.`,
3456
- });
3457
- }
3458
- else if (baseTokens >= contextWindow * 0.7) {
3459
- this._emit({
3460
- type: "warning",
3461
- message: `Base configuration (system prompt and active tools) consumes ${baseTokens} tokens (${Math.round((baseTokens / contextWindow) * 100)}% of the ${contextWindow} context window). This leaves very little room for conversation history and may cause immediate compaction or context overflow.`,
3462
- });
3463
- }
2900
+ this._compaction.checkContextWindowUsageWarning();
3464
2901
  }
3465
- async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
3466
- const settings = this._getAdaptedCompactionSettings();
3467
- if (!settings.enabled)
3468
- return false;
3469
- // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
3470
- if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
3471
- return false;
3472
- const contextWindow = this.model?.contextWindow ?? 0;
3473
- // Skip overflow check if the message came from a different model.
3474
- // This handles the case where user switched from a smaller-context model (e.g. opus)
3475
- // to a larger-context model (e.g. codex) - the overflow error from the old model
3476
- // shouldn't trigger compaction for the new model.
3477
- const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
3478
- // Skip compaction checks if this assistant message is older than the latest
3479
- // compaction boundary. This prevents a stale pre-compaction usage/error
3480
- // from retriggering compaction on the first prompt after compaction.
3481
- const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
3482
- const assistantIsFromBeforeCompaction = compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
3483
- if (assistantIsFromBeforeCompaction) {
3484
- return false;
3485
- }
3486
- // Case 1: Overflow - LLM returned context overflow error
3487
- if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
3488
- if (this._overflowRecoveryAttempted) {
3489
- this._emit({
3490
- type: "compaction_end",
3491
- reason: "overflow",
3492
- result: undefined,
3493
- aborted: false,
3494
- willRetry: false,
3495
- errorMessage: "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.",
3496
- });
3497
- return false;
3498
- }
3499
- this._overflowRecoveryAttempted = true;
3500
- // Remove the error message from agent state (it IS saved to session for history,
3501
- // but we don't want it in context for the retry)
3502
- const messages = this.agent.state.messages;
3503
- if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
3504
- this.agent.state.messages = messages.slice(0, -1);
3505
- }
3506
- return await this._runAutoCompaction("overflow", true);
3507
- }
3508
- // Case 2: Threshold - context is getting large
3509
- // For error messages (no usage data), estimate from last successful response.
3510
- // This ensures sessions that hit persistent API errors (e.g. 529) can still compact.
3511
- let contextTokens;
3512
- if (assistantMessage.stopReason === "error") {
3513
- const messages = this.agent.state.messages;
3514
- const estimate = estimateContextTokens(messages);
3515
- if (estimate.lastUsageIndex === null)
3516
- return false; // No usage data at all
3517
- // Verify the usage source is post-compaction. Kept pre-compaction messages
3518
- // have stale usage reflecting the old (larger) context and would falsely
3519
- // trigger compaction right after one just finished.
3520
- const usageMsg = messages[estimate.lastUsageIndex];
3521
- if (compactionEntry &&
3522
- usageMsg.role === "assistant" &&
3523
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
3524
- return false;
3525
- }
3526
- contextTokens = estimate.tokens;
3527
- }
3528
- else {
3529
- contextTokens = calculateContextTokens(assistantMessage.usage);
3530
- const estimate = estimateContextTokens(this.agent.state.messages);
3531
- if (estimate.lastUsageIndex !== null) {
3532
- const usageMsg = this.agent.state.messages[estimate.lastUsageIndex];
3533
- const usageIsPostCompaction = !(compactionEntry &&
3534
- usageMsg.role === "assistant" &&
3535
- usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime());
3536
- if (usageIsPostCompaction) {
3537
- contextTokens = Math.max(contextTokens, estimate.tokens);
3538
- }
3539
- }
3540
- }
3541
- if (shouldCompact(contextTokens, contextWindow, settings, this.model?.autoCompactionTriggerTokens)) {
3542
- return await this._runAutoCompaction("threshold", false);
3543
- }
3544
- return false;
2902
+ _checkCompaction(assistantMessage, skipAbortedCheck = true) {
2903
+ return this._compaction.check(assistantMessage, skipAbortedCheck);
3545
2904
  }
3546
2905
  _measureLiveContextTokensForCompaction() {
3547
- const estimatedTokens = this._pipeline.estimateCurrentContextTokens(this.agent.state.messages);
3548
- const assistantMessage = findLastAssistantMessage(this.agent.state.messages);
3549
- if (!assistantMessage || assistantMessage.stopReason === "error" || assistantMessage.stopReason === "aborted") {
3550
- return estimatedTokens;
3551
- }
3552
- const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
3553
- if (compactionEntry && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime()) {
3554
- return estimatedTokens;
3555
- }
3556
- return Math.max(calculateContextTokens(assistantMessage.usage), estimatedTokens);
2906
+ return this._compaction.measureLiveContextTokens();
3557
2907
  }
3558
2908
  /**
3559
2909
  * Internal: Run auto-compaction with events.
3560
2910
  */
3561
- async _runAutoCompaction(reason, willRetry) {
3562
- const settings = this._getAdaptedCompactionSettings();
3563
- const model = this.model;
3564
- this._emit({ type: "compaction_start", reason });
3565
- const hadQueuedMessages = this.agent.hasQueuedMessages();
3566
- this._autoCompactionAbortController = new AbortController();
3567
- const signal = this._autoCompactionAbortController.signal;
3568
- let fromExtension = false;
3569
- let lastCompaction;
3570
- let extensionCancelled = false;
3571
- try {
3572
- if (!model) {
3573
- this._emit({
3574
- type: "compaction_end",
3575
- reason,
3576
- result: undefined,
3577
- aborted: false,
3578
- willRetry: false,
3579
- skipReason: "no model selected",
3580
- });
3581
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3582
- }
3583
- const contextWindow = model.contextWindow;
3584
- const margin = Math.max(0, Math.floor(0.01 * contextWindow));
3585
- const outcome = await runCompactionLoop({
3586
- getBranch: () => this.sessionManager.getBranch(),
3587
- measureLiveTokens: () => this._measureLiveContextTokensForCompaction(),
3588
- shouldCompact: reason === "overflow"
3589
- ? () => true
3590
- : (tokens) => shouldCompact(tokens, contextWindow, settings, model.autoCompactionTriggerTokens),
3591
- getPostApplyMargin: () => margin,
3592
- getBaseKeepRecentTokens: () => settings.keepRecentTokens,
3593
- resolveModelAndAuth: async (modelTier) => this._resolveCompactionModelAndAuth(modelTier === "session" ? model : this._resolveCompactionModel(model), model),
3594
- summarizeAndVerify: async (params, compactModel, apiKey, headers, branchEntries) => {
3595
- fromExtension = false;
3596
- const adaptedSettings = { ...settings, keepRecentTokens: params.keepRecentTokens };
3597
- const preparation = prepareCompaction(branchEntries, adaptedSettings);
3598
- if (!preparation) {
3599
- throw new Error("already compacted");
3600
- }
3601
- const compactionThinkingLevel = this._resolveCompactionThinkingLevel(compactModel, model);
3602
- if (this._extensionRunner.hasHandlers("session_before_compact")) {
3603
- const extensionResult = (await this._extensionRunner.emit({
3604
- type: "session_before_compact",
3605
- preparation,
3606
- branchEntries,
3607
- customInstructions: undefined,
3608
- signal,
3609
- }));
3610
- if (extensionResult?.cancel) {
3611
- extensionCancelled = true;
3612
- throw new Error("auto-compaction-cancelled");
3613
- }
3614
- if (extensionResult?.compaction) {
3615
- fromExtension = true;
3616
- return { result: extensionResult.compaction };
3617
- }
3618
- }
3619
- const compactResult = await this._compactWithRetry(() => compact(preparation, compactModel, apiKey, headers, undefined, signal, compactionThinkingLevel, this.agent.streamFn, this._buildCompactionPreDigest(), { chunked: params.chunked }), signal, compactModel.provider);
3620
- return { result: compactResult };
3621
- },
3622
- buildDeterministicCheckpoint: async () => {
3623
- const branch = this.sessionManager.getBranch();
3624
- const preparation = prepareCompaction(branch, {
3625
- ...settings,
3626
- });
3627
- if (!preparation) {
3628
- throw new Error("already compacted");
3629
- }
3630
- fromExtension = false;
3631
- return { result: createDeterministicCompaction(preparation) };
3632
- },
3633
- apply: async (result) => {
3634
- lastCompaction = result;
3635
- this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, fromExtension);
3636
- this._refreshAfterCompaction();
3637
- const newEntries = this.sessionManager.getEntries();
3638
- const savedCompactionEntry = newEntries.find((entry) => entry.type === "compaction" && entry.summary === result.summary);
3639
- if (this._extensionRunner && savedCompactionEntry) {
3640
- await this._extensionRunner.emit({
3641
- type: "session_compact",
3642
- compactionEntry: savedCompactionEntry,
3643
- fromExtension,
3644
- });
3645
- }
3646
- },
3647
- verifyPostApplyEffect: reason === "overflow" ? () => false : undefined,
3648
- onTransition: ({ cycle, cause, detail }) => {
3649
- this._emit({
3650
- type: "warning",
3651
- message: `auto-compaction cycle ${cycle}: ${cause}${detail ? ` (${detail})` : ""} — retrying from step 0 (${this._describeCompactionSummarizer()})`,
3652
- });
3653
- },
3654
- signal,
3655
- });
3656
- if (outcome.kind === "skip") {
3657
- this._emit({
3658
- type: "compaction_end",
3659
- reason,
3660
- result: undefined,
3661
- aborted: false,
3662
- willRetry: false,
3663
- skipReason: outcome.reason,
3664
- });
3665
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3666
- }
3667
- if (outcome.kind === "failed") {
3668
- if (outcome.reason === "aborted") {
3669
- this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
3670
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3671
- }
3672
- throw new Error(outcome.reason);
3673
- }
3674
- if (extensionCancelled || signal.aborted) {
3675
- this._emit({ type: "compaction_end", reason, result: undefined, aborted: true, willRetry: false });
3676
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3677
- }
3678
- const result = outcome.kind === "success" ? outcome.result : lastCompaction;
3679
- if (!result) {
3680
- throw new Error("Auto-compaction succeeded without a result");
3681
- }
3682
- this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
3683
- if (willRetry) {
3684
- const messages = this.agent.state.messages;
3685
- const lastMsg = messages[messages.length - 1];
3686
- if (lastMsg?.role === "assistant" && lastMsg.stopReason === "error") {
3687
- this.agent.state.messages = messages.slice(0, -1);
3688
- }
3689
- return true;
3690
- }
3691
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3692
- }
3693
- catch (error) {
3694
- const errorMessage = error instanceof Error ? error.message : "compaction failed";
3695
- this._emit({
3696
- type: "compaction_end",
3697
- reason,
3698
- result: undefined,
3699
- aborted: false,
3700
- willRetry: false,
3701
- errorMessage: reason === "overflow"
3702
- ? `Context overflow recovery failed: ${errorMessage}`
3703
- : `Auto-compaction failed: ${errorMessage}`,
3704
- });
3705
- return hadQueuedMessages || this.agent.hasQueuedMessages();
3706
- }
3707
- finally {
3708
- this._autoCompactionAbortController = undefined;
3709
- }
2911
+ _runAutoCompaction(reason, willRetry) {
2912
+ return this._compaction.runAuto(reason, willRetry);
3710
2913
  }
3711
2914
  /**
3712
2915
  * Run one compaction attempt, retrying retryable provider failures (stream stalls,
@@ -3716,30 +2919,8 @@ export class AgentSession {
3716
2919
  * same failure via auto-retry. Caller aborts are never retried; sleepAbortable rejects
3717
2920
  * with the abort reason if the signal fires mid-backoff.
3718
2921
  */
3719
- async _compactWithRetry(run, signal, provider) {
3720
- const retrySettings = this.settingsManager.getRetrySettings();
3721
- const maxAttempts = retrySettings.enabled ? Math.max(1, retrySettings.maxRetries + 1) : 1;
3722
- const policy = {
3723
- maxAttempts,
3724
- baseDelayMs: retrySettings.baseDelayMs,
3725
- maxDelayMs: DEFAULT_RETRY_POLICY.maxDelayMs,
3726
- jitterRatio: 0,
3727
- };
3728
- for (let attempt = 1;; attempt++) {
3729
- try {
3730
- return await run();
3731
- }
3732
- catch (error) {
3733
- if (signal.aborted || attempt >= maxAttempts)
3734
- throw error;
3735
- const message = error instanceof Error ? error.message : String(error);
3736
- const classified = classifyFailure({ message, provider });
3737
- this._failureCorpus.record({ provider, message, classified });
3738
- if (!classified.retryable)
3739
- throw error;
3740
- await sleepAbortable(computeRetryDelayMs(policy, attempt, { retryAfterMs: classified.retryAfterMs }), signal);
3741
- }
3742
- }
2922
+ _compactWithRetry(run, signal, provider) {
2923
+ return this._compaction.compactWithRetry(run, signal, provider);
3743
2924
  }
3744
2925
  /**
3745
2926
  * Toggle auto-compaction setting.
@@ -4018,40 +3199,15 @@ export class AgentSession {
4018
3199
  // =========================================================================
4019
3200
  // Auto-Retry
4020
3201
  // =========================================================================
4021
- /**
4022
- * Check if an error is retryable (transient provider/network failures). Billing/quota and auth
4023
- * are terminal; context overflow is handled by compaction, not retry. The verdict comes from the
4024
- * reliability kernel's classifier, fed the host-computed context-overflow flag.
4025
- */
4026
- _classifyAssistantError(message) {
4027
- if (message.stopReason !== "error" || !message.errorMessage)
4028
- return undefined;
4029
- const contextWindow = this.model?.contextWindow ?? 0;
4030
- return classifyFailure({
4031
- message: message.errorMessage,
4032
- contextOverflow: isContextOverflow(message, contextWindow),
4033
- provider: message.provider,
4034
- });
4035
- }
4036
- _isRetryableError(message) {
4037
- return this._classifyAssistantError(message)?.retryable ?? false;
4038
- }
4039
- /**
4040
- * Prepare a retryable error for continuation with exponential backoff.
4041
- * @returns true if the caller should continue the agent, false otherwise
4042
- */
4043
- async _prepareRetry(message) {
4044
- return this._retryController.prepareRetry(message);
4045
- }
4046
3202
  /**
4047
3203
  * Cancel in-progress retry.
4048
3204
  */
4049
3205
  abortRetry() {
4050
- this._retryController.abort();
3206
+ this._foregroundRecovery.abortRetry();
4051
3207
  }
4052
3208
  /** Whether auto-retry is currently in progress */
4053
3209
  get isRetrying() {
4054
- return this._retryController.isRetrying;
3210
+ return this._foregroundRecovery.isRetrying;
4055
3211
  }
4056
3212
  /** Whether auto-retry is enabled */
4057
3213
  get autoRetryEnabled() {
@@ -4132,7 +3288,17 @@ export class AgentSession {
4132
3288
  * @returns the id of the appended custom entry
4133
3289
  */
4134
3290
  saveGoalStateSnapshot(state) {
4135
- return appendGoalStateSnapshot(this.sessionManager, state);
3291
+ const entryId = appendGoalStateSnapshot(this.sessionManager, state);
3292
+ try {
3293
+ this._backgroundLanes.synchronizeGoalState(state);
3294
+ }
3295
+ catch (error) {
3296
+ this._emit({
3297
+ type: "warning",
3298
+ message: `Goal state persisted but durable worker reconciliation failed: ${error instanceof Error ? error.message : String(error)}`,
3299
+ });
3300
+ }
3301
+ return entryId;
4136
3302
  }
4137
3303
  /**
4138
3304
  * Retrieve the latest valid goal state snapshot from the session log.
@@ -4182,13 +3348,14 @@ export class AgentSession {
4182
3348
  return appendEvidenceBundleSnapshot(this.sessionManager, bundle);
4183
3349
  }
4184
3350
  /**
4185
- * Retrieve the latest valid evidence bundle snapshot from the session log.
3351
+ * Retrieve the latest valid evidence bundle snapshot from the active branch.
4186
3352
  */
4187
3353
  getEvidenceBundleSnapshot() {
4188
- return getLatestEvidenceBundleSnapshot(this.sessionManager.getEntries());
3354
+ return getLatestEvidenceBundleSnapshot(getActiveSessionBranchEntries(this.sessionManager));
4189
3355
  }
3356
+ /** Retrieve all valid evidence bundle snapshots from the active branch. */
4190
3357
  getEvidenceBundleSnapshots() {
4191
- return getEvidenceBundleSnapshots(this.sessionManager.getEntries());
3358
+ return getEvidenceBundleSnapshots(getActiveSessionBranchEntries(this.sessionManager));
4192
3359
  }
4193
3360
  /** Live lane records tracked by this process (running and terminal). */
4194
3361
  getLaneRecords() {
@@ -4207,17 +3374,17 @@ export class AgentSession {
4207
3374
  getGateOutcomeHistory() {
4208
3375
  return this._autonomyTelemetry.getGateOutcomeHistory();
4209
3376
  }
4210
- saveWorkerResultSnapshot(result, request) {
4211
- return appendWorkerResultSnapshot(this.sessionManager, result, request);
3377
+ saveWorkerClaimSnapshot(claim, request) {
3378
+ return appendWorkerClaimSnapshot(this.sessionManager, claim, request);
4212
3379
  }
4213
- getWorkerResultSnapshots() {
4214
- return getWorkerResultSnapshots(this.sessionManager.getEntries());
3380
+ getWorkerClaimSnapshots() {
3381
+ return getWorkerClaimSnapshots(getActiveSessionBranchEntries(this.sessionManager));
4215
3382
  }
4216
3383
  saveLearningDecisionSnapshot(decision) {
4217
3384
  return appendLearningDecisionSnapshot(this.sessionManager, decision);
4218
3385
  }
4219
3386
  getLearningDecisionSnapshots() {
4220
- return getLearningDecisionSnapshots(this.sessionManager.getEntries());
3387
+ return getLearningDecisionSnapshots(getActiveSessionBranchEntries(this.sessionManager));
4221
3388
  }
4222
3389
  /**
4223
3390
  * The single injection point that makes the goal-continuation snapshot lane-aware:
@@ -4231,6 +3398,7 @@ export class AgentSession {
4231
3398
  sessionManager: this.sessionManager,
4232
3399
  settings,
4233
3400
  laneRecords: this._backgroundLanes.getLaneRecords(),
3401
+ taskRuntime: this._backgroundLanes.getTaskRuntimeSnapshot(),
4234
3402
  });
4235
3403
  }
4236
3404
  /**
@@ -4254,7 +3422,7 @@ export class AgentSession {
4254
3422
  getLaneWorkerRefusal() {
4255
3423
  const profile = this.getModelCapabilityProfile();
4256
3424
  const model = this.model;
4257
- const verdict = model ? this._toolProbeVerdict(model) : undefined;
3425
+ const verdict = model ? this._toolProtocol.getToolProbeVerdict(model) : undefined;
4258
3426
  return evaluateLaneWorkerRefusal({
4259
3427
  capabilityClass: profile.class,
4260
3428
  contextWindow: profile.contextWindow,
@@ -4270,7 +3438,7 @@ export class AgentSession {
4270
3438
  return this._backgroundLanes.runResearchLaneOnce(request);
4271
3439
  }
4272
3440
  /**
4273
- * Run one bounded scout-worker delegation. Delegates to {@link BackgroundLaneController};
3441
+ * Run one bounded profile-bound worker delegation. Delegates to {@link BackgroundLaneController};
4274
3442
  * consumed by the `delegate` tool.
4275
3443
  */
4276
3444
  async runWorkerDelegationOnce(request) {
@@ -4370,13 +3538,4 @@ export class AgentSession {
4370
3538
  return this._extensionRunner;
4371
3539
  }
4372
3540
  }
4373
- function findLastAssistantMessage(messages) {
4374
- for (let i = messages.length - 1; i >= 0; i--) {
4375
- const message = messages[i];
4376
- if (message.role === "assistant") {
4377
- return message;
4378
- }
4379
- }
4380
- return undefined;
4381
- }
4382
3541
  //# sourceMappingURL=agent-session.js.map