@dylanpiercey/work 0.2.19 → 0.2.20
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.
- package/README.md +3 -1
- package/bin/cli/plan.mjs +85 -0
- package/bin/cli/session.mjs +267 -31
- package/bin/cli/topic.mjs +63 -40
- package/dist/_BHlbSSAl.js +182 -0
- package/dist/_C9c_BxRk.js +787 -0
- package/dist/_DrEeyoLF.js +2 -0
- package/dist/_E0fWorAK.js +2 -0
- package/dist/{_CsokArOR.js → _Hb3LeA5Q.js} +218 -58
- package/dist/index.mjs +3456 -2299
- package/dist/public/assets/_-TUXzZ5j.js +1 -0
- package/dist/public/assets/_-TUXzZ5j.js.br +0 -0
- package/dist/public/assets/_0BO-2AH8.js +1 -0
- package/dist/public/assets/_1MkpPhCv.js +1 -0
- package/dist/public/assets/_2Czzn44S.js +2 -0
- package/dist/public/assets/_2Czzn44S.js.br +0 -0
- package/dist/public/assets/{_B9qGqvit.js → _2M3NjHcy.js} +1 -1
- package/dist/public/assets/_3J3NCTfA.js +1 -0
- package/dist/public/assets/_3yfaou9w.js +1 -0
- package/dist/public/assets/{_DSeQngGR.js → _6fcPfRKt.js} +1 -1
- package/dist/public/assets/_6fcPfRKt.js.br +0 -0
- package/dist/public/assets/_9kzNcuvf.js +1 -0
- package/dist/public/assets/_AbxeWvtk.js +1 -0
- package/dist/public/assets/{_D__ojGam.js → _B1AH7m_8.js} +1 -1
- package/dist/public/assets/_B1AH7m_8.js.br +0 -0
- package/dist/public/assets/{_BAWjkhA8.js → _B2zs9J9f.js} +1 -1
- package/dist/public/assets/_B2zs9J9f.js.br +0 -0
- package/dist/public/assets/_B4Tl4g-I.js +1 -0
- package/dist/public/assets/_B6GPBTnX.js +1 -0
- package/dist/public/assets/{_qxffRpjc.js → _B7u83Fqc.js} +1 -1
- package/dist/public/assets/_B89z0EK7.js +1 -0
- package/dist/public/assets/{_C6Yt6h_R.js → _BCYHTWZd.js} +1 -1
- package/dist/public/assets/_BCYHTWZd.js.br +0 -0
- package/dist/public/assets/_BD01_px3.js +1 -0
- package/dist/public/assets/_BDt0PKxz.js +1 -0
- package/dist/public/assets/{_0sTkxfPx.js → _BFD3zDfn.js} +1 -1
- package/dist/public/assets/_BFD3zDfn.js.br +0 -0
- package/dist/public/assets/{_CBEKHwUV.js → _BH5TfgM7.js} +1 -1
- package/dist/public/assets/_BH5TfgM7.js.br +0 -0
- package/dist/public/assets/_BHEzkZBl.js +1 -0
- package/dist/public/assets/{_CB52LkYt.js → _BHxaJLxf.js} +1 -1
- package/dist/public/assets/_BHxaJLxf.js.br +0 -0
- package/dist/public/assets/{_D8ac72_O.js → _BJc28pt5.js} +1 -1
- package/dist/public/assets/_BJc28pt5.js.br +0 -0
- package/dist/public/assets/{_BBCzcNZY.js → _BJpqJK9r.js} +1 -1
- package/dist/public/assets/_BJpqJK9r.js.br +0 -0
- package/dist/public/assets/_BKSNShOz.js +1 -0
- package/dist/public/assets/{_DTNiWZFr.js → _BKcc-iTi.js} +1 -1
- package/dist/public/assets/_BKcc-iTi.js.br +0 -0
- package/dist/public/assets/{_CG383oZY.js → _BMXIDoUj.js} +1 -1
- package/dist/public/assets/_BMXIDoUj.js.br +0 -0
- package/dist/public/assets/{_CEC5O89D.js → _BQMwlV-0.js} +1 -1
- package/dist/public/assets/_BQMwlV-0.js.br +0 -0
- package/dist/public/assets/_BUjgl28E.js +1 -0
- package/dist/public/assets/_BYUg95ML.js +1 -0
- package/dist/public/assets/{_CspPbDoP.js → _B_LxZatx.js} +1 -1
- package/dist/public/assets/_B_LxZatx.js.br +0 -0
- package/dist/public/assets/{_CfP0f0PU.js → _BeQUut65.js} +1 -1
- package/dist/public/assets/_BeQUut65.js.br +0 -0
- package/dist/public/assets/{_DJBR8Qwq.js → _Bf7EsJwj.js} +1 -1
- package/dist/public/assets/_Bf7EsJwj.js.br +0 -0
- package/dist/public/assets/_BfVmvTXe.js +1 -0
- package/dist/public/assets/_BfqDnPV7.js +1 -0
- package/dist/public/assets/{_BdSsUhCi.js → _Bfuu6mAX.js} +1 -1
- package/dist/public/assets/_Bfuu6mAX.js.br +0 -0
- package/dist/public/assets/{_CPaMvBMG.js → _BgBUZElB.js} +1 -1
- package/dist/public/assets/_BgBUZElB.js.br +0 -0
- package/dist/public/assets/{_CToqok6Q.js → _Bgbj7Vl5.js} +1 -1
- package/dist/public/assets/_Bgbj7Vl5.js.br +0 -0
- package/dist/public/assets/{_J3wdy6bJ.js → _BhrCWDl2.js} +1 -1
- package/dist/public/assets/_BhrCWDl2.js.br +0 -0
- package/dist/public/assets/_Bjsevhjr.js +1 -0
- package/dist/public/assets/_BjzBYn47.js +1 -0
- package/dist/public/assets/_BmyB1F-C.js +1 -0
- package/dist/public/assets/{_bDI-pCZR.js → _BpGvpggY.js} +1 -1
- package/dist/public/assets/_BpGvpggY.js.br +0 -0
- package/dist/public/assets/_BpjknrsF.js +1 -0
- package/dist/public/assets/{_BClUYs-k.js → _BpunB-I_.js} +1 -1
- package/dist/public/assets/_BpunB-I_.js.br +0 -0
- package/dist/public/assets/{_DsehEuMD.js → _Bqgtb-fc.js} +1 -1
- package/dist/public/assets/_Bqgtb-fc.js.br +0 -0
- package/dist/public/assets/_BtkDWVh9.js +1 -0
- package/dist/public/assets/{_zN8SypHW.js → _BvEJgNLp.js} +1 -1
- package/dist/public/assets/_BvEJgNLp.js.br +0 -0
- package/dist/public/assets/_BvYgAZl3.js +1 -0
- package/dist/public/assets/_C0Bfz_Is.js +1 -0
- package/dist/public/assets/_C1Aa6bJ9.js +1 -0
- package/dist/public/assets/_C3i3LRNx.js +1 -0
- package/dist/public/assets/_C4RHKQLq.js +1 -0
- package/dist/public/assets/{_DN7pTn2g.js → _C5Wk6L8u.js} +1 -1
- package/dist/public/assets/_C5Wk6L8u.js.br +0 -0
- package/dist/public/assets/_C7N1s7w_.js +1 -0
- package/dist/public/assets/_C7N1s7w_.js.br +0 -0
- package/dist/public/assets/_C96lrIeU.js +1 -0
- package/dist/public/assets/{_CPj_VL66.js → _CAhkFk0k.js} +1 -1
- package/dist/public/assets/_CAhkFk0k.js.br +0 -0
- package/dist/public/assets/{_bHL-NcWS.js → _CCmHKlXX.js} +1 -1
- package/dist/public/assets/_CCmHKlXX.js.br +0 -0
- package/dist/public/assets/{_DpbHp8yR.js → _CEY_n10t.js} +1 -1
- package/dist/public/assets/_CEY_n10t.js.br +0 -0
- package/dist/public/assets/_CEejNMib.js +1 -0
- package/dist/public/assets/_CFo4GZR9.js +1 -0
- package/dist/public/assets/{_Cmtl59QM.js → _CGH2Busx.js} +1 -1
- package/dist/public/assets/_CGH2Busx.js.br +0 -0
- package/dist/public/assets/{_DuyCZnNC.js → _CHwp4prQ.js} +1 -1
- package/dist/public/assets/_CHwp4prQ.js.br +0 -0
- package/dist/public/assets/{_C_D5TPb7.js → _CN7Wz2eA.js} +1 -1
- package/dist/public/assets/_CN7Wz2eA.js.br +0 -0
- package/dist/public/assets/_CP58TjsV.js +1 -0
- package/dist/public/assets/_CP58TjsV.js.br +0 -0
- package/dist/public/assets/_CPruMiA4.js +1 -0
- package/dist/public/assets/_CQEZuOE_.js +1 -0
- package/dist/public/assets/_CSMoDPwM.js +16 -0
- package/dist/public/assets/_CSMoDPwM.js.br +0 -0
- package/dist/public/assets/{_briyaWgK.js → _CVEabnQh.js} +1 -1
- package/dist/public/assets/_CVEabnQh.js.br +0 -0
- package/dist/public/assets/{_J1v2clxU.js → _CWTelCGM.js} +1 -1
- package/dist/public/assets/_CWTelCGM.js.br +0 -0
- package/dist/public/assets/_CXpplsaj.js +1 -0
- package/dist/public/assets/{_BhecGD7y.js → _CY0xLX0Y.js} +1 -1
- package/dist/public/assets/_CY0xLX0Y.js.br +0 -0
- package/dist/public/assets/{_dVHSDgPC.js → _CZNztOn6.js} +1 -1
- package/dist/public/assets/_CZNztOn6.js.br +0 -0
- package/dist/public/assets/{_Dtx52FYB.js → _Cabq459u.js} +1 -1
- package/dist/public/assets/_Cb593OOX.js +1 -0
- package/dist/public/assets/_Cbis2WSl.js +1 -0
- package/dist/public/assets/{_T5HFHZgl.js → _CeZa36O-.js} +1 -1
- package/dist/public/assets/_CeZa36O-.js.br +0 -0
- package/dist/public/assets/{_CCoTlnvb.js → _ChTMXnIu.js} +1 -1
- package/dist/public/assets/_ChTMXnIu.js.br +0 -0
- package/dist/public/assets/_Cig63HLr.js +1 -0
- package/dist/public/assets/_CjkPh72t.js +1 -0
- package/dist/public/assets/_Cm6nigjK.js +1 -0
- package/dist/public/assets/_CnAEQUMb.js +1 -0
- package/dist/public/assets/{_uCKs5N6Q.js → _Coroy2lI.js} +1 -1
- package/dist/public/assets/_Coroy2lI.js.br +0 -0
- package/dist/public/assets/_CpAeLnqO.js +1 -0
- package/dist/public/assets/_CpAeLnqO.js.br +0 -0
- package/dist/public/assets/_Cpkby1iI.js +1 -0
- package/dist/public/assets/_Cs0Q8LaN.js +1 -0
- package/dist/public/assets/_Ct2pbvTU.js +1 -0
- package/dist/public/assets/{_DWb03VKf.js → _CtC83UyG.js} +1 -1
- package/dist/public/assets/_CtC83UyG.js.br +0 -0
- package/dist/public/assets/_CtVSUTWd.js +1 -0
- package/dist/public/assets/_Ctf0pL3c.js +1 -0
- package/dist/public/assets/_CtvMc-Fn.js +1 -0
- package/dist/public/assets/_Cv7TctYK.js +1 -0
- package/dist/public/assets/_Cv7TctYK.js.br +0 -0
- package/dist/public/assets/{_DmoI4cIl.js → _CvKDzuxc.js} +1 -1
- package/dist/public/assets/_CvKDzuxc.js.br +0 -0
- package/dist/public/assets/_CvxhsaLv.js +1 -0
- package/dist/public/assets/_CwxTh04m.js +1 -0
- package/dist/public/assets/_CyMRBJfa.js +1 -0
- package/dist/public/assets/{_CdtjIoUd.js → _D-wwbDX7.js} +1 -1
- package/dist/public/assets/_D-wwbDX7.js.br +0 -0
- package/dist/public/assets/_D02jmNge.js +1 -0
- package/dist/public/assets/{_C1RigMYD.js → _D0gWUr-E.js} +1 -1
- package/dist/public/assets/_D0gWUr-E.js.br +0 -0
- package/dist/public/assets/_D2wtQpHN.js +1 -0
- package/dist/public/assets/_D2wtQpHN.js.br +0 -0
- package/dist/public/assets/_D3uCXh8z.js +2 -0
- package/dist/public/assets/_D3uCXh8z.js.br +0 -0
- package/dist/public/assets/_D5GW0gg0.js +1 -0
- package/dist/public/assets/_D5GW0gg0.js.br +0 -0
- package/dist/public/assets/{_BmpOmFbo.js → _D6YVA8vA.js} +1 -1
- package/dist/public/assets/_D6YVA8vA.js.br +0 -0
- package/dist/public/assets/_D85prZ-z.js +1 -0
- package/dist/public/assets/_D8QlsPTv.js +1 -0
- package/dist/public/assets/_D8QlsPTv.js.br +0 -0
- package/dist/public/assets/_D8rDqOqJ.js +1 -0
- package/dist/public/assets/{_1M3oZPSf.js → _DAcmaIgY.js} +1 -1
- package/dist/public/assets/_DAcmaIgY.js.br +0 -0
- package/dist/public/assets/_DBlahxfT.js +1 -0
- package/dist/public/assets/_DCPGqAo6.js +1 -0
- package/dist/public/assets/{_MHZgY1AQ.js → _DDBuDrkA.js} +1 -1
- package/dist/public/assets/_DDBuDrkA.js.br +0 -0
- package/dist/public/assets/_DDfwtidv.js +1 -0
- package/dist/public/assets/_DDfwtidv.js.br +0 -0
- package/dist/public/assets/_DEqzNO7a.js +1 -0
- package/dist/public/assets/_DEyhBnnd.js +1 -0
- package/dist/public/assets/{_DYUFCi44.js → _DG1LpTGX.js} +1 -1
- package/dist/public/assets/_DG1LpTGX.js.br +0 -0
- package/dist/public/assets/_DJK-MioE.js +1 -0
- package/dist/public/assets/{_DDcVFyaQ.js → _DLCgfhNS.js} +1 -1
- package/dist/public/assets/_DLCgfhNS.js.br +0 -0
- package/dist/public/assets/_DLYvRpNz.js +6 -0
- package/dist/public/assets/_DLYvRpNz.js.br +0 -0
- package/dist/public/assets/_DPzjUc1B.js +1 -0
- package/dist/public/assets/_DRDW7RgT.js +1 -0
- package/dist/public/assets/{_CHmEM8Sm.js → _DSoJ6ck3.js} +1 -1
- package/dist/public/assets/_DSoJ6ck3.js.br +0 -0
- package/dist/public/assets/{_C3fslTAD.js → _DW92UwGh.js} +1 -1
- package/dist/public/assets/_DW92UwGh.js.br +0 -0
- package/dist/public/assets/{_Bcc0Xs64.js → _DYIjiPYY.js} +1 -1
- package/dist/public/assets/_DYIjiPYY.js.br +0 -0
- package/dist/public/assets/_DZeeRtLs.js +1 -0
- package/dist/public/assets/{_XsB9KmcW.js → _DZzucnmx.js} +1 -1
- package/dist/public/assets/_DZzucnmx.js.br +0 -0
- package/dist/public/assets/{_EcMqP1xI.js → _D_AjKzkm.js} +1 -1
- package/dist/public/assets/_D_AjKzkm.js.br +0 -0
- package/dist/public/assets/{_DYhs8iJg.js → _DdVG8Rbg.js} +116 -116
- package/dist/public/assets/_DdVG8Rbg.js.br +0 -0
- package/dist/public/assets/{_BH9BIky6.js → _DdcPtDZz.js} +1 -1
- package/dist/public/assets/_DdcPtDZz.js.br +0 -0
- package/dist/public/assets/{_-7Amnss4.js → _De_msLFQ.js} +1 -1
- package/dist/public/assets/_De_msLFQ.js.br +0 -0
- package/dist/public/assets/_DeiCZJcm.js +1 -0
- package/dist/public/assets/_DeiCZJcm.js.br +0 -0
- package/dist/public/assets/{_CL8DzBla.js → _DfNM5GDo.js} +1 -1
- package/dist/public/assets/_DfNM5GDo.js.br +0 -0
- package/dist/public/assets/{_C7Y4xApK.js → _Dg6OsRrR.js} +1 -1
- package/dist/public/assets/_Dg6OsRrR.js.br +0 -0
- package/dist/public/assets/_DgrmX7cI.js +1 -0
- package/dist/public/assets/_DiPK_ps-.js +1 -0
- package/dist/public/assets/{_DbT6uo7W.js → _Dow17jzJ.js} +1 -1
- package/dist/public/assets/_Dow17jzJ.js.br +0 -0
- package/dist/public/assets/{_BtedGrGs.js → _DpSZYTyI.js} +1 -1
- package/dist/public/assets/_DpSZYTyI.js.br +0 -0
- package/dist/public/assets/_DpXO9fPK.js +1 -0
- package/dist/public/assets/_DpXO9fPK.js.br +0 -0
- package/dist/public/assets/{_DzNFlI0M.js → _DqiBcDpF.js} +1 -1
- package/dist/public/assets/_DqiBcDpF.js.br +0 -0
- package/dist/public/assets/_DqkJjiCU.js +1 -0
- package/dist/public/assets/{_ahCaQNeo.js → _Draa__tn.js} +1 -1
- package/dist/public/assets/_Draa__tn.js.br +0 -0
- package/dist/public/assets/_Drs3T7dh.js +1 -0
- package/dist/public/assets/_Drs3T7dh.js.br +0 -0
- package/dist/public/assets/_DtBGe4AJ.js +1 -0
- package/dist/public/assets/{_Dw1P6xZJ.js → _DxSnjCPi.js} +1 -1
- package/dist/public/assets/_DxSnjCPi.js.br +0 -0
- package/dist/public/assets/{_B9I2AEux.js → _DyEhTpVE.js} +1 -1
- package/dist/public/assets/_DyEhTpVE.js.br +0 -0
- package/dist/public/assets/{_D3IviZbJ.js → _DyFcXAXE.js} +1 -1
- package/dist/public/assets/_DyFcXAXE.js.br +0 -0
- package/dist/public/assets/_FPItrlZz.js +1 -0
- package/dist/public/assets/{_UZAFx6D8.js → _HGP7qpI4.js} +1 -1
- package/dist/public/assets/_HGP7qpI4.js.br +0 -0
- package/dist/public/assets/{_DCrPsldZ.js → _L-b7Vsug.js} +1 -1
- package/dist/public/assets/_L-b7Vsug.js.br +0 -0
- package/dist/public/assets/{_Cl7LTITm.js → _LWpc-K1g.js} +1 -1
- package/dist/public/assets/_LWpc-K1g.js.br +0 -0
- package/dist/public/assets/{_C-DhXiEm.js → _MwkLbzHQ.js} +1 -1
- package/dist/public/assets/_MwkLbzHQ.js.br +0 -0
- package/dist/public/assets/_O4DIriLZ.js +1 -0
- package/dist/public/assets/{_YoXk_8Bl.js → _T0Ktx7Km.js} +1 -1
- package/dist/public/assets/_T0Ktx7Km.js.br +0 -0
- package/dist/public/assets/_T6O8KX0b.js +1 -0
- package/dist/public/assets/_TWZgGi12.js +1 -0
- package/dist/public/assets/{_C-BYkx5h.js → _UBgRR4E9.js} +1 -1
- package/dist/public/assets/_UBgRR4E9.js.br +0 -0
- package/dist/public/assets/_X3T0JcUM.js +87 -0
- package/dist/public/assets/_X3T0JcUM.js.br +0 -0
- package/dist/public/assets/_YtP2gTmD.js +1 -0
- package/dist/public/assets/{_BQW1kIop.js → __1UtPhZb.js} +1 -1
- package/dist/public/assets/__1UtPhZb.js.br +0 -0
- package/dist/public/assets/__WDcLOp3.js +1 -0
- package/dist/public/assets/{_CIfCdAzS.js → __cyNMcUU.js} +1 -1
- package/dist/public/assets/{_BPDSQUbl.js → _csFWQzWs.js} +1 -1
- package/dist/public/assets/_csFWQzWs.js.br +0 -0
- package/dist/public/assets/_eURsGF_D.js +1 -0
- package/dist/public/assets/_fEPizk9T.js +1 -0
- package/dist/public/assets/{_B9xuaMHt.js → _hC5RUmrf.js} +1 -1
- package/dist/public/assets/_hC5RUmrf.js.br +0 -0
- package/dist/public/assets/_hfgYVPfe.js +1 -0
- package/dist/public/assets/_i51v5tw8.js +1 -0
- package/dist/public/assets/{_CuglAToi.js → _k1uZxEJ-.js} +1 -1
- package/dist/public/assets/_k1uZxEJ-.js.br +0 -0
- package/dist/public/assets/_l4nNKr9o.js +1 -0
- package/dist/public/assets/{_D7qWWxdI.js → _lA6BOkV1.js} +1 -1
- package/dist/public/assets/_lA6BOkV1.js.br +0 -0
- package/dist/public/assets/_qyrPXpIj.js +1 -0
- package/dist/public/assets/{_Bt03HU8S.js → _sLUOjXIk.js} +1 -1
- package/dist/public/assets/{_G1BLeMns.js → _sjeWoc5X.js} +1 -1
- package/dist/public/assets/_sjeWoc5X.js.br +0 -0
- package/dist/public/assets/_tByKwKUc.js +1 -0
- package/dist/public/assets/{_G8KNSVdM.js → _tFVcmmKI.js} +1 -1
- package/dist/public/assets/_tFVcmmKI.js.br +0 -0
- package/dist/public/assets/{_Ctjz3BD9.js → _txvO7pXr.js} +1 -1
- package/dist/public/assets/_txvO7pXr.js.br +0 -0
- package/dist/public/assets/_um7Vy7NX.js +1 -0
- package/dist/public/assets/_veNUvOqP.js +1 -0
- package/dist/public/assets/{_DSCqWPO0.js → _w8hTnp0H.js} +1 -1
- package/dist/public/assets/_w8hTnp0H.js.br +0 -0
- package/dist/public/assets/_wGWNa6Sp.js +1 -0
- package/dist/public/assets/_wxbEv1JK.js +1 -0
- package/dist/public/assets/files-view-BOE5HTrF.js +2 -0
- package/dist/public/assets/manage-workspace-ClwsrQ4a.js +2 -0
- package/dist/public/assets/new-CJ6ZElay.js +1 -0
- package/dist/public/assets/new-workspace-DWSBO4Vx.js +2 -0
- package/dist/public/assets/review-view-DffAZHOz.js +2 -0
- package/dist/public/assets/settings-CJ6ZElay.js +1 -0
- package/dist/public/assets/settings-page-CYL_T3Vf.js +2 -0
- package/dist/public/assets/styles-taHUfRpm.css +1 -0
- package/dist/public/assets/styles-taHUfRpm.css.br +0 -0
- package/dist/public/assets/w-CJ6ZElay.js +1 -0
- package/dist/public/assets/w._-CJ6ZElay.js +1 -0
- package/dist/public/assets/w._-ChCIRlhY.js +1 -0
- package/dist/public/assets/w._-WsR2-skZ.js +1 -0
- package/package.json +1 -1
- package/dist/_L2HNAA_8.js +0 -2
- package/dist/__TCdgPY5.js +0 -173
- package/dist/public/assets/_-7Amnss4.js.br +0 -0
- package/dist/public/assets/_0XXNUttq.js +0 -1
- package/dist/public/assets/_0sTkxfPx.js.br +0 -0
- package/dist/public/assets/_0zzYbK0A.js +0 -1
- package/dist/public/assets/_1M3oZPSf.js.br +0 -0
- package/dist/public/assets/_2O5HnHht.js +0 -1
- package/dist/public/assets/_7WlUaJPH.js +0 -1
- package/dist/public/assets/_8Tr7ByPg.js +0 -1
- package/dist/public/assets/_9AXMmQSO.js +0 -1
- package/dist/public/assets/_B1VqbV-X.js +0 -1
- package/dist/public/assets/_B9I2AEux.js.br +0 -0
- package/dist/public/assets/_B9xuaMHt.js.br +0 -0
- package/dist/public/assets/_BAWjkhA8.js.br +0 -0
- package/dist/public/assets/_BBCzcNZY.js.br +0 -0
- package/dist/public/assets/_BBW2szHh.js +0 -1
- package/dist/public/assets/_BCP2Njnu.js +0 -1
- package/dist/public/assets/_BClUYs-k.js.br +0 -0
- package/dist/public/assets/_BDdZkZRc.js +0 -1
- package/dist/public/assets/_BDdZkZRc.js.br +0 -0
- package/dist/public/assets/_BEDmGSgS.js +0 -1
- package/dist/public/assets/_BEDmGSgS.js.br +0 -0
- package/dist/public/assets/_BH9BIky6.js.br +0 -0
- package/dist/public/assets/_BLdi8Ee5.js +0 -1
- package/dist/public/assets/_BPDSQUbl.js.br +0 -0
- package/dist/public/assets/_BQW1kIop.js.br +0 -0
- package/dist/public/assets/_BQeoUfFw.js +0 -1
- package/dist/public/assets/_BRzroLbh.js +0 -1
- package/dist/public/assets/_BSXsTYSe.js +0 -1
- package/dist/public/assets/_BTXDT3Na.js +0 -1
- package/dist/public/assets/_BXm4xeFI.js +0 -1
- package/dist/public/assets/_BY8UeogK.js +0 -1
- package/dist/public/assets/_BYpyhp64.js +0 -1
- package/dist/public/assets/_BYpyhp64.js.br +0 -0
- package/dist/public/assets/_B_Kz7LnY.js +0 -1
- package/dist/public/assets/_Bcc0Xs64.js.br +0 -0
- package/dist/public/assets/_BdSsUhCi.js.br +0 -0
- package/dist/public/assets/_BdaWssEH.js +0 -1
- package/dist/public/assets/_BhecGD7y.js.br +0 -0
- package/dist/public/assets/_Bj9dpPhN.js +0 -1
- package/dist/public/assets/_BjZqS7za.js +0 -1
- package/dist/public/assets/_BjZqS7za.js.br +0 -0
- package/dist/public/assets/_BkSUiyHK.js +0 -1
- package/dist/public/assets/_BlshXH2Y.js +0 -16
- package/dist/public/assets/_BlshXH2Y.js.br +0 -0
- package/dist/public/assets/_BmX-VP7w.js +0 -1
- package/dist/public/assets/_BmpOmFbo.js.br +0 -0
- package/dist/public/assets/_Bo6YntUl.js +0 -1
- package/dist/public/assets/_BprZx4vg.js +0 -2
- package/dist/public/assets/_BprZx4vg.js.br +0 -0
- package/dist/public/assets/_Bqm7OnIV.js +0 -1
- package/dist/public/assets/_BquBGUNK.js +0 -1
- package/dist/public/assets/_BquBGUNK.js.br +0 -0
- package/dist/public/assets/_BrB66aRZ.js +0 -1
- package/dist/public/assets/_BtedGrGs.js.br +0 -0
- package/dist/public/assets/_C-BYkx5h.js.br +0 -0
- package/dist/public/assets/_C-DhXiEm.js.br +0 -0
- package/dist/public/assets/_C-NBFnST.js +0 -1
- package/dist/public/assets/_C1RigMYD.js.br +0 -0
- package/dist/public/assets/_C2vRS-ES.js +0 -1
- package/dist/public/assets/_C3fslTAD.js.br +0 -0
- package/dist/public/assets/_C4EYjWGn.js +0 -1
- package/dist/public/assets/_C4EYjWGn.js.br +0 -0
- package/dist/public/assets/_C6Yt6h_R.js.br +0 -0
- package/dist/public/assets/_C7Y4xApK.js.br +0 -0
- package/dist/public/assets/_CB52LkYt.js.br +0 -0
- package/dist/public/assets/_CBEKHwUV.js.br +0 -0
- package/dist/public/assets/_CBSLBYQF.js +0 -1
- package/dist/public/assets/_CBgzBB_k.js +0 -1
- package/dist/public/assets/_CBgzBB_k.js.br +0 -0
- package/dist/public/assets/_CCoTlnvb.js.br +0 -0
- package/dist/public/assets/_CD26To-f.js +0 -1
- package/dist/public/assets/_CDyWgQ0v.js +0 -6
- package/dist/public/assets/_CDyWgQ0v.js.br +0 -0
- package/dist/public/assets/_CEC5O89D.js.br +0 -0
- package/dist/public/assets/_CF2kEAU7.js +0 -1
- package/dist/public/assets/_CFURqbmO.js +0 -1
- package/dist/public/assets/_CG383oZY.js.br +0 -0
- package/dist/public/assets/_CHNJosg3.js +0 -1
- package/dist/public/assets/_CHmEM8Sm.js.br +0 -0
- package/dist/public/assets/_CKyzH0fl.js +0 -1
- package/dist/public/assets/_CL8DzBla.js.br +0 -0
- package/dist/public/assets/_CNYS94C5.js +0 -1
- package/dist/public/assets/_CNhypHJN.js +0 -1
- package/dist/public/assets/_CO-FkxlA.js +0 -1
- package/dist/public/assets/_COiGBSpO.js +0 -1
- package/dist/public/assets/_COvolvRQ.js +0 -1
- package/dist/public/assets/_CPaMvBMG.js.br +0 -0
- package/dist/public/assets/_CPj_VL66.js.br +0 -0
- package/dist/public/assets/_CQJC8Vx4.js +0 -1
- package/dist/public/assets/_CRHNlzBC.js +0 -1
- package/dist/public/assets/_CToqok6Q.js.br +0 -0
- package/dist/public/assets/_CUFKkl0B.js +0 -1
- package/dist/public/assets/_CZsOUa-q.js +0 -1
- package/dist/public/assets/_C_D5TPb7.js.br +0 -0
- package/dist/public/assets/_CdHI48v6.js +0 -1
- package/dist/public/assets/_CdpmkkzR.js +0 -1
- package/dist/public/assets/_CdpmkkzR.js.br +0 -0
- package/dist/public/assets/_CdtjIoUd.js.br +0 -0
- package/dist/public/assets/_Ce9Tv01m.js +0 -1
- package/dist/public/assets/_CeC7Lv10.js +0 -1
- package/dist/public/assets/_CfP0f0PU.js.br +0 -0
- package/dist/public/assets/_Cg7DH3xk.js +0 -1
- package/dist/public/assets/_Cg7DH3xk.js.br +0 -0
- package/dist/public/assets/_CikNq0z8.js +0 -1
- package/dist/public/assets/_Cl7LTITm.js.br +0 -0
- package/dist/public/assets/_Cmtl59QM.js.br +0 -0
- package/dist/public/assets/_CshYq_Dp.js +0 -1
- package/dist/public/assets/_CspPbDoP.js.br +0 -0
- package/dist/public/assets/_Ctjz3BD9.js.br +0 -0
- package/dist/public/assets/_CuglAToi.js.br +0 -0
- package/dist/public/assets/_CwSoAVRN.js +0 -1
- package/dist/public/assets/_D0XQV-Hm.js +0 -1
- package/dist/public/assets/_D36PnqCD.js +0 -1
- package/dist/public/assets/_D36PnqCD.js.br +0 -0
- package/dist/public/assets/_D3IviZbJ.js.br +0 -0
- package/dist/public/assets/_D6rgUB9O.js +0 -1
- package/dist/public/assets/_D7qWWxdI.js.br +0 -0
- package/dist/public/assets/_D8ac72_O.js.br +0 -0
- package/dist/public/assets/_D9WWxrQR.js +0 -1
- package/dist/public/assets/_D9hI5dSA.js +0 -1
- package/dist/public/assets/_DCrPsldZ.js.br +0 -0
- package/dist/public/assets/_DDcVFyaQ.js.br +0 -0
- package/dist/public/assets/_DDhkKo8Q.js +0 -1
- package/dist/public/assets/_DJBR8Qwq.js.br +0 -0
- package/dist/public/assets/_DLpa6VYT.js +0 -1
- package/dist/public/assets/_DMqe6rR-.js +0 -1
- package/dist/public/assets/_DN7pTn2g.js.br +0 -0
- package/dist/public/assets/_DP8UuFuJ.js +0 -1
- package/dist/public/assets/_DQedUaqM.js +0 -1
- package/dist/public/assets/_DSCqWPO0.js.br +0 -0
- package/dist/public/assets/_DSeQngGR.js.br +0 -0
- package/dist/public/assets/_DTNiWZFr.js.br +0 -0
- package/dist/public/assets/_DThlusXY.js +0 -1
- package/dist/public/assets/_DUK16H19.js +0 -1
- package/dist/public/assets/_DUZ13t3U.js +0 -1
- package/dist/public/assets/_DWb03VKf.js.br +0 -0
- package/dist/public/assets/_DWsyhfgn.js +0 -1
- package/dist/public/assets/_DXuzYhW6.js +0 -1
- package/dist/public/assets/_DXyOzA2b.js +0 -1
- package/dist/public/assets/_DYUFCi44.js.br +0 -0
- package/dist/public/assets/_DYhs8iJg.js.br +0 -0
- package/dist/public/assets/_D__ojGam.js.br +0 -0
- package/dist/public/assets/_DaFI_W5D.js +0 -1
- package/dist/public/assets/_DbT6uo7W.js.br +0 -0
- package/dist/public/assets/_DhnT0cCE.js +0 -1
- package/dist/public/assets/_Dm9OGn2q.js +0 -1
- package/dist/public/assets/_DmkeGFoM.js +0 -1
- package/dist/public/assets/_DmoI4cIl.js.br +0 -0
- package/dist/public/assets/_DpbHp8yR.js.br +0 -0
- package/dist/public/assets/_Dqz_HBE1.js +0 -1
- package/dist/public/assets/_DsehEuMD.js.br +0 -0
- package/dist/public/assets/_DuEyirL6.js +0 -1
- package/dist/public/assets/_DuuloSIi.js +0 -1
- package/dist/public/assets/_DuyCZnNC.js.br +0 -0
- package/dist/public/assets/_Dw1P6xZJ.js.br +0 -0
- package/dist/public/assets/_DzNFlI0M.js.br +0 -0
- package/dist/public/assets/_EcMqP1xI.js.br +0 -0
- package/dist/public/assets/_G1BLeMns.js.br +0 -0
- package/dist/public/assets/_G8KNSVdM.js.br +0 -0
- package/dist/public/assets/_J1v2clxU.js.br +0 -0
- package/dist/public/assets/_J3wdy6bJ.js.br +0 -0
- package/dist/public/assets/_MHZgY1AQ.js.br +0 -0
- package/dist/public/assets/_NHTZitq1.js +0 -2
- package/dist/public/assets/_NHTZitq1.js.br +0 -0
- package/dist/public/assets/_PsuNqRL9.js +0 -86
- package/dist/public/assets/_PsuNqRL9.js.br +0 -0
- package/dist/public/assets/_T5HFHZgl.js.br +0 -0
- package/dist/public/assets/_UZAFx6D8.js.br +0 -0
- package/dist/public/assets/_XA8_RQXF.js +0 -1
- package/dist/public/assets/_XsB9KmcW.js.br +0 -0
- package/dist/public/assets/_YoXk_8Bl.js.br +0 -0
- package/dist/public/assets/_YubCdy3Z.js +0 -1
- package/dist/public/assets/_ahCaQNeo.js.br +0 -0
- package/dist/public/assets/_bDI-pCZR.js.br +0 -0
- package/dist/public/assets/_bHL-NcWS.js.br +0 -0
- package/dist/public/assets/_bqRzDYc8.js +0 -1
- package/dist/public/assets/_briyaWgK.js.br +0 -0
- package/dist/public/assets/_dVHSDgPC.js.br +0 -0
- package/dist/public/assets/_gkZjZ9I4.js +0 -1
- package/dist/public/assets/_plyCYAnN.js +0 -1
- package/dist/public/assets/_q0Z44mWJ.js +0 -1
- package/dist/public/assets/_q0Z44mWJ.js.br +0 -0
- package/dist/public/assets/_qAsGEOt5.js +0 -1
- package/dist/public/assets/_qAsGEOt5.js.br +0 -0
- package/dist/public/assets/_uCKs5N6Q.js.br +0 -0
- package/dist/public/assets/_utWnC80Q.js +0 -1
- package/dist/public/assets/_utvzcBHT.js +0 -1
- package/dist/public/assets/_v8WbCPI8.js +0 -1
- package/dist/public/assets/_yK4b_Bl5.js +0 -1
- package/dist/public/assets/_yoQW4BY5.js +0 -1
- package/dist/public/assets/_zB6USXHX.js +0 -1
- package/dist/public/assets/_zN8SypHW.js.br +0 -0
- package/dist/public/assets/files-view-Fl0rU3Ns.js +0 -2
- package/dist/public/assets/manage-workspace-DggRcsXf.js +0 -2
- package/dist/public/assets/new-C7NxTJAT.js +0 -1
- package/dist/public/assets/new-workspace-C0pw5Iz1.js +0 -2
- package/dist/public/assets/review-view-C4YQ0J-X.js +0 -2
- package/dist/public/assets/settings-C7NxTJAT.js +0 -1
- package/dist/public/assets/settings-page-Cl_1tWCv.js +0 -2
- package/dist/public/assets/styles-Vcsj4CA7.css +0 -1
- package/dist/public/assets/styles-Vcsj4CA7.css.br +0 -0
- package/dist/public/assets/w-C7NxTJAT.js +0 -1
- package/dist/public/assets/w._-BbFH2lR-.js +0 -1
- package/dist/public/assets/w._-C7NxTJAT.js +0 -1
- package/dist/public/assets/w._-YIzLOiyZ.js +0 -1
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import { C as sendToSession, F as activityOf, I as agentPhase, Mt as getSetting, Nt as isKnownAgent, Q as sessionWatched, S as renameSession, V as lastTypedAt, X as onPhaseChange, Y as onFinished, Z as onTurnEnd, _n as all, at as turnPhase, b as onSessionDispose, d as killSession, dn as httpError, et as setExpecting, h as listPanesWithin, i as createSession, it as summaryOf, j as cleanSummary, ot as turnSettling, u as getSession, ut as agentLabel, vn as db, y as notifySessionsChanged, yn as get, z as hookReportedAt } from "./_Hb3LeA5Q.js";
|
|
2
|
+
//#region src/server/messages.ts
|
|
3
|
+
var team = {
|
|
4
|
+
memberGroup: () => null,
|
|
5
|
+
groupReleased: () => true,
|
|
6
|
+
groupTitle: (id) => id,
|
|
7
|
+
waitingOwners: () => [],
|
|
8
|
+
openGroups: () => [],
|
|
9
|
+
memberAnswered: () => {},
|
|
10
|
+
memberGone: () => {}
|
|
11
|
+
};
|
|
12
|
+
function setTeamHooks(hooks) {
|
|
13
|
+
team = hooks;
|
|
14
|
+
}
|
|
15
|
+
/** Someone typed into the recipient's terminal this recently: what they
|
|
16
|
+
* are writing is in its input box, where a paste would join it and the
|
|
17
|
+
* Enter after it send both. */
|
|
18
|
+
var TYPING_QUIET_MS = 2e4;
|
|
19
|
+
/** How often a queue that is waiting looks again: typing that stopped, a
|
|
20
|
+
* startup question answered, a kick that found the session mid-change. */
|
|
21
|
+
var RETRY_MS = 3e3;
|
|
22
|
+
/** A message is a request, not a document: a file says more for less. */
|
|
23
|
+
var MAX_BODY = 5e4;
|
|
24
|
+
/** A reply past this is cut, pointing at the rest. */
|
|
25
|
+
var MAX_REPLY = 2e4;
|
|
26
|
+
/** A pane just started for a message can be missing from a pane list
|
|
27
|
+
* taken a moment before, which is not the session exiting. */
|
|
28
|
+
var START_GRACE_MS = 1e4;
|
|
29
|
+
/** Two agents asking each other in a loop meet this; a conversation does
|
|
30
|
+
* not, since every message is a turn of the agent it goes to. */
|
|
31
|
+
var SEND_WINDOW_MS = 6e5;
|
|
32
|
+
var MAX_SENDS = 40;
|
|
33
|
+
var sends = /* @__PURE__ */ new Map();
|
|
34
|
+
/** Recipients a delivery is typing into right now. */
|
|
35
|
+
var delivering = /* @__PURE__ */ new Set();
|
|
36
|
+
var deliveredAt = /* @__PURE__ */ new Map();
|
|
37
|
+
/** Sessions any row is addressed to: the scraper asks about every session
|
|
38
|
+
* without a pane on every tick, so the answer for one with none is a set
|
|
39
|
+
* lookup rather than a query. */
|
|
40
|
+
var mailboxes = /* @__PURE__ */ new Set();
|
|
41
|
+
function labelOf(session) {
|
|
42
|
+
return `${session.id} (${agentLabel(session.agent)}${session.title ? `, "${session.title}"` : ""})`;
|
|
43
|
+
}
|
|
44
|
+
/** The text a row is typed in as. A message from an agent says who sent
|
|
45
|
+
* it and what becomes of the turn it starts, so any CLI's agent knows how
|
|
46
|
+
* to answer with nothing in its instructions about work. */
|
|
47
|
+
function render(row) {
|
|
48
|
+
const from = row.from_label ?? row.from_session;
|
|
49
|
+
switch (row.kind) {
|
|
50
|
+
case "message":
|
|
51
|
+
if (!row.from_session) return row.body;
|
|
52
|
+
return row.wants_reply ? `[work] From session ${from}: your last message this turn goes back to it as your reply, so make it the whole answer (to ask it something first: work session send ${row.from_session} "…").\n\n${row.body}` : `[work] From session ${from}, no reply needed:\n\n${row.body}`;
|
|
53
|
+
case "reply": return `[work] Reply from session ${from} (to follow up: work session send ${row.from_session} "…"):\n\n${row.body}`;
|
|
54
|
+
case "closed": return `[work] ${row.body}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** A group's replies as one: a head saying how many, then each member's
|
|
58
|
+
* under its name. */
|
|
59
|
+
function renderGroup(groupId, rows) {
|
|
60
|
+
const closed = rows.filter((r) => r.kind === "closed").length;
|
|
61
|
+
return [`[work] Group "${team.groupTitle(groupId)}" (${groupId}): ${rows.length - closed} ${rows.length - closed === 1 ? "reply" : "replies"}${closed ? `, ${closed} closed before replying` : ""}. To follow up with one: work session send <id> "…"`, ...rows.map((r) => `## ${r.from_label ?? r.from_session}\n\n${r.body}`)].join("\n\n");
|
|
62
|
+
}
|
|
63
|
+
/** Rows as typed in: each on its own, a group's together where its first
|
|
64
|
+
* would be. */
|
|
65
|
+
function renderAll(rows) {
|
|
66
|
+
const out = [];
|
|
67
|
+
const done = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
if (!row.group_id) {
|
|
70
|
+
out.push(render(row));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (done.has(row.group_id)) continue;
|
|
74
|
+
done.add(row.group_id);
|
|
75
|
+
const members = rows.filter((r) => r.group_id === row.group_id);
|
|
76
|
+
out.push(members.length > 1 || team.groupReleased(row.group_id) ? renderGroup(row.group_id, members) : render(row));
|
|
77
|
+
}
|
|
78
|
+
return out.join("\n\n");
|
|
79
|
+
}
|
|
80
|
+
function replyBody(session, message) {
|
|
81
|
+
const tab = `/w/${session.workspace_id}?s=${session.id}`;
|
|
82
|
+
if (!message) return `(Its CLI reports no last message: see its tab, ${tab})`;
|
|
83
|
+
if (message.length <= MAX_REPLY) return message;
|
|
84
|
+
return `${message.slice(0, MAX_REPLY)}\n\n[cut at ${MAX_REPLY} characters: the rest is in its tab, ${tab}]`;
|
|
85
|
+
}
|
|
86
|
+
function insert(to, from, kind, body, wantsReply = false, groupId = null) {
|
|
87
|
+
return Number(db.prepare(`INSERT INTO session_messages (to_session, from_session, from_label, kind, body, wants_reply, group_id)
|
|
88
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(to, from?.id ?? null, from ? labelOf(from) : null, kind, body, wantsReply ? 1 : 0, groupId).lastInsertRowid);
|
|
89
|
+
}
|
|
90
|
+
function deleteIds(ids) {
|
|
91
|
+
if (!ids.length) return;
|
|
92
|
+
db.prepare(`DELETE FROM session_messages WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
|
|
93
|
+
}
|
|
94
|
+
/** Who a message is still to wake, for agent-state: a session owed a
|
|
95
|
+
* reply, one with something waiting to be typed in (held for its group
|
|
96
|
+
* or not), and a lead whose groups have members yet to answer — queued
|
|
97
|
+
* ones have no session to owe it anything yet. */
|
|
98
|
+
function recompute() {
|
|
99
|
+
setExpecting(/* @__PURE__ */ new Set([...all(`SELECT from_session AS id FROM session_messages
|
|
100
|
+
WHERE wants_reply = 1 AND from_session IS NOT NULL
|
|
101
|
+
UNION SELECT to_session FROM session_messages WHERE delivered_at IS NULL`).map((r) => r.id), ...team.waitingOwners()]));
|
|
102
|
+
mailboxes = new Set(all("SELECT DISTINCT to_session AS id FROM session_messages").map((r) => r.id));
|
|
103
|
+
}
|
|
104
|
+
function admit(sender) {
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
const times = (sends.get(sender) || []).filter((t) => now - t < SEND_WINDOW_MS);
|
|
107
|
+
if (times.length >= MAX_SENDS) throw httpError(429, `${MAX_SENDS} messages in ${SEND_WINDOW_MS / 6e4} minutes is the most from one session: wait ${Math.ceil((SEND_WINDOW_MS - (now - times[0])) / 6e4)} min`);
|
|
108
|
+
times.push(now);
|
|
109
|
+
sends.set(sender, times);
|
|
110
|
+
}
|
|
111
|
+
/** What is queued for `id` and may go now: a held group's replies wait
|
|
112
|
+
* for the rest of their group. */
|
|
113
|
+
function deliverable(id) {
|
|
114
|
+
return all("SELECT * FROM session_messages WHERE to_session = ? AND delivered_at IS NULL ORDER BY id", id).filter((r) => !r.group_id || team.groupReleased(r.group_id));
|
|
115
|
+
}
|
|
116
|
+
/** Type what is queued for `id` into it, if it is at its prompt. Returns
|
|
117
|
+
* why not, or undefined once it is typed in (or there was nothing). */
|
|
118
|
+
async function deliver(id) {
|
|
119
|
+
if (delivering.has(id)) return "another message is being typed in";
|
|
120
|
+
const phase = turnPhase(id);
|
|
121
|
+
if (phase === "waiting") return "it is waiting on the user";
|
|
122
|
+
if (phase === "working" || turnSettling(id)) return "it is working";
|
|
123
|
+
if (Date.now() - lastTypedAt(id) < TYPING_QUIET_MS) return "someone is typing in its terminal";
|
|
124
|
+
const rows = deliverable(id);
|
|
125
|
+
if (!rows.length) return void 0;
|
|
126
|
+
delivering.add(id);
|
|
127
|
+
try {
|
|
128
|
+
await sendToSession(id, renderAll(rows));
|
|
129
|
+
} catch (err) {
|
|
130
|
+
return err.message;
|
|
131
|
+
} finally {
|
|
132
|
+
delivering.delete(id);
|
|
133
|
+
}
|
|
134
|
+
deliveredAt.set(id, Date.now());
|
|
135
|
+
deleteIds(rows.filter((r) => !r.wants_reply).map((r) => r.id));
|
|
136
|
+
const owed = rows.filter((r) => r.wants_reply).map((r) => r.id);
|
|
137
|
+
if (owed.length) db.prepare(`UPDATE session_messages SET delivered_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
138
|
+
WHERE id IN (${owed.map(() => "?").join(",")})`).run(...owed);
|
|
139
|
+
recompute();
|
|
140
|
+
}
|
|
141
|
+
var pumping = false;
|
|
142
|
+
var again = false;
|
|
143
|
+
var retry = null;
|
|
144
|
+
/** Deliver to every running session with something queued. Never starts
|
|
145
|
+
* a session: only a sender's own request does that (`sendMessage`). */
|
|
146
|
+
async function pump() {
|
|
147
|
+
if (pumping) {
|
|
148
|
+
again = true;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
pumping = true;
|
|
152
|
+
try {
|
|
153
|
+
for (let i = 0; i < 5; i++) {
|
|
154
|
+
again = false;
|
|
155
|
+
const queued = all("SELECT DISTINCT to_session AS id FROM session_messages WHERE delivered_at IS NULL").map((r) => r.id).filter((id) => deliverable(id).length);
|
|
156
|
+
if (!queued.length) break;
|
|
157
|
+
const panes = await listPanesWithin(2e3);
|
|
158
|
+
if (!panes) break;
|
|
159
|
+
const running = new Set(panes.map((p) => p.id));
|
|
160
|
+
for (const id of queued) if (running.has(id)) await deliver(id);
|
|
161
|
+
if (!again) break;
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
pumping = false;
|
|
165
|
+
const waiting = all("SELECT DISTINCT to_session AS id FROM session_messages WHERE delivered_at IS NULL").some((r) => deliverable(r.id).length);
|
|
166
|
+
if (!retry && waiting) {
|
|
167
|
+
retry = setTimeout(() => {
|
|
168
|
+
retry = null;
|
|
169
|
+
pump();
|
|
170
|
+
}, RETRY_MS);
|
|
171
|
+
retry.unref?.();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Look at the queues now (a group finished: its replies may go). */
|
|
176
|
+
function kick() {
|
|
177
|
+
pump().catch((err) => console.error(`[work] messages: ${err.message}`));
|
|
178
|
+
}
|
|
179
|
+
/** A message from `opts.from` (a session id; none: the user's own
|
|
180
|
+
* terminal) to session `to`, typed in now if it is at its prompt, else
|
|
181
|
+
* queued until it is. From an agent's session it wants a reply unless
|
|
182
|
+
* `reply` is false: the recipient's last message as that turn ends.
|
|
183
|
+
* `opts.member` is a fan-out starting a member (server/team): its group's
|
|
184
|
+
* own caps bound it, not the sender's. */
|
|
185
|
+
async function sendMessage(to, text, opts = {}) {
|
|
186
|
+
const target = getSession(to);
|
|
187
|
+
if (!target) throw httpError(404, `no session ${to}`);
|
|
188
|
+
if (target.agent === "shell") throw httpError(409, `${to} is a shell: a message would run as a command there, so messages go to agent sessions`);
|
|
189
|
+
if (opts.from === to) throw httpError(409, "a session cannot message itself");
|
|
190
|
+
if (text.length > MAX_BODY) throw httpError(413, `a message is at most ${MAX_BODY} characters: write the rest to a file and send its path`);
|
|
191
|
+
const sender = opts.from ? getSession(opts.from) : void 0;
|
|
192
|
+
const from = sender && sender.agent !== "shell" ? sender : null;
|
|
193
|
+
if (from && !opts.member) admit(from.id);
|
|
194
|
+
if (!from && target.parent_id) db.prepare("UPDATE sessions SET keep = 1 WHERE id = ?").run(to);
|
|
195
|
+
const wantsReply = !!from && opts.reply !== false;
|
|
196
|
+
const id = insert(to, from, "message", text, wantsReply);
|
|
197
|
+
recompute();
|
|
198
|
+
const hold = await deliver(to);
|
|
199
|
+
const row = get("SELECT delivered_at FROM session_messages WHERE id = ?", id);
|
|
200
|
+
if (row && !row.delivered_at) kick();
|
|
201
|
+
return {
|
|
202
|
+
to: {
|
|
203
|
+
id: target.id,
|
|
204
|
+
agent: target.agent,
|
|
205
|
+
title: target.title
|
|
206
|
+
},
|
|
207
|
+
delivered: !row || !!row.delivered_at,
|
|
208
|
+
queued: row && !row.delivered_at ? hold ?? "queued" : null,
|
|
209
|
+
wantsReply
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** The replies (and word of sessions that closed) waiting for `me`, from
|
|
213
|
+
* the sessions or groups `from` names or anyone, taken so they are not
|
|
214
|
+
* typed in too; and who still owes `me` one. A group's replies are taken
|
|
215
|
+
* once all of them are in, unless `any`: then as each comes. `work
|
|
216
|
+
* session wait` asks this until nobody owes it anything. */
|
|
217
|
+
function takeReplies(me, from, any = false) {
|
|
218
|
+
const wanted = (r) => !from?.length || r.from_session !== null && from.includes(r.from_session) || r.group_id !== null && from.includes(r.group_id);
|
|
219
|
+
const rows = delivering.has(me) ? [] : all("SELECT * FROM session_messages WHERE to_session = ? AND kind != 'message' AND delivered_at IS NULL ORDER BY id", me).filter(wanted).filter((r) => any || !r.group_id || team.groupReleased(r.group_id));
|
|
220
|
+
deleteIds(rows.map((r) => r.id));
|
|
221
|
+
const groups = team.openGroups(me);
|
|
222
|
+
const inGroup = new Set(groups.map((g) => g.id));
|
|
223
|
+
const owing = all(`SELECT DISTINCT m.to_session AS id, gm.group_id FROM session_messages m
|
|
224
|
+
LEFT JOIN group_members gm ON gm.session_id = m.to_session AND gm.state = 'running'
|
|
225
|
+
WHERE m.from_session = ? AND m.wants_reply = 1`, me).filter((r) => !r.group_id || !inGroup.has(r.group_id)).filter((r) => !from?.length || from.includes(r.id)).map((r) => {
|
|
226
|
+
const s = getSession(r.id);
|
|
227
|
+
return {
|
|
228
|
+
id: r.id,
|
|
229
|
+
label: s ? labelOf(s) : r.id
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
for (const g of groups) if (!from?.length || from.includes(g.id)) owing.push({
|
|
233
|
+
id: g.id,
|
|
234
|
+
label: `group "${g.title}"`
|
|
235
|
+
});
|
|
236
|
+
if (rows.length) recompute();
|
|
237
|
+
return {
|
|
238
|
+
replies: rows.map((r) => ({
|
|
239
|
+
from: r.from_session,
|
|
240
|
+
group: r.group_id,
|
|
241
|
+
label: r.from_label,
|
|
242
|
+
kind: r.kind,
|
|
243
|
+
text: r.body
|
|
244
|
+
})),
|
|
245
|
+
owing
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Per session with any: the sessions it waits on for a reply, and how
|
|
249
|
+
* many messages wait to be typed into it. */
|
|
250
|
+
function messageStates() {
|
|
251
|
+
const states = /* @__PURE__ */ new Map();
|
|
252
|
+
const of = (id) => {
|
|
253
|
+
let s = states.get(id);
|
|
254
|
+
if (!s) states.set(id, s = {
|
|
255
|
+
waitingOn: [],
|
|
256
|
+
queued: 0
|
|
257
|
+
});
|
|
258
|
+
return s;
|
|
259
|
+
};
|
|
260
|
+
for (const r of all("SELECT DISTINCT from_session, to_session FROM session_messages WHERE wants_reply = 1 AND from_session IS NOT NULL")) of(r.from_session).waitingOn.push(r.to_session);
|
|
261
|
+
for (const r of all("SELECT to_session AS id, COUNT(*) AS n FROM session_messages WHERE delivered_at IS NULL GROUP BY to_session")) of(r.id).queued = r.n;
|
|
262
|
+
return states;
|
|
263
|
+
}
|
|
264
|
+
/** A turn's end answers what the session was asked: its last message
|
|
265
|
+
* goes to each session that asked, as a reply — a member's held for its
|
|
266
|
+
* group. */
|
|
267
|
+
function answer(id, message) {
|
|
268
|
+
if (!mailboxes.has(id)) return false;
|
|
269
|
+
const owed = all("SELECT * FROM session_messages WHERE to_session = ? AND wants_reply = 1 AND delivered_at IS NOT NULL", id);
|
|
270
|
+
const session = getSession(id);
|
|
271
|
+
if (!owed.length || !session) return false;
|
|
272
|
+
const body = replyBody(session, message);
|
|
273
|
+
const group = team.memberGroup(id);
|
|
274
|
+
let told = false;
|
|
275
|
+
for (const asker of new Set(owed.map((r) => r.from_session))) {
|
|
276
|
+
const to = asker ? getSession(asker) : void 0;
|
|
277
|
+
if (!to || to.agent === "shell") continue;
|
|
278
|
+
insert(to.id, session, "reply", body, false, group);
|
|
279
|
+
told = true;
|
|
280
|
+
}
|
|
281
|
+
deleteIds(owed.map((r) => r.id));
|
|
282
|
+
if (told) db.prepare("UPDATE sessions SET answered_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?").run(id);
|
|
283
|
+
if (group) team.memberAnswered(id, message);
|
|
284
|
+
recompute();
|
|
285
|
+
kick();
|
|
286
|
+
return told;
|
|
287
|
+
}
|
|
288
|
+
/** The session's process is gone, or the session is: whoever waits on it
|
|
289
|
+
* for a reply is told none is coming, and what was queued for it goes —
|
|
290
|
+
* a reply means nothing to the process that next runs there. */
|
|
291
|
+
function settle(id, why) {
|
|
292
|
+
const session = getSession(id);
|
|
293
|
+
const label = session ? labelOf(session) : id;
|
|
294
|
+
const group = team.memberGroup(id);
|
|
295
|
+
const askers = all("SELECT DISTINCT from_session AS id FROM session_messages WHERE to_session = ? AND wants_reply = 1 AND from_session IS NOT NULL", id).map((r) => r.id);
|
|
296
|
+
for (const asker of askers) {
|
|
297
|
+
const to = getSession(asker);
|
|
298
|
+
if (to && to.id !== id && to.agent !== "shell") insert(to.id, session ?? null, "closed", `Session ${label} ${why}`, false, group);
|
|
299
|
+
}
|
|
300
|
+
db.prepare("DELETE FROM session_messages WHERE to_session = ?").run(id);
|
|
301
|
+
if (group) team.memberGone(id);
|
|
302
|
+
recompute();
|
|
303
|
+
kick();
|
|
304
|
+
}
|
|
305
|
+
/** The scraper found no pane for the session. */
|
|
306
|
+
function sessionGone(id) {
|
|
307
|
+
if (!mailboxes.has(id) || delivering.has(id)) return;
|
|
308
|
+
if (Date.now() - (deliveredAt.get(id) ?? 0) < START_GRACE_MS) return;
|
|
309
|
+
settle(id, "exited before it replied.");
|
|
310
|
+
}
|
|
311
|
+
var started$1 = false;
|
|
312
|
+
function startMessages() {
|
|
313
|
+
if (started$1) return;
|
|
314
|
+
started$1 = true;
|
|
315
|
+
onTurnEnd(answer);
|
|
316
|
+
onPhaseChange(kick);
|
|
317
|
+
onSessionDispose((id) => {
|
|
318
|
+
settle(id, "was closed before it replied.");
|
|
319
|
+
db.prepare("DELETE FROM session_messages WHERE from_session = ? AND delivered_at IS NOT NULL AND kind = 'message'").run(id);
|
|
320
|
+
db.prepare("UPDATE session_messages SET wants_reply = 0 WHERE from_session = ?").run(id);
|
|
321
|
+
recompute();
|
|
322
|
+
});
|
|
323
|
+
recompute();
|
|
324
|
+
kick();
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/server/team.ts
|
|
328
|
+
/** A group's members at most: past it, a fan-out is a sweep that wants
|
|
329
|
+
* the agent's own in-process subagents, not a CLI each. */
|
|
330
|
+
var MAX_MEMBERS = 64;
|
|
331
|
+
/** Members one lead may have queued, every group's together: a lead
|
|
332
|
+
* fanning out in a loop meets it long before the queue is a problem. */
|
|
333
|
+
var MAX_QUEUED = 128;
|
|
334
|
+
var MAX_PARALLEL = 16;
|
|
335
|
+
var DEFAULT_PARALLEL = 4;
|
|
336
|
+
/** Boots at once, whatever the pool allows running. */
|
|
337
|
+
var MAX_BOOTING = 2;
|
|
338
|
+
/** A pool with queued members looks again this often, for a slot that a
|
|
339
|
+
* session freed without a hook to say so. */
|
|
340
|
+
var POOL_RETRY_MS = 5e3;
|
|
341
|
+
/** Lines a plan keeps of its log. */
|
|
342
|
+
var MAX_LOG = 200;
|
|
343
|
+
/** A finished group older than this goes as the server starts. */
|
|
344
|
+
var PRUNE_AFTER = "-1 day";
|
|
345
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
346
|
+
var newId = (prefix) => `${prefix}${Math.random().toString(36).slice(2, 9)}`;
|
|
347
|
+
/** The machine's cap on members running at once, every group's together
|
|
348
|
+
* (Settings, `team-max-running`). */
|
|
349
|
+
function maxRunning() {
|
|
350
|
+
const n = Number(getSetting("ui.team-max-running"));
|
|
351
|
+
return Number.isInteger(n) && n > 0 ? n : 6;
|
|
352
|
+
}
|
|
353
|
+
function groupOf(id) {
|
|
354
|
+
return get("SELECT * FROM session_groups WHERE id = ?", id);
|
|
355
|
+
}
|
|
356
|
+
function membersOf(groupId) {
|
|
357
|
+
return all("SELECT * FROM group_members WHERE group_id = ? ORDER BY ord", groupId);
|
|
358
|
+
}
|
|
359
|
+
function memberBySession(sessionId) {
|
|
360
|
+
return get("SELECT * FROM group_members WHERE session_id = ? ORDER BY attempts DESC LIMIT 1", sessionId);
|
|
361
|
+
}
|
|
362
|
+
function changed(workspaceId) {
|
|
363
|
+
if (workspaceId) notifySessionsChanged(workspaceId);
|
|
364
|
+
}
|
|
365
|
+
/** Whether a group still has members to answer. */
|
|
366
|
+
function open(groupId) {
|
|
367
|
+
return !!get("SELECT 1 AS x FROM group_members WHERE group_id = ? AND state IN ('queued', 'running')", groupId);
|
|
368
|
+
}
|
|
369
|
+
/** Start a fan-out of `owner`'s: its members, queued, and the pool told. */
|
|
370
|
+
function createGroup(owner, input) {
|
|
371
|
+
const lead = getSession(owner);
|
|
372
|
+
if (!lead?.workspace_id) throw httpError(404, `no session ${owner}`);
|
|
373
|
+
if (lead.agent === "shell") throw httpError(409, "a fan-out is run by an agent's session: its members' replies go back to it");
|
|
374
|
+
const agents = [...new Set(input.agents.filter(Boolean))];
|
|
375
|
+
if (!agents.length) throw httpError(400, "a fan-out needs an --agent");
|
|
376
|
+
for (const a of agents) if (!isKnownAgent(a) || a === "shell") throw httpError(400, `no agent ${a} here (\`work agent\` lists them)`);
|
|
377
|
+
const items = (input.items ?? []).map((i) => i.trim()).filter(Boolean);
|
|
378
|
+
const count = Math.max(1, Math.floor(input.count ?? 1));
|
|
379
|
+
const slots = [];
|
|
380
|
+
for (const agent of agents) if (items.length) for (const item of items) slots.push({
|
|
381
|
+
agent,
|
|
382
|
+
item,
|
|
383
|
+
n: 0
|
|
384
|
+
});
|
|
385
|
+
else for (let n = 1; n <= count; n++) slots.push({
|
|
386
|
+
agent,
|
|
387
|
+
item: null,
|
|
388
|
+
n
|
|
389
|
+
});
|
|
390
|
+
if (slots.length > MAX_MEMBERS) throw httpError(400, `${slots.length} members is more than ${MAX_MEMBERS}: a sweep that wide is a job for your CLI's own subagents`);
|
|
391
|
+
const queued = get(`SELECT COUNT(*) AS n FROM group_members m JOIN session_groups g ON g.id = m.group_id
|
|
392
|
+
WHERE g.owner = ? AND m.state = 'queued'`, owner).n;
|
|
393
|
+
if (queued + slots.length > MAX_QUEUED) throw httpError(429, `${queued} members are already queued for this session: let them start (or skip some) before fanning out more`);
|
|
394
|
+
const dirs = input.dirs ?? [];
|
|
395
|
+
if (dirs.length && dirs.length !== slots.length) throw httpError(400, `${dirs.length} folders for ${slots.length} members`);
|
|
396
|
+
const title = input.title.trim() || "fan-out";
|
|
397
|
+
const parallel = Math.min(MAX_PARALLEL, Math.max(1, Math.floor(input.parallel ?? DEFAULT_PARALLEL)));
|
|
398
|
+
const current = openPlan(owner);
|
|
399
|
+
const id = newId("g");
|
|
400
|
+
db.prepare(`INSERT INTO session_groups (id, workspace_id, owner, plan_id, phase, title, parallel, fan_in, close_done, keep)
|
|
401
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, lead.workspace_id, owner, current?.id ?? null, input.phase?.trim() || null, title, parallel, input.fanIn === false ? 0 : 1, input.closeDone ? 1 : 0, input.keep ? 1 : 0);
|
|
402
|
+
const several = new Set(slots.map((p) => p.agent)).size > 1;
|
|
403
|
+
const insert = db.prepare(`INSERT INTO group_members (id, group_id, ord, agent, title, prompt, launch_dir)
|
|
404
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
405
|
+
slots.forEach((p, i) => {
|
|
406
|
+
const name = p.item ?? (slots.length > agents.length ? `#${p.n}` : "");
|
|
407
|
+
const who = several ? p.agent : "";
|
|
408
|
+
const memberTitle = [title, [name, who].filter(Boolean).join(" ")].filter(Boolean).join(" · ").slice(0, 60);
|
|
409
|
+
const prompt = input.prompt.replaceAll("{}", p.item ?? "").replaceAll("{i}", String(i + 1));
|
|
410
|
+
insert.run(newId("m"), id, i + 1, p.agent, memberTitle, prompt, dirs[i] ?? null);
|
|
411
|
+
});
|
|
412
|
+
recompute();
|
|
413
|
+
changed(lead.workspace_id);
|
|
414
|
+
kickPool();
|
|
415
|
+
return groupView(groupOf(id));
|
|
416
|
+
}
|
|
417
|
+
/** Rerun a member that closed: a fresh session with the same prompt. Its
|
|
418
|
+
* lead's word that it closed is taken back, so the group waits for it. */
|
|
419
|
+
function retryMember(memberId) {
|
|
420
|
+
const m = get("SELECT * FROM group_members WHERE id = ?", memberId);
|
|
421
|
+
if (!m) throw httpError(404, `no member ${memberId}`);
|
|
422
|
+
if (m.state !== "closed") throw httpError(409, `${m.title} is ${m.state}: only a closed one reruns`);
|
|
423
|
+
const g = groupOf(m.group_id);
|
|
424
|
+
if (m.session_id) db.prepare("DELETE FROM session_messages WHERE group_id = ? AND from_session = ? AND kind = 'closed' AND delivered_at IS NULL").run(g.id, m.session_id);
|
|
425
|
+
db.prepare("UPDATE group_members SET state = 'queued', session_id = NULL, note = NULL, summary = NULL, done_at = NULL WHERE id = ?").run(memberId);
|
|
426
|
+
recompute();
|
|
427
|
+
changed(g.workspace_id);
|
|
428
|
+
kickPool();
|
|
429
|
+
}
|
|
430
|
+
/** Stop a member: a queued one never starts, a running one's session is
|
|
431
|
+
* closed (with whatever it started), and either counts as answered. */
|
|
432
|
+
async function skipMember(memberId) {
|
|
433
|
+
const m = get("SELECT * FROM group_members WHERE id = ?", memberId);
|
|
434
|
+
if (!m) throw httpError(404, `no member ${memberId}`);
|
|
435
|
+
const g = groupOf(m.group_id);
|
|
436
|
+
if (m.state === "queued") {
|
|
437
|
+
db.prepare("UPDATE group_members SET state = 'closed', note = 'skipped', done_at = ? WHERE id = ?").run(now(), memberId);
|
|
438
|
+
released(g);
|
|
439
|
+
} else if (m.state === "running" && m.session_id) {
|
|
440
|
+
db.prepare("UPDATE group_members SET note = 'skipped' WHERE id = ?").run(memberId);
|
|
441
|
+
await killSession(m.session_id);
|
|
442
|
+
}
|
|
443
|
+
changed(g.workspace_id);
|
|
444
|
+
}
|
|
445
|
+
/** Close the sessions of a group's members that have answered: their
|
|
446
|
+
* results are kept, their CLIs no longer hold memory. */
|
|
447
|
+
async function closeDone(groupId) {
|
|
448
|
+
const g = groupOf(groupId);
|
|
449
|
+
if (!g) throw httpError(404, `no group ${groupId}`);
|
|
450
|
+
const done = membersOf(groupId).filter((m) => m.state === "done" && m.session_id);
|
|
451
|
+
for (const m of done) await killSession(m.session_id);
|
|
452
|
+
changed(g.workspace_id);
|
|
453
|
+
return done.length;
|
|
454
|
+
}
|
|
455
|
+
/** Forget a finished group (its members' sessions closed with it). */
|
|
456
|
+
async function clearGroup(groupId) {
|
|
457
|
+
const g = groupOf(groupId);
|
|
458
|
+
if (!g) throw httpError(404, `no group ${groupId}`);
|
|
459
|
+
if (open(groupId)) throw httpError(409, `"${g.title}" still has members working: skip them first`);
|
|
460
|
+
await closeDone(groupId);
|
|
461
|
+
db.prepare("DELETE FROM session_groups WHERE id = ?").run(groupId);
|
|
462
|
+
changed(g.workspace_id);
|
|
463
|
+
}
|
|
464
|
+
/** A group whose last member just answered or closed: its held replies
|
|
465
|
+
* may go, and its lead stops waiting on it. */
|
|
466
|
+
function released(g) {
|
|
467
|
+
recompute();
|
|
468
|
+
if (!open(g.id)) kick();
|
|
469
|
+
kickPool();
|
|
470
|
+
}
|
|
471
|
+
var booting = /* @__PURE__ */ new Set();
|
|
472
|
+
var pooling = false;
|
|
473
|
+
var poolAgain = false;
|
|
474
|
+
var poolRetry = null;
|
|
475
|
+
function kickPool() {
|
|
476
|
+
pool().catch((err) => console.error(`[work] team: ${err.message}`));
|
|
477
|
+
}
|
|
478
|
+
async function pool() {
|
|
479
|
+
if (pooling) {
|
|
480
|
+
poolAgain = true;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
pooling = true;
|
|
484
|
+
try {
|
|
485
|
+
do {
|
|
486
|
+
poolAgain = false;
|
|
487
|
+
const running = all("SELECT group_id, COUNT(*) AS n FROM group_members WHERE state = 'running' GROUP BY group_id");
|
|
488
|
+
const perGroup = new Map(running.map((r) => [r.group_id, r.n]));
|
|
489
|
+
let free = maxRunning() - running.reduce((n, r) => n + r.n, 0);
|
|
490
|
+
if (free <= 0) break;
|
|
491
|
+
const queued = all(`SELECT m.*, g.parallel FROM group_members m JOIN session_groups g ON g.id = m.group_id
|
|
492
|
+
WHERE m.state = 'queued' ORDER BY g.created_at, m.ord`);
|
|
493
|
+
for (const m of queued) {
|
|
494
|
+
if (free <= 0 || booting.size >= MAX_BOOTING) break;
|
|
495
|
+
const n = perGroup.get(m.group_id) ?? 0;
|
|
496
|
+
if (n >= m.parallel) continue;
|
|
497
|
+
perGroup.set(m.group_id, n + 1);
|
|
498
|
+
free--;
|
|
499
|
+
db.prepare("UPDATE group_members SET state = 'running', attempts = attempts + 1, started_at = ?, note = NULL WHERE id = ? AND state = 'queued'").run(now(), m.id);
|
|
500
|
+
booting.add(m.id);
|
|
501
|
+
start(m).finally(() => {
|
|
502
|
+
booting.delete(m.id);
|
|
503
|
+
kickPool();
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
} while (poolAgain);
|
|
507
|
+
} finally {
|
|
508
|
+
pooling = false;
|
|
509
|
+
if (get("SELECT 1 AS x FROM group_members WHERE state = 'queued'") && !poolRetry) {
|
|
510
|
+
poolRetry = setTimeout(() => {
|
|
511
|
+
poolRetry = null;
|
|
512
|
+
kickPool();
|
|
513
|
+
}, POOL_RETRY_MS);
|
|
514
|
+
poolRetry.unref?.();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/** A member's session: made under its lead, named, and handed its prompt
|
|
519
|
+
* as a message from the lead, which starts its CLI. */
|
|
520
|
+
async function start(m) {
|
|
521
|
+
const g = groupOf(m.group_id);
|
|
522
|
+
const lead = g ? getSession(g.owner) : void 0;
|
|
523
|
+
if (!g || !lead?.workspace_id) {
|
|
524
|
+
db.prepare("UPDATE group_members SET state = 'closed', note = 'its lead is gone', done_at = ? WHERE id = ?").run(now(), m.id);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
let sessionId = null;
|
|
528
|
+
try {
|
|
529
|
+
const session = createSession(lead.workspace_id, m.agent, false, {
|
|
530
|
+
parent: lead.id,
|
|
531
|
+
launchDir: m.launch_dir,
|
|
532
|
+
keep: !!g.keep
|
|
533
|
+
});
|
|
534
|
+
sessionId = session.id;
|
|
535
|
+
renameSession(session.id, m.title);
|
|
536
|
+
db.prepare("UPDATE group_members SET session_id = ? WHERE id = ?").run(session.id, m.id);
|
|
537
|
+
const sent = await sendMessage(session.id, m.prompt, {
|
|
538
|
+
from: lead.id,
|
|
539
|
+
reply: true,
|
|
540
|
+
member: true
|
|
541
|
+
});
|
|
542
|
+
if (!sent.delivered) db.prepare("UPDATE group_members SET note = ? WHERE id = ?").run(sent.queued, m.id);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
const why = err.message;
|
|
545
|
+
if (!sessionId || !getSession(sessionId)) {
|
|
546
|
+
db.prepare("UPDATE group_members SET state = 'closed', note = ?, done_at = ? WHERE id = ?").run(why, now(), m.id);
|
|
547
|
+
released(g);
|
|
548
|
+
} else db.prepare("UPDATE group_members SET note = ? WHERE id = ?").run(why, m.id);
|
|
549
|
+
}
|
|
550
|
+
changed(lead.workspace_id);
|
|
551
|
+
}
|
|
552
|
+
setTeamHooks({
|
|
553
|
+
memberGroup(sessionId) {
|
|
554
|
+
const m = memberBySession(sessionId);
|
|
555
|
+
return m?.state === "running" ? m.group_id : null;
|
|
556
|
+
},
|
|
557
|
+
groupReleased(groupId) {
|
|
558
|
+
const g = groupOf(groupId);
|
|
559
|
+
return !g || !g.fan_in || !open(groupId);
|
|
560
|
+
},
|
|
561
|
+
groupTitle(groupId) {
|
|
562
|
+
return groupOf(groupId)?.title ?? groupId;
|
|
563
|
+
},
|
|
564
|
+
waitingOwners() {
|
|
565
|
+
return all(`SELECT DISTINCT g.owner FROM session_groups g JOIN group_members m ON m.group_id = g.id
|
|
566
|
+
WHERE m.state IN ('queued', 'running')`).map((r) => r.owner);
|
|
567
|
+
},
|
|
568
|
+
openGroups(owner) {
|
|
569
|
+
return all(`SELECT DISTINCT g.id, g.title FROM session_groups g JOIN group_members m ON m.group_id = g.id
|
|
570
|
+
WHERE g.owner = ? AND m.state IN ('queued', 'running')`, owner);
|
|
571
|
+
},
|
|
572
|
+
memberAnswered(sessionId, message) {
|
|
573
|
+
const m = memberBySession(sessionId);
|
|
574
|
+
if (!m || m.state !== "running") return;
|
|
575
|
+
db.prepare("UPDATE group_members SET state = 'done', summary = ?, note = NULL, done_at = ? WHERE id = ?").run(message ? cleanSummary(message) ?? null : null, now(), m.id);
|
|
576
|
+
const g = groupOf(m.group_id);
|
|
577
|
+
if (g.close_done) setTimeout(() => void killSession(sessionId), 0);
|
|
578
|
+
released(g);
|
|
579
|
+
changed(g.workspace_id);
|
|
580
|
+
},
|
|
581
|
+
memberGone(sessionId) {
|
|
582
|
+
const m = memberBySession(sessionId);
|
|
583
|
+
if (!m || m.state !== "running") return;
|
|
584
|
+
db.prepare("UPDATE group_members SET state = 'closed', note = COALESCE(note, 'closed before it replied'), done_at = ? WHERE id = ?").run(now(), m.id);
|
|
585
|
+
const g = groupOf(m.group_id);
|
|
586
|
+
released(g);
|
|
587
|
+
changed(g.workspace_id);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
function openPlan(owner) {
|
|
591
|
+
return get("SELECT * FROM plans WHERE owner = ? AND done_at IS NULL ORDER BY created_at DESC LIMIT 1", owner);
|
|
592
|
+
}
|
|
593
|
+
/** A lead's plan: the phases its groups fall under, in order. A new one
|
|
594
|
+
* ends the lead's last. */
|
|
595
|
+
function createPlan(owner, name, phases) {
|
|
596
|
+
const lead = getSession(owner);
|
|
597
|
+
if (!lead?.workspace_id) throw httpError(404, `no session ${owner}`);
|
|
598
|
+
if (!name.trim()) throw httpError(400, "a plan needs a name");
|
|
599
|
+
db.prepare("UPDATE plans SET done_at = ? WHERE owner = ? AND done_at IS NULL").run(now(), owner);
|
|
600
|
+
const id = newId("p");
|
|
601
|
+
const clean = phases.map((p) => ({
|
|
602
|
+
title: p.title.trim(),
|
|
603
|
+
detail: p.detail?.trim() || void 0
|
|
604
|
+
})).filter((p) => p.title);
|
|
605
|
+
db.prepare("INSERT INTO plans (id, workspace_id, owner, name, phases) VALUES (?, ?, ?, ?, ?)").run(id, lead.workspace_id, owner, name.trim(), JSON.stringify(clean));
|
|
606
|
+
changed(lead.workspace_id);
|
|
607
|
+
return planView(get("SELECT * FROM plans WHERE id = ?", id));
|
|
608
|
+
}
|
|
609
|
+
/** A line of what the lead is doing, over its plan's phases. */
|
|
610
|
+
function logPlan(owner, text) {
|
|
611
|
+
const plan = openPlan(owner);
|
|
612
|
+
if (!plan) throw httpError(409, "no plan to log to: start one with `work plan new \"<name>\"`");
|
|
613
|
+
db.prepare("INSERT INTO plan_log (plan_id, text) VALUES (?, ?)").run(plan.id, text.trim().slice(0, 300));
|
|
614
|
+
db.prepare(`DELETE FROM plan_log WHERE plan_id = ? AND id NOT IN
|
|
615
|
+
(SELECT id FROM plan_log WHERE plan_id = ? ORDER BY id DESC LIMIT ${MAX_LOG})`).run(plan.id, plan.id);
|
|
616
|
+
changed(plan.workspace_id);
|
|
617
|
+
}
|
|
618
|
+
function finishPlan(owner) {
|
|
619
|
+
const plan = openPlan(owner);
|
|
620
|
+
if (!plan) throw httpError(409, "no plan under way");
|
|
621
|
+
db.prepare("UPDATE plans SET done_at = ? WHERE id = ?").run(now(), plan.id);
|
|
622
|
+
changed(plan.workspace_id);
|
|
623
|
+
}
|
|
624
|
+
function memberStatus(m, alive) {
|
|
625
|
+
if (m.state !== "running") return m.state;
|
|
626
|
+
if (booting.has(m.id) || !m.session_id) return "starting";
|
|
627
|
+
if (alive && !alive.has(m.session_id)) return "starting";
|
|
628
|
+
const phase = agentPhase(m.session_id);
|
|
629
|
+
if (phase === "waiting") return "waiting";
|
|
630
|
+
if (hookReportedAt(m.session_id) === void 0 && m.note) return "waiting";
|
|
631
|
+
if (turnPhase(m.session_id) === "working" || turnSettling(m.session_id)) return "working";
|
|
632
|
+
if (phase === "working") return "working";
|
|
633
|
+
return hookReportedAt(m.session_id) === void 0 ? "working" : "idle";
|
|
634
|
+
}
|
|
635
|
+
function memberView(m, alive) {
|
|
636
|
+
const status = memberStatus(m, alive);
|
|
637
|
+
const live = m.session_id ? getSession(m.session_id) : void 0;
|
|
638
|
+
const line = status === "done" ? m.summary : status === "working" && m.session_id ? activityOf(m.session_id) ?? live?.summary ?? null : status === "waiting" && m.session_id ? summaryOf(m.session_id) ?? m.note ?? null : m.note ?? live?.summary ?? null;
|
|
639
|
+
return {
|
|
640
|
+
id: m.id,
|
|
641
|
+
title: m.title,
|
|
642
|
+
agent: m.agent,
|
|
643
|
+
session: m.session_id,
|
|
644
|
+
status,
|
|
645
|
+
line,
|
|
646
|
+
dir: m.launch_dir,
|
|
647
|
+
attempts: m.attempts,
|
|
648
|
+
startedAt: m.started_at,
|
|
649
|
+
doneAt: m.done_at
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function groupView(g, alive = null) {
|
|
653
|
+
const members = membersOf(g.id).map((m) => memberView(m, alive));
|
|
654
|
+
const counts = {
|
|
655
|
+
queued: 0,
|
|
656
|
+
starting: 0,
|
|
657
|
+
working: 0,
|
|
658
|
+
waiting: 0,
|
|
659
|
+
idle: 0,
|
|
660
|
+
done: 0,
|
|
661
|
+
closed: 0
|
|
662
|
+
};
|
|
663
|
+
for (const m of members) counts[m.status]++;
|
|
664
|
+
return {
|
|
665
|
+
id: g.id,
|
|
666
|
+
title: g.title,
|
|
667
|
+
phase: g.phase,
|
|
668
|
+
parallel: g.parallel,
|
|
669
|
+
fanIn: !!g.fan_in,
|
|
670
|
+
closeDone: !!g.close_done,
|
|
671
|
+
open: open(g.id),
|
|
672
|
+
createdAt: g.created_at,
|
|
673
|
+
counts,
|
|
674
|
+
members
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function planView(p) {
|
|
678
|
+
let phases = [];
|
|
679
|
+
try {
|
|
680
|
+
phases = JSON.parse(p.phases);
|
|
681
|
+
} catch {}
|
|
682
|
+
return {
|
|
683
|
+
id: p.id,
|
|
684
|
+
name: p.name,
|
|
685
|
+
phases,
|
|
686
|
+
log: all("SELECT text, at FROM plan_log WHERE plan_id = ? ORDER BY id DESC LIMIT 5", p.id).reverse(),
|
|
687
|
+
done: !!p.done_at,
|
|
688
|
+
createdAt: p.created_at
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
/** Every lead's team in a workspace: its plan (the open one, else its
|
|
692
|
+
* last) and its groups. */
|
|
693
|
+
async function teamsOf(workspaceId) {
|
|
694
|
+
const panes = await listPanesWithin(2e3);
|
|
695
|
+
const alive = panes ? new Set(panes.flatMap((p) => p.id ? [p.id] : [])) : null;
|
|
696
|
+
const groups = all("SELECT * FROM session_groups WHERE workspace_id = ? ORDER BY created_at", workspaceId);
|
|
697
|
+
const plans = all(`SELECT * FROM plans p WHERE workspace_id = ? AND created_at =
|
|
698
|
+
(SELECT MAX(created_at) FROM plans WHERE owner = p.owner)`, workspaceId);
|
|
699
|
+
return [.../* @__PURE__ */ new Set([...groups.map((g) => g.owner), ...plans.map((p) => p.owner)])].map((owner) => {
|
|
700
|
+
const plan = plans.find((p) => p.owner === owner);
|
|
701
|
+
return {
|
|
702
|
+
owner,
|
|
703
|
+
plan: plan ? planView(plan) : null,
|
|
704
|
+
groups: groups.filter((g) => g.owner === owner).map((g) => groupView(g, alive))
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/** The groups `owner` runs, for `work session team`. */
|
|
709
|
+
async function teamOf(owner) {
|
|
710
|
+
const s = getSession(owner);
|
|
711
|
+
if (!s?.workspace_id) throw httpError(404, `no session ${owner}`);
|
|
712
|
+
return (await teamsOf(s.workspace_id)).find((t) => t.owner === owner) ?? {
|
|
713
|
+
owner,
|
|
714
|
+
plan: null,
|
|
715
|
+
groups: []
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/** A child that answered and has sat idle this long closes: nobody is
|
|
719
|
+
* coming back to it, and each is a whole CLI's memory. */
|
|
720
|
+
var REAP_IDLE_MS = 6e5;
|
|
721
|
+
var REAP_EVERY_MS = 6e4;
|
|
722
|
+
/** Whether a session an agent started is done with: it has answered,
|
|
723
|
+
* nothing is queued or owed to it, it is at its prompt with nothing it
|
|
724
|
+
* waits on (its own children working keep it: it is their lead), and the
|
|
725
|
+
* user neither has it on screen nor has taken it over. */
|
|
726
|
+
function doneWith(s) {
|
|
727
|
+
if (!s.parent_id || s.keep || !s.answered_at) return false;
|
|
728
|
+
if (agentPhase(s.id) !== void 0 || turnSettling(s.id)) return false;
|
|
729
|
+
if (sessionWatched(s.id)) return false;
|
|
730
|
+
if (get("SELECT 1 AS x FROM session_messages WHERE to_session = ?", s.id)) return false;
|
|
731
|
+
if (lastTypedAt(s.id) > Date.parse(s.answered_at)) {
|
|
732
|
+
db.prepare("UPDATE sessions SET keep = 1 WHERE id = ?").run(s.id);
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
/** Close the sessions an agent started that are done with: `parent`'s
|
|
738
|
+
* once its job is done (or asked: `work session kill --done`), else any
|
|
739
|
+
* idle past `REAP_IDLE_MS`. A fan-out member's result stays in its group. */
|
|
740
|
+
async function closeFinished(parent) {
|
|
741
|
+
const rows = parent ? all("SELECT * FROM sessions WHERE parent_id = ?", parent) : all("SELECT * FROM sessions WHERE parent_id IS NOT NULL AND answered_at IS NOT NULL AND answered_at < ?", (/* @__PURE__ */ new Date(Date.now() - REAP_IDLE_MS)).toISOString());
|
|
742
|
+
let closed = 0;
|
|
743
|
+
for (const s of rows) {
|
|
744
|
+
if (!doneWith(s)) continue;
|
|
745
|
+
await killSession(s.id);
|
|
746
|
+
closed++;
|
|
747
|
+
}
|
|
748
|
+
return closed;
|
|
749
|
+
}
|
|
750
|
+
/** Reaps asked for while one runs: leads whose job finished meanwhile
|
|
751
|
+
* (`""` is the idle sweep), each run once, in turn. */
|
|
752
|
+
var toReap = /* @__PURE__ */ new Set();
|
|
753
|
+
var reaping = false;
|
|
754
|
+
function reap(parent = "") {
|
|
755
|
+
toReap.add(parent);
|
|
756
|
+
if (reaping) return;
|
|
757
|
+
reaping = true;
|
|
758
|
+
(async () => {
|
|
759
|
+
while (toReap.size) {
|
|
760
|
+
const next = toReap.values().next().value;
|
|
761
|
+
toReap.delete(next);
|
|
762
|
+
await closeFinished(next || void 0).catch((err) => console.error(`[work] team: ${err.message}`));
|
|
763
|
+
}
|
|
764
|
+
})().finally(() => {
|
|
765
|
+
reaping = false;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
var started = false;
|
|
769
|
+
function startTeam() {
|
|
770
|
+
if (started) return;
|
|
771
|
+
started = true;
|
|
772
|
+
onTurnEnd((id, message) => {
|
|
773
|
+
if (!message) return false;
|
|
774
|
+
const line = cleanSummary(message);
|
|
775
|
+
if (line) db.prepare("UPDATE sessions SET summary = ? WHERE id = ?").run(line, id);
|
|
776
|
+
return false;
|
|
777
|
+
});
|
|
778
|
+
db.prepare(`DELETE FROM session_groups WHERE created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '${PRUNE_AFTER}')
|
|
779
|
+
AND id NOT IN (SELECT group_id FROM group_members WHERE state IN ('queued', 'running'))
|
|
780
|
+
AND id NOT IN (SELECT group_id FROM session_messages WHERE group_id IS NOT NULL)`).run();
|
|
781
|
+
onFinished((id) => reap(id));
|
|
782
|
+
setInterval(() => reap(), REAP_EVERY_MS).unref?.();
|
|
783
|
+
recompute();
|
|
784
|
+
kickPool();
|
|
785
|
+
}
|
|
786
|
+
//#endregion
|
|
787
|
+
export { startMessages as _, createPlan as a, logPlan as c, startTeam as d, teamOf as f, sessionGone as g, sendMessage as h, createGroup as i, retryMember as l, messageStates as m, closeDone as n, finishPlan as o, teamsOf as p, closeFinished as r, kickPool as s, clearGroup as t, skipMember as u, takeReplies as v };
|