@dylanpiercey/work 0.0.201 → 0.0.203

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 (503) hide show
  1. package/README.md +30 -6
  2. package/bin/work.mjs +33 -1
  3. package/dist/_7W8iLTql.js +2 -0
  4. package/dist/{_BntJJ2eD.js → _B9FeLAO5.js} +863 -218
  5. package/dist/index.mjs +1210 -1030
  6. package/dist/public/assets/{_D9nJ4nG1.js → _-K2pkpBq.js} +1 -1
  7. package/dist/public/assets/_-K2pkpBq.js.br +0 -0
  8. package/dist/public/assets/_-O6dm4G6.js +1 -0
  9. package/dist/public/assets/_-tNsWCRW.js +1 -0
  10. package/dist/public/assets/{_BfP8hsVl.js → _3aic_W0y.js} +1 -1
  11. package/dist/public/assets/{_DFMu7qvY.js → _3ewKhZLc.js} +1 -1
  12. package/dist/public/assets/_3ewKhZLc.js.br +0 -0
  13. package/dist/public/assets/{_Dj8W5hAy.js → _3xHfzmVK.js} +1 -1
  14. package/dist/public/assets/_3xHfzmVK.js.br +0 -0
  15. package/dist/public/assets/{_B7pXsDiF.js → _8Q9P2uHp.js} +1 -1
  16. package/dist/public/assets/_8Q9P2uHp.js.br +0 -0
  17. package/dist/public/assets/{_DAVXBkYN.js → _AoYqPW6v.js} +1 -1
  18. package/dist/public/assets/_AoYqPW6v.js.br +0 -0
  19. package/dist/public/assets/{_C8hQ66UC.js → _B1q5pQla.js} +1 -1
  20. package/dist/public/assets/_B1q5pQla.js.br +0 -0
  21. package/dist/public/assets/{_CgUj7qP_.js → _B66w2fqU.js} +1 -1
  22. package/dist/public/assets/_B66w2fqU.js.br +0 -0
  23. package/dist/public/assets/{_DVy22yDU.js → _B80CP8fn.js} +1 -1
  24. package/dist/public/assets/_B80CP8fn.js.br +0 -0
  25. package/dist/public/assets/_B9FnTf4h.js +1 -0
  26. package/dist/public/assets/{_tPOATR_S.js → _B9kU6sMh.js} +1 -1
  27. package/dist/public/assets/_B9kU6sMh.js.br +0 -0
  28. package/dist/public/assets/{_CD9reWft.js → _BADK8qnN.js} +1 -1
  29. package/dist/public/assets/_BADK8qnN.js.br +0 -0
  30. package/dist/public/assets/_BD2qsrlz.js +1 -0
  31. package/dist/public/assets/{_4w6Dwjlh.js → _BDdtj9Cq.js} +1 -1
  32. package/dist/public/assets/_BDdtj9Cq.js.br +0 -0
  33. package/dist/public/assets/{_DPtSJ35G.js → _BERyw-_I.js} +1 -1
  34. package/dist/public/assets/_BERyw-_I.js.br +0 -0
  35. package/dist/public/assets/_BH5mQvfn.js +1 -0
  36. package/dist/public/assets/{_BubcBfAr.js → _BK0E9Er2.js} +1 -1
  37. package/dist/public/assets/_BK0E9Er2.js.br +0 -0
  38. package/dist/public/assets/{_oA-Vr8Cf.js → _BKkFTJtI.js} +1 -1
  39. package/dist/public/assets/_BKkFTJtI.js.br +0 -0
  40. package/dist/public/assets/_BOBHk1-a.js +1 -0
  41. package/dist/public/assets/_BOk1AEgN.js +1 -0
  42. package/dist/public/assets/{_Ba1mrOxI.js → _BZ3C3l3R.js} +1 -1
  43. package/dist/public/assets/_BZ3C3l3R.js.br +0 -0
  44. package/dist/public/assets/{_Bc_Ezk5L.js → _BaiQtGR9.js} +1 -1
  45. package/dist/public/assets/_BaiQtGR9.js.br +0 -0
  46. package/dist/public/assets/_BdUIic1U.js +1 -0
  47. package/dist/public/assets/_BdUIic1U.js.br +0 -0
  48. package/dist/public/assets/{_DPT6ZO90.js → _BeVk3dbG.js} +1 -1
  49. package/dist/public/assets/_BeVk3dbG.js.br +0 -0
  50. package/dist/public/assets/_Bedi1JZC.js +1 -0
  51. package/dist/public/assets/_BgCpq5St.js +1 -0
  52. package/dist/public/assets/{_CwXTg1fU.js → _BjXiH2oW.js} +1 -1
  53. package/dist/public/assets/_BjXiH2oW.js.br +0 -0
  54. package/dist/public/assets/{_Ckc7dIlo.js → _Bk31AYSk.js} +1 -1
  55. package/dist/public/assets/_Bk31AYSk.js.br +0 -0
  56. package/dist/public/assets/{_P1g2Hmou.js → _BmZVM8Ww.js} +1 -1
  57. package/dist/public/assets/_BmZVM8Ww.js.br +0 -0
  58. package/dist/public/assets/_BqgMkZ3f.js +1 -0
  59. package/dist/public/assets/{_oBcpaEKd.js → _Brbaz_V-.js} +1 -1
  60. package/dist/public/assets/_Brbaz_V-.js.br +0 -0
  61. package/dist/public/assets/_Bs9HVA9T.js +1 -0
  62. package/dist/public/assets/{_B4t3OJDi.js → _Bwo88m9W.js} +1 -1
  63. package/dist/public/assets/_Bwo88m9W.js.br +0 -0
  64. package/dist/public/assets/{_Bg1dyEQN.js → _Bx5kmADX.js} +1 -1
  65. package/dist/public/assets/_Bx5kmADX.js.br +0 -0
  66. package/dist/public/assets/{_hI9Uq04u.js → _BzU-RPev.js} +1 -1
  67. package/dist/public/assets/_BzVRtEsz.js +1 -0
  68. package/dist/public/assets/{_BACT_fWM.js → _C0K-WRJD.js} +1 -1
  69. package/dist/public/assets/_C0K-WRJD.js.br +0 -0
  70. package/dist/public/assets/{_DMb-aGhT.js → _C3M50_zF.js} +1 -1
  71. package/dist/public/assets/_C3M50_zF.js.br +0 -0
  72. package/dist/public/assets/_C5l9z0N5.js +1 -0
  73. package/dist/public/assets/{_BLMA7nPf.js → _CArazMIS.js} +1 -1
  74. package/dist/public/assets/_CArazMIS.js.br +0 -0
  75. package/dist/public/assets/{_BWXbXLjv.js → _CBZB5Yxv.js} +1 -1
  76. package/dist/public/assets/_CBZB5Yxv.js.br +0 -0
  77. package/dist/public/assets/{_og2j-mr5.js → _CCDOu5-k.js} +1 -1
  78. package/dist/public/assets/_CCDOu5-k.js.br +0 -0
  79. package/dist/public/assets/_CCnHpcK_.js +1 -0
  80. package/dist/public/assets/_CDMh_Czk.js +1 -0
  81. package/dist/public/assets/_CE_CwnMm.js +1 -0
  82. package/dist/public/assets/_CG4M2EXP.js +1 -0
  83. package/dist/public/assets/{_DxS6OhEn.js → _CHEpaVmI.js} +1 -1
  84. package/dist/public/assets/_CHEpaVmI.js.br +0 -0
  85. package/dist/public/assets/_CHxSYsqp.js +1 -0
  86. package/dist/public/assets/_CJMlU8h-.js +1 -0
  87. package/dist/public/assets/{_DKj8yOqM.js → _CJoG6S3-.js} +1 -1
  88. package/dist/public/assets/_CJoG6S3-.js.br +0 -0
  89. package/dist/public/assets/{_TX0w8pNw.js → _CKO6VeWe.js} +1 -1
  90. package/dist/public/assets/_CKO6VeWe.js.br +0 -0
  91. package/dist/public/assets/_COzp254y.js +1 -0
  92. package/dist/public/assets/{_Cgv_vb21.js → _CPH47jXm.js} +1 -1
  93. package/dist/public/assets/_CPH47jXm.js.br +0 -0
  94. package/dist/public/assets/{_vSKAFeTh.js → _CRZGR7so.js} +1 -1
  95. package/dist/public/assets/_CRZGR7so.js.br +0 -0
  96. package/dist/public/assets/{_DQGEdzOn.js → _CSmKiIv8.js} +1 -1
  97. package/dist/public/assets/{_DTJl30hJ.js → _CTPBS0LC.js} +1 -1
  98. package/dist/public/assets/_CTPBS0LC.js.br +0 -0
  99. package/dist/public/assets/{_BsM6FL0G.js → _CThA4lbu.js} +1 -1
  100. package/dist/public/assets/_CThA4lbu.js.br +0 -0
  101. package/dist/public/assets/{_DqvnbtAR.js → _CXBw8HMs.js} +1 -1
  102. package/dist/public/assets/_CXBw8HMs.js.br +0 -0
  103. package/dist/public/assets/_CXzJ6bil.js +1 -0
  104. package/dist/public/assets/{_BVgYEt31.js → _CYUNskmo.js} +1 -1
  105. package/dist/public/assets/_CYUNskmo.js.br +0 -0
  106. package/dist/public/assets/{_C4rxuku1.js → _C_Lr0eFb.js} +1 -1
  107. package/dist/public/assets/_C_Lr0eFb.js.br +0 -0
  108. package/dist/public/assets/_CbnW4odW.js +1 -0
  109. package/dist/public/assets/_Cfuem2T_.js +1 -0
  110. package/dist/public/assets/{_CEKYlz0a.js → _Cg-uvNvy.js} +1 -1
  111. package/dist/public/assets/_Cg-uvNvy.js.br +0 -0
  112. package/dist/public/assets/{_C6u-Srqk.js → _CgLkQJNG.js} +1 -1
  113. package/dist/public/assets/_CgLkQJNG.js.br +0 -0
  114. package/dist/public/assets/{_DZIuSdjQ.js → _CkwMzG0u.js} +1 -1
  115. package/dist/public/assets/_CkwMzG0u.js.br +0 -0
  116. package/dist/public/assets/{_B2k34w2U.js → _Cl7KlO12.js} +1 -1
  117. package/dist/public/assets/_Cl7KlO12.js.br +0 -0
  118. package/dist/public/assets/_ClSzF1-k.js +1 -0
  119. package/dist/public/assets/{_CgP5xz-S.js → _CljaR6t8.js} +1 -1
  120. package/dist/public/assets/_CljaR6t8.js.br +0 -0
  121. package/dist/public/assets/{_DtY8_Qtl.js → _CoZYY0F5.js} +1 -1
  122. package/dist/public/assets/_CoZYY0F5.js.br +0 -0
  123. package/dist/public/assets/{_BaxiLRDq.js → _CpposQLo.js} +1 -1
  124. package/dist/public/assets/_CpposQLo.js.br +0 -0
  125. package/dist/public/assets/{_B9vNWNy5.js → _CrONT8um.js} +1 -1
  126. package/dist/public/assets/_CrONT8um.js.br +0 -0
  127. package/dist/public/assets/_Cu1guAvn.js +1 -0
  128. package/dist/public/assets/{_Dem8VmJt.js → _CygSeSG1.js} +1 -1
  129. package/dist/public/assets/_CygSeSG1.js.br +0 -0
  130. package/dist/public/assets/_Czf21qbL.js +1 -0
  131. package/dist/public/assets/_D-2kyUyG.js +9 -0
  132. package/dist/public/assets/_D-2kyUyG.js.br +0 -0
  133. package/dist/public/assets/{_CNG8WwTi.js → _D07_FXWo.js} +1 -1
  134. package/dist/public/assets/_D3Je7YVw.js +1 -0
  135. package/dist/public/assets/_D4xPo0HZ.js +1 -0
  136. package/dist/public/assets/_D605QHvK.js +1 -0
  137. package/dist/public/assets/{_BSyVWzez.js → _D8WMh6gu.js} +1 -1
  138. package/dist/public/assets/_D8WMh6gu.js.br +0 -0
  139. package/dist/public/assets/{_2jMrHLFD.js → _D8ve50C6.js} +1 -1
  140. package/dist/public/assets/_D8ve50C6.js.br +0 -0
  141. package/dist/public/assets/{_BIY6vLu9.js → _D9b8PlSh.js} +17 -17
  142. package/dist/public/assets/_D9b8PlSh.js.br +0 -0
  143. package/dist/public/assets/{_BX4xzjKF.js → _D9cUlE3a.js} +1 -1
  144. package/dist/public/assets/_D9cUlE3a.js.br +0 -0
  145. package/dist/public/assets/{_Clv6aVv7.js → _DCg8UEc3.js} +1 -1
  146. package/dist/public/assets/_DCg8UEc3.js.br +0 -0
  147. package/dist/public/assets/{_BGPJXvZs.js → _DEf8mUHj.js} +1 -1
  148. package/dist/public/assets/_DEf8mUHj.js.br +0 -0
  149. package/dist/public/assets/{_B-Y1YuyQ.js → _DFjp3R_E.js} +1 -1
  150. package/dist/public/assets/_DFjp3R_E.js.br +0 -0
  151. package/dist/public/assets/_DGLS_XIM.js +1 -0
  152. package/dist/public/assets/{_DvW_OT8c.js → _DH7j4yfd.js} +1 -1
  153. package/dist/public/assets/_DH7j4yfd.js.br +0 -0
  154. package/dist/public/assets/{_CSsQyHnz.js → _DITz3wmu.js} +1 -1
  155. package/dist/public/assets/_DITz3wmu.js.br +0 -0
  156. package/dist/public/assets/{_B23autIT.js → _DIbtkkt7.js} +1 -1
  157. package/dist/public/assets/_DIhhsT9E.js +1 -0
  158. package/dist/public/assets/_DJ16po4M.js +1 -0
  159. package/dist/public/assets/_DKPOXVYN.js +1 -0
  160. package/dist/public/assets/_DKPOXVYN.js.br +0 -0
  161. package/dist/public/assets/{_BqoWO0Lt.js → _DMVwpwke.js} +1 -1
  162. package/dist/public/assets/_DMVwpwke.js.br +0 -0
  163. package/dist/public/assets/_DMWaKaIG.js +1 -0
  164. package/dist/public/assets/_DOXCLF1P.js +1 -0
  165. package/dist/public/assets/_DQYtsGep.js +1 -0
  166. package/dist/public/assets/_DRNYHvLK.js +86 -0
  167. package/dist/public/assets/_DRNYHvLK.js.br +0 -0
  168. package/dist/public/assets/{_BFUZuxfB.js → _DSn_einI.js} +1 -1
  169. package/dist/public/assets/_DSn_einI.js.br +0 -0
  170. package/dist/public/assets/{_fkTQCq_W.js → _DVs9_2EC.js} +1 -1
  171. package/dist/public/assets/_DVs9_2EC.js.br +0 -0
  172. package/dist/public/assets/{_CLSP-PhQ.js → _DY6PUWO1.js} +1 -1
  173. package/dist/public/assets/_DY6PUWO1.js.br +0 -0
  174. package/dist/public/assets/_DYq5I1a_.js +1 -0
  175. package/dist/public/assets/{_DiBlTRgB.js → _DZaWjeov.js} +1 -1
  176. package/dist/public/assets/_DZaWjeov.js.br +0 -0
  177. package/dist/public/assets/{_DyJiy6q7.js → _D_zYO-l8.js} +1 -1
  178. package/dist/public/assets/_D_zYO-l8.js.br +0 -0
  179. package/dist/public/assets/_Db15f2uj.js +1 -0
  180. package/dist/public/assets/_DcyKrmeq.js +1 -0
  181. package/dist/public/assets/{_1ROJt5O3.js → _DdHifLBM.js} +1 -1
  182. package/dist/public/assets/_DdHifLBM.js.br +0 -0
  183. package/dist/public/assets/{_Ci0fM4lS.js → _DdfXpdn_.js} +1 -1
  184. package/dist/public/assets/_DdfXpdn_.js.br +0 -0
  185. package/dist/public/assets/_DevPwgsp.js +1 -0
  186. package/dist/public/assets/_DgwVeEsD.js +6 -0
  187. package/dist/public/assets/_DgwVeEsD.js.br +0 -0
  188. package/dist/public/assets/{_-dqzO7S_.js → _DhHO6Tuz.js} +1 -1
  189. package/dist/public/assets/_DhHO6Tuz.js.br +0 -0
  190. package/dist/public/assets/_DkSuDAhs.js +1 -0
  191. package/dist/public/assets/{_8LHYZ0G8.js → _DmGawoV0.js} +1 -1
  192. package/dist/public/assets/_DmGawoV0.js.br +0 -0
  193. package/dist/public/assets/{_DaSvMIzg.js → _DnlCrUDV.js} +2 -2
  194. package/dist/public/assets/_DnlCrUDV.js.br +0 -0
  195. package/dist/public/assets/{_CT9R7V5L.js → _Do95KzEK.js} +1 -1
  196. package/dist/public/assets/_Do95KzEK.js.br +0 -0
  197. package/dist/public/assets/_Dob_Fmbm.js +1 -0
  198. package/dist/public/assets/{_B0m_B4cy.js → _Dq-Jad5X.js} +1 -1
  199. package/dist/public/assets/_Dq-Jad5X.js.br +0 -0
  200. package/dist/public/assets/{_rd-8foIp.js → _DqAamLSj.js} +1 -1
  201. package/dist/public/assets/_DqAamLSj.js.br +0 -0
  202. package/dist/public/assets/_DsT0GWgJ.js +1 -0
  203. package/dist/public/assets/_Ds_ya4mr.js +1 -0
  204. package/dist/public/assets/{_CcKsIH5h.js → _Dsv1qAjm.js} +1 -1
  205. package/dist/public/assets/_Dsv1qAjm.js.br +0 -0
  206. package/dist/public/assets/_DuR6ueoD.js +1 -0
  207. package/dist/public/assets/_Dy4QBSz2.js +1 -0
  208. package/dist/public/assets/_GHV_3jJp.js +1 -0
  209. package/dist/public/assets/_GN2ORfhz.js +1 -0
  210. package/dist/public/assets/{_MatnNE0E.js → _GnLkrWZM.js} +1 -1
  211. package/dist/public/assets/_GnLkrWZM.js.br +0 -0
  212. package/dist/public/assets/_IsgXwMDs.js +1 -0
  213. package/dist/public/assets/{_PwtpBaMf.js → _ItxI9nk3.js} +1 -1
  214. package/dist/public/assets/_ItxI9nk3.js.br +0 -0
  215. package/dist/public/assets/_IzVuLVN1.js +1 -0
  216. package/dist/public/assets/{_B_DIXi0k.js → _JekmkJTS.js} +1 -1
  217. package/dist/public/assets/_JekmkJTS.js.br +0 -0
  218. package/dist/public/assets/_KiUN85cI.js +1 -0
  219. package/dist/public/assets/_NeT1bfJI.js +1 -0
  220. package/dist/public/assets/_QWPZ0V06.js +1 -0
  221. package/dist/public/assets/_Qs4Lwkwv.js +1 -0
  222. package/dist/public/assets/_Qs4Lwkwv.js.br +0 -0
  223. package/dist/public/assets/_RNDgNS1c.js +1 -0
  224. package/dist/public/assets/_RVKQdbGj.js +1 -0
  225. package/dist/public/assets/{_DeqN8IUm.js → _RwgpF7LC.js} +1 -1
  226. package/dist/public/assets/_RwgpF7LC.js.br +0 -0
  227. package/dist/public/assets/{_Dr6eQaDf.js → _S99TppFt.js} +1 -1
  228. package/dist/public/assets/_S99TppFt.js.br +0 -0
  229. package/dist/public/assets/_S_seG_by.js +1 -0
  230. package/dist/public/assets/{_Btm6Fw4r.js → _TcqYUNNo.js} +1 -1
  231. package/dist/public/assets/_TcqYUNNo.js.br +0 -0
  232. package/dist/public/assets/_WkU7AijT.js +1 -0
  233. package/dist/public/assets/__bV-79mJ.js +1 -0
  234. package/dist/public/assets/{_C2vaJoKP.js → _e-EoIF14.js} +1 -1
  235. package/dist/public/assets/_e-EoIF14.js.br +0 -0
  236. package/dist/public/assets/{_C7NJXs7I.js → _eutX35zw.js} +1 -1
  237. package/dist/public/assets/_eutX35zw.js.br +0 -0
  238. package/dist/public/assets/{_DLQCOYuD.js → _gkUCl--6.js} +1 -1
  239. package/dist/public/assets/_gkUCl--6.js.br +0 -0
  240. package/dist/public/assets/{_DqzA5hgG.js → _kmXANkGr.js} +1 -1
  241. package/dist/public/assets/_kmXANkGr.js.br +0 -0
  242. package/dist/public/assets/{_0C7z67Qm.js → _kqic6B_q.js} +1 -1
  243. package/dist/public/assets/_kqic6B_q.js.br +0 -0
  244. package/dist/public/assets/_oXuFhEmo.js +1 -0
  245. package/dist/public/assets/{_Opj7Hn0u.js → _q4Q8wf1-.js} +1 -1
  246. package/dist/public/assets/_q4Q8wf1-.js.br +0 -0
  247. package/dist/public/assets/_rHKeW7zh.js +1 -0
  248. package/dist/public/assets/_ryYtmLmM.js +1 -0
  249. package/dist/public/assets/_tHa-ZHkQ.js +1 -0
  250. package/dist/public/assets/{_RA0df7Za.js → _up7lLRgA.js} +1 -1
  251. package/dist/public/assets/{_Be9c3c5k.js → _vunrMsGK.js} +1 -1
  252. package/dist/public/assets/_vunrMsGK.js.br +0 -0
  253. package/dist/public/assets/{_Crcj75Rn.js → _wjT9tLjb.js} +1 -1
  254. package/dist/public/assets/_wjT9tLjb.js.br +0 -0
  255. package/dist/public/assets/_xw7rRdJa.js +1 -0
  256. package/dist/public/assets/_y1uwlPvR.js +1 -0
  257. package/dist/public/assets/{_BwugFc4p.js → _z3bkSqoV.js} +1 -1
  258. package/dist/public/assets/_z3bkSqoV.js.br +0 -0
  259. package/dist/public/assets/{app-page-CyU0ft_x.css → app-page-a-M15PMY.css} +1 -1
  260. package/dist/public/assets/app-page-a-M15PMY.css.br +0 -0
  261. package/dist/public/assets/{command-palette-BgUVS5fJ.css → command-palette-DnTWF_r2.css} +1 -1
  262. package/dist/public/assets/command-palette-DnTWF_r2.css.br +0 -0
  263. package/dist/public/assets/files-view-C_Ak1o_u.css +1 -0
  264. package/dist/public/assets/files-view-C_Ak1o_u.css.br +0 -0
  265. package/dist/public/assets/files-view-D29gwNTU.js +2 -0
  266. package/dist/public/assets/{icon-search-Cslr912D.css → icon-search-CiIAr7NA.css} +1 -1
  267. package/dist/public/assets/icon-search-CiIAr7NA.css.br +0 -0
  268. package/dist/public/assets/manage-workspace-FNt9673q.css +1 -0
  269. package/dist/public/assets/manage-workspace-LPUu3-Rp.js +2 -0
  270. package/dist/public/assets/{modal-view-C0q2p23m.css → modal-view-B53laKnF.css} +1 -1
  271. package/dist/public/assets/modal-view-B53laKnF.css.br +0 -0
  272. package/dist/public/assets/new-pJJ6ZABY.js +1 -0
  273. package/dist/public/assets/new-workspace-Bj3jwBnp.css +1 -0
  274. package/dist/public/assets/new-workspace-Bj3jwBnp.css.br +0 -0
  275. package/dist/public/assets/new-workspace-Cukm9rzJ.js +2 -0
  276. package/dist/public/assets/port-chip-B2VAUY_i.css +1 -0
  277. package/dist/public/assets/port-chip-B2VAUY_i.css.br +0 -0
  278. package/dist/public/assets/prompt-dialog-CtmcwmFw.css +1 -0
  279. package/dist/public/assets/prompt-dialog-CtmcwmFw.css.br +0 -0
  280. package/dist/public/assets/quick-open-CuR4WzpL.css +1 -0
  281. package/dist/public/assets/quick-open-CuR4WzpL.css.br +0 -0
  282. package/dist/public/assets/repo-picker-DFTPYkNQ.css +1 -0
  283. package/dist/public/assets/repo-picker-DFTPYkNQ.css.br +2 -0
  284. package/dist/public/assets/review-view-B6bsrcls.css +1 -0
  285. package/dist/public/assets/review-view-B6bsrcls.css.br +0 -0
  286. package/dist/public/assets/review-view-BNyQ646Y.js +2 -0
  287. package/dist/public/assets/section-aiv_79hg.css +1 -0
  288. package/dist/public/assets/section-aiv_79hg.css.br +0 -0
  289. package/dist/public/assets/settings-pJJ6ZABY.js +1 -0
  290. package/dist/public/assets/settings-page-DAwtsEQ9.css +1 -0
  291. package/dist/public/assets/settings-page-DAwtsEQ9.css.br +0 -0
  292. package/dist/public/assets/settings-page-DsbRNeSq.js +2 -0
  293. package/dist/public/assets/sso-auth-JILClN0h.css +1 -0
  294. package/dist/public/assets/sso-auth-JILClN0h.css.br +0 -0
  295. package/dist/public/assets/w-pJJ6ZABY.js +1 -0
  296. package/dist/public/assets/w._-C4FeaeOO.js +1 -0
  297. package/dist/public/assets/w._-DChqn_ll.js +1 -0
  298. package/dist/public/assets/w._-pJJ6ZABY.js +1 -0
  299. package/dist/public/badge-96.png +0 -0
  300. package/dist/public/sw.js +51 -7
  301. package/dist/public/sw.js.br +0 -0
  302. package/lib/review/markdown.mjs +51 -34
  303. package/lib/setup/agents.json +4 -1
  304. package/package.json +1 -1
  305. package/scripts/migrate.mjs +27 -0
  306. package/dist/_V3gojze9.js +0 -2
  307. package/dist/public/assets/_-dqzO7S_.js.br +0 -0
  308. package/dist/public/assets/_0C7z67Qm.js.br +0 -9
  309. package/dist/public/assets/_1ROJt5O3.js.br +0 -0
  310. package/dist/public/assets/_1XZR3z1L.js +0 -1
  311. package/dist/public/assets/_2jMrHLFD.js.br +0 -0
  312. package/dist/public/assets/_4w6Dwjlh.js.br +0 -0
  313. package/dist/public/assets/_7qQCv119.js +0 -1
  314. package/dist/public/assets/_8LHYZ0G8.js.br +0 -0
  315. package/dist/public/assets/_9lyEjkvB.js +0 -1
  316. package/dist/public/assets/_ATtSGU4R.js +0 -1
  317. package/dist/public/assets/_B-Y1YuyQ.js.br +0 -0
  318. package/dist/public/assets/_B0m_B4cy.js.br +0 -0
  319. package/dist/public/assets/_B2k34w2U.js.br +0 -0
  320. package/dist/public/assets/_B2r0KxkM.js +0 -1
  321. package/dist/public/assets/_B4t3OJDi.js.br +0 -0
  322. package/dist/public/assets/_B6XD8a0B.js +0 -1
  323. package/dist/public/assets/_B6XD8a0B.js.br +0 -0
  324. package/dist/public/assets/_B7pXsDiF.js.br +0 -0
  325. package/dist/public/assets/_B9vNWNy5.js.br +0 -0
  326. package/dist/public/assets/_BACT_fWM.js.br +0 -0
  327. package/dist/public/assets/_BD9DXti2.js +0 -1
  328. package/dist/public/assets/_BFUZuxfB.js.br +0 -0
  329. package/dist/public/assets/_BGPJXvZs.js.br +0 -0
  330. package/dist/public/assets/_BHPRD7wn.js +0 -1
  331. package/dist/public/assets/_BIY6vLu9.js.br +0 -0
  332. package/dist/public/assets/_BIqT1F8o.js +0 -1
  333. package/dist/public/assets/_BKPR7Exm.js +0 -1
  334. package/dist/public/assets/_BLMA7nPf.js.br +0 -0
  335. package/dist/public/assets/_BMG0X0XF.js +0 -1
  336. package/dist/public/assets/_BPX5CxjX.js +0 -1
  337. package/dist/public/assets/_BS9oCUE0.js +0 -1
  338. package/dist/public/assets/_BSSpkgu8.js +0 -1
  339. package/dist/public/assets/_BSyVWzez.js.br +0 -0
  340. package/dist/public/assets/_BVelHN7q.js +0 -1
  341. package/dist/public/assets/_BVgYEt31.js.br +0 -0
  342. package/dist/public/assets/_BWXbXLjv.js.br +0 -0
  343. package/dist/public/assets/_BX4xzjKF.js.br +0 -0
  344. package/dist/public/assets/_BXdWjkI5.js +0 -1
  345. package/dist/public/assets/_B_DIXi0k.js.br +0 -0
  346. package/dist/public/assets/_Ba1mrOxI.js.br +0 -0
  347. package/dist/public/assets/_BaxiLRDq.js.br +0 -0
  348. package/dist/public/assets/_Bc_Ezk5L.js.br +0 -0
  349. package/dist/public/assets/_Be9c3c5k.js.br +0 -0
  350. package/dist/public/assets/_BeNkN5H0.js +0 -1
  351. package/dist/public/assets/_Bg1dyEQN.js.br +0 -0
  352. package/dist/public/assets/_Bi9nBUKQ.js +0 -1
  353. package/dist/public/assets/_BmB3qu5w.js +0 -1
  354. package/dist/public/assets/_BpyT0ApF.js +0 -1
  355. package/dist/public/assets/_BqA4Q1j3.js +0 -1
  356. package/dist/public/assets/_BqoWO0Lt.js.br +0 -0
  357. package/dist/public/assets/_BsM6FL0G.js.br +0 -0
  358. package/dist/public/assets/_Btm6Fw4r.js.br +0 -0
  359. package/dist/public/assets/_BubcBfAr.js.br +0 -0
  360. package/dist/public/assets/_BvKUsWT1.js +0 -1
  361. package/dist/public/assets/_BwugFc4p.js.br +0 -0
  362. package/dist/public/assets/_BxlXGcbO.js +0 -1
  363. package/dist/public/assets/_Byer70eB.js +0 -1
  364. package/dist/public/assets/_C0udqXoV.js +0 -1
  365. package/dist/public/assets/_C1a168fm.js +0 -1
  366. package/dist/public/assets/_C1x7pcqW.js +0 -1
  367. package/dist/public/assets/_C2vaJoKP.js.br +0 -0
  368. package/dist/public/assets/_C3uAjjk3.js +0 -1
  369. package/dist/public/assets/_C4rxuku1.js.br +0 -0
  370. package/dist/public/assets/_C6KQjdMM.js +0 -1
  371. package/dist/public/assets/_C6u-Srqk.js.br +0 -0
  372. package/dist/public/assets/_C7NJXs7I.js.br +0 -0
  373. package/dist/public/assets/_C8hQ66UC.js.br +0 -0
  374. package/dist/public/assets/_CAB7vlnJ.js +0 -1
  375. package/dist/public/assets/_CASEbE0K.js +0 -86
  376. package/dist/public/assets/_CASEbE0K.js.br +0 -0
  377. package/dist/public/assets/_CD9reWft.js.br +0 -0
  378. package/dist/public/assets/_CEKYlz0a.js.br +0 -0
  379. package/dist/public/assets/_CEj4Mwc1.js +0 -1
  380. package/dist/public/assets/_CI6Hg_A9.js +0 -1
  381. package/dist/public/assets/_CIFyxofO.js +0 -1
  382. package/dist/public/assets/_CKxJlwEy.js +0 -1
  383. package/dist/public/assets/_CLSP-PhQ.js.br +0 -0
  384. package/dist/public/assets/_CPzscDEb.js +0 -1
  385. package/dist/public/assets/_CQLI-En1.js +0 -1
  386. package/dist/public/assets/_CSsQyHnz.js.br +0 -0
  387. package/dist/public/assets/_CT9R7V5L.js.br +0 -0
  388. package/dist/public/assets/_CYk469iO.js +0 -1
  389. package/dist/public/assets/_CZ5Fi642.js +0 -1
  390. package/dist/public/assets/_CZ5Fi642.js.br +0 -0
  391. package/dist/public/assets/_C_5HKb42.js +0 -9
  392. package/dist/public/assets/_C_5HKb42.js.br +0 -0
  393. package/dist/public/assets/_CcKsIH5h.js.br +0 -0
  394. package/dist/public/assets/_Ce4nsqo2.js +0 -6
  395. package/dist/public/assets/_Ce4nsqo2.js.br +0 -0
  396. package/dist/public/assets/_Cf4gqSQz.js +0 -1
  397. package/dist/public/assets/_CgP5xz-S.js.br +0 -0
  398. package/dist/public/assets/_CgUj7qP_.js.br +0 -0
  399. package/dist/public/assets/_Cgv_vb21.js.br +0 -0
  400. package/dist/public/assets/_Ci0fM4lS.js.br +0 -0
  401. package/dist/public/assets/_Ckc7dIlo.js.br +0 -0
  402. package/dist/public/assets/_Clv6aVv7.js.br +0 -0
  403. package/dist/public/assets/_CqkTkgJB.js +0 -1
  404. package/dist/public/assets/_Crcj75Rn.js.br +0 -0
  405. package/dist/public/assets/_CuGgpK2u.js +0 -1
  406. package/dist/public/assets/_CuVmBZbd.js +0 -1
  407. package/dist/public/assets/_Cw8c7j2h.js +0 -1
  408. package/dist/public/assets/_CwXTg1fU.js.br +0 -0
  409. package/dist/public/assets/_D6fhoYuY.js +0 -1
  410. package/dist/public/assets/_D9nJ4nG1.js.br +0 -0
  411. package/dist/public/assets/_DAVXBkYN.js.br +0 -0
  412. package/dist/public/assets/_DDQhJR3T.js +0 -1
  413. package/dist/public/assets/_DFMu7qvY.js.br +0 -0
  414. package/dist/public/assets/_DKj8yOqM.js.br +0 -0
  415. package/dist/public/assets/_DLQCOYuD.js.br +0 -0
  416. package/dist/public/assets/_DMb-aGhT.js.br +0 -0
  417. package/dist/public/assets/_DOghH8Tl.js +0 -1
  418. package/dist/public/assets/_DPT6ZO90.js.br +0 -0
  419. package/dist/public/assets/_DPtSJ35G.js.br +0 -0
  420. package/dist/public/assets/_DTJl30hJ.js.br +0 -0
  421. package/dist/public/assets/_DTcQCcgk.js +0 -1
  422. package/dist/public/assets/_DVy22yDU.js.br +0 -0
  423. package/dist/public/assets/_DWyu89Hi.js +0 -1
  424. package/dist/public/assets/_DZIuSdjQ.js.br +0 -0
  425. package/dist/public/assets/_DaSvMIzg.js.br +0 -0
  426. package/dist/public/assets/_DbZpXQPs.js +0 -1
  427. package/dist/public/assets/_Dd3TMJAY.js +0 -1
  428. package/dist/public/assets/_Dem8VmJt.js.br +0 -0
  429. package/dist/public/assets/_DeqN8IUm.js.br +0 -0
  430. package/dist/public/assets/_DfrEESyG.js +0 -1
  431. package/dist/public/assets/_DiBlTRgB.js.br +0 -0
  432. package/dist/public/assets/_Dj8W5hAy.js.br +0 -0
  433. package/dist/public/assets/_DmehuAAq.js +0 -1
  434. package/dist/public/assets/_DmehuAAq.js.br +0 -0
  435. package/dist/public/assets/_DoTqr-WV.js +0 -1
  436. package/dist/public/assets/_DqvnbtAR.js.br +0 -0
  437. package/dist/public/assets/_DqzA5hgG.js.br +0 -0
  438. package/dist/public/assets/_Dr6eQaDf.js.br +0 -0
  439. package/dist/public/assets/_DtY8_Qtl.js.br +0 -0
  440. package/dist/public/assets/_Dv8p2zgH.js +0 -1
  441. package/dist/public/assets/_DvW_OT8c.js.br +0 -0
  442. package/dist/public/assets/_DxS6OhEn.js.br +0 -0
  443. package/dist/public/assets/_DyJiy6q7.js.br +0 -0
  444. package/dist/public/assets/_FC28AlBj.js +0 -1
  445. package/dist/public/assets/_MatnNE0E.js.br +0 -0
  446. package/dist/public/assets/_N0oP21xr.js +0 -1
  447. package/dist/public/assets/_Opj7Hn0u.js.br +0 -0
  448. package/dist/public/assets/_P1g2Hmou.js.br +0 -0
  449. package/dist/public/assets/_PwtpBaMf.js.br +0 -0
  450. package/dist/public/assets/_TX0w8pNw.js.br +0 -0
  451. package/dist/public/assets/_ZHFqgS6H.js +0 -1
  452. package/dist/public/assets/_awhwVzn2.js +0 -1
  453. package/dist/public/assets/_bcmbsCHv.js +0 -1
  454. package/dist/public/assets/_cX3i7NsL.js +0 -1
  455. package/dist/public/assets/_fkTQCq_W.js.br +0 -0
  456. package/dist/public/assets/_h4esEJW5.js +0 -1
  457. package/dist/public/assets/_kxgPx4kL.js +0 -1
  458. package/dist/public/assets/_naTBruJS.js +0 -1
  459. package/dist/public/assets/_oA-Vr8Cf.js.br +0 -0
  460. package/dist/public/assets/_oBcpaEKd.js.br +0 -0
  461. package/dist/public/assets/_og2j-mr5.js.br +0 -0
  462. package/dist/public/assets/_qI-Bm0LQ.js +0 -1
  463. package/dist/public/assets/_rd-8foIp.js.br +0 -0
  464. package/dist/public/assets/_sQjKpNXF.js +0 -1
  465. package/dist/public/assets/_tPOATR_S.js.br +0 -0
  466. package/dist/public/assets/_vSKAFeTh.js.br +0 -0
  467. package/dist/public/assets/_yUTKWdFy.js +0 -1
  468. package/dist/public/assets/app-page-CyU0ft_x.css.br +0 -0
  469. package/dist/public/assets/command-palette-BgUVS5fJ.css.br +0 -0
  470. package/dist/public/assets/files-view-68OApH0T.css +0 -1
  471. package/dist/public/assets/files-view-68OApH0T.css.br +0 -0
  472. package/dist/public/assets/files-view-CK0sC8sL.js +0 -2
  473. package/dist/public/assets/icon-search-Cslr912D.css.br +0 -0
  474. package/dist/public/assets/manage-workspace-Cp4MnvNw.js +0 -2
  475. package/dist/public/assets/manage-workspace-EFWH7-Cv.css +0 -1
  476. package/dist/public/assets/modal-view-C0q2p23m.css.br +0 -0
  477. package/dist/public/assets/new-BHVoRMDD.js +0 -1
  478. package/dist/public/assets/new-workspace-ChGmqibN.css +0 -1
  479. package/dist/public/assets/new-workspace-ChGmqibN.css.br +0 -0
  480. package/dist/public/assets/new-workspace-TC9V3qGi.js +0 -2
  481. package/dist/public/assets/port-chip-DefDqlaR.css +0 -1
  482. package/dist/public/assets/port-chip-DefDqlaR.css.br +0 -0
  483. package/dist/public/assets/prompt-dialog-CJDGPwkv.css +0 -1
  484. package/dist/public/assets/prompt-dialog-CJDGPwkv.css.br +0 -0
  485. package/dist/public/assets/quick-open-Diva1_qC.css +0 -1
  486. package/dist/public/assets/quick-open-Diva1_qC.css.br +0 -0
  487. package/dist/public/assets/repo-picker-CpKpWL1n.css +0 -1
  488. package/dist/public/assets/repo-picker-CpKpWL1n.css.br +0 -0
  489. package/dist/public/assets/review-view-CFzk48_R.js +0 -2
  490. package/dist/public/assets/review-view-CgJv3K8t.css +0 -1
  491. package/dist/public/assets/review-view-CgJv3K8t.css.br +0 -0
  492. package/dist/public/assets/section-iG3mkmoF.css +0 -1
  493. package/dist/public/assets/section-iG3mkmoF.css.br +0 -0
  494. package/dist/public/assets/settings-BHVoRMDD.js +0 -1
  495. package/dist/public/assets/settings-page-SZbATvFs.js +0 -2
  496. package/dist/public/assets/settings-page-lzx2uvfg.css +0 -1
  497. package/dist/public/assets/settings-page-lzx2uvfg.css.br +0 -0
  498. package/dist/public/assets/sso-auth-hsN6VJBq.css +0 -1
  499. package/dist/public/assets/sso-auth-hsN6VJBq.css.br +0 -4
  500. package/dist/public/assets/w-BHVoRMDD.js +0 -1
  501. package/dist/public/assets/w._-BHVoRMDD.js +0 -1
  502. package/dist/public/assets/w._-Cwa3FQTr.js +0 -1
  503. package/dist/public/assets/w._-XzA5CKe1.js +0 -1
