@coool/opencode-adaptive 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/README.md +8 -7
  2. package/dist/adapters/codex-capacity/index.d.ts +0 -1
  3. package/dist/adapters/codex-capacity/index.js.map +1 -1
  4. package/dist/adapters/codex-capacity/parse.d.ts +2 -2
  5. package/dist/adapters/codex-capacity/parse.js +19 -3
  6. package/dist/adapters/codex-capacity/parse.js.map +1 -1
  7. package/dist/adapters/codex-capacity/reading.d.ts +0 -1
  8. package/dist/adapters/codex-capacity/reading.js.map +1 -1
  9. package/dist/adapters/codex-capacity/sensor.d.ts +0 -1
  10. package/dist/adapters/codex-capacity/sensor.js.map +1 -1
  11. package/dist/adapters/codex-capacity/types.d.ts +0 -4
  12. package/dist/adapters/codex-capacity/types.js +14 -0
  13. package/dist/adapters/codex-capacity/types.js.map +1 -1
  14. package/dist/adapters/opencode/escalation-tool.d.ts +0 -1
  15. package/dist/adapters/opencode/escalation-tool.js +5 -3
  16. package/dist/adapters/opencode/escalation-tool.js.map +1 -1
  17. package/dist/adapters/opencode/events.d.ts +0 -1
  18. package/dist/adapters/opencode/events.js.map +1 -1
  19. package/dist/adapters/opencode/hooks.d.ts +0 -1
  20. package/dist/adapters/opencode/hooks.js +24 -7
  21. package/dist/adapters/opencode/hooks.js.map +1 -1
  22. package/dist/adapters/opencode/index.d.ts +0 -1
  23. package/dist/adapters/opencode/index.js.map +1 -1
  24. package/dist/adapters/opencode/inventory.d.ts +0 -1
  25. package/dist/adapters/opencode/inventory.js.map +1 -1
  26. package/dist/adapters/opencode/report.d.ts +0 -1
  27. package/dist/adapters/opencode/report.js.map +1 -1
  28. package/dist/adapters/opencode/session-ops.d.ts +0 -1
  29. package/dist/adapters/opencode/session-ops.js.map +1 -1
  30. package/dist/adapters/opencode/storage.d.ts +0 -1
  31. package/dist/adapters/opencode/storage.js.map +1 -1
  32. package/dist/adapters/opencode/tier-tool.d.ts +0 -1
  33. package/dist/adapters/opencode/tier-tool.js.map +1 -1
  34. package/dist/adapters/opencode/types.d.ts +0 -1
  35. package/dist/adapters/opencode/types.js.map +1 -1
  36. package/dist/core/compatibility/gate.d.ts +0 -3
  37. package/dist/core/compatibility/gate.js +0 -4
  38. package/dist/core/compatibility/gate.js.map +1 -1
  39. package/dist/core/compatibility/index.d.ts +0 -1
  40. package/dist/core/compatibility/index.js.map +1 -1
  41. package/dist/core/compatibility/types.d.ts +0 -1
  42. package/dist/core/compatibility/types.js.map +1 -1
  43. package/dist/core/scheduler/admit.d.ts +0 -1
  44. package/dist/core/scheduler/admit.js +22 -21
  45. package/dist/core/scheduler/admit.js.map +1 -1
  46. package/dist/core/scheduler/escalate.d.ts +0 -1
  47. package/dist/core/scheduler/escalate.js +6 -17
  48. package/dist/core/scheduler/escalate.js.map +1 -1
  49. package/dist/core/scheduler/index.d.ts +0 -1
  50. package/dist/core/scheduler/index.js.map +1 -1
  51. package/dist/core/scheduler/mode.d.ts +0 -1
  52. package/dist/core/scheduler/mode.js.map +1 -1
  53. package/dist/core/scheduler/types.d.ts +2 -9
  54. package/dist/core/scheduler/types.js +2 -1
  55. package/dist/core/scheduler/types.js.map +1 -1
  56. package/dist/core/tier-policy/index.d.ts +0 -1
  57. package/dist/core/tier-policy/index.js.map +1 -1
  58. package/dist/core/tier-policy/resolve.d.ts +0 -1
  59. package/dist/core/tier-policy/resolve.js.map +1 -1
  60. package/dist/core/tier-policy/tier-list.d.ts +0 -1
  61. package/dist/core/tier-policy/tier-list.js.map +1 -1
  62. package/dist/core/tier-policy/types.d.ts +2 -1
  63. package/dist/core/tier-policy/types.js +4 -0
  64. package/dist/core/tier-policy/types.js.map +1 -1
  65. package/dist/core/work-unit/index.d.ts +0 -1
  66. package/dist/core/work-unit/index.js.map +1 -1
  67. package/dist/core/work-unit/interruption.d.ts +0 -1
  68. package/dist/core/work-unit/interruption.js.map +1 -1
  69. package/dist/core/work-unit/tracker.d.ts +11 -4
  70. package/dist/core/work-unit/tracker.js +26 -6
  71. package/dist/core/work-unit/tracker.js.map +1 -1
  72. package/dist/core/work-unit/types.d.ts +6 -1
  73. package/dist/core/work-unit/types.js.map +1 -1
  74. package/dist/execution/continuation/execute.d.ts +0 -1
  75. package/dist/execution/continuation/execute.js.map +1 -1
  76. package/dist/execution/continuation/index.d.ts +0 -2
  77. package/dist/execution/continuation/index.js +0 -1
  78. package/dist/execution/continuation/index.js.map +1 -1
  79. package/dist/execution/continuation/plan.d.ts +0 -1
  80. package/dist/execution/continuation/plan.js +1 -3
  81. package/dist/execution/continuation/plan.js.map +1 -1
  82. package/dist/execution/continuation/prompts.d.ts +0 -1
  83. package/dist/execution/continuation/prompts.js.map +1 -1
  84. package/dist/execution/continuation/types.d.ts +0 -2
  85. package/dist/execution/continuation/types.js.map +1 -1
  86. package/dist/execution/escalation/index.d.ts +0 -1
  87. package/dist/execution/escalation/index.js.map +1 -1
  88. package/dist/execution/escalation/types.d.ts +0 -1
  89. package/dist/execution/escalation/types.js.map +1 -1
  90. package/dist/execution/index.d.ts +0 -1
  91. package/dist/execution/index.js.map +1 -1
  92. package/dist/execution/parking/index.d.ts +0 -1
  93. package/dist/execution/parking/index.js.map +1 -1
  94. package/dist/execution/parking/plan.d.ts +0 -1
  95. package/dist/execution/parking/plan.js.map +1 -1
  96. package/dist/execution/retry/decide.d.ts +0 -1
  97. package/dist/execution/retry/decide.js +1 -2
  98. package/dist/execution/retry/decide.js.map +1 -1
  99. package/dist/execution/retry/index.d.ts +0 -1
  100. package/dist/execution/retry/index.js.map +1 -1
  101. package/dist/execution/retry/types.d.ts +0 -2
  102. package/dist/execution/retry/types.js.map +1 -1
  103. package/dist/gui-experimental.cjs +1 -1
  104. package/dist/gui.d.ts +0 -1
  105. package/dist/gui.js +6 -3
  106. package/dist/gui.js.map +1 -1
  107. package/dist/health/capacity/index.d.ts +0 -1
  108. package/dist/health/capacity/index.js.map +1 -1
  109. package/dist/health/capacity/merge.d.ts +0 -1
  110. package/dist/health/capacity/merge.js.map +1 -1
  111. package/dist/health/capacity/registry.d.ts +0 -1
  112. package/dist/health/capacity/registry.js.map +1 -1
  113. package/dist/health/capacity/types.d.ts +0 -1
  114. package/dist/health/capacity/types.js.map +1 -1
  115. package/dist/health/circuit-breaker/breaker.d.ts +8 -1
  116. package/dist/health/circuit-breaker/breaker.js +18 -0
  117. package/dist/health/circuit-breaker/breaker.js.map +1 -1
  118. package/dist/health/circuit-breaker/index.d.ts +0 -1
  119. package/dist/health/circuit-breaker/index.js.map +1 -1
  120. package/dist/health/circuit-breaker/types.d.ts +0 -1
  121. package/dist/health/circuit-breaker/types.js.map +1 -1
  122. package/dist/health/classifier/classify.d.ts +0 -1
  123. package/dist/health/classifier/classify.js.map +1 -1
  124. package/dist/health/classifier/index.d.ts +0 -1
  125. package/dist/health/classifier/index.js.map +1 -1
  126. package/dist/health/classifier/types.d.ts +0 -1
  127. package/dist/health/classifier/types.js.map +1 -1
  128. package/dist/health/index.d.ts +0 -1
  129. package/dist/health/index.js.map +1 -1
  130. package/dist/health/resource-health/index.d.ts +0 -1
  131. package/dist/health/resource-health/index.js.map +1 -1
  132. package/dist/health/resource-health/key.d.ts +0 -1
  133. package/dist/health/resource-health/key.js.map +1 -1
  134. package/dist/health/resource-health/registry.d.ts +6 -1
  135. package/dist/health/resource-health/registry.js +19 -1
  136. package/dist/health/resource-health/registry.js.map +1 -1
  137. package/dist/health/resource-health/transitions.d.ts +2 -4
  138. package/dist/health/resource-health/transitions.js +10 -18
  139. package/dist/health/resource-health/transitions.js.map +1 -1
  140. package/dist/health/resource-health/types.d.ts +7 -1
  141. package/dist/health/resource-health/types.js +1 -0
  142. package/dist/health/resource-health/types.js.map +1 -1
  143. package/dist/index.d.ts +0 -6
  144. package/dist/index.js +44 -19
  145. package/dist/index.js.map +1 -1
  146. package/dist/plugin-id.d.ts +0 -1
  147. package/dist/plugin-id.js.map +1 -1
  148. package/dist/rpc/definition.d.ts +24 -1
  149. package/dist/rpc/definition.js +28 -2
  150. package/dist/rpc/definition.js.map +1 -1
  151. package/dist/rpc/handlers.d.ts +12 -24
  152. package/dist/rpc/handlers.js +22 -8
  153. package/dist/rpc/handlers.js.map +1 -1
  154. package/dist/rpc/status.d.ts +0 -1
  155. package/dist/rpc/status.js.map +1 -1
  156. package/dist/rpc.d.ts +0 -1
  157. package/dist/rpc.js.map +1 -1
  158. package/dist/state/capacity/index.d.ts +0 -1
  159. package/dist/state/capacity/index.js.map +1 -1
  160. package/dist/state/capacity/parse.d.ts +0 -1
  161. package/dist/state/capacity/parse.js +14 -2
  162. package/dist/state/capacity/parse.js.map +1 -1
  163. package/dist/state/capacity/store.d.ts +0 -1
  164. package/dist/state/capacity/store.js +16 -2
  165. package/dist/state/capacity/store.js.map +1 -1
  166. package/dist/state/capacity/types.d.ts +0 -1
  167. package/dist/state/capacity/types.js.map +1 -1
  168. package/dist/state/health/index.d.ts +0 -1
  169. package/dist/state/health/index.js.map +1 -1
  170. package/dist/state/health/parse.d.ts +0 -1
  171. package/dist/state/health/parse.js +20 -4
  172. package/dist/state/health/parse.js.map +1 -1
  173. package/dist/state/health/store.d.ts +0 -1
  174. package/dist/state/health/store.js +16 -2
  175. package/dist/state/health/store.js.map +1 -1
  176. package/dist/state/health/types.d.ts +0 -1
  177. package/dist/state/health/types.js.map +1 -1
  178. package/dist/state/parked-work/index.d.ts +0 -1
  179. package/dist/state/parked-work/index.js.map +1 -1
  180. package/dist/state/parked-work/parse.d.ts +0 -1
  181. package/dist/state/parked-work/parse.js.map +1 -1
  182. package/dist/state/parked-work/store.d.ts +0 -1
  183. package/dist/state/parked-work/store.js +20 -3
  184. package/dist/state/parked-work/store.js.map +1 -1
  185. package/dist/state/parked-work/types.d.ts +0 -1
  186. package/dist/state/parked-work/types.js.map +1 -1
  187. package/dist/state/receipts/bound.d.ts +0 -1
  188. package/dist/state/receipts/bound.js.map +1 -1
  189. package/dist/state/receipts/index.d.ts +0 -1
  190. package/dist/state/receipts/index.js.map +1 -1
  191. package/dist/state/receipts/parse.d.ts +0 -1
  192. package/dist/state/receipts/parse.js.map +1 -1
  193. package/dist/state/receipts/store.d.ts +0 -1
  194. package/dist/state/receipts/store.js +24 -4
  195. package/dist/state/receipts/store.js.map +1 -1
  196. package/dist/state/receipts/types.d.ts +0 -1
  197. package/dist/state/receipts/types.js.map +1 -1
  198. package/dist/state/session-modes/index.d.ts +0 -1
  199. package/dist/state/session-modes/index.js.map +1 -1
  200. package/dist/state/session-modes/parse.d.ts +0 -1
  201. package/dist/state/session-modes/parse.js.map +1 -1
  202. package/dist/state/session-modes/store.d.ts +0 -1
  203. package/dist/state/session-modes/store.js +15 -1
  204. package/dist/state/session-modes/store.js.map +1 -1
  205. package/dist/state/session-modes/types.d.ts +0 -1
  206. package/dist/state/session-modes/types.js.map +1 -1
  207. package/dist/state/settings/index.d.ts +0 -1
  208. package/dist/state/settings/index.js.map +1 -1
  209. package/dist/state/settings/parse.d.ts +0 -1
  210. package/dist/state/settings/parse.js.map +1 -1
  211. package/dist/state/settings/store.d.ts +0 -1
  212. package/dist/state/settings/store.js +9 -1
  213. package/dist/state/settings/store.js.map +1 -1
  214. package/dist/state/settings/types.d.ts +0 -1
  215. package/dist/state/settings/types.js.map +1 -1
  216. package/dist/state/tier-requests/index.d.ts +4 -0
  217. package/dist/state/tier-requests/index.js +4 -0
  218. package/dist/state/tier-requests/index.js.map +1 -0
  219. package/dist/state/tier-requests/parse.d.ts +3 -0
  220. package/dist/state/tier-requests/parse.js +53 -0
  221. package/dist/state/tier-requests/parse.js.map +1 -0
  222. package/dist/state/tier-requests/store.d.ts +27 -0
  223. package/dist/state/tier-requests/store.js +96 -0
  224. package/dist/state/tier-requests/store.js.map +1 -0
  225. package/dist/state/tier-requests/types.d.ts +30 -0
  226. package/dist/state/tier-requests/types.js +7 -0
  227. package/dist/state/tier-requests/types.js.map +1 -0
  228. package/dist/supervisor/index.d.ts +0 -1
  229. package/dist/supervisor/index.js.map +1 -1
  230. package/dist/supervisor/log.d.ts +0 -1
  231. package/dist/supervisor/log.js.map +1 -1
  232. package/dist/supervisor/supervisor.d.ts +23 -6
  233. package/dist/supervisor/supervisor.js +397 -111
  234. package/dist/supervisor/supervisor.js.map +1 -1
  235. package/dist/supervisor/types.d.ts +0 -1
  236. package/dist/supervisor/types.js.map +1 -1
  237. package/dist/supervisor/view.d.ts +0 -1
  238. package/dist/supervisor/view.js.map +1 -1
  239. package/dist/tui.d.ts +0 -1
  240. package/dist/tui.js +14 -10
  241. package/dist/tui.js.map +1 -1
  242. package/dist/ui/gui/api.d.ts +0 -1
  243. package/dist/ui/gui/api.js.map +1 -1
  244. package/dist/ui/gui/view.d.ts +0 -1
  245. package/dist/ui/gui/view.js.map +1 -1
  246. package/dist/ui/tui/controller.d.ts +0 -1
  247. package/dist/ui/tui/controller.js +28 -12
  248. package/dist/ui/tui/controller.js.map +1 -1
  249. package/dist/ui/tui/menu.d.ts +1 -2
  250. package/dist/ui/tui/menu.js +2 -2
  251. package/dist/ui/tui/menu.js.map +1 -1
  252. package/dist/ui/tui/options.d.ts +0 -1
  253. package/dist/ui/tui/options.js.map +1 -1
  254. package/dist/ui/tui/receipts.d.ts +0 -1
  255. package/dist/ui/tui/receipts.js +4 -1
  256. package/dist/ui/tui/receipts.js.map +1 -1
  257. package/dist/ui/tui/status.d.ts +1 -2
  258. package/dist/ui/tui/status.js +4 -2
  259. package/dist/ui/tui/status.js.map +1 -1
  260. package/dist/ui/tui/tiers.d.ts +0 -1
  261. package/dist/ui/tui/tiers.js.map +1 -1
  262. package/package.json +2 -2
  263. package/dist/adapters/codex-capacity/index.d.ts.map +0 -1
  264. package/dist/adapters/codex-capacity/parse.d.ts.map +0 -1
  265. package/dist/adapters/codex-capacity/reading.d.ts.map +0 -1
  266. package/dist/adapters/codex-capacity/sensor.d.ts.map +0 -1
  267. package/dist/adapters/codex-capacity/types.d.ts.map +0 -1
  268. package/dist/adapters/opencode/escalation-tool.d.ts.map +0 -1
  269. package/dist/adapters/opencode/events.d.ts.map +0 -1
  270. package/dist/adapters/opencode/hooks.d.ts.map +0 -1
  271. package/dist/adapters/opencode/index.d.ts.map +0 -1
  272. package/dist/adapters/opencode/inventory.d.ts.map +0 -1
  273. package/dist/adapters/opencode/report.d.ts.map +0 -1
  274. package/dist/adapters/opencode/session-ops.d.ts.map +0 -1
  275. package/dist/adapters/opencode/storage.d.ts.map +0 -1
  276. package/dist/adapters/opencode/tier-tool.d.ts.map +0 -1
  277. package/dist/adapters/opencode/types.d.ts.map +0 -1
  278. package/dist/core/compatibility/gate.d.ts.map +0 -1
  279. package/dist/core/compatibility/index.d.ts.map +0 -1
  280. package/dist/core/compatibility/types.d.ts.map +0 -1
  281. package/dist/core/scheduler/admit.d.ts.map +0 -1
  282. package/dist/core/scheduler/escalate.d.ts.map +0 -1
  283. package/dist/core/scheduler/index.d.ts.map +0 -1
  284. package/dist/core/scheduler/mode.d.ts.map +0 -1
  285. package/dist/core/scheduler/types.d.ts.map +0 -1
  286. package/dist/core/tier-policy/index.d.ts.map +0 -1
  287. package/dist/core/tier-policy/resolve.d.ts.map +0 -1
  288. package/dist/core/tier-policy/tier-list.d.ts.map +0 -1
  289. package/dist/core/tier-policy/types.d.ts.map +0 -1
  290. package/dist/core/work-unit/index.d.ts.map +0 -1
  291. package/dist/core/work-unit/interruption.d.ts.map +0 -1
  292. package/dist/core/work-unit/tracker.d.ts.map +0 -1
  293. package/dist/core/work-unit/types.d.ts.map +0 -1
  294. package/dist/execution/continuation/execute.d.ts.map +0 -1
  295. package/dist/execution/continuation/index.d.ts.map +0 -1
  296. package/dist/execution/continuation/ledger.d.ts +0 -20
  297. package/dist/execution/continuation/ledger.d.ts.map +0 -1
  298. package/dist/execution/continuation/ledger.js +0 -31
  299. package/dist/execution/continuation/ledger.js.map +0 -1
  300. package/dist/execution/continuation/plan.d.ts.map +0 -1
  301. package/dist/execution/continuation/prompts.d.ts.map +0 -1
  302. package/dist/execution/continuation/types.d.ts.map +0 -1
  303. package/dist/execution/escalation/index.d.ts.map +0 -1
  304. package/dist/execution/escalation/types.d.ts.map +0 -1
  305. package/dist/execution/index.d.ts.map +0 -1
  306. package/dist/execution/parking/index.d.ts.map +0 -1
  307. package/dist/execution/parking/plan.d.ts.map +0 -1
  308. package/dist/execution/recovery/gate.d.ts +0 -20
  309. package/dist/execution/recovery/gate.d.ts.map +0 -1
  310. package/dist/execution/recovery/gate.js +0 -23
  311. package/dist/execution/recovery/gate.js.map +0 -1
  312. package/dist/execution/recovery/index.d.ts +0 -3
  313. package/dist/execution/recovery/index.d.ts.map +0 -1
  314. package/dist/execution/recovery/index.js +0 -2
  315. package/dist/execution/recovery/index.js.map +0 -1
  316. package/dist/execution/retry/decide.d.ts.map +0 -1
  317. package/dist/execution/retry/index.d.ts.map +0 -1
  318. package/dist/execution/retry/types.d.ts.map +0 -1
  319. package/dist/gui.d.ts.map +0 -1
  320. package/dist/health/capacity/index.d.ts.map +0 -1
  321. package/dist/health/capacity/merge.d.ts.map +0 -1
  322. package/dist/health/capacity/registry.d.ts.map +0 -1
  323. package/dist/health/capacity/types.d.ts.map +0 -1
  324. package/dist/health/circuit-breaker/breaker.d.ts.map +0 -1
  325. package/dist/health/circuit-breaker/index.d.ts.map +0 -1
  326. package/dist/health/circuit-breaker/types.d.ts.map +0 -1
  327. package/dist/health/classifier/classify.d.ts.map +0 -1
  328. package/dist/health/classifier/index.d.ts.map +0 -1
  329. package/dist/health/classifier/types.d.ts.map +0 -1
  330. package/dist/health/index.d.ts.map +0 -1
  331. package/dist/health/resource-health/index.d.ts.map +0 -1
  332. package/dist/health/resource-health/key.d.ts.map +0 -1
  333. package/dist/health/resource-health/registry.d.ts.map +0 -1
  334. package/dist/health/resource-health/transitions.d.ts.map +0 -1
  335. package/dist/health/resource-health/types.d.ts.map +0 -1
  336. package/dist/index.d.ts.map +0 -1
  337. package/dist/plugin-id.d.ts.map +0 -1
  338. package/dist/rpc/definition.d.ts.map +0 -1
  339. package/dist/rpc/handlers.d.ts.map +0 -1
  340. package/dist/rpc/status.d.ts.map +0 -1
  341. package/dist/rpc.d.ts.map +0 -1
  342. package/dist/state/capacity/index.d.ts.map +0 -1
  343. package/dist/state/capacity/parse.d.ts.map +0 -1
  344. package/dist/state/capacity/store.d.ts.map +0 -1
  345. package/dist/state/capacity/types.d.ts.map +0 -1
  346. package/dist/state/health/index.d.ts.map +0 -1
  347. package/dist/state/health/parse.d.ts.map +0 -1
  348. package/dist/state/health/store.d.ts.map +0 -1
  349. package/dist/state/health/types.d.ts.map +0 -1
  350. package/dist/state/parked-work/index.d.ts.map +0 -1
  351. package/dist/state/parked-work/parse.d.ts.map +0 -1
  352. package/dist/state/parked-work/store.d.ts.map +0 -1
  353. package/dist/state/parked-work/types.d.ts.map +0 -1
  354. package/dist/state/receipts/bound.d.ts.map +0 -1
  355. package/dist/state/receipts/index.d.ts.map +0 -1
  356. package/dist/state/receipts/parse.d.ts.map +0 -1
  357. package/dist/state/receipts/store.d.ts.map +0 -1
  358. package/dist/state/receipts/types.d.ts.map +0 -1
  359. package/dist/state/session-modes/index.d.ts.map +0 -1
  360. package/dist/state/session-modes/parse.d.ts.map +0 -1
  361. package/dist/state/session-modes/store.d.ts.map +0 -1
  362. package/dist/state/session-modes/types.d.ts.map +0 -1
  363. package/dist/state/settings/index.d.ts.map +0 -1
  364. package/dist/state/settings/parse.d.ts.map +0 -1
  365. package/dist/state/settings/store.d.ts.map +0 -1
  366. package/dist/state/settings/types.d.ts.map +0 -1
  367. package/dist/supervisor/index.d.ts.map +0 -1
  368. package/dist/supervisor/log.d.ts.map +0 -1
  369. package/dist/supervisor/supervisor.d.ts.map +0 -1
  370. package/dist/supervisor/types.d.ts.map +0 -1
  371. package/dist/supervisor/view.d.ts.map +0 -1
  372. package/dist/tui.d.ts.map +0 -1
  373. package/dist/ui/gui/api.d.ts.map +0 -1
  374. package/dist/ui/gui/view.d.ts.map +0 -1
  375. package/dist/ui/tui/controller.d.ts.map +0 -1
  376. package/dist/ui/tui/menu.d.ts.map +0 -1
  377. package/dist/ui/tui/options.d.ts.map +0 -1
  378. package/dist/ui/tui/receipts.d.ts.map +0 -1
  379. package/dist/ui/tui/status.d.ts.map +0 -1
  380. package/dist/ui/tui/tiers.d.ts.map +0 -1
