@askexenow/exe-os 0.9.279 → 0.9.281
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/deploy/compose/.env.customer.example +1 -1
- package/deploy/compose/.env.default +2 -1
- package/deploy/compose/.env.example +1 -1
- package/deploy/compose/docker-compose.yml +74 -13
- package/deploy/compose/erp-nginx/nginx.conf +52 -0
- package/deploy/compose/generate-env.ts +1 -1
- package/deploy/compose/init-db.sql +13 -2
- package/deploy/stack-manifests/v0.9.json +11 -31
- package/dist/active-agent-2G5ARPXZ.js +26 -0
- package/dist/active-agent-2IAUPOJO.js +27 -0
- package/dist/active-agent-6ZBHGHXF.js +26 -0
- package/dist/active-agent-E23SCYER.js +27 -0
- package/dist/active-agent-KMZT44S4.js +26 -0
- package/dist/active-agent-LFFTVROM.js +27 -0
- package/dist/agentic-ontology-PCZB5HV5.js +25 -0
- package/dist/agentic-ontology-PGGJN2ES.js +25 -0
- package/dist/agentic-ontology-YLZRGG7G.js +25 -0
- package/dist/assets/tmux.conf +2 -0
- package/dist/backfill-metadata-GHUTESVD.js +599 -0
- package/dist/backfill-metadata-KQ4FEVUR.js +599 -0
- package/dist/backfill-metadata-MSIZ5PV2.js +599 -0
- package/dist/backfill-metadata-SKUAF4QY.js +599 -0
- package/dist/backfill-metadata-WXUP5OVY.js +599 -0
- package/dist/backfill-metadata-Y3YWCHKJ.js +599 -0
- package/dist/behaviors-27D52NU4.js +39 -0
- package/dist/behaviors-BHIVZRAN.js +39 -0
- package/dist/behaviors-H4DZECKL.js +39 -0
- package/dist/behaviors-HAEVJHQN.js +39 -0
- package/dist/behaviors-LDDK7WEB.js +39 -0
- package/dist/behaviors-WIUTIJF6.js +39 -0
- package/dist/bin/agentic-ontology-backfill.js +6 -6
- package/dist/bin/agentic-reflection-backfill.js +7 -7
- package/dist/bin/agentic-semantic-label.js +6 -6
- package/dist/bin/backfill-conversations.js +7 -7
- package/dist/bin/backfill-responses.js +7 -7
- package/dist/bin/backfill-vectors.js +9 -9
- package/dist/bin/bulk-sync-postgres.js +8 -8
- package/dist/bin/cc-doctor.js +4 -4
- package/dist/bin/cleanup-stale-review-tasks.js +10 -10
- package/dist/bin/cli.js +151 -16
- package/dist/bin/deferred-daemon-restart.js +1 -1
- package/dist/bin/exe-agent-config.js +2 -2
- package/dist/bin/exe-agent.js +7 -7
- package/dist/bin/exe-assign.js +9 -9
- package/dist/bin/exe-boot.js +18 -18
- package/dist/bin/exe-call.js +4 -4
- package/dist/bin/exe-cloud.js +6 -6
- package/dist/bin/exe-dispatch.js +10 -10
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-export-behaviors.js +8 -8
- package/dist/bin/exe-forget.js +7 -7
- package/dist/bin/exe-gateway.js +7 -7
- package/dist/bin/exe-healthcheck.js +4 -4
- package/dist/bin/exe-heartbeat.js +10 -10
- package/dist/bin/exe-kill.js +14 -14
- package/dist/bin/exe-launch-agent.js +18 -18
- package/dist/bin/exe-new-employee.js +7 -7
- package/dist/bin/exe-pending-messages.js +11 -11
- package/dist/bin/exe-pending-notifications.js +10 -10
- package/dist/bin/exe-pending-reviews.js +10 -10
- package/dist/bin/exe-rename.js +4 -4
- package/dist/bin/exe-review.js +13 -13
- package/dist/bin/exe-search.js +6 -6
- package/dist/bin/exe-session-cleanup.js +16 -16
- package/dist/bin/exe-settings.js +5 -5
- package/dist/bin/exe-start-codex.js +24 -13
- package/dist/bin/exe-start-opencode.js +9 -9
- package/dist/bin/exe-start.sh +2 -2
- package/dist/bin/exe-status.js +11 -11
- package/dist/bin/exe-support.js +2 -2
- package/dist/bin/exe-team.js +3 -3
- package/dist/bin/exe-watchdog.js +67 -4
- package/dist/bin/git-sweep.js +11 -11
- package/dist/bin/graph-backfill.js +7 -7
- package/dist/bin/graph-export.js +6 -6
- package/dist/bin/import-history.js +10 -10
- package/dist/bin/install.js +9 -8
- package/dist/bin/intercom-check.js +4 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +4 -4
- package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
- package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
- package/dist/bin/scan-tasks.js +10 -10
- package/dist/bin/setup.js +2 -2
- package/dist/bin/shard-migrate.js +5 -5
- package/dist/bin/stack-update.js +3 -3
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/capability-cards-F22XWGLB.js +88 -0
- package/dist/capability-cards-JG75HHOF.js +88 -0
- package/dist/capability-cards-KCKITXXZ.js +88 -0
- package/dist/capacity-monitor-CHXGKVEO.js +50 -0
- package/dist/capacity-monitor-CNW7PRLG.js +50 -0
- package/dist/capacity-monitor-HET7G4MG.js +50 -0
- package/dist/capacity-monitor-L3M5KB6T.js +50 -0
- package/dist/capacity-monitor-SAY7CD46.js +50 -0
- package/dist/capacity-monitor-V7LY63U6.js +50 -0
- package/dist/catchup-brief-BVZANLRZ.js +154 -0
- package/dist/catchup-brief-E7IF2QGR.js +154 -0
- package/dist/catchup-brief-JPOHC6UH.js +154 -0
- package/dist/catchup-brief-JQID2BF7.js +154 -0
- package/dist/catchup-brief-LDUGMC7S.js +154 -0
- package/dist/catchup-brief-NZDLL7SA.js +154 -0
- package/dist/catchup-brief-OOIXVGBA.js +154 -0
- package/dist/catchup-brief-RW5GN43K.js +154 -0
- package/dist/catchup-brief-UPXHDYTB.js +154 -0
- package/dist/chunk-2BI3FQKL.js +1876 -0
- package/dist/chunk-2CJUQGHH.js +362 -0
- package/dist/chunk-2EVYBMBJ.js +128 -0
- package/dist/chunk-2IA7BLFA.js +2078 -0
- package/dist/chunk-2NQOZOXG.js +2113 -0
- package/dist/chunk-2QK5E3LB.js +128 -0
- package/dist/chunk-2UATPAHA.js +244 -0
- package/dist/chunk-2VTCG4ZU.js +1352 -0
- package/dist/chunk-2XEWT25L.js +290 -0
- package/dist/chunk-2YGI36DV.js +1119 -0
- package/dist/chunk-2ZFUZKF5.js +197 -0
- package/dist/chunk-32BQN2QN.js +185 -0
- package/dist/chunk-33JMO4UV.js +157 -0
- package/dist/chunk-37C3FRF2.js +1352 -0
- package/dist/chunk-3ABB3GEN.js +97 -0
- package/dist/chunk-3DZAYLXY.js +377 -0
- package/dist/chunk-3FCQSK7F.js +4318 -0
- package/dist/chunk-3LEZZS52.js +2113 -0
- package/dist/chunk-3MGBE7GR.js +76 -0
- package/dist/chunk-3MTV4FJL.js +271 -0
- package/dist/chunk-3NN7VQ27.js +1352 -0
- package/dist/chunk-3O46YCET.js +30 -0
- package/dist/chunk-3SDHTGTT.js +369 -0
- package/dist/chunk-3XLXETYH.js +128 -0
- package/dist/chunk-433KKYLZ.js +539 -0
- package/dist/chunk-4533HNOG.js +70 -0
- package/dist/chunk-456UW3MT.js +731 -0
- package/dist/chunk-4AU56XV2.js +58 -0
- package/dist/chunk-4CCAANIM.js +85 -0
- package/dist/chunk-4J5TKW7C.js +1352 -0
- package/dist/chunk-4MRP6EBR.js +280 -0
- package/dist/chunk-4MRWW52U.js +14219 -0
- package/dist/chunk-4QN4XAB5.js +227 -0
- package/dist/chunk-4T2F5SJA.js +2078 -0
- package/dist/chunk-4WWECNAY.js +50 -0
- package/dist/chunk-4ZJDDR6L.js +171 -0
- package/dist/chunk-522EOPM6.js +382 -0
- package/dist/chunk-52HOURUP.js +369 -0
- package/dist/chunk-52UDAVZE.js +3208 -0
- package/dist/chunk-52XEYRFA.js +362 -0
- package/dist/chunk-54XM4CYO.js +1076 -0
- package/dist/chunk-5BECNOD5.js +14219 -0
- package/dist/chunk-5BUPCYCN.js +604 -0
- package/dist/chunk-5CVQOWOY.js +1350 -0
- package/dist/chunk-5DGFPLCO.js +214 -0
- package/dist/chunk-5DSH4NNZ.js +731 -0
- package/dist/chunk-5FAMUB4X.js +204 -0
- package/dist/chunk-5MCJQCAM.js +128 -0
- package/dist/chunk-5OCQ4NMK.js +284 -0
- package/dist/chunk-5ODRK4TE.js +1076 -0
- package/dist/chunk-5PGZJQUI.js +58 -0
- package/dist/chunk-5QDLLWZ3.js +510 -0
- package/dist/chunk-5TT4VL25.js +167 -0
- package/dist/chunk-5VV3WZB6.js +76 -0
- package/dist/chunk-67TZJXNZ.js +262 -0
- package/dist/chunk-6FEZ7GN2.js +123 -0
- package/dist/chunk-6H74SOGI.js +204 -0
- package/dist/chunk-6M74TBVW.js +290 -0
- package/dist/chunk-6MDKSLYM.js +123 -0
- package/dist/chunk-6MOGND7S.js +14219 -0
- package/dist/chunk-6OFFJSL5.js +33 -0
- package/dist/chunk-6ST7MMLG.js +240 -0
- package/dist/chunk-6TRSVY7L.js +181 -0
- package/dist/chunk-6WCS7ZNK.js +85 -0
- package/dist/chunk-6WFUH2B4.js +345 -0
- package/dist/chunk-6WRYDREW.js +539 -0
- package/dist/chunk-72UA2FB3.js +181 -0
- package/dist/chunk-74YEBPGM.js +271 -0
- package/dist/chunk-77C3E5LS.js +54 -0
- package/dist/chunk-77DMFEOL.js +30 -0
- package/dist/chunk-7COXVQ5W.js +214 -0
- package/dist/chunk-7EAYV7G2.js +345 -0
- package/dist/chunk-7FT4TXF3.js +4318 -0
- package/dist/chunk-7HLWBYH7.js +60 -0
- package/dist/chunk-7L2EV3XX.js +366 -0
- package/dist/chunk-7MAQO7FA.js +394 -0
- package/dist/chunk-7NPWPF2X.js +712 -0
- package/dist/chunk-7NWWQ6QX.js +1094 -0
- package/dist/chunk-AB5QAKU3.js +1119 -0
- package/dist/chunk-AEQOKSU4.js +214 -0
- package/dist/chunk-AFPNLGJJ.js +377 -0
- package/dist/chunk-ALSTZCWT.js +204 -0
- package/dist/chunk-ARUTDXZX.js +280 -0
- package/dist/chunk-ARWKPNWX.js +284 -0
- package/dist/chunk-AU47B4QY.js +129 -0
- package/dist/chunk-AXOREWVL.js +836 -0
- package/dist/chunk-B3UT6H2X.js +14219 -0
- package/dist/chunk-B5MNC54V.js +127 -0
- package/dist/chunk-B74VSMKX.js +1350 -0
- package/dist/chunk-BDEB2THB.js +127 -0
- package/dist/chunk-BHWLH44J.js +362 -0
- package/dist/chunk-BIISXDSB.js +50 -0
- package/dist/chunk-BOCQAH6E.js +171 -0
- package/dist/chunk-BUPZ3HD2.js +85 -0
- package/dist/chunk-BVETHEMM.js +456 -0
- package/dist/chunk-BWVLMA53.js +2113 -0
- package/dist/chunk-C3N2MXIP.js +85 -0
- package/dist/chunk-C3PAAUNW.js +54 -0
- package/dist/chunk-C5WLBKMJ.js +50 -0
- package/dist/chunk-C62T6R2A.js +97 -0
- package/dist/chunk-C7CRZGD7.js +81 -0
- package/dist/chunk-CAQE2WME.js +214 -0
- package/dist/chunk-CAWCO2VR.js +2078 -0
- package/dist/chunk-CB4SB3VJ.js +377 -0
- package/dist/chunk-CFLJBUHT.js +1094 -0
- package/dist/chunk-CITYIYX5.js +89 -0
- package/dist/chunk-CLBCRQWU.js +210 -0
- package/dist/chunk-CV5KBAIK.js +33 -0
- package/dist/chunk-D5IPAFJO.js +82 -0
- package/dist/chunk-DA53BA7V.js +271 -0
- package/dist/chunk-DAYSDWXA.js +1068 -0
- package/dist/chunk-DHBIRCFL.js +290 -0
- package/dist/chunk-DHP3ISXV.js +185 -0
- package/dist/chunk-DI4URIUB.js +227 -0
- package/dist/chunk-DIAOAGZ6.js +38 -0
- package/dist/chunk-DJ2MBAAF.js +280 -0
- package/dist/chunk-DKH2VU3V.js +97 -0
- package/dist/chunk-DNTCYFJ6.js +76 -0
- package/dist/chunk-DOTMUSXW.js +81 -0
- package/dist/chunk-DPDRRS7T.js +103 -0
- package/dist/chunk-DQM5DO5S.js +128 -0
- package/dist/chunk-DT3EV6CW.js +103 -0
- package/dist/chunk-DX45HDWY.js +1076 -0
- package/dist/chunk-E72BD6MG.js +284 -0
- package/dist/chunk-EAPUSVKS.js +375 -0
- package/dist/chunk-EFUANRRT.js +85 -0
- package/dist/chunk-EG2SCT5R.js +1352 -0
- package/dist/chunk-EGFKI6W2.js +129 -0
- package/dist/chunk-EJD2JU77.js +58 -0
- package/dist/chunk-EMA5YFAV.js +362 -0
- package/dist/chunk-EMXYUAVP.js +81 -0
- package/dist/chunk-ENM2TAAM.js +14219 -0
- package/dist/chunk-EPDRTPVP.js +1876 -0
- package/dist/chunk-EQQNJ4B3.js +128 -0
- package/dist/chunk-EVVR5QCW.js +290 -0
- package/dist/chunk-EW6XDHID.js +221 -0
- package/dist/chunk-EXECVV2I.js +539 -0
- package/dist/chunk-EYEGSAWZ.js +1094 -0
- package/dist/chunk-F6L33PAQ.js +231 -0
- package/dist/chunk-F73FLMUA.js +668 -0
- package/dist/chunk-FC2SCTVE.js +38 -0
- package/dist/chunk-FECRU7AB.js +221 -0
- package/dist/chunk-FGMF5K2G.js +231 -0
- package/dist/chunk-FIWKVHEU.js +204 -0
- package/dist/chunk-FJ5WSXU5.js +97 -0
- package/dist/chunk-FLBMX6YY.js +75 -0
- package/dist/chunk-FLKGAL3F.js +333 -0
- package/dist/chunk-FRXQSRTO.js +1076 -0
- package/dist/chunk-FSRKIZGZ.js +630 -0
- package/dist/chunk-FTG7I5CB.js +81 -0
- package/dist/chunk-FVHQL6ND.js +89 -0
- package/dist/chunk-G2FYKVRB.js +54 -0
- package/dist/chunk-G4RPL2RH.js +333 -0
- package/dist/chunk-GACE3UCH.js +271 -0
- package/dist/chunk-GAQ2OCPU.js +204 -0
- package/dist/chunk-GMM3ALHG.js +1350 -0
- package/dist/chunk-GSNDMSWZ.js +1119 -0
- package/dist/chunk-GVFRLWX7.js +30 -0
- package/dist/chunk-H3XMZOWW.js +1119 -0
- package/dist/chunk-H4IGERTU.js +712 -0
- package/dist/chunk-HAKXE6LN.js +123 -0
- package/dist/chunk-HCIMNY45.js +2078 -0
- package/dist/chunk-HKB4GOGL.js +159 -0
- package/dist/chunk-HLP3ZDTW.js +448 -0
- package/dist/chunk-HMMM6YFN.js +1068 -0
- package/dist/chunk-HOYWKQAA.js +510 -0
- package/dist/chunk-HVKPZCXA.js +271 -0
- package/dist/chunk-HWDD64IW.js +712 -0
- package/dist/chunk-HWMCULHY.js +127 -0
- package/dist/chunk-HZJDZ6NX.js +89 -0
- package/dist/chunk-HZZHRZPK.js +210 -0
- package/dist/chunk-I5BYXBAD.js +1350 -0
- package/dist/chunk-I6NQVJYF.js +97 -0
- package/dist/chunk-I6YJU2DE.js +128 -0
- package/dist/chunk-I7GRBEFS.js +690 -0
- package/dist/chunk-ICRWTYNW.js +103 -0
- package/dist/chunk-IFYEMOZG.js +85 -0
- package/dist/chunk-IHR5L3YV.js +244 -0
- package/dist/chunk-ILIAYMKP.js +362 -0
- package/dist/chunk-IPZ4JWBG.js +4318 -0
- package/dist/chunk-IQHXWJ2Q.js +402 -0
- package/dist/chunk-J2PDHWJV.js +448 -0
- package/dist/chunk-J373WY35.js +82 -0
- package/dist/chunk-J5MWPC33.js +167 -0
- package/dist/chunk-JAZEO27N.js +1094 -0
- package/dist/chunk-JBXANNNB.js +70 -0
- package/dist/chunk-JDSJ3NU7.js +836 -0
- package/dist/chunk-JMK2BOGQ.js +2113 -0
- package/dist/chunk-JPBMIYWF.js +1352 -0
- package/dist/chunk-JS2KQWMD.js +377 -0
- package/dist/chunk-JU42WYSP.js +448 -0
- package/dist/chunk-JY6EXBFI.js +373 -0
- package/dist/chunk-JZLIBXI7.js +14219 -0
- package/dist/chunk-JZPTKXJ6.js +668 -0
- package/dist/chunk-K26DLCUP.js +299 -0
- package/dist/chunk-K4JTUUWC.js +1094 -0
- package/dist/chunk-KBUQ4X5Y.js +1076 -0
- package/dist/chunk-KEYMA4ZP.js +510 -0
- package/dist/chunk-KFSW4EKI.js +50 -0
- package/dist/chunk-KHGNN6GL.js +2078 -0
- package/dist/chunk-KT2XEGXZ.js +345 -0
- package/dist/chunk-KVLB2PD6.js +97 -0
- package/dist/chunk-KZ57MNYJ.js +562 -0
- package/dist/chunk-L3YBRBKL.js +1076 -0
- package/dist/chunk-L7VZ32NA.js +89 -0
- package/dist/chunk-LEHLADW4.js +221 -0
- package/dist/chunk-LFAX3MCC.js +668 -0
- package/dist/chunk-LJZZS4B5.js +81 -0
- package/dist/chunk-LNV6OOHY.js +150 -0
- package/dist/chunk-LPD4HILQ.js +262 -0
- package/dist/chunk-LSJ3ADDI.js +51 -0
- package/dist/chunk-LUMS2MAS.js +58 -0
- package/dist/chunk-LV4SEC6C.js +394 -0
- package/dist/chunk-LXXBEI4A.js +284 -0
- package/dist/chunk-LYH3ZLYF.js +197 -0
- package/dist/chunk-M2QK3YD5.js +214 -0
- package/dist/chunk-M4NPCAIH.js +456 -0
- package/dist/chunk-M5N2TE3L.js +76 -0
- package/dist/chunk-MFJ62LQ5.js +157 -0
- package/dist/chunk-MGEZNKOD.js +836 -0
- package/dist/chunk-MJ44I6HG.js +244 -0
- package/dist/chunk-MJ7X5IBW.js +227 -0
- package/dist/chunk-MLGRWCY4.js +54 -0
- package/dist/chunk-MQZX57IY.js +348 -0
- package/dist/chunk-MRRYZQUS.js +510 -0
- package/dist/chunk-MY7MFF6J.js +103 -0
- package/dist/chunk-MYA2X5OY.js +185 -0
- package/dist/chunk-MYEABW5Z.js +630 -0
- package/dist/chunk-MZ2TDCAL.js +402 -0
- package/dist/chunk-MZV4HV2T.js +363 -0
- package/dist/chunk-N663LPHD.js +97 -0
- package/dist/chunk-NDG4GHYH.js +171 -0
- package/dist/chunk-NF3FRB7Z.js +271 -0
- package/dist/chunk-NGK4DUTW.js +85 -0
- package/dist/chunk-NXYIFEPV.js +539 -0
- package/dist/chunk-O25CS3BH.js +836 -0
- package/dist/chunk-O2GVE5B5.js +58 -0
- package/dist/chunk-O5GUCDR2.js +456 -0
- package/dist/chunk-OADURIIN.js +258 -0
- package/dist/chunk-OBZNRECA.js +128 -0
- package/dist/chunk-OCYNSIQR.js +204 -0
- package/dist/chunk-OGYVGJ7F.js +402 -0
- package/dist/chunk-OMZ2RLJG.js +214 -0
- package/dist/chunk-ORDHJRWN.js +299 -0
- package/dist/chunk-OSPIJMCD.js +210 -0
- package/dist/chunk-OUGWEH4J.js +240 -0
- package/dist/chunk-OUKLRG7K.js +54 -0
- package/dist/chunk-OV5MJQGC.js +1876 -0
- package/dist/chunk-OVOWVZXV.js +127 -0
- package/dist/chunk-P3AUAXMW.js +712 -0
- package/dist/chunk-P3YEMU7L.js +167 -0
- package/dist/chunk-P73YSDV7.js +284 -0
- package/dist/chunk-PAMQBA5K.js +157 -0
- package/dist/chunk-PC635OAG.js +4318 -0
- package/dist/chunk-PDOGDOO6.js +81 -0
- package/dist/chunk-PDSMTX4H.js +448 -0
- package/dist/chunk-PH46R4J6.js +348 -0
- package/dist/chunk-PJP2EP7P.js +394 -0
- package/dist/chunk-PN5VKI6G.js +448 -0
- package/dist/chunk-PODFWH3V.js +333 -0
- package/dist/chunk-PPWH3SHR.js +1068 -0
- package/dist/chunk-PW4S2SN7.js +14219 -0
- package/dist/chunk-PWJI2O5R.js +171 -0
- package/dist/chunk-PWPJK7KB.js +4318 -0
- package/dist/chunk-PXONZVG4.js +377 -0
- package/dist/chunk-PZLB7XHF.js +1119 -0
- package/dist/chunk-QAOGJRZD.js +369 -0
- package/dist/chunk-QC3LAEI7.js +197 -0
- package/dist/chunk-QDWQDUWI.js +668 -0
- package/dist/chunk-QKJFD6BH.js +1350 -0
- package/dist/chunk-QLBF5REP.js +731 -0
- package/dist/chunk-QMU256CS.js +262 -0
- package/dist/chunk-QMVUSITJ.js +539 -0
- package/dist/chunk-QNYVJGFM.js +345 -0
- package/dist/chunk-QOWE36S5.js +1119 -0
- package/dist/chunk-QQF3XGQ5.js +14219 -0
- package/dist/chunk-QRLP3WDY.js +373 -0
- package/dist/chunk-QRPFQNI3.js +150 -0
- package/dist/chunk-QSSU5XWD.js +731 -0
- package/dist/chunk-QWFF3NWP.js +382 -0
- package/dist/chunk-QXZAGVAV.js +2078 -0
- package/dist/chunk-RA54MW64.js +244 -0
- package/dist/chunk-RF7PUWXI.js +197 -0
- package/dist/chunk-RUF7DGXX.js +1350 -0
- package/dist/chunk-S3INDYSO.js +244 -0
- package/dist/chunk-S5JWN7XT.js +382 -0
- package/dist/chunk-SEIYC4D4.js +197 -0
- package/dist/chunk-SFQXO7I6.js +1352 -0
- package/dist/chunk-SLQVTHH5.js +369 -0
- package/dist/chunk-SO4SCJZ2.js +362 -0
- package/dist/chunk-SOLGWOCM.js +836 -0
- package/dist/chunk-SVHHWATZ.js +377 -0
- package/dist/chunk-SVOMFAJM.js +50 -0
- package/dist/chunk-SY2B74KL.js +345 -0
- package/dist/chunk-SYIROM36.js +197 -0
- package/dist/chunk-SYSDSINS.js +76 -0
- package/dist/chunk-T5XMAQAC.js +85 -0
- package/dist/chunk-T6QPXXXW.js +712 -0
- package/dist/chunk-TGHIBL4B.js +244 -0
- package/dist/chunk-TH22CNKK.js +70 -0
- package/dist/chunk-TJMNFB2W.js +58 -0
- package/dist/chunk-TO5M5YCT.js +41 -0
- package/dist/chunk-TQ4VXUAF.js +129 -0
- package/dist/chunk-TWPNURFU.js +50 -0
- package/dist/chunk-U3PEZZ7D.js +712 -0
- package/dist/chunk-U76CXW3D.js +55 -0
- package/dist/chunk-UAB7RQC4.js +41 -0
- package/dist/chunk-UJUPYSVY.js +333 -0
- package/dist/chunk-UKVPGFD4.js +4318 -0
- package/dist/chunk-UMEIBDYW.js +97 -0
- package/dist/chunk-UVD2VXDC.js +127 -0
- package/dist/chunk-UXW5TB7Y.js +240 -0
- package/dist/chunk-V6M2GKUD.js +89 -0
- package/dist/chunk-VB2N5WOX.js +150 -0
- package/dist/chunk-VD5NF6RG.js +348 -0
- package/dist/chunk-VFPBTJBN.js +58 -0
- package/dist/chunk-VIFBK7VL.js +510 -0
- package/dist/chunk-VLZEMRG3.js +167 -0
- package/dist/chunk-VM3V6VK7.js +230 -0
- package/dist/chunk-VMCGKBHB.js +1352 -0
- package/dist/chunk-VNBHLJTU.js +76 -0
- package/dist/chunk-VNIYZAR5.js +128 -0
- package/dist/chunk-VO73AAFF.js +539 -0
- package/dist/chunk-VOZL5CBT.js +731 -0
- package/dist/chunk-VYV4KOD2.js +85 -0
- package/dist/chunk-W4SRJBAT.js +171 -0
- package/dist/chunk-W5W3LZ3Q.js +54 -0
- package/dist/chunk-WDNZEOM3.js +38 -0
- package/dist/chunk-WK2X6PE6.js +167 -0
- package/dist/chunk-WUKEXVOR.js +3208 -0
- package/dist/chunk-WYU7FFTV.js +127 -0
- package/dist/chunk-WZLKGEUJ.js +510 -0
- package/dist/chunk-X3Z35Q6L.js +373 -0
- package/dist/chunk-X46O77S4.js +630 -0
- package/dist/chunk-X4VOU6BQ.js +382 -0
- package/dist/chunk-X6C2EL46.js +836 -0
- package/dist/chunk-X6EEVSVG.js +290 -0
- package/dist/chunk-XFHGWGNB.js +1094 -0
- package/dist/chunk-XKOLRWYA.js +33 -0
- package/dist/chunk-XMMIL3UD.js +402 -0
- package/dist/chunk-XWILC6VA.js +290 -0
- package/dist/chunk-XZ2KJ3OG.js +103 -0
- package/dist/chunk-Y2PCFIJE.js +345 -0
- package/dist/chunk-Y4OQCX4C.js +97 -0
- package/dist/chunk-Y5OXHXCP.js +731 -0
- package/dist/chunk-Y67VYYOA.js +231 -0
- package/dist/chunk-YGQCQTQH.js +230 -0
- package/dist/chunk-YGWFBN5A.js +299 -0
- package/dist/chunk-YKRQXHXX.js +1352 -0
- package/dist/chunk-YMKUXZIG.js +379 -0
- package/dist/chunk-YMUH7TWT.js +382 -0
- package/dist/chunk-YO43YXZS.js +1076 -0
- package/dist/chunk-YOMLMT7E.js +230 -0
- package/dist/chunk-YPESIZOB.js +14219 -0
- package/dist/chunk-YWN6DKZJ.js +181 -0
- package/dist/chunk-Z2CGCIU2.js +89 -0
- package/dist/chunk-Z42MXYWW.js +691 -0
- package/dist/chunk-Z75DAPMI.js +14219 -0
- package/dist/chunk-ZDCRV6PH.js +58 -0
- package/dist/chunk-ZDIKSXMV.js +58 -0
- package/dist/chunk-ZFU7SITA.js +284 -0
- package/dist/chunk-ZGBIUKNK.js +668 -0
- package/dist/chunk-ZJP3ZONM.js +3208 -0
- package/dist/chunk-ZLAWNHQR.js +448 -0
- package/dist/chunk-ZLXATOXN.js +167 -0
- package/dist/chunk-ZM5WXR57.js +382 -0
- package/dist/chunk-ZME5UQSN.js +333 -0
- package/dist/chunk-ZQ4CBSJ3.js +171 -0
- package/dist/chunk-ZQXOZ4U2.js +14219 -0
- package/dist/chunk-ZTS2KRU5.js +333 -0
- package/dist/co-activation-NUEQYXE5.js +73 -0
- package/dist/co-activation-PLLVFEDK.js +73 -0
- package/dist/co-activation-ZG5HLBCZ.js +73 -0
- package/dist/co-occurrence-7S5KWQB2.js +94 -0
- package/dist/co-occurrence-OIK6WLXN.js +94 -0
- package/dist/co-occurrence-QB4MQSY7.js +94 -0
- package/dist/co-occurrence-X5SWDXT2.js +94 -0
- package/dist/code-context-index-CG4P2JZI.js +30 -0
- package/dist/conversation-entity-extractor-ZN3J4WGY.js +114 -0
- package/dist/core-memory-D3AWRAMA.js +110 -0
- package/dist/core-memory-GOPBRGGZ.js +110 -0
- package/dist/core-memory-JUTA3DWM.js +110 -0
- package/dist/core-memory-NW6GT543.js +110 -0
- package/dist/core-memory-TPY5XO3E.js +110 -0
- package/dist/core-memory-XLCU6L5M.js +110 -0
- package/dist/crdt-sync-6GDTEJBT.js +33 -0
- package/dist/crdt-sync-EPKHPGRZ.js +33 -0
- package/dist/crdt-sync-UIQJ5U7T.js +33 -0
- package/dist/crm-webhook-2BYS65SZ.js +10 -0
- package/dist/crm-webhook-3AZQWNMQ.js +10 -0
- package/dist/crm-webhook-AUYAFOV3.js +10 -0
- package/dist/crm-webhook-MKN23JNU.js +10 -0
- package/dist/crm-webhook-SM63BPXO.js +10 -0
- package/dist/crm-webhook-VM72DSLW.js +10 -0
- package/dist/cto-delegation-gate-GQNNQON7.js +279 -0
- package/dist/cto-delegation-gate-LTI5OTGF.js +279 -0
- package/dist/cto-delegation-gate-PQY5TOVZ.js +279 -0
- package/dist/cto-delegation-gate-Q2IIOK3T.js +279 -0
- package/dist/cto-delegation-gate-V5VVUR3G.js +279 -0
- package/dist/cto-delegation-gate-VVDKZGQ6.js +279 -0
- package/dist/daemon-orchestration-2ZD3N5AP.js +138 -0
- package/dist/daemon-orchestration-3J4K62I5.js +138 -0
- package/dist/daemon-orchestration-C7AAS67Q.js +138 -0
- package/dist/daemon-orchestration-L47GDSJG.js +138 -0
- package/dist/daemon-orchestration-OBCAJB2H.js +138 -0
- package/dist/daemon-orchestration-ZAQF437L.js +138 -0
- package/dist/db-backup-F7VP4QRH.js +33 -0
- package/dist/db-backup-KVYC57W7.js +33 -0
- package/dist/db-backup-L64NT6GW.js +33 -0
- package/dist/doc-graph-extractor-7EXXBHN5.js +132 -0
- package/dist/doc-graph-extractor-H2ETEINP.js +132 -0
- package/dist/doc-graph-extractor-M3G5IZAT.js +132 -0
- package/dist/doc-graph-extractor-PCUZEYCH.js +132 -0
- package/dist/dreaming-3CVAKE4O.js +33 -0
- package/dist/dreaming-4OZXSLE3.js +33 -0
- package/dist/dreaming-DMKA2UH6.js +33 -0
- package/dist/dreaming-EP3WNLQ3.js +33 -0
- package/dist/dreaming-INJT3QVE.js +33 -0
- package/dist/dreaming-Z2RYEYNT.js +33 -0
- package/dist/entity-boost-DVQU26WN.js +375 -0
- package/dist/exe-drift-GEWNIK7A.js +69 -0
- package/dist/exe-drift-XCGH7AFO.js +69 -0
- package/dist/exe-drift-ZCUGNELY.js +69 -0
- package/dist/exe-export-5LKQALH2.js +75 -0
- package/dist/exe-export-7CME3MXT.js +75 -0
- package/dist/exe-export-7DKAU5IP.js +75 -0
- package/dist/exe-export-BCHH6OE6.js +75 -0
- package/dist/exe-export-GQMMQWON.js +75 -0
- package/dist/exe-export-Q5O2YADE.js +75 -0
- package/dist/exe-import-25AMCNVB.js +78 -0
- package/dist/exe-import-DMXDVIF2.js +78 -0
- package/dist/exe-import-EHJM6OZW.js +78 -0
- package/dist/exe-import-J6BPUDJ3.js +78 -0
- package/dist/exe-import-PDRIZVYF.js +78 -0
- package/dist/exe-import-ZCKUDFKL.js +78 -0
- package/dist/exe-key-4SRZOC5W.js +580 -0
- package/dist/exe-key-5QK7VKBJ.js +580 -0
- package/dist/exe-key-FUWLLI3U.js +580 -0
- package/dist/exe-key-GFLJRRHG.js +580 -0
- package/dist/exe-key-RKKNVUMP.js +580 -0
- package/dist/exe-key-XWTYIY3B.js +580 -0
- package/dist/exe-snapshot-CR2AFYRP.js +337 -0
- package/dist/exe-snapshot-DWXKW6SC.js +337 -0
- package/dist/exe-snapshot-G4I5FQMK.js +337 -0
- package/dist/exe-snapshot-GWU7QTZK.js +337 -0
- package/dist/exe-snapshot-IYPOSAWY.js +337 -0
- package/dist/exe-snapshot-QIIQZXZM.js +337 -0
- package/dist/fast-db-init-6QG6YQNT.js +7 -0
- package/dist/fast-db-init-BSUZIK6R.js +7 -0
- package/dist/fast-db-init-MAISJZ3L.js +7 -0
- package/dist/fast-db-init-WKK5SPFG.js +7 -0
- package/dist/fast-db-init-X2QDQUA4.js +7 -0
- package/dist/fast-db-init-XZ6WHYNC.js +7 -0
- package/dist/founder-context-TOMNUBGJ.js +96 -0
- package/dist/founder-context-UU3V6MAS.js +96 -0
- package/dist/gateway/index.js +8 -8
- package/dist/gateway-client-GRBOJQYC.js +11 -0
- package/dist/git-staleness-FEPFMZKF.js +111 -0
- package/dist/git-staleness-G7347RT3.js +111 -0
- package/dist/git-staleness-HYVYLCW3.js +111 -0
- package/dist/git-task-sweep-GGTGURJ6.js +41 -0
- package/dist/git-task-sweep-IRV52JIM.js +41 -0
- package/dist/git-task-sweep-T6BSM3GS.js +41 -0
- package/dist/git-task-sweep-TQA4QTSM.js +41 -0
- package/dist/git-task-sweep-UCJ3KLDS.js +41 -0
- package/dist/git-task-sweep-XFX3OTBY.js +41 -0
- package/dist/global-procedures-3AURRMKO.js +21 -0
- package/dist/global-procedures-3NYFIROX.js +21 -0
- package/dist/global-procedures-JPCYBZYC.js +21 -0
- package/dist/global-procedures-NOBNQDNA.js +21 -0
- package/dist/global-procedures-ZFIEDO7E.js +21 -0
- package/dist/gotrue-session-2IT7MDGW.js +171 -0
- package/dist/graph-auto-extract-HC576FNS.js +182 -0
- package/dist/graph-auto-extract-OC3AOSMW.js +182 -0
- package/dist/graph-auto-extract-PVDYEJBY.js +182 -0
- package/dist/graph-auto-extract-RMZT3EYN.js +182 -0
- package/dist/graph-rag-HFBYZF3M.js +31 -0
- package/dist/hooks/bug-report-worker.js +12 -12
- package/dist/hooks/codex-stop-task-finalizer.js +12 -12
- package/dist/hooks/commit-complete.js +12 -12
- package/dist/hooks/error-recall.js +7 -7
- package/dist/hooks/exe-heartbeat-hook.js +3 -3
- package/dist/hooks/ingest-worker.js +3 -3
- package/dist/hooks/ingest.js +6 -6
- package/dist/hooks/instructions-loaded.js +4 -4
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +4 -4
- package/dist/hooks/post-compact.js +11 -11
- package/dist/hooks/post-tool-combined.js +6 -6
- package/dist/hooks/pre-compact.js +15 -15
- package/dist/hooks/pre-tool-use.js +15 -15
- package/dist/hooks/prompt-submit.js +25 -25
- package/dist/hooks/session-end.js +20 -20
- package/dist/hooks/session-start.js +11 -11
- package/dist/hooks/stop.js +18 -18
- package/dist/hooks/subagent-stop.js +11 -11
- package/dist/hooks/summary-worker.js +19 -19
- package/dist/index.js +18 -18
- package/dist/installer-3QCLMBSY.js +297 -0
- package/dist/installer-6SAIKLV6.js +343 -0
- package/dist/installer-72XXLBRP.js +39 -0
- package/dist/installer-HDXG2BZN.js +343 -0
- package/dist/installer-JALMKPCS.js +297 -0
- package/dist/installer-Q46SNNLU.js +39 -0
- package/dist/installer-RN25LFX6.js +39 -0
- package/dist/installer-W7PIPRCX.js +343 -0
- package/dist/installer-Z7WQEOS7.js +297 -0
- package/dist/lib/cloud-sync.js +5 -5
- package/dist/lib/consolidation.js +8 -8
- package/dist/lib/database.js +2 -2
- package/dist/lib/db-daemon-client.js +2 -2
- package/dist/lib/db.js +2 -2
- package/dist/lib/embedder.js +3 -3
- package/dist/lib/employee-templates.js +4 -4
- package/dist/lib/employees.js +2 -2
- package/dist/lib/exe-daemon-client.js +2 -2
- package/dist/lib/exe-daemon.js +50 -46
- package/dist/lib/hybrid-search.js +6 -6
- package/dist/lib/identity.js +2 -2
- package/dist/lib/keychain.js +1 -1
- package/dist/lib/license.js +1 -1
- package/dist/lib/messaging.js +10 -10
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +6 -6
- package/dist/lib/session-registry.js +4 -4
- package/dist/lib/skill-learning.js +7 -7
- package/dist/lib/store.js +5 -5
- package/dist/lib/task-router.js +3 -3
- package/dist/lib/tasks.js +11 -11
- package/dist/lib/tmux-routing.js +9 -9
- package/dist/lib/token-spend.js +3 -3
- package/dist/lib/ws-client.js +1 -1
- package/dist/license-gate-7JZCHOAG.js +14 -0
- package/dist/license-gate-OP4SKL4P.js +14 -0
- package/dist/mcp/register-tools.js +66 -66
- package/dist/mcp/server.js +67 -67
- package/dist/mcp/tools/complete-reminder.js +4 -4
- package/dist/mcp/tools/create-reminder.js +4 -4
- package/dist/mcp/tools/create-task.js +13 -13
- package/dist/mcp/tools/deactivate-behavior.js +8 -8
- package/dist/mcp/tools/list-reminders.js +4 -4
- package/dist/mcp/tools/list-tasks.js +13 -13
- package/dist/mcp/tools/send-message.js +12 -12
- package/dist/mcp/tools/update-task.js +12 -12
- package/dist/mcp-health-VULNT722.js +17 -0
- package/dist/mcp-health-WDOB6XUB.js +19 -0
- package/dist/mcp-http-config-2OZ7N74D.js +28 -0
- package/dist/mcp-http-config-4VXA5K73.js +28 -0
- package/dist/mcp-http-config-YFZWGX4W.js +28 -0
- package/dist/memory-cards-2K6QRZU6.js +179 -0
- package/dist/memory-cards-AKXTAQZG.js +179 -0
- package/dist/memory-cards-KSJF5OH2.js +179 -0
- package/dist/memory-graph-extractor-IJD5HWYT.js +21 -0
- package/dist/memory-graph-extractor-O4GAXOK5.js +21 -0
- package/dist/memory-graph-extractor-O7KEZROX.js +21 -0
- package/dist/memory-graph-extractor-Q66O22GE.js +21 -0
- package/dist/memory-poisoning-defense-2JRPWT5V.js +223 -0
- package/dist/memory-poisoning-defense-ADIGRTRJ.js +223 -0
- package/dist/memory-poisoning-defense-DH4A25NU.js +223 -0
- package/dist/memory-queue-client-4ZAOMEJR.js +16 -0
- package/dist/memory-reflection-ISY2BBDB.js +243 -0
- package/dist/memory-reflection-RAWMCOLK.js +243 -0
- package/dist/memory-reflection-Z5AQRR6H.js +243 -0
- package/dist/message-queue-client-EDB46D6M.js +92 -0
- package/dist/notifications-2VSWK2UJ.js +46 -0
- package/dist/notifications-4S253VQM.js +46 -0
- package/dist/notifications-JRKUMV4T.js +46 -0
- package/dist/notifications-MOTAHF7S.js +46 -0
- package/dist/notifications-SSRKYMKV.js +46 -0
- package/dist/notifications-XIMWAK7P.js +46 -0
- package/dist/oauth-server-D7D4574D.js +437 -0
- package/dist/oauth-server-MACN54SJ.js +437 -0
- package/dist/orchestration-events-BGP5RYQI.js +26 -0
- package/dist/orchestration-events-H6EZJEMV.js +26 -0
- package/dist/orchestration-events-MDXUEVRZ.js +26 -0
- package/dist/orchestrator-BCSQHCCZ.js +34 -0
- package/dist/orchestrator-DHK7RSSH.js +34 -0
- package/dist/orchestrator-J2EDJBWJ.js +34 -0
- package/dist/orchestrator-QODIF3U2.js +34 -0
- package/dist/orchestrator-R75WHQVA.js +34 -0
- package/dist/orchestrator-W6AO4RZM.js +34 -0
- package/dist/pipeline-router-2IFHZ5KC.js +14 -0
- package/dist/pipeline-router-4WUKQQEC.js +14 -0
- package/dist/pipeline-router-GWB2XK2Q.js +14 -0
- package/dist/pipeline-router-VCJCI5W7.js +14 -0
- package/dist/pipeline-router-VEP3NSZF.js +14 -0
- package/dist/pipeline-router-WV34UOJV.js +14 -0
- package/dist/plan-limits-IQK6XLCF.js +27 -0
- package/dist/plan-limits-NNJRAESF.js +27 -0
- package/dist/plan-limits-YTQW4UR4.js +27 -0
- package/dist/project-boot-46GZJTEX.js +299 -0
- package/dist/project-boot-PPHBBGIF.js +299 -0
- package/dist/project-boot-QWRBVBFG.js +299 -0
- package/dist/projection-worker-74KP2LHZ.js +1034 -0
- package/dist/projection-worker-GJ2M2BUF.js +1034 -0
- package/dist/projection-worker-HOLOWWX7.js +1034 -0
- package/dist/projection-worker-JEC7EM5Y.js +1034 -0
- package/dist/projection-worker-UPAWXI7P.js +1034 -0
- package/dist/projection-worker-ZIKDYBW5.js +1034 -0
- package/dist/reranker-5ZBP2RRN.js +19 -0
- package/dist/reranker-E2MQIMJL.js +19 -0
- package/dist/reranker-GLSDJT3V.js +19 -0
- package/dist/reranker-LBBXWNOD.js +19 -0
- package/dist/reranker-SIUVV2JD.js +19 -0
- package/dist/reranker-TEEWFPPU.js +19 -0
- package/dist/reranker-USWG7L7N.js +19 -0
- package/dist/reranker-VI3T6F2P.js +19 -0
- package/dist/reranker-XZ2EF4OH.js +19 -0
- package/dist/retrieval-health-JYRKPSII.js +7 -0
- package/dist/retrieval-health-OUV25J6S.js +7 -0
- package/dist/retrieval-health-U73JUAZL.js +7 -0
- package/dist/retrieval-health-WIEQKVAV.js +7 -0
- package/dist/retrieval-health-WSZ7TYFF.js +7 -0
- package/dist/review-polling-62JV55ZT.js +125 -0
- package/dist/review-polling-CJXLWFWK.js +125 -0
- package/dist/review-polling-DNBH4D7X.js +125 -0
- package/dist/review-polling-FAG2O7R4.js +125 -0
- package/dist/review-polling-RRD7IIEV.js +125 -0
- package/dist/review-polling-WVH6DK22.js +125 -0
- package/dist/runtime/index.js +12 -12
- package/dist/session-events-2ADD54VI.js +37 -0
- package/dist/session-events-67N3ESQN.js +37 -0
- package/dist/session-events-NCPIQK5Q.js +37 -0
- package/dist/session-events-QIJVBSKS.js +37 -0
- package/dist/session-events-SF75L7NG.js +37 -0
- package/dist/session-events-VTP7KQQM.js +37 -0
- package/dist/session-kill-telemetry-5MTQD4JW.js +30 -0
- package/dist/session-kill-telemetry-HS6HD2YE.js +30 -0
- package/dist/session-kill-telemetry-MRT5FVSM.js +30 -0
- package/dist/session-scope-6QDWFFGE.js +87 -0
- package/dist/session-scope-7ICYPC33.js +87 -0
- package/dist/session-scope-FZ7CJ2PU.js +87 -0
- package/dist/session-scope-KMXD6EE6.js +87 -0
- package/dist/session-scope-QGR72GYJ.js +87 -0
- package/dist/session-scope-YRJWFFQ5.js +87 -0
- package/dist/setup-wizard-2MGNOCDA.js +12 -0
- package/dist/setup-wizard-2ZHU6PBC.js +12 -0
- package/dist/setup-wizard-B6GIT7YC.js +12 -0
- package/dist/setup-wizard-JUIJ4UZO.js +12 -0
- package/dist/setup-wizard-LGMBT5MA.js +12 -0
- package/dist/setup-wizard-MC6OYLSN.js +12 -0
- package/dist/skill-refinement-645HA6R7.js +158 -0
- package/dist/skill-refinement-6S5GJPBC.js +158 -0
- package/dist/skill-refinement-7M3OO2KL.js +158 -0
- package/dist/skill-refinement-HBS65LE7.js +158 -0
- package/dist/skill-refinement-HIOX4VMC.js +158 -0
- package/dist/skill-refinement-T7JXRYUW.js +158 -0
- package/dist/stack-update-5KE6BZKQ.js +74 -0
- package/dist/stack-update-OP2RHP7N.js +74 -0
- package/dist/stack-update-VGCWDJEE.js +74 -0
- package/dist/steward-gate-JSGYBZRR.js +14 -0
- package/dist/steward-gate-L22WE3SY.js +14 -0
- package/dist/steward-gate-YKD2LUWN.js +14 -0
- package/dist/support-outbox-TIC66SRX.js +494 -0
- package/dist/support-outbox-YLXLMEKF.js +503 -0
- package/dist/task-enforcement-5AOKXTY4.js +439 -0
- package/dist/task-enforcement-7CV4AMJY.js +439 -0
- package/dist/task-enforcement-BQ4L3T2J.js +439 -0
- package/dist/task-enforcement-L5EZK5MJ.js +439 -0
- package/dist/task-enforcement-S6KEUCL6.js +439 -0
- package/dist/task-enforcement-VO3YEGIO.js +439 -0
- package/dist/task-scope-47VM3OGT.js +36 -0
- package/dist/task-scope-MONXB37O.js +36 -0
- package/dist/task-scope-RPYOVJOD.js +36 -0
- package/dist/task-scope-V6BS7L5C.js +36 -0
- package/dist/task-scope-YV2WPKRD.js +36 -0
- package/dist/task-scope-ZSXDZBRE.js +36 -0
- package/dist/tasks-crud-AMWLBEJT.js +78 -0
- package/dist/tasks-crud-C6KADACT.js +78 -0
- package/dist/tasks-crud-LD2WCC5Z.js +78 -0
- package/dist/tasks-crud-NV6JEWGL.js +78 -0
- package/dist/tasks-crud-SMVAMAK7.js +78 -0
- package/dist/tasks-crud-UVSMJ7SQ.js +78 -0
- package/dist/tasks-notify-5KWFXJBA.js +39 -0
- package/dist/tasks-notify-6Q2CJIR5.js +39 -0
- package/dist/tasks-notify-E22HSN6O.js +39 -0
- package/dist/tasks-notify-FG2NXZP6.js +39 -0
- package/dist/tasks-notify-NBHN537I.js +39 -0
- package/dist/tasks-notify-RPSEQ4WV.js +39 -0
- package/dist/tasks-review-333PSSFU.js +48 -0
- package/dist/tasks-review-B6VOLVVZ.js +48 -0
- package/dist/tasks-review-MFD4VTZL.js +48 -0
- package/dist/tasks-review-MYPYFT6C.js +48 -0
- package/dist/tasks-review-V4ZLXOAZ.js +48 -0
- package/dist/tasks-review-ZVRI73JE.js +48 -0
- package/dist/telemetry-upload-45DH7FR7.js +740 -0
- package/dist/telemetry-upload-LXUH7SKI.js +740 -0
- package/dist/telemetry-upload-OSTVGOIS.js +740 -0
- package/dist/telemetry-upload-PNLXKL43.js +740 -0
- package/dist/telemetry-upload-QOKD4NUY.js +740 -0
- package/dist/telemetry-upload-TCDAZTUQ.js +740 -0
- package/dist/token-budget-OFBEZJTA.js +85 -0
- package/dist/token-budget-RJ2VKVYI.js +85 -0
- package/dist/token-budget-WAN57V6S.js +85 -0
- package/dist/tool-capability-index-ANXYTG5M.js +10 -0
- package/dist/tool-telemetry-UA3N32PK.js +17 -0
- package/dist/tool-telemetry-W6I2BO5E.js +17 -0
- package/dist/tool-telemetry-XXZJ35RR.js +17 -0
- package/dist/tui/App.js +18 -18
- package/dist/tui-data-46QLCJUE.js +259 -0
- package/dist/tui-data-A6PDJW45.js +259 -0
- package/dist/tui-data-GOGDXQF5.js +259 -0
- package/dist/tui-data-VIY77WXK.js +259 -0
- package/dist/tui-data-YOZSLQMZ.js +259 -0
- package/dist/tui-data-ZDB7BLP2.js +259 -0
- package/dist/wiki-acl-HHSIBPF3.js +111 -0
- package/dist/wiki-acl-O65GZ2ZF.js +111 -0
- package/dist/wiki-acl-Z5XZ6X4K.js +111 -0
- package/dist/worker-gate-27I4GAEZ.js +21 -0
- package/dist/worker-gate-34CD6WYY.js +21 -0
- package/dist/worker-gate-7T2DKS3A.js +21 -0
- package/dist/worker-gate-DXU4HEPY.js +21 -0
- package/dist/worker-gate-LN2IHRFB.js +21 -0
- package/dist/worker-gate-MQJTGE5N.js +21 -0
- package/dist/workflow-engine-63EOEJ5Q.js +28 -0
- package/dist/workflow-engine-C6F2RMPN.js +28 -0
- package/dist/workflow-engine-KWAYDFUW.js +28 -0
- package/dist/workflow-engine-PXQ6N3C7.js +28 -0
- package/dist/workflow-engine-TECXAR65.js +28 -0
- package/dist/workflow-engine-UWEMT2OU.js +28 -0
- package/dist/worktree-5Q4VB2LR.js +27 -0
- package/dist/worktree-SFKKOMFD.js +27 -0
- package/dist/worktree-SVCE3S7X.js +27 -0
- package/dist/worktree-sweep-5KENYHYC.js +20 -0
- package/dist/worktree-sweep-S3JHJTVP.js +20 -0
- package/dist/worktree-sweep-U3TIQ7WL.js +20 -0
- package/package.json +1 -1
- package/release-notes.json +174 -49
|
@@ -0,0 +1,2078 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordSessionKill
|
|
3
|
+
} from "./chunk-6MDKSLYM.js";
|
|
4
|
+
import {
|
|
5
|
+
updateTask
|
|
6
|
+
} from "./chunk-J2PDHWJV.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureEmployeeAsync,
|
|
9
|
+
extractRootExe,
|
|
10
|
+
getSessionState,
|
|
11
|
+
isExeSession,
|
|
12
|
+
queryTaskRows,
|
|
13
|
+
sendIntercomAsync,
|
|
14
|
+
sessionScopeFilter,
|
|
15
|
+
strictSessionScopeFilter,
|
|
16
|
+
writeNotification
|
|
17
|
+
} from "./chunk-IPZ4JWBG.js";
|
|
18
|
+
import {
|
|
19
|
+
queueIntercom
|
|
20
|
+
} from "./chunk-4FVVJ7ME.js";
|
|
21
|
+
import {
|
|
22
|
+
listSessions
|
|
23
|
+
} from "./chunk-FGMF5K2G.js";
|
|
24
|
+
import {
|
|
25
|
+
getTransport
|
|
26
|
+
} from "./chunk-MVW62NIZ.js";
|
|
27
|
+
import {
|
|
28
|
+
listTmuxSessions,
|
|
29
|
+
parseContextPercentage
|
|
30
|
+
} from "./chunk-SSTLTIF3.js";
|
|
31
|
+
import {
|
|
32
|
+
recordOrchestrationEventBestEffort
|
|
33
|
+
} from "./chunk-X46O77S4.js";
|
|
34
|
+
import {
|
|
35
|
+
getAgentRuntime
|
|
36
|
+
} from "./chunk-PMVOUPTY.js";
|
|
37
|
+
import {
|
|
38
|
+
baseAgentName,
|
|
39
|
+
getCoordinatorName,
|
|
40
|
+
isCoordinatorName,
|
|
41
|
+
shouldAutoInstance
|
|
42
|
+
} from "./chunk-ZJP3ZONM.js";
|
|
43
|
+
import {
|
|
44
|
+
loadConfigSync
|
|
45
|
+
} from "./chunk-T3B5RK4H.js";
|
|
46
|
+
|
|
47
|
+
// src/lib/daemon-orchestration.ts
|
|
48
|
+
import { execFile, execFileSync, execSync } from "child_process";
|
|
49
|
+
import { promisify } from "util";
|
|
50
|
+
import { randomUUID } from "crypto";
|
|
51
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, unlinkSync, statSync as statSync2 } from "fs";
|
|
52
|
+
import { homedir, loadavg, freemem, cpus as osCpus, platform } from "os";
|
|
53
|
+
import { join } from "path";
|
|
54
|
+
|
|
55
|
+
// src/lib/agent-signals.ts
|
|
56
|
+
import { existsSync, openSync, closeSync, readSync, statSync } from "fs";
|
|
57
|
+
import os from "os";
|
|
58
|
+
import path from "path";
|
|
59
|
+
var CONSERVATIVE_ON_ERROR = true;
|
|
60
|
+
var INTERCOM_ACK_TAIL_BYTES = 64 * 1024;
|
|
61
|
+
function readTailUtf8(filePath, maxBytes) {
|
|
62
|
+
const stat = statSync(filePath);
|
|
63
|
+
const start = Math.max(0, stat.size - maxBytes);
|
|
64
|
+
const length = stat.size - start;
|
|
65
|
+
const fd = openSync(filePath, "r");
|
|
66
|
+
try {
|
|
67
|
+
const buf = Buffer.alloc(length);
|
|
68
|
+
readSync(fd, buf, 0, length, start);
|
|
69
|
+
return buf.toString("utf8");
|
|
70
|
+
} finally {
|
|
71
|
+
closeSync(fd);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function hasOpenTasks(client, agentId, sessionScope) {
|
|
75
|
+
try {
|
|
76
|
+
const scope = sessionScopeFilter(sessionScope);
|
|
77
|
+
const result = await client.execute({
|
|
78
|
+
sql: `SELECT 1 FROM tasks
|
|
79
|
+
WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'blocked')${scope.sql}
|
|
80
|
+
LIMIT 1`,
|
|
81
|
+
args: [agentId, ...scope.args]
|
|
82
|
+
});
|
|
83
|
+
return result.rows.length > 0;
|
|
84
|
+
} catch {
|
|
85
|
+
return CONSERVATIVE_ON_ERROR;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function hasNeedsReview(client, agentId, sessionScope) {
|
|
89
|
+
try {
|
|
90
|
+
const scope = sessionScopeFilter(sessionScope);
|
|
91
|
+
const result = await client.execute({
|
|
92
|
+
sql: `SELECT 1 FROM tasks
|
|
93
|
+
WHERE assigned_to = ? AND status = 'needs_review'${scope.sql}
|
|
94
|
+
LIMIT 1`,
|
|
95
|
+
args: [agentId, ...scope.args]
|
|
96
|
+
});
|
|
97
|
+
return result.rows.length > 0;
|
|
98
|
+
} catch {
|
|
99
|
+
return CONSERVATIVE_ON_ERROR;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function hasUnreadInbox(client, agentId, sessionScope) {
|
|
103
|
+
try {
|
|
104
|
+
const scope = strictSessionScopeFilter(sessionScope);
|
|
105
|
+
const result = await client.execute({
|
|
106
|
+
sql: `SELECT 1 FROM messages
|
|
107
|
+
WHERE target_agent = ? AND status = 'pending'
|
|
108
|
+
${scope.sql}
|
|
109
|
+
LIMIT 1`,
|
|
110
|
+
args: [agentId, ...scope.args]
|
|
111
|
+
});
|
|
112
|
+
return result.rows.length > 0;
|
|
113
|
+
} catch {
|
|
114
|
+
return CONSERVATIVE_ON_ERROR;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path.join(os.homedir(), ".exe-os", "intercom.log")) {
|
|
118
|
+
if (!existsSync(intercomLog)) return false;
|
|
119
|
+
try {
|
|
120
|
+
const raw = readTailUtf8(intercomLog, INTERCOM_ACK_TAIL_BYTES);
|
|
121
|
+
const lines = raw.split("\n");
|
|
122
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
123
|
+
const line = lines[i];
|
|
124
|
+
if (!line || !line.includes(sessionName)) continue;
|
|
125
|
+
const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z)/);
|
|
126
|
+
if (!tsMatch) continue;
|
|
127
|
+
const ts = Date.parse(tsMatch[1]);
|
|
128
|
+
if (!Number.isFinite(ts)) continue;
|
|
129
|
+
if (nowMs - ts <= windowMs) return true;
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
} catch {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function collectAgentSignals(client, agentId, sessionName, intercomAckWindowMs, nowMs = Date.now()) {
|
|
138
|
+
const coordinatorScope = extractRootExe(sessionName) ?? null;
|
|
139
|
+
const [open, review, inbox] = await Promise.all([
|
|
140
|
+
hasOpenTasks(client, agentId, coordinatorScope),
|
|
141
|
+
hasNeedsReview(client, agentId, coordinatorScope),
|
|
142
|
+
hasUnreadInbox(client, agentId, coordinatorScope)
|
|
143
|
+
]);
|
|
144
|
+
return {
|
|
145
|
+
hasOpenTasks: open,
|
|
146
|
+
hasNeedsReview: review,
|
|
147
|
+
hasUnreadInbox: inbox,
|
|
148
|
+
hadRecentIntercomAck: hadRecentIntercomAck(sessionName, intercomAckWindowMs, nowMs)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/lib/daemon-orchestration.ts
|
|
153
|
+
var cpuCount = () => osCpus().length;
|
|
154
|
+
function getAvailableMemoryGB() {
|
|
155
|
+
if (platform() === "darwin") {
|
|
156
|
+
try {
|
|
157
|
+
const vmStatOutput = execFileSync("vm_stat", [], {
|
|
158
|
+
encoding: "utf8",
|
|
159
|
+
timeout: 3e3,
|
|
160
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
161
|
+
});
|
|
162
|
+
const pageSizeMatch = vmStatOutput.match(/page size of (\d+)/);
|
|
163
|
+
const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : 16384;
|
|
164
|
+
const parsePages = (label) => {
|
|
165
|
+
const re = new RegExp(`${label}:\\s+([\\d]+)\\.`);
|
|
166
|
+
const m = vmStatOutput.match(re);
|
|
167
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
168
|
+
};
|
|
169
|
+
const freePages = parsePages("Pages free");
|
|
170
|
+
const inactivePages = parsePages("Pages inactive");
|
|
171
|
+
const purgeablePages = parsePages("Pages purgeable");
|
|
172
|
+
const availableBytes = (freePages + inactivePages + purgeablePages) * pageSize;
|
|
173
|
+
return availableBytes / 1024 ** 3;
|
|
174
|
+
} catch {
|
|
175
|
+
return freemem() / 1024 ** 3;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return freemem() / 1024 ** 3;
|
|
179
|
+
}
|
|
180
|
+
var execFileAsync = promisify(execFile);
|
|
181
|
+
var IDLE_NUDGE_DEDUP_MS = Number(process.env.EXE_NUDGE_INTERVAL_MS) || 6e4;
|
|
182
|
+
var SESSION_TTL_HOURS = Number(process.env.EXE_SESSION_TTL_HOURS) || 8;
|
|
183
|
+
var SESSION_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_CONTEXT_KILL_PCT) || 80;
|
|
184
|
+
var IDLE_KILL_INTERCOM_ACK_WINDOW_MS = Number(process.env.EXE_INTERCOM_ACK_WINDOW_MS) || 6e4;
|
|
185
|
+
function shouldNudgeEmployee(sessionState, hasOpenTasks2, lastNudgeMs, nowMs, dedupMs) {
|
|
186
|
+
if (sessionState !== "idle") return false;
|
|
187
|
+
if (!hasOpenTasks2) return false;
|
|
188
|
+
if (nowMs - lastNudgeMs < dedupMs) return false;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
function shouldKillSession(ageHours, contextPct) {
|
|
192
|
+
return classifyTtlKillReason(ageHours, contextPct) !== null;
|
|
193
|
+
}
|
|
194
|
+
function classifyTtlKillReason(ageHours, contextPct) {
|
|
195
|
+
if (ageHours > SESSION_TTL_HOURS) return "ttl_age";
|
|
196
|
+
if (contextPct !== null && contextPct > SESSION_CONTEXT_THRESHOLD_PCT) return "ttl_context";
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function shouldKillIdleSession(input) {
|
|
200
|
+
if (!input.enabled) return false;
|
|
201
|
+
if (isExeSession(input.sessionName)) return false;
|
|
202
|
+
if (input.state !== "idle" && input.state !== "no_claude") return false;
|
|
203
|
+
if (input.signals.hasOpenTasks) return false;
|
|
204
|
+
if (input.signals.hasNeedsReview) return false;
|
|
205
|
+
if (input.signals.hasUnreadInbox) return false;
|
|
206
|
+
if (input.signals.hadRecentIntercomAck) return false;
|
|
207
|
+
if (input.idleTicks < input.ticksRequired) return false;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
async function pollIdleEmployees(deps, lastNudge) {
|
|
211
|
+
const registered = deps.listRegisteredSessions().filter(
|
|
212
|
+
(s) => !isCoordinatorName(s.agentId)
|
|
213
|
+
);
|
|
214
|
+
if (registered.length === 0) return [];
|
|
215
|
+
let liveSessions;
|
|
216
|
+
try {
|
|
217
|
+
liveSessions = new Set(deps.listTmuxSessions());
|
|
218
|
+
} catch (e) {
|
|
219
|
+
process.stderr.write("[daemon-orch] list tmux sessions for idle nudge: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const nudged = [];
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
for (const entry of registered) {
|
|
225
|
+
if (!liveSessions.has(entry.windowName)) continue;
|
|
226
|
+
const state = deps.getSessionState(entry.windowName);
|
|
227
|
+
const lastMs = lastNudge.get(entry.windowName) ?? 0;
|
|
228
|
+
if (state !== "idle") continue;
|
|
229
|
+
if (now - lastMs < IDLE_NUDGE_DEDUP_MS) continue;
|
|
230
|
+
const employeeScope = entry.windowName.includes("-") ? entry.windowName.slice(entry.windowName.indexOf("-") + 1) : null;
|
|
231
|
+
const task = await deps.queryOpenTask(entry.agentId, employeeScope);
|
|
232
|
+
if (!task) continue;
|
|
233
|
+
if (shouldNudgeEmployee(state, true, lastMs, now, IDLE_NUDGE_DEDUP_MS)) {
|
|
234
|
+
await deps.sendIntercom(entry.windowName);
|
|
235
|
+
lastNudge.set(entry.windowName, now);
|
|
236
|
+
nudged.push(entry.windowName);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return nudged;
|
|
240
|
+
}
|
|
241
|
+
async function checkSessionTTL(deps) {
|
|
242
|
+
const registered = deps.listRegisteredSessions().filter(
|
|
243
|
+
(s) => !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
|
|
244
|
+
);
|
|
245
|
+
if (registered.length === 0) return [];
|
|
246
|
+
let liveSessions;
|
|
247
|
+
try {
|
|
248
|
+
liveSessions = new Set(deps.listTmuxSessions());
|
|
249
|
+
} catch (e) {
|
|
250
|
+
process.stderr.write("[daemon-orch] list tmux sessions for TTL check: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
const killed = [];
|
|
254
|
+
for (const entry of registered) {
|
|
255
|
+
if (!liveSessions.has(entry.windowName)) continue;
|
|
256
|
+
if (isCoordinatorName(entry.agentId) || isExeSession(entry.windowName)) continue;
|
|
257
|
+
const createdEpoch = deps.getSessionCreatedEpoch(entry.windowName);
|
|
258
|
+
if (createdEpoch === null) continue;
|
|
259
|
+
const ageHours = (Date.now() / 1e3 - createdEpoch) / 3600;
|
|
260
|
+
const contextPct = deps.parseContextPercentage(entry.windowName);
|
|
261
|
+
const killReason = classifyTtlKillReason(ageHours, contextPct);
|
|
262
|
+
if (killReason !== null) {
|
|
263
|
+
const isContextFull = contextPct !== null && contextPct > 85;
|
|
264
|
+
if (deps.hasActiveTasks && !isContextFull) {
|
|
265
|
+
try {
|
|
266
|
+
const active = await deps.hasActiveTasks(entry.agentId);
|
|
267
|
+
if (active) {
|
|
268
|
+
process.stderr.write(
|
|
269
|
+
`[exed] Session TTL: SKIPPING ${entry.windowName} (age=${ageHours.toFixed(1)}h, reason=${killReason}) \u2014 agent has active tasks
|
|
270
|
+
`
|
|
271
|
+
);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
process.stderr.write(
|
|
276
|
+
`[exed] Session TTL: SKIPPING ${entry.windowName} \u2014 hasActiveTasks query failed (conservative)
|
|
277
|
+
`
|
|
278
|
+
);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (isContextFull) {
|
|
283
|
+
process.stderr.write(
|
|
284
|
+
`[exed] Session TTL: context-full override for ${entry.windowName} (${contextPct}% used) \u2014 killing despite active tasks
|
|
285
|
+
`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
process.stderr.write(
|
|
289
|
+
`[exed] Session TTL: killing ${entry.windowName} (age=${ageHours.toFixed(1)}h, context=${contextPct}%, reason=${killReason})
|
|
290
|
+
`
|
|
291
|
+
);
|
|
292
|
+
deps.recordKill?.({
|
|
293
|
+
sessionName: entry.windowName,
|
|
294
|
+
agentId: entry.agentId,
|
|
295
|
+
reason: killReason
|
|
296
|
+
});
|
|
297
|
+
recordOrchestrationEventBestEffort({
|
|
298
|
+
eventType: killReason === "ttl_age" ? "session.ttl_kill" : "session.context_kill",
|
|
299
|
+
source: "daemon-orchestration.checkSessionTTL",
|
|
300
|
+
agentId: entry.agentId,
|
|
301
|
+
tmuxSession: entry.windowName,
|
|
302
|
+
sessionScope: entry.windowName.includes("-") ? entry.windowName.slice(entry.windowName.indexOf("-") + 1) : null,
|
|
303
|
+
payload: { ageHours: parseFloat(ageHours.toFixed(2)), contextPct, killReason }
|
|
304
|
+
});
|
|
305
|
+
deps.killSession(entry.windowName);
|
|
306
|
+
killed.push(entry.windowName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return killed;
|
|
310
|
+
}
|
|
311
|
+
async function pollIdleKill(deps, idleTickCounts, opts) {
|
|
312
|
+
if (!opts.enabled) return [];
|
|
313
|
+
const registered = deps.listRegisteredSessions().filter(
|
|
314
|
+
(s) => !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
|
|
315
|
+
);
|
|
316
|
+
if (registered.length === 0) return [];
|
|
317
|
+
let liveSessions;
|
|
318
|
+
try {
|
|
319
|
+
liveSessions = new Set(deps.listTmuxSessions());
|
|
320
|
+
} catch (e) {
|
|
321
|
+
process.stderr.write("[daemon-orch] list tmux sessions for idle kill: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
const killed = [];
|
|
325
|
+
for (const entry of registered) {
|
|
326
|
+
if (!liveSessions.has(entry.windowName)) {
|
|
327
|
+
idleTickCounts.delete(entry.windowName);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const state = deps.getSessionState(entry.windowName);
|
|
331
|
+
if (state !== "idle" && state !== "no_claude") {
|
|
332
|
+
idleTickCounts.delete(entry.windowName);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const signals = await deps.collectSignals(entry.agentId, entry.windowName);
|
|
336
|
+
if (signals.hasOpenTasks) {
|
|
337
|
+
const instanceName = entry.windowName.split("-")[0] ?? entry.agentId;
|
|
338
|
+
if (instanceName !== entry.agentId) {
|
|
339
|
+
try {
|
|
340
|
+
const markerPath = join(
|
|
341
|
+
homedir(),
|
|
342
|
+
".exe-os",
|
|
343
|
+
"session-cache",
|
|
344
|
+
`current-task-${instanceName}.json`
|
|
345
|
+
);
|
|
346
|
+
if (!existsSync2(markerPath)) {
|
|
347
|
+
signals.hasOpenTasks = false;
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (entry.registeredAt) {
|
|
354
|
+
const ageMs = Date.now() - new Date(entry.registeredAt).getTime();
|
|
355
|
+
if (ageMs < 5 * 60 * 1e3) {
|
|
356
|
+
idleTickCounts.delete(entry.windowName);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (signals.hasOpenTasks || signals.hasNeedsReview || signals.hasUnreadInbox || signals.hadRecentIntercomAck) {
|
|
361
|
+
idleTickCounts.delete(entry.windowName);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const prevTicks = idleTickCounts.get(entry.windowName) ?? 0;
|
|
365
|
+
const nextTicks = prevTicks + 1;
|
|
366
|
+
const shouldKill = shouldKillIdleSession({
|
|
367
|
+
sessionName: entry.windowName,
|
|
368
|
+
state,
|
|
369
|
+
signals,
|
|
370
|
+
idleTicks: nextTicks,
|
|
371
|
+
ticksRequired: opts.ticksRequired,
|
|
372
|
+
enabled: opts.enabled
|
|
373
|
+
});
|
|
374
|
+
const warningThreshold = Math.max(1, Math.floor(opts.ticksRequired * 0.7));
|
|
375
|
+
if (nextTicks === warningThreshold && !shouldKill) {
|
|
376
|
+
try {
|
|
377
|
+
const { execFileSync: warnExec } = await import("child_process");
|
|
378
|
+
const shutdownMsg = [
|
|
379
|
+
"SHUTDOWN WARNING: Your session will be terminated in ~90 seconds due to inactivity.",
|
|
380
|
+
"All your tasks are complete. Before shutdown:",
|
|
381
|
+
"1. store_memory() any important decisions, discoveries, or context from this session",
|
|
382
|
+
"2. If you learned anything non-obvious, commit it to memory for future sessions",
|
|
383
|
+
"3. If you have in-progress thoughts or plans, checkpoint them now",
|
|
384
|
+
"This is your last chance to persist state. After shutdown, this context is lost."
|
|
385
|
+
].join(" ");
|
|
386
|
+
warnExec("tmux", ["send-keys", "-t", entry.windowName, "-l", shutdownMsg], { timeout: 2e3 });
|
|
387
|
+
warnExec("tmux", ["send-keys", "-t", entry.windowName, "Enter"], { timeout: 1e3 });
|
|
388
|
+
process.stderr.write(`[exed] Idle wind-down warning sent to ${entry.windowName} (tick ${nextTicks}/${opts.ticksRequired})
|
|
389
|
+
`);
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (shouldKill) {
|
|
394
|
+
process.stderr.write(
|
|
395
|
+
`[exed] Idle kill: ${entry.windowName} (agentId=${entry.agentId}, ticks=${nextTicks})
|
|
396
|
+
`
|
|
397
|
+
);
|
|
398
|
+
deps.recordKill?.({
|
|
399
|
+
sessionName: entry.windowName,
|
|
400
|
+
agentId: entry.agentId,
|
|
401
|
+
reason: "idle",
|
|
402
|
+
ticksIdle: nextTicks
|
|
403
|
+
});
|
|
404
|
+
recordOrchestrationEventBestEffort({
|
|
405
|
+
eventType: "session.idle_kill",
|
|
406
|
+
source: "daemon-orchestration.pollIdleKill",
|
|
407
|
+
agentId: entry.agentId,
|
|
408
|
+
tmuxSession: entry.windowName,
|
|
409
|
+
sessionScope: entry.windowName.includes("-") ? entry.windowName.slice(entry.windowName.indexOf("-") + 1) : null,
|
|
410
|
+
payload: { ticksIdle: nextTicks }
|
|
411
|
+
});
|
|
412
|
+
deps.killSession(entry.windowName);
|
|
413
|
+
try {
|
|
414
|
+
const { execFileSync: verifyExec } = await import("child_process");
|
|
415
|
+
verifyExec("tmux", ["has-session", "-t", entry.windowName], { timeout: 2e3 });
|
|
416
|
+
process.stderr.write(
|
|
417
|
+
`[exed] Idle kill FAILED for ${entry.windowName} \u2014 session still alive, retrying with force
|
|
418
|
+
`
|
|
419
|
+
);
|
|
420
|
+
verifyExec("tmux", ["kill-session", "-t", entry.windowName], { timeout: 3e3 });
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
killed.push(entry.windowName);
|
|
424
|
+
idleTickCounts.delete(entry.windowName);
|
|
425
|
+
} else {
|
|
426
|
+
idleTickCounts.set(entry.windowName, nextTicks);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return killed;
|
|
430
|
+
}
|
|
431
|
+
var REVIEW_NUDGE_COOLDOWN_MS = 3e5;
|
|
432
|
+
async function pollReviewNudge(deps, state) {
|
|
433
|
+
let sessions;
|
|
434
|
+
try {
|
|
435
|
+
sessions = deps.listTmuxSessions().filter((s) => isExeSession(s));
|
|
436
|
+
} catch (e) {
|
|
437
|
+
process.stderr.write("[daemon-orch] list tmux sessions for review nudge: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
if (sessions.length === 0) return [];
|
|
441
|
+
const nudged = [];
|
|
442
|
+
const now = Date.now();
|
|
443
|
+
for (const exeSession of sessions) {
|
|
444
|
+
const prev = state.lastNudge.get(exeSession);
|
|
445
|
+
if (prev && now - prev.at < REVIEW_NUDGE_COOLDOWN_MS) continue;
|
|
446
|
+
const sessionState = deps.getSessionState(exeSession);
|
|
447
|
+
if (sessionState !== "idle") continue;
|
|
448
|
+
let count;
|
|
449
|
+
try {
|
|
450
|
+
count = await deps.countReviewsForScope(exeSession);
|
|
451
|
+
} catch (e) {
|
|
452
|
+
process.stderr.write("[daemon-orch] count reviews for scope: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (count === 0) continue;
|
|
456
|
+
if (prev && prev.count === count) continue;
|
|
457
|
+
try {
|
|
458
|
+
deps.sendNudge(exeSession);
|
|
459
|
+
state.lastNudge.set(exeSession, { at: now, count });
|
|
460
|
+
nudged.push(exeSession);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
process.stderr.write("[daemon-orch] send review nudge: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (nudged.length > 0 && deps.persistState) {
|
|
466
|
+
try {
|
|
467
|
+
deps.persistState(state);
|
|
468
|
+
} catch {
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return nudged;
|
|
472
|
+
}
|
|
473
|
+
var NUDGE_STATE_PATH = join(homedir(), ".exe-os", "review-nudge-state.json");
|
|
474
|
+
function loadNudgeState() {
|
|
475
|
+
const state = { lastNudge: /* @__PURE__ */ new Map() };
|
|
476
|
+
try {
|
|
477
|
+
if (!existsSync2(NUDGE_STATE_PATH)) return state;
|
|
478
|
+
const raw = JSON.parse(readFileSync(NUDGE_STATE_PATH, "utf8"));
|
|
479
|
+
if (Array.isArray(raw)) {
|
|
480
|
+
for (const [key, val] of raw) {
|
|
481
|
+
if (key && typeof val?.at === "number" && typeof val?.count === "number") {
|
|
482
|
+
state.lastNudge.set(key, val);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
return state;
|
|
489
|
+
}
|
|
490
|
+
function saveNudgeState(state) {
|
|
491
|
+
const entries = Array.from(state.lastNudge.entries());
|
|
492
|
+
writeFileSync(NUDGE_STATE_PATH, JSON.stringify(entries), "utf8");
|
|
493
|
+
}
|
|
494
|
+
function createReviewNudgeRealDeps(_getClient) {
|
|
495
|
+
return {
|
|
496
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
497
|
+
getSessionState: (sessionName) => getSessionState(sessionName),
|
|
498
|
+
countReviewsForScope: async (sessionScope) => {
|
|
499
|
+
const rows = await queryTaskRows({
|
|
500
|
+
status: "needs_review",
|
|
501
|
+
columns: "COUNT(*) as cnt",
|
|
502
|
+
sessionScope,
|
|
503
|
+
projectName: null,
|
|
504
|
+
limit: 1,
|
|
505
|
+
orderBy: "1"
|
|
506
|
+
});
|
|
507
|
+
return Number(rows[0]?.cnt ?? 0);
|
|
508
|
+
},
|
|
509
|
+
sendNudge: (sessionName) => {
|
|
510
|
+
queueIntercom(sessionName, "review nudge: pending reviews", "completion");
|
|
511
|
+
},
|
|
512
|
+
persistState: saveNudgeState
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function createIdleNudgeRealDeps(_getClient) {
|
|
516
|
+
return {
|
|
517
|
+
listRegisteredSessions: () => listSessions(),
|
|
518
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
519
|
+
getSessionState: (sessionName) => getSessionState(sessionName),
|
|
520
|
+
queryOpenTask: async (agentId, sessionScope) => {
|
|
521
|
+
const rows = await queryTaskRows({
|
|
522
|
+
assignedTo: agentId,
|
|
523
|
+
status: ["open", "in_progress"],
|
|
524
|
+
columns: "id, title, priority",
|
|
525
|
+
sessionScope: sessionScope ?? null,
|
|
526
|
+
projectName: null,
|
|
527
|
+
orderBy: "CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 ELSE 2 END",
|
|
528
|
+
limit: 1
|
|
529
|
+
});
|
|
530
|
+
if (rows.length === 0) return null;
|
|
531
|
+
return rows[0];
|
|
532
|
+
},
|
|
533
|
+
sendIntercom: (sessionName) => sendIntercomAsync(sessionName),
|
|
534
|
+
killSession: (sessionName) => {
|
|
535
|
+
getTransport().kill(sessionName);
|
|
536
|
+
},
|
|
537
|
+
getAgentRuntime: (agentId) => {
|
|
538
|
+
try {
|
|
539
|
+
return getAgentRuntime(agentId).runtime;
|
|
540
|
+
} catch {
|
|
541
|
+
return "claude";
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function createSessionTTLRealDeps(getClient) {
|
|
547
|
+
return {
|
|
548
|
+
listRegisteredSessions: () => listSessions(),
|
|
549
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
550
|
+
getSessionCreatedEpoch: (sessionName) => {
|
|
551
|
+
try {
|
|
552
|
+
const sessions = listSessions();
|
|
553
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
554
|
+
if (entry?.registeredAt) {
|
|
555
|
+
const epoch = new Date(entry.registeredAt).getTime() / 1e3;
|
|
556
|
+
if (!isNaN(epoch)) return epoch;
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const out = execFileSync(
|
|
562
|
+
"tmux",
|
|
563
|
+
["display-message", "-t", sessionName, "-p", "#{session_created}"],
|
|
564
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e3 }
|
|
565
|
+
).trim();
|
|
566
|
+
const epoch = parseInt(out, 10);
|
|
567
|
+
return isNaN(epoch) ? null : epoch;
|
|
568
|
+
} catch {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
parseContextPercentage: (sessionName) => parseContextPercentage(sessionName),
|
|
573
|
+
killSession: (sessionName) => {
|
|
574
|
+
getTransport().kill(sessionName);
|
|
575
|
+
},
|
|
576
|
+
hasActiveTasks: getClient ? async (agentId) => {
|
|
577
|
+
const rows = await queryTaskRows({
|
|
578
|
+
assignedTo: agentId,
|
|
579
|
+
status: ["open", "in_progress"],
|
|
580
|
+
columns: "1",
|
|
581
|
+
sessionScope: null,
|
|
582
|
+
// null = no session filter (check all)
|
|
583
|
+
projectName: null,
|
|
584
|
+
limit: 1
|
|
585
|
+
});
|
|
586
|
+
return rows.length > 0;
|
|
587
|
+
} : void 0,
|
|
588
|
+
recordKill: (input) => {
|
|
589
|
+
void recordSessionKill(input);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function createIdleKillRealDeps(getClient, intercomAckWindowMs) {
|
|
594
|
+
return {
|
|
595
|
+
listRegisteredSessions: () => listSessions(),
|
|
596
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
597
|
+
getSessionState: (sessionName) => getSessionState(sessionName),
|
|
598
|
+
collectSignals: async (agentId, sessionName) => {
|
|
599
|
+
return collectAgentSignals(getClient(), agentId, sessionName, intercomAckWindowMs);
|
|
600
|
+
},
|
|
601
|
+
killSession: (sessionName) => {
|
|
602
|
+
getTransport().kill(sessionName);
|
|
603
|
+
},
|
|
604
|
+
recordKill: (input) => {
|
|
605
|
+
void recordSessionKill(input);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
var AUTO_WAKE_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
610
|
+
var AUTO_WAKE_MAX_RETRIES = 3;
|
|
611
|
+
var AUTO_WAKE_STATE_PATH = join(homedir(), ".exe-os", "session-cache", "auto-wake-state.json");
|
|
612
|
+
var CRASH_LOOP_WINDOW_MS = 15 * 60 * 1e3;
|
|
613
|
+
var CRASH_LOOP_THRESHOLD = 3;
|
|
614
|
+
var BACKOFF_BASE_MS = 2 * 60 * 1e3;
|
|
615
|
+
var BACKOFF_CAP_MS = 30 * 60 * 1e3;
|
|
616
|
+
var CPU_PRESSURE_THRESHOLD = 80;
|
|
617
|
+
var FREE_RAM_MIN_GB = 4;
|
|
618
|
+
function loadAutoWakeState() {
|
|
619
|
+
try {
|
|
620
|
+
if (existsSync2(AUTO_WAKE_STATE_PATH)) {
|
|
621
|
+
const raw = JSON.parse(readFileSync(AUTO_WAKE_STATE_PATH, "utf8"));
|
|
622
|
+
return {
|
|
623
|
+
lastSpawn: raw.lastSpawn ?? {},
|
|
624
|
+
taskRetries: raw.taskRetries ?? {},
|
|
625
|
+
recentSpawns: raw.recentSpawns ?? {},
|
|
626
|
+
crashLoopCount: raw.crashLoopCount ?? {},
|
|
627
|
+
lastSaved: raw.lastSaved ?? 0
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
return { lastSpawn: {}, taskRetries: {}, recentSpawns: {}, crashLoopCount: {}, lastSaved: 0 };
|
|
633
|
+
}
|
|
634
|
+
function saveAutoWakeState(state) {
|
|
635
|
+
try {
|
|
636
|
+
state.lastSaved = Date.now();
|
|
637
|
+
writeFileSync(AUTO_WAKE_STATE_PATH, JSON.stringify(state), "utf8");
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
var _autoWakeState = loadAutoWakeState();
|
|
642
|
+
var _autoWakeLastSpawn = {
|
|
643
|
+
get: (key) => _autoWakeState.lastSpawn[key],
|
|
644
|
+
set: (key, val) => {
|
|
645
|
+
_autoWakeState.lastSpawn[key] = val;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var _autoWakeTaskRetries = {
|
|
649
|
+
get: (key) => _autoWakeState.taskRetries[key],
|
|
650
|
+
set: (key, val) => {
|
|
651
|
+
_autoWakeState.taskRetries[key] = val;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
var _autoWakeRecentSpawns = {
|
|
655
|
+
get: (key) => _autoWakeState.recentSpawns[key],
|
|
656
|
+
set: (key, val) => {
|
|
657
|
+
_autoWakeState.recentSpawns[key] = val;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
function _resetAutoWakeState() {
|
|
661
|
+
_autoWakeState = { lastSpawn: {}, taskRetries: {}, recentSpawns: {}, crashLoopCount: {}, lastSaved: 0 };
|
|
662
|
+
try {
|
|
663
|
+
unlinkSync(AUTO_WAKE_STATE_PATH);
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function checkAutoWakeGates(key, nowMs) {
|
|
668
|
+
try {
|
|
669
|
+
const lagMarker = join(homedir(), ".exe-os", "session-cache", "event-loop-blocked.marker");
|
|
670
|
+
if (existsSync2(lagMarker)) {
|
|
671
|
+
const age = nowMs - statSync2(lagMarker).mtimeMs;
|
|
672
|
+
if (age < 6e4) return "daemon_event_loop_blocked";
|
|
673
|
+
}
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
const loopCount = _autoWakeState.crashLoopCount[key] ?? 0;
|
|
677
|
+
if (loopCount > 0) {
|
|
678
|
+
const backoffMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, loopCount - 1), BACKOFF_CAP_MS);
|
|
679
|
+
const lastSpawn = _autoWakeState.lastSpawn[key] ?? 0;
|
|
680
|
+
if (nowMs - lastSpawn < backoffMs) {
|
|
681
|
+
return `crash_loop_backoff_${Math.round(backoffMs / 6e4)}min`;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
const loadAvg = loadavg()[0];
|
|
686
|
+
const cpus = cpuCount();
|
|
687
|
+
const loadPct = loadAvg / cpus * 100;
|
|
688
|
+
if (loadPct > CPU_PRESSURE_THRESHOLD) return `cpu_pressure_${Math.round(loadPct)}pct`;
|
|
689
|
+
const freeGB = getAvailableMemoryGB();
|
|
690
|
+
if (freeGB < FREE_RAM_MIN_GB) return `low_ram_${freeGB.toFixed(1)}gb`;
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
function shouldAutoWake(input) {
|
|
696
|
+
if (isCoordinatorName(input.agentId)) return false;
|
|
697
|
+
if (input.nowMs - input.lastSpawnMs < input.cooldownMs) return false;
|
|
698
|
+
const running = input.runningInstances ?? (input.hasRunningSession ? 1 : 0);
|
|
699
|
+
const pending = input.pendingTaskGroups ?? 1;
|
|
700
|
+
if (running >= pending) return false;
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
async function pollOrphanedTasks(deps, nowMs = Date.now()) {
|
|
704
|
+
const checkGate = deps.checkGate ?? checkAutoWakeGates;
|
|
705
|
+
const globalGate = checkGate("__global__", nowMs);
|
|
706
|
+
if (globalGate) {
|
|
707
|
+
process.stderr.write(`[auto-wake] GLOBAL GATE BLOCKED: ${globalGate} \u2014 skipping all spawns
|
|
708
|
+
`);
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
let liveSessions;
|
|
712
|
+
try {
|
|
713
|
+
liveSessions = deps.listTmuxSessions();
|
|
714
|
+
} catch (e) {
|
|
715
|
+
process.stderr.write("[daemon-orch] list tmux sessions for orphan poll: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
716
|
+
return [];
|
|
717
|
+
}
|
|
718
|
+
const liveAgentScopedCounts = /* @__PURE__ */ new Map();
|
|
719
|
+
for (const session of liveSessions) {
|
|
720
|
+
const agent = deps.parseAgentFromSession(session);
|
|
721
|
+
if (agent) {
|
|
722
|
+
const dashIdx = session.indexOf("-");
|
|
723
|
+
const scope = dashIdx >= 0 ? session.slice(dashIdx + 1) : session;
|
|
724
|
+
const scopedKey = `${agent}::${scope}`;
|
|
725
|
+
liveAgentScopedCounts.set(scopedKey, (liveAgentScopedCounts.get(scopedKey) ?? 0) + 1);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const coordinatorSessions = liveSessions.filter((s) => isExeSession(s));
|
|
729
|
+
let tasksByAgent;
|
|
730
|
+
try {
|
|
731
|
+
tasksByAgent = await deps.queryTasksByAgent();
|
|
732
|
+
} catch (e) {
|
|
733
|
+
process.stderr.write("[daemon-orch] query tasks by agent: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
const coordinatorSet = new Set(coordinatorSessions);
|
|
737
|
+
const VALID_SESSION_SCOPE = /^[a-zA-Z0-9_]+$/;
|
|
738
|
+
const agentTasks = /* @__PURE__ */ new Map();
|
|
739
|
+
for (const t of tasksByAgent) {
|
|
740
|
+
const rawScope = t.sessionScope;
|
|
741
|
+
if (rawScope && !VALID_SESSION_SCOPE.test(rawScope)) {
|
|
742
|
+
process.stderr.write(
|
|
743
|
+
`[auto-wake] IGNORING corrupted session_scope for ${t.agentId} task ${t.taskId}: "${rawScope.slice(0, 60)}"
|
|
744
|
+
`
|
|
745
|
+
);
|
|
746
|
+
t.sessionScope = null;
|
|
747
|
+
}
|
|
748
|
+
let scope = t.sessionScope;
|
|
749
|
+
if (!scope) {
|
|
750
|
+
if (coordinatorSessions.length !== 1) {
|
|
751
|
+
process.stderr.write(
|
|
752
|
+
`[auto-wake] Skipping ${t.agentId} task ${t.taskId} \u2014 missing session_scope and ${coordinatorSessions.length} live coordinators; refusing to guess
|
|
753
|
+
`
|
|
754
|
+
);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
scope = coordinatorSessions[0];
|
|
758
|
+
}
|
|
759
|
+
if (!scope) continue;
|
|
760
|
+
if (t.sessionScope && !coordinatorSet.has(t.sessionScope)) {
|
|
761
|
+
process.stderr.write(
|
|
762
|
+
`[auto-wake] Skipping ${t.agentId} task ${t.taskId} \u2014 coordinator session "${t.sessionScope}" is dead
|
|
763
|
+
`
|
|
764
|
+
);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const key = `${t.agentId}::${scope}`;
|
|
768
|
+
const existing = agentTasks.get(key) ?? [];
|
|
769
|
+
existing.push({ taskId: t.taskId, priority: t.priority, sessionScope: scope });
|
|
770
|
+
agentTasks.set(key, existing);
|
|
771
|
+
}
|
|
772
|
+
const woken = [];
|
|
773
|
+
for (const [key, tasks] of agentTasks) {
|
|
774
|
+
const agentId = key.split("::")[0];
|
|
775
|
+
const sessionScope = tasks[0].sessionScope;
|
|
776
|
+
if (!shouldAutoWake({
|
|
777
|
+
agentId,
|
|
778
|
+
hasRunningSession: liveAgentScopedCounts.has(key),
|
|
779
|
+
lastSpawnMs: _autoWakeLastSpawn.get(key) ?? 0,
|
|
780
|
+
nowMs,
|
|
781
|
+
cooldownMs: AUTO_WAKE_COOLDOWN_MS,
|
|
782
|
+
runningInstances: liveAgentScopedCounts.get(key) ?? 0,
|
|
783
|
+
pendingTaskGroups: tasks.length
|
|
784
|
+
})) {
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
const recentSpawns = _autoWakeRecentSpawns.get(key) ?? [];
|
|
788
|
+
const recentCount = recentSpawns.filter((ts) => nowMs - ts < CRASH_LOOP_WINDOW_MS).length;
|
|
789
|
+
if (recentCount >= CRASH_LOOP_THRESHOLD) {
|
|
790
|
+
const loopCount = (_autoWakeState.crashLoopCount[key] ?? 0) + 1;
|
|
791
|
+
_autoWakeState.crashLoopCount[key] = loopCount;
|
|
792
|
+
const backoffMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, loopCount - 1), BACKOFF_CAP_MS);
|
|
793
|
+
process.stderr.write(
|
|
794
|
+
`[auto-wake] CRASH LOOP #${loopCount}: ${agentId} spawned ${recentCount}x in ${CRASH_LOOP_WINDOW_MS / 6e4}min \u2014 backoff ${Math.round(backoffMs / 6e4)}min
|
|
795
|
+
`
|
|
796
|
+
);
|
|
797
|
+
saveAutoWakeState(_autoWakeState);
|
|
798
|
+
try {
|
|
799
|
+
await writeNotification({
|
|
800
|
+
agentId,
|
|
801
|
+
agentRole: "employee",
|
|
802
|
+
event: "error_spike",
|
|
803
|
+
project: String(tasks[0]?.sessionScope ?? ""),
|
|
804
|
+
summary: `Crash loop #${loopCount}: ${agentId} died ${recentCount}x. Backoff ${Math.round(backoffMs / 6e4)}min. Check session-deaths.jsonl.`,
|
|
805
|
+
sessionScope
|
|
806
|
+
});
|
|
807
|
+
} catch {
|
|
808
|
+
}
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const agentGate = checkGate(key, nowMs);
|
|
812
|
+
if (agentGate) {
|
|
813
|
+
process.stderr.write(`[auto-wake] AGENT GATE BLOCKED: ${agentId} \u2014 ${agentGate}
|
|
814
|
+
`);
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const topTask = tasks[0];
|
|
818
|
+
const retries = _autoWakeTaskRetries.get(topTask.taskId) ?? 0;
|
|
819
|
+
if (retries >= AUTO_WAKE_MAX_RETRIES) {
|
|
820
|
+
try {
|
|
821
|
+
await deps.markTaskNeedsReview(
|
|
822
|
+
topTask.taskId,
|
|
823
|
+
`Auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times \u2014 auto-closed: session ended without explicit completion. Needs manual review.`
|
|
824
|
+
);
|
|
825
|
+
process.stderr.write(
|
|
826
|
+
`[auto-wake] ${agentId} task ${topTask.taskId} exceeded ${AUTO_WAKE_MAX_RETRIES} retries \u2014 marked needs_review
|
|
827
|
+
`
|
|
828
|
+
);
|
|
829
|
+
} catch (e) {
|
|
830
|
+
process.stderr.write("[daemon-orch] mark task needs_review after retries: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
await writeNotification({
|
|
834
|
+
agentId,
|
|
835
|
+
agentRole: "employee",
|
|
836
|
+
event: "orphan_task",
|
|
837
|
+
project: String(topTask.sessionScope ?? ""),
|
|
838
|
+
summary: `\u26A0\uFE0F Task ${topTask.taskId} for ${agentId} needs review \u2014 auto-wake failed ${AUTO_WAKE_MAX_RETRIES} times. Session could not be recovered.`,
|
|
839
|
+
sessionScope
|
|
840
|
+
});
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
process.stderr.write(
|
|
846
|
+
`[auto-wake] ${agentId} has ${tasks.length} pending task(s) but no session \u2014 spawning in ${sessionScope} (attempt ${retries + 1} for task ${topTask.taskId})
|
|
847
|
+
`
|
|
848
|
+
);
|
|
849
|
+
try {
|
|
850
|
+
const result = await deps.ensureEmployee(agentId, sessionScope);
|
|
851
|
+
if (result.status === "failed") {
|
|
852
|
+
process.stderr.write(
|
|
853
|
+
`[auto-wake] Failed to spawn ${agentId}: ${result.error ?? "unknown"}
|
|
854
|
+
`
|
|
855
|
+
);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
_autoWakeLastSpawn.set(key, nowMs);
|
|
859
|
+
_autoWakeTaskRetries.set(topTask.taskId, retries + 1);
|
|
860
|
+
const spawns = _autoWakeRecentSpawns.get(key) ?? [];
|
|
861
|
+
spawns.push(nowMs);
|
|
862
|
+
while (spawns.length > 0 && nowMs - spawns[0] > CRASH_LOOP_WINDOW_MS) spawns.shift();
|
|
863
|
+
_autoWakeRecentSpawns.set(key, spawns);
|
|
864
|
+
saveAutoWakeState(_autoWakeState);
|
|
865
|
+
woken.push(agentId);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
process.stderr.write(
|
|
868
|
+
`[auto-wake] Error spawning ${agentId}: ${err instanceof Error ? err.message : String(err)}
|
|
869
|
+
`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return woken;
|
|
874
|
+
}
|
|
875
|
+
var STUCK_TASK_GRACE_MS = 5 * 60 * 1e3;
|
|
876
|
+
async function releaseStuckTasks(deps, nowMs = Date.now()) {
|
|
877
|
+
let liveSessions;
|
|
878
|
+
try {
|
|
879
|
+
liveSessions = deps.listTmuxSessions();
|
|
880
|
+
} catch (e) {
|
|
881
|
+
process.stderr.write("[daemon-orch] list tmux sessions for stuck tasks: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
const liveAgents = /* @__PURE__ */ new Set();
|
|
885
|
+
for (const session of liveSessions) {
|
|
886
|
+
const agent = deps.parseAgentFromSession(session);
|
|
887
|
+
if (agent) liveAgents.add(agent);
|
|
888
|
+
}
|
|
889
|
+
for (const session of liveSessions) {
|
|
890
|
+
if (isExeSession(session)) {
|
|
891
|
+
liveAgents.add(session);
|
|
892
|
+
liveAgents.add(getCoordinatorName());
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
let tasks;
|
|
896
|
+
try {
|
|
897
|
+
tasks = await deps.queryInProgressTasks();
|
|
898
|
+
} catch (e) {
|
|
899
|
+
process.stderr.write("[daemon-orch] query in-progress tasks: " + (e instanceof Error ? e.message : String(e)) + "\n");
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
const released = [];
|
|
903
|
+
const STALE_TASK_ESCALATION_MS = 60 * 60 * 1e3;
|
|
904
|
+
for (const t of tasks) {
|
|
905
|
+
const updatedMs = new Date(t.updatedAt).getTime();
|
|
906
|
+
if (isNaN(updatedMs)) continue;
|
|
907
|
+
const ageMinutes = Math.round((nowMs - updatedMs) / 6e4);
|
|
908
|
+
if (!liveAgents.has(t.agentId)) {
|
|
909
|
+
const leaseMs = t.leaseExpiresAt ? new Date(t.leaseExpiresAt).getTime() : NaN;
|
|
910
|
+
const leaseExpired = !isNaN(leaseMs) && leaseMs < nowMs;
|
|
911
|
+
if (!leaseExpired && nowMs - updatedMs < STUCK_TASK_GRACE_MS) continue;
|
|
912
|
+
const reason = leaseExpired ? `Auto-closed: agent "${t.agentId}" task lease expired (${t.leaseExpiresAt}) and no live local session exists. Needs manual review.` : `Auto-closed: agent "${t.agentId}" session ended without explicit completion (in_progress for ${ageMinutes}m, no live session). Needs manual review.`;
|
|
913
|
+
try {
|
|
914
|
+
await deps.markTaskNeedsReview(t.taskId, reason);
|
|
915
|
+
released.push(t.taskId);
|
|
916
|
+
process.stderr.write(
|
|
917
|
+
`[stuck-release] Task ${t.taskId} (${t.agentId}) \u2014 in_progress for ${ageMinutes}m, agent dead \u2192 needs_review
|
|
918
|
+
`
|
|
919
|
+
);
|
|
920
|
+
} catch (err) {
|
|
921
|
+
process.stderr.write(`[stuck-release] Failed to mark task ${t.taskId} needs_review: ${err instanceof Error ? err.message : String(err)}
|
|
922
|
+
`);
|
|
923
|
+
}
|
|
924
|
+
if (deps.notifyOrphan) {
|
|
925
|
+
try {
|
|
926
|
+
await deps.notifyOrphan(t.taskId, t.agentId, t.sessionScope);
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
if (nowMs - updatedMs > STALE_TASK_ESCALATION_MS) {
|
|
933
|
+
process.stderr.write(
|
|
934
|
+
`[stuck-release] STALE WARNING: Task ${t.taskId} (${t.agentId}) \u2014 in_progress for ${ageMinutes}m, session alive but no update. Escalating to coordinator.
|
|
935
|
+
`
|
|
936
|
+
);
|
|
937
|
+
if (deps.notifyOrphan) {
|
|
938
|
+
try {
|
|
939
|
+
await deps.notifyOrphan(t.taskId, t.agentId, t.sessionScope);
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return released;
|
|
947
|
+
}
|
|
948
|
+
function createStuckTaskRealDeps(getClient) {
|
|
949
|
+
return {
|
|
950
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
951
|
+
queryInProgressTasks: async () => {
|
|
952
|
+
const { loadDeviceId } = await import("./lib/license.js");
|
|
953
|
+
const deviceId = loadDeviceId();
|
|
954
|
+
const client = getClient();
|
|
955
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
956
|
+
const rows = await client.execute({
|
|
957
|
+
sql: `SELECT id, assigned_to, session_scope, updated_at, lease_expires_at
|
|
958
|
+
FROM tasks
|
|
959
|
+
WHERE status = 'in_progress'
|
|
960
|
+
AND (device_id = ? OR device_id IS NULL OR lease_expires_at < ?)
|
|
961
|
+
ORDER BY updated_at ASC
|
|
962
|
+
LIMIT 1000`,
|
|
963
|
+
args: [deviceId, nowIso]
|
|
964
|
+
});
|
|
965
|
+
return rows.rows.map((r) => ({
|
|
966
|
+
taskId: String(r.id),
|
|
967
|
+
agentId: String(r.assigned_to),
|
|
968
|
+
sessionScope: r.session_scope ? String(r.session_scope) : null,
|
|
969
|
+
updatedAt: String(r.updated_at),
|
|
970
|
+
leaseExpiresAt: r.lease_expires_at ? String(r.lease_expires_at) : null
|
|
971
|
+
}));
|
|
972
|
+
},
|
|
973
|
+
markTaskNeedsReview: async (taskId, reason) => {
|
|
974
|
+
try {
|
|
975
|
+
await updateTask({ taskId, status: "needs_review", result: reason });
|
|
976
|
+
} catch {
|
|
977
|
+
const client = getClient();
|
|
978
|
+
await client.execute({
|
|
979
|
+
sql: `UPDATE tasks SET status = 'needs_review', result = ?, updated_at = ? WHERE id = ?`,
|
|
980
|
+
args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
parseAgentFromSession: (sessionName) => {
|
|
985
|
+
if (!sessionName.includes("-")) return null;
|
|
986
|
+
const agentPart = sessionName.split("-")[0];
|
|
987
|
+
return baseAgentName(agentPart);
|
|
988
|
+
},
|
|
989
|
+
notifyOrphan: async (taskId, agentId, sessionScope) => {
|
|
990
|
+
await writeNotification({
|
|
991
|
+
agentId,
|
|
992
|
+
agentRole: "employee",
|
|
993
|
+
event: "orphan_task",
|
|
994
|
+
project: "",
|
|
995
|
+
summary: `Agent "${agentId}" session died \u2014 task ${taskId.slice(0, 8)} needs review (auto-closed: session ended without completion)`,
|
|
996
|
+
sessionScope: sessionScope ?? void 0
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
var DEFAULT_STALE_REVIEW_DAYS = 7;
|
|
1002
|
+
async function cleanupStaleReviews(deps) {
|
|
1003
|
+
const thresholdDays = deps.getStaleReviewThreshold();
|
|
1004
|
+
let stale;
|
|
1005
|
+
try {
|
|
1006
|
+
stale = await deps.queryStaleReviews(thresholdDays);
|
|
1007
|
+
} catch (e) {
|
|
1008
|
+
process.stderr.write(`[stale-review] query failed: ${e instanceof Error ? e.message : String(e)}
|
|
1009
|
+
`);
|
|
1010
|
+
return [];
|
|
1011
|
+
}
|
|
1012
|
+
const cleaned = [];
|
|
1013
|
+
for (const t of stale) {
|
|
1014
|
+
const reason = `auto-closed: stale review \u2014 no reviewer action for ${thresholdDays} days`;
|
|
1015
|
+
try {
|
|
1016
|
+
await deps.cancelTask(t.taskId, reason);
|
|
1017
|
+
cleaned.push(t.taskId);
|
|
1018
|
+
process.stderr.write(
|
|
1019
|
+
`[stale-review] Cancelled task ${t.taskId.slice(0, 8)} "${t.title}" (${t.assignedTo}) \u2014 needs_review since ${t.updatedAt}
|
|
1020
|
+
`
|
|
1021
|
+
);
|
|
1022
|
+
} catch (err) {
|
|
1023
|
+
process.stderr.write(`[stale-review] Failed to cancel ${t.taskId}: ${err instanceof Error ? err.message : String(err)}
|
|
1024
|
+
`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return cleaned;
|
|
1028
|
+
}
|
|
1029
|
+
function createStaleReviewRealDeps(getClient) {
|
|
1030
|
+
return {
|
|
1031
|
+
queryStaleReviews: async (thresholdDays) => {
|
|
1032
|
+
const client = getClient();
|
|
1033
|
+
const scopeRows = await client.execute({
|
|
1034
|
+
sql: `SELECT DISTINCT session_scope FROM tasks
|
|
1035
|
+
WHERE session_scope IS NOT NULL AND status NOT IN ('closed', 'cancelled')`,
|
|
1036
|
+
args: []
|
|
1037
|
+
});
|
|
1038
|
+
const scopes = scopeRows.rows.map((r) => r.session_scope ? String(r.session_scope) : null);
|
|
1039
|
+
scopes.push(null);
|
|
1040
|
+
const allRows = [];
|
|
1041
|
+
for (const scope of scopes) {
|
|
1042
|
+
const rows = await queryTaskRows({
|
|
1043
|
+
status: "needs_review",
|
|
1044
|
+
columns: "id, title, assigned_to, updated_at",
|
|
1045
|
+
sessionScope: scope,
|
|
1046
|
+
strictSession: scope !== null,
|
|
1047
|
+
projectName: null,
|
|
1048
|
+
orderBy: "updated_at ASC",
|
|
1049
|
+
limit: 1e3,
|
|
1050
|
+
extraConditions: ["updated_at < datetime('now', '-' || ? || ' days')"],
|
|
1051
|
+
extraArgs: [String(thresholdDays)]
|
|
1052
|
+
});
|
|
1053
|
+
allRows.push(...rows);
|
|
1054
|
+
}
|
|
1055
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1056
|
+
return allRows.filter((r) => {
|
|
1057
|
+
const id = String(r.id);
|
|
1058
|
+
if (seen.has(id)) return false;
|
|
1059
|
+
seen.add(id);
|
|
1060
|
+
return true;
|
|
1061
|
+
}).map((r) => ({
|
|
1062
|
+
taskId: String(r.id),
|
|
1063
|
+
title: String(r.title),
|
|
1064
|
+
assignedTo: String(r.assigned_to),
|
|
1065
|
+
updatedAt: String(r.updated_at)
|
|
1066
|
+
}));
|
|
1067
|
+
},
|
|
1068
|
+
cancelTask: async (taskId, reason) => {
|
|
1069
|
+
try {
|
|
1070
|
+
await updateTask({ taskId, status: "cancelled", result: reason });
|
|
1071
|
+
} catch {
|
|
1072
|
+
const client = getClient();
|
|
1073
|
+
await client.execute({
|
|
1074
|
+
sql: `UPDATE tasks SET status = 'cancelled', result = ?, updated_at = ? WHERE id = ?`,
|
|
1075
|
+
args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
getStaleReviewThreshold: () => {
|
|
1080
|
+
try {
|
|
1081
|
+
const cfg = loadConfigSync();
|
|
1082
|
+
return cfg.staleReviewDays ?? DEFAULT_STALE_REVIEW_DAYS;
|
|
1083
|
+
} catch {
|
|
1084
|
+
return DEFAULT_STALE_REVIEW_DAYS;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function createAutoWakeRealDeps(getClient, projectDir) {
|
|
1090
|
+
return {
|
|
1091
|
+
listTmuxSessions: () => listTmuxSessions(),
|
|
1092
|
+
queryTasksByAgent: async () => {
|
|
1093
|
+
const { loadDeviceId } = await import("./lib/license.js");
|
|
1094
|
+
const deviceId = loadDeviceId();
|
|
1095
|
+
const rows = await queryTaskRows({
|
|
1096
|
+
status: ["open", "in_progress"],
|
|
1097
|
+
columns: "assigned_to, id, priority, session_scope",
|
|
1098
|
+
sessionScope: null,
|
|
1099
|
+
// daemon-level: no session filter
|
|
1100
|
+
projectName: null,
|
|
1101
|
+
deviceId,
|
|
1102
|
+
orderBy: "CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END, created_at ASC",
|
|
1103
|
+
limit: 1e3
|
|
1104
|
+
});
|
|
1105
|
+
return rows.map((r) => ({
|
|
1106
|
+
agentId: String(r.assigned_to),
|
|
1107
|
+
taskId: String(r.id),
|
|
1108
|
+
priority: String(r.priority),
|
|
1109
|
+
sessionScope: r.session_scope ? String(r.session_scope) : null
|
|
1110
|
+
}));
|
|
1111
|
+
},
|
|
1112
|
+
ensureEmployee: async (agentName, scope) => {
|
|
1113
|
+
let resolvedProjectDir = projectDir;
|
|
1114
|
+
try {
|
|
1115
|
+
const sessions = listSessions();
|
|
1116
|
+
const coordSession = sessions.find((s) => s.windowName === scope);
|
|
1117
|
+
if (coordSession?.projectDir && existsSync2(coordSession.projectDir)) {
|
|
1118
|
+
resolvedProjectDir = coordSession.projectDir;
|
|
1119
|
+
}
|
|
1120
|
+
} catch {
|
|
1121
|
+
process.stderr.write(
|
|
1122
|
+
`[auto-wake] WARN: could not resolve project dir from session registry for scope="${scope}", falling back to daemon cwd="${projectDir}"
|
|
1123
|
+
`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
return ensureEmployeeAsync(agentName, scope, resolvedProjectDir, shouldAutoInstance(agentName));
|
|
1127
|
+
},
|
|
1128
|
+
markTaskNeedsReview: async (taskId, reason) => {
|
|
1129
|
+
try {
|
|
1130
|
+
await updateTask({ taskId, status: "needs_review", result: reason });
|
|
1131
|
+
} catch {
|
|
1132
|
+
const client = getClient();
|
|
1133
|
+
await client.execute({
|
|
1134
|
+
sql: `UPDATE tasks SET status = 'needs_review', result = ?, updated_at = ? WHERE id = ?`,
|
|
1135
|
+
args: [reason, (/* @__PURE__ */ new Date()).toISOString(), taskId]
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
parseAgentFromSession: (sessionName) => {
|
|
1140
|
+
if (!sessionName.includes("-")) return null;
|
|
1141
|
+
const agentPart = sessionName.split("-")[0];
|
|
1142
|
+
return baseAgentName(agentPart);
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
var COO_CONTEXT_THRESHOLD_PCT = Number(process.env.EXE_COO_CONTEXT_THRESHOLD_PCT) || 85;
|
|
1147
|
+
var COO_RESTART_COOLDOWN_MS = Number(process.env.EXE_COO_RESTART_COOLDOWN_MS) || 5 * 60 * 1e3;
|
|
1148
|
+
var _cooLastRestart = /* @__PURE__ */ new Map();
|
|
1149
|
+
function _resetCooRestartState() {
|
|
1150
|
+
_cooLastRestart.clear();
|
|
1151
|
+
}
|
|
1152
|
+
async function pollCoordinatorContextRestart(deps, projectDir) {
|
|
1153
|
+
let liveSessions;
|
|
1154
|
+
try {
|
|
1155
|
+
liveSessions = deps.listTmuxSessions();
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
process.stderr.write(`[daemon-orch] list tmux sessions for coordinator context: ${e instanceof Error ? e.message : String(e)}
|
|
1158
|
+
`);
|
|
1159
|
+
return [];
|
|
1160
|
+
}
|
|
1161
|
+
const coordinatorSessions = liveSessions.filter((s) => isExeSession(s));
|
|
1162
|
+
if (coordinatorSessions.length === 0) return [];
|
|
1163
|
+
const restarted = [];
|
|
1164
|
+
const now = Date.now();
|
|
1165
|
+
for (const sessionName of coordinatorSessions) {
|
|
1166
|
+
const lastRestart = _cooLastRestart.get(sessionName) ?? 0;
|
|
1167
|
+
if (now - lastRestart < COO_RESTART_COOLDOWN_MS) continue;
|
|
1168
|
+
const contextPct = deps.parseContextPercentage(sessionName);
|
|
1169
|
+
if (contextPct === null || contextPct < COO_CONTEXT_THRESHOLD_PCT) continue;
|
|
1170
|
+
try {
|
|
1171
|
+
if (await deps.hasUnsavedWork(sessionName)) {
|
|
1172
|
+
process.stderr.write(
|
|
1173
|
+
`[exed] Coordinator context restart: SKIPPING ${sessionName} (context=${contextPct}%) \u2014 has unsaved work
|
|
1174
|
+
`
|
|
1175
|
+
);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
} catch {
|
|
1179
|
+
process.stderr.write(
|
|
1180
|
+
`[exed] Coordinator context restart: SKIPPING ${sessionName} \u2014 unsaved work check failed (conservative)
|
|
1181
|
+
`
|
|
1182
|
+
);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
process.stderr.write(
|
|
1186
|
+
`[exed] Coordinator context restart: ${sessionName} at ${contextPct}% (threshold: ${COO_CONTEXT_THRESHOLD_PCT}%). Checkpointing and restarting.
|
|
1187
|
+
`
|
|
1188
|
+
);
|
|
1189
|
+
try {
|
|
1190
|
+
await deps.storeCheckpoint(sessionName, contextPct);
|
|
1191
|
+
deps.recordKill?.({
|
|
1192
|
+
sessionName,
|
|
1193
|
+
agentId: sessionName,
|
|
1194
|
+
reason: "ttl_context"
|
|
1195
|
+
});
|
|
1196
|
+
deps.killSession(sessionName);
|
|
1197
|
+
const result = await deps.respawnCoordinator(sessionName, projectDir);
|
|
1198
|
+
if (result.status === "failed") {
|
|
1199
|
+
process.stderr.write(
|
|
1200
|
+
`[exed] Coordinator context restart: respawn FAILED for ${sessionName}: ${result.error ?? "unknown"}
|
|
1201
|
+
`
|
|
1202
|
+
);
|
|
1203
|
+
} else {
|
|
1204
|
+
_cooLastRestart.set(sessionName, now);
|
|
1205
|
+
restarted.push(sessionName);
|
|
1206
|
+
process.stderr.write(
|
|
1207
|
+
`[exed] Coordinator context restart: ${sessionName} respawned successfully.
|
|
1208
|
+
`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
process.stderr.write(
|
|
1213
|
+
`[exed] Coordinator context restart: error for ${sessionName}: ${err instanceof Error ? err.message : String(err)}
|
|
1214
|
+
`
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return restarted;
|
|
1219
|
+
}
|
|
1220
|
+
function createCoordinatorRestartRealDeps(getClient) {
|
|
1221
|
+
return {
|
|
1222
|
+
listTmuxSessions: () => {
|
|
1223
|
+
return listTmuxSessions();
|
|
1224
|
+
},
|
|
1225
|
+
parseContextPercentage: (sessionName) => {
|
|
1226
|
+
return parseContextPercentage(sessionName);
|
|
1227
|
+
},
|
|
1228
|
+
killSession: (sessionName) => {
|
|
1229
|
+
getTransport().kill(sessionName);
|
|
1230
|
+
},
|
|
1231
|
+
respawnCoordinator: async (sessionName, projectDir) => {
|
|
1232
|
+
try {
|
|
1233
|
+
await execFileAsync("tmux", ["new-session", "-d", "-s", sessionName, "-c", projectDir, "claude --resume"], {
|
|
1234
|
+
encoding: "utf8",
|
|
1235
|
+
timeout: 1e4
|
|
1236
|
+
});
|
|
1237
|
+
return { status: "ok" };
|
|
1238
|
+
} catch (err) {
|
|
1239
|
+
return { status: "failed", error: err instanceof Error ? err.message : String(err) };
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
storeCheckpoint: async (sessionName, contextPct) => {
|
|
1243
|
+
let paneContent = "";
|
|
1244
|
+
try {
|
|
1245
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
1246
|
+
const { promisify: promisify2 } = await import("util");
|
|
1247
|
+
const execFileAsync2 = promisify2(execFile2);
|
|
1248
|
+
const { stdout } = await execFileAsync2("tmux", [
|
|
1249
|
+
"capture-pane",
|
|
1250
|
+
"-t",
|
|
1251
|
+
sessionName,
|
|
1252
|
+
"-p"
|
|
1253
|
+
], { encoding: "utf8", timeout: 3e3 });
|
|
1254
|
+
paneContent = stdout.split("\n").slice(-30).join("\n").trim();
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
const client = getClient();
|
|
1258
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1259
|
+
const summary = [
|
|
1260
|
+
`CONTEXT CHECKPOINT [auto-restart]: Coordinator ${sessionName} at ${contextPct}% context capacity.`,
|
|
1261
|
+
`Auto-restarted by daemon at ${now}.`,
|
|
1262
|
+
paneContent ? `Last activity:
|
|
1263
|
+
${paneContent.slice(0, 500)}` : ""
|
|
1264
|
+
].filter(Boolean).join("\n");
|
|
1265
|
+
await client.execute({
|
|
1266
|
+
sql: `INSERT INTO memories (id, agent_id, agent_role, text, importance, memory_type, project_name, tool_name, created_at, updated_at, session_scope)
|
|
1267
|
+
VALUES (?, ?, 'coordinator', ?, 9, 'observation', ?, 'daemon', ?, ?, NULL)`,
|
|
1268
|
+
args: [
|
|
1269
|
+
randomUUID(),
|
|
1270
|
+
sessionName,
|
|
1271
|
+
summary,
|
|
1272
|
+
"exe-os",
|
|
1273
|
+
now,
|
|
1274
|
+
now
|
|
1275
|
+
]
|
|
1276
|
+
});
|
|
1277
|
+
},
|
|
1278
|
+
hasUnsavedWork: async (sessionName) => {
|
|
1279
|
+
try {
|
|
1280
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
1281
|
+
const { promisify: promisify2 } = await import("util");
|
|
1282
|
+
const execFileAsync2 = promisify2(execFile2);
|
|
1283
|
+
const { stdout } = await execFileAsync2("tmux", [
|
|
1284
|
+
"capture-pane",
|
|
1285
|
+
"-t",
|
|
1286
|
+
sessionName,
|
|
1287
|
+
"-p"
|
|
1288
|
+
], { encoding: "utf8", timeout: 3e3 });
|
|
1289
|
+
const tail = stdout.split("\n").slice(-10).join("\n");
|
|
1290
|
+
if (/(?:git commit|committing|staging)/i.test(tail)) return true;
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
return false;
|
|
1294
|
+
},
|
|
1295
|
+
recordKill: (input) => {
|
|
1296
|
+
void recordSessionKill(input);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
var ORPHAN_SIGKILL_DELAY_MS = 5e3;
|
|
1301
|
+
var ORPHAN_MAX_AGE_SECS = 120;
|
|
1302
|
+
var ORPHAN_PATTERNS = [
|
|
1303
|
+
"exe-os/dist/mcp/server.js",
|
|
1304
|
+
"exe-mem/dist/mcp/server.js",
|
|
1305
|
+
"exe-os/dist/hooks/ingest-worker.js",
|
|
1306
|
+
"exe-mem/dist/hooks/ingest-worker.js",
|
|
1307
|
+
// Hook processes that become orphaned and never get reaped.
|
|
1308
|
+
// These accumulate at ~70MB each and caused 1GB+ memory waste.
|
|
1309
|
+
"codex-stop-task-finalizer.js",
|
|
1310
|
+
"scan-tasks.js",
|
|
1311
|
+
"exe-pending-reviews.js",
|
|
1312
|
+
"exe-pending-messages.js",
|
|
1313
|
+
"exe-pending-notifications.js"
|
|
1314
|
+
];
|
|
1315
|
+
var HOOK_PROCESS_PATTERNS = [
|
|
1316
|
+
"codex-stop-task-finalizer.js",
|
|
1317
|
+
"scan-tasks.js",
|
|
1318
|
+
"exe-pending-reviews.js",
|
|
1319
|
+
"exe-pending-messages.js",
|
|
1320
|
+
"exe-pending-notifications.js",
|
|
1321
|
+
// Hook scripts that can zombie (added 2026-06-01 — orphan bug a4eaf106)
|
|
1322
|
+
"prompt-submit.js",
|
|
1323
|
+
"stop.js",
|
|
1324
|
+
"subagent-stop.js",
|
|
1325
|
+
"pre-tool-use.js",
|
|
1326
|
+
"post-tool-combined.js",
|
|
1327
|
+
"ingest.js",
|
|
1328
|
+
"intercom-check.js",
|
|
1329
|
+
"exe-heartbeat-hook.js"
|
|
1330
|
+
];
|
|
1331
|
+
var AGENT_CLI_EXECUTABLES = /* @__PURE__ */ new Set(["claude", "codex", "opencode"]);
|
|
1332
|
+
function getProcessExecutable(args) {
|
|
1333
|
+
const first = args.trim().split(/\s+/)[0] ?? "";
|
|
1334
|
+
return first.split("/").pop() ?? first;
|
|
1335
|
+
}
|
|
1336
|
+
function getProcessCommandPrefix(args) {
|
|
1337
|
+
return args.split(/\s+--system-prompt\b/)[0] ?? args;
|
|
1338
|
+
}
|
|
1339
|
+
var ZOMBIE_AGENT_MAX_AGE_SECS = 300;
|
|
1340
|
+
function reapZombieAgentProcesses(deps) {
|
|
1341
|
+
let liveSessions;
|
|
1342
|
+
try {
|
|
1343
|
+
liveSessions = new Set(deps.listTmuxSessions());
|
|
1344
|
+
} catch {
|
|
1345
|
+
return [];
|
|
1346
|
+
}
|
|
1347
|
+
const lines = deps.listProcesses();
|
|
1348
|
+
const reaped = [];
|
|
1349
|
+
const procMap = /* @__PURE__ */ new Map();
|
|
1350
|
+
for (const line of lines) {
|
|
1351
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
|
|
1352
|
+
if (!match) continue;
|
|
1353
|
+
procMap.set(parseInt(match[1], 10), {
|
|
1354
|
+
ppid: parseInt(match[2], 10),
|
|
1355
|
+
etime: match[3],
|
|
1356
|
+
tty: match[4],
|
|
1357
|
+
args: match[5]
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
const AGENT_PATTERNS = [
|
|
1361
|
+
/\bclaude\b/,
|
|
1362
|
+
// Claude Code CLI
|
|
1363
|
+
/\bcodex\b/,
|
|
1364
|
+
// OpenAI Codex CLI
|
|
1365
|
+
/\bopencode\b/
|
|
1366
|
+
// OpenCode CLI
|
|
1367
|
+
];
|
|
1368
|
+
const DESKTOP_APP_EXCLUSIONS = [
|
|
1369
|
+
/Claude\s+Helper/i,
|
|
1370
|
+
// "Claude Helper (Renderer)", "Claude Helper (GPU)", etc.
|
|
1371
|
+
/Claude\.app\b/i,
|
|
1372
|
+
// macOS app bundle path
|
|
1373
|
+
/Electron\s+Helper/i,
|
|
1374
|
+
// Generic Electron helper (Claude Desktop is Electron-based)
|
|
1375
|
+
/Claude.*\bRenderer\b/i,
|
|
1376
|
+
// Renderer process variants
|
|
1377
|
+
/Claude.*\bGPU\b/i,
|
|
1378
|
+
// GPU process variants
|
|
1379
|
+
/Contents\/Frameworks\/.*Helper/i,
|
|
1380
|
+
// macOS framework helpers
|
|
1381
|
+
/\/Applications\/Claude/i
|
|
1382
|
+
// Launched from /Applications
|
|
1383
|
+
];
|
|
1384
|
+
for (const [pid, info] of procMap) {
|
|
1385
|
+
if (pid === deps.selfPid) continue;
|
|
1386
|
+
const isAgent = AGENT_PATTERNS.some((pat) => pat.test(info.args));
|
|
1387
|
+
if (!isAgent) continue;
|
|
1388
|
+
const isDesktopApp = DESKTOP_APP_EXCLUSIONS.some((pat) => pat.test(info.args));
|
|
1389
|
+
if (isDesktopApp) continue;
|
|
1390
|
+
const ageSecs = parseEtime(info.etime);
|
|
1391
|
+
if (ageSecs < ZOMBIE_AGENT_MAX_AGE_SECS) continue;
|
|
1392
|
+
if (info.tty !== "??" && info.tty !== "-") continue;
|
|
1393
|
+
const wrapperInfo = procMap.get(info.ppid);
|
|
1394
|
+
if (wrapperInfo && wrapperInfo.tty !== "??" && wrapperInfo.tty !== "-") continue;
|
|
1395
|
+
let hasLiveSession = false;
|
|
1396
|
+
let cur = info.ppid;
|
|
1397
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
1398
|
+
const parent = procMap.get(cur);
|
|
1399
|
+
if (!parent) break;
|
|
1400
|
+
cur = parent.ppid;
|
|
1401
|
+
}
|
|
1402
|
+
const wrapperPid = info.ppid;
|
|
1403
|
+
for (const session of liveSessions) {
|
|
1404
|
+
try {
|
|
1405
|
+
const panePids = deps.getPanePids ? deps.getPanePids(session) : execSync(
|
|
1406
|
+
`tmux list-panes -t ${JSON.stringify(session)} -F '#{pane_pid}' 2>/dev/null`,
|
|
1407
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
1408
|
+
).trim().split("\n").map(Number);
|
|
1409
|
+
if (panePids.includes(wrapperPid)) {
|
|
1410
|
+
hasLiveSession = true;
|
|
1411
|
+
break;
|
|
1412
|
+
}
|
|
1413
|
+
let ancestor = wrapperPid;
|
|
1414
|
+
for (let i = 0; i < 3; i++) {
|
|
1415
|
+
const p = procMap.get(ancestor);
|
|
1416
|
+
if (!p) break;
|
|
1417
|
+
if (panePids.includes(p.ppid)) {
|
|
1418
|
+
hasLiveSession = true;
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
ancestor = p.ppid;
|
|
1422
|
+
}
|
|
1423
|
+
if (hasLiveSession) break;
|
|
1424
|
+
} catch {
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (hasLiveSession) continue;
|
|
1428
|
+
const coordinatorSessions = [...liveSessions].filter(
|
|
1429
|
+
(s) => isCoordinatorName(s.replace(/^\w+-/, "")) || isExeSession(s)
|
|
1430
|
+
);
|
|
1431
|
+
if (coordinatorSessions.length > 0) {
|
|
1432
|
+
let belongsToCoordinator = false;
|
|
1433
|
+
for (const cs of coordinatorSessions) {
|
|
1434
|
+
try {
|
|
1435
|
+
const panePids = (deps.getPanePids ? deps.getPanePids(cs) : execSync(
|
|
1436
|
+
`tmux list-panes -t ${JSON.stringify(cs)} -F '#{pane_pid}' 2>/dev/null`,
|
|
1437
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
1438
|
+
).trim().split("\n").map(Number)).filter(Boolean);
|
|
1439
|
+
for (const panePid of panePids) {
|
|
1440
|
+
let walk = pid;
|
|
1441
|
+
for (let d = 0; d < 10; d++) {
|
|
1442
|
+
if (walk === panePid) {
|
|
1443
|
+
belongsToCoordinator = true;
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
const p = procMap.get(walk);
|
|
1447
|
+
if (!p || p.ppid <= 1) break;
|
|
1448
|
+
walk = p.ppid;
|
|
1449
|
+
}
|
|
1450
|
+
if (belongsToCoordinator) break;
|
|
1451
|
+
}
|
|
1452
|
+
} catch {
|
|
1453
|
+
}
|
|
1454
|
+
if (belongsToCoordinator) break;
|
|
1455
|
+
}
|
|
1456
|
+
if (belongsToCoordinator) {
|
|
1457
|
+
process.stderr.write(
|
|
1458
|
+
`[zombie-agent-reaper] SKIPPING PID ${pid} \u2014 belongs to coordinator session (${info.args.slice(0, 60)})
|
|
1459
|
+
`
|
|
1460
|
+
);
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
const rssKb = getRssKb(pid);
|
|
1465
|
+
const rssMb = rssKb ? Math.round(rssKb / 1024) : "?";
|
|
1466
|
+
const desc = `PID ${pid} (age=${ageSecs}s, RSS=${rssMb}MB, ${info.args.slice(0, 80)})`;
|
|
1467
|
+
try {
|
|
1468
|
+
deps.killProcess(pid, "SIGTERM");
|
|
1469
|
+
} catch {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
reaped.push(desc);
|
|
1473
|
+
process.stderr.write(`[zombie-agent-reaper] Killed ${desc}
|
|
1474
|
+
`);
|
|
1475
|
+
if (wrapperPid > 1 && procMap.has(wrapperPid)) {
|
|
1476
|
+
try {
|
|
1477
|
+
deps.killProcess(wrapperPid, "SIGTERM");
|
|
1478
|
+
process.stderr.write(`[zombie-agent-reaper] Killed wrapper PID ${wrapperPid}
|
|
1479
|
+
`);
|
|
1480
|
+
} catch {
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
deps.scheduleKill(pid, ORPHAN_SIGKILL_DELAY_MS, () => {
|
|
1484
|
+
try {
|
|
1485
|
+
deps.killProcess(pid, "SIGKILL");
|
|
1486
|
+
} catch {
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
if (wrapperPid > 1) {
|
|
1490
|
+
deps.scheduleKill(wrapperPid, ORPHAN_SIGKILL_DELAY_MS, () => {
|
|
1491
|
+
try {
|
|
1492
|
+
deps.killProcess(wrapperPid, "SIGKILL");
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return reaped;
|
|
1499
|
+
}
|
|
1500
|
+
function getRssKb(pid) {
|
|
1501
|
+
try {
|
|
1502
|
+
const out = execSync(`ps -o rss= -p ${pid} 2>/dev/null`, { encoding: "utf8", timeout: 2e3 });
|
|
1503
|
+
const val = parseInt(out.trim(), 10);
|
|
1504
|
+
return isNaN(val) ? null : val;
|
|
1505
|
+
} catch {
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function createZombieAgentReaperRealDeps() {
|
|
1510
|
+
const panePidMap = /* @__PURE__ */ new Map();
|
|
1511
|
+
let panePidCacheReady = false;
|
|
1512
|
+
try {
|
|
1513
|
+
const output = execSync("tmux list-panes -a -F '#{session_name} #{pane_pid}' 2>/dev/null", {
|
|
1514
|
+
encoding: "utf8",
|
|
1515
|
+
timeout: 5e3
|
|
1516
|
+
});
|
|
1517
|
+
for (const line of output.split("\n")) {
|
|
1518
|
+
const [sessionName, pidText] = line.trim().split(/\s+/);
|
|
1519
|
+
const pid = Number(pidText);
|
|
1520
|
+
if (!sessionName || !Number.isFinite(pid)) continue;
|
|
1521
|
+
const arr = panePidMap.get(sessionName) ?? [];
|
|
1522
|
+
arr.push(pid);
|
|
1523
|
+
panePidMap.set(sessionName, arr);
|
|
1524
|
+
}
|
|
1525
|
+
panePidCacheReady = true;
|
|
1526
|
+
} catch {
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
listProcesses: () => {
|
|
1530
|
+
const output = execSync("ps -eo pid,ppid,etime,tty,args", {
|
|
1531
|
+
encoding: "utf8",
|
|
1532
|
+
timeout: 5e3
|
|
1533
|
+
});
|
|
1534
|
+
return output.split("\n");
|
|
1535
|
+
},
|
|
1536
|
+
listTmuxSessions: () => {
|
|
1537
|
+
const output = execSync("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
|
|
1538
|
+
encoding: "utf8",
|
|
1539
|
+
timeout: 3e3
|
|
1540
|
+
});
|
|
1541
|
+
return output.trim().split("\n").filter(Boolean);
|
|
1542
|
+
},
|
|
1543
|
+
killProcess: (pid, signal) => {
|
|
1544
|
+
process.kill(pid, signal);
|
|
1545
|
+
},
|
|
1546
|
+
...panePidCacheReady ? { getPanePids: (sessionName) => panePidMap.get(sessionName) ?? [] } : {},
|
|
1547
|
+
scheduleKill: (_pid, delayMs, cb) => {
|
|
1548
|
+
setTimeout(() => {
|
|
1549
|
+
try {
|
|
1550
|
+
cb();
|
|
1551
|
+
} catch {
|
|
1552
|
+
}
|
|
1553
|
+
}, delayMs).unref();
|
|
1554
|
+
},
|
|
1555
|
+
selfPid: process.pid
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function parseEtime(etime) {
|
|
1559
|
+
const trimmed = etime.trim();
|
|
1560
|
+
const dayMatch = trimmed.match(/^(\d+)-(\d+):(\d+):(\d+)$/);
|
|
1561
|
+
if (dayMatch) {
|
|
1562
|
+
return parseInt(dayMatch[1], 10) * 86400 + parseInt(dayMatch[2], 10) * 3600 + parseInt(dayMatch[3], 10) * 60 + parseInt(dayMatch[4], 10);
|
|
1563
|
+
}
|
|
1564
|
+
const parts = trimmed.split(":").map((p) => parseInt(p, 10));
|
|
1565
|
+
if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
1566
|
+
if (parts.length === 2) return parts[0] * 60 + parts[1];
|
|
1567
|
+
if (parts.length === 1) return parts[0];
|
|
1568
|
+
return 0;
|
|
1569
|
+
}
|
|
1570
|
+
function reapOrphanedMcpProcesses(deps) {
|
|
1571
|
+
const lines = deps.listProcesses();
|
|
1572
|
+
const reaped = [];
|
|
1573
|
+
for (const line of lines) {
|
|
1574
|
+
const trimmed = line.trim();
|
|
1575
|
+
const match = trimmed.match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
|
|
1576
|
+
if (!match) continue;
|
|
1577
|
+
const pid = parseInt(match[1], 10);
|
|
1578
|
+
const ppid = parseInt(match[2], 10);
|
|
1579
|
+
const etime = match[3];
|
|
1580
|
+
const args = match[4];
|
|
1581
|
+
if (pid === deps.selfPid) continue;
|
|
1582
|
+
if (AGENT_CLI_EXECUTABLES.has(getProcessExecutable(args))) continue;
|
|
1583
|
+
const matchScope = getProcessCommandPrefix(args);
|
|
1584
|
+
const isPpid1Orphan = ppid === 1 && ORPHAN_PATTERNS.some((pat) => matchScope.includes(pat));
|
|
1585
|
+
const ageSecs = parseEtime(etime);
|
|
1586
|
+
const isAgedHookOrphan = ageSecs > ORPHAN_MAX_AGE_SECS && HOOK_PROCESS_PATTERNS.some((pat) => matchScope.includes(pat));
|
|
1587
|
+
if (!isPpid1Orphan && !isAgedHookOrphan) continue;
|
|
1588
|
+
try {
|
|
1589
|
+
deps.killProcess(pid, "SIGTERM");
|
|
1590
|
+
} catch {
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
const reason = isPpid1Orphan ? "ppid=1" : `age=${ageSecs}s`;
|
|
1594
|
+
const desc = `PID ${pid} (${reason}, ${args.slice(0, 100)})`;
|
|
1595
|
+
reaped.push(desc);
|
|
1596
|
+
process.stderr.write(`[orphan-reaper] Killed ${desc}
|
|
1597
|
+
`);
|
|
1598
|
+
deps.scheduleKill(pid, ORPHAN_SIGKILL_DELAY_MS, () => {
|
|
1599
|
+
try {
|
|
1600
|
+
deps.killProcess(pid, "SIGKILL");
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
return reaped;
|
|
1606
|
+
}
|
|
1607
|
+
function createOrphanReaperRealDeps() {
|
|
1608
|
+
return {
|
|
1609
|
+
listProcesses: () => {
|
|
1610
|
+
const output = execSync("ps -eo pid,ppid,etime,args", {
|
|
1611
|
+
encoding: "utf8",
|
|
1612
|
+
timeout: 5e3
|
|
1613
|
+
});
|
|
1614
|
+
return output.split("\n");
|
|
1615
|
+
},
|
|
1616
|
+
killProcess: (pid, signal) => {
|
|
1617
|
+
process.kill(pid, signal);
|
|
1618
|
+
},
|
|
1619
|
+
scheduleKill: (_pid, delayMs, cb) => {
|
|
1620
|
+
setTimeout(() => {
|
|
1621
|
+
try {
|
|
1622
|
+
cb();
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
}, delayMs).unref();
|
|
1626
|
+
},
|
|
1627
|
+
selfPid: process.pid
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
var WORKTREE_REAPER_INTERVAL_MS = 30 * 60 * 1e3;
|
|
1631
|
+
var WORKTREE_MIN_AGE_MS = 60 * 60 * 1e3;
|
|
1632
|
+
var WORKTREE_MAX_PRUNES_PER_TICK = 5;
|
|
1633
|
+
var WORKTREE_DIRTY_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1634
|
+
async function reapOrphanedWorktrees(deps, nowMs = Date.now()) {
|
|
1635
|
+
const result = { pruned: [], skipped: [] };
|
|
1636
|
+
let sessions;
|
|
1637
|
+
try {
|
|
1638
|
+
sessions = deps.listTmuxSessions();
|
|
1639
|
+
} catch {
|
|
1640
|
+
return result;
|
|
1641
|
+
}
|
|
1642
|
+
const liveAgents = /* @__PURE__ */ new Set();
|
|
1643
|
+
for (const s of sessions) {
|
|
1644
|
+
if (!s.includes("-")) continue;
|
|
1645
|
+
const agentPart = s.split("-")[0];
|
|
1646
|
+
liveAgents.add(baseAgentName(agentPart));
|
|
1647
|
+
}
|
|
1648
|
+
const repoRoots = /* @__PURE__ */ new Set();
|
|
1649
|
+
for (const s of sessions) {
|
|
1650
|
+
const cwd = deps.getPaneCwd(s);
|
|
1651
|
+
if (!cwd) continue;
|
|
1652
|
+
const root = deps.getGitRoot(cwd);
|
|
1653
|
+
if (root) repoRoots.add(root);
|
|
1654
|
+
}
|
|
1655
|
+
for (const repoRoot of repoRoots) {
|
|
1656
|
+
if (result.pruned.length >= WORKTREE_MAX_PRUNES_PER_TICK) break;
|
|
1657
|
+
let worktrees;
|
|
1658
|
+
try {
|
|
1659
|
+
worktrees = deps.listWorktrees(repoRoot);
|
|
1660
|
+
} catch {
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
for (const wt of worktrees) {
|
|
1664
|
+
if (result.pruned.length >= WORKTREE_MAX_PRUNES_PER_TICK) break;
|
|
1665
|
+
if (!wt.path.includes("/.worktrees/")) continue;
|
|
1666
|
+
const agentName = deps.parseAgentFromWorktreePath(wt.path);
|
|
1667
|
+
if (!agentName) {
|
|
1668
|
+
result.skipped.push({ path: wt.path, reason: "could not parse agent name" });
|
|
1669
|
+
recordOrchestrationEventBestEffort({
|
|
1670
|
+
eventType: "worktree.skipped",
|
|
1671
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1672
|
+
severity: "warn",
|
|
1673
|
+
payload: { reason: "could not parse agent name" }
|
|
1674
|
+
});
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (liveAgents.has(agentName)) continue;
|
|
1678
|
+
const mtimeMs = deps.getDirMtimeMs(wt.path);
|
|
1679
|
+
if (mtimeMs === null) continue;
|
|
1680
|
+
const ageMs = nowMs - mtimeMs;
|
|
1681
|
+
if (ageMs < WORKTREE_MIN_AGE_MS) {
|
|
1682
|
+
result.skipped.push({ path: wt.path, reason: `too young (${Math.round(ageMs / 6e4)}m < 60m)` });
|
|
1683
|
+
recordOrchestrationEventBestEffort({
|
|
1684
|
+
eventType: "worktree.skipped",
|
|
1685
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1686
|
+
agentId: agentName,
|
|
1687
|
+
payload: { reason: "too young", ageMinutes: Math.round(ageMs / 6e4) }
|
|
1688
|
+
});
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
if (deps.isWorktreeDirty(wt.path)) {
|
|
1692
|
+
const ageH2 = Math.round(ageMs / 36e5);
|
|
1693
|
+
if (ageMs >= WORKTREE_DIRTY_MAX_AGE_MS && deps.archiveDirtyWorktree) {
|
|
1694
|
+
process.stderr.write(
|
|
1695
|
+
`[worktree-reaper] Dirty orphan worktree aged out (${ageH2}h > ${WORKTREE_DIRTY_MAX_AGE_MS / 36e5}h), archiving: ${wt.path} (agent: ${agentName})
|
|
1696
|
+
`
|
|
1697
|
+
);
|
|
1698
|
+
try {
|
|
1699
|
+
const archived = await deps.archiveDirtyWorktree(repoRoot, wt.path, wt.branch, agentName);
|
|
1700
|
+
if (archived) {
|
|
1701
|
+
const removed2 = await deps.removeWorktree(repoRoot, wt.path);
|
|
1702
|
+
if (removed2) {
|
|
1703
|
+
await deps.deleteBranch(repoRoot, wt.branch);
|
|
1704
|
+
await deps.deleteRemoteBranch(repoRoot, wt.branch);
|
|
1705
|
+
process.stderr.write(
|
|
1706
|
+
`[worktree-reaper] Archived and pruned dirty orphan: ${wt.path} (agent: ${agentName}, age: ${ageH2}h)
|
|
1707
|
+
`
|
|
1708
|
+
);
|
|
1709
|
+
recordOrchestrationEventBestEffort({
|
|
1710
|
+
eventType: "worktree.archived_and_pruned",
|
|
1711
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1712
|
+
agentId: agentName,
|
|
1713
|
+
payload: { branch: wt.branch, ageHours: ageH2, dirty: true }
|
|
1714
|
+
});
|
|
1715
|
+
result.pruned.push(wt.path);
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
process.stderr.write(
|
|
1721
|
+
`[worktree-reaper] Failed to archive dirty orphan ${wt.path}: ${err instanceof Error ? err.message : String(err)}
|
|
1722
|
+
`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
process.stderr.write(
|
|
1727
|
+
`[worktree-reaper] WARNING: Orphan worktree has uncommitted changes, skipping: ${wt.path} (agent: ${agentName}, age: ${ageH2}h)
|
|
1728
|
+
`
|
|
1729
|
+
);
|
|
1730
|
+
result.skipped.push({ path: wt.path, reason: ageMs >= WORKTREE_DIRTY_MAX_AGE_MS ? "uncommitted changes (archive failed)" : "uncommitted changes" });
|
|
1731
|
+
recordOrchestrationEventBestEffort({
|
|
1732
|
+
eventType: "worktree.skipped",
|
|
1733
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1734
|
+
severity: "warn",
|
|
1735
|
+
agentId: agentName,
|
|
1736
|
+
payload: { reason: "uncommitted changes", ageHours: ageH2, agedOut: ageMs >= WORKTREE_DIRTY_MAX_AGE_MS }
|
|
1737
|
+
});
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
const ageH = Math.round(ageMs / 36e5);
|
|
1741
|
+
const removed = await deps.removeWorktree(repoRoot, wt.path);
|
|
1742
|
+
if (removed) {
|
|
1743
|
+
await deps.deleteBranch(repoRoot, wt.branch);
|
|
1744
|
+
await deps.deleteRemoteBranch(repoRoot, wt.branch);
|
|
1745
|
+
process.stderr.write(
|
|
1746
|
+
`[worktree-reaper] Pruned orphan worktree: ${wt.path} (agent: ${agentName}, age: ${ageH}h)
|
|
1747
|
+
`
|
|
1748
|
+
);
|
|
1749
|
+
recordOrchestrationEventBestEffort({
|
|
1750
|
+
eventType: "worktree.pruned",
|
|
1751
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1752
|
+
agentId: agentName,
|
|
1753
|
+
payload: { branch: wt.branch, ageHours: ageH }
|
|
1754
|
+
});
|
|
1755
|
+
result.pruned.push(wt.path);
|
|
1756
|
+
} else {
|
|
1757
|
+
result.skipped.push({ path: wt.path, reason: "git worktree remove failed" });
|
|
1758
|
+
recordOrchestrationEventBestEffort({
|
|
1759
|
+
eventType: "worktree.failed",
|
|
1760
|
+
source: "daemon-orchestration.reapOrphanedWorktrees",
|
|
1761
|
+
severity: "warn",
|
|
1762
|
+
agentId: agentName,
|
|
1763
|
+
errorCode: "git_worktree_remove_failed",
|
|
1764
|
+
payload: { branch: wt.branch, ageHours: ageH }
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return result;
|
|
1770
|
+
}
|
|
1771
|
+
async function createWorktreeReaperRealDeps() {
|
|
1772
|
+
const { getPaneCwdAsync } = await import("./lib/tmux-status.js");
|
|
1773
|
+
const { getGitRoot, isWorktreeDirty: isDirty } = await import("./worktree-5Q4VB2LR.js");
|
|
1774
|
+
const { statSync: statSync3 } = await import("fs");
|
|
1775
|
+
const { basename } = await import("path");
|
|
1776
|
+
const { promisify: promisify2 } = await import("util");
|
|
1777
|
+
const { execFile: execFileCb } = await import("child_process");
|
|
1778
|
+
const execFileAsync2 = promisify2(execFileCb);
|
|
1779
|
+
const sessions = listTmuxSessions();
|
|
1780
|
+
const cwdMap = /* @__PURE__ */ new Map();
|
|
1781
|
+
const cwdPromises = sessions.map(async (s) => {
|
|
1782
|
+
try {
|
|
1783
|
+
const cwd = await getPaneCwdAsync(s);
|
|
1784
|
+
if (cwd) cwdMap.set(s, cwd);
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
await Promise.all(cwdPromises);
|
|
1789
|
+
const repoRoots = /* @__PURE__ */ new Set();
|
|
1790
|
+
for (const cwd of cwdMap.values()) {
|
|
1791
|
+
const root = getGitRoot(cwd);
|
|
1792
|
+
if (root) repoRoots.add(root);
|
|
1793
|
+
}
|
|
1794
|
+
const worktreeCache = /* @__PURE__ */ new Map();
|
|
1795
|
+
const wtPromises = [...repoRoots].map(async (repoRoot) => {
|
|
1796
|
+
try {
|
|
1797
|
+
const { stdout } = await execFileAsync2("git", ["worktree", "list", "--porcelain"], {
|
|
1798
|
+
cwd: repoRoot,
|
|
1799
|
+
timeout: 1e4
|
|
1800
|
+
});
|
|
1801
|
+
const worktrees = [];
|
|
1802
|
+
let currentPath = "";
|
|
1803
|
+
for (const line of stdout.split("\n")) {
|
|
1804
|
+
if (line.startsWith("worktree ")) {
|
|
1805
|
+
currentPath = line.slice("worktree ".length);
|
|
1806
|
+
} else if (line.startsWith("branch ") && currentPath) {
|
|
1807
|
+
const branch = line.slice("branch refs/heads/".length);
|
|
1808
|
+
worktrees.push({ path: currentPath, branch });
|
|
1809
|
+
currentPath = "";
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
worktreeCache.set(repoRoot, worktrees);
|
|
1813
|
+
} catch {
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
await Promise.all(wtPromises);
|
|
1817
|
+
return {
|
|
1818
|
+
listTmuxSessions: () => sessions,
|
|
1819
|
+
getPaneCwd: (s) => cwdMap.get(s),
|
|
1820
|
+
listWorktrees: (repoRoot) => worktreeCache.get(repoRoot) ?? [],
|
|
1821
|
+
isWorktreeDirty: (wtPath) => isDirty(wtPath),
|
|
1822
|
+
getDirMtimeMs: (dirPath) => {
|
|
1823
|
+
try {
|
|
1824
|
+
return statSync3(dirPath).mtimeMs;
|
|
1825
|
+
} catch {
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
removeWorktree: async (repoRoot, wtPath) => {
|
|
1830
|
+
try {
|
|
1831
|
+
await execFileAsync2("git", ["worktree", "remove", wtPath, "--force"], {
|
|
1832
|
+
cwd: repoRoot,
|
|
1833
|
+
encoding: "utf-8",
|
|
1834
|
+
timeout: 1e4
|
|
1835
|
+
});
|
|
1836
|
+
return true;
|
|
1837
|
+
} catch {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
deleteBranch: async (repoRoot, branch) => {
|
|
1842
|
+
try {
|
|
1843
|
+
await execFileAsync2("git", ["branch", "-D", branch], {
|
|
1844
|
+
cwd: repoRoot,
|
|
1845
|
+
encoding: "utf-8",
|
|
1846
|
+
timeout: 1e4
|
|
1847
|
+
});
|
|
1848
|
+
return true;
|
|
1849
|
+
} catch {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
deleteRemoteBranch: async (repoRoot, branch) => {
|
|
1854
|
+
try {
|
|
1855
|
+
await execFileAsync2("git", ["push", "origin", "--delete", branch], {
|
|
1856
|
+
cwd: repoRoot,
|
|
1857
|
+
encoding: "utf-8",
|
|
1858
|
+
timeout: 15e3
|
|
1859
|
+
});
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1862
|
+
},
|
|
1863
|
+
parseAgentFromWorktreePath: (wtPath) => {
|
|
1864
|
+
const dirName = basename(wtPath);
|
|
1865
|
+
if (!dirName) return null;
|
|
1866
|
+
return baseAgentName(dirName);
|
|
1867
|
+
},
|
|
1868
|
+
getGitRoot: (dir) => getGitRoot(dir),
|
|
1869
|
+
archiveDirtyWorktree: async (_repoRoot, wtPath, _branch, agentName) => {
|
|
1870
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1871
|
+
const quarantineBranch = `quarantine/${agentName}-${timestamp}`;
|
|
1872
|
+
try {
|
|
1873
|
+
await execFileAsync2("git", ["-C", wtPath, "checkout", "-b", quarantineBranch], {
|
|
1874
|
+
encoding: "utf-8",
|
|
1875
|
+
timeout: 1e4
|
|
1876
|
+
});
|
|
1877
|
+
await execFileAsync2("git", ["-C", wtPath, "add", "-A"], {
|
|
1878
|
+
encoding: "utf-8",
|
|
1879
|
+
timeout: 1e4
|
|
1880
|
+
});
|
|
1881
|
+
await execFileAsync2("git", [
|
|
1882
|
+
"-C",
|
|
1883
|
+
wtPath,
|
|
1884
|
+
"commit",
|
|
1885
|
+
"-m",
|
|
1886
|
+
`[quarantine] Auto-archived dirty orphan worktree
|
|
1887
|
+
|
|
1888
|
+
Agent: ${agentName}
|
|
1889
|
+
Original worktree: ${wtPath}
|
|
1890
|
+
Archived by daemon worktree reaper after 7-day dirty orphan threshold.`
|
|
1891
|
+
], {
|
|
1892
|
+
encoding: "utf-8",
|
|
1893
|
+
timeout: 1e4,
|
|
1894
|
+
env: {
|
|
1895
|
+
...process.env,
|
|
1896
|
+
GIT_AUTHOR_NAME: "exe-os daemon",
|
|
1897
|
+
GIT_AUTHOR_EMAIL: "daemon@exe-os.local",
|
|
1898
|
+
GIT_COMMITTER_NAME: "exe-os daemon",
|
|
1899
|
+
GIT_COMMITTER_EMAIL: "daemon@exe-os.local"
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
process.stderr.write(
|
|
1903
|
+
`[worktree-reaper] Archived dirty changes to branch ${quarantineBranch}
|
|
1904
|
+
`
|
|
1905
|
+
);
|
|
1906
|
+
return true;
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
process.stderr.write(
|
|
1909
|
+
`[worktree-reaper] Archive failed for ${wtPath}: ${err instanceof Error ? err.message : String(err)}
|
|
1910
|
+
`
|
|
1911
|
+
);
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
var _lastStaleCheckpointGcMs = 0;
|
|
1918
|
+
var STALE_CHECKPOINT_GC_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
1919
|
+
async function gcStaleCheckpoints() {
|
|
1920
|
+
const now = Date.now();
|
|
1921
|
+
if (now - _lastStaleCheckpointGcMs < STALE_CHECKPOINT_GC_INTERVAL_MS) return 0;
|
|
1922
|
+
_lastStaleCheckpointGcMs = now;
|
|
1923
|
+
try {
|
|
1924
|
+
const { getClient } = await import("./lib/database.js");
|
|
1925
|
+
const client = getClient();
|
|
1926
|
+
const scope = sessionScopeFilter();
|
|
1927
|
+
const result = await client.execute({
|
|
1928
|
+
sql: `UPDATE tasks SET checkpoint = NULL, checkpoint_count = 0
|
|
1929
|
+
WHERE checkpoint IS NOT NULL
|
|
1930
|
+
AND status IN ('done', 'closed', 'cancelled', 'needs_review')
|
|
1931
|
+
${scope.sql}`,
|
|
1932
|
+
args: scope.args
|
|
1933
|
+
});
|
|
1934
|
+
const cleared = Number(result.rowsAffected ?? 0);
|
|
1935
|
+
if (cleared > 0) {
|
|
1936
|
+
process.stderr.write(`[daemon-orchestration] GC'd ${cleared} stale checkpoint(s)
|
|
1937
|
+
`);
|
|
1938
|
+
recordOrchestrationEventBestEffort({
|
|
1939
|
+
eventType: "checkpoint.gc",
|
|
1940
|
+
source: "daemon-orchestration.gcStaleCheckpoints",
|
|
1941
|
+
payload: { cleared }
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
return cleared;
|
|
1945
|
+
} catch (err) {
|
|
1946
|
+
if (process.env.EXE_DEBUG === "1") {
|
|
1947
|
+
process.stderr.write(
|
|
1948
|
+
`[daemon-orchestration] gcStaleCheckpoints error: ${err instanceof Error ? err.message : String(err)}
|
|
1949
|
+
`
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
return 0;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
async function pollTaskGroupBarriers() {
|
|
1956
|
+
try {
|
|
1957
|
+
const { checkAndFireBarriers } = await import("./tasks-crud-LD2WCC5Z.js");
|
|
1958
|
+
const firedGroupIds = await checkAndFireBarriers();
|
|
1959
|
+
if (firedGroupIds.length === 0) return 0;
|
|
1960
|
+
const { getClient } = await import("./lib/database.js");
|
|
1961
|
+
const client = getClient();
|
|
1962
|
+
for (const groupId of firedGroupIds) {
|
|
1963
|
+
try {
|
|
1964
|
+
const groupRow = await client.execute({
|
|
1965
|
+
sql: "SELECT title, coordinator, session_scope, aggregated_result FROM task_groups WHERE id = ?",
|
|
1966
|
+
args: [groupId]
|
|
1967
|
+
});
|
|
1968
|
+
if (groupRow.rows.length === 0) continue;
|
|
1969
|
+
const group = groupRow.rows[0];
|
|
1970
|
+
const coordinator = String(group.coordinator);
|
|
1971
|
+
const sessionScope = group.session_scope != null ? String(group.session_scope) : null;
|
|
1972
|
+
const title = String(group.title);
|
|
1973
|
+
let summary = `Task group "${title}" barrier fired.`;
|
|
1974
|
+
try {
|
|
1975
|
+
const agg = JSON.parse(String(group.aggregated_result ?? "{}"));
|
|
1976
|
+
summary = `Task group "${title}" complete: ${agg.succeeded ?? 0}/${agg.total ?? 0} succeeded`;
|
|
1977
|
+
if (agg.cancelled) summary += `, ${agg.cancelled} cancelled`;
|
|
1978
|
+
if (agg.timed_out) summary += " (timed out)";
|
|
1979
|
+
} catch {
|
|
1980
|
+
}
|
|
1981
|
+
if (sessionScope) {
|
|
1982
|
+
try {
|
|
1983
|
+
if (isCoordinatorName(coordinator) && isExeSession(sessionScope)) {
|
|
1984
|
+
await sendIntercomAsync(sessionScope, { force: true, reason: "completion" });
|
|
1985
|
+
} else {
|
|
1986
|
+
const { employeeSessionName } = await import("./lib/tmux-routing.js");
|
|
1987
|
+
await sendIntercomAsync(employeeSessionName(coordinator, sessionScope), { force: true, reason: "completion" });
|
|
1988
|
+
}
|
|
1989
|
+
} catch {
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
await writeNotification({
|
|
1994
|
+
agentId: coordinator,
|
|
1995
|
+
agentRole: isCoordinatorName(coordinator) ? "COO" : "manager",
|
|
1996
|
+
event: "task_group_barrier",
|
|
1997
|
+
project: "",
|
|
1998
|
+
summary,
|
|
1999
|
+
sessionScope: sessionScope ?? void 0
|
|
2000
|
+
});
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
recordOrchestrationEventBestEffort({
|
|
2004
|
+
eventType: "task_group.barrier_notified",
|
|
2005
|
+
source: "daemon-orchestration.pollTaskGroupBarriers",
|
|
2006
|
+
agentId: coordinator,
|
|
2007
|
+
sessionScope,
|
|
2008
|
+
payload: { groupId, title, summary }
|
|
2009
|
+
});
|
|
2010
|
+
} catch {
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return firedGroupIds.length;
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
if (process.env.EXE_DEBUG === "1") {
|
|
2016
|
+
process.stderr.write(
|
|
2017
|
+
`[daemon-orchestration] pollTaskGroupBarriers error: ${err instanceof Error ? err.message : String(err)}
|
|
2018
|
+
`
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
return 0;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
export {
|
|
2026
|
+
getAvailableMemoryGB,
|
|
2027
|
+
IDLE_NUDGE_DEDUP_MS,
|
|
2028
|
+
SESSION_TTL_HOURS,
|
|
2029
|
+
SESSION_CONTEXT_THRESHOLD_PCT,
|
|
2030
|
+
IDLE_KILL_INTERCOM_ACK_WINDOW_MS,
|
|
2031
|
+
shouldNudgeEmployee,
|
|
2032
|
+
shouldKillSession,
|
|
2033
|
+
classifyTtlKillReason,
|
|
2034
|
+
shouldKillIdleSession,
|
|
2035
|
+
pollIdleEmployees,
|
|
2036
|
+
checkSessionTTL,
|
|
2037
|
+
pollIdleKill,
|
|
2038
|
+
REVIEW_NUDGE_COOLDOWN_MS,
|
|
2039
|
+
pollReviewNudge,
|
|
2040
|
+
loadNudgeState,
|
|
2041
|
+
saveNudgeState,
|
|
2042
|
+
createReviewNudgeRealDeps,
|
|
2043
|
+
createIdleNudgeRealDeps,
|
|
2044
|
+
createSessionTTLRealDeps,
|
|
2045
|
+
createIdleKillRealDeps,
|
|
2046
|
+
AUTO_WAKE_COOLDOWN_MS,
|
|
2047
|
+
AUTO_WAKE_MAX_RETRIES,
|
|
2048
|
+
CRASH_LOOP_WINDOW_MS,
|
|
2049
|
+
CRASH_LOOP_THRESHOLD,
|
|
2050
|
+
_resetAutoWakeState,
|
|
2051
|
+
checkAutoWakeGates,
|
|
2052
|
+
shouldAutoWake,
|
|
2053
|
+
pollOrphanedTasks,
|
|
2054
|
+
STUCK_TASK_GRACE_MS,
|
|
2055
|
+
releaseStuckTasks,
|
|
2056
|
+
createStuckTaskRealDeps,
|
|
2057
|
+
cleanupStaleReviews,
|
|
2058
|
+
createStaleReviewRealDeps,
|
|
2059
|
+
createAutoWakeRealDeps,
|
|
2060
|
+
COO_CONTEXT_THRESHOLD_PCT,
|
|
2061
|
+
COO_RESTART_COOLDOWN_MS,
|
|
2062
|
+
_resetCooRestartState,
|
|
2063
|
+
pollCoordinatorContextRestart,
|
|
2064
|
+
createCoordinatorRestartRealDeps,
|
|
2065
|
+
ORPHAN_SIGKILL_DELAY_MS,
|
|
2066
|
+
ORPHAN_MAX_AGE_SECS,
|
|
2067
|
+
ORPHAN_PATTERNS,
|
|
2068
|
+
HOOK_PROCESS_PATTERNS,
|
|
2069
|
+
reapZombieAgentProcesses,
|
|
2070
|
+
createZombieAgentReaperRealDeps,
|
|
2071
|
+
reapOrphanedMcpProcesses,
|
|
2072
|
+
createOrphanReaperRealDeps,
|
|
2073
|
+
WORKTREE_REAPER_INTERVAL_MS,
|
|
2074
|
+
reapOrphanedWorktrees,
|
|
2075
|
+
createWorktreeReaperRealDeps,
|
|
2076
|
+
gcStaleCheckpoints,
|
|
2077
|
+
pollTaskGroupBarriers
|
|
2078
|
+
};
|