@askexenow/exe-os 0.9.340 → 0.9.342

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 (417) hide show
  1. package/README.md +159 -196
  2. package/deploy/compose/.env.customer.example +1 -0
  3. package/deploy/compose/.env.example +1 -0
  4. package/deploy/compose/docker-compose.yml +13 -4
  5. package/deploy/compose/generate-env.ts +4 -0
  6. package/dist/{active-agent-EC6M7KTX.js → active-agent-PVGE57FY.js} +5 -5
  7. package/dist/{active-agent-WHIUCHLA.js → active-agent-TNIO6IY3.js} +5 -5
  8. package/dist/{agentic-ontology-RNSXGXW4.js → agentic-ontology-3FXYMHKW.js} +1 -1
  9. package/dist/{backfill-gate-J7UXFNXU.js → backfill-gate-65LSYVE5.js} +3 -3
  10. package/dist/{backfill-metadata-MFY6BHKE.js → backfill-metadata-ZSMZC45J.js} +8 -8
  11. package/dist/{background-jobs-STURKDCS.js → background-jobs-EXR5RVGG.js} +3 -3
  12. package/dist/backup-verify-3Y2FHGZU.js +425 -0
  13. package/dist/{behaviors-HLZUJSMP.js → behaviors-KYRB3NGJ.js} +9 -9
  14. package/dist/bin/age-ontology-load.js +3 -3
  15. package/dist/bin/agentic-ontology-backfill.js +10 -10
  16. package/dist/bin/agentic-reflection-backfill.js +11 -11
  17. package/dist/bin/agentic-semantic-label.js +10 -10
  18. package/dist/bin/backfill-conversations.js +10 -10
  19. package/dist/bin/backfill-responses.js +10 -10
  20. package/dist/bin/backfill-vectors.js +14 -14
  21. package/dist/bin/bulk-sync-postgres.js +19 -19
  22. package/dist/bin/cc-doctor.js +10 -8
  23. package/dist/bin/cleanup-stale-review-tasks.js +26 -24
  24. package/dist/bin/cli.js +111 -25
  25. package/dist/bin/customer-readiness.js +1 -1
  26. package/dist/bin/deferred-daemon-restart.js +97 -90
  27. package/dist/bin/exe-agent-config.js +9 -8
  28. package/dist/bin/exe-agent.js +20 -20
  29. package/dist/bin/exe-assign.js +12 -12
  30. package/dist/bin/exe-boot.js +201 -68
  31. package/dist/bin/exe-call.js +7 -7
  32. package/dist/bin/exe-cloud.js +20 -20
  33. package/dist/bin/exe-config-dump.js +9 -8
  34. package/dist/bin/exe-dispatch.js +26 -24
  35. package/dist/bin/exe-doctor.js +10 -6
  36. package/dist/bin/exe-export-behaviors.js +11 -11
  37. package/dist/bin/exe-forget.js +12 -10
  38. package/dist/bin/exe-gateway.js +15 -10
  39. package/dist/bin/exe-grant.js +5 -5
  40. package/dist/bin/exe-healthcheck.js +14 -8
  41. package/dist/bin/exe-heartbeat.js +29 -28
  42. package/dist/bin/exe-kill.js +31 -29
  43. package/dist/bin/exe-launch-agent.js +129 -68
  44. package/dist/bin/exe-memory-reads.js +257 -0
  45. package/dist/bin/exe-new-employee.js +12 -11
  46. package/dist/bin/exe-pending-messages.js +27 -25
  47. package/dist/bin/exe-pending-notifications.js +26 -24
  48. package/dist/bin/exe-pending-reviews.js +26 -24
  49. package/dist/bin/exe-rename.js +7 -7
  50. package/dist/bin/exe-review.js +30 -28
  51. package/dist/bin/exe-search.js +11 -9
  52. package/dist/bin/exe-session-cleanup.js +116 -86
  53. package/dist/bin/exe-settings.js +24 -23
  54. package/dist/bin/exe-start-codex.js +163 -41
  55. package/dist/bin/exe-start-opencode.js +15 -15
  56. package/dist/bin/exe-status.js +27 -25
  57. package/dist/bin/exe-support.js +5 -5
  58. package/dist/bin/exe-team.js +5 -5
  59. package/dist/bin/exe-timers.js +32 -213
  60. package/dist/bin/exe-watchdog.js +6 -5
  61. package/dist/bin/git-sweep.js +27 -25
  62. package/dist/bin/graph-backfill.js +9 -9
  63. package/dist/bin/graph-export.js +9 -9
  64. package/dist/bin/import-history.js +11 -11
  65. package/dist/bin/install.js +102 -95
  66. package/dist/bin/intercom-check.js +4 -4
  67. package/dist/bin/mcp-sessions.js +2 -2
  68. package/dist/bin/orchestration-metrics.js +6 -6
  69. package/dist/bin/postgres-agentic-reflection-backfill.js +5 -5
  70. package/dist/bin/postgres-agentic-semantic-backfill.js +7 -7
  71. package/dist/bin/pre-publish.js +5 -3
  72. package/dist/bin/reconcile-backlog.js +484 -0
  73. package/dist/bin/registry-proxy.js +1 -1
  74. package/dist/bin/scan-tasks.js +26 -24
  75. package/dist/bin/setup.js +6 -5
  76. package/dist/bin/shard-migrate.js +9 -9
  77. package/dist/bin/stack-update.js +15 -9
  78. package/dist/bin/update.js +1 -1
  79. package/dist/bin/vps-health-gate.js +1 -1
  80. package/dist/boot-generation-BL2ISK4U.js +22 -0
  81. package/dist/boot-review-sections-DBMQJT5N.js +107 -0
  82. package/dist/branch-hygiene-TZWK6QCF.js +224 -0
  83. package/dist/{branding-JTEOF4ZV.js → branding-DVXBGVDH.js} +3 -3
  84. package/dist/{browser-session-store-CXQMOSEC.js → browser-session-store-B2NR3LIX.js} +2 -1
  85. package/dist/{capability-cards-YOKVMMOL.js → capability-cards-DN5UIKVN.js} +4 -4
  86. package/dist/{capacity-monitor-S54IOWRL.js → capacity-monitor-ZG5GGFR2.js} +27 -25
  87. package/dist/{catchup-brief-DRRQCMKA.js → catchup-brief-D6HH6DWS.js} +32 -29
  88. package/dist/{support-outbox-MGUVVGHT.js → chunk-25X4JD2V.js} +28 -12
  89. package/dist/{chunk-IYB2NIW3.js → chunk-2KZNGDSW.js} +1 -1
  90. package/dist/{chunk-FUIIVSW6.js → chunk-2WAVZ5UJ.js} +1 -1
  91. package/dist/{chunk-K5WFKEXQ.js → chunk-2ZYO73KO.js} +4 -3
  92. package/dist/{chunk-CLAYNAXC.js → chunk-3EUMUSLE.js} +1 -1
  93. package/dist/{chunk-BVV6NHKQ.js → chunk-3SW4OEHB.js} +5 -5
  94. package/dist/{task-enforcement-VI7L43DC.js → chunk-4CMVFVFC.js} +173 -69
  95. package/dist/{chunk-BU2QQWBQ.js → chunk-4J4PMPXZ.js} +3 -2
  96. package/dist/{chunk-JD5S5AAV.js → chunk-4N2NRGCM.js} +2 -2
  97. package/dist/chunk-4QCFFW2H.js +378 -0
  98. package/dist/{chunk-7EA7LYW4.js → chunk-4VSV5P4D.js} +4 -4
  99. package/dist/{chunk-52KNOQMK.js → chunk-4ZCD2UWW.js} +39 -10
  100. package/dist/{chunk-DZ6OQM67.js → chunk-5ERDIOV3.js} +2 -2
  101. package/dist/chunk-5H47VHB7.js +30 -0
  102. package/dist/{chunk-UCQITDLM.js → chunk-5TY4FPH6.js} +1 -1
  103. package/dist/{chunk-R44Z32O2.js → chunk-6CH3TPHR.js} +2 -2
  104. package/dist/{chunk-WYLV4S6A.js → chunk-6WQ3NNCE.js} +3 -3
  105. package/dist/{chunk-HHIBOU3P.js → chunk-6XWTQUO4.js} +1 -1
  106. package/dist/chunk-76CJBM36.js +88 -0
  107. package/dist/{chunk-H2L4PHGH.js → chunk-7CTRGNLW.js} +2 -2
  108. package/dist/{chunk-XUKPALWT.js → chunk-7V4JADD6.js} +2 -2
  109. package/dist/{chunk-RMOBQ6A5.js → chunk-AD2VCHSS.js} +1 -1
  110. package/dist/{chunk-QXSBB26A.js → chunk-AJYCFKJR.js} +1 -1
  111. package/dist/chunk-AM7S2TY6.js +54 -0
  112. package/dist/{chunk-HEPR6QTL.js → chunk-AOHXREB7.js} +334 -66
  113. package/dist/{chunk-FNZR3B7U.js → chunk-ASAFVDVR.js} +1 -1
  114. package/dist/{chunk-TALWBVZE.js → chunk-BUCXNV32.js} +1 -1
  115. package/dist/{chunk-SHIICYJW.js → chunk-BYTDMAGB.js} +4 -2
  116. package/dist/{chunk-32SXNM3G.js → chunk-C5QAPURN.js} +1 -1
  117. package/dist/{chunk-XCTGRD4S.js → chunk-CE7F6RHG.js} +587 -77
  118. package/dist/{chunk-7WPOJDTA.js → chunk-CNVBKDEY.js} +21 -15
  119. package/dist/{chunk-KPIN44GZ.js → chunk-CWXVH7WV.js} +1 -1
  120. package/dist/chunk-D74XYSA3.js +621 -0
  121. package/dist/{chunk-A47MXY2I.js → chunk-EQHQF5IM.js} +50 -32
  122. package/dist/{chunk-AOO77BDE.js → chunk-EUS4QUKP.js} +1 -1
  123. package/dist/{chunk-RLRT2BVM.js → chunk-EV46QTD3.js} +1 -1
  124. package/dist/{chunk-PAEXYFMY.js → chunk-F27XXN6C.js} +1 -1
  125. package/dist/{chunk-XQNNB5GY.js → chunk-F55P3H4S.js} +37 -2
  126. package/dist/{chunk-MWZFF3Q7.js → chunk-F5O7V7UK.js} +1 -1
  127. package/dist/{chunk-LMZISHSJ.js → chunk-FPFPLPUY.js} +6 -6
  128. package/dist/{chunk-7TS64TBG.js → chunk-G4RGKBCA.js} +8 -3
  129. package/dist/{chunk-4IYSBPX3.js → chunk-G5T6KDG4.js} +1 -1
  130. package/dist/{chunk-7WDXEKUL.js → chunk-GBTTBXAF.js} +4 -4
  131. package/dist/{chunk-6MRWJMI5.js → chunk-GDIJE6D7.js} +1 -1
  132. package/dist/{chunk-AVKXCSBI.js → chunk-GG5NDQNN.js} +2 -2
  133. package/dist/{chunk-I3XKO3KQ.js → chunk-GJ26DULE.js} +2 -2
  134. package/dist/chunk-GL57PO3K.js +104 -0
  135. package/dist/chunk-GWDSB25T.js +6 -0
  136. package/dist/{chunk-F55JF43M.js → chunk-H6BV5TKD.js} +39 -24
  137. package/dist/{chunk-HCJCW7X4.js → chunk-H7I47RS5.js} +34 -1
  138. package/dist/{chunk-65SVQELL.js → chunk-HA5R6USD.js} +31 -5
  139. package/dist/{chunk-VJHUVFIW.js → chunk-HAF2DJYV.js} +3 -3
  140. package/dist/chunk-HJ447YER.js +224 -0
  141. package/dist/chunk-HV4KESRY.js +395 -0
  142. package/dist/{chunk-B76TSEP3.js → chunk-I26FWCSP.js} +216 -9
  143. package/dist/{chunk-X2FHTDIU.js → chunk-IBYL4DHM.js} +1 -1
  144. package/dist/{chunk-JCBKARSK.js → chunk-IOEUAQWP.js} +1 -1
  145. package/dist/{chunk-MW2GJJ53.js → chunk-JGIPOUIK.js} +3 -3
  146. package/dist/{chunk-2233TQCA.js → chunk-JLZ4RYSD.js} +2 -2
  147. package/dist/{chunk-Q7L6G6KV.js → chunk-JMYHW3AL.js} +2 -2
  148. package/dist/{chunk-OJTJYEMX.js → chunk-JPUZPAAF.js} +4 -4
  149. package/dist/{chunk-XWHE4LZK.js → chunk-JTBIS4R5.js} +21 -2
  150. package/dist/{chunk-5Z5NATNS.js → chunk-K7KFGDWF.js} +1 -1
  151. package/dist/{chunk-UIV2WPBT.js → chunk-KGWRTRKC.js} +2 -2
  152. package/dist/{chunk-KTPG4XKI.js → chunk-KP3U5QGJ.js} +1 -1
  153. package/dist/{chunk-TLYAEISW.js → chunk-KW2SD2FL.js} +2 -2
  154. package/dist/{chunk-X7H3HMFH.js → chunk-KWZMQACA.js} +11 -7
  155. package/dist/{chunk-OFX5B2S7.js → chunk-L2R3LN2P.js} +1 -1
  156. package/dist/{chunk-T4KZ2Z2N.js → chunk-L4A5DL6Z.js} +3 -3
  157. package/dist/{chunk-X425OEYF.js → chunk-LU2FALL6.js} +3 -3
  158. package/dist/{chunk-6M36JFFD.js → chunk-MKYFAKDW.js} +4 -4
  159. package/dist/{chunk-I5RLNOFK.js → chunk-MOPUCPXK.js} +12 -4
  160. package/dist/{chunk-ZIVEA3NB.js → chunk-MR6LHP4F.js} +1 -1
  161. package/dist/chunk-MVYB76QG.js +341 -0
  162. package/dist/{chunk-2SG2UGCH.js → chunk-MZM6EJCU.js} +4 -4
  163. package/dist/{chunk-ZREGOV2Q.js → chunk-N7QJKQMX.js} +258 -14
  164. package/dist/{chunk-QLFRCA6I.js → chunk-NBZL5CGA.js} +93 -12
  165. package/dist/{chunk-QFPTDSY2.js → chunk-NN2B2QKU.js} +1 -1
  166. package/dist/chunk-NX5INHHG.js +40 -0
  167. package/dist/{chunk-B75BOKEI.js → chunk-NXUSDL37.js} +2 -2
  168. package/dist/{chunk-TPHCZDE3.js → chunk-O5V57XYE.js} +1 -1
  169. package/dist/chunk-O7LWR36Z.js +269 -0
  170. package/dist/{chunk-ATOU6CYR.js → chunk-OLMQ4UOF.js} +57 -11
  171. package/dist/{chunk-6HBURYON.js → chunk-OTF44GXL.js} +4 -4
  172. package/dist/{chunk-DILD54TU.js → chunk-OZEBAGKR.js} +277 -137
  173. package/dist/{chunk-DADMPMYX.js → chunk-P5HIWY2E.js} +1 -1
  174. package/dist/{chunk-EIF2LQHZ.js → chunk-PDWS3E22.js} +55 -4
  175. package/dist/{chunk-KTY7I7UH.js → chunk-PPCDBE6Z.js} +1 -1
  176. package/dist/{chunk-XACL67CS.js → chunk-PQIDGXSG.js} +13 -4
  177. package/dist/chunk-PWUFMX75.js +186 -0
  178. package/dist/{chunk-UPWE5NOG.js → chunk-QDGZJ653.js} +3 -3
  179. package/dist/{chunk-MQRFKSLI.js → chunk-QOO5RW3K.js} +6 -5
  180. package/dist/{chunk-KCC5A7YL.js → chunk-QQ4E2RYW.js} +95 -18
  181. package/dist/{chunk-BPC5LZ6Y.js → chunk-QRYVCDWS.js} +2 -2
  182. package/dist/{chunk-Z42MXYWW.js → chunk-QWVXN224.js} +110 -28
  183. package/dist/{chunk-542EWGY3.js → chunk-QYZJHTNE.js} +26 -27
  184. package/dist/{chunk-I7Z6KTGF.js → chunk-R2NN3JKH.js} +43 -6
  185. package/dist/{chunk-3OBWSCJN.js → chunk-RACAOE2Z.js} +14 -14
  186. package/dist/{chunk-5RACW3LX.js → chunk-RAXVPNMR.js} +1 -1
  187. package/dist/{chunk-2HZY3ECT.js → chunk-RHWZUUS4.js} +6 -6
  188. package/dist/{chunk-RNAYCUNM.js → chunk-S3THSOWV.js} +1 -1
  189. package/dist/{chunk-W7DHFGCB.js → chunk-S7QUM2UK.js} +2 -2
  190. package/dist/{chunk-SSTLTIF3.js → chunk-SKGMF2SO.js} +25 -2
  191. package/dist/{chunk-GN45PBWI.js → chunk-SQKXKV2Y.js} +2 -2
  192. package/dist/{chunk-QZLIZAS7.js → chunk-TLT7NZCW.js} +4 -4
  193. package/dist/chunk-TTCUG2CF.js +91 -0
  194. package/dist/chunk-UEA3LXOB.js +40 -0
  195. package/dist/{chunk-DRCRZIIJ.js → chunk-UNXQBELK.js} +1 -1
  196. package/dist/{chunk-PLRIJALF.js → chunk-UP75Z5X7.js} +921 -176
  197. package/dist/{chunk-TNJRWUCE.js → chunk-USSB56WK.js} +1 -1
  198. package/dist/{chunk-KMBEL7VB.js → chunk-UUEKJDK6.js} +90 -10
  199. package/dist/{chunk-XJI7Z6LZ.js → chunk-UUQBZPJJ.js} +1 -1
  200. package/dist/{chunk-RRWDYOQS.js → chunk-V3EEVY4L.js} +1 -1
  201. package/dist/{chunk-N5I7RL3I.js → chunk-VPZHJCKW.js} +2 -2
  202. package/dist/chunk-VRAQTFD6.js +265 -0
  203. package/dist/{chunk-H37HHX3E.js → chunk-VSRPS3J7.js} +14 -14
  204. package/dist/{chunk-5ZNY7IZI.js → chunk-WMESXBJM.js} +37 -3
  205. package/dist/{chunk-54QCG45Y.js → chunk-WOYADZF5.js} +56 -5
  206. package/dist/{chunk-7Y5UDEVM.js → chunk-WZHZCDDO.js} +6 -5
  207. package/dist/{chunk-3NGZ4PL7.js → chunk-X2Q5NLUL.js} +11 -2
  208. package/dist/{chunk-YE7NH53A.js → chunk-X33NYQYJ.js} +2 -2
  209. package/dist/{chunk-UXBD22CD.js → chunk-XHK3FS3W.js} +3 -2
  210. package/dist/{chunk-3QDSCJ3F.js → chunk-XZXBB3TN.js} +2 -2
  211. package/dist/{chunk-JSKBHUR7.js → chunk-YCANDMI2.js} +2 -2
  212. package/dist/{chunk-JBSCL2XT.js → chunk-YLXTKI6M.js} +1 -1
  213. package/dist/{chunk-G7WO77Z2.js → chunk-YTCFACP4.js} +812 -896
  214. package/dist/{chunk-LTVCRHHD.js → chunk-Z6RSN7KS.js} +7 -6
  215. package/dist/{cleanup-graph-garbage-KUABW6LS.js → cleanup-graph-garbage-XWUFAMFO.js} +6 -6
  216. package/dist/{co-activation-XMPHKGHR.js → co-activation-ZCHPLLP6.js} +4 -4
  217. package/dist/{co-occurrence-SHKSJ6CV.js → co-occurrence-KG3LKALN.js} +4 -4
  218. package/dist/{code-context-index-EXXDY3QZ.js → code-context-index-WSOJQ72R.js} +5 -5
  219. package/dist/{content-extractor-RHITF2KM.js → content-extractor-NXYNEF5K.js} +2 -2
  220. package/dist/{conversation-wiki-populator-WU4HEQ7C.js → conversation-wiki-populator-WQN3IUU6.js} +1 -1
  221. package/dist/{crdt-sync-UJELKSNX.js → crdt-sync-4PFGPZXV.js} +8 -1
  222. package/dist/{crm-webhook-HHNBS7LY.js → crm-webhook-II7Z45Q6.js} +2 -2
  223. package/dist/cto-delegation-gate-JWWOJPNC.js +669 -0
  224. package/dist/{daemon-auth-7IFSV5FG.js → daemon-auth-3FEAHFWC.js} +5 -3
  225. package/dist/{daemon-orchestration-7QB6QOXT.js → daemon-orchestration-FX4XZDR5.js} +46 -27
  226. package/dist/{daemon-ready-5XI5BIJD.js → daemon-ready-4JTSFKAY.js} +1 -1
  227. package/dist/{daemon-ready-signal-E7VMIQ6I.js → daemon-ready-signal-LPAZWQOV.js} +5 -1
  228. package/dist/daemon-restart-circuit-breaker-PEGEIP6H.js +35 -0
  229. package/dist/daemon-timer-report-NM44WQRH.js +25 -0
  230. package/dist/{db-backup-DLD4QZBO.js → db-backup-W2QFSYQC.js} +15 -5
  231. package/dist/{db-restore-events-JVEEP52Z.js → db-restore-events-MQ2DBGEQ.js} +2 -2
  232. package/dist/dispatch-ack-VYH3ONSS.js +27 -0
  233. package/dist/{doc-graph-extractor-3TRLGD7M.js → doc-graph-extractor-F6ODFIID.js} +4 -4
  234. package/dist/{documents-FHIBU7KJ.js → documents-23WX2FPQ.js} +12 -12
  235. package/dist/dreaming-NWD22UQ5.js +46 -0
  236. package/dist/{exe-drift-KY2KHZEL.js → exe-drift-CW72FBGE.js} +5 -5
  237. package/dist/{exe-export-SP6G3QF6.js → exe-export-AUJ7CV7G.js} +11 -11
  238. package/dist/{exe-import-KJU7FBVQ.js → exe-import-JX4I52VE.js} +11 -11
  239. package/dist/{exe-key-M6TYHNUW.js → exe-key-2N6LY2BU.js} +241 -226
  240. package/dist/{exe-org-BGZNNE4H.js → exe-org-X4CSNCKS.js} +3 -3
  241. package/dist/{exe-snapshot-ZNPGUKRZ.js → exe-snapshot-XZ7FVCYZ.js} +31 -29
  242. package/dist/{fast-db-init-QQB6IVWV.js → fast-db-init-4CWHSOOR.js} +1 -1
  243. package/dist/{founder-context-PQR3BUS7.js → founder-context-7L3CFPYN.js} +4 -4
  244. package/dist/gateway/index.js +14 -14
  245. package/dist/{gateway-client-GRBOJQYC.js → gateway-client-7LMZKHTH.js} +1 -1
  246. package/dist/git-main-guard-UZP3KXYI.js +39 -0
  247. package/dist/{git-staleness-UGB5J3K7.js → git-staleness-CI2R4QOJ.js} +4 -4
  248. package/dist/{global-procedures-R7KRREEK.js → global-procedures-XQX3LUH6.js} +6 -6
  249. package/dist/{graph-auto-extract-LKIQ57PL.js → graph-auto-extract-OURWMGBS.js} +4 -4
  250. package/dist/{hook-integrity-WURH6DS3.js → hook-integrity-XY47IM7G.js} +1 -1
  251. package/dist/hook-prefetch-PHRRKY4C.js +137 -0
  252. package/dist/hooks/bug-report-worker.js +32 -28
  253. package/dist/hooks/codex-stop-task-finalizer.js +32 -28
  254. package/dist/hooks/commit-complete.js +29 -27
  255. package/dist/hooks/error-recall.js +12 -11
  256. package/dist/hooks/exe-heartbeat-hook.js +6 -6
  257. package/dist/hooks/ingest-worker.js +11 -11
  258. package/dist/hooks/ingest.js +18 -18
  259. package/dist/hooks/instructions-loaded.js +7 -7
  260. package/dist/hooks/manifest.json +20 -20
  261. package/dist/hooks/notification.js +7 -7
  262. package/dist/hooks/post-compact.js +28 -26
  263. package/dist/hooks/post-tool-combined.js +12 -12
  264. package/dist/hooks/pre-compact.js +29 -27
  265. package/dist/hooks/pre-tool-use.js +180 -47
  266. package/dist/hooks/prompt-submit.js +324 -273
  267. package/dist/hooks/session-end.js +35 -33
  268. package/dist/hooks/session-start.js +28 -21
  269. package/dist/hooks/stop.js +35 -33
  270. package/dist/hooks/subagent-stop.js +28 -26
  271. package/dist/hooks/summary-worker.js +36 -34
  272. package/dist/index.js +39 -37
  273. package/dist/{installer-SB2N5NKH.js → installer-G3JAO3NT.js} +9 -9
  274. package/dist/{installer-MZWWBXM6.js → installer-JND3RJ6Q.js} +9 -9
  275. package/dist/{installer-MNPXDLGG.js → installer-L7EVH5U5.js} +9 -9
  276. package/dist/{intercom-queue-VU2G6F5R.js → intercom-queue-ZFEQALR3.js} +25 -23
  277. package/dist/{key-backup-status-TNEHXA2X.js → key-backup-status-LOSMVATJ.js} +2 -2
  278. package/dist/lib/agent-config.js +4 -3
  279. package/dist/lib/cloud-sync.js +19 -19
  280. package/dist/lib/config.js +2 -2
  281. package/dist/lib/consolidation.js +9 -9
  282. package/dist/lib/crypto.js +3 -3
  283. package/dist/lib/database.js +6 -4
  284. package/dist/lib/db-daemon-client.js +5 -5
  285. package/dist/lib/db-read-worker-client.js +9 -3
  286. package/dist/lib/db.js +6 -4
  287. package/dist/lib/device-registry.js +3 -3
  288. package/dist/lib/embed-worker.js +2 -2
  289. package/dist/lib/embedder.js +5 -5
  290. package/dist/lib/employee-templates.js +7 -7
  291. package/dist/lib/employees.js +10 -4
  292. package/dist/lib/exe-daemon-client.js +4 -4
  293. package/dist/lib/exe-daemon.js +651 -158
  294. package/dist/lib/hybrid-search.js +11 -9
  295. package/dist/lib/identity.js +4 -4
  296. package/dist/lib/keychain.js +1 -1
  297. package/dist/lib/license.js +4 -4
  298. package/dist/lib/messaging.js +28 -24
  299. package/dist/lib/post-tool-memory.js +3 -3
  300. package/dist/lib/registry-proxy.js +1 -1
  301. package/dist/lib/reminders.js +15 -7
  302. package/dist/lib/schedules.js +9 -9
  303. package/dist/lib/session-registry.js +6 -6
  304. package/dist/lib/skill-learning.js +10 -10
  305. package/dist/lib/status-brief.js +4 -4
  306. package/dist/lib/store.js +8 -8
  307. package/dist/lib/task-router.js +5 -5
  308. package/dist/lib/tasks.js +29 -25
  309. package/dist/lib/tmux-routing.js +56 -25
  310. package/dist/lib/tmux-status.js +9 -3
  311. package/dist/lib/tmux-transport.js +1 -1
  312. package/dist/lib/token-spend.js +5 -5
  313. package/dist/lib/transport.js +2 -2
  314. package/dist/lib/ws-client.js +1 -1
  315. package/dist/{license-gate-BYDMFMFT.js → license-gate-QTRQHV4F.js} +5 -5
  316. package/dist/{license-sync-N3RNYVP2.js → license-sync-N3UJFWAW.js} +4 -4
  317. package/dist/mcp/register-tools.js +96 -89
  318. package/dist/mcp/server.js +98 -91
  319. package/dist/mcp/tools/complete-reminder.js +6 -6
  320. package/dist/mcp/tools/create-reminder.js +6 -6
  321. package/dist/mcp/tools/create-task.js +37 -28
  322. package/dist/mcp/tools/deactivate-behavior.js +11 -11
  323. package/dist/mcp/tools/list-reminders.js +6 -6
  324. package/dist/mcp/tools/list-tasks.js +35 -29
  325. package/dist/mcp/tools/send-message.js +28 -26
  326. package/dist/mcp/tools/update-task.js +34 -28
  327. package/dist/{mcp-http-config-EMAVS2JI.js → mcp-http-config-BDL36JZ3.js} +6 -6
  328. package/dist/{mcp-port-config-FFAMMLLE.js → mcp-port-config-6HYY6J7K.js} +2 -2
  329. package/dist/mcp-session-identity-T7H2EKFQ.js +177 -0
  330. package/dist/{memory-cards-TQRLZI4D.js → memory-cards-ZRDW7QBX.js} +8 -8
  331. package/dist/{memory-graph-extractor-IWGNVAFU.js → memory-graph-extractor-QLXDMHRS.js} +5 -5
  332. package/dist/{memory-poisoning-defense-T4ITBGSG.js → memory-poisoning-defense-HJTXYCWH.js} +4 -4
  333. package/dist/{memory-pressure-watchdog-RRIAXPXQ.js → memory-pressure-watchdog-HN5X7AVN.js} +76 -37
  334. package/dist/{memory-queue-X2CSEAKE.js → memory-queue-2UOOF27L.js} +5 -3
  335. package/dist/{memory-queue-client-BKQHTYTH.js → memory-queue-client-O5U6F6XP.js} +6 -6
  336. package/dist/{memory-reflection-6N4J2FL7.js → memory-reflection-WVNFBJAA.js} +4 -4
  337. package/dist/{monitor-grants-RZOVSYVO.js → monitor-grants-E3ELDEWD.js} +5 -5
  338. package/dist/{monitor-identity-MOXISYNG.js → monitor-identity-GLFZSEAH.js} +2 -2
  339. package/dist/notification-ledger-SZEPGJQ7.js +409 -0
  340. package/dist/{notifications-KONB3C62.js → notifications-4UT53CDG.js} +25 -23
  341. package/dist/{orchestration-events-5SIJOE4H.js → orchestration-events-WV4NWKZH.js} +5 -5
  342. package/dist/{orchestration-phase-CXRCBCK7.js → orchestration-phase-AWBXPO52.js} +3 -3
  343. package/dist/orchestrator-UAC3RW2T.js +47 -0
  344. package/dist/{pipeline-router-O6HU7DKH.js → pipeline-router-LM53NMWJ.js} +5 -5
  345. package/dist/{plan-limits-GRNBOBB3.js → plan-limits-2FEY2GOS.js} +7 -7
  346. package/dist/platform-procedures-X5DFJFV7.js +13 -0
  347. package/dist/{preferences-TYTGDXQZ.js → preferences-TWIXZBKS.js} +2 -2
  348. package/dist/{project-boot-SD7GM24K.js → project-boot-4NUH5FRU.js} +2 -2
  349. package/dist/{projection-worker-ZVOFHTXT.js → projection-worker-FZ7YSN3G.js} +5 -5
  350. package/dist/{prospective-memory-W6S67LZZ.js → prospective-memory-6D2ASWM3.js} +4 -4
  351. package/dist/{push-notifications-W5JRXLKJ.js → push-notifications-U6FIBQRW.js} +3 -3
  352. package/dist/{reranker-QATGTAPC.js → reranker-GYYRPANK.js} +3 -3
  353. package/dist/{retrieval-health-TWHKYCRK.js → retrieval-health-4Z242P3B.js} +1 -1
  354. package/dist/{review-polling-PCMD5BFN.js → review-polling-HXAFJZDA.js} +26 -24
  355. package/dist/runtime/index.js +37 -35
  356. package/dist/{session-events-3LCVQEL7.js → session-events-O3BV56ZX.js} +26 -24
  357. package/dist/{session-kill-telemetry-CZSPN5V6.js → session-kill-telemetry-AELE4JDK.js} +26 -24
  358. package/dist/session-mcp-config-YKNXG5DW.js +50 -0
  359. package/dist/{session-scope-CFHVSVAQ.js → session-scope-5EAPUT45.js} +25 -23
  360. package/dist/setup-wizard-NB6R4QVS.js +14 -0
  361. package/dist/{shard-manager-MBWQEYNG.js → shard-manager-35PWFR3B.js} +3 -3
  362. package/dist/{skill-refinement-74T6VDFM.js → skill-refinement-DVSYXBBP.js} +5 -5
  363. package/dist/stack-promote-RSOWO6EY.js +130 -0
  364. package/dist/{stack-release-XXMV7SPX.js → stack-release-HGFFFCGC.js} +112 -22
  365. package/dist/{stack-update-E2TKHHSQ.js → stack-update-ATCJFOEK.js} +7 -5
  366. package/dist/{steward-gate-WT7WXQIE.js → steward-gate-3GQ5XKMJ.js} +5 -5
  367. package/dist/support-inbox-4L62P4EP.js +33 -0
  368. package/dist/support-outbox-WHGPUYL2.js +16 -0
  369. package/dist/task-enforcement-IPIZX2MM.js +90 -0
  370. package/dist/{task-scope-CQEG5SSJ.js → task-scope-FOIW25HR.js} +27 -23
  371. package/dist/{tasks-crud-UGK5A25L.js → tasks-crud-HBDKE6C5.js} +35 -23
  372. package/dist/tasks-notify-G2U2U2YA.js +53 -0
  373. package/dist/{tasks-review-JKBRACII.js → tasks-review-27SFX5XN.js} +27 -23
  374. package/dist/{telemetry-upload-7LBYGPCB.js → telemetry-upload-ZOXHRWR5.js} +9 -9
  375. package/dist/tmux-command-guard-KSJKIX3C.js +117 -0
  376. package/dist/{token-budget-QBRJR4M5.js → token-budget-5Y4YPDO3.js} +5 -5
  377. package/dist/{tool-capability-index-Q6YQDL7I.js → tool-capability-index-VYF5GNJ2.js} +1 -1
  378. package/dist/{tool-telemetry-ULIDUK3P.js → tool-telemetry-KBXSBDTK.js} +1 -1
  379. package/dist/tui/App.js +40 -38
  380. package/dist/{tui-data-NPTGKT37.js → tui-data-NLFMBIQI.js} +25 -23
  381. package/dist/{update-check-HFO4VHO4.js → update-check-3FHAWIG2.js} +7 -3
  382. package/dist/update-support-UVUSZG7R.js +355 -0
  383. package/dist/{whatsapp-accounts-BWRZUY23.js → whatsapp-accounts-BFIOXYW5.js} +1 -1
  384. package/dist/{whatsapp-config-GOSELKTE.js → whatsapp-config-FAX6EA5K.js} +20 -13
  385. package/dist/{worker-gate-MXUQUG4M.js → worker-gate-3KUBAU32.js} +3 -3
  386. package/dist/{workflow-engine-EWCOLD47.js → workflow-engine-YITHXZR5.js} +2 -2
  387. package/dist/{working-memory-PY4AG577.js → working-memory-NUVB5SFV.js} +1 -1
  388. package/dist/worktree-LL62PLQB.js +47 -0
  389. package/dist/{worktree-sweep-ZAG3V24E.js → worktree-sweep-WTK3MTKH.js} +7 -7
  390. package/package.json +3 -2
  391. package/release-notes.json +86 -76
  392. package/dist/branch-hygiene-R5NVTRAY.js +0 -138
  393. package/dist/chunk-6IRVGOUL.js +0 -30
  394. package/dist/chunk-HMP5VT6A.js +0 -118
  395. package/dist/chunk-PVYR4FCQ.js +0 -113
  396. package/dist/chunk-UFAXZ2PY.js +0 -138
  397. package/dist/chunk-Y25OJWOQ.js +0 -45
  398. package/dist/chunk-YGAAZN3E.js +0 -47
  399. package/dist/cto-delegation-gate-DXNF5HVW.js +0 -290
  400. package/dist/dispatch-ack-4S44HBWB.js +0 -15
  401. package/dist/dreaming-OMFUEUCQ.js +0 -44
  402. package/dist/orchestrator-DUUX7FQV.js +0 -45
  403. package/dist/setup-wizard-TOFEJPLF.js +0 -13
  404. package/dist/tasks-notify-SMLDFBWM.js +0 -50
  405. package/dist/worktree-GVF2I5FP.js +0 -31
  406. /package/dist/{chunk-FLNSJ2TC.js → chunk-GIBYNT6W.js} +0 -0
  407. /package/dist/{chunk-NQBRGXYR.js → chunk-JRM4TAGS.js} +0 -0
  408. /package/dist/{chunk-WHRTDN5J.js → chunk-MGGOSFCR.js} +0 -0
  409. /package/dist/{chunk-D3QASX2W.js → chunk-QIFYLIUF.js} +0 -0
  410. /package/dist/{chunk-OADURIIN.js → chunk-WTH4CSAP.js} +0 -0
  411. /package/dist/{core-memory-VO5ZIBKH.js → core-memory-GAC6EWOQ.js} +0 -0
  412. /package/dist/{entity-boost-DNI4A5I7.js → entity-boost-NT6UO2Z3.js} +0 -0
  413. /package/dist/{message-queue-client-Q5BW3O5W.js → message-queue-client-4EOQRUXW.js} +0 -0
  414. /package/dist/{oauth-server-ROQFOEMM.js → oauth-server-67YBDP32.js} +0 -0
  415. /package/dist/{webhook-pipe-FZYLO6RR.js → webhook-pipe-LEXMUQ5B.js} +0 -0
  416. /package/dist/{wiki-acl-JA5UCQZ4.js → wiki-acl-74BLWG6J.js} +0 -0
  417. /package/dist/{wiki-client-VNDOBK63.js → wiki-client-EAN7F5LV.js} +0 -0