@@ -20,13 +20,13 @@ var _a;
20
20
  * (a later episode supersedes it); no outcome is ever fabricated.
21
21
  * - escalation (ADP-015..017) triggers only on capability-classified unit
22
22
  * failures (context overflow, content policy); infrastructure failures
23
- * never escalate, and the per-unit budget is finite.
23
+ * never escalate, and the per-task budget is finite.
24
24
  */
25
25
  import { WorkUnitTracker } from "../core/work-unit/tracker.js";
26
26
  import { sameModelRef, } from "../core/work-unit/types.js";
27
27
  import { INTERRUPT_REASONS } from "../core/work-unit/interruption.js";
28
28
  import { resolveRouting } from "../core/tier-policy/resolve.js";
29
- import { TIERS, nextTier } from "../core/tier-policy/types.js";
29
+ import { isTierName, nextTier } from "../core/tier-policy/types.js";
30
30
  import { admit, forMode as schedulerForMode, planEscalation, schedulerHealthKey, } from "../core/scheduler/index.js";
31
31
  import { MAX_ESCALATION_EVIDENCE } from "../core/scheduler/types.js";
32
32
  import { checkCompatibility } from "../core/compatibility/gate.js";
@@ -38,6 +38,7 @@ import { mergeReadings } from "../health/capacity/merge.js";
38
38
  import { resourceChain, resourceKeyID } from "../health/resource-health/key.js";