@@ -36,14 +36,31 @@ var config = {
36
36
  * `3000-work.example.com`. The server accepts both; this only picks
37
37
  * which one the port chips link to. `dash` stays one label under the
38
38
  * zone, which is what a free Cloudflare certificate covers. */
39
- portHosts: process.env.WORK_PORT_HOSTS === "dash" ? "dash" : "sub"
39
+ portHosts: process.env.WORK_PORT_HOSTS === "dash" ? "dash" : "sub",
40
+ /** The address the server listens on: this machine only, unless set
41
+ * (`0.0.0.0` / `::` for every interface, or one address) — reaching it
42
+ * from elsewhere is an auth wrapper's job (server/access.ts). Under
43
+ * systemd the socket unit binds, and `work setup` writes this into it. */
44
+ listen: process.env.WORK_LISTEN || "127.0.0.1",
45
+ /** The auth wrapper's addresses when it runs on another machine (IPs or
46
+ * CIDRs, or `*`): the only peers besides this machine work answers. */
47
+ trustedProxies: list(process.env.WORK_TRUSTED_PROXIES),
48
+ /** Names beyond this machine's own that work answers to — the ones a
49
+ * wrapper in front of it serves (their port subdomains included). */
50
+ allowedHosts: list(process.env.WORK_ALLOWED_HOSTS)
40
51
  };