@@ -1,50 +1,68 @@
1
+ import {
2
+ isPaneBusy
3
+ } from "./chunk-4CMVFVFC.js";
1
4
  import {
2
5
  recordSessionKill
3
- } from "./chunk-T4KZ2Z2N.js";
6
+ } from "./chunk-L4A5DL6Z.js";
4
7
  import {
5
8
  updateTask
6
- } from "./chunk-542EWGY3.js";
9
+ } from "./chunk-QYZJHTNE.js";
10
+ import {
11
+ checkDispatchStatus,
12
+ readTerminalLauncherFailure,
13
+ releaseDispatchClaim,
14
+ tryAcquireDispatchClaim
15
+ } from "./chunk-O7LWR36Z.js";
7
16
  import {
8
17
  countPendingReviews,
9
18
  ensureEmployeeAsync,
10
19
  extractRootExe,
11
20
  getSessionState,
21
+ getSessionStateAsync,
12
22
  isExeSession,
23
+ isValidSessionScope,
13
24
  queryTaskRows,
14
25
  queueIntercom,
15
26
  sendIntercomAsync,
27
+ sessionHasPendingInputAsync,
16
28
  sessionScopeFilter,
17
29
  strictSessionScopeFilter,
18
30
  writeNotification
19
- } from "./chunk-PLRIJALF.js";
31
+ } from "./chunk-UP75Z5X7.js";
20
32
  import {
33
+ LEASE_MINUTES,
21
34
  isLeaseExpired
22
35
  } from "./chunk-E23LNUWJ.js";