39
39
  import { SettingsStore, SETTINGS_KEY } from "../state/settings/index.js";
40
40
  import { SessionModeStore, SESSION_MODES_KEY } from "../state/session-modes/index.js";
41
+ import { TierRequestStore, TIER_REQUESTS_KEY } from "../state/tier-requests/index.js";
41
42
  import { ReceiptStore, RECEIPTS_KEY } from "../state/receipts/index.js";
42
43
  import { buildStatus } from "../rpc/status.js";
43
44
  import { RECEIPTS_VIEW_LIMIT } from "./view.js";
@@ -48,11 +49,8 @@ import { listCandidates, sessionOps, storageSeam } from "../adapters/opencode/in
48
49
  import { CodexCapacitySensor } from "../adapters/codex-capacity/index.js";
49
50
  import { logFailOpen } from "./log.js";
50
51
  import { CODEX_PROVIDER_ID } from "./types.js";
52
+ import { TIERS } from "../core/tier-policy/types.js";
51
53
  /** Which tiers currently have configured resources (ADP-005). */
52
- /** Vocabulary guard without a cast: strict member equality. */
53
- function isTierName(tier) {
54
- return TIERS.some((name) => name === tier);
55
- }
56
54
  function configuredTiers(settings) {
57
55
  return TIERS.filter((tier) => settings.tiers[tier].length > 0);
58
56
  }
@@ -71,8 +69,20 @@ const CAPABILITY_EVIDENCE_CATEGORIES = [
71
69
  "context_overflow",
72
70
  "content_policy",
73
71
  ];