52
+ function list(value) {
53
+ return (value || "").split(",").map((s) => s.trim()).filter(Boolean);
54
+ }
41
55
  fs.mkdirSync(config.dataDir, { recursive: true });
42
56
  //#endregion
43
57
  //#region src/server/exec.ts
44
58
  /** Dedicated tmux server so we never touch another multiplexer's or the
45
- * user's default tmux. */
46
- var TMUX_SOCKET = "work";
59
+ * user's default tmux. `WORK_TMUX_SOCKET` names another for a server that
60
+ * must not share the machine's (the smoke test, a throwaway): each start
61
+ * detaches every client on its socket, which on the live one cut every
62
+ * open terminal. */
63
+ var TMUX_SOCKET = process.env.WORK_TMUX_SOCKET || "work";
47
64
  var execFileAsync = promisify(execFile);
48
65
  async function run$1(cmd, args, cwd, env, opts = {}) {
49
66
  const child = execFileAsync(cmd, args, {
@@ -305,7 +322,8 @@ var agents_default = [
305
322
  "shell": "sh"
306
323
  },
307
324
  "icon": "M4 5.25h16v2.9h-2.35v10.6h-2.9V8.15h-3.5v10.6h-2.9V8.15H7.1c-.62 0-1.13.32-1.46.86l-.36.6L4 8.6V5.25z",
308
- "reauth": "pi"
325
+ "reauth": "pi",
326
+ "pathDirs": ["~/.pi/agent/bin"]
309
327
  }
310
328
  ];
311
329
  //#endregion
@@ -890,7 +908,7 @@ function shared(fn, ttlMs) {
890
908
  }
891
909
  //#endregion
892
910
  //#region src/server/events.ts
893
- var listeners = /* @__PURE__ */ new Set();
911
+ var listeners$1 = /* @__PURE__ */ new Set();
894
912
  function publish(type, data) {
895
913
  publishJson(type, data === void 0 ? void 0 : JSON.stringify(data), data);
896
914
  }
@@ -902,20 +920,20 @@ function publishJson(type, dataJson, data) {
902
920
  data,
903
921
  frame: dataJson === void 0 ? JSON.stringify({ type }) : `{"type":${JSON.stringify(type)},"data":${dataJson}}`
904
922
  };
905
- for (const listener of [...listeners]) try {
923
+ for (const listener of [...listeners$1]) try {
906
924
  listener(event);
907
925
  } catch (err) {
908
926
  console.error("[work] event listener error", err);
909
927
  }
910
928
  }
911
929
  function subscribe(listener) {
912
- listeners.add(listener);
913
- return () => listeners.delete(listener);
930
+ listeners$1.add(listener);
931
+ return () => listeners$1.delete(listener);
914
932
  }
915
933
  /** Connected SSE clients — lets background watchers idle when no one
916
934
  * is listening. */
917
935
  function subscriberCount() {
918
- return listeners.size;
936
+ return listeners$1.size;
919
937
  }
920
938
  //#endregion
921
939
  //#region node_modules/.pnpm/bn.js@4.12.5/node_modules/bn.js/lib/bn.js
@@ -6563,12 +6581,20 @@ var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
6563
6581
  sendNotification: webPush.sendNotification.bind(webPush)
6564
6582
  };
6565
6583
  })))(), 1);
6566
- /** Web Push for "agent waiting" alerts. VAPID keys are generated once and
6567
- * kept in the data dir; subscriptions live in sqlite and are pruned when a
6568
- * push endpoint reports them gone. */
6584
+ /** Web Push for agent alerts (a turn finished, an agent stopped on the
6585
+ * user). VAPID keys are generated once and kept in the data dir;
6586
+ * subscriptions live in sqlite and are pruned when a push endpoint reports
6587
+ * them gone. */
6569
6588
  function pushPublicKey() {
6570
6589
  return keys().publicKey;
6571
6590
  }
6591
+ /** Whether a subscription's keys are the sizes Web Push needs (a P-256
6592
+ * point and a 16-byte secret): one that is not fails every send, forever,
6593
+ * with nothing to say it is gone. */
6594
+ function validSubscription(subscription) {
6595
+ const bytes = (b64) => Buffer.from(b64, "base64url").length;
6596
+ return bytes(subscription.keys.p256dh) === 65 && bytes(subscription.keys.auth) === 16;
6597
+ }
6572
6598
  function saveSubscription(subscription) {
6573
6599
  db.prepare("INSERT OR REPLACE INTO push_subscriptions (endpoint, data) VALUES (?, ?)").run(subscription.endpoint, JSON.stringify(subscription));
6574
6600
  }
@@ -6576,13 +6602,15 @@ function deleteSubscription(endpoint) {
6576
6602
  db.prepare("DELETE FROM push_subscriptions WHERE endpoint = ?").run(endpoint);
6577
6603
  }
6578
6604
  /** Push to every subscribed device, or to the one `endpoint` names (a
6579
- * test from Settings → Notifications). */
6580
- async function sendPush(payload, endpoint) {
6605
+ * test from Settings → Notifications), and say what became of each.
6606
+ * `urgency`: how hard a phone should wake for it (a finished turn can wait
6607
+ * for the next wake; a question cannot). */
6608
+ async function sendPush(payload, { endpoint, urgency = "high" } = {}) {
6581
6609
  const rows = all("SELECT endpoint, data FROM push_subscriptions").filter((row) => !endpoint || row.endpoint === endpoint);
6582
- if (!rows.length) return;
6610
+ if (!rows.length) return [];
6583
6611
  const { publicKey, privateKey } = keys();
6584
6612
  const body = JSON.stringify(payload);
6585
- await Promise.all(rows.map(async (row) => {
6613
+ return Promise.all(rows.map(async (row) => {
6586
6614
  try {
6587
6615
  await import_src.default.sendNotification(JSON.parse(row.data), body, {
6588
6616
  vapidDetails: {
@@ -6591,26 +6619,45 @@ async function sendPush(payload, endpoint) {
6591
6619
  privateKey
6592
6620
  },
6593
6621
  TTL: 600,
6594
- urgency: "high"
6622
+ urgency,
6623
+ timeout: 1e4
6595
6624
  });
6625
+ return { ok: true };
6596
6626
  } catch (err) {
6597
6627
  const status = err.statusCode;
6598
6628
  if (status === 404 || status === 410) deleteSubscription(row.endpoint);
6599
6629
  else console.warn("[work] push failed", status ?? err);
6630
+ return {
6631
+ ok: false,
6632
+ status,
6633
+ error: status === 404 || status === 410 ? "the push service no longer knows this device" : status === 401 || status === 403 ? "the push service refused this server's key" : err.message || String(err)
6634
+ };
6600
6635
  }
6601
6636
  }));
6602
6637
  }
6603
6638
  var VAPID_SUBJECT = "https://github.com/DylanPiercey/work";
6604
6639
  var cachedKeys = null;
6640
+ /** The server's key pair, made once. Only a missing file makes a new
6641
+ * one: every subscription was made for the old key, and a new key fails
6642
+ * every send to them, so a file that will not read is an error to see,
6643
+ * not a reason to silence every device. Written whole or not at all. */
6605
6644
  function keys() {
6606
6645
  if (cachedKeys) return cachedKeys;
6607
6646
  const file = path.join(config.dataDir, "vapid.json");
6647
+ let text = null;
6608
6648
  try {
6609
- cachedKeys = JSON.parse(fs.readFileSync(file, "utf8"));
6610
- } catch {
6611
- cachedKeys = import_src.default.generateVAPIDKeys();
6612
- fs.writeFileSync(file, JSON.stringify(cachedKeys), { mode: 384 });
6649
+ text = fs.readFileSync(file, "utf8");
6650
+ } catch (err) {
6651
+ if (err.code !== "ENOENT") throw err;
6652
+ }
6653
+ if (text != null) {
6654
+ cachedKeys = JSON.parse(text);
6655
+ return cachedKeys;
6613
6656
  }
6657
+ cachedKeys = import_src.default.generateVAPIDKeys();
6658
+ const tmp = `${file}.${process.pid}.tmp`;
6659
+ fs.writeFileSync(tmp, JSON.stringify(cachedKeys), { mode: 384 });
6660
+ fs.renameSync(tmp, file);
6614
6661
  return cachedKeys;
6615
6662
  }
6616
6663
  var ACCENT_RE = /^#[0-9a-f]{6}$/;
@@ -6764,7 +6811,8 @@ var meta_default = {
6764
6811
  "shell": "sh"
6765
6812
  },
6766
6813
  icon: "M4 5.25h16v2.9h-2.35v10.6h-2.9V8.15h-3.5v10.6h-2.9V8.15H7.1c-.62 0-1.13.32-1.46.86l-.36.6L4 8.6V5.25z",
6767
- reauth: "pi"
6814
+ reauth: "pi",
6815
+ pathDirs: ["~/.pi/agent/bin"]
6768
6816
  };
6769
6817
  //#endregion
6770
6818
  //#region src/agents/meta.ts
@@ -6824,14 +6872,37 @@ var scraped = /* @__PURE__ */ new Map();
6824
6872
  var reasons = /* @__PURE__ */ new Map();
6825
6873
  var summaries = /* @__PURE__ */ new Map();
6826
6874
  var viewers = /* @__PURE__ */ new Map();
6875
+ /** "Finished" pushes waiting out `DONE_SETTLE_MS`. */
6876
+ var pendingDone = /* @__PURE__ */ new Map();
6877
+ /** When each session's hooks last reported, since its process started:
6878
+ * a session whose hooks never report (not loaded, not trusted, turned
6879
+ * off) is left to the pane scraper rather than to silence. */
6880
+ var reportedAt = /* @__PURE__ */ new Map();
6881
+ /** A turn's end is pushed once it has stayed ended this long. Stop fires
6882
+ * again each time something sends the agent back to work (a stop hook
6883
+ * that continues it, a goal loop, background subagents reporting in), and
6884
+ * each of those ends was a "Finished" on the phone while it carried on. */
6885
+ var DONE_SETTLE_MS = 3e3;
6886
+ var listeners = /* @__PURE__ */ new Set();
6887
+ /** Called on every phase transition (the status snapshot drops itself). */
6888
+ function onPhaseChange(fn) {
6889
+ listeners.add(fn);
6890
+ }
6827
6891
  function setAgentPhase(sessionId, phase, reason, summary) {
6892
+ reportedAt.set(sessionId, Date.now());
6893
+ cancelDone(sessionId);
6894
+ if (phase === "idle") {
6895
+ if (phases.has(sessionId)) applyChange(sessionId, () => dropHook(sessionId));
6896
+ return;
6897
+ }
6828
6898
  if (phase === "waiting" && reason === "done") {
6899
+ const failed = phases.get(sessionId) === "waiting" && reasons.get(sessionId) === "attention";
6829
6900
  if (summary) summaries.set(sessionId, summary);
6830
- notify(sessionId, "done");
6831
- if (phases.has(sessionId)) applyChange(sessionId, () => {
6832
- phases.delete(sessionId);
6833
- reasons.delete(sessionId);
6834
- }, true);
6901
+ if (phases.has(sessionId)) applyChange(sessionId, () => dropHook(sessionId));
6902
+ if (!failed) pendingDone.set(sessionId, setTimeout(() => {
6903
+ pendingDone.delete(sessionId);
6904
+ notify(sessionId, "done");
6905
+ }, DONE_SETTLE_MS));
6835
6906
  return;
6836
6907
  }
6837
6908
  if (phase === "waiting" && summary) summaries.set(sessionId, summary);
@@ -6844,7 +6915,43 @@ function setAgentPhase(sessionId, phase, reason, summary) {
6844
6915
  applyChange(sessionId, () => {
6845
6916
  phases.set(sessionId, phase);
6846
6917
  if (phase === "waiting" && reason) reasons.set(sessionId, reason);
6847
- }, true);
6918
+ }, reasons.get(sessionId) !== next);
6919
+ }
6920
+ function dropHook(sessionId) {
6921
+ phases.delete(sessionId);
6922
+ reasons.delete(sessionId);
6923
+ }
6924
+ function cancelDone(sessionId) {
6925
+ const timer = pendingDone.get(sessionId);
6926
+ if (!timer) return;
6927
+ clearTimeout(timer);
6928
+ pendingDone.delete(sessionId);
6929
+ }
6930
+ /** What the session's hooks last said, apart from the scraper. */
6931
+ function hookPhase(sessionId) {
6932
+ return phases.get(sessionId);
6933
+ }
6934
+ /** When the session's hooks last reported (undefined: never, this run). */
6935
+ function hookReportedAt(sessionId) {
6936
+ return reportedAt.get(sessionId);
6937
+ }
6938
+ /** A hook's "working" whose pane has gone still: the turn ended without
6939
+ * a hook saying so (an interrupt, a failure the CLI reports no hook for),
6940
+ * since every agent's screen animates while it works. Idle, no push. */
6941
+ function settleQuietWorking(sessionId) {
6942
+ if (phases.get(sessionId) !== "working") return;
6943
+ applyChange(sessionId, () => dropHook(sessionId));
6944
+ }
6945
+ /** The user answered at the terminal while the session was stopped on
6946
+ * them (approved, denied, picked, interrupted): no longer waiting on
6947
+ * them, whether or not the CLI has a hook for the answer. Working until
6948
+ * a hook says otherwise, or the pane goes still. */
6949
+ function noteUserAnswered(sessionId) {
6950
+ if (phases.get(sessionId) !== "waiting") return;
6951
+ applyChange(sessionId, () => {
6952
+ phases.set(sessionId, "working");
6953
+ reasons.delete(sessionId);
6954
+ });
6848
6955
  }
6849
6956
  /** Live visible-terminal count per session, reported by the term server;
6850
6957
  * a session someone is watching does not push notifications. */
@@ -6888,15 +6995,19 @@ async function notify(sessionId, reason) {
6888
6995
  workspaceId: row.workspace_id,
6889
6996
  sessionId
6890
6997
  }
6891
- }).catch(() => {});
6998
+ }, { urgency: reason === "done" ? "normal" : "high" }).catch(() => {});
6892
6999
  }