23
36
  import {
24
37
  listSessions
25
- } from "./chunk-UIV2WPBT.js";
26
- import {
27
- listTmuxSessions,
28
- parseContextPercentage
29
- } from "./chunk-SSTLTIF3.js";
38
+ } from "./chunk-KGWRTRKC.js";
30
39
  import {
31
40
  getTransport
32
- } from "./chunk-RMOBQ6A5.js";
41
+ } from "./chunk-AD2VCHSS.js";
42
+ import {
43
+ capturePaneLinesAsync,
44
+ listTmuxSessions,
45
+ parseContextPercentage,
46
+ parseContextPercentageAsync
47
+ } from "./chunk-SKGMF2SO.js";
33
48
  import {
34
49
  recordOrchestrationEventBestEffort
35
- } from "./chunk-FUIIVSW6.js";
50
+ } from "./chunk-2WAVZ5UJ.js";
36
51
  import {
37
52
  getAgentRuntime
38
- } from "./chunk-65SVQELL.js";
53
+ } from "./chunk-HA5R6USD.js";
54
+ import {
55
+ BUSY_PATTERN
56
+ } from "./chunk-GWDSB25T.js";
39
57
  import {
40
58
  baseAgentName,
41
59
  getCoordinatorName,
42
60
  isCoordinatorName,
43
61
  shouldAutoInstance
44
- } from "./chunk-HEPR6QTL.js";
62
+ } from "./chunk-AOHXREB7.js";
45
63
  import {
46
64
  loadConfigSync
47
- } from "./chunk-7Y5UDEVM.js";
65
+ } from "./chunk-WZHZCDDO.js";
48
66
 