74
- /** Finite per-unit escalation budget (ADP-017). */
75
- const MAX_ESCALATIONS_PER_UNIT = 2;
72
+ /** Finite per-task escalation budget (ADP-017). */
73
+ const MAX_ESCALATIONS_PER_TASK = 2;
74
+ /**
75
+ * The recovery's post-settle guard aborted it (the session went OFF or the
76
+ * user interrupted while the host settle was in flight). Callers use it to
77
+ * skip the ordinary ops-failure degradation (parking) — an aborted recovery
78
+ * is a steering change, not an infrastructure failure.
79
+ */
80
+ export class RecoveryAbortedError extends Error {
81
+ constructor(sessionID) {
82
+ super(`recovery aborted for session ${sessionID}`);
83
+ this.name = "RecoveryAbortedError";
84
+ }
85
+ }
76
86
  export class Supervisor {
77
87
  #options;
78
88
  #settings;
@@ -86,6 +96,12 @@ export class Supervisor {
86
96
  #capacity;
87
97
  #codex;
88
98
  #vetoes = new Map();
99
+ /**
100
+ * Escalations spent per session on the current task (one user prompt and
101
+ * the owned continuations it spawns). Keyed by session, not unit: every
102
+ * escalation continues on a NEW unit, so a per-unit count would restart
103
+ * the budget at each step. A fresh user prompt starts a new task.
104
+ */
89
105
  #escalations = new Map();
90
106
  #admissionTiers = new Map();
91
107
  /** Recorded escalation-tool requests per unit (ADP-015 agent self-reports). */
@@ -100,10 +116,20 @@ export class Supervisor {
100
116
  * Per-session tier requests (agent-declared via the adaptive_tier tool):
101
117
  * the request is STICKY — it steers the session's next and following
102
118
  * admissions until cleared or the session dies — because an agent works
103
- * across many units in one task. Insertion order gives the eviction.
119
+ * across many units in one task. Persisted like the session-mode
120
+ * overrides: the host's double plugin instance (hooks vs RPC) must not
121
+ * strand a request on the instance that never routes.
104
122
  */
105
- static MAX_TIER_REQUESTS = 1024;
106
- #tierRequests = new Map();
123
+ #tierRequests;
124
+ /**
125
+ * Sessions whose last model change was the user's own pick
126
+ * (`selected-external`): prompt admission does not re-route them —
127
+ * switching would undo the user's `/model` choice (ADP-028). Cleared by an
128
+ * adaptive switch, a tier request, or a mode change; a standing settings
129
+ * pin overrides the suppression (the pin is manual intent too).
130
+ */
131
+ static MAX_MANUAL_SELECTIONS = 1024;
132
+ #manualSelections = new Map();
107
133
  /**
108
134
  * Sessions with an owned continuation prompt in flight: the prompt hook
109
135
  * will fire for it, but that prompt belongs to the active unit — it must
@@ -119,6 +145,15 @@ export class Supervisor {
119
145
  * make the continuation re-admit (admission ping-pong).
120
146
  */
121
147
  #continuationsInFlight = new Map();
148
+ /**
149
+ * Sessions with an owned recovery in flight (between veto/resume and the
150
+ * delivered continuation prompt). The host settle inside it can take
151
+ * unbounded time; an interruption arriving during that window must abort
152
+ * the stale decision instead of acting after it.
153
+ */
154
+ #recoveryPending = new Set();
155
+ /** Interruptions observed while a recovery was pending on the session. */
156
+ #recoveryInterrupted = new Set();
122
157
  /**
123
158
  * Half-open probe tickets by session: a probe admitted while computing the
124
159
  * recovery for a veto belongs to the continuation episode that follows, so
@@ -132,6 +167,21 @@ export class Supervisor {
132
167
  * it — otherwise a lost probe would wedge the resource in half_open.
133
168
  */
134
169
  #probeTickets = new Map();
170
+ /**
171
+ * Per-unit dedup for breaker failure evidence (ADP-019). One failed attempt
172
+ * surfaces through up to three host signals — the pumped `http.response`
173
+ * event, the `retry` hook (the same request), and the terminal
174
+ * `execution-failed` (the last attempt) — and counting each would trip the
175
+ * breaker after ~2 real failures instead of `failureThreshold`. The exact
176
+ * arrival order of a pair is not guaranteed (the retry hook runs in the
177
+ * request lifecycle, the event pump lags), so the dedup is order-symmetric:
178
+ * the first signal of a pair counts and arms, the matching counterpart
179
+ * consumes it, a completed pair closes the attempt, and the terminal
180
+ * repeats only when the last attempt left no live evidence. A same-category
181
+ * observation with nothing armed is a NEW attempt (consecutive http-only
182
+ * failures each count). Cleared at unit close.
183
+ */
184
+ #attemptFailures = new Map();
135
185
  /**
136
186
  * Session → tier admitted by the prompt hook for the unit that is about to
137
187
  * open. Consumed by execution-started, which then skips re-routing (the
@@ -162,6 +212,7 @@ export class Supervisor {
162
212
  this.#healthRecords = new HealthRecordStore(storageSeam(HEALTH_KEY, options.storage));
163
213
  this.#capacityStore = new CapacityReadingStore(storageSeam(CAPACITY_KEY, options.storage));
164
214
  this.#sessionModes = new SessionModeStore(storageSeam(SESSION_MODES_KEY, options.storage), options.clock);
215
+ this.#tierRequests = new TierRequestStore(storageSeam(TIER_REQUESTS_KEY, options.storage), options.clock);
165
216
  this.#ops = sessionOps(options.session, {
166
217
  beforeSwitch: (sessionID, model) => {
167
218
  this.#tracker.noteOwnSwitch(sessionID, model);
@@ -234,6 +285,10 @@ export class Supervisor {
234
285
  // host's double-instance reconciliation) must keep steering sessions
235
286
  // under the modes the user set for them.
236
287
  await this.#sessionModes.load();
288
+ // Tier requests hydrate the same way: a tier request recorded through
289
+ // the host's other plugin instance (or before a restart) must still
290
+ // steer this instance's admissions.
291
+ await this.#tierRequests.load();
237
292
  await this.#capacityStore.load();
238
293
  return this.#settings.load();
239
294
  };
@@ -270,6 +325,27 @@ export class Supervisor {
270
325
  logFailOpen("report", error);
271
326
  }
272
327
  }
328
+ /** Marks the session's manual-selection state from an external selection event. */
329
+ #noteManualSelection(sessionID) {
330
+ if (!this.#manualSelections.has(sessionID) &&
331
+ this.#manualSelections.size >= _a.MAX_MANUAL_SELECTIONS) {
332
+ // The guard above ensures the map is at its cap (>= 1 entries), so the
333
+ // first key always exists; the assertion avoids a branch the coverage
334
+ // gate would reject as unreachable.
335
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
336
+ const oldest = this.#manualSelections.keys().next().value;
337
+ this.#manualSelections.delete(oldest);
338
+ }
339
+ this.#manualSelections.set(sessionID, true);
340
+ }
341
+ /**
342
+ * True while the session's last model change was the user's own pick.
343
+ * A standing settings pin is explicit manual intent as well (ADP-002):
344
+ * honoring it does not fight the pick, so suppression is skipped there.
345
+ */
346
+ #manuallySelected(sessionID, settings) {
347
+ return settings.pin === null && this.#manualSelections.has(sessionID);
348
+ }
273
349
  /**
274
350
  * Handle one `retry` hook invocation. On a veto this mutates
275
351
  * `input.decision` to `{ retry: false }`; otherwise the decision is left
@@ -308,33 +384,38 @@ export class Supervisor {
308
384
  * tool). STICKY: it steers the session's next and following admissions
309
385
  * until cleared — an agent works across many units in one task. Returns
310
386
  * false on an unknown tier (the tool re-validates before calling).
387
+ *
388
+ * Fire-and-forget persistence: the recorder signature is sync on both the
389
+ * tool and the RPC source interfaces, so the write is enqueued without
390
+ * awaiting it. Readers (routing, getTierRequest, the idle sweep) flush the
391
+ * store's queue before reading, which makes the record visible
392
+ * deterministically; a write failure drops the record fail-open (the tool
393
+ * told the agent "recorded", the persisted mirror simply never sees it —
394
+ * the same degradation the in-memory map had on restart).
311
395
  */
312
396
  noteTierRequest(sessionID, tier) {
313
397
  if (!isTierName(tier))
314
398
  return false;
315
- if (!this.#tierRequests.has(sessionID) &&
316
- this.#tierRequests.size >= _a.MAX_TIER_REQUESTS) {
317
- // The map is non-empty here (size >= MAX_TIER_REQUESTS > 0): Map
318
- // preserves insertion order, so the first key is the oldest request.
319
- for (const oldest of this.#tierRequests.keys()) {
320
- this.#tierRequests.delete(oldest);
321
- break;
322
- }
323
- }
324
- this.#tierRequests.set(sessionID, { tier, via: "manual-command" });
399
+ void this.#tierRequests.set(sessionID, tier).catch(() => undefined);
400
+ // The user/agent explicitly asked Adaptive to route again (ADP-028).
401
+ this.#manualSelections.delete(sessionID);
325
402
  return true;
326
403
  }
327
404
  /** The session's tier request, or `null` when it follows the routing ladder. */
328
405
  async getTierRequest(sessionID) {
329
406
  await this.#ready;
330
- return this.#tierRequests.get(sessionID)?.tier ?? null;
407
+ // A fire-and-forget note may still be in the queue: flush before reading.
408
+ await this.#tierRequests.flush();
409
+ return this.#tierRequests.get(sessionID);
331
410
  }
332
411
  /** Clear one session's tier request (`null` is the only valid input). */
333
412
  async setTierRequest(sessionID, tier) {
334
413
  await this.#ready;
335
414
  if (tier !== null)
336
415
  throw new Error("only null clears a tier request");
337
- this.#tierRequests.delete(sessionID);
416
+ await this.#tierRequests.set(sessionID, null);
417
+ // Clearing through the control surface is the user asking Adaptive back.
418
+ this.#manualSelections.delete(sessionID);
338
419
  }
339
420
  // ---------- state API for the control surfaces (ADP-033/034/036) ----------
340
421
  /** Current settings (awaits the constructor load). */
@@ -344,7 +425,13 @@ export class Supervisor {
344
425
  /** Validate and persist a settings patch; returns per-field errors. */
345
426
  async updateSettings(patch) {
346
427
  await this.#ready;
347
- return this.#settings.update(patch);
428
+ const result = await this.#settings.update(patch);
429
+ if (result.ok && patch.mode !== undefined) {
430
+ // A global mode change is the user steering Adaptive explicitly
431
+ // (ADP-028): manual-selection suppression lifts for every session.
432
+ this.#manualSelections.clear();
433
+ }
434
+ return result;
348
435
  }
349
436
  /** The session's mode override, or `null` when it follows the global mode. */
350
437
  async getSessionMode(sessionID) {
@@ -359,6 +446,8 @@ export class Supervisor {
359
446
  async setSessionMode(sessionID, mode) {
360
447
  await this.#ready;
361
448
  await this.#sessionModes.set(sessionID, mode);
449
+ // A mode change is the user steering Adaptive explicitly (ADP-028).
450
+ this.#manualSelections.delete(sessionID);
362
451
  }
363
452
  /**
364
453
  * Bounded status snapshot for the status view (ADP-033). Health is read
@@ -416,14 +505,24 @@ export class Supervisor {
416
505
  case "execution-interrupted":
417
506
  await this.#onExecutionInterrupted(report);
418
507
  return;
419
- case "model-selected":
420
- this.#tracker.apply({
508
+ case "model-selected": {
509
+ const result = this.#tracker.apply({
421
510
  kind: "model-selected",
422
511
  sessionID: report.sessionID,
423
512
  at: report.at,
424
513
  model: report.model,
425
514
  });
515
+ // Whether the change was the user's pick or Adaptive's own switch
516
+ // decides who controls the session's model (ADP-028); the result
517
+ // carries the attribution the tracker resolved. Only the two
518
+ // selection sources reach this event kind.
519
+ const { source } = result.transition;
520
+ if (source === "selected-adaptive")
521
+ this.#manualSelections.delete(report.sessionID);
522
+ else
523
+ this.#noteManualSelection(report.sessionID);
426
524
  return;
525
+ }
427
526
  case "model-request":
428
527
  if (report.agent !== undefined) {
429
528
  if (!this.#agents.has(report.sessionID) &&
@@ -437,12 +536,20 @@ export class Supervisor {
437
536
  }
438
537
  this.#agents.set(report.sessionID, report.agent);
439
538
  }
440
- this.#tracker.apply({
441
- kind: "model-request",
442
- sessionID: report.sessionID,
443
- at: this.#options.clock(),
444
- model: report.model,
445
- });
539
+ // Only the agent loop's own request reports the model actually
540
+ // executing the unit's work. An auxiliary request (title, compaction,
541
+ // generate — ADP-027) carries the host's own cheap pick for that side
542
+ // request; adopting it as the session's model would misattribute the
543
+ // primary unit's health evidence and leave the next admission
544
+ // believing the unit runs on the wrong resource.
545
+ if (report.requestKind === "primary") {
546
+ this.#tracker.apply({
547
+ kind: "model-request",
548
+ sessionID: report.sessionID,
549
+ at: this.#options.clock(),
550
+ model: report.model,
551
+ });
552
+ }
446
553
  return;
447
554
  case "http-response":
448
555
  await this.#onHttpResponse(report);
@@ -481,14 +588,25 @@ export class Supervisor {
481
588
  // this admission re-routes from scratch.
482
589
  this.#continuationTiers.delete(sessionID);
483
590
  this.#promptAdmission.delete(sessionID);
591
+ this.#escalations.delete(sessionID);
484
592
  // A pending probe ticket from an earlier prompt admission (or a failed
485
593
  // recovery) can only be carried by the unit the ORIGINAL prompt opens;
486
- // a fresh admission means that episode never ran, so the ticket re-arms
487
- // here instead of being adopted by whichever unit opens next.
594
+ // a fresh admission means that episode never ran, so the ticket is
595
+ // released here instead of being adopted by whichever unit opens next.
488
596
  const stale = this.#sessionProbeTickets.get(sessionID);
489
597
  if (stale !== undefined) {
490
598
  this.#sessionProbeTickets.delete(sessionID);
491
- this.#reportProbeOutcome(stale, { success: false, category: "user_cancelled" });
599
+ this.#health.releaseProbe(stale);
600
+ }
601
+ if (this.#manuallySelected(sessionID, settings)) {
602
+ // ADP-028: the user's own model pick stands. Recording the defer (no
603
+ // switch, no marker) leaves OpenCode's choice — the picked model — in
604
+ // place; execution-started will skip routing the same way.
605
+ await this.#recordReceipt({
606
+ sessionID,
607
+ decision: { kind: "defer", reason: "routing-defer", detail: "manual-selection" },
608
+ });
609
+ return;
492
610
  }
493
611
  let probeTicket;
494
612
  try {
@@ -513,9 +631,9 @@ export class Supervisor {
513
631
  }
514
632
  catch (error) {
515
633
  // A probe admitted for this turn is lost when the admission fails (the
516
- // switch threw): no unit will ever carry the ticket, so re-arm it.
634
+ // switch threw): no unit will ever carry the ticket, so release it.
517
635
  if (probeTicket !== undefined) {
518
- this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
636
+ this.#health.releaseProbe(probeTicket);
519
637
  }
520
638
  // Fail-open: no marker → execution-started re-routes the unit itself.
521
639
  logFailOpen("admission", error);
@@ -582,14 +700,22 @@ export class Supervisor {
582
700
  }
583
701
  }
584
702
  /** Fresh admission: routing ladder → scheduler, raw (unwrapped). */
585
- async #admit(settings, sessionID) {
703
+ async #admit(settings, sessionID, requiredTier) {
704
+ // The agent-declared tier request (adaptive_tier tool) outranks the
705
+ // role hints but stays below the pin (ADP-002 ladder). The note is
706
+ // recorded fire-and-forget, so flush the store's queue before reading.
707
+ await this.#tierRequests.flush();
708
+ const tierRequest = this.#tierRequests.get(sessionID);
709
+ let routedTier;
710
+ if (requiredTier !== undefined) {
711
+ routedTier = { tier: requiredTier, via: "manual-command" };
712
+ }
713
+ else if (tierRequest !== null) {
714
+ routedTier = { tier: tierRequest, via: "manual-command" };
715
+ }
586
716
  const routing = resolveRouting({
587
717
  ...(settings.pin !== null ? { pin: settings.pin } : {}),
588
- // The agent-declared tier request (adaptive_tier tool) outranks the
589
- // role hints but stays below the pin (ADP-002 ladder).
590
- ...(this.#tierRequests.has(sessionID)
591
- ? { tierRequest: this.#tierRequests.get(sessionID) }
592
- : {}),
718
+ ...(routedTier !== undefined ? { tierRequest: routedTier } : {}),
593
719
  // The agent-role hint is the session's last observed agent — the host
594
720
  // reports it on every model request (ADP-002 level 3).
595
721
  ...(this.#agents.has(sessionID) ? { agent: this.#agents.get(sessionID) } : {}),
@@ -603,10 +729,13 @@ export class Supervisor {
603
729
  await this.#capacity.refresh();
604
730
  const health = {};
605
731
  const capacity = {};
606
- // Tickets issued by this assessment sweep (see the re-arm below).
732
+ // Tickets issued by this assessment sweep (see the release below).
607
733
  const issued = [];
608
- for (const candidate of inventory) {
609
- const ref = candidateRef(candidate);
734
+ // Only the routed candidates are assessed: assess flips a due breaker to
735
+ // half_open, and admitting a quick unit has no business touching an
736
+ // advanced-tier resource's breaker.
737
+ const routed = routing.kind === "pin" ? [routing.model] : tierCandidates;
738
+ for (const ref of routed) {
610
739
  const mapKey = schedulerHealthKey(ref);
611
740
  const assessment = this.#health.assess(refKey(ref));
612
741
  health[mapKey] = assessment;
@@ -630,12 +759,12 @@ export class Supervisor {
630
759
  };
631
760
  const decision = admit(input);
632
761
  // Assess admits a half-open probe for every due breaker it touches —
633
- // including candidates this decision passed over (a ready sibling wins)
634
- // or dispatched nothing for (defer/park). An issued-but-undispatched
635
- // probe would wedge its resource in half_open: no unit ever carries the
636
- // ticket, so nothing reports it and the resource never recovers. Re-arm
637
- // each unselected ticket with the breaker's same backoff window — a
638
- // probe that never ran is no health evidence.
762
+ // including candidates this decision passed over (an earlier ready
763
+ // candidate wins) or dispatched nothing for (defer/park). An
764
+ // issued-but-undispatched probe would wedge its resource in half_open:
765
+ // no unit ever carries the ticket. Release each unselected ticket — it
766
+ // stays due, because a probe that never ran is no health evidence and
767
+ // restarting its backoff would push it back on every admission.
639
768
  const selectedId = decision.kind === "apply" ? decision.evidence.probeId : undefined;
640
769
  let probeTicket;
641
770
  for (const ticket of issued) {
@@ -643,12 +772,21 @@ export class Supervisor {
643
772
  probeTicket = ticket;
644
773
  }
645
774
  else {
646
- this.#health.reportProbe(ticket, { success: false, category: "user_cancelled" });
775
+ this.#health.releaseProbe(ticket);
647
776
  }
648
777
  }
649
778
  return { routing, decision, ...(probeTicket !== undefined ? { probeTicket } : {}) };
650
779
  }
651
780
  async #routeAdmission(unit, settings, mode) {
781
+ if (this.#manuallySelected(unit.sessionID, settings)) {
782
+ // ADP-028: the session runs on the user's own pick — no re-route.
783
+ await this.#recordReceipt({
784
+ sessionID: unit.sessionID,
785
+ unitID: unit.id,
786
+ decision: { kind: "defer", reason: "routing-defer", detail: "manual-selection" },
787
+ });
788
+ return;
789
+ }
652
790
  const { routing, decision, probeTicket } = await this.#admit(settings, unit.sessionID);
653
791
  // The tier the unit was admitted under is escalation context (ADP-015);
654
792
  // recorded for every tiered routing outcome (including a defer, which
@@ -663,9 +801,9 @@ export class Supervisor {
663
801
  }
664
802
  catch (error) {
665
803
  // The episode cannot carry the probe when the switch itself failed —
666
- // re-arm the ticket instead of wedging the resource half-open.
804
+ // release the ticket instead of wedging the resource half-open.
667
805
  if (probeTicket !== undefined) {
668
- this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
806
+ this.#health.releaseProbe(probeTicket);
669
807
  }
670
808
  throw error;
671
809
  }
@@ -675,8 +813,8 @@ export class Supervisor {
675
813
  }
676
814
  if (probeTicket !== undefined) {
677
815
  // OBSERVE wraps an apply — including a probe admission — without
678
- // running it: no episode will ever report the ticket, so re-arm it.
679
- this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
816
+ // running it: no episode will ever report the ticket, so release it.
817
+ this.#health.releaseProbe(probeTicket);
680
818
  }
681
819
  // Observe-only records the identical decision; defer and park leave the
682
820
  // episode on OpenCode's own model (admission cannot hold an episode).
@@ -702,7 +840,7 @@ export class Supervisor {
702
840
  async #onExecutionSucceeded(report) {
703
841
  const unit = this.#tracker.active(report.sessionID);
704
842
  const model = unit?.model ?? this.#tracker.lastModel(report.sessionID)?.model;
705
- const probeTicket = unit !== undefined ? this.#probeTickets.get(unit.id) : undefined;
843
+ const probeTicket = this.#takeProbeTicket(unit?.id);
706
844
  const closed = this.#tracker.apply({
707
845
  kind: "execution-succeeded",
708
846
  sessionID: report.sessionID,
@@ -714,7 +852,6 @@ export class Supervisor {
714
852
  }
715
853
  if (unit !== undefined) {
716
854
  if (probeTicket !== undefined) {
717
- this.#probeTickets.delete(unit.id);
718
855
  this.#reportProbeOutcome(probeTicket, { success: true });
719
856
  }
720
857
  await this.#attachOutcome(unit, { status: "succeeded" });
@@ -724,19 +861,16 @@ export class Supervisor {
724
861
  const unit = this.#tracker.active(report.sessionID);
725
862
  const model = unit?.model ?? this.#tracker.lastModel(report.sessionID)?.model;
726
863
  const unitID = unit?.id;
727
- // Budget and tier are read before the close clears the per-unit maps.
864
+ // The tier is read before the close clears the per-unit maps.
728
865
  const admissionTier = unitID !== undefined ? this.#admissionTiers.get(unitID) : undefined;
729
- const escalationUsed = unitID !== undefined ? (this.#escalations.get(unitID) ?? 0) : 0;
866
+ const escalationUsed = this.#escalations.get(report.sessionID) ?? 0;
730
867
  // Tool evidence is captured pre-close like the other per-unit maps.
731
868
  const toolEvidence = unitID !== undefined ? (this.#escalationEvidence.get(unitID) ?? []) : [];
732
- const probeTicket = unitID !== undefined ? this.#probeTickets.get(unitID) : undefined;
733
- const closed = this.#tracker.apply({
734
- kind: "execution-failed",
735
- sessionID: report.sessionID,
736
- at: report.at,
737
- error: report.error,
738
- });
739
- this.#afterUnitClose(closed);
869
+ const probeTicket = this.#takeProbeTicket(unitID);
870
+ // The terminal classifies and dedups BEFORE the close: the close clears
871
+ // the unit's attempt-failure entry, and the terminal must consult it —
872
+ // it repeats the episode's last attempt, which an attempt signal
873
+ // (http-response or retry hook) already counted.
740
874
  const classification = classifyFailure({
741
875
  structured: {
742
876
  type: report.error.type,
@@ -750,13 +884,20 @@ export class Supervisor {
750
884
  // The retry hook deliberately omits text: it must stay conservative.
751
885
  text: report.error.message,
752
886
  });
753
- if (model !== undefined) {
887
+ const countTerminal = this.#countTerminalFailure(unitID, classification.category);
888
+ const closed = this.#tracker.apply({
889
+ kind: "execution-failed",
890
+ sessionID: report.sessionID,
891
+ at: report.at,
892
+ error: report.error,
893
+ });
894
+ this.#afterUnitClose(closed);
895
+ if (model !== undefined && countTerminal) {
754
896
  this.#observeHealth(failureEvent(refKey(model), classification));
755
897
  }
756
898
  if (unit !== undefined) {
757
899
  await this.#attachOutcome(unit, { status: "failed", category: classification.category });
758
900
  if (probeTicket !== undefined) {
759
- this.#probeTickets.delete(unit.id);
760
901
  this.#reportProbeOutcome(probeTicket, { success: false, category: classification.category });
761
902
  }
762
903
  }
@@ -783,8 +924,11 @@ export class Supervisor {
783
924
  return [];
784
925
  }
785
926
  const eligible = [];
927
+ // Compared by resource key: the host reports base-model calls as variant
928
+ // "default", which is the same resource as the bare tier entry.
929
+ const failedID = resourceKeyID(refKey(failed));
786
930
  for (const ref of refs) {
787
- if (sameModelRef(ref, failed))
931
+ if (resourceKeyID(refKey(ref)) === failedID)
788
932
  continue;
789
933
  const candidate = inventory.find((entry) => sameModelRef(candidateRef(entry), ref));
790
934
  if (candidate === undefined)
@@ -822,11 +966,11 @@ export class Supervisor {
822
966
  currentTier: admissionTier,
823
967
  nextTierResources: eligible,
824
968
  evidence,
825
- budget: { used: escalationUsed, max: MAX_ESCALATIONS_PER_UNIT },
969
+ budget: { used: escalationUsed, max: MAX_ESCALATIONS_PER_TASK },
826
970
  });
827
971
  const wrapped = schedulerForMode(plan, this.#modeFor(report.sessionID, settings));
828
972
  if (wrapped.kind === "escalate") {
829
- this.#escalations.set(unitID, escalationUsed + 1);
973
+ this.#escalations.set(report.sessionID, escalationUsed + 1);
830
974
  // Recorded unit-less on purpose: the receipt describes the CONTINUATION
831
975
  // decision, so the continuation unit adopts it and its outcome attaches
832
976
  // here (the failed unit's own outcome attaches to its apply receipt).
@@ -838,10 +982,11 @@ export class Supervisor {
838
982
  // Continue the interrupted work on the escalated resource; the unit is
839
983
  // terminally failed, so an ops failure is only logged (never parked).
840
984
  try {
841
- await this.#ownRecovery(report.sessionID, wrapped.target, wrapped.tier);
985
+ await this.#ownRecovery(report.sessionID, wrapped.target, wrapped.tier, () => this.#modeFor(report.sessionID, settings) !== "off");
842
986
  }
843
987
  catch (error) {
844
- logFailOpen("escalation-ops", error);
988
+ if (!(error instanceof RecoveryAbortedError))
989
+ logFailOpen("escalation-ops", error);
845
990
  }
846
991
  return;
847
992
  }
@@ -858,7 +1003,13 @@ export class Supervisor {
858
1003
  return;
859
1004
  }
860
1005
  const unit = this.#tracker.active(report.sessionID);
861
- const probeTicket = unit !== undefined ? this.#probeTickets.get(unit.id) : undefined;
1006
+ const probeTicket = this.#takeProbeTicket(unit?.id);
1007
+ // An interruption while a recovery is pending on this session aborts it
1008
+ // at its post-settle guard (the pending recovery's decision predates the
1009
+ // interruption and must not act after it).
1010
+ if (this.#recoveryPending.has(report.sessionID)) {
1011
+ this.#recoveryInterrupted.add(report.sessionID);
1012
+ }
862
1013
  const closed = this.#tracker.apply({
863
1014
  kind: "execution-interrupted",
864
1015
  sessionID: report.sessionID,
@@ -872,7 +1023,6 @@ export class Supervisor {
872
1023
  // An interrupted probe is no evidence about resource health: re-arm the
873
1024
  // breaker's same window rather than doubling it.
874
1025
  if (probeTicket !== undefined) {
875
- this.#probeTickets.delete(unit.id);
876
1026
  this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
877
1027
  }
878
1028
  await this.#attachOutcome(unit, { status: "interrupted", reason });
@@ -889,23 +1039,81 @@ export class Supervisor {
889
1039
  // unknown ticket) are reported by its boolean return, not by throwing.
890
1040
  this.#health.reportProbe(ticket, outcome);
891
1041
  }
1042
+ /**
1043
+ * One attempt's failure evidence (http-response event or retry hook):
1044
+ * returns whether THIS observation is the attempt's first count.
1045
+ */
1046
+ #countAttemptFailure(unitID, source, category) {
1047
+ if (unitID === undefined)
1048
+ return true;
1049
+ const ev = this.#attemptFailures.get(unitID) ?? {};
1050
+ if (source === "http" ? ev.retry === category : ev.http === category) {
1051
+ // The other host signal already counted this attempt: consume it.
1052
+ const closed = { ...ev, http: undefined, retry: undefined, closed: category };
1053
+ this.#attemptFailures.set(unitID, closed);
1054
+ return false;
1055
+ }
1056
+ this.#attemptFailures.set(unitID, source === "http" ? { ...ev, http: category } : { ...ev, retry: category });
1057
+ return true;
1058
+ }
1059
+ /**
1060
+ * The terminal `execution-failed` repeats the episode's last attempt: it
1061
+ * counts only when the last attempt left no live evidence (e.g. a
1062
+ * capability failure that never produced an HTTP response or retry hook).
1063
+ */
1064
+ #countTerminalFailure(unitID, category) {
1065
+ if (unitID === undefined)
1066
+ return true;
1067
+ const ev = this.#attemptFailures.get(unitID) ?? {};
1068
+ if (ev.http === category) {
1069
+ this.#attemptFailures.set(unitID, { ...ev, http: undefined });
1070
+ return false;
1071
+ }
1072
+ if (ev.retry === category) {
1073
+ this.#attemptFailures.set(unitID, { ...ev, retry: undefined });
1074
+ return false;
1075
+ }
1076
+ if (ev.closed === category) {
1077
+ this.#attemptFailures.set(unitID, { ...ev, closed: undefined });
1078
+ return false;
1079
+ }
1080
+ this.#attemptFailures.set(unitID, { ...ev, closed: category });
1081
+ return true;
1082
+ }
1083
+ /**
1084
+ * Hands a closing unit's probe ticket to its explicit outcome reporter.
1085
+ * Taken BEFORE the close: #afterUnitClose treats any ticket still held as
1086
+ * lost and re-opens the breaker, which would turn a probe's real outcome
1087
+ * into a stale report.
1088
+ */
1089
+ #takeProbeTicket(unitID) {
1090
+ if (unitID === undefined)
1091
+ return undefined;
1092
+ const ticket = this.#probeTickets.get(unitID);
1093
+ this.#probeTickets.delete(unitID);
1094
+ return ticket;
1095
+ }
892
1096
  /** Bounded per-unit bookkeeping once the tracker closed the unit. */
893
1097
  #afterUnitClose(closed) {
894
1098
  const unitID = closedUnitID(closed);
895
1099
  if (unitID !== undefined) {
896
1100
  this.#vetoes.delete(unitID);
897
- this.#escalations.delete(unitID);
898
1101
  this.#admissionTiers.delete(unitID);
899
1102
  this.#escalationEvidence.delete(unitID);
900
1103
  const leftover = this.#probeTickets.get(unitID);
901
1104
  if (leftover !== undefined) {
902
- // The explicit outcome reporters run before this cleanup; reaching
903
- // here means the unit ended without one (superseded or lost).
1105
+ // The explicit outcome reporters take their ticket before this
1106
+ // cleanup; reaching here means the unit ended without one
1107
+ // (superseded or lost).
904
1108
  this.#reportProbeOutcome(leftover, { success: false, category: "user_cancelled" });
905
1109
  this.#probeTickets.delete(unitID);
906
1110
  }
907
1111
  }
908
1112
  if (closed.kind === "opened" || closed.kind === "closed") {
1113
+ const unitID = closedUnitID(closed);
1114
+ if (unitID !== undefined) {
1115
+ this.#attemptFailures.delete(unitID);
1116
+ }
909
1117
  // Per-session flags for a unit that will never open again must not
910
1118
  // leak: a stale continuation mark would swallow a real user prompt's
911
1119
  // admission; a stale admission marker would skip routing. The guard
@@ -939,7 +1147,11 @@ export class Supervisor {
939
1147
  ? { headers: { "retry-after": report.retryAfterHeader } }
940
1148
  : {}),
941
1149
  });
942
- this.#observeHealth(failureEvent(refKey(model), classification));
1150
+ // The retry hook classifies the same request for the host's retry
1151
+ // decision; counting both would double every attempt (ADP-019).
1152
+ if (this.#countAttemptFailure(unit?.id, "http", classification.category)) {
1153
+ this.#observeHealth(failureEvent(refKey(model), classification));
1154
+ }
943
1155
  }
944
1156
  // ---------- retry hook ----------
945
1157
  async #handleRetry(input, settings) {
@@ -952,7 +1164,12 @@ export class Supervisor {
952
1164
  },
953
1165
  status: input.error.status,
954
1166
  });
955
- this.#observeHealth(failureEvent(refKey(input.model), classification));
1167
+ const unitID = this.#tracker.active(input.sessionID)?.id;
1168
+ // The pumped http-response event describes the same failed request;
1169
+ // counting both would double every attempt (ADP-019).
1170
+ if (this.#countAttemptFailure(unitID, "retry", classification.category)) {
1171
+ this.#observeHealth(failureEvent(refKey(input.model), classification));
1172
+ }
956
1173
  // Recovery = fresh admission; the failed resource is already health-blocked.
957
1174
  let recovery = { kind: "none" };
958
1175
  let routingTier;
@@ -976,7 +1193,6 @@ export class Supervisor {
976
1193
  catch (error) {
977
1194
  logFailOpen("recovery", error);
978
1195
  }
979
- const unitID = this.#tracker.active(input.sessionID)?.id;
980
1196
  const vetoesUsed = unitID !== undefined ? (this.#vetoes.get(unitID) ?? 0) : 0;
981
1197
  const core = decideRetry({
982
1198
  classification,
@@ -988,10 +1204,10 @@ export class Supervisor {
988
1204
  const wrapped = retryForMode(core, this.#modeFor(input.sessionID, settings));
989
1205
  if (wrapped.kind !== "veto") {
990
1206
  // OBSERVE (and a declined veto) never runs the recovery episode, so an
991
- // admitted probe has no continuation to report it — re-arm it here or
1207
+ // admitted probe has no continuation to report it — release it here or
992
1208
  // the resource wedges half-open.
993
1209
  if (probeTicket !== undefined) {
994
- this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
1210
+ this.#health.releaseProbe(probeTicket);
995
1211
  }
996
1212
  return;
997
1213
  }
@@ -1004,10 +1220,10 @@ export class Supervisor {
1004
1220
  if (probeTicket !== undefined) {
1005
1221
  // The session holds one pending probe slot. A still-pending older
1006
1222
  // ticket belongs to a recovery whose continuation has not started (its
1007
- // episode can no longer claim this slot) — re-arm it before replacing.
1223
+ // episode can no longer claim this slot) — release it before replacing.
1008
1224
  const stale = this.#sessionProbeTickets.get(input.sessionID);
1009
1225
  if (stale !== undefined && stale !== probeTicket) {
1010
- this.#reportProbeOutcome(stale, { success: false, category: "user_cancelled" });
1226
+ this.#health.releaseProbe(stale);
1011
1227
  }
1012
1228
  this.#sessionProbeTickets.set(input.sessionID, probeTicket);
1013
1229
  }
@@ -1024,17 +1240,28 @@ export class Supervisor {
1024
1240
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- a veto implies the admission was an apply (the recovery exists only then)
1025
1241
  decision: admission,
1026
1242
  });
1027
- void this.#ownRecovery(input.sessionID, wrapped.model, routingTier)
1243
+ void this.#ownRecovery(input.sessionID, wrapped.model, routingTier, () => this.#modeFor(input.sessionID, settings) !== "off")
1028
1244
  .then(() => undefined)
1029
1245
  .catch(async (error) => {
1246
+ if (error instanceof RecoveryAbortedError) {
1247
+ // The user steered away (interruption / OFF) during the settle: the
1248
+ // veto stands, but no continuation episode will ever carry the
1249
+ // probe it admitted — release it instead of wedging half-open.
1250
+ if (probeTicket !== undefined &&
1251
+ this.#sessionProbeTickets.get(input.sessionID) === probeTicket) {
1252
+ this.#sessionProbeTickets.delete(input.sessionID);
1253
+ this.#health.releaseProbe(probeTicket);
1254
+ }
1255
+ return;
1256
+ }
1030
1257
  logFailOpen("recovery-ops", error);
1031
1258
  // The recovery rejected before (or while) delivering its continuation
1032
- // prompt, so no unit will ever carry the probe it admitted — re-arm
1259
+ // prompt, so no unit will ever carry the probe it admitted — release
1033
1260
  // it instead of wedging the resource half-open.
1034
1261
  if (probeTicket !== undefined &&
1035
1262
  this.#sessionProbeTickets.get(input.sessionID) === probeTicket) {
1036
1263
  this.#sessionProbeTickets.delete(input.sessionID);
1037
- this.#reportProbeOutcome(probeTicket, { success: false, category: "user_cancelled" });
1264
+ this.#health.releaseProbe(probeTicket);
1038
1265
  }
1039
1266
  if (unitID === undefined || routingTier === undefined)
1040
1267
  return;
@@ -1059,17 +1286,33 @@ export class Supervisor {
1059
1286
  * continuation's prompt hook re-arms the admission marker (the unit must
1060
1287
  * not be re-routed: that would discard the recovery/escalation decision).
1061
1288
  */
1062
- async #ownRecovery(sessionID, model, tier) {
1289
+ async #ownRecovery(sessionID, model, tier, stillValid) {
1063
1290
  if (tier !== undefined)
1064
1291
  this.#continuationTiers.set(sessionID, tier);
1065
- const alive = await this.#isSessionAlive(sessionID);
1066
- const recovery = { kind: "switch", model };
1067
- const action = planContinuation({
1068
- recovery,
1069
- continuationPossible: alive,
1070
- });
1292
+ // The guard window opens before the first await so an interrupt pumped
1293
+ // during the admission race is still seen.
1294
+ this.#recoveryPending.add(sessionID);
1071
1295
  try {
1072
- await executeContinuation({ sessionID, action, ops: this.#ops });
1296
+ const alive = await this.#isSessionAlive(sessionID);
1297
+ const recovery = { kind: "switch", model };
1298
+ const action = planContinuation({
1299
+ recovery,
1300
+ continuationPossible: alive,
1301
+ });
1302
+ // executeContinuation settles first; the supervisor settles here so the
1303
+ // post-settle guard (mode OFF, user interruption) can abort before any
1304
+ // switch or prompt — the settle is the only step that can take
1305
+ // unbounded time, and a decision made before it may be stale after it.
1306
+ await this.#ops.settle(sessionID);
1307
+ if (!stillValid() || this.#recoveryInterrupted.delete(sessionID)) {
1308
+ throw new RecoveryAbortedError(sessionID);
1309
+ }
1310
+ // Already settled above; the action's own settle is a no-op.
1311
+ await executeContinuation({
1312
+ sessionID,
1313
+ action,
1314
+ ops: { ...this.#ops, settle: () => Promise.resolve() },
1315
+ });
1073
1316
  }
1074
1317
  catch (error) {
1075
1318
  // The continuation prompt never reached the host, so no unit will open
@@ -1079,6 +1322,9 @@ export class Supervisor {
1079
1322
  this.#continuationTiers.delete(sessionID);
1080
1323
  throw error;
1081
1324
  }
1325
+ finally {
1326
+ this.#recoveryPending.delete(sessionID);
1327
+ }
1082
1328
  }
1083
1329
  // ---------- parked-work resume (safe boundary: idle) ----------
1084
1330
  async #onIdle(settings) {
@@ -1087,7 +1333,8 @@ export class Supervisor {
1087
1333
  // pruned here (its per-session steering is worthless once the session is
1088
1334
  // gone), in the same sweep that clears the other per-session maps.
1089
1335
  const overrideIDs = this.#sessionModes.snapshot().map((record) => record.sessionID);
1090
- if (records.length === 0 && overrideIDs.length === 0 && this.#tierRequests.size === 0)
1336
+ const tierRequestIDs = this.#tierRequests.snapshot().map((record) => record.sessionID);
1337
+ if (records.length === 0 && overrideIDs.length === 0 && tierRequestIDs.length === 0)
1091
1338
  return;
1092
1339
  // OFF sessions are not supervised: their parked records are neither
1093
1340
  // dropped nor resumed, and no alive probe runs for them.
@@ -1100,7 +1347,7 @@ export class Supervisor {
1100
1347
  }
1101
1348
  // Tier-request holders join the alive sweep too: a dead session's sticky
1102
1349
  // tier request dies with its session, like the other per-session maps.
1103
- for (const sessionID of this.#tierRequests.keys()) {
1350
+ for (const sessionID of tierRequestIDs) {
1104
1351
  if (!alive.has(sessionID))
1105
1352
  alive.set(sessionID, await this.#isSessionAlive(sessionID));
1106
1353
  }
@@ -1114,9 +1361,18 @@ export class Supervisor {
1114
1361
  await this.#dropParked(dropped);
1115
1362
  }
1116
1363
  // Tier requests die with their session, like the other per-session maps.
1117
- for (const sessionID of this.#tierRequests.keys()) {
1118
- if (alive.get(sessionID) === false)
1119
- this.#tierRequests.delete(sessionID);
1364
+ // The fire-and-forget note means the queue may still hold writes; the
1365
+ // serialized set applies after them. A write failure just leaves the
1366
+ // record (fail-open — it will die with the session on the next sweep).
1367
+ for (const sessionID of tierRequestIDs) {
1368
+ if (alive.get(sessionID) === false) {
1369
+ try {
1370
+ await this.#tierRequests.set(sessionID, null);
1371
+ }
1372
+ catch (error) {
1373
+ logFailOpen("tier-requests", error);
1374
+ }
1375
+ }
1120
1376
  }
1121
1377
  // At most ONE continuation per session per sweep: prompting a session
1122
1378
  // with an active unit would supersede the user's in-flight episode (not
@@ -1138,6 +1394,7 @@ export class Supervisor {
1138
1394
  this.#continuationTiers.delete(sessionID);
1139
1395
  this.#promptAdmission.delete(sessionID);
1140
1396
  this.#continuationPending.delete(sessionID);
1397
+ this.#escalations.delete(sessionID);
1141
1398
  if (this.#sessionModes.get(sessionID) !== null) {
1142
1399
  try {
1143
1400
  await this.#sessionModes.set(sessionID, null);
@@ -1163,14 +1420,19 @@ export class Supervisor {
1163
1420
  // report's session's mode.
1164
1421
  const mode = this.#modeFor(record.sessionID, settings);
1165
1422
  let decision;
1423
+ let probeTicket;
1166
1424
  try {
1167
- decision = (await this.#admit(settings, record.sessionID)).decision;
1425
+ const admission = await this.#admit(settings, record.sessionID, record.tier);
1426
+ decision = admission.decision;
1427
+ probeTicket = admission.probeTicket;
1168
1428
  }
1169
1429
  catch (error) {
1170
1430
  logFailOpen("resume", error);
1171
1431
  return;
1172
1432
  }
1173
1433
  if (mode === "observe") {
1434
+ if (probeTicket !== undefined)
1435
+ this.#health.releaseProbe(probeTicket);
1174
1436
  await this.#recordReceipt({
1175
1437
  sessionID: record.sessionID,
1176
1438
  unitID: record.id,
@@ -1180,7 +1442,9 @@ export class Supervisor {
1180
1442
  return;
1181
1443
  }
1182
1444
  if (decision.kind !== "apply") {
1183
- // Still no usable resource (or the routing deferred) — stay parked.
1445
+ // Still no usable resource (or the routing deferred) — stay parked. The
1446
+ // admission held no probe ticket: #admit keeps a ticket only for apply
1447
+ // decisions and releases every other one itself.
1184
1448
  await this.#recordReceipt({
1185
1449
  sessionID: record.sessionID,
1186
1450
  unitID: record.id,
@@ -1190,8 +1454,11 @@ export class Supervisor {
1190
1454
  return;
1191
1455
  }
1192
1456
  const popped = await this.#popParked(record);
1193
- if (popped === undefined)
1457
+ if (popped === undefined) {
1458
+ if (probeTicket !== undefined)
1459
+ this.#health.releaseProbe(probeTicket);
1194
1460
  return;
1461
+ }
1195
1462
  // The resume continuation is not re-admitted (owned continuation), so
1196
1463
  // the freshly routed admission is recorded here, unit-less; the unit
1197
1464
  // that opens next adopts it and carries the outcome.
@@ -1204,10 +1471,23 @@ export class Supervisor {
1204
1471
  // freshly routed model, then prompt — the episode's first LLM call must
1205
1472
  // not hit the model the work was parked away from.
1206
1473
  try {
1207
- await this.#ownRecovery(record.sessionID, decision.model, record.tier);
1474
+ if (probeTicket !== undefined)
1475
+ this.#sessionProbeTickets.set(record.sessionID, probeTicket);
1476
+ await this.#ownRecovery(record.sessionID, decision.model, record.tier,
1477
+ // The parked session's own effective mode and its still-open unit
1478
+ // window are re-checked after the settle; a session that gained an
1479
+ // active unit must not be prompted (not a safe boundary).
1480
+ () => this.#modeFor(record.sessionID, settings) !== "off" &&
1481
+ this.#tracker.active(record.sessionID) === undefined);
1208
1482
  }
1209
1483
  catch (error) {
1210
- logFailOpen("resume", error);
1484
+ if (probeTicket !== undefined &&
1485
+ this.#sessionProbeTickets.get(record.sessionID) === probeTicket) {
1486
+ this.#sessionProbeTickets.delete(record.sessionID);
1487
+ this.#health.releaseProbe(probeTicket);
1488
+ }
1489
+ if (!(error instanceof RecoveryAbortedError))
1490
+ logFailOpen("resume", error);
1211
1491
  await this.#rePark(record);
1212
1492
  }
1213
1493
  }
@@ -1238,8 +1518,14 @@ export class Supervisor {
1238
1518
  await this.#options.session.get({ sessionID });
1239
1519
  return true;
1240
1520
  }
1241
- catch {
1242
- return false;
1521
+ catch (error) {
1522
+ // The host rejects `session.get` for a missing session with a plain
1523
+ // error and exposes no typed status; only a definitive not-found
1524
+ // counts as dead. Anything else is treated as alive (fail open toward
1525
+ // keeping durable user state, ADP-031): a transient storage or host
1526
+ // error must never prune parked work, mode overrides, or tier
1527
+ // requests — the next sweep re-probes instead.
1528
+ return !/not found/i.test(String(error));
1243
1529
  }
1244
1530
  }
1245
1531
  async #recordReceipt(input) {