visionclaw 0.1.218 → 0.1.219-dev.codex-issue-2805-worker-runtime.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 (595) hide show
  1. package/dist/agent/api-proxy.d.ts +0 -16
  2. package/dist/agent/api-proxy.d.ts.map +1 -1
  3. package/dist/agent/api-proxy.js +9 -8
  4. package/dist/agent/api-proxy.js.map +1 -1
  5. package/dist/agent/browser-launcher.d.ts +3 -0
  6. package/dist/agent/browser-launcher.d.ts.map +1 -1
  7. package/dist/agent/browser-launcher.js +101 -43
  8. package/dist/agent/browser-launcher.js.map +1 -1
  9. package/dist/agent/coding-conventions.d.ts +8 -0
  10. package/dist/agent/coding-conventions.d.ts.map +1 -0
  11. package/dist/agent/coding-conventions.js +19 -0
  12. package/dist/agent/coding-conventions.js.map +1 -0
  13. package/dist/agent/command-handlers.d.ts.map +1 -1
  14. package/dist/agent/command-handlers.js +4 -16
  15. package/dist/agent/command-handlers.js.map +1 -1
  16. package/dist/agent/context-action-restart-fallback.d.ts.map +1 -1
  17. package/dist/agent/context-action-restart-fallback.js +9 -3
  18. package/dist/agent/context-action-restart-fallback.js.map +1 -1
  19. package/dist/agent/context-refresh.d.ts.map +1 -1
  20. package/dist/agent/context-refresh.js +0 -7
  21. package/dist/agent/context-refresh.js.map +1 -1
  22. package/dist/agent/context.d.ts +2 -4
  23. package/dist/agent/context.d.ts.map +1 -1
  24. package/dist/agent/context.js +12 -21
  25. package/dist/agent/context.js.map +1 -1
  26. package/dist/agent/engine-fallback.d.ts +0 -35
  27. package/dist/agent/engine-fallback.d.ts.map +1 -1
  28. package/dist/agent/engine-fallback.js +4 -3
  29. package/dist/agent/engine-fallback.js.map +1 -1
  30. package/dist/agent/engine-switch.d.ts +19 -38
  31. package/dist/agent/engine-switch.d.ts.map +1 -1
  32. package/dist/agent/engine-switch.js +46 -111
  33. package/dist/agent/engine-switch.js.map +1 -1
  34. package/dist/agent/engines/browser-tool-policy.d.ts +12 -1
  35. package/dist/agent/engines/browser-tool-policy.d.ts.map +1 -1
  36. package/dist/agent/engines/browser-tool-policy.js +17 -2
  37. package/dist/agent/engines/browser-tool-policy.js.map +1 -1
  38. package/dist/agent/engines/claude/executor-shell-groups.d.ts +11 -0
  39. package/dist/agent/engines/claude/executor-shell-groups.d.ts.map +1 -0
  40. package/dist/agent/engines/claude/executor-shell-groups.js +22 -0
  41. package/dist/agent/engines/claude/executor-shell-groups.js.map +1 -0
  42. package/dist/agent/engines/claude/session.d.ts +13 -4
  43. package/dist/agent/engines/claude/session.d.ts.map +1 -1
  44. package/dist/agent/engines/claude/session.js +165 -84
  45. package/dist/agent/engines/claude/session.js.map +1 -1
  46. package/dist/agent/engines/pi/shared/usage.d.ts +5 -0
  47. package/dist/agent/engines/pi/shared/usage.d.ts.map +1 -1
  48. package/dist/agent/engines/pi/shared/usage.js +22 -3
  49. package/dist/agent/engines/pi/shared/usage.js.map +1 -1
  50. package/dist/agent/engines/pi/tools.d.ts +3 -10
  51. package/dist/agent/engines/pi/tools.d.ts.map +1 -1
  52. package/dist/agent/engines/pi/tools.js +6 -17
  53. package/dist/agent/engines/pi/tools.js.map +1 -1
  54. package/dist/agent/engines/pi-coding/executor-shell.d.ts +4 -0
  55. package/dist/agent/engines/pi-coding/executor-shell.d.ts.map +1 -0
  56. package/dist/agent/engines/pi-coding/executor-shell.js +175 -0
  57. package/dist/agent/engines/pi-coding/executor-shell.js.map +1 -0
  58. package/dist/agent/engines/pi-coding/session.d.ts +4 -4
  59. package/dist/agent/engines/pi-coding/session.d.ts.map +1 -1
  60. package/dist/agent/engines/pi-coding/session.js +118 -66
  61. package/dist/agent/engines/pi-coding/session.js.map +1 -1
  62. package/dist/agent/engines/pi-coding/tools.d.ts +2 -5
  63. package/dist/agent/engines/pi-coding/tools.d.ts.map +1 -1
  64. package/dist/agent/engines/pi-coding/tools.js +7 -5
  65. package/dist/agent/engines/pi-coding/tools.js.map +1 -1
  66. package/dist/agent/engines/session-types.d.ts +8 -8
  67. package/dist/agent/engines/session-types.d.ts.map +1 -1
  68. package/dist/agent/external-context/interrupt.d.ts +0 -2
  69. package/dist/agent/external-context/interrupt.d.ts.map +1 -1
  70. package/dist/agent/external-context/interrupt.js +0 -1
  71. package/dist/agent/external-context/interrupt.js.map +1 -1
  72. package/dist/agent/external-context/registry.d.ts +0 -2
  73. package/dist/agent/external-context/registry.d.ts.map +1 -1
  74. package/dist/agent/external-context/registry.js +2 -4
  75. package/dist/agent/external-context/registry.js.map +1 -1
  76. package/dist/agent/external-context/runtime.d.ts.map +1 -1
  77. package/dist/agent/external-context/runtime.js +15 -14
  78. package/dist/agent/external-context/runtime.js.map +1 -1
  79. package/dist/agent/external-context/types.d.ts +0 -2
  80. package/dist/agent/external-context/types.d.ts.map +1 -1
  81. package/dist/agent/fast-responder.d.ts +4 -20
  82. package/dist/agent/fast-responder.d.ts.map +1 -1
  83. package/dist/agent/fast-responder.js +28 -42
  84. package/dist/agent/fast-responder.js.map +1 -1
  85. package/dist/agent/interrupt-handler.d.ts +0 -6
  86. package/dist/agent/interrupt-handler.d.ts.map +1 -1
  87. package/dist/agent/interrupt-handler.js +5 -47
  88. package/dist/agent/interrupt-handler.js.map +1 -1
  89. package/dist/agent/lab-run-system-prompt.d.ts.map +1 -1
  90. package/dist/agent/lab-run-system-prompt.js +4 -1
  91. package/dist/agent/lab-run-system-prompt.js.map +1 -1
  92. package/dist/agent/lifecycle-trace.d.ts.map +1 -1
  93. package/dist/agent/lifecycle-trace.js +6 -5
  94. package/dist/agent/lifecycle-trace.js.map +1 -1
  95. package/dist/agent/loop.d.ts +8 -2
  96. package/dist/agent/loop.d.ts.map +1 -1
  97. package/dist/agent/loop.js +291 -156
  98. package/dist/agent/loop.js.map +1 -1
  99. package/dist/agent/memory-protocol.d.ts +2 -2
  100. package/dist/agent/memory-protocol.d.ts.map +1 -1
  101. package/dist/agent/memory-protocol.js +3 -15
  102. package/dist/agent/memory-protocol.js.map +1 -1
  103. package/dist/agent/memory-store.d.ts +58 -0
  104. package/dist/agent/memory-store.d.ts.map +1 -0
  105. package/dist/agent/memory-store.js +152 -0
  106. package/dist/agent/memory-store.js.map +1 -0
  107. package/dist/agent/model-provider.d.ts +18 -5
  108. package/dist/agent/model-provider.d.ts.map +1 -1
  109. package/dist/agent/model-provider.js +58 -18
  110. package/dist/agent/model-provider.js.map +1 -1
  111. package/dist/agent/nightly-maintenance/dreaming-batch.d.ts +184 -0
  112. package/dist/agent/nightly-maintenance/dreaming-batch.d.ts.map +1 -0
  113. package/dist/agent/nightly-maintenance/dreaming-batch.js +192 -0
  114. package/dist/agent/nightly-maintenance/dreaming-batch.js.map +1 -0
  115. package/dist/agent/nightly-maintenance/dreaming-files.d.ts +48 -0
  116. package/dist/agent/nightly-maintenance/dreaming-files.d.ts.map +1 -0
  117. package/dist/agent/nightly-maintenance/dreaming-files.js +109 -0
  118. package/dist/agent/nightly-maintenance/dreaming-files.js.map +1 -0
  119. package/dist/agent/nightly-maintenance/dreaming-ledger.d.ts +62 -0
  120. package/dist/agent/nightly-maintenance/dreaming-ledger.d.ts.map +1 -0
  121. package/dist/agent/nightly-maintenance/dreaming-ledger.js +146 -0
  122. package/dist/agent/nightly-maintenance/dreaming-ledger.js.map +1 -0
  123. package/dist/agent/nightly-maintenance/dreaming-planner.d.ts +86 -0
  124. package/dist/agent/nightly-maintenance/dreaming-planner.d.ts.map +1 -0
  125. package/dist/agent/nightly-maintenance/dreaming-planner.js +304 -0
  126. package/dist/agent/nightly-maintenance/dreaming-planner.js.map +1 -0
  127. package/dist/agent/nightly-maintenance/dreaming-runner.d.ts +93 -0
  128. package/dist/agent/nightly-maintenance/dreaming-runner.d.ts.map +1 -0
  129. package/dist/agent/nightly-maintenance/dreaming-runner.js +156 -0
  130. package/dist/agent/nightly-maintenance/dreaming-runner.js.map +1 -0
  131. package/dist/agent/nightly-maintenance/dreaming-sources.d.ts +115 -0
  132. package/dist/agent/nightly-maintenance/dreaming-sources.d.ts.map +1 -0
  133. package/dist/agent/nightly-maintenance/dreaming-sources.js +270 -0
  134. package/dist/agent/nightly-maintenance/dreaming-sources.js.map +1 -0
  135. package/dist/agent/nightly-maintenance/index.d.ts +28 -8
  136. package/dist/agent/nightly-maintenance/index.d.ts.map +1 -1
  137. package/dist/agent/nightly-maintenance/index.js +42 -15
  138. package/dist/agent/nightly-maintenance/index.js.map +1 -1
  139. package/dist/agent/nightly-maintenance/skill-learning-gate.js +1 -1
  140. package/dist/agent/nightly-maintenance/skill-learning-gate.js.map +1 -1
  141. package/dist/agent/nightly-maintenance/vocabulary-processor.d.ts +1 -1
  142. package/dist/agent/nightly-maintenance/vocabulary-processor.d.ts.map +1 -1
  143. package/dist/agent/owner-channel-resolver.d.ts +2 -0
  144. package/dist/agent/owner-channel-resolver.d.ts.map +1 -1
  145. package/dist/agent/owner-channel-resolver.js +4 -0
  146. package/dist/agent/owner-channel-resolver.js.map +1 -1
  147. package/dist/agent/product-identity.d.ts +5 -4
  148. package/dist/agent/product-identity.d.ts.map +1 -1
  149. package/dist/agent/product-identity.js +7 -6
  150. package/dist/agent/product-identity.js.map +1 -1
  151. package/dist/agent/public-activity.js +1 -1
  152. package/dist/agent/public-activity.js.map +1 -1
  153. package/dist/agent/runtime-context.d.ts +27 -0
  154. package/dist/agent/runtime-context.d.ts.map +1 -0
  155. package/dist/agent/runtime-context.js +60 -0
  156. package/dist/agent/runtime-context.js.map +1 -0
  157. package/dist/agent/runtime-disclosure-policy.d.ts +6 -0
  158. package/dist/agent/runtime-disclosure-policy.d.ts.map +1 -0
  159. package/dist/agent/runtime-disclosure-policy.js +16 -0
  160. package/dist/agent/runtime-disclosure-policy.js.map +1 -0
  161. package/dist/agent/runtime-surface.d.ts +15 -4
  162. package/dist/agent/runtime-surface.d.ts.map +1 -1
  163. package/dist/agent/runtime-surface.js +0 -1
  164. package/dist/agent/runtime-surface.js.map +1 -1
  165. package/dist/agent/session-manager.d.ts +30 -126
  166. package/dist/agent/session-manager.d.ts.map +1 -1
  167. package/dist/agent/session-manager.js +60 -484
  168. package/dist/agent/session-manager.js.map +1 -1
  169. package/dist/agent/state.d.ts +42 -2
  170. package/dist/agent/state.d.ts.map +1 -1
  171. package/dist/agent/state.js +21 -11
  172. package/dist/agent/state.js.map +1 -1
  173. package/dist/agent/stream-handler.d.ts +6 -13
  174. package/dist/agent/stream-handler.d.ts.map +1 -1
  175. package/dist/agent/stream-handler.js +8 -62
  176. package/dist/agent/stream-handler.js.map +1 -1
  177. package/dist/agent/system-prompt.d.ts +2 -0
  178. package/dist/agent/system-prompt.d.ts.map +1 -1
  179. package/dist/agent/system-prompt.js +21 -20
  180. package/dist/agent/system-prompt.js.map +1 -1
  181. package/dist/agent/tool-usage.d.ts.map +1 -1
  182. package/dist/agent/tool-usage.js +0 -1
  183. package/dist/agent/tool-usage.js.map +1 -1
  184. package/dist/agent/transcript/transcript-backfill.js +3 -3
  185. package/dist/agent/transcript/transcript-backfill.js.map +1 -1
  186. package/dist/agent/transcript/transcript-indexer.d.ts +3 -31
  187. package/dist/agent/transcript/transcript-indexer.d.ts.map +1 -1
  188. package/dist/agent/transcript/transcript-indexer.js +58 -57
  189. package/dist/agent/transcript/transcript-indexer.js.map +1 -1
  190. package/dist/agent/transcript-parsers/trivial-tools.d.ts.map +1 -1
  191. package/dist/agent/transcript-parsers/trivial-tools.js +0 -2
  192. package/dist/agent/transcript-parsers/trivial-tools.js.map +1 -1
  193. package/dist/agent/transcript-parsers/types.d.ts +1 -1
  194. package/dist/agent/transcript-summarizer.d.ts +14 -0
  195. package/dist/agent/transcript-summarizer.d.ts.map +1 -1
  196. package/dist/agent/transcript-summarizer.js +69 -45
  197. package/dist/agent/transcript-summarizer.js.map +1 -1
  198. package/dist/agent/usage/usage-report-queue.d.ts.map +1 -1
  199. package/dist/agent/usage/usage-report-queue.js +53 -52
  200. package/dist/agent/usage/usage-report-queue.js.map +1 -1
  201. package/dist/agent/usage/usage-report-types.d.ts +1 -1
  202. package/dist/agent/usage/usage-report-types.d.ts.map +1 -1
  203. package/dist/agent/vocabulary-glossary.d.ts +3 -3
  204. package/dist/agent/wake-cycle-runner.d.ts +22 -17
  205. package/dist/agent/wake-cycle-runner.d.ts.map +1 -1
  206. package/dist/agent/wake-cycle-runner.js +190 -320
  207. package/dist/agent/wake-cycle-runner.js.map +1 -1
  208. package/dist/builtin-skills/owner-manual/SKILL.md +2 -2
  209. package/dist/calendar/occurrence-ownership.d.ts.map +1 -1
  210. package/dist/calendar/occurrence-ownership.js +6 -5
  211. package/dist/calendar/occurrence-ownership.js.map +1 -1
  212. package/dist/calendar/scheduler.d.ts.map +1 -1
  213. package/dist/calendar/scheduler.js +4 -3
  214. package/dist/calendar/scheduler.js.map +1 -1
  215. package/dist/channels/interface.d.ts +8 -2
  216. package/dist/channels/interface.d.ts.map +1 -1
  217. package/dist/channels/interface.js +8 -1
  218. package/dist/channels/interface.js.map +1 -1
  219. package/dist/channels/manager.d.ts +12 -0
  220. package/dist/channels/manager.d.ts.map +1 -1
  221. package/dist/channels/manager.js +97 -8
  222. package/dist/channels/manager.js.map +1 -1
  223. package/dist/channels/queue.d.ts.map +1 -1
  224. package/dist/channels/queue.js +8 -0
  225. package/dist/channels/queue.js.map +1 -1
  226. package/dist/channels/telegram.d.ts.map +1 -1
  227. package/dist/channels/telegram.js +28 -2
  228. package/dist/channels/telegram.js.map +1 -1
  229. package/dist/channels/tui.d.ts +14 -1
  230. package/dist/channels/tui.d.ts.map +1 -1
  231. package/dist/channels/tui.js +64 -2
  232. package/dist/channels/tui.js.map +1 -1
  233. package/dist/channels/unavailable-target.d.ts +20 -0
  234. package/dist/channels/unavailable-target.d.ts.map +1 -0
  235. package/dist/channels/unavailable-target.js +82 -0
  236. package/dist/channels/unavailable-target.js.map +1 -0
  237. package/dist/cli/index.d.ts +2 -2
  238. package/dist/cli/index.d.ts.map +1 -1
  239. package/dist/cli/index.js +22 -53
  240. package/dist/cli/index.js.map +1 -1
  241. package/dist/config/index.d.ts +13 -7
  242. package/dist/config/index.d.ts.map +1 -1
  243. package/dist/config/index.js +51 -64
  244. package/dist/config/index.js.map +1 -1
  245. package/dist/config/types.d.ts +28 -7
  246. package/dist/config/types.d.ts.map +1 -1
  247. package/dist/config/types.js +22 -20
  248. package/dist/config/types.js.map +1 -1
  249. package/dist/conversations/lazy-runtime.d.ts +5 -0
  250. package/dist/conversations/lazy-runtime.d.ts.map +1 -0
  251. package/dist/conversations/lazy-runtime.js +12 -0
  252. package/dist/conversations/lazy-runtime.js.map +1 -0
  253. package/dist/conversations/runtime.d.ts +17 -0
  254. package/dist/conversations/runtime.d.ts.map +1 -0
  255. package/dist/conversations/runtime.js +92 -0
  256. package/dist/conversations/runtime.js.map +1 -0
  257. package/dist/conversations/scheduler.d.ts +57 -0
  258. package/dist/conversations/scheduler.d.ts.map +1 -0
  259. package/dist/conversations/scheduler.js +175 -0
  260. package/dist/conversations/scheduler.js.map +1 -0
  261. package/dist/data-processing/pipeline.d.ts +0 -2
  262. package/dist/data-processing/pipeline.d.ts.map +1 -1
  263. package/dist/data-processing/pipeline.js +19 -24
  264. package/dist/data-processing/pipeline.js.map +1 -1
  265. package/dist/data-processing/restructure.d.ts +0 -2
  266. package/dist/data-processing/restructure.d.ts.map +1 -1
  267. package/dist/data-processing/restructure.js +6 -9
  268. package/dist/data-processing/restructure.js.map +1 -1
  269. package/dist/data-processing/types.d.ts +2 -9
  270. package/dist/data-processing/types.d.ts.map +1 -1
  271. package/dist/data-processing/types.js +2 -13
  272. package/dist/data-processing/types.js.map +1 -1
  273. package/dist/email/email-route-store.d.ts.map +1 -1
  274. package/dist/email/email-route-store.js +6 -5
  275. package/dist/email/email-route-store.js.map +1 -1
  276. package/dist/i18n/messages.d.ts +26 -2
  277. package/dist/i18n/messages.d.ts.map +1 -1
  278. package/dist/i18n/messages.js +30 -5
  279. package/dist/i18n/messages.js.map +1 -1
  280. package/dist/index.js.map +1 -1
  281. package/dist/logger.d.ts +11 -0
  282. package/dist/logger.d.ts.map +1 -1
  283. package/dist/logger.js +49 -1
  284. package/dist/logger.js.map +1 -1
  285. package/dist/obs/mini-app/status.d.ts.map +1 -1
  286. package/dist/obs/mini-app/status.js +0 -1
  287. package/dist/obs/mini-app/status.js.map +1 -1
  288. package/dist/onboarding/onboarding-service-client.d.ts +1 -0
  289. package/dist/onboarding/onboarding-service-client.d.ts.map +1 -1
  290. package/dist/onboarding/onboarding-service-client.js.map +1 -1
  291. package/dist/onboarding/remote-set-owner.d.ts.map +1 -1
  292. package/dist/onboarding/remote-set-owner.js +0 -1
  293. package/dist/onboarding/remote-set-owner.js.map +1 -1
  294. package/dist/onboarding/set-owner.d.ts +1 -1
  295. package/dist/onboarding/set-owner.d.ts.map +1 -1
  296. package/dist/onboarding/set-owner.js +3 -6
  297. package/dist/onboarding/set-owner.js.map +1 -1
  298. package/dist/reconfigure.d.ts.map +1 -1
  299. package/dist/reconfigure.js +1 -32
  300. package/dist/reconfigure.js.map +1 -1
  301. package/dist/reset.d.ts.map +1 -1
  302. package/dist/reset.js +3 -1
  303. package/dist/reset.js.map +1 -1
  304. package/dist/threads/active-threads-store.d.ts.map +1 -1
  305. package/dist/threads/active-threads-store.js +8 -27
  306. package/dist/threads/active-threads-store.js.map +1 -1
  307. package/dist/threads/dreaming-execution.d.ts +10 -0
  308. package/dist/threads/dreaming-execution.d.ts.map +1 -0
  309. package/dist/threads/dreaming-execution.js +78 -0
  310. package/dist/threads/dreaming-execution.js.map +1 -0
  311. package/dist/threads/execution-authority.d.ts +43 -0
  312. package/dist/threads/execution-authority.d.ts.map +1 -0
  313. package/dist/threads/execution-authority.js +75 -0
  314. package/dist/threads/execution-authority.js.map +1 -0
  315. package/dist/threads/execution-budget.d.ts +107 -0
  316. package/dist/threads/execution-budget.d.ts.map +1 -0
  317. package/dist/threads/execution-budget.js +257 -0
  318. package/dist/threads/execution-budget.js.map +1 -0
  319. package/dist/threads/execution-host.d.ts +17 -0
  320. package/dist/threads/execution-host.d.ts.map +1 -0
  321. package/dist/threads/execution-host.js +66 -0
  322. package/dist/threads/execution-host.js.map +1 -0
  323. package/dist/threads/execution-scheduler.d.ts +162 -0
  324. package/dist/threads/execution-scheduler.d.ts.map +1 -0
  325. package/dist/threads/execution-scheduler.js +413 -0
  326. package/dist/threads/execution-scheduler.js.map +1 -0
  327. package/dist/threads/execution-snapshot.d.ts +91 -0
  328. package/dist/threads/execution-snapshot.d.ts.map +1 -0
  329. package/dist/threads/execution-snapshot.js +146 -0
  330. package/dist/threads/execution-snapshot.js.map +1 -0
  331. package/dist/threads/execution-store.d.ts +374 -0
  332. package/dist/threads/execution-store.d.ts.map +1 -0
  333. package/dist/threads/execution-store.js +910 -0
  334. package/dist/threads/execution-store.js.map +1 -0
  335. package/dist/threads/execution-thread-error.d.ts +9 -0
  336. package/dist/threads/execution-thread-error.d.ts.map +1 -0
  337. package/dist/threads/execution-thread-error.js +12 -0
  338. package/dist/threads/execution-thread-error.js.map +1 -0
  339. package/dist/threads/executor-exit.d.ts +18 -0
  340. package/dist/threads/executor-exit.d.ts.map +1 -0
  341. package/dist/threads/executor-exit.js +21 -0
  342. package/dist/threads/executor-exit.js.map +1 -0
  343. package/dist/threads/foreground-task.d.ts +91 -0
  344. package/dist/threads/foreground-task.d.ts.map +1 -0
  345. package/dist/threads/foreground-task.js +191 -0
  346. package/dist/threads/foreground-task.js.map +1 -0
  347. package/dist/threads/foreman-business.d.ts +9 -0
  348. package/dist/threads/foreman-business.d.ts.map +1 -0
  349. package/dist/threads/foreman-business.js +117 -0
  350. package/dist/threads/foreman-business.js.map +1 -0
  351. package/dist/threads/foreman-client.d.ts +18 -0
  352. package/dist/threads/foreman-client.d.ts.map +1 -0
  353. package/dist/threads/foreman-client.js +51 -0
  354. package/dist/threads/foreman-client.js.map +1 -0
  355. package/dist/threads/foreman-delivery.d.ts +5 -0
  356. package/dist/threads/foreman-delivery.d.ts.map +1 -0
  357. package/dist/threads/foreman-delivery.js +21 -0
  358. package/dist/threads/foreman-delivery.js.map +1 -0
  359. package/dist/threads/foreman-execution.d.ts +32 -0
  360. package/dist/threads/foreman-execution.d.ts.map +1 -0
  361. package/dist/threads/foreman-execution.js +90 -0
  362. package/dist/threads/foreman-execution.js.map +1 -0
  363. package/dist/threads/foreman-host.d.ts +77 -0
  364. package/dist/threads/foreman-host.d.ts.map +1 -0
  365. package/dist/threads/foreman-host.js +891 -0
  366. package/dist/threads/foreman-host.js.map +1 -0
  367. package/dist/threads/foreman-protocol.d.ts +242 -0
  368. package/dist/threads/foreman-protocol.d.ts.map +1 -0
  369. package/dist/threads/foreman-protocol.js +116 -0
  370. package/dist/threads/foreman-protocol.js.map +1 -0
  371. package/dist/threads/foreman-store.d.ts +151 -0
  372. package/dist/threads/foreman-store.d.ts.map +1 -0
  373. package/dist/threads/foreman-store.js +299 -0
  374. package/dist/threads/foreman-store.js.map +1 -0
  375. package/dist/threads/generic-execution.d.ts +25 -0
  376. package/dist/threads/generic-execution.d.ts.map +1 -0
  377. package/dist/threads/generic-execution.js +86 -0
  378. package/dist/threads/generic-execution.js.map +1 -0
  379. package/dist/threads/init-thread-dir.d.ts +2 -2
  380. package/dist/threads/init-thread-dir.d.ts.map +1 -1
  381. package/dist/threads/init-thread-dir.js +6 -6
  382. package/dist/threads/init-thread-dir.js.map +1 -1
  383. package/dist/threads/ipc.d.ts +108 -2
  384. package/dist/threads/ipc.d.ts.map +1 -1
  385. package/dist/threads/ipc.js +14 -2
  386. package/dist/threads/ipc.js.map +1 -1
  387. package/dist/threads/lab-run-thread.d.ts +3 -5
  388. package/dist/threads/lab-run-thread.d.ts.map +1 -1
  389. package/dist/threads/lab-run-thread.js +3 -5
  390. package/dist/threads/lab-run-thread.js.map +1 -1
  391. package/dist/threads/lab-runtime-policy.d.ts +26 -21
  392. package/dist/threads/lab-runtime-policy.d.ts.map +1 -1
  393. package/dist/threads/lab-runtime-policy.js +31 -126
  394. package/dist/threads/lab-runtime-policy.js.map +1 -1
  395. package/dist/threads/process-group.d.ts +41 -0
  396. package/dist/threads/process-group.d.ts.map +1 -0
  397. package/dist/threads/process-group.js +161 -0
  398. package/dist/threads/process-group.js.map +1 -0
  399. package/dist/threads/relay-adapter.d.ts +8 -0
  400. package/dist/threads/relay-adapter.d.ts.map +1 -1
  401. package/dist/threads/relay-adapter.js +26 -1
  402. package/dist/threads/relay-adapter.js.map +1 -1
  403. package/dist/threads/supervisor.d.ts +191 -57
  404. package/dist/threads/supervisor.d.ts.map +1 -1
  405. package/dist/threads/supervisor.js +766 -427
  406. package/dist/threads/supervisor.js.map +1 -1
  407. package/dist/threads/task-workspace.d.ts +8 -0
  408. package/dist/threads/task-workspace.d.ts.map +1 -0
  409. package/dist/threads/task-workspace.js +52 -0
  410. package/dist/threads/task-workspace.js.map +1 -0
  411. package/dist/threads/worker-entry.d.ts.map +1 -1
  412. package/dist/threads/worker-entry.js +314 -34
  413. package/dist/threads/worker-entry.js.map +1 -1
  414. package/dist/threads/worker-execution-runtime.d.ts +75 -0
  415. package/dist/threads/worker-execution-runtime.d.ts.map +1 -0
  416. package/dist/threads/worker-execution-runtime.js +142 -0
  417. package/dist/threads/worker-execution-runtime.js.map +1 -0
  418. package/dist/threads/worker-execution.d.ts +35 -0
  419. package/dist/threads/worker-execution.d.ts.map +1 -0
  420. package/dist/threads/worker-execution.js +2 -0
  421. package/dist/threads/worker-execution.js.map +1 -0
  422. package/dist/threads/worker-runtime-policy.d.ts +42 -0
  423. package/dist/threads/worker-runtime-policy.d.ts.map +1 -0
  424. package/dist/threads/worker-runtime-policy.js +98 -0
  425. package/dist/threads/worker-runtime-policy.js.map +1 -0
  426. package/dist/tools/adapter-tool.d.ts +12 -0
  427. package/dist/tools/adapter-tool.d.ts.map +1 -0
  428. package/dist/tools/adapter-tool.js +2 -0
  429. package/dist/tools/adapter-tool.js.map +1 -0
  430. package/dist/tools/data-service.d.ts +2 -2
  431. package/dist/tools/delegation.d.ts +121 -0
  432. package/dist/tools/delegation.d.ts.map +1 -0
  433. package/dist/tools/delegation.js +106 -0
  434. package/dist/tools/delegation.js.map +1 -0
  435. package/dist/tools/dream-memory.d.ts +122 -0
  436. package/dist/tools/dream-memory.d.ts.map +1 -0
  437. package/dist/tools/dream-memory.js +513 -0
  438. package/dist/tools/dream-memory.js.map +1 -0
  439. package/dist/tools/email.d.ts +1 -1
  440. package/dist/tools/execution-control.d.ts +31 -0
  441. package/dist/tools/execution-control.d.ts.map +1 -0
  442. package/dist/tools/execution-control.js +71 -0
  443. package/dist/tools/execution-control.js.map +1 -0
  444. package/dist/tools/index.d.ts +5 -7
  445. package/dist/tools/index.d.ts.map +1 -1
  446. package/dist/tools/index.js +7 -13
  447. package/dist/tools/index.js.map +1 -1
  448. package/dist/tools/mcp-manager.d.ts +1 -1
  449. package/dist/tools/memory.d.ts +1 -1
  450. package/dist/tools/memory.d.ts.map +1 -1
  451. package/dist/tools/memory.js +69 -45
  452. package/dist/tools/memory.js.map +1 -1
  453. package/dist/tools/notify.d.ts +5 -0
  454. package/dist/tools/notify.d.ts.map +1 -1
  455. package/dist/tools/notify.js +45 -10
  456. package/dist/tools/notify.js.map +1 -1
  457. package/dist/tools/skill.d.ts +1 -1
  458. package/dist/tools/totp.d.ts +1 -1
  459. package/dist/tui/client.d.ts +1 -16
  460. package/dist/tui/client.d.ts.map +1 -1
  461. package/dist/tui/client.js +0 -4
  462. package/dist/tui/client.js.map +1 -1
  463. package/dist/tui/index.d.ts.map +1 -1
  464. package/dist/tui/index.js +0 -41
  465. package/dist/tui/index.js.map +1 -1
  466. package/dist/tui/protocol.d.ts +2 -1
  467. package/dist/tui/protocol.d.ts.map +1 -1
  468. package/dist/tui/protocol.js +2 -1
  469. package/dist/tui/protocol.js.map +1 -1
  470. package/dist/tui/renderer.d.ts.map +1 -1
  471. package/dist/tui/renderer.js +0 -5
  472. package/dist/tui/renderer.js.map +1 -1
  473. package/dist/tui/request-store.d.ts +1 -1
  474. package/dist/tui/request-store.d.ts.map +1 -1
  475. package/dist/tui/request-store.js +2 -2
  476. package/dist/tui/request-store.js.map +1 -1
  477. package/dist/tui/server.d.ts.map +1 -1
  478. package/dist/tui/server.js +137 -25
  479. package/dist/tui/server.js.map +1 -1
  480. package/dist/utils/claude-transcripts.d.ts +6 -0
  481. package/dist/utils/claude-transcripts.d.ts.map +1 -1
  482. package/dist/utils/claude-transcripts.js +8 -2
  483. package/dist/utils/claude-transcripts.js.map +1 -1
  484. package/dist/utils/restart.d.ts.map +1 -1
  485. package/dist/utils/restart.js +6 -0
  486. package/dist/utils/restart.js.map +1 -1
  487. package/dist-agent/bundle.cjs +2 -2
  488. package/package.json +1 -1
  489. package/dist/agent/mailbox.d.ts +0 -50
  490. package/dist/agent/mailbox.d.ts.map +0 -1
  491. package/dist/agent/mailbox.js +0 -124
  492. package/dist/agent/mailbox.js.map +0 -1
  493. package/dist/agent/nightly-maintenance/memory-dreaming-sweep.d.ts +0 -77
  494. package/dist/agent/nightly-maintenance/memory-dreaming-sweep.d.ts.map +0 -1
  495. package/dist/agent/nightly-maintenance/memory-dreaming-sweep.js +0 -158
  496. package/dist/agent/nightly-maintenance/memory-dreaming-sweep.js.map +0 -1
  497. package/dist/agent/nightly-maintenance/memory-dreaming.d.ts +0 -116
  498. package/dist/agent/nightly-maintenance/memory-dreaming.d.ts.map +0 -1
  499. package/dist/agent/nightly-maintenance/memory-dreaming.js +0 -458
  500. package/dist/agent/nightly-maintenance/memory-dreaming.js.map +0 -1
  501. package/dist/builtin-skills/catalog/phone-adb-automation/SKILL.md +0 -412
  502. package/dist/builtin-skills/catalog/phone-adb-automation/phone_input.sh +0 -132
  503. package/dist/builtin-skills/catalog/phone-adb-automation/phone_launch.sh +0 -166
  504. package/dist/builtin-skills/catalog/phone-adb-automation/phone_screenshot.sh +0 -87
  505. package/dist/builtin-skills/catalog/phone-adb-automation/phone_security_kbd.py +0 -174
  506. package/dist/builtin-skills/catalog/phone-adb-automation/phone_setup.sh +0 -274
  507. package/dist/builtin-skills/catalog/phone-adb-automation/phone_swipe.sh +0 -111
  508. package/dist/builtin-skills/catalog/phone-adb-automation/phone_tap.sh +0 -87
  509. package/dist/builtin-skills/catalog/phone-adb-automation/phone_ui_parse.py +0 -176
  510. package/dist/builtin-skills/catalog/phone-adb-automation/phone_wake_unlock.sh +0 -67
  511. package/dist/builtin-skills/macos-automation/SKILL.md +0 -132
  512. package/dist/builtin-skills/transcribe-audio/SKILL.md +0 -122
  513. package/dist/builtin-skills/visionclaw-manual/SKILL.md +0 -337
  514. package/dist/obs/rdp/icons/icons/app_windows.svg +0 -4
  515. package/dist/obs/rdp/icons/icons/clip_get.svg +0 -4
  516. package/dist/obs/rdp/icons/icons/clip_send.svg +0 -4
  517. package/dist/obs/rdp/icons/icons/clip_shared.svg +0 -4
  518. package/dist/obs/rdp/icons/icons/clipboard.svg +0 -4
  519. package/dist/obs/rdp/icons/icons/clipboard_shared.svg +0 -4
  520. package/dist/obs/rdp/icons/icons/control.svg +0 -4
  521. package/dist/obs/rdp/icons/icons/desktop.svg +0 -4
  522. package/dist/obs/rdp/icons/icons/display.svg +0 -4
  523. package/dist/obs/rdp/icons/icons/launchpad.svg +0 -4
  524. package/dist/obs/rdp/icons/icons/mission_control.svg +0 -4
  525. package/dist/obs/rdp/icons/icons/screenshot.svg +0 -4
  526. package/dist/obs/rdp/icons/icons/zoom_actual.svg +0 -4
  527. package/dist/obs/rdp/icons/icons/zoom_fit.svg +0 -4
  528. package/dist/obs/rdp/icons/icons/zoom_in.svg +0 -4
  529. package/dist/obs/rdp/icons/icons/zoom_out.svg +0 -4
  530. package/dist/realtime/assets/index.html +0 -1058
  531. package/dist/realtime/assets/samples/alloy.mp3 +0 -0
  532. package/dist/realtime/assets/samples/ash.mp3 +0 -0
  533. package/dist/realtime/assets/samples/ballad.mp3 +0 -0
  534. package/dist/realtime/assets/samples/cedar.mp3 +0 -0
  535. package/dist/realtime/assets/samples/coral.mp3 +0 -0
  536. package/dist/realtime/assets/samples/echo.mp3 +0 -0
  537. package/dist/realtime/assets/samples/marin.mp3 +0 -0
  538. package/dist/realtime/assets/samples/sage.mp3 +0 -0
  539. package/dist/realtime/assets/samples/shimmer.mp3 +0 -0
  540. package/dist/realtime/assets/samples/verse.mp3 +0 -0
  541. package/dist/threads/task-contracts.d.ts +0 -194
  542. package/dist/threads/task-contracts.d.ts.map +0 -1
  543. package/dist/threads/task-contracts.js +0 -82
  544. package/dist/threads/task-contracts.js.map +0 -1
  545. package/dist/threads/task-sidecar.d.ts +0 -143
  546. package/dist/threads/task-sidecar.d.ts.map +0 -1
  547. package/dist/threads/task-sidecar.js +0 -309
  548. package/dist/threads/task-sidecar.js.map +0 -1
  549. package/dist/threads/task-store.d.ts +0 -172
  550. package/dist/threads/task-store.d.ts.map +0 -1
  551. package/dist/threads/task-store.js +0 -594
  552. package/dist/threads/task-store.js.map +0 -1
  553. package/dist/threads/thread-task-context.d.ts +0 -40
  554. package/dist/threads/thread-task-context.d.ts.map +0 -1
  555. package/dist/threads/thread-task-context.js +0 -159
  556. package/dist/threads/thread-task-context.js.map +0 -1
  557. package/dist/threads/thread-task-controller.d.ts +0 -122
  558. package/dist/threads/thread-task-controller.d.ts.map +0 -1
  559. package/dist/threads/thread-task-controller.js +0 -276
  560. package/dist/threads/thread-task-controller.js.map +0 -1
  561. package/dist/threads/thread-task-runtime.d.ts +0 -320
  562. package/dist/threads/thread-task-runtime.d.ts.map +0 -1
  563. package/dist/threads/thread-task-runtime.js +0 -843
  564. package/dist/threads/thread-task-runtime.js.map +0 -1
  565. package/dist/tools/switch-session.d.ts +0 -15
  566. package/dist/tools/switch-session.d.ts.map +0 -1
  567. package/dist/tools/switch-session.js +0 -88
  568. package/dist/tools/switch-session.js.map +0 -1
  569. package/dist/tools/thread-tasks.d.ts +0 -10
  570. package/dist/tools/thread-tasks.d.ts.map +0 -1
  571. package/dist/tools/thread-tasks.js +0 -88
  572. package/dist/tools/thread-tasks.js.map +0 -1
  573. package/dist-agent/assets/index.html +0 -1058
  574. package/dist-agent/assets/samples/alloy.mp3 +0 -0
  575. package/dist-agent/assets/samples/ash.mp3 +0 -0
  576. package/dist-agent/assets/samples/ballad.mp3 +0 -0
  577. package/dist-agent/assets/samples/cedar.mp3 +0 -0
  578. package/dist-agent/assets/samples/coral.mp3 +0 -0
  579. package/dist-agent/assets/samples/echo.mp3 +0 -0
  580. package/dist-agent/assets/samples/marin.mp3 +0 -0
  581. package/dist-agent/assets/samples/sage.mp3 +0 -0
  582. package/dist-agent/assets/samples/shimmer.mp3 +0 -0
  583. package/dist-agent/assets/samples/verse.mp3 +0 -0
  584. package/dist-agent/playwriter-relay.cjs +0 -95
  585. package/dist-agent/realtime/assets/index.html +0 -1058
  586. package/dist-agent/realtime/assets/samples/alloy.mp3 +0 -0
  587. package/dist-agent/realtime/assets/samples/ash.mp3 +0 -0
  588. package/dist-agent/realtime/assets/samples/ballad.mp3 +0 -0
  589. package/dist-agent/realtime/assets/samples/cedar.mp3 +0 -0
  590. package/dist-agent/realtime/assets/samples/coral.mp3 +0 -0
  591. package/dist-agent/realtime/assets/samples/echo.mp3 +0 -0
  592. package/dist-agent/realtime/assets/samples/marin.mp3 +0 -0
  593. package/dist-agent/realtime/assets/samples/sage.mp3 +0 -0
  594. package/dist-agent/realtime/assets/samples/shimmer.mp3 +0 -0
  595. package/dist-agent/realtime/assets/samples/verse.mp3 +0 -0