49
67
  // src/lib/daemon-orchestration.ts
50
68
  import { execFile, execFileSync, execSync } from "child_process";
@@ -54,6 +72,43 @@ import { existsSync as existsSync2, readFileSync, writeFileSync, unlinkSync, sta
54
72
  import { homedir, loadavg, freemem, cpus as osCpus, platform } from "os";
55
73
  import { join, basename } from "path";
56
74
 
75
+ // src/lib/session-pane-snapshot.ts
76
+ var CONTEXT_BAR = /[█░]+\s*(\d+)%/;
77
+ var PROMPT_INPUT_LINE = /(?:^|\s|│)\s*[❯›>]\s?(.*)$/u;
78
+ var PROMPT_PLACEHOLDER = /^Try\s+["'“]/u;
79
+ function parseContextFromPane(paneText) {
80
+ for (const line of paneText.split("\n")) {
81
+ const m = line.match(CONTEXT_BAR);
82
+ if (m) return parseInt(m[1], 10);
83
+ }
84
+ return null;
85
+ }
86
+ function isPaneWorking(paneText) {
87
+ return /Running…/.test(paneText) || BUSY_PATTERN.test(paneText);
88
+ }
89
+ function extractPaneInputBuffer(paneText) {
90
+ const lines = paneText.split("\n");
91
+ const tail = lines.slice(-12);
92
+ for (let i = tail.length - 1; i >= 0; i--) {
93
+ const line = tail[i];
94
+ if (!line || line.trim().length === 0) continue;
95
+ const m = PROMPT_INPUT_LINE.exec(line);
96
+ if (!m) continue;
97
+ const content = (m[1] ?? "").replace(/[│]\s*$/u, "").trim();
98
+ if (content.length === 0) return "";
99
+ if (PROMPT_PLACEHOLDER.test(content)) return "";
100
+ return content;
101
+ }
102
+ return "";
103
+ }
104
+ function derivePaneSnapshot(paneText) {
105
+ return {
106
+ contextPct: parseContextFromPane(paneText),
107
+ working: isPaneWorking(paneText),
108
+ inputBuffer: extractPaneInputBuffer(paneText)
109
+ };
110
+ }
111
+
57
112
  // src/lib/agent-signals.ts
58
113
  import { existsSync, openSync, closeSync, readSync, statSync } from "fs";
59
114
  import os from "os";
@@ -182,6 +237,7 @@ function getAvailableMemoryGB() {
182
237
  var execFileAsync = promisify(execFile);
183
238
  var IDLE_NUDGE_DEDUP_MS = Number(process.env.EXE_NUDGE_INTERVAL_MS) || 6e4;
184
239
  var SESSION_TTL_HOURS = Number(process.env.EXE_SESSION_TTL_HOURS) || 8;
240
+ var UNREACHABLE_ACTIVE_TASK_TTL_HOURS = Number(process.env.EXE_UNREACHABLE_ACTIVE_TASK_TTL_HOURS) || 1;
185
241
  var SESSION_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_CONTEXT_KILL_PCT) || 80;
186
242
  var IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDOW_MS) || 6e4;
187
243
  function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
@@ -198,6 +254,19 @@ function classifyTtlKillReason(ageHours, contextPct) {
198
254
  if (contextPct !== null && contextPct > SESSION_CONTEXT_THRESHOLD_PCT) return "ttl_context";
199
255
  return null;
200
256
  }
257
+ function isUnreachableActiveTaskSession(input) {
258
+ if (input.ageHours < UNREACHABLE_ACTIVE_TASK_TTL_HOURS) return false;
259
+ if (input.state === "offline" || input.state === "no_claude") return true;
260
+ if (input.hasPendingInput && input.state !== "thinking" && input.state !== "tool") return true;
261
+ return false;
262
+ }
263
+ var WEDGE_RECOVERY_TICKS_REQUIRED = Math.max(
264
+ 1,
265
+ Number(process.env.EXE_WEDGE_RECOVERY_TICKS) || 2
266
+ );
267
+ function clearInputBufferArgs(sessionName) {
268
+ return ["send-keys", "-t", sessionName, "C-u"];
269
+ }
201
270
  function shouldKillIdleSession(input) {
202
271
  if (!input.enabled) return false;
203
272
  if (isExeSession(input.sessionName)) return false;
@@ -240,38 +309,121 @@ async function pollIdleEmployees(deps, lastNudge) {
240
309
  }
241
310
  return nudged;
242
311
  }
243
- async function checkSessionTTL(deps) {
312
+ async function checkSessionTTL(deps, wedgeObservations = /* @__PURE__ */ new Map()) {
244
313
  const registered = deps.listRegisteredSessions().filter(
245
314
  (s) => !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
246
315
  );
247
316
  if (registered.length === 0) return [];
248
317
  let liveSessions;
249
318
  try {
250
- liveSessions = new Set(deps.listTmuxSessions());
319
+ liveSessions = new Set(await deps.listTmuxSessions());
251
320
  } catch (e) {
252
321
  process.stderr.write("[daemon-orch] list tmux sessions for TTL check: " + (e instanceof Error ? e.message : String(e)) + "\n");
253
322
  return [];
254
323
  }
255
324
  const killed = [];
256
325
  for (const entry of registered) {
257
- if (!liveSessions.has(entry.windowName)) continue;
326
+ if (!liveSessions.has(entry.windowName)) {
327
+ wedgeObservations.delete(entry.windowName);
328
+ continue;
329
+ }
258
330
  if (isCoordinatorName(entry.agentId) || isExeSession(entry.windowName)) continue;
259
- const createdEpoch = deps.getSessionCreatedEpoch(entry.windowName);
331
+ const createdEpoch = await deps.getSessionCreatedEpoch(entry.windowName);
260
332
  if (createdEpoch === null) continue;
261
333
  const ageHours = (Date.now() / 1e3 - createdEpoch) / 3600;
262
- const contextPct = deps.parseContextPercentage(entry.windowName);
334
+ const snapshot = await deps.captureSessionSnapshot?.(entry.windowName) ?? null;
335
+ const contextPct = snapshot ? snapshot.contextPct : await deps.parseContextPercentage(entry.windowName);
263
336
  const killReason = classifyTtlKillReason(ageHours, contextPct);
337
+ if (killReason === null && snapshot) {
338
+ const tooYoung = entry.registeredAt ? Date.now() - new Date(entry.registeredAt).getTime() < 5 * 60 * 1e3 : false;
339
+ const buffer = snapshot.inputBuffer;
340
+ const wedgeCandidate = !tooYoung && !snapshot.working && buffer.length > 0;
341
+ if (!wedgeCandidate) {
342
+ wedgeObservations.delete(entry.windowName);
343
+ } else {
344
+ const prev = wedgeObservations.get(entry.windowName);
345
+ if (!prev || prev.buffer !== buffer) {
346
+ wedgeObservations.set(entry.windowName, { buffer, ticks: 1, cleared: false });
347
+ } else {
348
+ const ticks = prev.ticks + 1;
349
+ if (ticks < WEDGE_RECOVERY_TICKS_REQUIRED) {
350
+ wedgeObservations.set(entry.windowName, { buffer, ticks, cleared: prev.cleared });
351
+ } else {
352
+ let hasActive = false;
353
+ try {
354
+ hasActive = deps.hasActiveTasks ? await deps.hasActiveTasks(entry.agentId) : false;
355
+ } catch {
356
+ hasActive = false;
357
+ }
358
+ const scope = entry.windowName.includes("-") ? entry.windowName.slice(entry.windowName.indexOf("-") + 1) : null;
359
+ if (!hasActive) {
360
+ wedgeObservations.delete(entry.windowName);
361
+ } else if (!prev.cleared && deps.clearInputBuffer) {
362
+ deps.clearInputBuffer(entry.windowName);
363
+ wedgeObservations.set(entry.windowName, { buffer, ticks: 0, cleared: true });
364
+ process.stderr.write(
365
+ `[exed] Session TTL: wedged-input CLEAR (C-u) for ${entry.windowName} (buffer stable ${ticks} ticks) \u2014 clearing stuck buffer so the queued nudge lands
366
+ `
367
+ );
368
+ recordOrchestrationEventBestEffort({
369
+ eventType: "session.wedge_clear",
370
+ source: "daemon-orchestration.checkSessionTTL",
371
+ agentId: entry.agentId,
372
+ tmuxSession: entry.windowName,
373
+ sessionScope: scope,
374
+ payload: { ageHours: parseFloat(ageHours.toFixed(2)), contextPct, trigger: "pending_input_wedge", wedgeTicks: ticks }
375
+ });
376
+ } else {
377
+ wedgeObservations.delete(entry.windowName);
378
+ process.stderr.write(
379
+ `[exed] Session TTL: wedged-input KILL for ${entry.windowName} (buffer byte-identical through clear + dwell) \u2014 killing to respawn active task
380
+ `
381
+ );
382
+ deps.recordKill?.({ sessionName: entry.windowName, agentId: entry.agentId, reason: "unreachable" });
383
+ recordOrchestrationEventBestEffort({
384
+ eventType: "session.unreachable_kill",
385
+ source: "daemon-orchestration.checkSessionTTL",
386
+ agentId: entry.agentId,
387
+ tmuxSession: entry.windowName,
388
+ sessionScope: scope,
389
+ payload: {
390
+ ageHours: parseFloat(ageHours.toFixed(2)),
391
+ contextPct,
392
+ killReason: "unreachable",
393
+ trigger: "pending_input_wedge",
394
+ wedgeTicks: ticks
395
+ }
396
+ });
397
+ deps.killSession(entry.windowName);
398
+ killed.push(entry.windowName);
399
+ continue;
400
+ }
401
+ }
402
+ }
403
+ }
404
+ }
264
405
  if (killReason !== null) {
265
406
  const isContextFull = contextPct !== null && contextPct > 85;
407
+ let telemetryReason = killReason;
266
408
  if (deps.hasActiveTasks && !isContextFull) {
267
409
  try {
268
410
  const active = await deps.hasActiveTasks(entry.agentId);
269
411
  if (active) {
270
- process.stderr.write(
271
- `[exed] Session TTL: SKIPPING ${entry.windowName} (age=${ageHours.toFixed(1)}h, reason=${killReason}) \u2014 agent has active tasks
412
+ const state = await deps.getSessionState?.(entry.windowName) ?? null;
413
+ const pendingInput = await deps.hasPendingInput?.(entry.windowName) ?? false;
414
+ if (isUnreachableActiveTaskSession({ ageHours, state, hasPendingInput: pendingInput })) {
415
+ telemetryReason = "unreachable";
416
+ process.stderr.write(
417
+ `[exed] Session TTL: unreachable override for ${entry.windowName} (age=${ageHours.toFixed(1)}h, state=${state ?? "unknown"}, pendingInput=${pendingInput}) \u2014 killing despite active tasks
272
418
  `
273
- );
274
- continue;
419
+ );
420
+ } else {
421
+ process.stderr.write(
422
+ `[exed] Session TTL: SKIPPING ${entry.windowName} (age=${ageHours.toFixed(1)}h, reason=${killReason}) \u2014 agent has active tasks
423
+ `
424
+ );
425
+ continue;
426
+ }
275
427
  }
276
428
  } catch {
277
429
  process.stderr.write(
@@ -288,23 +440,23 @@ async function checkSessionTTL(deps) {
288
440
  );
289
441
  }
290
442
  process.stderr.write(
291
- `[exed] Session TTL: killing ${entry.windowName} (age=${ageHours.toFixed(1)}h, context=${contextPct}%, reason=${killReason})
443
+ `[exed] Session TTL: killing ${entry.windowName} (age=${ageHours.toFixed(1)}h, context=${contextPct}%, reason=${telemetryReason})
292
444
  `
293
445
  );
294
446
  deps.recordKill?.({
295
447
  sessionName: entry.windowName,
296
448
  agentId: entry.agentId,
297
- reason: killReason
449
+ reason: telemetryReason
298
450
  });
299
451
  recordOrchestrationEventBestEffort({
300
- eventType: killReason === "ttl_age" ? "session.ttl_kill" : "session.context_kill",
452
+ eventType: telemetryReason === "ttl_age" ? "session.ttl_kill" : telemetryReason === "ttl_context" ? "session.context_kill" : "session.unreachable_kill",
301
453
  source: "daemon-orchestration.checkSessionTTL",
302
454
  agentId: entry.agentId,
303
455
  tmuxSession: entry.windowName,
304
456
  sessionScope: entry.windowName.includes("-") ? entry.windowName.slice(entry.windowName.indexOf("-") + 1) : null,
305
- payload: { ageHours: parseFloat(ageHours.toFixed(2)), contextPct, killReason }
457
+ payload: { ageHours: parseFloat(ageHours.toFixed(2)), contextPct, killReason: telemetryReason }
306
458
  });
307
- deps.killSession(entry.windowName);
459
+ await deps.killSession(entry.windowName);
308
460
  killed.push(entry.windowName);
309
461
  }
310
462
  }
@@ -478,6 +630,20 @@ async function pollReviewNudge(deps, state) {
478
630
  `[review-nudge] ${row.count} review(s) scoped to dead coordinator "${row.scope}" folded into live coordinator "${attributed}" (project "${row.projectName}")
479
631
  `
480
632
  );
633
+ const corruptedLiteral = row.scope;
634
+ if (deps.repairCorruptedScope && !isValidSessionScope(row.scope)) {
635
+ try {
636
+ const repaired = await deps.repairCorruptedScope(corruptedLiteral, row.projectName ?? null, attributed);
637
+ if (repaired > 0) {
638
+ process.stderr.write(
639
+ `[review-nudge] REPAIRED ${repaired} row(s) with corrupted session_scope ${JSON.stringify(corruptedLiteral.slice(0, 60))} \u2192 "${attributed}" (bug 4941abba)
640
+ `
641
+ );
642
+ }
643
+ } catch (e) {
644
+ process.stderr.write("[review-nudge] corrupted-scope repair failed: " + (e instanceof Error ? e.message : String(e)) + "\n");
645
+ }
646
+ }
481
647
  } else {
482
648
  const lastAlert = _deadScopeReviewAlertAt.get(row.scope) ?? 0;
483
649
  if (now - lastAlert < DEAD_SCOPE_REVIEW_ALERT_MS) continue;
@@ -550,8 +716,26 @@ function saveNudgeState(state) {
550
716
  const entries = Array.from(state.lastNudge.entries());
551
717
  writeFileSync(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
552
718
  }
553
- function createReviewNudgeRealDeps(_getClient) {
719
+ function createReviewNudgeRealDeps(getClient) {
554
720
  return {
721
+ // Bug 4941abba: additive cleanup — re-scope rows carrying a corrupted
722
+ // session_scope (leaked shell fragment) to the live coordinator the fold
723
+ // logic attributed them to. Scoped to the exact corrupted literal + the
724
+ // same project the attribution was decided on. UPDATE only; never
725
+ // destructive to user data.
726
+ repairCorruptedScope: async (corruptedScope, projectName, newScope) => {
727
+ if (!isValidSessionScope(newScope)) return 0;
728
+ const client = getClient();
729
+ const now = (/* @__PURE__ */ new Date()).toISOString();
730
+ const res = projectName === null ? await client.execute({
731
+ sql: `UPDATE tasks SET session_scope = ?, updated_at = ? WHERE session_scope = ? AND project_name IS NULL`,
732
+ args: [newScope, now, corruptedScope]
733
+ }) : await client.execute({
734
+ sql: `UPDATE tasks SET session_scope = ?, updated_at = ? WHERE session_scope = ? AND project_name = ?`,
735
+ args: [newScope, now, corruptedScope, projectName]
736
+ });
737
+ return Number(res?.rowsAffected ?? 0);
738
+ },
555
739
  listTmuxSessions: () => listTmuxSessions(),
556
740
  getSessionState: (sessionName) => getSessionState(sessionName),
557
741
  countReviewsForScope: async (sessionScope) => {
@@ -622,7 +806,12 @@ function createIdleNudgeRealDeps(_getClient) {
622
806
  if (rows.length === 0) return null;
623
807
  return rows[0];
624
808
  },
625
- sendIntercom: (sessionName) => sendIntercomAsync(sessionName),
809
+ // Bug f9302cc0: the daemon's idle nudge targets the employee session whose
810
+ // scoped task was just verified (queryOpenTask filters by the employee's
811
+ // own scope). Derive the caller scope from the TARGET session so the
812
+ // isolation gate compares task-lane against task-lane instead of blocking
813
+ // on unrelated ambient daemon context.
814
+ sendIntercom: (sessionName) => sendIntercomAsync(sessionName, { callerSessionScope: extractRootExe(sessionName) }),
626
815
  killSession: (sessionName) => {
627
816
  getTransport().kill(sessionName);
628
817
  },
@@ -638,8 +827,21 @@ function createIdleNudgeRealDeps(_getClient) {
638
827
  function createSessionTTLRealDeps(getClient) {
639
828
  return {
640
829
  listRegisteredSessions: () => listSessions(),
641
- listTmuxSessions: () => listTmuxSessions(),
642
- getSessionCreatedEpoch: (sessionName) => {
830
+ // Bug 1c870c78: TTL runs on a 5-min timer — list sessions async (execFile).
831
+ listTmuxSessions: async () => {
832
+ try {
833
+ const socket = process.env.EXE_TMUX_SOCKET;
834
+ const socketArgs = socket ? ["-L", socket] : [];
835
+ const { stdout } = await execFileAsync("tmux", [...socketArgs, "list-sessions", "-F", "#{session_name}"], {
836
+ encoding: "utf8",
837
+ timeout: 3e3
838
+ });
839
+ return String(stdout ?? "").trim().split("\n").filter(Boolean);
840
+ } catch {
841
+ return [];
842
+ }
843
+ },
844
+ getSessionCreatedEpoch: async (sessionName) => {
643
845
  try {
644
846
  const sessions = listSessions();
645
847
  const entry = sessions.find((s) => s.windowName === sessionName);
@@ -650,20 +852,60 @@ function createSessionTTLRealDeps(getClient) {
650
852
  } catch {
651
853
  }
652
854
  try {
653
- const out = execFileSync(
855
+ const { stdout } = await execFileAsync(
654
856
  "tmux",
655
857
  ["display-message", "-t", sessionName, "-p", "#{session_created}"],
656
- { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e3 }
657
- ).trim();
658
- const epoch = parseInt(out, 10);
858
+ { encoding: "utf8", timeout: 3e3 }
859
+ );
860
+ const epoch = parseInt(String(stdout ?? "").trim(), 10);
659
861
  return isNaN(epoch) ? null : epoch;
660
862
  } catch {
661
863
  return null;
662
864
  }
663
865
  },
664
- parseContextPercentage: (sessionName) => parseContextPercentage(sessionName),
665
- killSession: (sessionName) => {
666
- getTransport().kill(sessionName);
866
+ // UNION (fix/wake-daemon-20260711 x fix/phantom-goalhook-20260711). wake-daemon
867
+ // moves the TTL-timer deps onto async twins (bug 1c870c78) precisely so the
868
+ // daemon event loop is never blocked by a sync tmux subprocess — a stall here
869
+ // is the 14-38s event-loop block that drops every MCP client. phantom-goalhook
870
+ // (de5d9fe8) landed captureSessionSnapshot/clearInputBuffer on the SAME timer
871
+ // while that async work was in flight, so they arrived SYNC. Keeping both
872
+ // verbatim would re-introduce exactly the stall wake-daemon exists to remove,
873
+ // and would text-merge clean while doing it. Both are async here.
874
+ parseContextPercentage: (sessionName) => parseContextPercentageAsync(sessionName),
875
+ getSessionState: (sessionName) => getSessionStateAsync(sessionName),
876
+ hasPendingInput: (sessionName) => sessionHasPendingInputAsync(sessionName),
877
+ // de5d9fe8: SINGLE pane capture → context %, working-state, and input-buffer
878
+ // content. Used by checkSessionTTL for BOTH context % and the wedge check, so
879
+ // the wedge path adds no extra capture on the tick. Now async (capturePaneLinesAsync).
880
+ captureSessionSnapshot: async (sessionName) => {
881
+ try {
882
+ const transport = getTransport();
883
+ if (!transport.isAlive(sessionName)) return null;
884
+ const lines = await capturePaneLinesAsync(sessionName, 12);
885
+ return derivePaneSnapshot(lines.join("\n"));
886
+ } catch {
887
+ return null;
888
+ }
889
+ },
890
+ // de5d9fe8: non-destructive recovery — clear the stuck input buffer (C-u, no
891
+ // Enter) so the queued nudge can land. Never submits anything. execFileSync ->
892
+ // execFileAsync: this runs on a timer tick and must not block the event loop.
893
+ clearInputBuffer: async (sessionName) => {
894
+ try {
895
+ await execFileAsync("tmux", clearInputBufferArgs(sessionName), { timeout: 2e3 });
896
+ } catch {
897
+ }
898
+ },
899
+ // Bug 1c870c78: async kill on the TTL timer — TmuxTransport.killAsync runs
900
+ // kill-session via execFileAsync. Fall back to sync kill for transports that
901
+ // don't implement killAsync (e.g. test mocks).
902
+ killSession: async (sessionName) => {
903
+ const t = getTransport();
904
+ if (typeof t.killAsync === "function") {
905
+ await t.killAsync(sessionName);
906
+ } else {
907
+ t.kill(sessionName);
908
+ }
667
909
  },
668
910
  hasActiveTasks: getClient ? async (agentId) => {
669
911
  const rows = await queryTaskRows({
@@ -701,11 +943,13 @@ function createIdleKillRealDeps(getClient, intercomAckWindowMs) {
701
943
  var AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
702
944
  var AUTO_WAKE_MAX_RETRIES = 3;
703
945
  var DEAD_SCOPE_SKIP_THRESHOLD = 10;
946
+ var DISPATCH_PENDING_FRESH_MS = 90 * 1e3;
704
947
  var AUTO_WAKE_STATE_PATH = join(homedir(), ".exe-os", "session-cache", "auto-wake-state.json");
705
948
  var CRASH_LOOP_WINDOW_MS = 15 * 60 * 1e3;
706
949
  var CRASH_LOOP_THRESHOLD = 3;
707
950
  var BACKOFF_BASE_MS = 2 * 60 * 1e3;
708
951
  var BACKOFF_CAP_MS = 30 * 60 * 1e3;
952
+ var AUTO_WAKE_MAX_SPAWNS_PER_TICK = 4;
709
953
  var CPU_PRESSURE_THRESHOLD = 80;
710
954
  var FREE_RAM_MIN_GB = 4;
711
955
  function loadAutoWakeState() {
@@ -772,6 +1016,26 @@ function _resetAutoWakeState() {
772
1016
  } catch {
773
1017
  }
774
1018
  }
1019
+ var TERMINAL_LAUNCHER_FAILURE_GATE = "terminal_launcher_failure";
1020
+ function readTerminalFailureForAgent(agentId, scope, opts) {
1021
+ const readerOpts = {
1022
+ ...opts?.now != null ? { now: opts.now } : {},
1023
+ ...opts?.baseDir ? { baseDir: opts.baseDir } : {}
1024
+ };
1025
+ const normalize = opts?.baseAgent ?? baseAgentName;
1026
+ try {
1027
+ const direct = readTerminalLauncherFailure(agentId, scope, readerOpts);
1028
+ if (direct) return direct;
1029
+ const base = normalize(agentId);
1030
+ if (base && base !== agentId) {
1031
+ const b = readTerminalLauncherFailure(base, scope, readerOpts);
1032
+ if (b) return b;
1033
+ }
1034
+ return null;
1035
+ } catch {
1036
+ return null;
1037
+ }
1038
+ }
775
1039
  function checkAutoWakeGates(key, nowMs) {
776
1040
  try {
777
1041
  const blockWindowMs = parseInt(process.env.EXE_AUTOWAKE_BLOCK_WINDOW_MS ?? "", 10) || 15e3;
@@ -782,6 +1046,16 @@ function checkAutoWakeGates(key, nowMs) {
782
1046
  }
783
1047
  } catch {
784
1048
  }
1049
+ if (key !== "__global__") {
1050
+ const sep = key.indexOf("::");
1051
+ if (sep > 0) {
1052
+ const agentId = key.slice(0, sep);
1053
+ const scope = key.slice(sep + 2);
1054
+ if (scope && readTerminalFailureForAgent(agentId, scope, { now: nowMs })) {
1055
+ return TERMINAL_LAUNCHER_FAILURE_GATE;
1056
+ }
1057
+ }
1058
+ }
785
1059
  const loopCount = _autoWakeState.crashLoopCount[key] ?? 0;
786
1060
  if (loopCount > 0) {
787
1061
  const backoffMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, loopCount - 1), BACKOFF_CAP_MS);
@@ -827,16 +1101,37 @@ async function readTaskDeliverable(getClient, taskId) {
827
1101
  return null;
828
1102
  }
829
1103
  }
1104
+ async function annotateEnforcementNote(getClient, taskId, note) {
1105
+ try {
1106
+ const client = getClient();
1107
+ const stamped = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${note}`;
1108
+ await client.execute({
1109
+ sql: `UPDATE tasks
1110
+ SET enforcement_note = CASE
1111
+ WHEN enforcement_note IS NULL OR enforcement_note = '' THEN ?
1112
+ ELSE enforcement_note || char(10) || ?
1113
+ END
1114
+ WHERE id = ?`,
1115
+ args: [stamped, stamped, taskId]
1116
+ });
1117
+ } catch {
1118
+ }
1119
+ }
830
1120
  async function markTaskBlockedAbandoned(getClient, taskId, reason) {
831
1121
  try {
832
- await updateTask({ taskId, status: "blocked", result: reason });
1122
+ await updateTask({ taskId, status: "blocked", result: reason, preserveResultIfPresent: true });
833
1123
  } catch {
834
1124
  const client = getClient();
835
1125
  await client.execute({
836
- sql: `UPDATE tasks SET status = 'blocked', result = ?, updated_at = ? WHERE id = ?`,
1126
+ sql: `UPDATE tasks
1127
+ SET status = 'blocked',
1128
+ result = CASE WHEN result IS NULL OR trim(result, char(32)||char(9)||char(10)||char(13)) = '' THEN ? ELSE result END,
1129
+ updated_at = ?
1130
+ WHERE id = ?`,
837
1131
  args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
838
1132
  });
839
1133
  }
1134
+ await annotateEnforcementNote(getClient, taskId, reason);
840
1135
  }
841
1136
  function shouldAutoWake(input) {
842
1137
  if (isCoordinatorName(input.agentId)) return false;
@@ -881,14 +1176,17 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
881
1176
  process.stderr.write("[daemon-orch] list tmux sessions for orphan poll: " + (e instanceof Error ? e.message : String(e)) + "\n");
882
1177
  return [];
883
1178
  }
1179
+ const normalizeAgentForWake = deps.baseAgent ?? baseAgentName;
884
1180
  const liveAgentScopedCounts = /* @__PURE__ */ new Map();
885
1181
  for (const session of liveSessions) {
886
1182
  const agent = deps.parseAgentFromSession(session);
887
1183
  if (agent) {
888
1184
  const dashIdx = session.indexOf("-");
889
1185
  const scope = dashIdx >= 0 ? session.slice(dashIdx + 1) : session;
890
- const scopedKey = `${agent}::${scope}`;
891
- liveAgentScopedCounts.set(scopedKey, (liveAgentScopedCounts.get(scopedKey) ?? 0) + 1);
1186
+ for (const name of /* @__PURE__ */ new Set([agent, normalizeAgentForWake(agent)])) {
1187
+ const scopedKey = `${name}::${scope}`;
1188
+ liveAgentScopedCounts.set(scopedKey, (liveAgentScopedCounts.get(scopedKey) ?? 0) + 1);
1189
+ }
892
1190
  }
893
1191
  }
894
1192
  const coordinatorSessions = liveSessions.filter((s) => isExeSession(s));
@@ -900,7 +1198,6 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
900
1198
  return [];
901
1199
  }
902
1200
  const coordinatorSet = new Set(coordinatorSessions);
903
- const VALID_SESSION_SCOPE = /^[a-zA-Z0-9_]+$/;
904
1201
  const agentTasks = /* @__PURE__ */ new Map();
905
1202
  let deadScopeStateDirty = false;
906
1203
  let registryForResolution = [];
@@ -925,10 +1222,11 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
925
1222
  continue;
926
1223
  }
927
1224
  const rawScope = t.sessionScope;
1225
+ const rawScopeText = rawScope == null ? "" : String(rawScope);
928
1226
  const taskProject = t.projectName ?? null;
929
- if (rawScope && !VALID_SESSION_SCOPE.test(rawScope)) {
1227
+ if (rawScope && !isValidSessionScope(rawScope)) {
930
1228
  process.stderr.write(
931
- `[auto-wake] REPAIRING corrupted session_scope for ${t.agentId} task ${t.taskId}: "${rawScope.slice(0, 60)}"
1229
+ `[auto-wake] REPAIRING corrupted session_scope for ${t.agentId} task ${t.taskId}: "${rawScopeText.slice(0, 60)}"
932
1230
  `
933
1231
  );
934
1232
  const match = deps.reparentTask ? uniqueCoordinatorForProject(coordinatorSessions, getRegistryForResolution(), taskProject) : null;
@@ -1079,16 +1377,24 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
1079
1377
  }
1080
1378
  if (deadScopeStateDirty) saveAutoWakeState(_autoWakeState);
1081
1379
  const woken = [];
1380
+ let spawnAttempts = 0;
1082
1381
  for (const [key, tasks] of agentTasks) {
1083
1382
  const agentId = key.split("::")[0];
1084
1383
  const sessionScope = tasks[0].sessionScope;
1384
+ const scope = key.slice(agentId.length + 2);
1385
+ const baseKey = `${normalizeAgentForWake(agentId)}::${scope}`;
1386
+ const runningInstances = Math.max(
1387
+ liveAgentScopedCounts.get(key) ?? 0,
1388
+ liveAgentScopedCounts.get(baseKey) ?? 0
1389
+ );
1390
+ const hasRunningSession = liveAgentScopedCounts.has(key) || liveAgentScopedCounts.has(baseKey);
1085
1391
  if (!shouldAutoWake({
1086
1392
  agentId,
1087
- hasRunningSession: liveAgentScopedCounts.has(key),
1393
+ hasRunningSession,
1088
1394
  lastSpawnMs: _autoWakeLastSpawn.get(key) ?? 0,
1089
1395
  nowMs,
1090
1396
  cooldownMs: AUTO_WAKE_COOLDOWN_MS,
1091
- runningInstances: liveAgentScopedCounts.get(key) ?? 0,
1397
+ runningInstances,
1092
1398
  pendingTaskGroups: tasks.length
1093
1399
  })) {
1094
1400
  continue;
@@ -1118,24 +1424,52 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
1118
1424
  continue;
1119
1425
  }
1120
1426
  const agentGate = checkGate(key, nowMs);
1121
- if (agentGate) {
1427
+ if (agentGate && agentGate !== TERMINAL_LAUNCHER_FAILURE_GATE) {
1122
1428
  process.stderr.write(`[auto-wake] AGENT GATE BLOCKED: ${agentId} \u2014 ${agentGate}
1123
1429
  `);
1124
1430
  continue;
1125
1431
  }
1126
1432
  const topTask = tasks[0];
1433
+ if (deps.checkDispatchStatus) {
1434
+ try {
1435
+ const dispatch = deps.checkDispatchStatus(topTask.taskId);
1436
+ if (dispatch.status === "pending" && dispatch.signal?.dispatchedAt) {
1437
+ const ageMs = nowMs - new Date(dispatch.signal.dispatchedAt).getTime();
1438
+ if (ageMs < DISPATCH_PENDING_FRESH_MS) {
1439
+ process.stderr.write(
1440
+ `[auto-wake] SKIP ${agentId} task ${topTask.taskId} \u2014 create_task dispatched ${Math.max(0, Math.round(ageMs / 1e3))}s ago (base session still booting); not double-spawning
1441
+ `
1442
+ );
1443
+ continue;
1444
+ }
1445
+ }
1446
+ } catch (e) {
1447
+ process.stderr.write(
1448
+ "[auto-wake] dispatch-ack read failed (fail-open) for task " + topTask.taskId + ": " + (e instanceof Error ? e.message : String(e)) + "\n"
1449
+ );
1450
+ }
1451
+ }
1452
+ let terminalFailure = null;
1453
+ if (deps.readTerminalFailure) {
1454
+ try {
1455
+ terminalFailure = deps.readTerminalFailure(agentId, scope);
1456
+ } catch (e) {
1457
+ terminalFailure = null;
1458
+ process.stderr.write(
1459
+ "[auto-wake] terminal-failure read failed (fail-open) for task " + topTask.taskId + ": " + (e instanceof Error ? e.message : String(e)) + "\n"
1460
+ );
1461
+ }
1462
+ }
1127
1463
  const retries = _autoWakeTaskRetries.get(topTask.taskId) ?? 0;
1128
- if (retries >= AUTO_WAKE_MAX_RETRIES) {
1464
+ if (retries >= AUTO_WAKE_MAX_RETRIES || terminalFailure) {
1465
+ const reviewReason = terminalFailure ? `Terminal launcher failure (${terminalFailure.runtime}) \u2014 ${terminalFailure.reason}` : `Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 session ended after producing a deliverable. Needs manual review.`;
1466
+ const abandonReason = terminalFailure ? `Terminal launcher failure (${terminalFailure.runtime}) \u2014 ${terminalFailure.reason}${terminalFailure.detail ? ` [${terminalFailure.detail}]` : ""}` : `Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 session ended with NO deliverable. Blocked for manual re-dispatch (not a completed review).`;
1129
1467
  let routed = "needs_review";
1130
1468
  try {
1131
- routed = await routeUnrecoverableTask(
1132
- deps,
1133
- topTask.taskId,
1134
- `Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 session ended after producing a deliverable. Needs manual review.`,
1135
- `Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 session ended with NO deliverable. Blocked for manual re-dispatch (not a completed review).`
1136
- );
1469
+ routed = await routeUnrecoverableTask(deps, topTask.taskId, reviewReason, abandonReason);
1137
1470
  process.stderr.write(
1138
- `[auto-wake] ${agentId} task ${topTask.taskId} exceeded ${AUTO_WAKE_MAX_RETRIES} retries \u2014 marked ${routed}
1471
+ terminalFailure ? `[auto-wake] ${agentId} task ${topTask.taskId} \u2014 terminal launcher failure, marked ${routed}: ${terminalFailure.reason}
1472
+ ` : `[auto-wake] ${agentId} task ${topTask.taskId} exceeded ${AUTO_WAKE_MAX_RETRIES} retries \u2014 marked ${routed}
1139
1473
  `
1140
1474
  );
1141
1475
  } catch (e) {
@@ -1143,18 +1477,43 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
1143
1477
  }
1144
1478
  try {
1145
1479
  const attention = routed === "blocked" ? "needs re-dispatch (no deliverable)" : "needs review";
1480
+ const cause = terminalFailure ? `launcher reported a terminal failure: ${terminalFailure.reason}` : `auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times. Session could not be recovered.`;
1146
1481
  await writeNotification({
1147
1482
  agentId,
1148
1483
  agentRole: "employee",
1149
1484
  event: "orphan_task",
1150
1485
  project: String(topTask.sessionScope ?? ""),
1151
- summary: `\u26A0\uFE0F Task ${topTask.taskId} for ${agentId} ${attention} \u2014 auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times. Session could not be recovered.`,
1486
+ summary: `\u26A0\uFE0F Task ${topTask.taskId} for ${agentId} ${attention} \u2014 ${cause}`,
1152
1487
  sessionScope
1153
1488
  });
1154
1489
  } catch {
1155
1490
  }
1156
1491
  continue;
1157
1492
  }
1493
+ if (spawnAttempts >= AUTO_WAKE_MAX_SPAWNS_PER_TICK) {
1494
+ process.stderr.write(
1495
+ `[auto-wake] TICK CAP: ${AUTO_WAKE_MAX_SPAWNS_PER_TICK} spawns already this tick \u2014 deferring ${agentId} task ${topTask.taskId} to next tick
1496
+ `
1497
+ );
1498
+ continue;
1499
+ }
1500
+ spawnAttempts++;
1501
+ if (deps.acquireDispatchClaim) {
1502
+ let claimed = true;
1503
+ try {
1504
+ claimed = deps.acquireDispatchClaim(topTask.taskId, agentId);
1505
+ } catch {
1506
+ claimed = true;
1507
+ }
1508
+ if (!claimed) {
1509
+ spawnAttempts--;
1510
+ process.stderr.write(
1511
+ `[auto-wake] SKIP ${agentId} task ${topTask.taskId} \u2014 another dispatcher holds the spawn claim; not double-spawning
1512
+ `
1513
+ );
1514
+ continue;
1515
+ }
1516
+ }
1158
1517
  process.stderr.write(
1159
1518
  `[auto-wake] ${agentId} has ${tasks.length} pending task(s) but no session \u2014 spawning in ${sessionScope} (attempt ${retries + 1} for task ${topTask.taskId})
1160
1519
  `
@@ -1166,6 +1525,10 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
1166
1525
  `[auto-wake] Failed to spawn ${agentId}: ${result.error ?? "unknown"}
1167
1526
  `
1168
1527
  );
1528
+ try {
1529
+ deps.releaseDispatchClaim?.(topTask.taskId, agentId, "auto-wake spawn failed");
1530
+ } catch {
1531
+ }
1169
1532
  recordOrchestrationEventBestEffort({
1170
1533
  eventType: "auto_wake.spawn_failed",
1171
1534
  source: "daemon-orchestration.pollOrphanedTasks",
@@ -1200,6 +1563,10 @@ async function pollOrphanedTasks(deps, nowMs = Date.now()) {
1200
1563
  `[auto-wake] Error spawning ${agentId}: ${err instanceof Error ? err.message : String(err)}
1201
1564
  `
1202
1565
  );
1566
+ try {
1567
+ deps.releaseDispatchClaim?.(topTask.taskId, agentId, "auto-wake spawn error");
1568
+ } catch {
1569
+ }
1203
1570
  recordOrchestrationEventBestEffort({
1204
1571
  eventType: "auto_wake.spawn_error",
1205
1572
  source: "daemon-orchestration.pollOrphanedTasks",
@@ -1227,15 +1594,23 @@ var STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
1227
1594
  async function releaseStuckTasks(deps, nowMs = Date.now()) {
1228
1595
  let liveSessions;
1229
1596
  try {
1230
- liveSessions = deps.listTmuxSessions();
1597
+ liveSessions = deps.listTmuxSessionsAsync ? await deps.listTmuxSessionsAsync() : deps.listTmuxSessions();
1231
1598
  } catch (e) {
1232
1599
  process.stderr.write("[daemon-orch] list tmux sessions for stuck tasks: " + (e instanceof Error ? e.message : String(e)) + "\n");
1233
1600
  return [];
1234
1601
  }
1602
+ if (liveSessions.length === 0 && deps.tmuxSessionsVerified?.() === false) {
1603
+ process.stderr.write("[stuck-release] Skipping in_progress reclaim: tmux session list was not verified (probe failed/timeout). Preserving tasks.\n");
1604
+ return [];
1605
+ }
1606
+ const liveSessionSet = new Set(liveSessions);
1235
1607
  const liveAgents = /* @__PURE__ */ new Set();
1608
+ const liveInstanceAgents = /* @__PURE__ */ new Set();
1236
1609
  for (const session of liveSessions) {
1237
1610
  const agent = deps.parseAgentFromSession(session);
1238
1611
  if (agent) liveAgents.add(agent);
1612
+ const dash = session.indexOf("-");
1613
+ if (dash > 0) liveInstanceAgents.add(session.slice(0, dash));
1239
1614
  }
1240
1615
  for (const session of liveSessions) {
1241
1616
  if (isExeSession(session)) {
@@ -1252,13 +1627,42 @@ async function releaseStuckTasks(deps, nowMs = Date.now()) {
1252
1627
  }
1253
1628
  const released = [];
1254
1629
  const STALE_TASK_ESCALATION_MS = 60 * 60 * 1e3;
1630
+ const BUSY_DEFER_CAP_MS = LEASE_MINUTES * 60 * 1e3;
1631
+ const normalizeAgent = deps.baseAgent ?? baseAgentName;
1632
+ const paneBusyCache = /* @__PURE__ */ new Map();
1633
+ const claimedPaneBusy = async (session) => {
1634
+ const cached = paneBusyCache.get(session);
1635
+ if (cached !== void 0) return cached;
1636
+ let busy = false;
1637
+ try {
1638
+ busy = deps.isClaimedPaneBusy ? await deps.isClaimedPaneBusy(session) : false;
1639
+ } catch {
1640
+ busy = false;
1641
+ }
1642
+ paneBusyCache.set(session, busy);
1643
+ return busy;
1644
+ };
1255
1645
  for (const t of tasks) {
1256
1646
  const updatedMs = new Date(t.updatedAt).getTime();
1257
1647
  if (isNaN(updatedMs)) continue;
1258
1648
  const ageMinutes = Math.round((nowMs - updatedMs) / 6e4);
1259
- if (!liveAgents.has(t.agentId)) {
1649
+ const base = normalizeAgent(t.agentId);
1650
+ const isInstanceQualified = base !== t.agentId;
1651
+ const claimedAlive = t.claimedSession ? liveSessionSet.has(t.claimedSession) : false;
1652
+ const agentAlive = isInstanceQualified ? claimedAlive || liveInstanceAgents.has(t.agentId) : claimedAlive || liveAgents.has(t.agentId) || liveAgents.has(base) || liveInstanceAgents.has(t.agentId);
1653
+ if (!agentAlive) {
1260
1654
  const leaseExpired = isLeaseExpired(t.leaseExpiresAt, nowMs);
1261
1655
  if (!leaseExpired && nowMs - updatedMs < STUCK_TASK_GRACE_MS) continue;
1656
+ const busyDeferExhausted = leaseExpired || nowMs - updatedMs > BUSY_DEFER_CAP_MS;
1657
+ if (deps.isClaimedPaneBusy && t.claimedSession && !busyDeferExhausted) {
1658
+ if (await claimedPaneBusy(t.claimedSession)) {
1659
+ process.stderr.write(
1660
+ `[stuck-release] Task ${t.taskId} (${t.agentId}) \u2014 claimed pane ${t.claimedSession} is BUSY; deferring reclaim (bounded by lease/${Math.round(BUSY_DEFER_CAP_MS / 6e4)}m)
1661
+ `
1662
+ );
1663
+ continue;
1664
+ }
1665
+ }
1262
1666
  const reviewReason = leaseExpired ? `Auto-closed: agent "${t.agentId}" task lease expired (${t.leaseExpiresAt}); deliverable present. Needs manual review.` : `Auto-closed: agent "${t.agentId}" session ended after producing a deliverable (in_progress for ${ageMinutes}m, no live session). Needs manual review.`;
1263
1667
  const abandonReason = leaseExpired ? `Abandoned: agent "${t.agentId}" task lease expired (${t.leaseExpiresAt}) with NO deliverable. Blocked for manual re-dispatch.` : `Abandoned: agent "${t.agentId}" session ended with NO deliverable (in_progress for ${ageMinutes}m, no live session). Blocked for manual re-dispatch.`;
1264
1668
  try {
@@ -1312,15 +1716,51 @@ async function releaseStuckTasks(deps, nowMs = Date.now()) {
1312
1716
  return released;
1313
1717
  }
1314
1718
  function createStuckTaskRealDeps(getClient) {
1719
+ let lastTmuxListVerified = false;
1720
+ const listSync = () => {
1721
+ try {
1722
+ const socket = process.env.EXE_TMUX_SOCKET;
1723
+ const socketArgs = socket ? ["-L", socket] : [];
1724
+ const out = execFileSync("tmux", [...socketArgs, "list-sessions", "-F", "#{session_name}"], {
1725
+ encoding: "utf8",
1726
+ stdio: ["pipe", "pipe", "pipe"],
1727
+ timeout: 3e3
1728
+ });
1729
+ lastTmuxListVerified = true;
1730
+ return out.trim().split("\n").filter(Boolean);
1731
+ } catch {
1732
+ lastTmuxListVerified = false;
1733
+ return [];
1734
+ }
1735
+ };
1315
1736
  return {
1316
- listTmuxSessions: () => listTmuxSessions(),
1737
+ // Retained as a fallback; releaseStuckTasks prefers listTmuxSessionsAsync.
1738
+ listTmuxSessions: listSync,
1739
+ // Blocker 6: async list-sessions so the stuck-release timer never blocks the
1740
+ // event loop on a sync execFileSync("tmux").
1741
+ listTmuxSessionsAsync: async () => {
1742
+ try {
1743
+ const socket = process.env.EXE_TMUX_SOCKET;
1744
+ const socketArgs = socket ? ["-L", socket] : [];
1745
+ const { stdout } = await execFileAsync("tmux", [...socketArgs, "list-sessions", "-F", "#{session_name}"], {
1746
+ encoding: "utf8",
1747
+ timeout: 3e3
1748
+ });
1749
+ lastTmuxListVerified = true;
1750
+ return (stdout ?? "").trim().split("\n").filter(Boolean);
1751
+ } catch {
1752
+ lastTmuxListVerified = false;
1753
+ return [];
1754
+ }
1755
+ },
1756
+ tmuxSessionsVerified: () => lastTmuxListVerified,
1317
1757
  queryInProgressTasks: async () => {
1318
1758
  const { loadDeviceId } = await import("./lib/license.js");
1319
1759
  const deviceId = loadDeviceId();
1320
1760
  const client = getClient();
1321
1761
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1322
1762
  const rows = await client.execute({
1323
- sql: `SELECT id, assigned_to, session_scope, updated_at, lease_expires_at
1763
+ sql: `SELECT id, assigned_to, session_scope, updated_at, lease_expires_at, assigned_tmux
1324
1764
  FROM tasks
1325
1765
  WHERE status = 'in_progress'
1326
1766
  AND (device_id = ? OR device_id IS NULL OR lease_expires_at < ?)
@@ -1333,19 +1773,26 @@ function createStuckTaskRealDeps(getClient) {
1333
1773
  agentId: String(r.assigned_to),
1334
1774
  sessionScope: r.session_scope ? String(r.session_scope) : null,
1335
1775
  updatedAt: String(r.updated_at),
1336
- leaseExpiresAt: r.lease_expires_at ? String(r.lease_expires_at) : null
1776
+ leaseExpiresAt: r.lease_expires_at ? String(r.lease_expires_at) : null,
1777
+ // Blocker 1: the exact session that holds the claim.
1778
+ claimedSession: r.assigned_tmux ? String(r.assigned_tmux) : null
1337
1779
  }));
1338
1780
  },
1339
1781
  markTaskNeedsReview: async (taskId, reason) => {
1340
1782
  try {
1341
- await updateTask({ taskId, status: "needs_review", result: reason });
1783
+ await updateTask({ taskId, status: "needs_review", result: reason, preserveResultIfPresent: true });
1342
1784
  } catch {
1343
1785
  const client = getClient();
1344
1786
  await client.execute({
1345
- sql: `UPDATE tasks SET status = 'needs_review', result = ?, updated_at = ? WHERE id = ?`,
1787
+ sql: `UPDATE tasks
1788
+ SET status = 'needs_review',
1789
+ result = CASE WHEN result IS NULL OR trim(result, char(32)||char(9)||char(10)||char(13)) = '' THEN ? ELSE result END,
1790
+ updated_at = ?
1791
+ WHERE id = ?`,
1346
1792
  args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
1347
1793
  });
1348
1794
  }
1795
+ await annotateEnforcementNote(getClient, taskId, reason);
1349
1796
  },
1350
1797
  getTaskDeliverable: (taskId) => readTaskDeliverable(getClient, taskId),
1351
1798
  markTaskAbandoned: (taskId, reason) => markTaskBlockedAbandoned(getClient, taskId, reason),
@@ -1354,6 +1801,22 @@ function createStuckTaskRealDeps(getClient) {
1354
1801
  const agentPart = sessionName.split("-")[0];
1355
1802
  return baseAgentName(agentPart);
1356
1803
  },
1804
+ baseAgent: (agentId) => baseAgentName(agentId),
1805
+ isClaimedPaneBusy: async (claimedSession) => {
1806
+ if (!claimedSession) return false;
1807
+ try {
1808
+ const socket = process.env.EXE_TMUX_SOCKET;
1809
+ const socketArgs = socket ? ["-L", socket] : [];
1810
+ const { stdout } = await execFileAsync(
1811
+ "tmux",
1812
+ [...socketArgs, "capture-pane", "-t", claimedSession, "-p", "-S", "-12"],
1813
+ { encoding: "utf8", timeout: 3e3 }
1814
+ );
1815
+ return isPaneBusy(stdout ?? "");
1816
+ } catch {
1817
+ return false;
1818
+ }
1819
+ },
1357
1820
  notifyOrphan: async (taskId, agentId, sessionScope) => {
1358
1821
  await writeNotification({
1359
1822
  agentId,
@@ -1667,9 +2130,27 @@ function createAutoWakeRealDeps(getClient, projectDir, listTmuxSessionsAsync) {
1667
2130
  projectName: r.project_name ? String(r.project_name) : null
1668
2131
  }));
1669
2132
  },
2133
+ // DEFECT 1 (dup-spawn): honor create_task's fresh dispatch signal so auto-wake
2134
+ // never double-spawns a task whose base session is still booting.
2135
+ checkDispatchStatus: (taskId) => checkDispatchStatus(taskId),
2136
+ // Bug b2b1dc31: honor a launcher's terminal-failure marker (both key forms +
2137
+ // TTL + fail open) so a permanently-doomed config is routed to blocked with
2138
+ // the launcher's real reason instead of respawned into the misleading block.
2139
+ readTerminalFailure: (agentId, scope) => readTerminalFailureForAgent(agentId, scope),
2140
+ // Round-3: the real task-keyed spawn claim, so auto-wake contends on the SAME
2141
+ // atomic lock as create_task / nudge / resume — one spawner per task.
2142
+ acquireDispatchClaim: (taskId, assignedTo) => tryAcquireDispatchClaim(taskId, assignedTo),
2143
+ releaseDispatchClaim: (taskId, assignedTo, reason) => releaseDispatchClaim(taskId, assignedTo, reason),
1670
2144
  // C1: registry lookup + re-parent for dead-scope escalation.
1671
2145
  listRegisteredSessions: () => listSessions(),
1672
2146
  reparentTask: async (taskId, newSessionScope) => {
2147
+ if (!isValidSessionScope(newSessionScope)) {
2148
+ process.stderr.write(
2149
+ `[auto-wake] ERROR: refusing to re-parent task ${taskId} to corrupted scope ${JSON.stringify(String(newSessionScope).slice(0, 60))} (bug 4941abba)
2150
+ `
2151
+ );
2152
+ throw new Error(`invalid session_scope for re-parent: not a valid session token`);
2153
+ }
1673
2154
  const client = getClient();
1674
2155
  await client.execute({
1675
2156
  sql: `UPDATE tasks SET session_scope = ?, updated_at = ? WHERE id = ?`,
@@ -1694,14 +2175,19 @@ function createAutoWakeRealDeps(getClient, projectDir, listTmuxSessionsAsync) {
1694
2175
  },
1695
2176
  markTaskNeedsReview: async (taskId, reason) => {
1696
2177
  try {
1697
- await updateTask({ taskId, status: "needs_review", result: reason });
2178
+ await updateTask({ taskId, status: "needs_review", result: reason, preserveResultIfPresent: true });
1698
2179
  } catch {
1699
2180
  const client = getClient();
1700
2181
  await client.execute({
1701
- sql: `UPDATE tasks SET status = 'needs_review', result = ?, updated_at = ? WHERE id = ?`,
2182
+ sql: `UPDATE tasks
2183
+ SET status = 'needs_review',
2184
+ result = CASE WHEN result IS NULL OR trim(result, char(32)||char(9)||char(10)||char(13)) = '' THEN ? ELSE result END,
2185
+ updated_at = ?
2186
+ WHERE id = ?`,
1702
2187
  args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
1703
2188
  });
1704
2189
  }
2190
+ await annotateEnforcementNote(getClient, taskId, reason);
1705
2191
  },
1706
2192
  getTaskDeliverable: (taskId) => readTaskDeliverable(getClient, taskId),
1707
2193
  markTaskAbandoned: (taskId, reason) => markTaskBlockedAbandoned(getClient, taskId, reason),
@@ -1709,7 +2195,8 @@ function createAutoWakeRealDeps(getClient, projectDir, listTmuxSessionsAsync) {
1709
2195
  if (!sessionName.includes("-")) return null;
1710
2196
  const agentPart = sessionName.split("-")[0];
1711
2197
  return baseAgentName(agentPart);
1712
- }
2198
+ },
2199
+ baseAgent: (agentId) => baseAgentName(agentId)
1713
2200
  };
1714
2201
  }
1715
2202
  var COO_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_COO_CONTEXT_THRESHOLD_PCT) || 85;
@@ -2355,7 +2842,7 @@ async function reapOrphanedWorktrees(deps, nowMs = Date.now()) {
2355
2842
  });
2356
2843
  continue;
2357
2844
  }
2358
- if (deps.isWorktreeDirty(wt.path)) {
2845
+ if (await deps.isWorktreeDirty(wt.path)) {
2359
2846
  const ageH2 = Math.round(ageMs / 36e5);
2360
2847
  if (ageMs >= WORKTREE_DIRTY_MAX_AGE_MS && deps.archiveDirtyWorktree) {
2361
2848
  process.stderr.write(
@@ -2456,13 +2943,21 @@ async function reapOrphanedWorktrees(deps, nowMs = Date.now()) {
2456
2943
  }
2457
2944
  async function createWorktreeReaperRealDeps() {
2458
2945
  const { getPaneCwdAsync } = await import("./lib/tmux-status.js");
2459
- const { getGitRoot, isWorktreeDirty: isDirty } = await import("./worktree-GVF2I5FP.js");
2946
+ const { getGitRootAsync, isWorktreeDirtyAsync } = await import("./worktree-LL62PLQB.js");
2460
2947
  const { statSync: statSync3 } = await import("fs");
2461
2948
  const { basename: basename2 } = await import("path");
2462
2949
  const { promisify: promisify2 } = await import("util");
2463
2950
  const { execFile: execFileCb } = await import("child_process");
2464
2951
  const execFileAsync2 = promisify2(execFileCb);
2465
- const sessions = listTmuxSessions();
2952
+ let sessions = [];
2953
+ try {
2954
+ const { stdout } = await execFileAsync2("tmux", ["list-sessions", "-F", "#{session_name}"], {
2955
+ encoding: "utf-8",
2956
+ timeout: 1e4
2957
+ });
2958
+ sessions = String(stdout ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
2959
+ } catch {
2960
+ }
2466
2961
  const cwdMap = /* @__PURE__ */ new Map();
2467
2962
  const cwdPromises = sessions.map(async (s) => {
2468
2963
  try {
@@ -2472,9 +2967,15 @@ async function createWorktreeReaperRealDeps() {
2472
2967
  }
2473
2968
  });
2474
2969
  await Promise.all(cwdPromises);
2970
+ const gitRootCache = /* @__PURE__ */ new Map();
2971
+ const rootPromises = [...cwdMap.values()].map(async (cwd) => {
2972
+ if (gitRootCache.has(cwd)) return;
2973
+ const root = await getGitRootAsync(cwd);
2974
+ gitRootCache.set(cwd, root);
2975
+ });
2976
+ await Promise.all(rootPromises);
2475
2977
  const repoRoots = /* @__PURE__ */ new Set();
2476
- for (const cwd of cwdMap.values()) {
2477
- const root = getGitRoot(cwd);
2978
+ for (const root of gitRootCache.values()) {
2478
2979
  if (root) repoRoots.add(root);
2479
2980
  }
2480
2981
  const worktreeCache = /* @__PURE__ */ new Map();
@@ -2504,7 +3005,7 @@ async function createWorktreeReaperRealDeps() {
2504
3005
  listTmuxSessions: () => sessions,
2505
3006
  getPaneCwd: (s) => cwdMap.get(s),
2506
3007
  listWorktrees: (repoRoot) => worktreeCache.get(repoRoot) ?? [],
2507
- isWorktreeDirty: (wtPath) => isDirty(wtPath),
3008
+ isWorktreeDirty: (wtPath) => isWorktreeDirtyAsync(wtPath),
2508
3009
  getDirMtimeMs: (dirPath) => {
2509
3010
  try {
2510
3011
  return statSync3(dirPath).mtimeMs;
@@ -2582,7 +3083,9 @@ async function createWorktreeReaperRealDeps() {
2582
3083
  if (!dirName) return null;
2583
3084
  return baseAgentName(dirName);
2584
3085
  },
2585
- getGitRoot: (dir) => getGitRoot(dir),
3086
+ // Pure cache lookup — the async prefetch above populated gitRootCache for
3087
+ // every live session cwd, so the reaper never shells out on the event loop.
3088
+ getGitRoot: (dir) => gitRootCache.get(dir) ?? null,
2586
3089
  archiveDirtyWorktree: async (_repoRoot, wtPath, _branch, agentName) => {
2587
3090
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2588
3091
  const quarantineBranch = `quarantine/${agentName}-${timestamp}`;
@@ -2671,7 +3174,7 @@ async function gcStaleCheckpoints() {
2671
3174
  }
2672
3175
  async function pollTaskGroupBarriers() {
2673
3176
  try {
2674
- const { checkAndFireBarriers } = await import("./tasks-crud-UGK5A25L.js");
3177
+ const { checkAndFireBarriers } = await import("./tasks-crud-HBDKE6C5.js");
2675
3178
  const firedGroupIds = await checkAndFireBarriers();
2676
3179
  if (firedGroupIds.length === 0) return 0;
2677
3180
  const { getClient } = await import("./lib/database.js");
@@ -2698,10 +3201,10 @@ async function pollTaskGroupBarriers() {
2698
3201
  if (sessionScope) {
2699
3202
  try {
2700
3203
  if (isCoordinatorName(coordinator) && isExeSession(sessionScope)) {
2701
- await sendIntercomAsync(sessionScope, { force: true, reason: "completion" });
3204
+ await sendIntercomAsync(sessionScope, { force: true, reason: "completion", callerSessionScope: sessionScope });
2702
3205
  } else {
2703
3206
  const { employeeSessionName } = await import("./lib/tmux-routing.js");
2704
- await sendIntercomAsync(employeeSessionName(coordinator, sessionScope), { force: true, reason: "completion" });
3207
+ await sendIntercomAsync(employeeSessionName(coordinator, sessionScope), { force: true, reason: "completion", callerSessionScope: sessionScope });
2705
3208
  }
2706
3209
  } catch {
2707
3210
  }
@@ -2748,6 +3251,9 @@ export {
2748
3251
  shouldNudgeEmployee,
2749
3252
  shouldKillSession,
2750
3253
  classifyTtlKillReason,
3254
+ isUnreachableActiveTaskSession,
3255
+ WEDGE_RECOVERY_TICKS_REQUIRED,
3256
+ clearInputBufferArgs,
2751
3257
  shouldKillIdleSession,
2752
3258
  pollIdleEmployees,
2753
3259
  checkSessionTTL,
@@ -2766,10 +3272,14 @@ export {
2766
3272
  AUTO_WAKE_COOLDOWN_MS,
2767
3273
  AUTO_WAKE_MAX_RETRIES,
2768
3274
  DEAD_SCOPE_SKIP_THRESHOLD,
3275
+ DISPATCH_PENDING_FRESH_MS,
2769
3276
  CRASH_LOOP_WINDOW_MS,
2770
3277
  CRASH_LOOP_THRESHOLD,
3278
+ AUTO_WAKE_MAX_SPAWNS_PER_TICK,
2771
3279
  pruneAutoWakeState,
2772
3280
  _resetAutoWakeState,
3281
+ TERMINAL_LAUNCHER_FAILURE_GATE,
3282
+ readTerminalFailureForAgent,
2773
3283
  checkAutoWakeGates,
2774
3284
  shouldAutoWake,
2775
3285
  pollOrphanedTasks,