7000
+ /** Forget everything about a session: its pane went away, or it is being
7001
+ * started afresh (a respawn must not inherit the last process's wait). */
6893
7002
  function clearAgentPhase(sessionId) {
7003
+ cancelDone(sessionId);
7004
+ summaries.delete(sessionId);
7005
+ reportedAt.delete(sessionId);
6894
7006
  if (!phases.has(sessionId) && !scraped.has(sessionId)) return;
6895
7007
  applyChange(sessionId, () => {
6896
7008
  phases.delete(sessionId);
6897
7009
  scraped.delete(sessionId);
6898
7010
  reasons.delete(sessionId);
6899
- summaries.delete(sessionId);
6900
7011
  });
6901
7012
  }
6902
7013
  /** What a session is doing. The hook wins where there is one: it reports
@@ -6904,26 +7015,23 @@ function clearAgentPhase(sessionId) {
6904
7015
  function agentPhase(sessionId) {
6905
7016
  return phases.get(sessionId) ?? scraped.get(sessionId);
6906
7017
  }
6907
- /** A freshly booted server's first scans re-derive scraped state from
6908
- * panes that were already that way — that must not re-buzz every device
6909
- * after a deploy. Hook reports are exempt: each is a real transition the
6910
- * agent just made, and one that lands right after a restart (the turn
6911
- * that was running through it) is exactly the one worth hearing about. */
6912
- var BOOT_GRACE_MS = 15e3;
6913
- var bootedAt = Date.now();
6914
- function applyChange(sessionId, mutate, fromHook = false) {
7018
+ /** Apply a mutation and broadcast when it moved the session's phase (or,
7019
+ * `force`, its reason: finished and blocked draw differently). Only a
7020
+ * hook ever reports "waiting", so a transition into it is a push. */
7021
+ function applyChange(sessionId, mutate, force = false) {
6915
7022
  const before = agentPhase(sessionId);
6916
7023
  mutate();
6917
7024
  const after = agentPhase(sessionId);
6918
- if (before === after) return;
7025
+ if (before === after && !force) return;
6919
7026
  publishChange(sessionId);
6920
- if (after === "waiting" && before !== "waiting" && (fromHook || Date.now() - bootedAt > BOOT_GRACE_MS)) notify(sessionId, reasons.get(sessionId) ?? "attention");
7027
+ if (after === "waiting" && before !== "waiting") notify(sessionId, reasons.get(sessionId) ?? "attention");
6921
7028
  }
6922
7029
  function publishChange(sessionId) {
7030
+ for (const fn of listeners) fn();
6923
7031
  const row = get("SELECT workspace_id FROM sessions WHERE id = ?", sessionId);
6924
7032
  if (row?.workspace_id) {
6925
7033
  const workspaceId = row.workspace_id;
6926
- import("./_V3gojze9.js").then((m) => m.notifySessionsChanged(workspaceId));
7034
+ import("./_7W8iLTql.js").then((m) => m.notifySessionsChanged(workspaceId));
6927
7035
  return;
6928
7036
  }
6929
7037
  publish("sessions.changed");
@@ -6933,37 +7041,42 @@ function publishChange(sessionId) {
6933
7041
  /** Shell command a CLI hook runs to report a state: POSTs the hook's own
6934
7042
  * stdin JSON to the app, which mines it for a notification summary. `exit 0`
6935
7043
  * always — a state report must never block the agent. Session and port
6936
- * come from the env tmux gave the pane (see sessions.ts). */
6937
- function stateCurl(state, reason) {
6938
- const qs = `session=$WORK_SESSION_ID&state=${state}` + (reason ? `&reason=${reason}` : "");
6939
- return `[ -n "$WORK_SESSION_ID" ] && curl -sf -m 2 --retry 2 --retry-delay 1 --retry-connrefused -X POST "http://127.0.0.1:\${WORK_APP_PORT:-${config.appPort}}/api/agent-state?${qs}" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1; exit 0`;
7044
+ * come from the env tmux gave the pane (see sessions.ts); `family` names
7045
+ * the CLI, so one run inside another session's pane (a nested `grok -p`)
7046
+ * does not report as that session's agent. */
7047
+ function stateCurl(family, state, reason) {
7048
+ const qs = `session=$WORK_SESSION_ID&agent=${family}&state=${state}` + (reason ? `&reason=${reason}` : "");
7049
+ return `[ -n "$WORK_SESSION_ID" ] && curl -sf --connect-timeout 1 -m 2 -X POST "http://127.0.0.1:\${WORK_APP_PORT:-${config.appPort}}/api/agent-state?${qs}" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1; exit 0`;
6940
7050
  }
6941
7051
  /** Claude-format hook groups (`{matcher?, hooks:[{type:"command",command}]}`)
6942
7052
  * for a hook map — the JSON shape Claude, Copilot plugins and Grok all read. */
6943
- function claudeStyleHooks(map, extra = {}) {
7053
+ function claudeStyleHooks(family, map, extra = {}) {
6944
7054
  const hooks = {};
6945
7055
  for (const [event, state, reason, matcher] of map) (hooks[event] ??= []).push({
6946
7056
  ...matcher ? { matcher } : {},
6947
7057
  hooks: [{
6948
7058
  type: "command",
6949
- command: stateCurl(state, reason),
7059
+ command: stateCurl(family, state, reason),
6950
7060
  ...extra
6951
7061
  }]
6952
7062
  });
6953
7063
  return hooks;
6954
7064
  }
7065
+ /** The notifications that mean the agent is stopped on the user, and how
7066
+ * each reads. Only these: the CLIs also notify about an idle session (a
7067
+ * nudge a minute after Stop already pushed), a finished sign-in, a
7068
+ * question already answered, a subagent done, a quota wait resuming —
7069
+ * none of them a session needing anything, and each one read as
7070
+ * "attention" buzzed a phone and painted the nav red for nothing. */
6955
7071
  var NOTIFICATION_LABELS = {
6956
7072
  permission_prompt: "Waiting for permission",
6957
7073
  agent_needs_input: "Waiting for input",
6958
7074
  elicitation_dialog: "Asking a question",
6959
7075
  elicitation_url_dialog: "Needs a browser sign-in"
6960
7076
  };
6961
- /** Notifications that mean the agent is nudging about an idle session
6962
- * rather than blocking on one. Stop already said the turn ended and
6963
- * pushed for it; reporting the nudge too painted the nav red for a
6964
- * session that merely sat there, and — read as "done" — buzzed a second
6965
- * time a minute after every finish. Dropped entirely. */
6966
- var NUDGE_NOTIFICATIONS = /* @__PURE__ */ new Set(["idle_prompt"]);
7077
+ /** A matcher for the notifications worth a report, for CLIs whose hook
7078
+ * config takes one (so the rest never run a hook at all). */
7079
+ var NOTIFICATION_MATCHER = Object.keys(NOTIFICATION_LABELS).join("|");
6967
7080
  function str$1(v) {
6968
7081
  return typeof v === "string" ? v.trim() : "";
6969
7082
  }
@@ -6972,12 +7085,12 @@ function notificationType(p) {
6972
7085
  return str$1(p.notification_type) || str$1(p.notificationType);
6973
7086
  }
6974
7087
  /** Read a Claude-format payload: last message on Stop, the CLI's own
6975
- * notification line (or a label for its type) on Notification, and the
6976
- * idle nudge dropped. Families that speak this dialect start here and
6977
- * add their own quirks. */
7088
+ * notification line (or a label for its type) on Notification, and any
7089
+ * other notification dropped. Families that speak this dialect start
7090
+ * here and add their own quirks. */
6978
7091
  function readClaudeStyle(p, reported) {
6979
7092
  const kind = notificationType(p);
6980
- if (NUDGE_NOTIFICATIONS.has(kind)) return { ignore: true };
7093
+ if (kind && !NOTIFICATION_LABELS[kind]) return { ignore: true };
6981
7094
  const reason = reported;
6982
7095
  const text = str$1(p.last_assistant_message) || str$1(p["last-assistant-message"]) || str$1(p.lastAssistantMessage);
6983
7096
  if (text) return {
@@ -6997,6 +7110,16 @@ function cleanSummary(text) {
6997
7110
  if (!flat) return void 0;
6998
7111
  return flat.length > 180 ? `${flat.slice(0, 177)}…` : flat;
6999
7112
  }
7113
+ /** Write a file work keeps in its data dir for a CLI to load (a plugin,
7114
+ * an extension, a hook list), only when its content moved: each launch
7115
+ * asks, and a rewrite would bump the mtime a running CLI may watch. */
7116
+ function writeIfChanged(file, content) {
7117
+ try {
7118
+ if (fs.readFileSync(file, "utf8") === content) return;
7119
+ } catch {}
7120
+ fs.mkdirSync(path.dirname(file), { recursive: true });
7121
+ fs.writeFileSync(file, content);
7122
+ }
7000
7123
  //#endregion
7001
7124
  //#region src/server/usage/shared.ts
7002
7125
  var HOME = os.homedir();
@@ -7231,11 +7354,16 @@ function numberedDirs() {
7231
7354
  * macOS Claude Code keeps them in the login keychain instead (service
7232
7355
  * "Claude Code-credentials"), which `security` reads back as the same JSON.
7233
7356
  * The keychain read is cached per process tick of the usage refresh — it
7234
- * forks a process — and only tried for the primary account. */
7357
+ * forks a process — and only tried for the primary account. Read once per
7358
+ * account per refresh: a row asks for them half a dozen times, and every
7359
+ * refresh lists the accounts afresh. */
7360
+ var credentialsRead = /* @__PURE__ */ new WeakMap();
7235
7361
  var credentials = (account) => {
7236
- const file = readJson(path.join(account.dir, ".credentials.json"));
7237
- if (file || process.platform !== "darwin" || account.suffix) return file;
7238
- return keychainCredentials();
7362
+ if (credentialsRead.has(account)) return credentialsRead.get(account);
7363
+ let cred = readJson(path.join(account.dir, ".credentials.json"));
7364
+ if (!cred && process.platform === "darwin" && !account.suffix) cred = keychainCredentials();
7365
+ credentialsRead.set(account, cred);
7366
+ return cred;
7239
7367
  };
7240
7368
  var keychain = null;
7241
7369
  function keychainCredentials() {
@@ -7273,7 +7401,7 @@ function credentialNote(account) {
7273
7401
  if (expired(obj(cred.claudeAiOauth).expiresAt)) return "token expired — use this Claude account to refresh";
7274
7402
  return "usage unavailable";
7275
7403
  }
7276
- function tokenExpired(account) {
7404
+ function tokenExpired$1(account) {
7277
7405
  const cred = credentials(account);
7278
7406
  return cred ? expired(obj(cred.claudeAiOauth).expiresAt) : false;
7279
7407
  }
@@ -7297,6 +7425,38 @@ async function fetchUsage$3(account) {
7297
7425
  Accept: "application/json"
7298
7426
  } }));
7299
7427
  }
7428
+ /** How often an account's plan is asked for: it changes on an upgrade,
7429
+ * not by the minute, so once an hour stands behind the usage refresh. */
7430
+ var PROFILE_TTL_MS = 36e5;
7431
+ var profiles = /* @__PURE__ */ new Map();
7432
+ /** The account's plan as Anthropic has it now. `.claude.json` holds what
7433
+ * Claude Code saw at sign-in, and it only asks again on a new login, so an
7434
+ * upgrade (Max 5x → 20x) kept reading as the old plan until then. Asked
7435
+ * with the same token as the usage; the cached plan stands in when it
7436
+ * cannot be. */
7437
+ async function livePlan(account) {
7438
+ const hit = profiles.get(account.dir);
7439
+ if (hit && Date.now() - hit.at < PROFILE_TTL_MS) return hit.plan;
7440
+ const token = str(obj(credentials(account)?.claudeAiOauth).accessToken);
7441
+ if (!token) return null;
7442
+ try {
7443
+ const org = obj(obj(await fetchJson("https://api.anthropic.com/api/oauth/profile", { headers: {
7444
+ Authorization: `Bearer ${token}`,
7445
+ "anthropic-beta": "oauth-2025-04-20",
7446
+ "User-Agent": "claude-cli",
7447
+ Accept: "application/json"
7448
+ } })).organization);
7449
+ const plan = str(org.rate_limit_tier || org.organization_type);
7450
+ if (!plan) return null;
7451
+ profiles.set(account.dir, {
7452
+ at: Date.now(),
7453
+ plan
7454
+ });
7455
+ return plan;
7456
+ } catch {
7457
+ return hit?.plan ?? null;
7458
+ }
7459
+ }
7300
7460
  /** The CLI's own last-seen utilization, written into .claude.json. Older and
7301
7461
  * coarser than the API, but it survives an expired token. */
7302
7462
  function claudeJsonFallback(account) {
@@ -7368,8 +7528,9 @@ async function claudeRow(account) {
7368
7528
  note: row.note ?? credentialNote(account)
7369
7529
  };
7370
7530
  }
7371
- const lapsed = tokenExpired(account);
7372
- const loaded = await loadPayload(`claude${account.suffix}`, () => lapsed ? Promise.reject(new FetchFailed("auth expired")) : fetchUsage$3(account));
7531
+ const lapsed = tokenExpired$1(account);
7532
+ const [loaded, plan] = await Promise.all([loadPayload(`claude${account.suffix}`, () => lapsed ? Promise.reject(new FetchFailed("auth expired")) : fetchUsage$3(account)), lapsed ? null : livePlan(account)]);
7533
+ if (plan) account.plan = plan;
7373
7534
  const fallback = loaded.payload ? null : claudeJsonFallback(account);
7374
7535
  const row = parseRow$4(account, loaded.payload ?? fallback, fallback ? "claude.json" : loaded.source);
7375
7536
  if (lapsed) return {
@@ -7385,65 +7546,159 @@ var meta$6 = meta_default$6;
7385
7546
  * like any other, but the turn has not ended, so no Stop or Notification
7386
7547
  * says so — PreToolUse on this tool is the only signal, and PostToolUse
7387
7548
  * (the answer arrived) clears it. */
7388
- var QUESTION_TOOL = "AskUserQuestion";
7389
- /** Claude's dialect plus the question tool's prompt text. */
7390
- function readHook$4(p, reported) {
7549
+ var QUESTION_TOOL$1 = "AskUserQuestion";
7550
+ /** A teammate in an agent team stopped on a permission only the lead
7551
+ * session can grant: the user has to answer it there, so it is as much a
7552
+ * wait as the lead's own prompt. Claude's own notification type, beside
7553
+ * the shared ones. */
7554
+ var WORKER_PERMISSION = "worker_permission_prompt";
7555
+ /** How StopFailure's `error` reads on a phone. */
7556
+ var FAILURES = {
7557
+ authentication_failed: "Signed out",
7558
+ oauth_org_not_allowed: "Organization not allowed",
7559
+ account_on_hold: "Account on hold",
7560
+ verification_required: "Verification required",
7561
+ billing_error: "Billing problem",
7562
+ rate_limit: "Rate limited",
7563
+ overloaded: "Overloaded",
7564
+ invalid_request: "Request rejected",
7565
+ model_not_found: "Model not found",
7566
+ server_error: "Server error",
7567
+ max_output_tokens: "Hit the output limit",
7568
+ cloud_credential_error: "Cloud credentials failed"
7569
+ };
7570
+ /** Claude's dialect: subagents' tool calls left out, the question tool's
7571
+ * prompt, the command awaiting approval and a failed turn's error as the
7572
+ * summary, and a Stop that hands off to background subagents not counted
7573
+ * as the turn's end. */
7574
+ function readHook$6(p, reported, _state) {
7575
+ const event = p.hook_event_name;
7576
+ if ((event === "PreToolUse" || event === "PostToolUse" || event === "PostToolUseFailure") && str$1(p.agent_id)) return { ignore: true };
7577
+ if (event === "Stop" && runningSubagents(p)) return { ignore: true };
7578
+ if (event === "PostToolUseFailure" && p.is_interrupt === true) return { state: "idle" };
7579
+ if (notificationType(p) === WORKER_PERMISSION) return {
7580
+ reason: "attention",
7581
+ summary: cleanSummary(str$1(p.message)) ?? "A teammate needs permission"
7582
+ };
7583
+ if (event === "StopFailure") {
7584
+ const label = FAILURES[str$1(p.error)] ?? "The turn failed";
7585
+ const details = str$1(p.error_details);
7586
+ return {
7587
+ reason: "attention",
7588
+ summary: cleanSummary(details ? `${label}: ${details}` : label)
7589
+ };
7590
+ }
7391
7591
  const read = readClaudeStyle(p, reported);
7392
7592
  if (read.ignore) return read;
7393
- if (p.hook_event_name === "PreToolUse" && p.tool_name === QUESTION_TOOL) {
7593
+ if (event === "PreToolUse" && p.tool_name === QUESTION_TOOL$1) {
7394
7594
  const input = p.tool_input;
7395
7595
  const q = str$1(input?.questions?.[0]?.question);
7396
7596
  read.summary = q ? cleanSummary(q) : "Asking a question";
7597
+ } else if (event === "PermissionRequest" && !read.summary) {
7598
+ const input = p.tool_input;
7599
+ const what = str$1(input?.description) || str$1(input?.command) || str$1(p.tool_name);
7600
+ read.summary = what ? cleanSummary(`Approve: ${what}`) : "Waiting for permission";
7397
7601
  }
7398
7602
  return read;
7399
7603
  }
7604
+ /** Stop's `background_tasks` (in-flight work that will wake the session)
7605
+ * holding a subagent. A background shell (a dev server) is left out: it
7606
+ * may never finish, and the turn really has ended. */
7607
+ function runningSubagents(p) {
7608
+ const tasks = p.background_tasks;
7609
+ return Array.isArray(tasks) && tasks.some((t) => !!t && typeof t === "object" && t.type === "subagent");
7610
+ }
7400
7611
  /** Claude Code takes its settings inline (`--settings` accepts a JSON
7401
7612
  * string), so the hooks travel in argv and nothing is written anywhere.
7402
- * Its Notification hook fires for permission prompts and questions —
7403
- * and for the idle nudge, which readClaudeStyle drops. */
7613
+ * PermissionRequest is the prompt itself, the moment it shows; the
7614
+ * permission Notification only comes after the user has left it for a
7615
+ * while (each keystroke defers it), but it is the one that covers a
7616
+ * sandbox's network prompt. A turn that fails (rate limit, signed out)
7617
+ * ends with StopFailure rather than Stop; an interrupt ends with no hook
7618
+ * at all, which the pane going still catches (scraper.ts). */
7404
7619
  var claude = {
7405
7620
  meta: meta$6,
7406
- launchFlags: () => ["--settings", JSON.stringify({ hooks: claudeStyleHooks([
7621
+ launchFlags: () => ["--settings", JSON.stringify({ hooks: claudeStyleHooks("claude", [
7407
7622
  ["UserPromptSubmit", "working"],
7408
7623
  [
7409
7624
  "PreToolUse",
7410
7625
  "working",
7411
7626
  void 0,
7412
- `^(?!${QUESTION_TOOL}$)`
7627
+ `^(?!${QUESTION_TOOL$1}$)`
7413
7628
  ],
7414
7629
  [
7415
7630
  "PreToolUse",
7416
7631
  "waiting",
7417
7632
  "attention",
7418
- `^${QUESTION_TOOL}$`
7633
+ `^${QUESTION_TOOL$1}$`
7419
7634
  ],
7420
7635
  ["PostToolUse", "working"],
7636
+ ["PostToolUseFailure", "working"],
7637
+ [
7638
+ "PermissionRequest",
7639
+ "waiting",
7640
+ "attention"
7641
+ ],
7421
7642
  [
7422
7643
  "Stop",
7423
7644
  "waiting",
7424
7645
  "done"
7425
7646
  ],
7426
7647
  [
7427
- "Notification",
7648
+ "StopFailure",
7428
7649
  "waiting",
7429
7650
  "attention"
7651
+ ],
7652
+ [
7653
+ "Notification",
7654
+ "waiting",
7655
+ "attention",
7656
+ `${NOTIFICATION_MATCHER}|${WORKER_PERMISSION}`
7430
7657
  ]
7431
7658
  ]) })],
7432
7659
  reportsWorking: () => true,
7433
- envPrefixes: [
7434
- "CLAUDE",
7435
- "CLAUDECODE",
7436
- "AI_AGENT"
7437
- ],
7438
- readHook: readHook$4,
7660
+ envPrefixes: ["CLAUDE", "AI_AGENT"],
7661
+ startupPrompt: (screen) => /Is this a project you created or one you trust/.test(screen) ? ["Enter"] : void 0,
7662
+ readHook: readHook$6,
7439
7663
  usage: () => Promise.all(claudeAccounts().map((account) => claudeRow(account)))
7440
7664
  };
7441
7665
  //#endregion
7442
7666
  //#region src/agents/codex/usage.ts
7443
- var AUTH$2 = path.join(HOME, ".codex", "auth.json");
7667
+ /** Codex's home, where it keeps auth.json and config.toml: `CODEX_HOME`
7668
+ * when set, as the CLI reads it. */
7669
+ function codexHome() {
7670
+ return process.env.CODEX_HOME || path.join(HOME, ".codex");
7671
+ }
7672
+ var authFile$1 = () => path.join(codexHome(), "auth.json");
7673
+ /** Credentials Codex was told to keep in the system keyring
7674
+ * (`cli_auth_credentials_store = "keyring"`, or `"auto"` where one is
7675
+ * available) leave no auth.json to read: signed in, but nothing here can
7676
+ * ask on its behalf. */
7677
+ function keyringStore() {
7678
+ try {
7679
+ const top = fs.readFileSync(path.join(codexHome(), "config.toml"), "utf8").split(/^\s*\[/m)[0];
7680
+ const mode = /^\s*cli_auth_credentials_store\s*=\s*"(\w+)"/m.exec(top)?.[1];
7681
+ return mode === "keyring" || mode === "auto";
7682
+ } catch {
7683
+ return false;
7684
+ }
7685
+ }
7686
+ /** The access token's own `exp` (it is a JWT): whether a 401 or 429 is a
7687
+ * lapsed token rather than the provider throttling. Undefined when there
7688
+ * is no token or it does not decode. */
7689
+ function tokenExpired() {
7690
+ const payload = str(obj(readJson(authFile$1())?.tokens).access_token).split(".")[1];
7691
+ if (!payload) return void 0;
7692
+ try {
7693
+ const claims = obj(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")));
7694
+ return claims.exp == null ? void 0 : expired(claims.exp);
7695
+ } catch {
7696
+ return;
7697
+ }
7698
+ }
7444
7699
  /** Live rate-limit windows from the endpoint the Codex CLI itself uses. */
7445
7700
  async function fetchUsage$2() {
7446
- const tokens = obj(readJson(AUTH$2)?.tokens);
7701
+ const tokens = obj(readJson(authFile$1())?.tokens);
7447
7702
  const token = str(tokens.access_token);
7448
7703
  if (!token) return null;
7449
7704
  return obj(await fetchJson("https://chatgpt.com/backend-api/wham/usage", { headers: {
@@ -7484,7 +7739,7 @@ function parseRow$3(payload, source) {
7484
7739
  /** Signed in with an API key rather than a ChatGPT plan: pay-as-you-go,
7485
7740
  * no rate-limit windows to draw. */
7486
7741
  function apiKeyMode() {
7487
- const auth = readJson(AUTH$2);
7742
+ const auth = readJson(authFile$1());
7488
7743
  return !!auth && (str(auth.auth_mode) === "apikey" || !obj(auth.tokens).access_token && !!auth.OPENAI_API_KEY);
7489
7744
  }
7490
7745
  async function codexRow() {
@@ -7494,19 +7749,21 @@ async function codexRow() {
7494
7749
  note: API_KEY_NOTE
7495
7750
  };
7496
7751
  const { payload, source, reason } = await loadPayload("codex", fetchUsage$2);
7497
- const missingAuth = fs.existsSync(AUTH$2) ? void 0 : noAuthReason("codex");
7752
+ const missingAuth = fs.existsSync(authFile$1()) ? void 0 : keyringStore() ? "credentials are in the system keyring — usage can't be read" : noAuthReason("codex");
7498
7753
  return markStale(payload ? parseRow$3(payload, source) : {
7499
7754
  ...blankRow("codex", source),
7500
7755
  note: missingAuth ?? "usage unavailable"
7501
- }, "codex", void 0, missingAuth ?? reason);
7756
+ }, "codex", tokenExpired(), missingAuth ?? reason);
7502
7757
  }
7503
7758
  //#endregion
7504
7759
  //#region src/agents/codex/index.ts
7505
7760
  var meta$5 = meta_default$5;
7506
7761
  /** Codex hook events → state. PermissionRequest fires just before the
7507
7762
  * approval prompt (PostToolUse clears it once the approved call has run,
7508
- * or the next PreToolUse does if a reviewer auto-decided). */
7509
- var HOOKS$2 = claudeStyleHooks([
7763
+ * or the next PreToolUse does if a reviewer auto-decided). A turn the
7764
+ * user interrupts, or a session that ends, has no Stop: Interrupt and
7765
+ * SessionEnd say the pane is back at its prompt (or gone). */
7766
+ var HOOKS$2 = claudeStyleHooks("codex", [
7510
7767
  ["UserPromptSubmit", "working"],
7511
7768
  ["PreToolUse", "working"],
7512
7769
  ["PostToolUse", "working"],
@@ -7519,23 +7776,37 @@ var HOOKS$2 = claudeStyleHooks([
7519
7776
  "Stop",
7520
7777
  "waiting",
7521
7778
  "done"
7522
- ]
7779
+ ],
7780
+ ["Interrupt", "idle"],
7781
+ ["SessionEnd", "idle"]
7523
7782
  ]);
7783
+ /** Codex runs these two with a short deadline (one second by default,
7784
+ * three at most) where every other event gets ten minutes; the report
7785
+ * needs a little over two at worst (stateCurl's timeouts). */
7786
+ var SHORT_HOOKS = {
7787
+ Interrupt: 3,
7788
+ SessionEnd: 3
7789
+ };
7790
+ /** The tool Codex asks the user a question with (Plan mode and wherever
7791
+ * it is enabled). It goes through PreToolUse like any tool, but the turn
7792
+ * is waiting on the user until its PostToolUse. */
7793
+ var QUESTION_TOOL = "request_user_input";
7524
7794
  /** Codex only runs hooks it trusts. Trust is a hash of the normalized hook
7525
7795
  * kept in config (`hooks.state.<key>.trusted_hash`), and both the hooks and
7526
7796
  * that state are read from the `-c` override layer too — so the launch flags
7527
7797
  * can carry their own trust without touching ~/.codex/config.toml.
7528
7798
  * Mirrors codex-rs/hooks/src/engine/discovery.rs (hook_hash, hook_key,
7529
- * normalized Command config: timeout defaults to 600, async false, None
7530
- * fields omitted) and codex-rs/config/src/fingerprint.rs (sha256 of
7531
- * key-sorted JSON). scripts/smoke.mjs pins it against a hash Codex wrote. */
7532
- function codexHookTrustHash(event, command) {
7799
+ * normalize_command_hook: timeout defaults to 600, or 1 clamped to 1..3
7800
+ * for SessionEnd and Interrupt; async false; None fields omitted) and
7801
+ * codex-rs/config/src/fingerprint.rs (sha256 of key-sorted JSON).
7802
+ * scripts/smoke.mjs pins it against a hash Codex wrote. */
7803
+ function codexHookTrustHash(event, command, timeout = 600) {
7533
7804
  const identity = {
7534
7805
  event_name: hookKeyLabel(event),
7535
7806
  hooks: [{
7536
7807
  async: false,
7537
7808
  command,
7538
- timeout: 600,
7809
+ timeout,
7539
7810
  type: "command"
7540
7811
  }]
7541
7812
  };
@@ -7547,24 +7818,46 @@ function hookKeyLabel(event) {
7547
7818
  }
7548
7819
  /** Synthetic path Codex gives the `-c` override layer when keying hook state. */
7549
7820
  var SESSION_FLAGS_PATH = "/<session-flags>/config.toml";
7550
- /** `-c` overrides that install work's state hooks and trust them. */
7551
- function launchFlags() {
7821
+ /** `-c` overrides that install work's state hooks, trust them, and trust
7822
+ * the workspace folder for this session. Codex asks whether to trust a
7823
+ * folder it has not seen before it takes input, and every workspace is
7824
+ * one: the first prompt would land in that menu, and the Enter after it
7825
+ * would answer "trust" into the user's config.toml on their behalf. The
7826
+ * `-c` layer is this process's alone. Its keys split on every "." with no
7827
+ * quoting (codex-rs/config/src/overrides.rs), so a path goes in as the
7828
+ * key of an inline table, which is merged over the user's projects. */
7829
+ function launchFlags(ctx) {
7552
7830
  const flags = [];
7553
7831
  const state = [];
7554
7832
  for (const [event, groups] of Object.entries(HOOKS$2)) {
7555
7833
  const command = groups[0].hooks[0].command;
7556
- flags.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)}}]}]`);
7834
+ const timeout = SHORT_HOOKS[event];
7835
+ flags.push("-c", `hooks.${event}=[{hooks=[{type="command",command=${JSON.stringify(command)}${timeout ? `,timeout=${timeout}` : ""}}]}]`);
7557
7836
  const key = `${SESSION_FLAGS_PATH}:${hookKeyLabel(event)}:0:0`;
7558
- state.push(`${JSON.stringify(key)}={trusted_hash=${JSON.stringify(codexHookTrustHash(event, command))}}`);
7837
+ state.push(`${JSON.stringify(key)}={trusted_hash=${JSON.stringify(codexHookTrustHash(event, command, timeout))}}`);
7559
7838
  }
7560
7839
  flags.push("-c", `hooks.state={${state.join(",")}}`);
7840
+ const folders = /* @__PURE__ */ new Set([ctx.cwd]);
7841
+ try {
7842
+ folders.add(fs.realpathSync(ctx.cwd));
7843
+ } catch {}
7844
+ flags.push("-c", `projects={${[...folders].map((dir) => `${JSON.stringify(dir)}={trust_level="trusted"}`).join(",")}}`);
7561
7845
  return flags;
7562
7846
  }
7563
- function readHook$3(p, reported) {
7847
+ function readHook$5(p, reported, _state) {
7848
+ const input = p.tool_input;
7849
+ if (p.hook_event_name === "PreToolUse" && p.tool_name === QUESTION_TOOL) {
7850
+ const questions = input?.questions;
7851
+ const q = str$1(questions?.[0]?.question);
7852
+ return {
7853
+ state: "waiting",
7854
+ reason: "attention",
7855
+ summary: q ? cleanSummary(q) : "Asking a question"
7856
+ };
7857
+ }
7564
7858
  const read = readClaudeStyle(p, reported);
7565
7859
  if (!read.summary && p.hook_event_name === "PermissionRequest") {
7566
- const input = p.tool_input;
7567
- const what = str$1(input?.command) || str$1(p.tool_name);
7860
+ const what = str$1(input?.description) || str$1(input?.command) || str$1(p.tool_name);
7568
7861
  read.summary = what ? cleanSummary(`Approve: ${what}`) : "Waiting for permission";
7569
7862
  }
7570
7863
  return read;
@@ -7574,18 +7867,21 @@ var codex = {
7574
7867
  launchFlags,
7575
7868
  reportsWorking: () => true,
7576
7869
  envPrefixes: ["CODEX_"],
7577
- readHook: readHook$3,
7870
+ readHook: readHook$5,
7578
7871
  usage: () => codexRow().then((row) => [row])
7579
7872
  };
7580
7873
  //#endregion
7581
7874
  //#region src/agents/copilot/usage.ts
7582
7875
  var USER_API = "https://api.github.com/copilot_internal/user";
7583
7876
  var HOSTS = path.join(process.env.XDG_CONFIG_HOME || path.join(HOME, ".config"), "gh", "hosts.yml");
7584
- var SESSION_DB = path.join(HOME, ".copilot", "session-store.db");
7585
- /** github.com's `oauth_token` straight out of gh's own hosts.yml.
7877
+ /** Copilot's own home (`COPILOT_HOME`, as the CLI reads it). */
7878
+ var sessionDb = () => path.join(process.env.COPILOT_HOME || path.join(HOME, ".copilot"), "session-store.db");
7879
+ /** github.com's active `oauth_token` straight out of gh's own hosts.yml.
7586
7880
  *
7587
7881
  * Deliberately not a YAML parse (no dependency): track the current top-level
7588
- * host key by indentation and take the first token beneath github.com. */
7882
+ * host key by indentation and take the token that is a direct child of
7883
+ * github.com. With several accounts signed in, each also has one under
7884
+ * `users:`, listed first; the host's own is the active account's. */
7589
7885
  function hostsToken() {
7590
7886
  let text;
7591
7887
  try {
@@ -7594,12 +7890,17 @@ function hostsToken() {
7594
7890
  return null;
7595
7891
  }
7596
7892
  let host = "";
7893
+ let childIndent = -1;
7597
7894
  for (const line of text.split("\n")) {
7598
7895
  if (line[0]?.trim() && line.trimEnd().endsWith(":")) {
7599
7896
  host = line.trim().replace(/:$/, "");
7897
+ childIndent = -1;
7600
7898
  continue;
7601
7899
  }
7602
- if (host !== "github.com") continue;
7900
+ if (host !== "github.com" || !line.trim()) continue;
7901
+ const indent = line.length - line.trimStart().length;
7902
+ if (childIndent < 0) childIndent = indent;
7903
+ if (indent !== childIndent) continue;
7603
7904
  const match = /^\s+oauth_token:\s*(\S+)/.exec(line);
7604
7905
  if (match) return match[1] ?? null;
7605
7906
  }
@@ -7609,7 +7910,11 @@ function hostsToken() {
7609
7910
  * gh manages. Copilot entitlement is account-scoped, so any valid token for
7610
7911
  * the entitled user reads the quota. */
7611
7912
  async function token() {
7612
- for (const key of ["GH_TOKEN", "GITHUB_TOKEN"]) {
7913
+ for (const key of [
7914
+ "COPILOT_GITHUB_TOKEN",
7915
+ "GH_TOKEN",
7916
+ "GITHUB_TOKEN"
7917
+ ]) {
7613
7918
  const value = process.env[key]?.trim();
7614
7919
  if (value) return value;
7615
7920
  }
@@ -7647,9 +7952,10 @@ function periodStart(resetIso) {
7647
7952
  * only, which is why the caller floors it with upstream rather than trusting
7648
7953
  * it alone. */
7649
7954
  function localCredits(start) {
7650
- if (!start || !fs.existsSync(SESSION_DB)) return null;
7955
+ const file = sessionDb();
7956
+ if (!start || !fs.existsSync(file)) return null;
7651
7957
  try {
7652
- const db = new DatabaseSync(SESSION_DB, { readOnly: true });
7958
+ const db = new DatabaseSync(file, { readOnly: true });
7653
7959
  try {
7654
7960
  const nano = num(db.prepare("select sum(total_nano_aiu) as nano from assistant_usage_events where created_at >= ?").get(start)?.nano);
7655
7961
  return nano == null ? 0 : nano / 1e9;
@@ -7712,14 +8018,25 @@ var meta$4 = meta_default$4;
7712
8018
  /** Copilot hook events (Claude-format names, which make it emit Claude's
7713
8019
  * snake_case payloads) → state. Notification rather than PermissionRequest
7714
8020
  * for "needs input": the latter fires before Copilot's own allow rules, the
7715
- * former only once a prompt is actually on screen. */
7716
- var HOOKS$1 = claudeStyleHooks([
8021
+ * former only once a prompt is actually on screen. Its matcher keeps out
8022
+ * the rest of what Copilot notifies about (a background shell or subagent
8023
+ * finishing, an inbox message), none of which is the user being needed.
8024
+ * A tool that fails has PostToolUseFailure instead of PostToolUse; an
8025
+ * error the turn cannot recover from ends it with ErrorOccurred. */
8026
+ var HOOKS$1 = claudeStyleHooks("copilot", [
7717
8027
  ["UserPromptSubmit", "working"],
7718
8028
  ["PreToolUse", "working"],
7719
8029
  ["PostToolUse", "working"],
8030
+ ["PostToolUseFailure", "working"],
7720
8031
  [
7721
8032
  "Notification",
7722
8033
  "waiting",
8034
+ "attention",
8035
+ NOTIFICATION_MATCHER
8036
+ ],
8037
+ [
8038
+ "ErrorOccurred",
8039
+ "waiting",
7723
8040
  "attention"
7724
8041
  ],
7725
8042
  [
@@ -7733,41 +8050,36 @@ var HOOKS$1 = claudeStyleHooks([
7733
8050
  * prompt. The plugin is two files kept under work's data dir and rewritten
7734
8051
  * only when their content changes (a stale hook would report to the wrong
7735
8052
  * port after a config change). */
7736
- function pluginDir() {
8053
+ function pluginDir$1() {
7737
8054
  const dir = path.join(config.dataDir, "copilot-plugin");
7738
8055
  writeIfChanged(path.join(dir, ".claude-plugin", "plugin.json"), JSON.stringify({
7739
8056
  name: "work-session-state",
7740
8057
  version: "1",
7741
8058
  description: "Reports this session's working / needs-input / done state to work."
7742
8059
  }, null, 2));
7743
- writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({ hooks: HOOKS$1 }, null, 2));
8060
+ writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({
8061
+ version: 1,
8062
+ hooks: HOOKS$1
8063
+ }, null, 2));
7744
8064
  return dir;
7745
8065
  }
7746
- function writeIfChanged(file, content) {
7747
- try {
7748
- if (fs.readFileSync(file, "utf8") === content) return;
7749
- } catch {}
7750
- fs.mkdirSync(path.dirname(file), { recursive: true });
7751
- fs.writeFileSync(file, content);
7752
- }
7753
- /** Copilot routes shell and inbox chatter through the same Notification
7754
- * hook; those are neither "needs input" nor "done" and must be dropped. */
7755
- var CHATTER = /* @__PURE__ */ new Set([
7756
- "shell_completed",
7757
- "shell_detached_completed",
7758
- "new_inbox_message",
7759
- "instruction_discovered",
7760
- "factory_completed",
7761
- "unclassified"
7762
- ]);
7763
- function readHook$2(p, reported) {
7764
- if (CHATTER.has(notificationType(p))) return { ignore: true };
8066
+ function readHook$4(p, reported, _state) {
8067
+ if (p.hook_event_name === "ErrorOccurred") {
8068
+ if (p.recoverable !== false) return { ignore: true };
8069
+ const error = p.error;
8070
+ return {
8071
+ reason: "attention",
8072
+ summary: cleanSummary(str$1(error?.message)) ?? "Stopped on an error"
8073
+ };
8074
+ }
7765
8075
  const read = readClaudeStyle(p, reported);
7766
8076
  if (!read.summary && reported === "done") read.summary = lastMessageFromTranscript(p);
7767
8077
  return read;
7768
8078
  }
7769
- /** The last non-empty `assistant.message` in Copilot's events.jsonl. Reads
7770
- * the tail only — transcripts grow for the life of a session. */
8079
+ /** The last non-empty `assistant.message` of the turn that just ended in
8080
+ * Copilot's events.jsonl — none rather than an earlier turn's, so the scan
8081
+ * stops at the prompt that started it. Reads the tail only — transcripts
8082
+ * grow for the life of a session. */
7771
8083
  function lastMessageFromTranscript(p) {
7772
8084
  const file = p.transcript_path;
7773
8085
  if (typeof file !== "string" || !file.endsWith("events.jsonl")) return void 0;
@@ -7780,6 +8092,7 @@ function lastMessageFromTranscript(p) {
7780
8092
  fs.readSync(fd, buf, 0, len, size - len);
7781
8093
  const lines = buf.toString("utf8").split("\n");
7782
8094
  for (let i = lines.length - 1; i >= 0; i--) {
8095
+ if (lines[i].includes("\"user.message\"")) break;
7783
8096
  if (!lines[i].includes("\"assistant.message\"")) continue;
7784
8097
  try {
7785
8098
  const ev = JSON.parse(lines[i]);
@@ -7803,31 +8116,41 @@ function startupPrompt(screen) {
7803
8116
  }
7804
8117
  var copilot = {
7805
8118
  meta: meta$4,
7806
- launchFlags: () => ["--plugin-dir", pluginDir()],
8119
+ launchFlags: () => ["--plugin-dir", pluginDir$1()],
7807
8120
  startupPrompt,
7808
8121
  reportsWorking: () => true,
7809
8122
  envPrefixes: ["COPILOT_"],
7810
- readHook: readHook$2,
8123
+ readHook: readHook$4,
7811
8124
  usage: () => copilotRow().then((row) => [row])
7812
8125
  };
7813
8126
  //#endregion
7814
8127
  //#region src/agents/cursor/usage.ts
7815
- var AUTH$1 = path.join(HOME, ".config", "cursor", "auth.json");
7816
8128
  var CLI_CONFIG = path.join(HOME, ".cursor", "cli-config.json");
8129
+ /** Where the CLI keeps its auth, as it looks it up itself: `~/.cursor` on a
8130
+ * Mac, the XDG config dir elsewhere. */
8131
+ function authFile() {
8132
+ if (process.platform === "darwin") return path.join(HOME, ".cursor", "auth.json");
8133
+ const config = process.env.XDG_CONFIG_HOME || path.join(HOME, ".config");
8134
+ return path.join(config, "cursor", "auth.json");
8135
+ }
7817
8136
  var API = "https://api2.cursor.sh";
7818
8137
  var WWW = "https://cursor.com";
7819
8138
  function cursorAuth() {
7820
- const auth = readJson(AUTH$1);
8139
+ const auth = readJson(authFile());
7821
8140
  if (!auth) return null;
7822
8141
  const token = str(auth.accessToken || auth.access_token);
7823
8142
  if (!token) return null;
7824
8143
  let sub = "";
8144
+ let exp;
7825
8145
  try {
7826
- sub = str(obj(JSON.parse(Buffer.from(str(token.split(".")[1]), "base64url").toString())).sub);
8146
+ const claims = obj(JSON.parse(Buffer.from(str(token.split(".")[1]), "base64url").toString()));
8147
+ sub = str(claims.sub);
8148
+ exp = claims.exp;
7827
8149
  } catch {}
7828
8150
  return {
7829
8151
  token,
7830
8152
  sub,
8153
+ exp,
7831
8154
  email: str(obj(readJson(CLI_CONFIG)?.authInfo).email).trim()
7832
8155
  };
7833
8156
  }
@@ -7846,7 +8169,8 @@ var dashboardPost = async (token, method) => obj(await fetchJson(`${API}/aiserve
7846
8169
  * it is only readable with the web session cookie. */
7847
8170
  async function usageSummary(auth) {
7848
8171
  if (!auth.sub) return null;
7849
- const cookie = `WorkosCursorSessionToken=${encodeURIComponent(`${auth.sub}::${auth.token}`)}`;
8172
+ const user = auth.sub.split("|").pop();
8173
+ const cookie = `WorkosCursorSessionToken=${encodeURIComponent(`${user}::${auth.token}`)}`;
7850
8174
  return obj(await fetchJson(`${WWW}/api/usage-summary`, { headers: {
7851
8175
  Cookie: cookie,
7852
8176
  Accept: "application/json",
@@ -7915,14 +8239,58 @@ async function cursorRow() {
7915
8239
  return markStale(payload ? parseRow$1(payload, auth, source) : {
7916
8240
  ...blankRow("cursor", source),
7917
8241
  note: missingAuth ?? "usage unavailable"
7918
- }, "cursor", void 0, missingAuth ?? reason);
8242
+ }, "cursor", auth?.exp != null ? expired(auth.exp) : void 0, missingAuth ?? reason);
8243
+ }
8244
+ //#endregion
8245
+ //#region src/agents/cursor/index.ts
8246
+ var meta$3 = meta_default$3;
8247
+ /** Cursor has no hook for a permission prompt or a question, so the pane
8248
+ * scraper (scraper.ts) still says when a session is working and when it
8249
+ * has gone quiet — a stopped turn and a question look the same there. What
8250
+ * a hook adds is the end of a turn and how it ended, which is what a push
8251
+ * needs: one `stop` hook and nothing else. A hook that reported "working"
8252
+ * would outrank the scraper and hold a session stopped on a prompt at
8253
+ * "working" for good, since nothing reports the prompt.
8254
+ *
8255
+ * The hook comes from a plugin in work's data dir named with `--plugin-dir`
8256
+ * (the CLI runs plugin hooks for every step, those of a `--plugin-dir`
8257
+ * plugin included), never ~/.cursor/hooks.json — the user's, which Grok
8258
+ * also loads. Cursor hands the payload to a command as a heredoc after it,
8259
+ * so the report is a brace group: the heredoc is its stdin, not `exit`'s. */
8260
+ function pluginDir() {
8261
+ const dir = path.join(config.dataDir, "cursor-plugin");
8262
+ writeIfChanged(path.join(dir, ".cursor-plugin", "plugin.json"), JSON.stringify({
8263
+ name: "work-session-state",
8264
+ version: "1",
8265
+ description: "Reports when this session's turn ends to work."
8266
+ }, null, 2));
8267
+ writeIfChanged(path.join(dir, "hooks", "hooks.json"), JSON.stringify({
8268
+ version: 1,
8269
+ hooks: { stop: [{
8270
+ command: `{ ${stateCurl("cursor", "waiting", "done")}; }`,
8271
+ timeout: 5
8272
+ }] }
8273
+ }, null, 2));
8274
+ return dir;
8275
+ }
8276
+ /** `stop` carries how the turn ended: finished, interrupted (back at the
8277
+ * prompt, nothing to push) or failed (worth reading). */
8278
+ function readHook$3(p, reported) {
8279
+ if (p.status === "aborted") return { state: "idle" };
8280
+ if (p.status === "error") return {
8281
+ reason: "attention",
8282
+ summary: "The turn failed"
8283
+ };
8284
+ return { reason: reported };
7919
8285
  }
7920
- /** Cursor's plugin-dir hooks can't carry stop/prompt events (those load only
7921
- * from ~/.cursor/hooks.json) and it has no permission-prompt hook at all,
7922
- * so its sessions get working/idle from pane activity (see scraper.ts) and
7923
- * nothing else — a stopped turn and a question look the same. */
7924
8286
  var cursor = {
7925
- meta: meta_default$3,
8287
+ meta: meta$3,
8288
+ launchFlags: () => [
8289
+ "--trust",
8290
+ "--plugin-dir",
8291
+ pluginDir()
8292
+ ],
8293
+ readHook: readHook$3,
7926
8294
  readyScreen: /Plan, search, build anything|Add a follow-up/,
7927
8295
  usage: () => cursorRow().then((row) => [row])
7928
8296
  };
@@ -7985,7 +8353,8 @@ function billingFromLogs() {
7985
8353
  if (!Object.keys(cfg).length) return null;
7986
8354
  return {
7987
8355
  config: cfg,
7988
- subscriptionTier: ctx.subscriptionTier
8356
+ subscriptionTier: ctx.subscriptionTier,
8357
+ loggedAt: Date.parse(str(latest.ts)) || null
7989
8358
  };
7990
8359
  }
7991
8360
  function parseRow(payload, auth, source) {
@@ -8019,7 +8388,8 @@ async function grokRow() {
8019
8388
  const loaded = await loadPayload("grok", () => auth?.token ? fetchBilling(auth.token) : Promise.resolve(null));
8020
8389
  const fromLog = loaded.payload ? null : billingFromLogs();
8021
8390
  const payload = loaded.payload ?? fromLog;
8022
- const source = fromLog ? "unified.jsonl" : loaded.source;
8391
+ const loggedAt = num(fromLog?.loggedAt);
8392
+ const source = fromLog ? loggedAt ? `stale-cache ${Math.max(0, Math.round((Date.now() - loggedAt) / 1e3))}s` : "stale-cache" : loaded.source;
8023
8393
  const missingAuth = auth?.token ? void 0 : noAuthReason("grok");
8024
8394
  return markStale(payload ? parseRow(payload, auth, source) : {
8025
8395
  ...blankRow("grok", source),
@@ -8029,14 +8399,25 @@ async function grokRow() {
8029
8399
  //#endregion
8030
8400
  //#region src/agents/grok/index.ts
8031
8401
  var meta$2 = meta_default$2;
8032
- /** Grok hook events → state. Payloads are camelCase (hookEventName,
8033
- * notificationType, lastAssistantMessage). Notification is matched to the
8034
- * two kinds that mean a prompt is on screen; Stop fires on shutdown too,
8035
- * with a reason readHook uses to tell a finished turn from a closed
8036
- * session. */
8037
- var HOOKS = claudeStyleHooks([
8402
+ /** Grok hook events → state: the busy/idle indicator its hook guide spells
8403
+ * out, five registrations and two more. UserPromptSubmit starts a turn;
8404
+ * Stop, StopFailure and StopCancelled end it however it ended (readHook
8405
+ * tells an interrupt from a cut-off); the `idle_prompt` Notification is
8406
+ * the backstop for the turns that report none of the three, and
8407
+ * SessionEnd for a session that exits first. A tool call is not "working"
8408
+ * of itself — Grok's matchers are Rust regexes with no way to except one
8409
+ * tool — so PreToolUse only catches the question card, which stops the
8410
+ * turn on the user, and PostToolUse (the question answered, or any tool
8411
+ * done) puts it back to work. Payloads are camelCase (hookEventName,
8412
+ * notificationType, lastAssistantMessage). */
8413
+ var HOOKS = claudeStyleHooks("grok", [
8038
8414
  ["UserPromptSubmit", "working"],
8039
- ["PreToolUse", "working"],
8415
+ [
8416
+ "PreToolUse",
8417
+ "waiting",
8418
+ "attention",
8419
+ "^ask_user_question$"
8420
+ ],
8040
8421
  ["PostToolUse", "working"],
8041
8422
  [
8042
8423
  "Notification",
@@ -8045,23 +8426,49 @@ var HOOKS = claudeStyleHooks([
8045
8426
  "permission_prompt|elicitation_dialog"
8046
8427
  ],
8047
8428
  [
8048
- "Stop",
8049
- "waiting",
8050
- "done"
8429
+ "Notification",
8430
+ "idle",
8431
+ void 0,
8432
+ "^idle_prompt$"
8051
8433
  ],
8052
8434
  [
8053
- "StopCancelled",
8435
+ "Stop",
8054
8436
  "waiting",
8055
8437
  "done"
8056
8438
  ],
8439
+ ["StopCancelled", "idle"],
8057
8440
  [
8058
8441
  "StopFailure",
8059
8442
  "waiting",
8060
8443
  "attention"
8061
- ]
8444
+ ],
8445
+ ["SessionEnd", "idle"]
8062
8446
  ], { timeout: 5 });
8063
- var HOOK_FILE = () => path.join(config.dataDir, "grok-hooks.json");
8064
- var REGISTRY = () => path.join(process.env.HOME || "", ".grok", "hooks-paths");
8447
+ /** Where Grok keeps its config, the registry among it. */
8448
+ var GROK_HOME = () => process.env.GROK_HOME || path.join(os.homedir(), ".grok");
8449
+ var REGISTRY = () => path.join(GROK_HOME(), "hooks-paths");
8450
+ /** work's hook file, by its real path: Grok's sandbox refuses to start
8451
+ * while a registry line runs through a symlink. */
8452
+ function hookFile() {
8453
+ let dir = config.dataDir;
8454
+ try {
8455
+ dir = fs.realpathSync(dir);
8456
+ } catch {}
8457
+ return path.join(dir, "grok-hooks.json");
8458
+ }
8459
+ /** A data dir under the system temp dir is a scratch server's: naming its
8460
+ * hook file in the user's registry would outlive it, and a registry line
8461
+ * whose file is gone keeps Grok's sandbox from starting at all. */
8462
+ function scratch() {
8463
+ const tmp = (() => {
8464
+ try {
8465
+ return fs.realpathSync(os.tmpdir());
8466
+ } catch {
8467
+ return os.tmpdir();
8468
+ }
8469
+ })();
8470
+ return hookFile().startsWith(tmp + path.sep);
8471
+ }
8065
8472
  /** Whether Grok's hook registry names work's hook file — without it Grok
8066
8473
  * reports nothing and the screen has to speak for it. Read from disk, not
8067
8474
  * remembered from a launch: a session that outlives the app process still
@@ -8069,20 +8476,24 @@ var REGISTRY = () => path.join(process.env.HOME || "", ".grok", "hooks-paths");
8069
8476
  * back on a session that reports for itself. */
8070
8477
  function registered() {
8071
8478
  try {
8072
- const file = HOOK_FILE();
8479
+ const file = hookFile();
8073
8480
  return fs.readFileSync(REGISTRY(), "utf8").split("\n").some((line) => line.trim() === file);
8074
8481
  } catch {
8075
8482
  return false;
8076
8483
  }
8077
8484
  }
8078
- /** Grok has no flag for hooks. It discovers them from `~/.grok/hooks-paths`:
8485
+ /** Grok has no flag for hooks. It discovers them from `$GROK_HOME/hooks-paths`:
8079
8486
  * one absolute path per line, the registry its own `/hooks-add` command
8080
8487
  * appends to. work's hook file is kept in the data dir — the commands read
8081
8488
  * the port from env, so the file never needs rewriting — and named there
8082
8489
  * once. The one line in that registry is the only thing work writes
8083
- * outside its own dir for any agent. */
8490
+ * outside its own dir for any agent; lines naming a work hook file that is
8491
+ * gone (a data dir moved or removed) are dropped as it is written, since
8492
+ * Grok's sandbox will not start while one is there, and a stale one on
8493
+ * another port reported every event twice. */
8084
8494
  function registerHooks() {
8085
- const file = HOOK_FILE();
8495
+ if (scratch()) return;
8496
+ const file = hookFile();
8086
8497
  writeIfChanged(file, JSON.stringify({ hooks: HOOKS }, null, 2));
8087
8498
  const registry = REGISTRY();
8088
8499
  try {
@@ -8090,17 +8501,74 @@ function registerHooks() {
8090
8501
  try {
8091
8502
  current = fs.readFileSync(registry, "utf8");
8092
8503
  } catch {}
8093
- if (!current.split("\n").some((line) => line.trim() === file)) {
8504
+ const lines = current.split("\n").filter((line) => {
8505
+ const target = line.trim();
8506
+ return target === file || !target.endsWith(`${path.sep}grok-hooks.json`) || fs.existsSync(target);
8507
+ });
8508
+ if (!lines.some((line) => line.trim() === file)) {
8509
+ while (lines.length && !lines.at(-1).trim()) lines.pop();
8510
+ lines.push(file, "");
8511
+ }
8512
+ const next = lines.join("\n");
8513
+ if (next !== current) {
8094
8514
  fs.mkdirSync(path.dirname(registry), { recursive: true });
8095
- fs.appendFileSync(registry, `${current && !current.endsWith("\n") ? "\n" : ""}${file}\n`);
8515
+ fs.writeFileSync(registry, next);
8096
8516
  }
8097
8517
  } catch {}
8098
8518
  }
8099
- function readHook$1(p, reported) {
8100
- if (str$1(p.subagentType)) return { ignore: true };
8101
- if (p.hookEventName === "stop" && str$1(p.reason) && p.reason !== "end_turn") return { ignore: true };
8519
+ /** The turns each Grok session has started, oldest first, so a report
8520
+ * for a turn that has since been replaced is known for what it is: a
8521
+ * cancelled turn's report is sent off Grok's command loop, and can land
8522
+ * after the next turn's UserPromptSubmit. */
8523
+ var TURNS_KEPT = 32;
8524
+ var turns = /* @__PURE__ */ new Map();
8525
+ function noteTurn(session, prompt) {
8526
+ let list = turns.get(session);
8527
+ if (!list) {
8528
+ turns.set(session, list = []);
8529
+ if (turns.size > 200) turns.delete(turns.keys().next().value);
8530
+ }
8531
+ list.push(prompt);
8532
+ if (list.length > TURNS_KEPT) list.shift();
8533
+ }
8534
+ /** A turn-end report for a turn the session has moved on from. One for a
8535
+ * turn it never saw start (an interrupted bash-mode command) is not: it
8536
+ * settles like any other. */
8537
+ function superseded(session, prompt) {
8538
+ const list = turns.get(session);
8539
+ if (!list) return false;
8540
+ const at = list.indexOf(prompt);
8541
+ return at >= 0 && at < list.length - 1;
8542
+ }
8543
+ /** StopCancelled reasons the agent chose rather than the user: cut off,
8544
+ * the session is waiting on them to say what next. Anything else — an
8545
+ * interrupt, a declined or dismissed prompt, a reason Grok adds later —
8546
+ * is the user having stopped it. */
8547
+ var RUNTIME_STOPS = /* @__PURE__ */ new Set(["max_turns", "no_progress"]);
8548
+ function readHook$2(p, reported, state) {
8549
+ const event = str$1(p.hook_event_name);
8550
+ const session = str$1(p.sessionId);
8551
+ const prompt = str$1(p.promptId);
8552
+ if (str$1(p.subagentType) && notificationType(p) !== "permission_prompt") return { ignore: true };
8553
+ if (event === "UserPromptSubmit" && session && prompt) noteTurn(session, prompt);
8554
+ if ((event === "Stop" || event === "StopCancelled" || event === "StopFailure") && session && prompt && superseded(session, prompt)) return { ignore: true };
8555
+ if (event === "Stop" && str$1(p.reason) && p.reason !== "end_turn") return { state: "idle" };
8556
+ if (event === "Stop" && Array.isArray(p.backgroundTasks) && p.backgroundTasks.some((task) => task?.type === "subagent")) return { ignore: true };
8557
+ if (event === "StopCancelled") {
8558
+ if (!RUNTIME_STOPS.has(str$1(p.reason))) return { state: "idle" };
8559
+ return {
8560
+ state: "waiting",
8561
+ reason: "attention",
8562
+ summary: cleanSummary(str$1(p.reasonDetails) || str$1(p.lastAssistantMessage) || (p.reason === "max_turns" ? "Stopped at the turn limit" : "Stopped making progress"))
8563
+ };
8564
+ }
8565
+ if (state === "idle") return { state: "idle" };
8566
+ if (event === "PreToolUse") return {
8567
+ reason: "attention",
8568
+ summary: cleanSummary(str$1((p.toolInput ?? {}).questions?.[0]?.question)) ?? "Asking a question"
8569
+ };
8102
8570
  const read = readClaudeStyle(p, reported);
8103
- if (!read.summary) {
8571
+ if (!read.ignore && !read.summary) {
8104
8572
  const err = str$1(p.errorDetails) || str$1(p.error);
8105
8573
  if (err) read.summary = cleanSummary(err);
8106
8574
  }
@@ -8115,7 +8583,7 @@ var grok = {
8115
8583
  reportsWorking: registered,
8116
8584
  promptAsArgv: true,
8117
8585
  envPrefixes: ["GROK_"],
8118
- readHook: readHook$1,
8586
+ readHook: readHook$2,
8119
8587
  usage: () => grokRow().then((row) => [row])
8120
8588
  };
8121
8589
  //#endregion
@@ -8133,7 +8601,7 @@ const post = async (state, reason, summary) => {
8133
8601
  if (!session) return;
8134
8602
  const port = process.env.WORK_APP_PORT || "${appPort}";
8135
8603
  const qs =
8136
- \`session=\${encodeURIComponent(session)}&state=\${state}\` +
8604
+ \`session=\${encodeURIComponent(session)}&agent=opencode&state=\${state}\` +
8137
8605
  (reason ? \`&reason=\${reason}\` : "");
8138
8606
  try {
8139
8607
  await fetch(\`http://127.0.0.1:\${port}/api/agent-state?\${qs}\`, {
@@ -8145,19 +8613,51 @@ const post = async (state, reason, summary) => {
8145
8613
  } catch {}
8146
8614
  };
8147
8615
 
8148
- export const WorkSessionState = async () => {
8616
+ export const WorkSessionState = async ({ client } = {}) => {
8149
8617
  // A subagent's turn is not the pane's: task sessions carry a parentID,
8150
8618
  // and their busy/idle would otherwise end the turn the user is watching.
8619
+ // One this process never saw created (a resumed session's subagent,
8620
+ // restarted by task id) is looked up once.
8151
8621
  const children = new Set();
8622
+ const roots = new Set();
8152
8623
  const roles = new Map();
8153
8624
  const lastText = new Map();
8154
- const mine = (id) => !!id && !children.has(id);
8625
+ // Sessions whose turn has already been reported ended (so an idle that
8626
+ // follows another, old and new events both, reports once), and those
8627
+ // whose turn ended on an error (whose report was the error).
8628
+ const settled = new Set();
8629
+ const errored = new Set();
8630
+ const child = async (id) => {
8631
+ if (!id || children.has(id)) return true;
8632
+ if (roots.has(id)) return false;
8633
+ try {
8634
+ const res = await client?.session?.get?.({ sessionID: id, path: { id } });
8635
+ if ((res?.data ?? res)?.parentID) {
8636
+ children.add(id);
8637
+ return true;
8638
+ }
8639
+ } catch {}
8640
+ roots.add(id);
8641
+ return false;
8642
+ };
8643
+ const turnEnd = (id) => {
8644
+ if (settled.has(id)) return;
8645
+ settled.add(id);
8646
+ const summary = lastText.get(id);
8647
+ lastText.delete(id);
8648
+ roles.clear();
8649
+ // The error was the report; "Finished" after it would replace it, and
8650
+ // clear the session's call for attention with it.
8651
+ if (errored.delete(id)) return;
8652
+ return post("waiting", "done", summary);
8653
+ };
8155
8654
  return {
8156
8655
  event: async ({ event }) => {
8157
8656
  const type = event?.type;
8158
8657
  const p = event?.properties || {};
8159
8658
  if (type === "session.created" || type === "session.updated") {
8160
8659
  if (p.info?.parentID) children.add(p.info.id);
8660
+ else if (p.info?.id) roots.add(p.info.id);
8161
8661
  return;
8162
8662
  }
8163
8663
  // Text parts carry no role; the message they belong to does, and a
@@ -8171,22 +8671,9 @@ export const WorkSessionState = async () => {
8171
8671
  lastText.set(p.part.sessionID, p.part.text);
8172
8672
  return;
8173
8673
  }
8174
- if (!mine(p.sessionID)) return;
8175
- if (type === "session.status" && p.status?.type === "busy")
8176
- return post("working");
8177
- if (type === "session.idle") {
8178
- const summary = lastText.get(p.sessionID);
8179
- lastText.delete(p.sessionID);
8180
- roles.clear();
8181
- return post("waiting", "done", summary);
8182
- }
8183
- if (type === "session.error")
8184
- return post(
8185
- "waiting",
8186
- "attention",
8187
- p.error?.data?.message || p.error?.name,
8188
- );
8189
- // A prompt is on screen and nothing moves until it is answered.
8674
+ // A prompt is on screen and nothing moves until it is answered —
8675
+ // whichever session raised it: the TUI shows a subagent's with the
8676
+ // session's own.
8190
8677
  if (type === "permission.asked")
8191
8678
  return post(
8192
8679
  "waiting",
@@ -8195,7 +8682,44 @@ export const WorkSessionState = async () => {
8195
8682
  .filter(Boolean)
8196
8683
  .join(": "),
8197
8684
  );
8198
- if (type === "permission.replied") return post("working");
8685
+ if (type === "question.asked")
8686
+ return post(
8687
+ "waiting",
8688
+ "attention",
8689
+ p.questions?.[0]?.question || "Asking a question",
8690
+ );
8691
+ if (
8692
+ type === "permission.replied" ||
8693
+ type === "question.replied" ||
8694
+ type === "question.rejected"
8695
+ )
8696
+ return post("working");
8697
+ if (!p.sessionID || (await child(p.sessionID))) return;
8698
+ // session.status is the current event (idle | retry | busy);
8699
+ // session.idle the older one, which builds still send beside it.
8700
+ if (type === "session.status") {
8701
+ if (p.status?.type === "busy") {
8702
+ settled.delete(p.sessionID);
8703
+ errored.delete(p.sessionID);
8704
+ return post("working");
8705
+ }
8706
+ if (p.status?.type === "idle") return turnEnd(p.sessionID);
8707
+ return;
8708
+ }
8709
+ if (type === "session.idle") return turnEnd(p.sessionID);
8710
+ if (type === "session.error") {
8711
+ // Stopped by the user: back at the prompt, nothing to push.
8712
+ if (p.error?.name === "MessageAbortedError") {
8713
+ settled.add(p.sessionID);
8714
+ return post("idle");
8715
+ }
8716
+ errored.add(p.sessionID);
8717
+ return post(
8718
+ "waiting",
8719
+ "attention",
8720
+ p.error?.data?.message || p.error?.name,
8721
+ );
8722
+ }
8199
8723
  },
8200
8724
  };
8201
8725
  };
@@ -8205,7 +8729,7 @@ var PLUGIN_FILE = () => path.join(config.dataDir, "opencode-plugin.js");
8205
8729
  * screen has to speak for the session. Checked on disk rather than
8206
8730
  * remembered from a launch, so a session that outlives the app process
8207
8731
  * is still known to report for itself. */
8208
- function installed() {
8732
+ function installed$1() {
8209
8733
  return fs.existsSync(PLUGIN_FILE());
8210
8734
  }
8211
8735
  /** opencode resolves a `plugin` entry that looks like a path as a local
@@ -8219,6 +8743,114 @@ function pluginConfig() {
8219
8743
  } catch {}
8220
8744
  return JSON.stringify({ plugin: [file] });
8221
8745
  }
8746
+ function readHook$1(p, reported) {
8747
+ const summary = str$1(p.summary);
8748
+ return {
8749
+ reason: reported,
8750
+ summary: summary ? cleanSummary(summary) : void 0
8751
+ };
8752
+ }
8753
+ var opencode = {
8754
+ meta: meta$1,
8755
+ readyScreen: /Ask anything/,
8756
+ launchEnv: () => ({ OPENCODE_CONFIG_CONTENT: pluginConfig() }),
8757
+ reportsWorking: installed$1,
8758
+ envPrefixes: ["OPENCODE_"],
8759
+ readHook: readHook$1
8760
+ };
8761
+ //#endregion
8762
+ //#region src/agents/pi/index.ts
8763
+ var meta = meta_default;
8764
+ /** Pi's hooks are extensions: a module loaded with `-e <path>` (repeatable,
8765
+ * alongside the user's own, no config written) that subscribes to the
8766
+ * agent's events. `agent_settled` is the end of a turn — `agent_end` fires
8767
+ * after every run, and Pi may still retry, compact or carry on with a
8768
+ * queued follow-up — and `ui_prompt_start`/`_end` bracket an extension's
8769
+ * prompt, the one way Pi stops on the user. A Pi older than
8770
+ * `agent_settled` (0.80.4) never sends it, so a run's end waits a moment
8771
+ * for one and reports itself when none comes. The extension posts its own
8772
+ * reports, as opencode's plugin does; `readHook` only unwraps them. */
8773
+ var EXTENSION = (appPort) => `// Generated by work. Reports this
8774
+ // session's working / needs-input / done state to the app; edits are
8775
+ // overwritten. Port and session id come from the env tmux gave the pane.
8776
+ const post = async (state, reason, summary) => {
8777
+ const session = process.env.WORK_SESSION_ID;
8778
+ if (!session) return;
8779
+ const port = process.env.WORK_APP_PORT || "${appPort}";
8780
+ const qs =
8781
+ \`session=\${encodeURIComponent(session)}&agent=pi&state=\${state}\` +
8782
+ (reason ? \`&reason=\${reason}\` : "");
8783
+ try {
8784
+ await fetch(\`http://127.0.0.1:\${port}/api/agent-state?\${qs}\`, {
8785
+ method: "POST",
8786
+ headers: { "content-type": "application/json" },
8787
+ body: JSON.stringify({ summary }),
8788
+ signal: AbortSignal.timeout(3000),
8789
+ });
8790
+ } catch {}
8791
+ };
8792
+
8793
+ const textOf = (message) =>
8794
+ (Array.isArray(message?.content) ? message.content : [])
8795
+ .filter((part) => part?.type === "text" && part.text)
8796
+ .map((part) => part.text)
8797
+ .join("\\n");
8798
+
8799
+ export default function (pi) {
8800
+ // The run's last assistant message, for the summary and how it ended.
8801
+ let last = null;
8802
+ // This Pi sends agent_settled; until one arrives, a run's end waits.
8803
+ let settles = false;
8804
+ let fallback = null;
8805
+ const settle = () => {
8806
+ clearTimeout(fallback);
8807
+ fallback = null;
8808
+ const message = last;
8809
+ last = null;
8810
+ if (message?.stopReason === "aborted") return post("idle");
8811
+ if (message?.stopReason === "error")
8812
+ return post("waiting", "attention", message.errorMessage || textOf(message));
8813
+ return post("waiting", "done", textOf(message));
8814
+ };
8815
+ pi.on("agent_start", () => {
8816
+ clearTimeout(fallback);
8817
+ fallback = null;
8818
+ return post("working");
8819
+ });
8820
+ pi.on("agent_end", (event) => {
8821
+ const messages = Array.isArray(event?.messages) ? event.messages : [];
8822
+ last = [...messages].reverse().find((m) => m?.role === "assistant") ?? last;
8823
+ if (!settles) fallback = setTimeout(settle, 1500);
8824
+ });
8825
+ pi.on("agent_settled", (_event, ctx) => {
8826
+ settles = true;
8827
+ // Another extension may have started a run already.
8828
+ if (ctx?.isIdle && !ctx.isIdle()) return;
8829
+ return settle();
8830
+ });
8831
+ pi.on("ui_prompt_start", (event) =>
8832
+ post("waiting", "attention", event?.title || "Waiting for input"),
8833
+ );
8834
+ pi.on("ui_prompt_end", (_event, ctx) =>
8835
+ post(ctx?.isIdle && ctx.isIdle() ? "idle" : "working"),
8836
+ );
8837
+ }
8838
+ `;
8839
+ var EXTENSION_FILE = () => path.join(config.dataDir, "pi-extension.ts");
8840
+ /** Whether the extension is on disk for Pi to load — without it the
8841
+ * screen has to speak for the session. Checked on disk rather than
8842
+ * remembered from a launch, so a session that outlives the app process
8843
+ * is still known to report for itself. */
8844
+ function installed() {
8845
+ return fs.existsSync(EXTENSION_FILE());
8846
+ }
8847
+ function extensionFile() {
8848
+ const file = EXTENSION_FILE();
8849
+ try {
8850
+ writeIfChanged(file, EXTENSION(config.appPort));
8851
+ } catch {}
8852
+ return file;
8853
+ }
8222
8854
  function readHook(p, reported) {
8223
8855
  const summary = str$1(p.summary);
8224
8856
  return {
@@ -8237,17 +8869,13 @@ var AGENTS = [
8237
8869
  copilot,
8238
8870
  cursor,
8239
8871
  grok,
8872
+ opencode,
8240
8873
  {
8241
- meta: meta$1,
8242
- readyScreen: /Ask anything/,
8243
- launchEnv: () => ({ OPENCODE_CONFIG_CONTENT: pluginConfig() }),
8874
+ meta,
8875
+ launchFlags: () => ["-e", extensionFile()],
8244
8876
  reportsWorking: installed,
8245
- envPrefixes: ["OPENCODE_"],
8877
+ envPrefixes: ["PI_"],
8246
8878
  readHook
8247
- },
8248
- {
8249
- meta: meta_default,
8250
- envPrefixes: ["PI_"]
8251
8879
  }
8252
8880
  ];
8253
8881
  var byFamily = new Map(AGENTS.map((a) => [a.meta.id, a]));
@@ -8256,13 +8884,13 @@ function agentFor(id) {
8256
8884
  return id ? byFamily.get(agentFamily(id)) : void 0;
8257
8885
  }
8258
8886
  /** Extra argv for an agent CLI so it reports working/waiting state. */
8259
- function agentLaunchFlags(id) {
8260
- return agentFor(id)?.launchFlags?.() ?? [];
8887
+ function agentLaunchFlags(id, ctx) {
8888
+ return agentFor(id)?.launchFlags?.(ctx) ?? [];
8261
8889
  }
8262
8890
  /** Env for a spawned pane so the agent CLI reports state — the families
8263
8891
  * configured by environment rather than argv. */
8264
- function agentLaunchEnv(id) {
8265
- return agentFor(id)?.launchEnv?.() ?? {};
8892
+ function agentLaunchEnv(id, ctx) {
8893
+ return agentFor(id)?.launchEnv?.(ctx) ?? {};
8266
8894
  }
8267
8895
  /** Whether the agent says when it *starts* working, not just when it stops
8268
8896
  * — believing the screen over that costs it the state its own hook just
@@ -8270,6 +8898,14 @@ function agentLaunchEnv(id) {
8270
8898
  function reportsWorking(id) {
8271
8899
  return agentFor(id)?.reportsWorking?.() ?? false;
8272
8900
  }
8901
+ /** The pane title a CLI shows at its prompt, if it sets one. */
8902
+ function idleTitle(id) {
8903
+ return agentFor(id)?.idleTitle;
8904
+ }
8905
+ /** The CLI draws menus before its input box that work answers. */
8906
+ function hasStartupPrompt(id) {
8907
+ return !!agentFor(id)?.startupPrompt;
8908
+ }
8273
8909
  /** Keys that answer whatever startup prompt the CLI is showing, if any. */
8274
8910
  function startupPromptKeys(id, screen) {
8275
8911
  return agentFor(id)?.startupPrompt?.(screen);
@@ -8294,10 +8930,10 @@ function agentPathDirs(home) {
8294
8930
  }
8295
8931
  /** What a hook payload the session's CLI POSTed means. Unknown families
8296
8932
  * (or an unreadable body) keep the reported reason and carry no summary. */
8297
- function readHookPayload(id, payload, reported) {
8933
+ function readHookPayload(id, payload, reported, state) {
8298
8934
  if (!payload || typeof payload !== "object") return { reason: reported };
8299
8935
  const read = agentFor(id)?.readHook;
8300
- return read ? read(payload, reported) : { reason: reported };
8936
+ return read ? read(payload, reported, state) : { reason: reported };
8301
8937
  }
8302
8938
  //#endregion
8303
8939
  //#region src/server/sessions.ts
@@ -8481,18 +9117,19 @@ async function listPanesNow() {
8481
9117
  "list-panes",
8482
9118
  "-a",
8483
9119
  "-F",
8484
- "#{session_name} #{pane_current_command} #{pane_pid} #{window_activity}"
9120
+ "#{session_name} #{pane_current_command} #{pane_pid} #{window_activity} #{pane_title}"
8485
9121
  ]).catch(() => null);
8486
9122
  if (out === null) return null;
8487
9123
  const panes = [];
8488
9124
  for (const line of out.split("\n")) {
8489
9125
  if (!line) continue;
8490
- const [name, command, pid, stamp] = line.split(" ");
9126
+ const [name, command, pid, stamp, ...title] = line.split(" ");
8491
9127
  panes.push({
8492
9128
  id: name?.startsWith("wk-") ? name.slice(3) : void 0,
8493
9129
  command: command || "",
8494
9130
  pid: Number(pid),
8495
- activityAt: Number(stamp) * 1e3
9131
+ activityAt: Number(stamp) * 1e3,
9132
+ title: title.join(" ")
8496
9133
  });
8497
9134
  }
8498
9135
  lastPanes = {
@@ -8649,20 +9286,20 @@ function newSessionArgs(session, wsPath, mode, initialPrompt, size) {
8649
9286
  tmuxName(session.id),
8650
9287
  "-c",
8651
9288
  wsPath,
8652
- ...sessionEnvArgs(session.id, session.agent),
8653
- ...loginShellCommand(session.agent, initialPrompt)
9289
+ ...sessionEnvArgs(session.id, session.agent, wsPath),
9290
+ ...loginShellCommand(session.agent, wsPath, initialPrompt)
8654
9291
  ];
8655
9292
  }
8656
9293
  /** Run the agent through a login shell so the pane's PATH matches the
8657
9294
  * user's interactive terminal (mise activation, cargo, custom bins) —
8658
9295
  * a service manager's PATH is far slimmer than a login shell's. */
8659
- function loginShellCommand(agent, initialPrompt) {
9296
+ function loginShellCommand(agent, cwd, initialPrompt) {
8660
9297
  const words = sessionCommand(agent);
8661
9298
  if (!words.length) return [];
8662
9299
  const line = [
8663
9300
  ...words,
8664
- ...agentLaunchFlags(agent),
8665
- ...initialPrompt ? [initialPrompt] : []
9301
+ ...agentLaunchFlags(agent, { cwd }),
9302
+ ...initialPrompt ? initialPrompt.startsWith("-") ? ["--", initialPrompt] : [initialPrompt] : []
8666
9303
  ].map(shellQuote).join(" ");
8667
9304
  return [
8668
9305
  process.env.SHELL || "bash",
@@ -8691,6 +9328,14 @@ function cleanEnv() {
8691
9328
  env.PATH = [...parts, ...bins.filter((bin) => !parts.includes(bin))].join(":");
8692
9329
  return forceUtf8Locale(env);
8693
9330
  }
9331
+ /** Answer the menus a CLI draws before its input box (a folder-trust
9332
+ * question) on a pane an attach just created, as sending a first prompt
9333
+ * does — so a session opened with nothing to send is not left on one. */
9334
+ async function settleStartup(id) {
9335
+ const session = getSession(id);
9336
+ if (!session || !hasStartupPrompt(session.agent)) return;
9337
+ await waitForAgent(tmuxName(id), cleanEnv(), session.agent).catch(() => {});
9338
+ }
8694
9339
  /** Upper bound on waiting for an agent to be ready for input; past it
8695
9340
  * the paste goes in regardless (a TUI with a boot animation never settles). */
8696
9341
  var AGENT_BOOT_MS = 8e3;
@@ -8780,13 +9425,13 @@ function sessionCommand(agent) {
8780
9425
  /** tmux new-session env flags so the agent's hooks can report back which
8781
9426
  * session they belong to, plus whatever the family needs to load its own
8782
9427
  * hooks (opencode names its plugin here — tmux quotes it, not the shell). */
8783
- function sessionEnvArgs(sessionId, agent) {
9428
+ function sessionEnvArgs(sessionId, agent, cwd) {
8784
9429
  return [
8785
9430
  "-e",
8786
9431
  `WORK_SESSION_ID=${sessionId}`,
8787
9432
  "-e",
8788
9433
  `WORK_APP_PORT=${config.appPort}`,
8789
- ...Object.entries(agentLaunchEnv(agent)).flatMap(([key, value]) => ["-e", `${key}=${value}`])
9434
+ ...Object.entries(agentLaunchEnv(agent, { cwd })).flatMap(([key, value]) => ["-e", `${key}=${value}`])
8790
9435
  ];
8791
9436
  }
8792
9437
  function tmuxName(sessionId) {
@@ -8796,4 +9441,4 @@ function sleep(ms) {
8796
9441
  return new Promise((resolve) => setTimeout(resolve, ms));
8797
9442
  }
8798
9443
  //#endregion
8799
- export { allPrefs as $, agentBadge as A, setInstance as B, readHookPayload as C, TMUX_SOCKET as Ct, setAgentPhase as D, tmux as Dt, clearAgentPhase as E, run$1 as Et, ACCENT_RE as F, publish as G, pushPublicKey as H, getInstance as I, subscriberCount as J, publishJson as K, iconSvg as L, agentFromCommand as M, agentLabel as N, setScrapedPhase as O, config as Ot, agentMeta as P, agentChoices as Q, manifest as R, AGENTS as S, workspacePath as St, blankRow as T, git as Tt, saveSubscription as U, deleteSubscription as V, sendPush as W, swr as X, shared as Y, withLock as Z, renameSession as _, agentLabel$1 as _t, ensureTmuxServer as a, setPref as at, tmuxName as b, db as bt, killSession as c, doctor as ct, listPanesWithin as d, miseVersions as dt, disabledAgents as et, listSessions as f, setupPath as ft, panePids as g, agentById as gt, notifySessionsChanged as h, TOOLS as ht, disposeSessions as i, setDisabledAgents as it, agentFamily as j, setSessionViewers as k, killWorkspaceSessions as l, ghKnownHosts as lt, newSessionArgs as m, DEFAULT_GITHUB_HOST as mt, clearTmuxStarted as n, getSetting as nt, forgetPanes as o, setSetting as ot, markTmuxStarted as p, which as pt, subscribe as q, createSession as r, preferredAgent as rt, getSession as s, discoverAgents as st, cleanEnv as t, enabledAgents as tt, listPanes as u, miseInstalls as ut, sendToSession as v, toolById as vt, reportsWorking as w, gh as wt, tmuxWasStarted as x, get as xt, sessionStates as y, all as yt, normalizeAccent as z };
9444
+ export { validSubscription as $, hookPhase as A, get as At, agentFromCommand as B, AGENTS as C, DEFAULT_GITHUB_HOST as Ct, blankRow as D, toolById as Dt, reportsWorking as E, agentLabel$1 as Et, setScrapedPhase as F, run$1 as Ft, iconSvg as G, agentMeta as H, setSessionViewers as I, tmux as It, setInstance as J, manifest as K, settleQuietWorking as L, config as Lt, noteUserAnswered as M, TMUX_SOCKET as Mt, onPhaseChange as N, gh as Nt, agentPhase as O, all as Ot, setAgentPhase as P, git as Pt, sendPush as Q, agentBadge as R, tmuxWasStarted as S, which as St, readHookPayload as T, agentById as Tt, ACCENT_RE as U, agentLabel as V, getInstance as W, pushPublicKey as X, deleteSubscription as Y, saveSubscription as Z, renameSession as _, doctor as _t, ensureTmuxServer as a, swr as at, settleStartup as b, miseVersions as bt, killSession as c, allPrefs as ct, listPanesWithin as d, getSetting as dt, publish as et, listSessions as f, preferredAgent as ft, panePids as g, discoverAgents as gt, notifySessionsChanged as h, setSetting as ht, disposeSessions as i, shared as it, hookReportedAt as j, workspacePath as jt, clearAgentPhase as k, db as kt, killWorkspaceSessions as l, disabledAgents as lt, newSessionArgs as m, setPref as mt, clearTmuxStarted as n, subscribe as nt, forgetPanes as o, withLock as ot, markTmuxStarted as p, setDisabledAgents as pt, normalizeAccent as q, createSession as r, subscriberCount as rt, getSession as s, agentChoices as st, cleanEnv as t, publishJson as tt, listPanes as u, enabledAgents as ut, sendToSession as v, ghKnownHosts as vt, idleTitle as w, TOOLS as wt, tmuxName as x, setupPath as xt, sessionStates as y, miseInstalls as yt, agentFamily as z };