@@ -1,13 +1,15 @@
1
1
  /**
2
- * WakeCycleRunner — encapsulates the inner session-switch loop, Claude
2
+ * WakeCycleRunner — encapsulates the wake loop, Claude
3
3
  * stream processing, error classification / retry logic, transcript
4
4
  * sanitization, and post-cycle transcript memory indexing.
5
5
  *
6
6
  * Extracted from loop.ts so the main loop is a thin orchestrator.
7
7
  */
8
- import { isEmailChannel, normalizeChannelName, presentsAssistantText } from "../channels/interface.js";
8
+ import { isEmailChannel, presentsAssistantText } from "../channels/interface.js";
9
9
  import { isCalendarInterruptMessage } from "./owner-initiated-eligibility.js";
10
10
  import { emailAddressesMatch } from "../utils/email.js";
11
+ import { EXTERNAL_WAKE_KIND } from "../channels/queue.js";
12
+ import { nextRecoveryPhase } from "./lifecycle-transitions.js";
11
13
  import { COMPACTION_FAILURE_CONTINUATION, CompactionFailureError } from "./compaction-failure.js";
12
14
  import { ToolOutputRepairError } from "./transcript-output-repair.js";
13
15
  import { getFallbackEngine, getLlmEffort, getLlmRetryEffort, setFallbackEngine } from "./model-provider.js";
