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