@@ -31,36 +33,7 @@ import path from "path";
31
33
  import { randomUUID } from "node:crypto";
32
34
  import { runContextRefresh } from "./context-refresh.js";
33
35
  import { loadToolAbortRecoveryChain, saveToolAbortRecoveryChain } from "./recovery-chain.js";
34
- import { RECONCILE_WAIT_MS, buildThreadTaskContext, executionEvents, isTaskRelevantMessage, reconcileThreadTasks, recordExecutionEvent, } from "../threads/thread-task-runtime.js";
35
- import { isThreadTaskContextBlock } from "../threads/thread-task-context.js";
36
- /**
37
- * Session memos are internal working text (handoff instructions, worktree
38
- * paths, "do not notify" constraints). They must never be sent as owner chat.
39
- *
40
- * Last-resort ping when a coding completion was armed and this wake never
41
- * successfully notified (PR #839: do not silently drop the result). The
42
- * body is a fixed human line — never the memo, never a paraphrase of it.
43
- */
44
- async function deliverCodingCompletionFallbackPing(ownerConfig) {
45
- const message = t("coding.completion_unnotified", resolveLocale(ownerConfig.language));
46
- try {
47
- const result = await sendUserNotification({ message, purpose: "completion" });
48
- if (result.isError) {
49
- const text = result.content
50
- .map((c) => "text" in c ? c.text : "")
51
- .filter((s) => s.length > 0)
52
- .join(" ");
53
- logger.warn(`Coding completion notification failed: ${text || "unknown error"}`);
54
- return false;
55
- }
56
- logger.system("Delivered coding completion fallback ping (generic; not the session memo)");
57
- return true;
58
- }
59
- catch (err) {
60
- logger.warn(`Coding completion notification threw: ${err instanceof Error ? err.message : String(err)}`);
61
- return false;
62
- }
63
- }
36
+ import { getWorkerExecutionRuntime } from "../threads/worker-execution-runtime.js";
64
37
  const AUTH_PATTERNS = /\b(401|402|403|authentication|billing|unauthorized|forbidden|api.?key)\b/i;
65
38
  // Fatal environment/installation problems that won't fix themselves on retry.
66
39
  // In particular: the Claude Agent SDK immediately exits with code 1 when its
@@ -303,15 +276,15 @@ export function isSharedCredentialFailure(err) {
303
276
  }
304
277
  return classifyError(err) === "provider_failed" && !isEngineScopedFailure(err);
305
278
  }
306
- function formatOwnerErrorNotice(category, consecutiveErrors, sleepMin, errMsg) {
279
+ function formatOwnerErrorNotice(category, consecutiveErrors, errMsg) {
307
280
  if (category === "fatal") {
308
- return `Agent encountered an internal runtime error. Sleeping ${sleepMin}m before retrying.`;
281
+ return "Agent encountered an internal runtime error. This wake has stopped; the next message or heartbeat will retry.";
309
282
  }
310
283
  if (category === "local") {
311
284
  // Name the cause. The old text blamed the model provider for an error the
312
285
  // provider never saw, which is what made the original alert storm
313
286
  // impossible to diagnose from the notification alone.
314
- return `Agent hit a local session-coordination error (not a provider failure): ${errMsg ?? "unknown cause"}. Sleeping ${sleepMin}m before retrying.`;
287
+ return `Agent hit a local session-coordination error (not a provider failure): ${errMsg ?? "unknown cause"}. This wake has stopped; the next message or heartbeat will retry.`;
315
288
  }
316
289
  if (category === "request_invalid") {
317
290
  return `Agent stopped this wake because the provider rejected a malformed request (${errMsg ?? "unknown cause"}).`;
@@ -322,7 +295,7 @@ function formatOwnerErrorNotice(category, consecutiveErrors, sleepMin, errMsg) {
322
295
  if (category === "empty_turn") {
323
296
  return "The assistant returned another empty turn without confirming completion. The task may be incomplete. I stopped automatic continuation to avoid repeating work; existing results should be checked before continuing.";
324
297
  }
325
- return `Agent encountered an internal model/provider error ${consecutiveErrors} times in a row. Sleeping ${sleepMin}m before retrying.`;
298
+ return `Agent encountered an internal model/provider error ${consecutiveErrors} times in a row. This wake has stopped; the next message or heartbeat will retry.`;
326
299
  }
327
300
  function isPromptTooLong(err) {
328
301
  const terminalReason = getTerminalReason(err);
@@ -390,7 +363,6 @@ export const MAX_EMPTY_TURN_RETRIES = 1;
390
363
  const ERROR_RETRY_COMPACT_USED_PCT = 0.7;
391
364
  const BASE_DELAY_MS = 5_000;
392
365
  const MAX_DELAY_MS = 5 * 60_000;
393
- const FATAL_SLEEP_MS = 5 * 60_000;
394
366
  /** Max times to retry the LLM call on stream silence timeout before aborting. */
395
367
  export const MAX_SILENCE_RETRIES = 3;
396
368
  /** Bounded retries for transient stream/network disconnects in any wake cycle. */
@@ -404,26 +376,7 @@ export const MAX_OUTPUT_RECOVERY_ATTEMPTS = 2;
404
376
  /** Escalating silence timeouts (ms) per attempt: 5min, 10min, 15min. */
405
377
  const SILENCE_TIMEOUT_SCHEDULE_MS = [300_000, 600_000, 900_000];
406
378
  /** Decreasing effort levels per attempt to reduce thinking overhead on retries. */
407
- const EFFORT_SCHEDULE = {
408
- coding: ["xhigh", "high", "medium"],
409
- general: ["xhigh", "high", "medium"],
410
- };
411
- /**
412
- * A wake retried on a reset session re-sends its first message, whose thread
413
- * task block (#1996) was built before the reset's reconcile. Swap it for a
414
- * fresh one so the new session sees exactly one, current copy. Without a
415
- * task runtime this returns the blocks unchanged.
416
- */
417
- export function withFreshThreadTaskBlock(blocks) {
418
- const fresh = buildThreadTaskContext();
419
- const index = blocks.findIndex((block) => block.type === "text" && isThreadTaskContextBlock(block.text));
420
- if (index === -1)
421
- return fresh ? [...blocks, { type: "text", text: fresh }] : blocks;
422
- const rest = blocks.filter((_, i) => i !== index);
423
- if (fresh)
424
- rest.splice(index, 0, { type: "text", text: fresh });
425
- return rest;
426
- }
379
+ const EFFORT_SCHEDULE = ["xhigh", "high", "medium"];
427
380
  export class WakeCycleRunner {
428
381
  externalContextRegistry;
429
382
  deps;
@@ -435,13 +388,20 @@ export class WakeCycleRunner {
435
388
  localCoordinationErrors = 0;
436
389
  /** Empty Claude assistant turns in this wake (not counted as network failures). */
437
390
  emptyTurnRetries = 0;
391
+ /**
392
+ * Set once a call's silence ceiling is spent. Silence retries live inside
393
+ * the call, so the outer error handler must not spend another backoff on
394
+ * the same call. Cleared with the next wake.
395
+ */
396
+ silenceRecoveryExhausted = false;
397
+ /** Recovering only while a spendable bucket is in use. Reset when the wake ends. */
398
+ recoveryPhase = "Running";
438
399
  /**
439
400
  * Watchdog recoveries span multiple queue-driven wakes, so this state lives
440
401
  * on the resident runner rather than inside a single run() invocation.
441
402
  * Attempt count is retained across clean wakes that lack finish/stop so a
442
- * recovery chain can actually reach MAX_TOOL_ABORT_RECOVERIES. Temporary
443
- * switch_session(handoff) also retains the budget; only finish, stop, or
444
- * switch_session(complete) clear it.
403
+ * recovery chain can actually reach MAX_TOOL_ABORT_RECOVERIES. Only finish
404
+ * or stop clear it.
445
405
  *
446
406
  * Ordinary threads also persist the chain, so a restart cannot refill the
447
407
  * budget. LabRun queues are process-local and stay in memory.
@@ -521,28 +481,30 @@ export class WakeCycleRunner {
521
481
  }
522
482
  }
523
483
  /**
524
- * Execute one full wake cycle: the session-switch inner loop, stream
525
- * processing, error recovery, and post-cycle transcript indexing.
484
+ * Execute one full wake cycle: the wake loop, stream processing, error
485
+ * recovery, and post-cycle transcript indexing.
526
486
  *
527
487
  * @param contentBlocks The multimodal user-message blocks for this cycle.
528
488
  * @param trigger Whether this wake was from a message or heartbeat.
529
489
  * @param messages Inbound messages (empty for heartbeat).
530
- * @param calendarSummary Calendar summary string for session-switch context.
531
490
  * @param consecutiveErrors Current error counter from previous cycles.
532
491
  * @returns Updated consecutive error count.
533
492
  */
534
- async run(contentBlocks, trigger, messages, calendarSummary, consecutiveErrors) {
493
+ async run(contentBlocks, trigger, messages, consecutiveErrors) {
535
494
  const { config, ownerConfig, session, activityTracker, interruptHandler, messageQueue } = this.deps;
536
495
  this.malformedRequestRefreshAttempted = false;
537
496
  this.emptyTurnRetries = 0;
497
+ this.silenceRecoveryExhausted = false;
498
+ this.recoveryPhase = "Running";
538
499
  // --- Capture transcript start lines for post-cycle indexing ---
539
- // A wake cycle can switch sessions, so anchor by transcript path instead
540
- // of using a single start line captured from whichever session was active
541
- // at cycle entry.
500
+ // Recovery can rotate the session to a new transcript, so anchor by
501
+ // transcript path instead of using a single start line captured at cycle
502
+ // entry.
542
503
  const wakeCycleId = generateWakeCycleId();
543
504
  /** Numbers model turns within this wake for their task-event keys. */
544
- let responseSeq = 0;
545
505
  let awaitingUser = false;
506
+ /** The model ended the wake with an accepted finish (not a timeout or exhausted recovery). */
507
+ let wakeFinished = false;
546
508
  let terminalError = false;
547
509
  const wakeCycleTimestampStart = new Date().toISOString();
548
510
  const transcriptAnchors = new Map();
@@ -552,17 +514,6 @@ export class WakeCycleRunner {
552
514
  transcriptAnchors.set(activeTranscript.path, await countFileLines(activeTranscript.path));
553
515
  };
554
516
  await ensureTranscriptAnchor(resolveIndexableTranscript(session.activeSession.getTranscriptInfo(), session.activeSession.getTranscriptPath()));
555
- // Session switch loop: if the agent calls switch_session, we close the
556
- // current stream, switch sessions, and re-enter with a new stream.
557
- let didSwitch = false;
558
- let switchForwardMessage = "";
559
- let pendingMemoAck = true;
560
- // Armed on a coding→general completion handoff when the coding session
561
- // did not notify the owner itself. The re-entered general session is
562
- // expected to humanize the memo via notify_user. If this wake never
563
- // successfully notifies, runtime last-resorts a fixed human ping (PR #839)
564
- // — never the memo body.
565
- let pendingCompletionFallback = null;
566
517
  // Survives premature-end continuations: a later turn that repeats the
567
518
  // answer as prose must not re-deliver after notify_user already landed.
568
519
  let notifyUserDeliveredThisWake = false;
@@ -573,7 +524,6 @@ export class WakeCycleRunner {
573
524
  let silenceCompactionDone = false;
574
525
  const externalContext = await this.externalContextRegistry.collect({
575
526
  trigger,
576
- sessionMode: session.activeMode,
577
527
  messages,
578
528
  });
579
529
  setCurrentExternalContext(externalContext.section, externalContext.digest, externalContext.providerBlocks);
@@ -589,62 +539,18 @@ export class WakeCycleRunner {
589
539
  // failover. If the session cannot be rotated, stay on the current engine
590
540
  // and try again next wake rather than replaying its history elsewhere.
591
541
  if (recovered) {
592
- if (await this.rotateSessionForEngineSwitch(session, String(activeBefore), recovered.recovered, "recovery")) {
593
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
594
- }
595
- else {
542
+ if (!await this.rotateSessionForEngineSwitch(session, String(activeBefore), recovered.recovered, "recovery")) {
596
543
  setFallbackEngine(fallbackBefore);
597
544
  }
598
545
  }
599
546
  }
600
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite switch loop
547
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional: recovery paths re-enter via continue
601
548
  wakeLoop: while (true) {
602
549
  const activeSession = session.activeSession;
603
550
  try {
604
551
  await activeSession.warmUp();
605
552
  await ensureTranscriptAnchor(resolveIndexableTranscript(activeSession.getTranscriptInfo(), activeSession.getTranscriptPath()));
606
- let messageContent;
607
- if (didSwitch) {
608
- // Handoff boundary (#1996): fold what the previous session did into
609
- // the task checkpoints before the other session reads them. Bounded.
610
- await reconcileThreadTasks("handoff", RECONCILE_WAIT_MS.handoff);
611
- const switchMemos = session.consumeMemosForActive();
612
- const switchParts = [];
613
- switchParts.push(`[Session Switch] time=${new Date().toLocaleString()} active_session=${session.activeMode}`);
614
- if (switchMemos) {
615
- switchParts.push("");
616
- switchParts.push(switchMemos);
617
- }
618
- const codingStatus = session.getCodingSessionStatus();
619
- if (codingStatus && session.activeMode === "general") {
620
- switchParts.push("");
621
- switchParts.push(`## Active Coding Task`);
622
- switchParts.push(codingStatus);
623
- }
624
- if (calendarSummary) {
625
- switchParts.push("");
626
- switchParts.push("## Upcoming Calendar Events");
627
- switchParts.push(calendarSummary);
628
- }
629
- if (switchForwardMessage) {
630
- switchParts.push("");
631
- switchParts.push(switchForwardMessage);
632
- }
633
- // The destination session has its own context: give it the
634
- // thread's tasks as they are now.
635
- const threadTaskBlock = buildThreadTaskContext();
636
- messageContent = [
637
- { type: "text", text: switchParts.join("\n") },
638
- ...(threadTaskBlock ? [{ type: "text", text: threadTaskBlock }] : []),
639
- ];
640
- didSwitch = false;
641
- switchForwardMessage = "";
642
- pendingMemoAck = true;
643
- }
644
- else {
645
- messageContent =
646
- contentBlocks;
647
- }
553
+ let messageContent = contentBlocks;
648
554
  let streamResult = {
649
555
  toolTimeout: null,
650
556
  finishCalled: false,
@@ -654,6 +560,10 @@ export class WakeCycleRunner {
654
560
  notifyUserDelivered: false,
655
561
  awaitingUser: false,
656
562
  };
563
+ // A lifecycle-only continuation may call finish without repeating the
564
+ // answer from the previous turn. Keep that answer through this wake.
565
+ let lastReplyText = "";
566
+ let lastReplyPresented = false;
657
567
  let subagentContinuations = 0;
658
568
  let prematureEndContinuations = 0;
659
569
  // turnLoop: each iteration drains one streamed turn (including all
@@ -667,7 +577,7 @@ export class WakeCycleRunner {
667
577
  // Retry this inner business turn only for transient stream/socket failures.
668
578
  while (transientStreamRetries <= MAX_TRANSIENT_STREAM_RETRIES) {
669
579
  try {
670
- for (let silenceAttempt = 1; silenceAttempt <= MAX_SILENCE_RETRIES; silenceAttempt++) {
580
+ silenceLoop: for (let silenceAttempt = 1; silenceAttempt <= MAX_SILENCE_RETRIES; silenceAttempt++) {
671
581
  const scheduledSilenceMs = SILENCE_TIMEOUT_SCHEDULE_MS[silenceAttempt - 1] ?? SILENCE_TIMEOUT_SCHEDULE_MS[SILENCE_TIMEOUT_SCHEDULE_MS.length - 1];
672
582
  // Pass the scheduled bound. Pi auto-compaction starts after the
673
583
  // stream is already open (system/compaction_start); processAgentStream
@@ -676,23 +586,20 @@ export class WakeCycleRunner {
676
586
  if (silenceAttempt > 1) {
677
587
  activityTracker.notePublicPhase("Still working");
678
588
  }
679
- const effortSchedule = EFFORT_SCHEDULE[session.activeMode];
680
- const tierEffort = getLlmEffort(config, session.activeMode === "coding" ? "main_coding" : "main_general");
589
+ const effortSchedule = EFFORT_SCHEDULE;
590
+ const tierEffort = getLlmEffort(config, "main_general");
681
591
  const effort = tierEffort && silenceAttempt > 1
682
592
  ? getLlmRetryEffort(tierEffort, silenceAttempt - 1)
683
593
  : tierEffort ?? effortSchedule[silenceAttempt - 1];
684
594
  const queryStream = activeSession.sendAndStream(messageContent, { effort, externalContextDigest: externalContext.digest });
685
- if (pendingMemoAck) {
686
- session.ackMemosForActive();
687
- pendingMemoAck = false;
688
- }
689
595
  try {
690
- streamResult = await processAgentStream(queryStream, activeSession, activityTracker, session.dualSessionEnabled ? session : undefined, config, silenceTimeoutMs);
596
+ streamResult = await processAgentStream(queryStream, activeSession, activityTracker, config, silenceTimeoutMs);
691
597
  if (streamResult.notifyUserDelivered)
692
598
  notifyUserDeliveredThisWake = true;
693
- // One event per model turn with its complete prose; the task
694
- // sidecar folds it into progress. No-op without a task runtime.
695
- recordExecutionEvent(executionEvents.response(`${wakeCycleId}:${responseSeq++}`, streamResult.assistantText));
599
+ if (streamResult.lastAssistantText) {
600
+ lastReplyText = streamResult.lastAssistantText;
601
+ lastReplyPresented = streamResult.lastAssistantTextPresented === true;
602
+ }
696
603
  // A successful SDK envelope can still contain a legitimate empty
697
604
  // model turn. Apply the same task-state policy as SDK diagnostics.
698
605
  if (!this.deps.singleShot && streamResult.emptyTurn && !streamResult.finishCalled && !streamResult.toolTimeout) {
@@ -733,22 +640,37 @@ export class WakeCycleRunner {
733
640
  `skipping the remaining ${MAX_SILENCE_RETRIES - silenceAttempt} silence retries`);
734
641
  }
735
642
  }
736
- if (silenceAttempt < MAX_SILENCE_RETRIES && engineAnswersProbe) {
643
+ // silenceAttempt counts the tries already made in this call. A dead
644
+ // engine does not get the remaining ones: that is Stopping, not
645
+ // another Recovering retry.
646
+ const silencePhase = nextRecoveryPhase({
647
+ from: this.recoveryPhase,
648
+ bucket: "silence",
649
+ spent: engineAnswersProbe ? silenceAttempt : MAX_SILENCE_RETRIES,
650
+ });
651
+ if (silencePhase === "Recovering")
652
+ this.recoveryPhase = "Recovering";
653
+ if (silencePhase === "Recovering" && engineAnswersProbe) {
737
654
  const nextTimeoutMs = SILENCE_TIMEOUT_SCHEDULE_MS[silenceAttempt] ?? SILENCE_TIMEOUT_SCHEDULE_MS[SILENCE_TIMEOUT_SCHEDULE_MS.length - 1];
738
655
  const nextEffort = tierEffort
739
656
  ? getLlmRetryEffort(tierEffort, silenceAttempt)
740
657
  : effortSchedule[silenceAttempt];
741
658
  logger.system(`Stream silence timeout after ${silenceTimeoutMs / 1000}s (attempt ${silenceAttempt}/${MAX_SILENCE_RETRIES}) — engine still answering, retrying LLM call with ${nextTimeoutMs / 1000}s timeout and effort=${nextEffort}...`);
742
- // Re-send the same messageContent on next iteration
743
- continue;
659
+ // Re-send the same messageContent on the next attempt of this call.
660
+ continue silenceLoop;
744
661
  }
745
662
  // Retries exhausted, or cut short because the engine failed its
746
- // health probe.
663
+ // health probe. Both are Stopping for this call. A hot engine
664
+ // switch or a successful compaction below continues this wake, so
665
+ // the outer handler is only blocked once this call is actually
666
+ // abandoned.
747
667
  logger.err(engineAnswersProbe
748
668
  ? `Stream silence timeout: all ${MAX_SILENCE_RETRIES} attempts exhausted — aborting`
749
669
  : `Stream silence timeout on attempt ${silenceAttempt}/${MAX_SILENCE_RETRIES} with a failed health probe — aborting`);
750
- if (this.deps.singleShot)
670
+ if (this.deps.singleShot) {
671
+ this.silenceRecoveryExhausted = true;
751
672
  throw streamErr;
673
+ }
752
674
  const locale = resolveLocale(ownerConfig.language);
753
675
  // Report what actually happened: a probe-driven short circuit may
754
676
  // have spent only one of the three attempts, and telling the owner
@@ -765,7 +687,6 @@ export class WakeCycleRunner {
765
687
  const degraded = await this.degradeActiveEngine(engineFallback, "silence_exhausted", session);
766
688
  if (degraded === "hot") {
767
689
  consecutiveErrors = 0;
768
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
769
690
  continue wakeLoop;
770
691
  }
771
692
  if (degraded === "restarting") {
@@ -784,9 +705,6 @@ export class WakeCycleRunner {
784
705
  try {
785
706
  await activeSession.requestCompaction();
786
707
  consecutiveErrors = 0;
787
- // The compacted session must see the tasks as the compaction
788
- // reconcile just left them, not the wake-start block (#1996).
789
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
790
708
  continue wakeLoop;
791
709
  }
792
710
  catch (compactErr) {
@@ -803,6 +721,7 @@ export class WakeCycleRunner {
803
721
  ...taskContinuationRouting(this.deps.agentState.currentTaskReplyTarget),
804
722
  });
805
723
  logger.system("Enqueued interrupted-task recovery message for next wake cycle");
724
+ this.silenceRecoveryExhausted = true;
806
725
  throw streamErr; // propagate so the outer handler records the error
807
726
  }
808
727
  }
@@ -810,7 +729,18 @@ export class WakeCycleRunner {
810
729
  }
811
730
  catch (streamErr) {
812
731
  // TODO: guard retry against simultaneous stop requests and settle the failed input generator before retrying; both are rare stream/stop races.
813
- if (isTransientStreamFailure(streamErr) && transientStreamRetries < MAX_TRANSIENT_STREAM_RETRIES) {
732
+ // transientStreamRetries counts the disconnects already retried in
733
+ // this turn. Another one is Recovering; the ceiling throws, and
734
+ // the outer handler records it instead of retrying this turn.
735
+ const networkPhase = isTransientStreamFailure(streamErr)
736
+ ? nextRecoveryPhase({
737
+ from: this.recoveryPhase,
738
+ bucket: "network",
739
+ spent: transientStreamRetries,
740
+ })
741
+ : null;
742
+ if (networkPhase === "Recovering") {
743
+ this.recoveryPhase = "Recovering";
814
744
  transientStreamRetries++;
815
745
  logger.system(`Transient stream failure (retry ${transientStreamRetries}/${MAX_TRANSIENT_STREAM_RETRIES}) — retrying the current business turn...`);
816
746
  continue;
@@ -818,30 +748,10 @@ export class WakeCycleRunner {
818
748
  throw streamErr;
819
749
  }
820
750
  }
821
- // An owner-facing notification was DELIVERED this turn — any armed
822
- // coding completion fallback has been superseded by the model's own
823
- // summary. A notify_user call whose tool result errored (channel
824
- // outage, bad explicit target) must not disarm the fallback, or the
825
- // completion is silently lost.
826
- if (streamResult.notifyUserDelivered) {
827
- pendingCompletionFallback = null;
828
- }
829
751
  if (streamResult.finishCalled && streamResult.awaitingUser)
830
752
  awaitingUser = true;
831
- // The coding prompt asks the model to switch back with
832
- // reason="complete", but the generic finish tool is also available
833
- // and can be used after a successful completion notification. In
834
- // that case finish only closes the wake at the stream layer; finalize
835
- // the task here when the receipt is for this exact active task so a
836
- // later general heartbeat cannot resurrect it.
837
- const completionReceiptMatchesActiveCodingTask = session.activeMode === "coding" &&
838
- !session.pendingSwitch &&
839
- !streamResult.awaitingUser &&
840
- Boolean(this.deps.agentState.codingCompletionTaskStartedAt &&
841
- this.deps.agentState.codingCompletionTaskStartedAt === session.getActiveCodingTaskStartedAt());
842
- if (streamResult.finishCalled && completionReceiptMatchesActiveCodingTask) {
843
- session.finalizeCodingCompletionAfterFinish();
844
- }
753
+ if (streamResult.finishCalled)
754
+ wakeFinished = true;
845
755
  // A watchdog abort is scoped to the active tool. Preserve the result
846
756
  // for the next queue-driven wake rather than resuming the same provider
847
757
  // turn, which keeps lifecycle handling identical for Claude and Pi.
@@ -899,7 +809,7 @@ export class WakeCycleRunner {
899
809
  }
900
810
  // A successful SDK result only means the stream ended; it does not
901
811
  // prove the task ended intentionally. Keep the original wake context
902
- // alive until the model calls finish or requests a session switch.
812
+ // alive until the model calls finish.
903
813
  // Gated on "no subagents currently active" (not on whether a subagent
904
814
  // continuation ever fired): while subagents run, the subagent-wait
905
815
  // mechanism above owns the turn; once they are done, the explicit
@@ -907,22 +817,30 @@ export class WakeCycleRunner {
907
817
  // subagent continuation earlier.
908
818
  if (!this.deps.singleShot &&
909
819
  endedWithoutLifecycleAction &&
910
- !session.pendingSwitch &&
911
820
  runningSubagents.length === 0) {
912
- if (prematureEndContinuations < MAX_PREMATURE_END_CONTINUATIONS) {
821
+ // prematureEndContinuations counts the continuations already sent in
822
+ // this wake. One more is Recovering; the ceiling is Stopping, and
823
+ // this wake ends instead of starting another continuation.
824
+ const prematurePhase = nextRecoveryPhase({
825
+ from: this.recoveryPhase,
826
+ bucket: "premature_result",
827
+ spent: prematureEndContinuations,
828
+ });
829
+ if (prematurePhase === "Recovering") {
830
+ this.recoveryPhase = "Recovering";
913
831
  prematureEndContinuations++;
914
- logger.warn(`Turn ended without finish or switch_session — continuing in ${session.activeMode} session (${prematureEndContinuations}/${MAX_PREMATURE_END_CONTINUATIONS})`);
832
+ logger.warn(`Turn ended without finish — continuing (${prematureEndContinuations}/${MAX_PREMATURE_END_CONTINUATIONS})`);
915
833
  messageContent = [{
916
834
  type: "text",
917
835
  text: [
918
- "Your previous turn ended without an explicit finish or switch_session call, so this wake cycle is still active.",
836
+ "Your previous turn ended without an explicit finish call, so this wake cycle is still active.",
919
837
  "Inspect the transcript and existing tool results before acting. Do not repeat successful notifications, emails, uploads, or other mutations/side effects.",
920
- "If the requested work is already complete, only call finish now. Otherwise complete the remaining work, notify the user if needed, then call finish or switch_session as appropriate.",
838
+ "If the requested work is already complete, only call finish now. Otherwise complete the remaining work, notify the user if needed, then call finish.",
921
839
  ].join("\n"),
922
840
  }];
923
841
  continue turnLoop;
924
842
  }
925
- const errorMessage = `Wake cycle ended without finish or switch_session after ${MAX_PREMATURE_END_CONTINUATIONS} continuations`;
843
+ const errorMessage = `Wake cycle ended without finish after ${MAX_PREMATURE_END_CONTINUATIONS} continuations`;
926
844
  logger.err(errorMessage);
927
845
  // The alert is only actionable when the owner is actually waiting on
928
846
  // something, and it fires at most once per wake cycle — so a long run
@@ -943,7 +861,7 @@ export class WakeCycleRunner {
943
861
  const alreadyNotifiedOwner = this.deps.agentState.notifiedUserThisWake === true;
944
862
  const recoveryAlertEligible = trigger === "message" && !alreadyNotifiedOwner;
945
863
  if (recoveryAlertEligible) {
946
- void notifyOwner(`Agent wake recovery stopped after ${MAX_PREMATURE_END_CONTINUATIONS} attempts in the ${session.activeMode} session. The task may be incomplete; please ask me to resume it.`, config).catch((notifyErr) => {
864
+ void notifyOwner(`Agent wake recovery stopped after ${MAX_PREMATURE_END_CONTINUATIONS} attempts. The task may be incomplete; please ask me to resume it.`, config).catch((notifyErr) => {
947
865
  logger.warn(`Failed to notify owner about exhausted wake recovery: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
948
866
  });
949
867
  }
@@ -976,9 +894,18 @@ export class WakeCycleRunner {
976
894
  }
977
895
  }
978
896
  else {
897
+ // The persisted count is this chain's own budget. It survives
898
+ // wakes and restarts, so a new wake must not refill it.
899
+ const toolAbortPhase = nextRecoveryPhase({
900
+ from: this.recoveryPhase,
901
+ bucket: "tool_abort",
902
+ spent: this.toolAbortRecoveries,
903
+ });
979
904
  this.toolAbortRecoveryChainId ??= randomUUID();
980
905
  this.toolAbortRecoveries += 1;
981
- const retryable = this.toolAbortRecoveries <= MAX_TOOL_ABORT_RECOVERIES;
906
+ const retryable = toolAbortPhase === "Recovering";
907
+ if (retryable)
908
+ this.recoveryPhase = "Recovering";
982
909
  if (!retryable)
983
910
  this.toolAbortRecoveryExhausted = true;
984
911
  this.persistToolAbortRecoveryChain();
@@ -1020,12 +947,7 @@ export class WakeCycleRunner {
1020
947
  this.clearToolAbortRecoveryBudget();
1021
948
  }
1022
949
  else if (streamResult.finishCalled) {
1023
- // `finishCalled` is also set for accepted switch_session. Temporary
1024
- // handoffs must retain the budget; only a real finish tool clears
1025
- // here. switch_session(complete) clears below via reason.
1026
- if (streamResult.lifecycleTool !== "switch_session") {
1027
- this.clearToolAbortRecoveryBudget();
1028
- }
950
+ this.clearToolAbortRecoveryBudget();
1029
951
  }
1030
952
  // --- Direct assistant text delivery ---
1031
953
  // When the model writes the answer as prose, calls finish, and never
@@ -1033,44 +955,34 @@ export class WakeCycleRunner {
1033
955
  // adapters drop `assistant_text`). Send the LAST text block as a
1034
956
  // normal notify so the originating channel actually delivers it.
1035
957
  // Concatenated earlier blocks, thinking, and tool narration stay out.
1036
- // Heartbeat, switch_session, singleShot, and toolTimeout stay silent.
958
+ // Heartbeat, singleShot, and toolTimeout stay silent.
1037
959
  // Fin-App matches Telegram: live assistant_text is dropped, so this
1038
960
  // last-block notify is the courtesy delivery when finish has no
1039
961
  // notify_user. TUI still presents assistant_text in-thread.
1040
- const fallbackText = streamResult.lastAssistantText;
1041
- // An accepted switch_session always leaves a pending request here --
1042
- // `executePendingSwitch` only runs later in this wake -- so
1043
- // `pendingSwitch` already covers that exit on its own. Requiring an
1044
- // accepted `finish` below excludes it a second time.
1045
- const skipBecauseSwitch = Boolean(session.pendingSwitch);
962
+ const fallbackText = lastReplyText;
1046
963
  const skipBecauseNotified = notifyUserDeliveredThisWake || streamResult.notifyUserDelivered;
1047
964
  const finishAccepted = streamResult.lifecycleTool === "finish";
1048
965
  // Only replies to messages in the initial wake batch are eligible.
1049
966
  // Resumes, external wakes and calendars may carry a delivery target
1050
967
  // without requesting a reply. Mid-turn work uses explicit notify_user
1051
968
  // for its result; a quick acknowledgment does not enable this fallback.
1052
- const replyMessages = messages.filter((message) => isTaskRelevantMessage(message) && !isCalendarInterruptMessage(message));
969
+ const replyMessages = messages.filter((message) => message.channel !== "system" && message.meta?.kind !== EXTERNAL_WAKE_KIND && !isCalendarInterruptMessage(message));
1053
970
  const fallbackEligible = !this.deps.singleShot &&
1054
971
  !streamResult.toolTimeout &&
1055
972
  !skipBecauseNotified &&
1056
973
  fallbackText.length > 0 &&
1057
974
  trigger === "message" &&
1058
975
  finishAccepted &&
1059
- !skipBecauseSwitch &&
1060
976
  replyMessages.length > 0;
1061
977
  if (replyMessages.length === 0 && fallbackText.length > 0 && finishAccepted && !skipBecauseNotified) {
1062
978
  logger.info("Skipped finish-without-notify fallback: no reply message in the initial wake batch");
1063
979
  }
1064
- if (skipBecauseSwitch && fallbackText.length > 0 && trigger === "message") {
1065
- logger.warn(`Skipped assistant_text delivery: the turn took switch_session without notify_user, so this text was NOT delivered to the owner: ${fallbackText.slice(0, 2000)}`);
1066
- }
1067
980
  // Premature-end recovery re-prompts, so unfinished prose is deliberately
1068
981
  // not delivered (a rejected `finish` lands here too: it sets neither
1069
982
  // `finishCalled` nor `lifecycleTool`). The owner still gets the generic
1070
983
  // exhausted-recovery alert, but the text itself would vanish without a
1071
984
  // trace -- record it so the drop is diagnosable.
1072
985
  if (!finishAccepted &&
1073
- !skipBecauseSwitch &&
1074
986
  !skipBecauseNotified &&
1075
987
  !streamResult.toolTimeout &&
1076
988
  fallbackText.length > 0 &&
@@ -1080,16 +992,10 @@ export class WakeCycleRunner {
1080
992
  if (fallbackEligible) {
1081
993
  const fallbackTarget = resolveDirectAssistantFallbackTarget(replyMessages, ownerConfig.ownerEmail);
1082
994
  const livePresentedOnChat = Boolean(fallbackTarget && presentsAssistantText(fallbackTarget.channel)) &&
1083
- streamResult.lastAssistantTextPresented === true;
995
+ lastReplyPresented;
1084
996
  if (livePresentedOnChat) {
1085
997
  // Last live block actually landed in chat. Do not splice a
1086
998
  // second last-block notify into the same thread.
1087
- pendingCompletionFallback = null;
1088
- }
1089
- else if (fallbackTarget &&
1090
- normalizeChannelName(fallbackTarget.channel) === "finapp" &&
1091
- session.activeMode === "coding") {
1092
- // Coding narration never becomes a Fin-App chat bubble. TUI still last-block notifies.
1093
999
  }
1094
1000
  else if (fallbackTarget) {
1095
1001
  // Best-effort courtesy delivery: never let a fallback failure
@@ -1111,7 +1017,6 @@ export class WakeCycleRunner {
1111
1017
  }
1112
1018
  else {
1113
1019
  logger.warn(`Delivered finish-without-notify fallback via ${fallbackTarget.channel} to ${fallbackTarget.to}`);
1114
- pendingCompletionFallback = null;
1115
1020
  }
1116
1021
  }
1117
1022
  catch (err) {
@@ -1180,46 +1085,6 @@ export class WakeCycleRunner {
1180
1085
  logger.warn(`Post-query compaction failed: ${compactErr instanceof Error ? compactErr.message : String(compactErr)}`);
1181
1086
  }
1182
1087
  }
1183
- const switchResult = session.executePendingSwitch();
1184
- if (switchResult) {
1185
- // switch_session sets finishCalled, so the finishCalled branch above
1186
- // skips clearing when lifecycleTool is switch_session. Only complete
1187
- // clears here; temporary handoff retains the budget.
1188
- if (switchResult.reason === "complete") {
1189
- this.clearToolAbortRecoveryBudget();
1190
- }
1191
- didSwitch = true;
1192
- switchForwardMessage = [switchResult.workspaceNotice, switchResult.forwardMessage]
1193
- .filter(Boolean)
1194
- .join("\n\n");
1195
- // Completion handoffs are user-visible outcomes. The re-entered
1196
- // general session is expected to humanize the memo via notify_user.
1197
- // Do not dump the memo as chat (internal working text). Arm a marker:
1198
- // a later notify_user / last-block send disarms it; if the cycle
1199
- // ends with no successful owner ping, last-resort a fixed human line
1200
- // (PR #839: do not silently drop the result). Newest memo wins.
1201
- if (switchResult.target === "general" &&
1202
- switchResult.ownerNotificationRequired &&
1203
- switchResult.completionMemo) {
1204
- pendingCompletionFallback = switchResult.completionMemo;
1205
- }
1206
- // Coding already sent this task's final result. No memo was created,
1207
- // so end the wake instead of invoking general for a duplicate summary.
1208
- if (switchResult.target === "general" &&
1209
- switchResult.ownerNotificationRequired === false &&
1210
- switchResult.completionMemo) {
1211
- // reason="complete" already cleared the recovery budget above.
1212
- logger.system("Coding completion already delivered — ending wake without re-entering general");
1213
- this.deps.engineFallback.noteEngineSuccess();
1214
- this.invalidJsonBodyRecoveryCount = 0;
1215
- this.localCoordinationErrors = 0;
1216
- this.emptyTurnRetries = 0;
1217
- consecutiveErrors = 0;
1218
- break;
1219
- }
1220
- logger.system(`Re-entering wake cycle for ${switchResult.target} session`);
1221
- continue;
1222
- }
1223
1088
  this.deps.engineFallback.noteEngineSuccess();
1224
1089
  this.invalidJsonBodyRecoveryCount = 0;
1225
1090
  this.localCoordinationErrors = 0;
@@ -1245,23 +1110,14 @@ export class WakeCycleRunner {
1245
1110
  if (errorResult.kind === "continue_with_message") {
1246
1111
  consecutiveErrors = errorResult.consecutiveErrors;
1247
1112
  contentBlocks = [{ type: "text", text: errorResult.message }];
1248
- // A rebuilt session starts without the thread's tasks (#1996).
1249
- if (errorResult.newSession)
1250
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
1251
- didSwitch = false;
1252
- switchForwardMessage = "";
1253
1113
  continue;
1254
1114
  }
1255
1115
  if (errorResult.kind === "continue_with_reset") {
1256
1116
  consecutiveErrors = 0;
1257
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
1258
1117
  continue;
1259
1118
  }
1260
1119
  if (errorResult.kind === "continue_preserve_count") {
1261
1120
  consecutiveErrors = errorResult.consecutiveErrors;
1262
- // Invalid-json recovery may have compacted (#1996): resend the
1263
- // current task block. A no-op when nothing changed.
1264
- contentBlocks = withFreshThreadTaskBlock(contentBlocks);
1265
1121
  continue;
1266
1122
  }
1267
1123
  consecutiveErrors = errorResult.consecutiveErrors;
@@ -1269,17 +1125,7 @@ export class WakeCycleRunner {
1269
1125
  }
1270
1126
  }
1271
1127
  break;
1272
- } // end switch loop
1273
- // --- Coding completion last-resort ping (PR #839) ---
1274
- // The wake cycle is over and no owner notification superseded the armed
1275
- // completion (general silent, errored, or stopped). Send a fixed human
1276
- // line so the result is not silently lost. Never send the session memo
1277
- // (handoff instructions, paths, do-not-notify constraints).
1278
- if (pendingCompletionFallback) {
1279
- logger.warn("Wake cycle ended without an owner notification for a coding completion — sending a generic ping, not the session memo");
1280
- await deliverCodingCompletionFallbackPing(ownerConfig);
1281
- pendingCompletionFallback = null;
1282
- }
1128
+ } // end wake loop
1283
1129
  // --- Post-wake-cycle transcript indexing (best-effort) ---
1284
1130
  // Usage reports are no longer enqueued immediately here. Instead,
1285
1131
  // indexWakeCycle extracts and stores tool call facts, and the background
@@ -1293,9 +1139,7 @@ export class WakeCycleRunner {
1293
1139
  const wakeCycleOutcome = session.isStopRequested()
1294
1140
  ? "stopped"
1295
1141
  : !terminalError && consecutiveErrors === 0
1296
- ? didSwitch
1297
- ? "switched"
1298
- : "completed"
1142
+ ? "completed"
1299
1143
  : "error";
1300
1144
  // Always index wake cycles — including stopped/error cycles — so that
1301
1145
  // usage metering is consistent for all cycles. The outcome field records
@@ -1304,7 +1148,7 @@ export class WakeCycleRunner {
1304
1148
  const wakeCycleEndLine = await countFileLines(postCycleTranscript.path);
1305
1149
  void indexWakeCycle({
1306
1150
  wakeCycleId,
1307
- sessionType: session.activeMode,
1151
+ sessionType: "general",
1308
1152
  sourceFile: postCycleTranscript.path,
1309
1153
  sourceFormat: postCycleTranscript.format,
1310
1154
  startLine: postCycleStartLine,
@@ -1330,10 +1174,11 @@ export class WakeCycleRunner {
1330
1174
  : awaitingUser
1331
1175
  ? "waiting_user"
1332
1176
  : "completed",
1177
+ finished: wakeFinished,
1333
1178
  };
1334
1179
  }
1335
1180
  /**
1336
- * Handle an error from the session switch loop. Returns:
1181
+ * Handle an error from the wake loop. Returns:
1337
1182
  * - `continue_with_reset` for recovery paths that should retry immediately
1338
1183
  * with a clean error counter (compaction/sanitization succeeded)
1339
1184
  * - `continue_preserve_count` for normal retryable failures that should
@@ -1352,6 +1197,12 @@ export class WakeCycleRunner {
1352
1197
  logger.system(err.message);
1353
1198
  return { kind: "done", consecutiveErrors };
1354
1199
  }
1200
+ if (this.silenceRecoveryExhausted) {
1201
+ // The call already spent its own silence ceiling and moved to Stopping.
1202
+ // Another backoff here would replay that call instead of ending it.
1203
+ logger.system("Stream silence ceiling already spent — ending this wake cycle");
1204
+ return { kind: "done", consecutiveErrors: consecutiveErrors + 1 };
1205
+ }
1355
1206
  const errMsg = err instanceof Error ? err.message : String(err);
1356
1207
  const category = classifyError(err);
1357
1208
  // A local coordination error never reached a provider, so it must not
@@ -1369,6 +1220,8 @@ export class WakeCycleRunner {
1369
1220
  // terminal facts for this run, not a reason to start conversational
1370
1221
  // recovery, notify the owner directly, sleep, or enqueue another wake.
1371
1222
  if (this.deps.singleShot) {
1223
+ // Kept for the failure report, so the caller sees the cause.
1224
+ getWorkerExecutionRuntime()?.noteError(errMsg);
1372
1225
  return { kind: "done", consecutiveErrors };
1373
1226
  }
1374
1227
  if (err instanceof CompactionFailureError ||
@@ -1430,7 +1283,6 @@ export class WakeCycleRunner {
1430
1283
  message: `${config.agentName}: invalid Pi session could not be reset`,
1431
1284
  details: {
1432
1285
  code: "INVALID_PI_SESSION_RECOVERY_FAILED",
1433
- activeSession: session.activeMode,
1434
1286
  sessionId: invalidSessionId ?? "unknown",
1435
1287
  provider: config.provider,
1436
1288
  model: reportedModel(config),
@@ -1493,7 +1345,6 @@ export class WakeCycleRunner {
1493
1345
  message: `${config.agentName}: stale session could not be reset`,
1494
1346
  details: {
1495
1347
  code: "SESSION_NOT_FOUND_RECOVERY_FAILED",
1496
- activeSession: session.activeMode,
1497
1348
  sessionId: deadSessionId ?? "unknown",
1498
1349
  provider: config.provider,
1499
1350
  model: reportedModel(config),
@@ -1580,7 +1431,7 @@ export class WakeCycleRunner {
1580
1431
  }
1581
1432
  if (recoveryCount === 2) {
1582
1433
  const oldSessionId = session.activeSession.getSessionId();
1583
- logger.warn(`Invalid JSON body repeated after compaction; resetting ${session.activeSession.mode} session before retry`);
1434
+ logger.warn("Invalid JSON body repeated after compaction; resetting session before retry");
1584
1435
  session.activeSession.resetForNewSession("Previous session was reset automatically after repeated provider `400 Invalid JSON body` errors. " +
1585
1436
  "This usually means the provider rejected the context/payload even after compaction. Continue from memory and the current user request; avoid reloading large prior tool outputs unless needed.");
1586
1437
  logger.system(`Invalid-json-body recovery reset session: old=${oldSessionId ?? "unknown"} new=${session.activeSession.getSessionId() ?? "pending"}`);
@@ -1595,7 +1446,6 @@ export class WakeCycleRunner {
1595
1446
  code: "INVALID_JSON_BODY_RECOVERY_FAILED",
1596
1447
  consecutiveInvalidJsonBodyErrors: recoveryCount,
1597
1448
  actionsAttempted: ["compaction", "session_reset", "controlled_restart"],
1598
- activeSession: session.activeSession.mode,
1599
1449
  provider: config.provider,
1600
1450
  model: reportedModel(config),
1601
1451
  },
@@ -1620,16 +1470,15 @@ export class WakeCycleRunner {
1620
1470
  return { kind: "continue_preserve_count", consecutiveErrors };
1621
1471
  }
1622
1472
  // Retries exhausted: tell the owner what actually broke, naming the
1623
- // local cause instead of blaming the model provider.
1624
- const sleepMin = FATAL_SLEEP_MS / 60_000;
1473
+ // local cause instead of blaming the model provider. The wake ends here.
1474
+ // Sleeping would keep the foreground slot busy until the timer fired.
1625
1475
  logger.warn(`Notifying owner about persistent local coordination error: ${errMsg}`);
1626
1476
  try {
1627
- await notifyOwner(formatOwnerErrorNotice(category, consecutiveErrors, sleepMin, errMsg), config);
1477
+ await notifyOwner(formatOwnerErrorNotice(category, consecutiveErrors, errMsg), config);
1628
1478
  }
1629
1479
  catch (notifyErr) {
1630
1480
  logger.warn(`Failed to send error notification to owner: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
1631
1481
  }
1632
- await new Promise((r) => setTimeout(r, FATAL_SLEEP_MS));
1633
1482
  return { kind: "done", consecutiveErrors };
1634
1483
  }
1635
1484
  // --- Known malformed Responses replay ---
@@ -1668,7 +1517,7 @@ export class WakeCycleRunner {
1668
1517
  }
1669
1518
  logger.err(`Malformed Responses replay recovery failed — ending wake: ${errMsg}`);
1670
1519
  try {
1671
- await notifyOwner(formatOwnerErrorNotice(category, consecutiveErrors, 0, errMsg), config);
1520
+ await notifyOwner(formatOwnerErrorNotice(category, consecutiveErrors, errMsg), config);
1672
1521
  }
1673
1522
  catch (notifyErr) {
1674
1523
  logger.warn(`Failed to send error notification to owner: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
@@ -1680,10 +1529,7 @@ export class WakeCycleRunner {
1680
1529
  // whether this wake has work that still needs an explicit resolution.
1681
1530
  if (category === "empty_turn") {
1682
1531
  const { activityTracker, interruptHandler } = this.deps;
1683
- const hasPendingWork = activityTracker.getTodos().some((todo) => todo.status === "pending" || todo.status === "in_progress") || activityTracker.getActiveSubagents().length > 0
1684
- // Paused tasks persist across wakes; they do not make an otherwise
1685
- // idle heartbeat responsible for resuming coding work.
1686
- || session.getCodingSessionState() === "in_progress";
1532
+ const hasPendingWork = activityTracker.getTodos().some((todo) => todo.status === "pending" || todo.status === "in_progress") || activityTracker.getActiveSubagents().length > 0;
1687
1533
  const idleHeartbeat = wake.trigger === "heartbeat"
1688
1534
  && wake.messages.length === 0
1689
1535
  && !interruptHandler.hasAcceptedWorkThisCycle()
@@ -1692,39 +1538,45 @@ export class WakeCycleRunner {
1692
1538
  logger.system("Empty assistant turn on an idle heartbeat — ending normally without retry");
1693
1539
  return { kind: "completed", consecutiveErrors: 0 };
1694
1540
  }
1695
- this.emptyTurnRetries++;
1696
- if (this.emptyTurnRetries <= MAX_EMPTY_TURN_RETRIES) {
1697
- const message = [
1698
- "Your previous turn ended with no additional content. This alone does not indicate whether the task is complete.",
1699
- "Review the current request, task state, and existing tool results before acting. Do not repeat successful notifications, emails, uploads, or other side effects.",
1700
- "If no work remains, only call finish (or switch_session for a required handoff). Otherwise continue only the unfinished work, wait for running subagents as needed, then finish.",
1701
- "If completion cannot be verified or user input is needed, explain what remains uncertain to the user and call finish(awaiting_user=true).",
1702
- ].join("\n");
1703
- logger.system("Empty assistant turn with unresolved work — sending one explicit continuation");
1704
- // Context pressure is independent of the empty response. Only compact
1705
- // when the usage snapshot warrants it, and retain bounded recovery.
1541
+ const phase = nextRecoveryPhase({
1542
+ from: this.recoveryPhase,
1543
+ bucket: "empty_turn",
1544
+ spent: this.emptyTurnRetries,
1545
+ });
1546
+ if (phase === "Stopping") {
1547
+ logger.warn(`Empty-turn continuation did not confirm task completion: ${errMsg}`);
1706
1548
  try {
1707
- await this.compactBeforeErrorRetry(session, "empty-turn continuation");
1549
+ await notifyOwner(formatOwnerErrorNotice(category, this.emptyTurnRetries, errMsg), config);
1708
1550
  }
1709
- catch (compactErr) {
1710
- if (!(compactErr instanceof CompactionFailureError))
1711
- throw compactErr;
1712
- const recovery = await this.recoverOversizedContext(compactErr, session, consecutiveErrors);
1713
- if (recovery.kind !== "continue_with_reset")
1714
- return recovery;
1715
- // The session was rebuilt: it has no task block in its history.
1716
- return { kind: "continue_with_message", consecutiveErrors: 0, message, newSession: true };
1551
+ catch (notifyErr) {
1552
+ logger.warn(`Failed to send empty-turn notification to owner: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
1717
1553
  }
1718
- return { kind: "continue_with_message", consecutiveErrors, message };
1554
+ return { kind: "done", consecutiveErrors };
1719
1555
  }
1720
- logger.warn(`Empty-turn continuation did not confirm task completion: ${errMsg}`);
1556
+ this.recoveryPhase = "Recovering";
1557
+ this.emptyTurnRetries++;
1558
+ const message = [
1559
+ "Your previous turn ended with no additional content. This alone does not indicate whether the task is complete.",
1560
+ "Review the current request, task state, and existing tool results before acting. Do not repeat successful notifications, emails, uploads, or other side effects.",
1561
+ "If no work remains, only call finish. Otherwise continue only the unfinished work, wait for running subagents as needed, then finish.",
1562
+ "If completion cannot be verified or user input is needed, explain what remains uncertain to the user and call finish(awaiting_user=true).",
1563
+ ].join("\n");
1564
+ logger.system("Empty assistant turn with unresolved work — sending one explicit continuation");
1565
+ // Context pressure is independent of the empty response. Only compact
1566
+ // when the usage snapshot warrants it, and retain bounded recovery.
1721
1567
  try {
1722
- await notifyOwner(formatOwnerErrorNotice(category, this.emptyTurnRetries, 0, errMsg), config);
1568
+ await this.compactBeforeErrorRetry(session, "empty-turn continuation");
1723
1569
  }
1724
- catch (notifyErr) {
1725
- logger.warn(`Failed to send empty-turn notification to owner: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
1570
+ catch (compactErr) {
1571
+ if (!(compactErr instanceof CompactionFailureError))
1572
+ throw compactErr;
1573
+ const recovery = await this.recoverOversizedContext(compactErr, session, consecutiveErrors);
1574
+ if (recovery.kind !== "continue_with_reset")
1575
+ return recovery;
1576
+ // The session was rebuilt: it has no task block in its history.
1577
+ return { kind: "continue_with_message", consecutiveErrors: 0, message, newSession: true };
1726
1578
  }
1727
- return { kind: "done", consecutiveErrors };
1579
+ return { kind: "continue_with_message", consecutiveErrors, message };
1728
1580
  }
1729
1581
  // --- Engine fallback (issue #2519) ---
1730
1582
  // Engine-scoped failures cool the engine down and move on; failures of the
@@ -1763,10 +1615,22 @@ export class WakeCycleRunner {
1763
1615
  logger.system("No alternate engine available for transient network failures; keeping consecutiveErrors and the cooldown");
1764
1616
  }
1765
1617
  }
1766
- if (category === "fatal" ||
1767
- consecutiveErrors >= MAX_RETRIES_BEFORE_NOTIFY) {
1768
- const sleepMin = FATAL_SLEEP_MS / 60_000;
1769
- const notice = formatOwnerErrorNotice(category, consecutiveErrors, sleepMin);
1618
+ // consecutiveErrors counts the failures already seen in this wake. The
1619
+ // notify threshold is the unknown bucket, not the network disconnect
1620
+ // ceiling. Reaching it is Stopping: notify, then end the wake. The next
1621
+ // attempt waits for a queued message or the proactive timer, not a sleep
1622
+ // that holds this slot.
1623
+ const unknownPhase = category === "fatal"
1624
+ ? "Stopping"
1625
+ : nextRecoveryPhase({
1626
+ from: this.recoveryPhase,
1627
+ bucket: "unknown",
1628
+ spent: consecutiveErrors,
1629
+ });
1630
+ if (unknownPhase === "Recovering")
1631
+ this.recoveryPhase = "Recovering";
1632
+ if (unknownPhase === "Stopping") {
1633
+ const notice = formatOwnerErrorNotice(category, consecutiveErrors);
1770
1634
  logger.warn(`Notifying owner about repeated agent error: ${errMsg}`);
1771
1635
  try {
1772
1636
  await notifyOwner(notice, config);
@@ -1775,7 +1639,6 @@ export class WakeCycleRunner {
1775
1639
  logger.warn(`Failed to send error notification to owner: ${notifyErr instanceof Error ? notifyErr.message : String(notifyErr)}`);
1776
1640
  }
1777
1641
  consecutiveErrors = 0;
1778
- await new Promise((r) => setTimeout(r, FATAL_SLEEP_MS));
1779
1642
  }
1780
1643
  else {
1781
1644
  try {
@@ -1815,8 +1678,8 @@ export class WakeCycleRunner {
1815
1678
  return;
1816
1679
  }
1817
1680
  // Between the failed `/compact` and the end of the cycle the session may
1818
- // have been switched (coding <-> general) or already rotated by the error
1819
- // path. Only rotate the session that actually failed to compact.
1681
+ // have already been rotated by the error path. Only rotate the session
1682
+ // that actually failed to compact.
1820
1683
  if (pending.sessionId === null || session.activeSession.getSessionId() !== pending.sessionId) {
1821
1684
  logger.system(`Post-query compaction failed terminally (${err.errorCode}) but the active session changed before the cycle ended; leaving rotation to the next check`);
1822
1685
  return;
@@ -1854,8 +1717,15 @@ export class WakeCycleRunner {
1854
1717
  // Two recovery turns total: repair, then one refresh. The second branch is
1855
1718
  // gated by the ceiling, so raising MAX_OUTPUT_RECOVERY_ATTEMPTS is what
1856
1719
  // allows another turn. Advancing before I/O keeps a failed repair from
1857
- // resetting the allowance.
1858
- if (attempts[recoveryKind] === 0) {
1720
+ // resetting the allowance. The two kinds keep separate counts.
1721
+ const outputPhase = nextRecoveryPhase({
1722
+ from: this.recoveryPhase,
1723
+ bucket: recoveryKind === "policy_refused" ? "policy_refusal" : "oversized",
1724
+ spent: attempts[recoveryKind],
1725
+ });
1726
+ if (outputPhase === "Recovering")
1727
+ this.recoveryPhase = "Recovering";
1728
+ if (attempts[recoveryKind] === 0 && outputPhase === "Recovering") {
1859
1729
  attempts[recoveryKind]++;
1860
1730
  if (!compactionAlreadyFailed) {
1861
1731
  try {
@@ -1874,7 +1744,7 @@ export class WakeCycleRunner {
1874
1744
  logger.system("SDK auto-compaction already failed — skipping same-session transcript repair and rotating context");
1875
1745
  }
1876
1746
  }
1877
- if (attempts[recoveryKind] < MAX_OUTPUT_RECOVERY_ATTEMPTS) {
1747
+ if (attempts[recoveryKind] < MAX_OUTPUT_RECOVERY_ATTEMPTS && outputPhase === "Recovering") {
1878
1748
  attempts[recoveryKind]++;
1879
1749
  const previousId = session.activeSession.getSessionId();
1880
1750
  try {