@theokit/sdk 4.51.0 → 4.52.0
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/CHANGELOG.md +32 -0
- package/dist/agent-C66BEJGO.cjs +58 -0
- package/dist/{agent-B2WU3JP6.cjs.map → agent-C66BEJGO.cjs.map} +1 -1
- package/dist/agent-HYQGJTIN.js +49 -0
- package/dist/{agent-DI2QQHSJ.js.map → agent-HYQGJTIN.js.map} +1 -1
- package/dist/agent-session-store-EPX2WQI4.cjs +23 -0
- package/dist/{agent-session-store-EPUERCUU.cjs.map → agent-session-store-EPX2WQI4.cjs.map} +1 -1
- package/dist/agent-session-store-JMU7ASDB.js +6 -0
- package/dist/{agent-session-store-TXP5ZVOD.js.map → agent-session-store-JMU7ASDB.js.map} +1 -1
- package/dist/auth/index.cjs +21 -16
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.js +5 -4
- package/dist/auth/index.js.map +1 -1
- package/dist/{batch-OUF4N2TM.js → batch-NLIS4QTW.js} +9 -9
- package/dist/{batch-OUF4N2TM.js.map → batch-NLIS4QTW.js.map} +1 -1
- package/dist/{batch-URTEXK77.cjs → batch-WQ3AJCDV.cjs} +26 -26
- package/dist/{batch-URTEXK77.cjs.map → batch-WQ3AJCDV.cjs.map} +1 -1
- package/dist/{chunk-WUUSAMFC.cjs → chunk-2CD5XUAG.cjs} +24 -24
- package/dist/{chunk-WUUSAMFC.cjs.map → chunk-2CD5XUAG.cjs.map} +1 -1
- package/dist/{chunk-NETYW4V3.cjs → chunk-2CFEET3Y.cjs} +4 -4
- package/dist/{chunk-NETYW4V3.cjs.map → chunk-2CFEET3Y.cjs.map} +1 -1
- package/dist/{chunk-YULF6FPB.cjs → chunk-2YGIBOUH.cjs} +8 -8
- package/dist/{chunk-YULF6FPB.cjs.map → chunk-2YGIBOUH.cjs.map} +1 -1
- package/dist/{chunk-FZ4YMZA3.cjs → chunk-3R4ZCQAZ.cjs} +5 -5
- package/dist/{chunk-FZ4YMZA3.cjs.map → chunk-3R4ZCQAZ.cjs.map} +1 -1
- package/dist/{chunk-HECR7KF5.cjs → chunk-3YYDHUNH.cjs} +11 -11
- package/dist/{chunk-HECR7KF5.cjs.map → chunk-3YYDHUNH.cjs.map} +1 -1
- package/dist/{chunk-BRHRQOJA.cjs → chunk-44IISBRZ.cjs} +4 -4
- package/dist/{chunk-BRHRQOJA.cjs.map → chunk-44IISBRZ.cjs.map} +1 -1
- package/dist/{chunk-PBML2FHN.js → chunk-4X3SBHPK.js} +2 -2
- package/dist/chunk-4X3SBHPK.js.map +1 -0
- package/dist/{chunk-OLLOUAUJ.js → chunk-5XXUYU3M.js} +3 -3
- package/dist/{chunk-OLLOUAUJ.js.map → chunk-5XXUYU3M.js.map} +1 -1
- package/dist/{chunk-KCQUYQ3V.cjs → chunk-6DCTL32L.cjs} +17 -17
- package/dist/{chunk-KCQUYQ3V.cjs.map → chunk-6DCTL32L.cjs.map} +1 -1
- package/dist/{chunk-R2A3WIJY.js → chunk-6PTCKXD3.js} +8 -8
- package/dist/{chunk-R2A3WIJY.js.map → chunk-6PTCKXD3.js.map} +1 -1
- package/dist/{chunk-XR4LPBXH.cjs → chunk-7HQVDLFI.cjs} +9 -9
- package/dist/{chunk-XR4LPBXH.cjs.map → chunk-7HQVDLFI.cjs.map} +1 -1
- package/dist/{chunk-GNMPCZNN.js → chunk-7TB5U7RK.js} +6 -6
- package/dist/{chunk-GNMPCZNN.js.map → chunk-7TB5U7RK.js.map} +1 -1
- package/dist/{chunk-7PESI6YD.cjs → chunk-ADVZIC43.cjs} +10 -10
- package/dist/{chunk-7PESI6YD.cjs.map → chunk-ADVZIC43.cjs.map} +1 -1
- package/dist/{chunk-NNNUQELM.cjs → chunk-AMYY3JHQ.cjs} +9 -9
- package/dist/{chunk-NNNUQELM.cjs.map → chunk-AMYY3JHQ.cjs.map} +1 -1
- package/dist/{chunk-L4JFQCG4.js → chunk-BKRTYDVN.js} +3 -3
- package/dist/{chunk-L4JFQCG4.js.map → chunk-BKRTYDVN.js.map} +1 -1
- package/dist/{chunk-UJMEWGO6.cjs → chunk-BWGXYOBZ.cjs} +4 -4
- package/dist/{chunk-UJMEWGO6.cjs.map → chunk-BWGXYOBZ.cjs.map} +1 -1
- package/dist/{chunk-E7WDXT4X.js → chunk-BXVSBYGW.js} +5 -5
- package/dist/{chunk-E7WDXT4X.js.map → chunk-BXVSBYGW.js.map} +1 -1
- package/dist/{chunk-AF4BBKIF.js → chunk-C4EU627W.js} +4 -4
- package/dist/{chunk-AF4BBKIF.js.map → chunk-C4EU627W.js.map} +1 -1
- package/dist/{chunk-VQFSEKU5.js → chunk-CHVSMEKM.js} +3 -3
- package/dist/{chunk-VQFSEKU5.js.map → chunk-CHVSMEKM.js.map} +1 -1
- package/dist/{chunk-6X2HUT4A.cjs → chunk-CZHPR2G7.cjs} +8 -8
- package/dist/{chunk-6X2HUT4A.cjs.map → chunk-CZHPR2G7.cjs.map} +1 -1
- package/dist/{chunk-F4RUADRC.js → chunk-DHVNJYTO.js} +3 -3
- package/dist/{chunk-F4RUADRC.js.map → chunk-DHVNJYTO.js.map} +1 -1
- package/dist/{chunk-SEZJGV7C.js → chunk-DU7PBCAM.js} +5 -5
- package/dist/{chunk-SEZJGV7C.js.map → chunk-DU7PBCAM.js.map} +1 -1
- package/dist/{chunk-S4Q5OMIK.cjs → chunk-E3OCRJU6.cjs} +11 -11
- package/dist/{chunk-S4Q5OMIK.cjs.map → chunk-E3OCRJU6.cjs.map} +1 -1
- package/dist/{chunk-3KY7MHR4.js → chunk-E6T264NS.js} +3 -3
- package/dist/{chunk-3KY7MHR4.js.map → chunk-E6T264NS.js.map} +1 -1
- package/dist/{chunk-WCXOKGOQ.cjs → chunk-ECPL5RV6.cjs} +8 -8
- package/dist/{chunk-WCXOKGOQ.cjs.map → chunk-ECPL5RV6.cjs.map} +1 -1
- package/dist/{chunk-LLE7HPRI.js → chunk-EFXJ5C7X.js} +5 -5
- package/dist/{chunk-LLE7HPRI.js.map → chunk-EFXJ5C7X.js.map} +1 -1
- package/dist/{chunk-RWMNTFHZ.cjs → chunk-EQMLJ52C.cjs} +26 -26
- package/dist/{chunk-RWMNTFHZ.cjs.map → chunk-EQMLJ52C.cjs.map} +1 -1
- package/dist/{chunk-WIGJGFH4.cjs → chunk-EUBGGYPH.cjs} +5 -5
- package/dist/{chunk-WIGJGFH4.cjs.map → chunk-EUBGGYPH.cjs.map} +1 -1
- package/dist/{chunk-RBQYHEZD.cjs → chunk-FBZMSLDC.cjs} +4 -4
- package/dist/{chunk-RBQYHEZD.cjs.map → chunk-FBZMSLDC.cjs.map} +1 -1
- package/dist/{chunk-3G6ZDZ6L.cjs → chunk-FUASIT3E.cjs} +15 -15
- package/dist/{chunk-3G6ZDZ6L.cjs.map → chunk-FUASIT3E.cjs.map} +1 -1
- package/dist/{chunk-MRKSSN36.js → chunk-HI4ZW62S.js} +3 -3
- package/dist/{chunk-MRKSSN36.js.map → chunk-HI4ZW62S.js.map} +1 -1
- package/dist/{chunk-O3IZ7JEL.js → chunk-I732VEDW.js} +3 -3
- package/dist/{chunk-O3IZ7JEL.js.map → chunk-I732VEDW.js.map} +1 -1
- package/dist/{chunk-AZ6FKDQR.js → chunk-ITQ4NO4P.js} +3 -3
- package/dist/{chunk-AZ6FKDQR.js.map → chunk-ITQ4NO4P.js.map} +1 -1
- package/dist/{chunk-PYM5OUL4.js → chunk-J4RY5DTI.js} +5 -5
- package/dist/{chunk-PYM5OUL4.js.map → chunk-J4RY5DTI.js.map} +1 -1
- package/dist/{chunk-NOM2YWFP.js → chunk-JJZ4NIAG.js} +5 -5
- package/dist/{chunk-NOM2YWFP.js.map → chunk-JJZ4NIAG.js.map} +1 -1
- package/dist/{chunk-GC44RATP.cjs → chunk-K42QGAKM.cjs} +10 -10
- package/dist/{chunk-GC44RATP.cjs.map → chunk-K42QGAKM.cjs.map} +1 -1
- package/dist/{chunk-FRQ6F44U.js → chunk-KIX45IKP.js} +4 -4
- package/dist/{chunk-FRQ6F44U.js.map → chunk-KIX45IKP.js.map} +1 -1
- package/dist/{chunk-OEFFPR2S.cjs → chunk-KNZU4YO5.cjs} +4 -4
- package/dist/{chunk-OEFFPR2S.cjs.map → chunk-KNZU4YO5.cjs.map} +1 -1
- package/dist/{chunk-HK4HRLWE.js → chunk-KZBB4YKU.js} +3 -3
- package/dist/{chunk-HK4HRLWE.js.map → chunk-KZBB4YKU.js.map} +1 -1
- package/dist/{chunk-ZS6SLELQ.js → chunk-L7EGRCKJ.js} +4 -4
- package/dist/{chunk-ZS6SLELQ.js.map → chunk-L7EGRCKJ.js.map} +1 -1
- package/dist/{chunk-F7OBYH2X.cjs → chunk-LLK2RS6I.cjs} +294 -294
- package/dist/{chunk-F7OBYH2X.cjs.map → chunk-LLK2RS6I.cjs.map} +1 -1
- package/dist/{chunk-LXKNENHK.js → chunk-MV4TOCUK.js} +3 -3
- package/dist/{chunk-LXKNENHK.js.map → chunk-MV4TOCUK.js.map} +1 -1
- package/dist/{chunk-PAV4ZPSW.js → chunk-N43XLTHZ.js} +3 -3
- package/dist/{chunk-PAV4ZPSW.js.map → chunk-N43XLTHZ.js.map} +1 -1
- package/dist/{chunk-ZNW2TXLO.js → chunk-NACDF3PK.js} +3 -3
- package/dist/{chunk-ZNW2TXLO.js.map → chunk-NACDF3PK.js.map} +1 -1
- package/dist/{chunk-27DYRARU.cjs → chunk-NFATC6ZF.cjs} +4 -4
- package/dist/{chunk-27DYRARU.cjs.map → chunk-NFATC6ZF.cjs.map} +1 -1
- package/dist/{chunk-E54KDYOE.js → chunk-NOS7PTKP.js} +7 -7
- package/dist/{chunk-E54KDYOE.js.map → chunk-NOS7PTKP.js.map} +1 -1
- package/dist/{chunk-NRA36JKG.cjs → chunk-OI6RHLGC.cjs} +11 -11
- package/dist/{chunk-NRA36JKG.cjs.map → chunk-OI6RHLGC.cjs.map} +1 -1
- package/dist/{chunk-J5RW43IR.js → chunk-OIBHY6JQ.js} +4 -4
- package/dist/{chunk-J5RW43IR.js.map → chunk-OIBHY6JQ.js.map} +1 -1
- package/dist/{chunk-SNKY3C2L.js → chunk-OZXPA3ME.js} +4 -4
- package/dist/{chunk-SNKY3C2L.js.map → chunk-OZXPA3ME.js.map} +1 -1
- package/dist/{chunk-35NFZ7QB.js → chunk-PLUCC4N5.js} +3 -3
- package/dist/{chunk-35NFZ7QB.js.map → chunk-PLUCC4N5.js.map} +1 -1
- package/dist/{chunk-MMZX557L.cjs → chunk-PVBANCWU.cjs} +5 -5
- package/dist/{chunk-MMZX557L.cjs.map → chunk-PVBANCWU.cjs.map} +1 -1
- package/dist/{chunk-BV42CCRP.cjs → chunk-QLAWEGTZ.cjs} +7 -7
- package/dist/{chunk-BV42CCRP.cjs.map → chunk-QLAWEGTZ.cjs.map} +1 -1
- package/dist/{chunk-HKSSM5RK.js → chunk-R7TKOQMJ.js} +3 -3
- package/dist/{chunk-HKSSM5RK.js.map → chunk-R7TKOQMJ.js.map} +1 -1
- package/dist/{chunk-HLNLYLNN.cjs → chunk-RAACTJ7C.cjs} +4 -4
- package/dist/{chunk-HLNLYLNN.cjs.map → chunk-RAACTJ7C.cjs.map} +1 -1
- package/dist/{chunk-X2YQP452.cjs → chunk-RTLUZKDL.cjs} +11 -11
- package/dist/{chunk-X2YQP452.cjs.map → chunk-RTLUZKDL.cjs.map} +1 -1
- package/dist/{chunk-6W4U2G3J.cjs → chunk-SDJYLCJK.cjs} +5 -5
- package/dist/{chunk-6W4U2G3J.cjs.map → chunk-SDJYLCJK.cjs.map} +1 -1
- package/dist/{chunk-VK7SIDYI.js → chunk-SPZZSAHV.js} +5 -5
- package/dist/{chunk-VK7SIDYI.js.map → chunk-SPZZSAHV.js.map} +1 -1
- package/dist/{chunk-ZHPET3LZ.cjs → chunk-SXSPMRSV.cjs} +2 -2
- package/dist/chunk-SXSPMRSV.cjs.map +1 -0
- package/dist/{chunk-JA4FEA5Z.cjs → chunk-T6QUCG7L.cjs} +4 -4
- package/dist/{chunk-JA4FEA5Z.cjs.map → chunk-T6QUCG7L.cjs.map} +1 -1
- package/dist/{chunk-GLEKLAUW.cjs → chunk-T6ZCG5AP.cjs} +12 -12
- package/dist/{chunk-GLEKLAUW.cjs.map → chunk-T6ZCG5AP.cjs.map} +1 -1
- package/dist/chunk-U3PAW4PL.js +22 -0
- package/dist/chunk-U3PAW4PL.js.map +1 -0
- package/dist/{chunk-ZTEAJAE6.js → chunk-UJ3SXRZZ.js} +3 -3
- package/dist/{chunk-ZTEAJAE6.js.map → chunk-UJ3SXRZZ.js.map} +1 -1
- package/dist/{chunk-Z6N6SDNR.js → chunk-V2EJLKGA.js} +4 -4
- package/dist/{chunk-Z6N6SDNR.js.map → chunk-V2EJLKGA.js.map} +1 -1
- package/dist/{chunk-57KX5WTP.cjs → chunk-V45L2OYP.cjs} +9 -9
- package/dist/{chunk-57KX5WTP.cjs.map → chunk-V45L2OYP.cjs.map} +1 -1
- package/dist/{chunk-2WB6EIYW.js → chunk-W4FV7JBH.js} +4 -4
- package/dist/{chunk-2WB6EIYW.js.map → chunk-W4FV7JBH.js.map} +1 -1
- package/dist/chunk-WVDH34LK.cjs +24 -0
- package/dist/chunk-WVDH34LK.cjs.map +1 -0
- package/dist/{chunk-TJRPNBY5.js → chunk-XGYI2KQH.js} +4 -4
- package/dist/{chunk-TJRPNBY5.js.map → chunk-XGYI2KQH.js.map} +1 -1
- package/dist/{chunk-4JRRFRV4.cjs → chunk-XK54DTRF.cjs} +4 -4
- package/dist/{chunk-4JRRFRV4.cjs.map → chunk-XK54DTRF.cjs.map} +1 -1
- package/dist/{chunk-JGYYNDAZ.js → chunk-XWCTBGYK.js} +3 -3
- package/dist/{chunk-JGYYNDAZ.js.map → chunk-XWCTBGYK.js.map} +1 -1
- package/dist/{chunk-UQHIEUDY.cjs → chunk-YARJG3GV.cjs} +8 -8
- package/dist/{chunk-UQHIEUDY.cjs.map → chunk-YARJG3GV.cjs.map} +1 -1
- package/dist/{chunk-TDDRPFO3.js → chunk-YBB66SZZ.js} +13 -21
- package/dist/chunk-YBB66SZZ.js.map +1 -0
- package/dist/{chunk-XABN5HY6.cjs → chunk-YTO5BRBD.cjs} +8 -8
- package/dist/{chunk-XABN5HY6.cjs.map → chunk-YTO5BRBD.cjs.map} +1 -1
- package/dist/{chunk-72HEBGWN.cjs → chunk-Z5U2JGEK.cjs} +11 -11
- package/dist/{chunk-72HEBGWN.cjs.map → chunk-Z5U2JGEK.cjs.map} +1 -1
- package/dist/{chunk-ACHVCIDQ.js → chunk-ZFARZBOX.js} +35 -35
- package/dist/{chunk-ACHVCIDQ.js.map → chunk-ZFARZBOX.js.map} +1 -1
- package/dist/{chunk-QFFNMJAS.cjs → chunk-ZNEYQ56O.cjs} +124 -132
- package/dist/chunk-ZNEYQ56O.cjs.map +1 -0
- package/dist/{chunk-Q4DTGCSD.js → chunk-ZSJPRPN7.js} +3 -3
- package/dist/{chunk-Q4DTGCSD.js.map → chunk-ZSJPRPN7.js.map} +1 -1
- package/dist/compact-session-AXKDB6FC.cjs +57 -0
- package/dist/{compact-session-FKYCJRAK.cjs.map → compact-session-AXKDB6FC.cjs.map} +1 -1
- package/dist/compact-session-DUAUEFRR.js +20 -0
- package/dist/{compact-session-LF4YOT5S.js.map → compact-session-DUAUEFRR.js.map} +1 -1
- package/dist/compaction.cjs +16 -16
- package/dist/compaction.js +3 -3
- package/dist/concurrency.cjs +6 -6
- package/dist/concurrency.js +4 -4
- package/dist/context-4HGPCOH6.cjs +22 -0
- package/dist/{context-2G4375BZ.cjs.map → context-4HGPCOH6.cjs.map} +1 -1
- package/dist/context-DCECDKWN.js +5 -0
- package/dist/{context-GSQYVVMJ.js.map → context-DCECDKWN.js.map} +1 -1
- package/dist/cron.cjs +32 -31
- package/dist/cron.js +31 -30
- package/dist/errors.cjs +21 -21
- package/dist/errors.js +2 -2
- package/dist/eval.cjs +37 -36
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +31 -30
- package/dist/eval.js.map +1 -1
- package/dist/{executor-VQLTQY53.js → executor-3CGPLVJL.js} +7 -7
- package/dist/{executor-VQLTQY53.js.map → executor-3CGPLVJL.js.map} +1 -1
- package/dist/{executor-CB75B5BH.cjs → executor-4SW7QGZ4.cjs} +24 -24
- package/dist/{executor-CB75B5BH.cjs.map → executor-4SW7QGZ4.cjs.map} +1 -1
- package/dist/filesystem/index.cjs +7 -7
- package/dist/filesystem/index.js +3 -3
- package/dist/fs-session-store-2TF3ZWMT.js +10 -0
- package/dist/{fs-session-store-ZNUYZHYI.js.map → fs-session-store-2TF3ZWMT.js.map} +1 -1
- package/dist/fs-session-store-TL4RJTXZ.cjs +19 -0
- package/dist/{fs-session-store-MAZV5FXJ.cjs.map → fs-session-store-TL4RJTXZ.cjs.map} +1 -1
- package/dist/generate-object-E465DX5I.cjs +20 -0
- package/dist/{generate-object-6PWGH5DZ.cjs.map → generate-object-E465DX5I.cjs.map} +1 -1
- package/dist/generate-object-TVQCW2IZ.js +7 -0
- package/dist/{generate-object-IXMJ7FUX.js.map → generate-object-TVQCW2IZ.js.map} +1 -1
- package/dist/index-manager-EA6FGIQG.js +12 -0
- package/dist/{index-manager-OMJVYS2U.js.map → index-manager-EA6FGIQG.js.map} +1 -1
- package/dist/index-manager-HGGL4DD5.cjs +21 -0
- package/dist/{index-manager-34ELCOAP.cjs.map → index-manager-HGGL4DD5.cjs.map} +1 -1
- package/dist/index.cjs +134 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +44 -43
- package/dist/index.js.map +1 -1
- package/dist/{inject-session-MWRJENH4.js → inject-session-RBGOGACE.js} +7 -7
- package/dist/{inject-session-MWRJENH4.js.map → inject-session-RBGOGACE.js.map} +1 -1
- package/dist/{inject-session-J7Y6XACY.cjs → inject-session-TASLP5FA.cjs} +9 -9
- package/dist/{inject-session-J7Y6XACY.cjs.map → inject-session-TASLP5FA.cjs.map} +1 -1
- package/dist/internal/auth/api-key-prefix.d.ts +17 -0
- package/dist/internal/memory/adapters/index.cjs +8 -8
- package/dist/internal/memory/adapters/index.js +7 -7
- package/dist/internal/persistence/index.cjs +25 -25
- package/dist/internal/persistence/index.js +6 -6
- package/dist/internal/security/index.cjs +13 -13
- package/dist/internal/security/index.js +3 -3
- package/dist/judge-call-GVIJWQVE.cjs +22 -0
- package/dist/{judge-call-J5EETJL3.cjs.map → judge-call-GVIJWQVE.cjs.map} +1 -1
- package/dist/judge-call-QMKGC2ZK.js +5 -0
- package/dist/{judge-call-3QZO5H5N.js.map → judge-call-QMKGC2ZK.js.map} +1 -1
- package/dist/models.cjs +20 -20
- package/dist/models.js +5 -5
- package/dist/path-safety.cjs +10 -10
- package/dist/path-safety.js +3 -3
- package/dist/persistence.cjs +28 -28
- package/dist/persistence.js +8 -8
- package/dist/project.cjs +5 -5
- package/dist/project.js +3 -3
- package/dist/registry-GC7BQMSV.js +9 -0
- package/dist/{registry-PPFMCM4A.js.map → registry-GC7BQMSV.js.map} +1 -1
- package/dist/registry-UBY26R4I.cjs +46 -0
- package/dist/{registry-KB3TS6BM.cjs.map → registry-UBY26R4I.cjs.map} +1 -1
- package/dist/retry.cjs +4 -4
- package/dist/retry.js +3 -3
- package/dist/sandbox/index.cjs +6 -6
- package/dist/sandbox/index.js +2 -2
- package/dist/server/errors-envelope.cjs +14 -14
- package/dist/server/errors-envelope.js +3 -3
- package/dist/session-transcript-SKIRBEJE.js +5 -0
- package/dist/{session-transcript-7FWTO4BK.js.map → session-transcript-SKIRBEJE.js.map} +1 -1
- package/dist/session-transcript-TNAJ4O3M.cjs +50 -0
- package/dist/{session-transcript-FYRKWRWV.cjs.map → session-transcript-TNAJ4O3M.cjs.map} +1 -1
- package/dist/skills.cjs +7 -7
- package/dist/skills.js +5 -5
- package/dist/stream-object-QMNR3YFF.js +7 -0
- package/dist/{stream-object-NPJXDZXO.js.map → stream-object-QMNR3YFF.js.map} +1 -1
- package/dist/stream-object-T4DAMNVB.cjs +20 -0
- package/dist/{stream-object-3R3HZPQN.cjs.map → stream-object-T4DAMNVB.cjs.map} +1 -1
- package/dist/subagents-loader-3CXBQWWY.cjs +15 -0
- package/dist/{subagents-loader-FDXPBBRA.cjs.map → subagents-loader-3CXBQWWY.cjs.map} +1 -1
- package/dist/subagents-loader-WN2SB6PV.js +6 -0
- package/dist/{subagents-loader-5KQVYUNS.js.map → subagents-loader-WN2SB6PV.js.map} +1 -1
- package/dist/subagents-loader.cjs +6 -6
- package/dist/subagents-loader.js +4 -4
- package/dist/subscription/index.cjs +3 -3
- package/dist/subscription/index.js +2 -2
- package/dist/task-store.cjs +7 -7
- package/dist/task-store.js +4 -4
- package/dist/workflow.cjs +27 -27
- package/dist/workflow.js +6 -6
- package/package.json +1 -1
- package/dist/agent-B2WU3JP6.cjs +0 -57
- package/dist/agent-DI2QQHSJ.js +0 -48
- package/dist/agent-session-store-EPUERCUU.cjs +0 -23
- package/dist/agent-session-store-TXP5ZVOD.js +0 -6
- package/dist/chunk-PBML2FHN.js.map +0 -1
- package/dist/chunk-QFFNMJAS.cjs.map +0 -1
- package/dist/chunk-TDDRPFO3.js.map +0 -1
- package/dist/chunk-ZHPET3LZ.cjs.map +0 -1
- package/dist/compact-session-FKYCJRAK.cjs +0 -56
- package/dist/compact-session-LF4YOT5S.js +0 -19
- package/dist/context-2G4375BZ.cjs +0 -22
- package/dist/context-GSQYVVMJ.js +0 -5
- package/dist/fs-session-store-MAZV5FXJ.cjs +0 -19
- package/dist/fs-session-store-ZNUYZHYI.js +0 -10
- package/dist/generate-object-6PWGH5DZ.cjs +0 -20
- package/dist/generate-object-IXMJ7FUX.js +0 -7
- package/dist/index-manager-34ELCOAP.cjs +0 -21
- package/dist/index-manager-OMJVYS2U.js +0 -12
- package/dist/judge-call-3QZO5H5N.js +0 -5
- package/dist/judge-call-J5EETJL3.cjs +0 -22
- package/dist/registry-KB3TS6BM.cjs +0 -46
- package/dist/registry-PPFMCM4A.js +0 -9
- package/dist/session-transcript-7FWTO4BK.js +0 -5
- package/dist/session-transcript-FYRKWRWV.cjs +0 -50
- package/dist/stream-object-3R3HZPQN.cjs +0 -20
- package/dist/stream-object-NPJXDZXO.js +0 -7
- package/dist/subagents-loader-5KQVYUNS.js +0 -6
- package/dist/subagents-loader-FDXPBBRA.cjs +0 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/session/agent-session.ts"],"names":[],"mappings":"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","file":"chunk-VK7SIDYI.js","sourcesContent":["import type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type PersistTurnInput,\n persistTurn,\n readSessionMessages,\n type TranscriptLocation,\n} from \"./agent-session-store.js\";\n\n/**\n * Per-agent conversation history kept across runs (and across `Agent.resume()`\n * within the same process). Lets the fixture responder recall prior facts when\n * the user asks a follow-up question.\n *\n * SE40 — disk persistence IS the native Claude-shaped `.jsonl` transcript. The\n * in-memory cache holds the narrowed `SessionMessage[]` (user/assistant text) so\n * a send can read `priorMessages` synchronously; the whole rich turn (user +\n * assistant + tool blocks) lands on disk once per send via {@link persistTurnToTranscript}\n * (fed by `run.conversation()`). Hydration on resume reconstructs the session by\n * walking the transcript DAG (`readSessionMessages`).\n *\n * @internal\n */\n\n// `SessionMessage` lives in `./session-types.ts` (leaf types file). Re-exported\n// for back-compat with downstream importers that pulled it from here.\nexport type { SessionMessage } from \"./session-types.js\";\n\nimport type { SessionMessage } from \"./session-types.js\";\n\n// M75 — the cache lives in a leaf; see session-cache.ts for the reason (cycle broken by extraction).\nexport {\n hydratedKeys,\n invalidateSessionCache,\n sessions,\n transcriptKey,\n} from \"./session-cache.js\";\n\nimport { diag } from \"../diagnostics.js\";\nimport { hydratedKeys, sessions, transcriptKey } from \"./session-cache.js\";\n\nconst pendingWrites = new Map<string, Promise<void>>();\nconst recordCounts = new Map<string, number>();\n\n/**\n * Append a session message to the in-memory cache only. Disk persistence for the\n * whole turn happens once per send via {@link persistTurnToTranscript}; the cache\n * feeds `priorMessages` / `onBeforeSend.previousMessageCount` synchronously.\n *\n * @internal\n */\nexport function appendSessionMessage(agentId: string, message: SessionMessage): void {\n const existing = sessions.get(agentId) ?? [];\n existing.push(message);\n // `delete` + `set` reinserts at the END: a JS `Map` preserves insertion order, so the first key\n // is always the least recently touched. That is the entire LRU, with no new structure (parsimony\n // rungs 2/5 — the ordering we need is already a language guarantee).\n sessions.delete(agentId);\n sessions.set(agentId, existing);\n enforceCeiling();\n}\n\n/**\n * Ceiling on sessions kept in memory.\n *\n * The runtime only reads the **active** session; the rest is pure cache, rebuildable from the\n * on-disk transcript. 32 is deliberately generous — the primary removal path is the explicit\n * `discardSession()` at the end of the agent's life, and this ceiling is a safety net against a\n * long-lived process running hundreds of sessions (plan risk #2: a tight ceiling could evict a\n * session still referenced by an in-flight async path).\n */\nexport const MAX_CACHED_SESSIONS = 32;\n\nfunction enforceCeiling(): void {\n while (sessions.size > MAX_CACHED_SESSIONS) {\n const oldest = sessions.keys().next().value;\n if (oldest === undefined) return;\n sessions.delete(oldest);\n forgetBookkeeping(oldest);\n }\n}\n\n/**\n * Erases the bookkeeping for an `agentId` across EVERY `cwd` it appears in.\n *\n * The three maps are keyed by `transcriptKey(cwd, agentId)`; `sessions` is the only one keyed by the\n * raw `agentId`. The ceiling's first version deleted from all three using `sessions`' key — that is,\n * deleted nothing — and left `hydratedKeys` **orphaned**. Since `hydrateSession` returns early when\n * the marker is present, an evicted session came back **empty** instead of rehydrating from disk:\n * silent amnesia, and a regression new to M95, because before it nothing evicted.\n *\n * The ceiling only knows the `agentId`, not the `cwd`, so it scans by suffix — which is the format\n * `transcriptKey` produces. A scan rather than an index because these maps hold tens of entries, not\n * thousands: a reverse index here would be new structure for a problem that does not exist.\n */\nfunction forgetBookkeeping(agentId: string): void {\n const suffix = transcriptKey(\"\", agentId).slice(0 - agentId.length - 2);\n for (const k of [...hydratedKeys]) if (k.endsWith(suffix)) hydratedKeys.delete(k);\n for (const k of [...pendingWrites.keys()]) if (k.endsWith(suffix)) pendingWrites.delete(k);\n for (const k of [...recordCounts.keys()]) if (k.endsWith(suffix)) recordCounts.delete(k);\n}\n\n/**\n * Erases the agent's module bookkeeping and returns how many entries were removed.\n *\n * M95 — `invalidateSessionCache` used to clear **two** of the four maps (`sessions`, `hydratedKeys`);\n * `pendingWrites` and `recordCounts` were never touched by id, so they grew for the life of the\n * process. Neither is large per entry — the leak is in count, not volume — but a cache with no owner\n * for removal is a cache that only grows.\n *\n * Returns the count so the caller can prove the removal; a second discard returns 0, which is what\n * makes the idempotency test possible without exposing the maps.\n */\nexport function discardSession(cwd: string, agentId: string): number {\n const key = transcriptKey(cwd, agentId);\n let removed = 0;\n // `sessions` is NOT erased here — and the distinction is measured, not aesthetic. It holds the\n // readable conversation, and there is a legitimate reader AFTER dispose: the golden\n // `two-concurrent-sends-serialize` calls `getSessionMessages(agentId)` after `agent.dispose()`.\n // Erasing it here returned an empty list and broke two goldens. What bounds it is the LRU ceiling\n // above; the three below are pure bookkeeping, with no post-dispose reader.\n //\n // The key is `transcriptKey(cwd, agentId)` in ALL THREE — not the raw `agentId`. The first version\n // erased two of them by `agentId` and therefore **never erased anything**; the test did not catch\n // it because it only asserted that the SECOND call returns 0, which is true either way.\n if (hydratedKeys.delete(key)) removed++;\n if (pendingWrites.delete(key)) removed++;\n if (recordCounts.delete(key)) removed++;\n return removed;\n}\n\nexport function getSessionMessages(agentId: string): SessionMessage[] {\n return sessions.get(agentId) ?? [];\n}\n\n/**\n * Persist a full conversation turn (user + assistant + tool blocks) to the native\n * transcript. Chained per-(agent, transcript) so on-disk order matches send order,\n * and fire-and-forget so `send()` is not blocked by disk I/O. Every\n * M50 — when the caller supplies `turn.autoCompact`, size-driven auto-compaction (usage real vs\n * the model's context window) runs in this same chain, surfaced via the optional `onCompact` observer.\n *\n * @internal\n */\nexport function persistTurnToTranscript(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n onCompact?: () => void,\n): void {\n const key = transcriptKey(loc.cwd, loc.agentId);\n // PRE-EXISTING debt, exposed when M75 fixed the Biome config that used to abort before\n // sweeping these files (a nested root under refactor/). It is not new code and was not touched\n // by M75; refactoring SDK internals without review would trade a visible problem for a diff\n // risky. Tracked in usetheodev/theokit-sdk#151.\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: see the reason just above\n const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {\n try {\n await persistTurn(store, loc, sessionId, turn);\n const count = (recordCounts.get(key) ?? 0) + 1;\n recordCounts.set(key, count);\n // M50 — the 50-turn no-summary boundary stub is GONE (it silently amnesia'd the session).\n // Auto-compaction is now size-driven with a real summary: see `maybeAutoCompact` below,\n // invoked in this same write chain by the post-run lifecycle when usage is known.\n if (turn.autoCompact !== undefined) {\n const { autoCompactIfNeeded } = await import(\"./compact-session.js\");\n const fired = await autoCompactIfNeeded({\n store,\n loc,\n sessionId,\n usageTotal: turn.autoCompact.usageTotal,\n contextWindow: turn.autoCompact.contextWindow,\n turnCount: count,\n summarize: turn.autoCompact.summarize,\n });\n if (fired) onCompact?.();\n }\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n diag(`[theokit-sdk] session transcript write failed (${loc.agentId}): ${msg}\\n`);\n }\n });\n pendingWrites.set(\n key,\n chained.then(\n () => undefined,\n () => undefined,\n ),\n );\n}\n\n/**\n * Load the persisted transcript into the in-memory cache. Idempotent per\n * (baseDir, cwd, agentId). Call once per agent lifecycle before the first read.\n *\n * @internal\n */\nexport async function hydrateSession(\n agentId: string,\n loc: { store: SessionStore; cwd: string },\n): Promise<void> {\n const key = transcriptKey(loc.cwd, agentId);\n if (hydratedKeys.has(key)) return;\n hydratedKeys.add(key);\n\n const persisted = await readSessionMessages(loc.store, agentId);\n if (persisted.length === 0) return;\n // M51 review F4 — the DISK is the source of truth at hydration time: after an invalidation\n // (compact/inject), an in-flight turn may have repopulated the cache with a SINGLE message before\n // this hydrate ran; the old \"skip when non-empty\" guard then pinned the parent to a 1-message\n // context (history + injected pair lost until restart). The persist chain serializes writes, so\n // the disk already contains that in-flight turn — replacing is always correct.\n sessions.set(agentId, persisted);\n}\n\n/**\n * Wait for all pending transcript writes to settle. Used by tests and by the\n * agent dispose path so on-disk state matches in-memory before the caller proceeds.\n *\n * @internal\n */\nexport async function flushSessionWrites(): Promise<void> {\n while (pendingWrites.size > 0) {\n const all = Array.from(pendingWrites.values());\n pendingWrites.clear();\n await Promise.all(all);\n }\n}\n\nexport function clearSession(agentId: string): void {\n sessions.delete(agentId);\n}\n\n/**\n * M50 review F1 — drop BOTH the message cache and the hydration marker so the NEXT send re-hydrates\n * from disk. Without this, a compaction only helps after a process restart: the live process keeps\n * sending the full pre-compact history (the cache is read synchronously by every send).\n */\n/**\n * M50 review F5 — run `fn` serialized on the SAME per-(cwd,agentId) write chain the per-turn\n * persistence uses, so a manual `Agent.compact` can never interleave with an in-flight turn's\n * writes (boundary landing mid-turn would orphan the turn from the replay).\n */\nexport function enqueueSessionWrite<T>(\n cwd: string,\n agentId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = transcriptKey(cwd, agentId);\n const prior = pendingWrites.get(key) ?? Promise.resolve();\n const result = prior.then(fn);\n pendingWrites.set(\n key,\n result.then(\n () => undefined,\n () => undefined,\n ),\n );\n return result;\n}\n\n/** Test-only: drop every cached session and hydration marker. @internal */\nexport function clearAllSessions(): void {\n sessions.clear();\n hydratedKeys.clear();\n recordCounts.clear();\n // M50 review F11 — the auto-compact attempt marks live on globalThis; tests reset them here.\n const g = globalThis as unknown as Record<symbol, Map<string, number>>;\n const sym = Symbol.for(\"theokit-sdk.session.auto-compact-attempts\");\n g[sym]?.clear();\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/session/agent-session.ts"],"names":[],"mappings":";;;;;AAwCA,IAAM,aAAA,uBAAoB,GAAA,EAA2B;AACrD,IAAM,YAAA,uBAAmB,GAAA,EAAoB;AAStC,SAAS,oBAAA,CAAqB,SAAiB,OAAA,EAA+B;AACnF,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,GAAA,CAAI,OAAO,KAAK,EAAC;AAC3C,EAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AAIrB,EAAA,QAAA,CAAS,OAAO,OAAO,CAAA;AACvB,EAAA,QAAA,CAAS,GAAA,CAAI,SAAS,QAAQ,CAAA;AAC9B,EAAA,cAAA,EAAe;AACjB;AAWO,IAAM,mBAAA,GAAsB,EAAA;AAEnC,SAAS,cAAA,GAAuB;AAC9B,EAAA,OAAO,QAAA,CAAS,OAAO,mBAAA,EAAqB;AAC1C,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AACtC,IAAA,IAAI,WAAW,MAAA,EAAW;AAC1B,IAAA,QAAA,CAAS,OAAO,MAAM,CAAA;AACtB,IAAA,iBAAA,CAAkB,MAAM,CAAA;AAAA,EAC1B;AACF;AAeA,SAAS,kBAAkB,OAAA,EAAuB;AAChD,EAAA,MAAM,MAAA,GAAS,cAAc,EAAA,EAAI,OAAO,EAAE,KAAA,CAAM,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA;AACtE,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,YAAY,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA;AAChF,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,aAAA,CAAc,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,aAAA,CAAc,OAAO,CAAC,CAAA;AACzF,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,YAAA,CAAa,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,YAAA,CAAa,OAAO,CAAC,CAAA;AACzF;AAaO,SAAS,cAAA,CAAe,KAAa,OAAA,EAAyB;AACnE,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,EAAK,OAAO,CAAA;AACtC,EAAA,IAAI,OAAA,GAAU,CAAA;AAUd,EAAA,IAAI,YAAA,CAAa,MAAA,CAAO,GAAG,CAAA,EAAG,OAAA,EAAA;AAC9B,EAAA,IAAI,aAAA,CAAc,MAAA,CAAO,GAAG,CAAA,EAAG,OAAA,EAAA;AAC/B,EAAA,IAAI,YAAA,CAAa,MAAA,CAAO,GAAG,CAAA,EAAG,OAAA,EAAA;AAC9B,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,mBAAmB,OAAA,EAAmC;AACpE,EAAA,OAAO,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,IAAK,EAAC;AACnC;AAWO,SAAS,uBAAA,CACd,KAAA,EACA,GAAA,EACA,SAAA,EACA,MACA,SAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,CAAI,GAAA,EAAK,IAAI,OAAO,CAAA;AAM9C,EAAA,MAAM,OAAA,GAAA,CAAW,cAAc,GAAA,CAAI,GAAG,KAAK,OAAA,CAAQ,OAAA,EAAQ,EAAG,IAAA,CAAK,YAAY;AAC7E,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,CAAY,KAAA,EAAO,GAAA,EAAK,SAAA,EAAW,IAAI,CAAA;AAC7C,MAAA,MAAM,KAAA,GAAA,CAAS,YAAA,CAAa,GAAA,CAAI,GAAG,KAAK,CAAA,IAAK,CAAA;AAC7C,MAAA,YAAA,CAAa,GAAA,CAAI,KAAK,KAAK,CAAA;AAI3B,MAAA,IAAI,IAAA,CAAK,gBAAgB,KAAA,CAAA,EAAW;AAClC,QAAA,MAAM,EAAE,mBAAA,EAAoB,GAAI,MAAM,OAAO,+BAAsB,CAAA;AACnE,QAAA,MAAM,KAAA,GAAQ,MAAM,mBAAA,CAAoB;AAAA,UACtC,KAAA;AAAA,UACA,GAAA;AAAA,UACA,SAAA;AAAA,UACA,UAAA,EAAY,KAAK,WAAA,CAAY,UAAA;AAAA,UAC7B,aAAA,EAAe,KAAK,WAAA,CAAY,aAAA;AAAA,UAChC,SAAA,EAAW,KAAA;AAAA,UACX,SAAA,EAAW,KAAK,WAAA,CAAY;AAAA,SAC7B,CAAA;AACD,QAAA,IAAI,OAAO,SAAA,IAAY;AAAA,MACzB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,MAAM,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACjE,MAAA,IAAA,CAAK,CAAA,+CAAA,EAAkD,GAAA,CAAI,OAAO,CAAA,GAAA,EAAM,GAAG;AAAA,CAAI,CAAA;AAAA,IACjF;AAAA,EACF,CAAC,CAAA;AACD,EAAA,aAAA,CAAc,GAAA;AAAA,IACZ,GAAA;AAAA,IACA,OAAA,CAAQ,IAAA;AAAA,MACN,MAAM,MAAA;AAAA,MACN,MAAM;AAAA;AACR,GACF;AACF;AAQA,eAAsB,cAAA,CACpB,SACA,GAAA,EACe;AACf,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,CAAI,GAAA,EAAK,OAAO,CAAA;AAC1C,EAAA,IAAI,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA,EAAG;AAC3B,EAAA,YAAA,CAAa,IAAI,GAAG,CAAA;AAEpB,EAAA,MAAM,SAAA,GAAY,MAAM,mBAAA,CAAoB,GAAA,CAAI,OAAO,OAAO,CAAA;AAC9D,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAM5B,EAAA,QAAA,CAAS,GAAA,CAAI,SAAS,SAAS,CAAA;AACjC;AAQA,eAAsB,kBAAA,GAAoC;AACxD,EAAA,OAAO,aAAA,CAAc,OAAO,CAAA,EAAG;AAC7B,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,IAAA,CAAK,aAAA,CAAc,QAAQ,CAAA;AAC7C,IAAA,aAAA,CAAc,KAAA,EAAM;AACpB,IAAA,MAAM,OAAA,CAAQ,IAAI,GAAG,CAAA;AAAA,EACvB;AACF;AAEO,SAAS,aAAa,OAAA,EAAuB;AAClD,EAAA,QAAA,CAAS,OAAO,OAAO,CAAA;AACzB;AAYO,SAAS,mBAAA,CACd,GAAA,EACA,OAAA,EACA,EAAA,EACY;AACZ,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,GAAA,EAAK,OAAO,CAAA;AACtC,EAAA,MAAM,QAAQ,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,IAAK,QAAQ,OAAA,EAAQ;AACxD,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAC5B,EAAA,aAAA,CAAc,GAAA;AAAA,IACZ,GAAA;AAAA,IACA,MAAA,CAAO,IAAA;AAAA,MACL,MAAM,MAAA;AAAA,MACN,MAAM;AAAA;AACR,GACF;AACA,EAAA,OAAO,MAAA;AACT","file":"chunk-SPZZSAHV.js","sourcesContent":["import type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type PersistTurnInput,\n persistTurn,\n readSessionMessages,\n type TranscriptLocation,\n} from \"./agent-session-store.js\";\n\n/**\n * Per-agent conversation history kept across runs (and across `Agent.resume()`\n * within the same process). Lets the fixture responder recall prior facts when\n * the user asks a follow-up question.\n *\n * SE40 — disk persistence IS the native Claude-shaped `.jsonl` transcript. The\n * in-memory cache holds the narrowed `SessionMessage[]` (user/assistant text) so\n * a send can read `priorMessages` synchronously; the whole rich turn (user +\n * assistant + tool blocks) lands on disk once per send via {@link persistTurnToTranscript}\n * (fed by `run.conversation()`). Hydration on resume reconstructs the session by\n * walking the transcript DAG (`readSessionMessages`).\n *\n * @internal\n */\n\n// `SessionMessage` lives in `./session-types.ts` (leaf types file). Re-exported\n// for back-compat with downstream importers that pulled it from here.\nexport type { SessionMessage } from \"./session-types.js\";\n\nimport type { SessionMessage } from \"./session-types.js\";\n\n// M75 — the cache lives in a leaf; see session-cache.ts for the reason (cycle broken by extraction).\nexport {\n hydratedKeys,\n invalidateSessionCache,\n sessions,\n transcriptKey,\n} from \"./session-cache.js\";\n\nimport { diag } from \"../diagnostics.js\";\nimport { hydratedKeys, sessions, transcriptKey } from \"./session-cache.js\";\n\nconst pendingWrites = new Map<string, Promise<void>>();\nconst recordCounts = new Map<string, number>();\n\n/**\n * Append a session message to the in-memory cache only. Disk persistence for the\n * whole turn happens once per send via {@link persistTurnToTranscript}; the cache\n * feeds `priorMessages` / `onBeforeSend.previousMessageCount` synchronously.\n *\n * @internal\n */\nexport function appendSessionMessage(agentId: string, message: SessionMessage): void {\n const existing = sessions.get(agentId) ?? [];\n existing.push(message);\n // `delete` + `set` reinserts at the END: a JS `Map` preserves insertion order, so the first key\n // is always the least recently touched. That is the entire LRU, with no new structure (parsimony\n // rungs 2/5 — the ordering we need is already a language guarantee).\n sessions.delete(agentId);\n sessions.set(agentId, existing);\n enforceCeiling();\n}\n\n/**\n * Ceiling on sessions kept in memory.\n *\n * The runtime only reads the **active** session; the rest is pure cache, rebuildable from the\n * on-disk transcript. 32 is deliberately generous — the primary removal path is the explicit\n * `discardSession()` at the end of the agent's life, and this ceiling is a safety net against a\n * long-lived process running hundreds of sessions (plan risk #2: a tight ceiling could evict a\n * session still referenced by an in-flight async path).\n */\nexport const MAX_CACHED_SESSIONS = 32;\n\nfunction enforceCeiling(): void {\n while (sessions.size > MAX_CACHED_SESSIONS) {\n const oldest = sessions.keys().next().value;\n if (oldest === undefined) return;\n sessions.delete(oldest);\n forgetBookkeeping(oldest);\n }\n}\n\n/**\n * Erases the bookkeeping for an `agentId` across EVERY `cwd` it appears in.\n *\n * The three maps are keyed by `transcriptKey(cwd, agentId)`; `sessions` is the only one keyed by the\n * raw `agentId`. The ceiling's first version deleted from all three using `sessions`' key — that is,\n * deleted nothing — and left `hydratedKeys` **orphaned**. Since `hydrateSession` returns early when\n * the marker is present, an evicted session came back **empty** instead of rehydrating from disk:\n * silent amnesia, and a regression new to M95, because before it nothing evicted.\n *\n * The ceiling only knows the `agentId`, not the `cwd`, so it scans by suffix — which is the format\n * `transcriptKey` produces. A scan rather than an index because these maps hold tens of entries, not\n * thousands: a reverse index here would be new structure for a problem that does not exist.\n */\nfunction forgetBookkeeping(agentId: string): void {\n const suffix = transcriptKey(\"\", agentId).slice(0 - agentId.length - 2);\n for (const k of [...hydratedKeys]) if (k.endsWith(suffix)) hydratedKeys.delete(k);\n for (const k of [...pendingWrites.keys()]) if (k.endsWith(suffix)) pendingWrites.delete(k);\n for (const k of [...recordCounts.keys()]) if (k.endsWith(suffix)) recordCounts.delete(k);\n}\n\n/**\n * Erases the agent's module bookkeeping and returns how many entries were removed.\n *\n * M95 — `invalidateSessionCache` used to clear **two** of the four maps (`sessions`, `hydratedKeys`);\n * `pendingWrites` and `recordCounts` were never touched by id, so they grew for the life of the\n * process. Neither is large per entry — the leak is in count, not volume — but a cache with no owner\n * for removal is a cache that only grows.\n *\n * Returns the count so the caller can prove the removal; a second discard returns 0, which is what\n * makes the idempotency test possible without exposing the maps.\n */\nexport function discardSession(cwd: string, agentId: string): number {\n const key = transcriptKey(cwd, agentId);\n let removed = 0;\n // `sessions` is NOT erased here — and the distinction is measured, not aesthetic. It holds the\n // readable conversation, and there is a legitimate reader AFTER dispose: the golden\n // `two-concurrent-sends-serialize` calls `getSessionMessages(agentId)` after `agent.dispose()`.\n // Erasing it here returned an empty list and broke two goldens. What bounds it is the LRU ceiling\n // above; the three below are pure bookkeeping, with no post-dispose reader.\n //\n // The key is `transcriptKey(cwd, agentId)` in ALL THREE — not the raw `agentId`. The first version\n // erased two of them by `agentId` and therefore **never erased anything**; the test did not catch\n // it because it only asserted that the SECOND call returns 0, which is true either way.\n if (hydratedKeys.delete(key)) removed++;\n if (pendingWrites.delete(key)) removed++;\n if (recordCounts.delete(key)) removed++;\n return removed;\n}\n\nexport function getSessionMessages(agentId: string): SessionMessage[] {\n return sessions.get(agentId) ?? [];\n}\n\n/**\n * Persist a full conversation turn (user + assistant + tool blocks) to the native\n * transcript. Chained per-(agent, transcript) so on-disk order matches send order,\n * and fire-and-forget so `send()` is not blocked by disk I/O. Every\n * M50 — when the caller supplies `turn.autoCompact`, size-driven auto-compaction (usage real vs\n * the model's context window) runs in this same chain, surfaced via the optional `onCompact` observer.\n *\n * @internal\n */\nexport function persistTurnToTranscript(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n onCompact?: () => void,\n): void {\n const key = transcriptKey(loc.cwd, loc.agentId);\n // PRE-EXISTING debt, exposed when M75 fixed the Biome config that used to abort before\n // sweeping these files (a nested root under refactor/). It is not new code and was not touched\n // by M75; refactoring SDK internals without review would trade a visible problem for a diff\n // risky. Tracked in usetheodev/theokit-sdk#151.\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: see the reason just above\n const chained = (pendingWrites.get(key) ?? Promise.resolve()).then(async () => {\n try {\n await persistTurn(store, loc, sessionId, turn);\n const count = (recordCounts.get(key) ?? 0) + 1;\n recordCounts.set(key, count);\n // M50 — the 50-turn no-summary boundary stub is GONE (it silently amnesia'd the session).\n // Auto-compaction is now size-driven with a real summary: see `maybeAutoCompact` below,\n // invoked in this same write chain by the post-run lifecycle when usage is known.\n if (turn.autoCompact !== undefined) {\n const { autoCompactIfNeeded } = await import(\"./compact-session.js\");\n const fired = await autoCompactIfNeeded({\n store,\n loc,\n sessionId,\n usageTotal: turn.autoCompact.usageTotal,\n contextWindow: turn.autoCompact.contextWindow,\n turnCount: count,\n summarize: turn.autoCompact.summarize,\n });\n if (fired) onCompact?.();\n }\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n diag(`[theokit-sdk] session transcript write failed (${loc.agentId}): ${msg}\\n`);\n }\n });\n pendingWrites.set(\n key,\n chained.then(\n () => undefined,\n () => undefined,\n ),\n );\n}\n\n/**\n * Load the persisted transcript into the in-memory cache. Idempotent per\n * (baseDir, cwd, agentId). Call once per agent lifecycle before the first read.\n *\n * @internal\n */\nexport async function hydrateSession(\n agentId: string,\n loc: { store: SessionStore; cwd: string },\n): Promise<void> {\n const key = transcriptKey(loc.cwd, agentId);\n if (hydratedKeys.has(key)) return;\n hydratedKeys.add(key);\n\n const persisted = await readSessionMessages(loc.store, agentId);\n if (persisted.length === 0) return;\n // M51 review F4 — the DISK is the source of truth at hydration time: after an invalidation\n // (compact/inject), an in-flight turn may have repopulated the cache with a SINGLE message before\n // this hydrate ran; the old \"skip when non-empty\" guard then pinned the parent to a 1-message\n // context (history + injected pair lost until restart). The persist chain serializes writes, so\n // the disk already contains that in-flight turn — replacing is always correct.\n sessions.set(agentId, persisted);\n}\n\n/**\n * Wait for all pending transcript writes to settle. Used by tests and by the\n * agent dispose path so on-disk state matches in-memory before the caller proceeds.\n *\n * @internal\n */\nexport async function flushSessionWrites(): Promise<void> {\n while (pendingWrites.size > 0) {\n const all = Array.from(pendingWrites.values());\n pendingWrites.clear();\n await Promise.all(all);\n }\n}\n\nexport function clearSession(agentId: string): void {\n sessions.delete(agentId);\n}\n\n/**\n * M50 review F1 — drop BOTH the message cache and the hydration marker so the NEXT send re-hydrates\n * from disk. Without this, a compaction only helps after a process restart: the live process keeps\n * sending the full pre-compact history (the cache is read synchronously by every send).\n */\n/**\n * M50 review F5 — run `fn` serialized on the SAME per-(cwd,agentId) write chain the per-turn\n * persistence uses, so a manual `Agent.compact` can never interleave with an in-flight turn's\n * writes (boundary landing mid-turn would orphan the turn from the replay).\n */\nexport function enqueueSessionWrite<T>(\n cwd: string,\n agentId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = transcriptKey(cwd, agentId);\n const prior = pendingWrites.get(key) ?? Promise.resolve();\n const result = prior.then(fn);\n pendingWrites.set(\n key,\n result.then(\n () => undefined,\n () => undefined,\n ),\n );\n return result;\n}\n\n/** Test-only: drop every cached session and hydration marker. @internal */\nexport function clearAllSessions(): void {\n sessions.clear();\n hydratedKeys.clear();\n recordCounts.clear();\n // M50 review F11 — the auto-compact attempt marks live on globalThis; tests reset them here.\n const g = globalThis as unknown as Record<symbol, Map<string, number>>;\n const sym = Symbol.for(\"theokit-sdk.session.auto-compact-attempts\");\n g[sym]?.clear();\n}\n"]}
|
|
@@ -154,5 +154,5 @@ exports.diag = diag;
|
|
|
154
154
|
exports.maskToken = maskToken;
|
|
155
155
|
exports.redactSecrets = redactSecrets;
|
|
156
156
|
exports.setDiagnosticsSink = setDiagnosticsSink;
|
|
157
|
-
//# sourceMappingURL=chunk-
|
|
158
|
-
//# sourceMappingURL=chunk-
|
|
157
|
+
//# sourceMappingURL=chunk-SXSPMRSV.cjs.map
|
|
158
|
+
//# sourceMappingURL=chunk-SXSPMRSV.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/diagnostics.ts","../src/internal/security/redact.ts"],"names":[],"mappings":"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","file":"chunk-SXSPMRSV.cjs","sourcesContent":["/**\n * The library's single diagnostics channel — silent by default (#147).\n *\n * ## The problem\n *\n * The SDK wrote diagnostics straight to `process.stderr` from hot paths — 92 sites across 51\n * files under `internal/`. In a TUI host (Ink, alternate screen), those writes interleave with the\n * render and **corrupt the frame**. And the host had no way to intercept them: there was no\n * injectable logger. One consumer went as far as installing `proper-lockfile` just to silence ONE\n * of them.\n *\n * A library cannot assume `stdout`/`stderr` are free sinks. The application owns the terminal, not\n * the dependency.\n *\n * ## The contract\n *\n * - **`setDiagnosticsSink(fn)`** hands the messages to the application, which decides where to put\n * them (a status line, a file, a panel). It is what was missing for a TUI to coexist with the SDK.\n * - **With no sink, nothing is emitted.** A library does not own the host's terminal.\n *\n * ## Silent by default, and how 36 suites survived the flip\n *\n * The default WAS `stderr`, and 36 test files spy on `process.stderr.write` to assert a given\n * warning is emitted. That contract is real and had to survive; migrating all 36 by hand, across\n * four different spy styles, is how you weaken 36 suites in one commit.\n *\n * So the flip happened at the default, and `vitest.setup.ts` installs a sink that FORWARDS to\n * `stderr` for the duration of every test. Those assertions still test what they always tested —\n * \"this condition emits a diagnostic\" — through the channel a host would use, while production\n * emits nothing unless asked.\n *\n * The cost is stated rather than hidden: no test observes the production default by accident, so\n * `tests/diagnostics-public-entry.test.ts` pins it explicitly by clearing the sink first.\n *\n * ## Coverage is the whole guarantee\n *\n * The first sweep migrated the 92 sites under `internal/` and left `src/`'s own modules —\n * `event-bus.ts`, `batch.ts`, `compaction.ts`, `internal/workflow/step-branch.ts`. A host could\n * therefore install a sink and still have its frame corrupted by a batch run, which is the reported\n * defect with a smaller blast radius. Those are routed here too, and\n * `tests/lint/no-direct-terminal-write.test.ts` is what keeps the next hot path from\n * reintroducing one. \"Mostly interceptable\" is not interceptable.\n *\n * The remaining direct writers are allowlisted there with a reason, and every one of them is a seam\n * whose destination the CALLER already chooses (`opts.warn`, `opts.logger`, the Workflow logger).\n *\n * ## What this is NOT\n *\n * It is not a logger with levels, formatting or multiple destinations. It is the minimum that\n * resolves the reported blocker; a full logger here would be inventing a requirement nobody asked\n * for.\n */\n\n/** Receives each diagnostic message already formatted, with the trailing `\\n`. */\nexport type DiagnosticsSink = (message: string) => void;\n\n/**\n * The registry lives on `globalThis`, not in a module-level `let` (theokit#173).\n *\n * A module-level binding is a per-INSTANCE singleton, and a package manager will install two\n * physical copies of the same version whenever two dependents resolve different peer sets. Measured:\n * `@theokit/sdk@4.39.1` existed under two pnpm store hashes in the theokit workspace, so a sink\n * installed through `@theokit/agents` landed in a different registry than the emitter writes to.\n *\n * The failure mode is the quiet one — the re-export resolves, the function is callable, nothing\n * throws, and no diagnostic ever arrives. `Symbol.for` gives every copy in the process the same\n * slot; the `v1` suffix leaves room to change the shape without colliding with an older copy.\n */\nconst SINK_SLOT = Symbol.for(\"theokit.sdk.diagnostics.sink.v1\");\n\ntype SinkHolder = Record<symbol, DiagnosticsSink | undefined>;\n\nfunction currentSink(): DiagnosticsSink | undefined {\n return (globalThis as unknown as SinkHolder)[SINK_SLOT];\n}\n\n/**\n * Installs (or removes, by passing `undefined`) the diagnostics destination.\n *\n * When a sink is present it is the ONLY destination — `stderr` gets no copy. Duplicating\n * destinations would hand the problem back to the TUI that installed the sink precisely to get the\n * messages out of the terminal.\n */\nexport function setDiagnosticsSink(next: DiagnosticsSink | undefined): void {\n (globalThis as unknown as SinkHolder)[SINK_SLOT] = next;\n}\n\n/**\n * Emits a library diagnostic message.\n *\n * Replaces `process.stderr.write` on internal paths. Never throws: a faulty sink must not\n * must not take down the run it merely observes.\n */\nexport function diag(message: string): void {\n // Silent by default (#147): with no sink installed the message is dropped. A library must not\n // assume the host's stdout/stderr are free-form log sinks — in a TUI they are the render surface.\n const sink = currentSink();\n if (sink === undefined) return;\n try {\n sink(message);\n } catch {\n // Observability never breaks the run — same principle as `emitRunEvent`.\n }\n}\n\n/**\n * Emits a diagnostic that reports a USER-VISIBLE FAILURE, and is never silently dropped.\n *\n * `diag` is silent with no sink installed, and that is right for chatter: a library must not assume\n * the host's stderr is a free-form log, because in a TUI it is the render surface. A failure is a\n * different message. `theokit-sdk#189` is the record of the difference — an MCP server failed to\n * start, the only report went to `diag()`, the embedding UI never read it, and the user saw an\n * agent with missing tools and no reason given.\n *\n * The two failure modes are not symmetric, which is the whole decision: a corrupted frame is\n * visible and recoverable, while a silently dropped failure is neither. So this falls back to\n * stderr rather than to silence.\n *\n * A sink still takes precedence — the host installed it precisely to keep these off the terminal —\n * EXCEPT when the sink throws. A broken sink swallowing the one report of a failure is the same\n * defect one layer further in.\n *\n * @internal\n */\nexport function diagFailure(message: string): void {\n const sink = currentSink();\n if (sink !== undefined) {\n try {\n sink(message);\n return;\n } catch {\n // Fall through: the sink is broken, and this message is too important to drop with it.\n }\n }\n try {\n process.stderr.write(`${message}\\n`);\n } catch {\n // Nothing left to try. Observability never breaks the run.\n }\n}\n","import { diag } from \"../diagnostics.js\";\n\n/**\n * Canonical secret redaction module (ADRs D68-D73).\n *\n * Single source of truth for credential pattern masking across the SDK.\n * Wired at output boundaries: `ErrorMetadata.raw` (mappers/shared.ts),\n * telemetry span attributes (telemetry/tracer.ts), transcript JSONL\n * appends (agent-session-store.ts), migration logger output\n * (memory/migrate-sqlite-to-lance.ts).\n *\n * - D68: central module, single source of truth (replaces 2 duplicates)\n * - D69: env snapshot at module init (prompt-injection defense)\n * - D70: ON by default, warn on opt-out\n * - D71: two-bucket masking — short fully masked, long preserves prefix+suffix\n * - D72: `codeFile` opt-out for legitimate prefix-shaped content\n * - D73: redact at OUTPUT boundaries, not at storage\n *\n * @internal\n */\n\n// D69: env snapshot captured at module load. Subsequent mutations of\n// process.env.THEOKIT_REDACT_SECRETS are ignored — defends against\n// prompt injection that tries to disable redaction mid-run.\nlet REDACT_ENABLED: boolean = readEnvOnce();\n\nfunction readEnvOnce(): boolean {\n const raw = process.env.THEOKIT_REDACT_SECRETS;\n if (raw === undefined) return true; // D70: default ON\n return [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\n// D70: warn once on opt-out so the user knows they're vulnerable.\nlet warnedOptOut = false;\nif (!REDACT_ENABLED && !warnedOptOut) {\n diag(\n \"[theokit-sdk] Secret redaction is DISABLED via THEOKIT_REDACT_SECRETS. \" +\n \"Credentials may leak into errors, telemetry, logs, transcripts.\\n\",\n );\n warnedOptOut = true;\n}\n\n/**\n * Built-in credential patterns. Order matters — more specific prefixes\n * must come before generic ones (e.g., `sk-ant-` before `sk-`). Quantifiers\n * are all bounded `{n,m}` or applied to char classes — linear time, no ReDoS.\n *\n * @internal\n */\nconst BUILTIN_PATTERNS: readonly RegExp[] = [\n // T5.4: 30+ vendor prefixes (was 12 pre-T5.4). Order matters — more\n // specific prefixes precede generic ones (e.g., sk-ant-admin01 before\n // sk-ant-, sk-proj- before sk-). PEM block deliberately first so its\n // multi-line span runs before any per-line patterns can fire.\n /-----BEGIN[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\\s\\S]+?-----END[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g,\n // JWT — exact 3-segment base64url. Dotted; the body floor of 4 chars per\n // segment matches the minimum legal payload while skipping `a.b.c` noise.\n /eyJ[A-Za-z0-9_-]{4,}\\.eyJ[A-Za-z0-9_-]{4,}\\.[A-Za-z0-9_-]{4,}/g,\n // Azure Storage SAS — match the sig= component (URL-encoded base64).\n /(?<=[?&]sig=)[A-Za-z0-9%+/]{20,}/g,\n // Anthropic\n /sk-ant-admin01-[A-Za-z0-9_-]{10,}/g, // Anthropic admin keys (must precede sk-ant-)\n /sk-ant-[A-Za-z0-9_-]{10,}/g, // Anthropic regular\n // OpenAI family + clones (sk- generic must come AFTER all sk-foo- variants)\n /sk-proj-[A-Za-z0-9_-]{10,}/g, // OpenAI project key (must precede sk- generic)\n /sk-[A-Za-z0-9_-]{10,}/g, // OpenAI / OpenRouter / DeepInfra / Together / DeepSeek\n // Provider prefixes (alphabetized for maintainability)\n /AIza[A-Za-z0-9_-]{35}/g, // Google API key\n /AKIA[A-Z0-9]{16}/g, // AWS access key\n /fw_[A-Za-z0-9]{20,}/g, // Fireworks\n /glpat-[A-Za-z0-9_-]{20}/g, // GitLab PAT\n /ghp_[A-Za-z0-9]{36}/g, // GitHub PAT classic\n /github_pat_[A-Za-z0-9_]{82}/g, // GitHub PAT fine-grained\n /gsk_[A-Za-z0-9]{20,}/g, // Groq\n /hf_[A-Za-z0-9]{20,}/g, // HuggingFace\n /\\bpa-[A-Za-z0-9_-]{20,}/g, // Voyage AI (word-boundary to skip CSS / kebab IDs)\n /pcsk_[A-Za-z0-9_-]{20,}/g, // Pinecone\n /pplx-[A-Za-z0-9_-]{20,}/g, // Perplexity\n /r8_[A-Za-z0-9_-]{20,}/g, // Replicate\n /rk_live_[A-Za-z0-9]{20,}/g, // Stripe restricted\n /sk_live_[A-Za-z0-9]{20,}/g, // Stripe secret\n /sntrys_[A-Za-z0-9]{40,}/g, // Sentry user auth\n /xai-[A-Za-z0-9_-]{20,}/g, // xAI (Grok)\n /xox[bpasr]-[A-Za-z0-9-]{10,}/g, //Slack tokens\n // Additional unique-prefix tokens with low false-positive risk\n /npm_[A-Za-z0-9]{36}/g, // npm access token\n /SG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}/g, // SendGrid\n /\\bSK[A-Za-z0-9]{32}\\b/g, // Twilio API SID (word-boundary to skip CSS class noise)\n /\\bkey-[a-f0-9]{32}\\b/g, // Mailgun (hex-only narrows false positives)\n /MT[A-Za-z0-9_-]{23}\\.[A-Za-z0-9_-]{6}\\.[A-Za-z0-9_-]{27}/g, // Discord bot\n /\\b(?:sdk|mob)-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\b/g, // LaunchDarkly\n];\n\n// `Bearer <token>` matched as its own first-class pattern so PARAM_PATTERN\n// doesn't have to handle the unusual `Authorization: Bearer xxx` shape\n// (no `:` or `=` between \"Bearer\" and the value — bare whitespace).\nconst BEARER_PATTERN = /\\b(Bearer\\s+)([A-Za-z0-9_\\-.+/=]{8,})/g;\n\n// Parametric: matches `key=value` and `key: value` (with optional quote\n// between the key and the separator, to handle JSON: `\"api_key\": \"...\"`)\n// in URLs, query strings, JSON-like bodies, HTTP headers. Captures the\n// prefix so we keep it visible while masking the value.\n//\n// `authorization` deliberately excluded — BEARER_PATTERN handles the\n// common `Authorization: Bearer xxx` shape. Including it here causes\n// double-masking (\"Authorization: *** ***\") after Bearer fires.\n// T5.4: keyword set expanded from 6 → 16 to cover the OAuth / JWT / generic\n// credential vocabulary surfaced by DR6 finding #4. `authorization`,\n// `auth`, `bearer` stay excluded — BEARER_PATTERN handles the\n// `Authorization: Bearer xxx` shape and including these here would\n// re-catch the post-BUILTIN-masked form (D71 prefix-preservation\n// contract) and double-mask to `***`.\n//\n// Value class includes `.` so JWT / `.env` / dotted base64url values\n// match; the callback skips already-masked values (containing the\n// `...` D71 separator) to preserve the BUILTIN prefix-mask result.\nconst PARAM_PATTERN =\n /(\\b(?:access_token|api_key|api-key|client_secret|credential|credentials|id_token|jwt|password|private_key|refresh_token|secret|service_account|session_token|token|x-api-key)\\b[\"']?\\s*[:=]\\s*[\"']?)([A-Za-z0-9_\\-.+/]+)/gi;\n\nconst _extraPatterns: RegExp[] = [];\n\n/**\n * Add a user-defined redaction pattern. Additive — never removes builtins.\n * Throws if the regex lacks the `/g` flag (without `/g`, `.replace` only\n * substitutes the first match and the rest leaks).\n *\n * @internal — exposed publicly via `Security.addPattern` in `src/security.ts`.\n */\nexport function addPattern(re: RegExp): void {\n if (!re.global) {\n throw new Error(\"Security.addPattern: regex must have /g flag for replace-all semantics\");\n }\n _extraPatterns.push(re);\n}\n\n/**\n * Two-bucket masking (D71):\n * - tokens shorter than 18 chars → fully masked as `***`\n * - tokens >= 18 chars → keep first 6 + `...` + last 4\n *\n * Rationale: long tokens are unique per-account; prefix+suffix preserves\n * debuggability without revealing the secret middle.\n *\n * @internal\n */\nexport function maskToken(token: string): string {\n if (token.length < 18) return \"***\";\n return `${token.slice(0, 6)}...${token.slice(-4)}`;\n}\n\n// issue #117 — the EXACT shape of a `maskToken` output (`slice(0,6)+\"...\"+slice(-4)`\n// = 6 chars, literal `...`, 4 chars). Used to detect a value PARAM_PATTERN would\n// otherwise re-mask, WITHOUT skipping a raw secret that merely contains `...`.\nconst MASK_SHAPE = /^.{6}\\.\\.\\..{4}$/s;\n\n/**\n * Redact known credential patterns from `text`. Default behavior masks\n * builtins + extras + parametric `key=value` sinks.\n *\n * With `{ codeFile: true }` (D72), skips PARAM_PATTERN to avoid mangling\n * `.env.example`, schema JSON, or test fixtures that legitimately contain\n * prefix-like strings.\n *\n * Returns the redacted string. Coerces non-strings via JSON.stringify;\n * EC-7 fix (edge-case review): wraps in try/catch so circular references\n * never propagate — returns sentinel `\"[unredactable: circular]\"`.\n *\n * @internal\n */\n// Coerce arbitrary input to a string for redaction. Returns `null`\n// sentinel when the value is null/undefined/non-stringifiable, so the\n// caller can short-circuit with `\"\"`. EC-7 fix: circular refs go through\n// the try/catch and produce the sentinel marker, never throwing.\nfunction coerceToString(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (value === null || value === undefined) return null;\n if (typeof value === \"object\") {\n try {\n const s = JSON.stringify(value);\n return s === undefined ? null : s;\n } catch {\n return \"[unredactable: circular]\";\n }\n }\n return String(value);\n}\n\nexport function redactSecrets(text: unknown, opts?: { codeFile?: boolean }): string {\n const coerced = coerceToString(text);\n if (coerced === null) return \"\";\n if (!REDACT_ENABLED) return coerced;\n\n let s = coerced;\n for (const re of BUILTIN_PATTERNS) {\n s = s.replace(re, (m) => maskToken(m));\n }\n for (const re of _extraPatterns) {\n s = s.replace(re, (m) => maskToken(m));\n }\n if (!opts?.codeFile) {\n // Bearer first (preserves \"Bearer \" prefix, masks the token after).\n // Must run before PARAM_PATTERN so the bare-whitespace shape doesn't\n // get mis-handled as a value.\n s = s.replace(BEARER_PATTERN, (_, prefix: string) => `${prefix}***`);\n // T5.4: skip if value already contains the D71 bucket-mask separator\n // (`...`) — BUILTIN ran first and produced a prefix-preserved mask;\n // re-masking would lose the prefix and degrade debuggability.\n s = s.replace(PARAM_PATTERN, (whole, prefix: string, value: string) => {\n // issue #117 — skip ONLY when the value is already a mask that a BUILTIN\n // pattern produced (maskToken's exact `6chars...4chars` shape), so we don't\n // re-mask and lose the prefix. The old `value.includes(\"...\")` was too broad:\n // a REAL secret that happens to contain `...` (e.g. `L_-cxw-.2UI_..._`) was\n // skipped and LEAKED. The mask shape is exact (maskToken: slice(0,6)+\"...\"+\n // slice(-4)), so a raw secret with `...` elsewhere is now masked.\n if (MASK_SHAPE.test(value)) return whole;\n return `${prefix}***`;\n });\n }\n return s;\n}\n\n/**\n * Test-only helper exported for `_test-reset.ts`. NOT included in the\n * `index.ts` barrel — vitest setup imports the dedicated module via\n * explicit path to discourage production callers.\n *\n * @internal\n */\nexport function _resetForTests(opts: { enabled?: boolean; clearExtras?: boolean }): void {\n if (opts.enabled !== undefined) REDACT_ENABLED = opts.enabled;\n if (opts.clearExtras === true) _extraPatterns.length = 0;\n}\n\n/**\n * T5.4 — Test-only count of BUILTIN_PATTERNS. Exposed so the count-floor\n * assertion can run without re-deriving the array shape in test land.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__BUILTIN_PATTERN_COUNT(): number {\n return BUILTIN_PATTERNS.length;\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk2YGIBOUH_cjs = require('./chunk-2YGIBOUH.cjs');
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var os = require('os');
|
|
6
6
|
|
|
7
|
-
var SessionBusyError = class extends
|
|
7
|
+
var SessionBusyError = class extends chunk2YGIBOUH_cjs.TheokitAgentError {
|
|
8
8
|
constructor(sessionPath) {
|
|
9
9
|
super(
|
|
10
10
|
`another process is already writing this session: ${sessionPath}. Fork it to a new id instead of appending \u2014 two writers interleave lines into a sequence that parses but is not what either process wrote.`,
|
|
@@ -107,5 +107,5 @@ function createLease(sessionPath, lockPath) {
|
|
|
107
107
|
exports.SessionBusyError = SessionBusyError;
|
|
108
108
|
exports.acquireSessionWriter = acquireSessionWriter;
|
|
109
109
|
exports.sessionHasWriter = sessionHasWriter;
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
110
|
+
//# sourceMappingURL=chunk-T6QUCG7L.cjs.map
|
|
111
|
+
//# sourceMappingURL=chunk-T6QUCG7L.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","hostname","openSync","writeSync","fchmodSync","closeSync","readFileSync","existsSync","rmSync"],"mappings":";;;;;;AAmDO,IAAM,gBAAA,GAAN,cAA+BA,mCAAA,CAAkB;AAAA,EAGtD,YAAqB,WAAA,EAAqB;AACxC,IAAA,KAAA;AAAA,MACE,oDAAoD,WAAW,CAAA,gJAAA,CAAA;AAAA,MAG/D,EAAE,IAAA,EAAM,cAAA,EAAgB,WAAA,EAAa,KAAA;AAAM,KAC7C;AANmB,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAOrB;AAAA,EAPqB,WAAA;AAAA,EAFH,IAAA,GAAO,kBAAA;AAU3B;AAiDO,IAAM,mBAAA,GAAsB,GAAA;AAUnC,SAAS,QAAA,GAAsB;AAC7B,EAAA,OAAO,EAAE,GAAA,EAAK,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAUC,aAAS,EAAG,KAAA,EAAO,IAAA,CAAK,GAAA,EAAI,EAAE;AACrE;AAWA,SAAS,UAAA,CAAW,UAAkB,KAAA,EAAwB;AAC5D,EAAA,MAAM,EAAA,GAAKC,WAAA,CAAS,QAAA,EAAU,GAAA,EAAK,GAAK,CAAA;AACxC,EAAA,IAAI;AACF,IAAAC,YAAA,CAAU,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAInC,IAAAC,aAAA,CAAW,IAAI,GAAK,CAAA;AAAA,EACtB,CAAA,SAAE;AACA,IAAAC,YAAA,CAAU,EAAE,CAAA;AAAA,EACd;AACF;AAGA,SAAS,UAAU,QAAA,EAAyC;AAC1D,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAMC,eAAA,CAAa,UAAU,MAAM,CAAA;AAAA,EACrC,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAQ,GAAA,CAA8B,IAAA;AAE5C,IAAA,IAAI,IAAA,KAAS,UAAU,OAAO,MAAA;AAO9B,IAAA,IAAI,IAAA,KAAS,UAAU,OAAO,MAAA;AAQ9B,IAAA,MAAM,IAAI,gBAAA,CAAiB,QAAA,CAAS,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAA;AAAA,EACpE;AACA,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,IACE,OAAO,CAAA,CAAE,GAAA,KAAQ,QAAA,IACjB,OAAO,CAAA,CAAE,QAAA,KAAa,QAAA,IACtB,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,EACnB;AACA,MAAA,OAAO,KAAA,CAAA;AAAA,IACT;AACA,IAAA,OAAO,EAAE,KAAK,CAAA,CAAE,GAAA,EAAK,UAAU,CAAA,CAAE,QAAA,EAAU,KAAA,EAAO,CAAA,CAAE,KAAA,EAAM;AAAA,EAC5D,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,aAAa,GAAA,EAAsB;AAC1C,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,GAAA,EAAK;AAEZ,IAAA,OAAQ,IAA8B,IAAA,KAAS,OAAA;AAAA,EACjD;AACF;AAwBA,SAAS,YAAY,KAAA,EAAuC;AAC1D,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,IAAA;AAChC,EAAA,IAAI,KAAA,CAAM,QAAA,KAAaL,WAAA,EAAS,EAAG;AACjC,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,KAAA,GAAQ,mBAAA;AAAA,EACpC;AACA,EAAA,OAAO,CAAC,YAAA,CAAa,KAAA,CAAM,GAAG,CAAA;AAChC;AAeO,SAAS,iBAAiB,WAAA,EAA8B;AAC7D,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,IAAI,CAACM,aAAA,CAAW,QAAQ,CAAA,EAAG,OAAO,KAAA;AAClC,EAAA,IAAI;AACF,IAAA,OAAO,CAAC,WAAA,CAAY,SAAA,CAAU,QAAQ,CAAC,CAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,eAAsB,qBAAqB,WAAA,EAAkD;AAU3F,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,MAAM,OAAkB,QAAA,EAAS;AACjC,EAAA,IAAI,EAAA;AACJ,EAAA,IAAI;AAEF,IAAA,EAAA,GAAKL,WAAA,CAAS,QAAA,EAAU,IAAA,EAAM,GAAK,CAAA;AAAA,EACrC,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAA8B,IAAA,KAAS,QAAA,EAAU,MAAM,GAAA;AAI5D,IAAA,IAAI,CAAC,YAAY,SAAA,CAAU,QAAQ,CAAC,CAAA,EAAG,MAAM,IAAI,gBAAA,CAAiB,WAAW,CAAA;AAC7E,IAAA,UAAA,CAAW,UAAU,IAAI,CAAA;AACzB,IAAA,OAAO,WAAA,CAAY,aAAa,QAAQ,CAAA;AAAA,EAC1C;AACA,EAAAG,YAAA,CAAU,EAAE,CAAA;AACZ,EAAA,UAAA,CAAW,UAAU,IAAI,CAAA;AAEzB,EAAA,OAAO,WAAA,CAAY,aAAa,QAAQ,CAAA;AAC1C;AAGA,SAAS,WAAA,CAAY,aAAqB,QAAA,EAAsC;AAC9E,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,SAAS,YAA2B;AAClC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,GAAW,IAAA;AACX,MAAAG,SAAA,CAAO,QAAA,EAAU,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAClC,CAAA;AAAA,IACA,OAAO,MAAY;AAIjB,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,UAAA,CAAW,QAAA,EAAU,UAAU,CAAA;AAAA,IACjC;AAAA,GACF;AACF","file":"chunk-JA4FEA5Z.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n * @internal\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","hostname","openSync","writeSync","fchmodSync","closeSync","readFileSync","existsSync","rmSync"],"mappings":"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","file":"chunk-T6QUCG7L.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n * @internal\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkXK54DTRF_cjs = require('./chunk-XK54DTRF.cjs');
|
|
4
|
+
var chunkSXSPMRSV_cjs = require('./chunk-SXSPMRSV.cjs');
|
|
5
5
|
var fs = require('fs');
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var url = require('url');
|
|
@@ -25,14 +25,14 @@ var REGISTRY = globalSingleton(
|
|
|
25
25
|
var ALIASES = globalSingleton("theokit-sdk.providers.aliases", () => /* @__PURE__ */ new Map());
|
|
26
26
|
function registerProvider(profile) {
|
|
27
27
|
if (REGISTRY.has(profile.name)) {
|
|
28
|
-
|
|
28
|
+
chunkSXSPMRSV_cjs.diag(`[theokit-sdk] Provider "${profile.name}" overridden by user plugin.
|
|
29
29
|
`);
|
|
30
30
|
}
|
|
31
31
|
REGISTRY.set(profile.name, profile);
|
|
32
32
|
for (const alias of profile.aliases ?? []) {
|
|
33
33
|
const previous = ALIASES.get(alias);
|
|
34
34
|
if (previous !== void 0 && previous !== profile.name) {
|
|
35
|
-
|
|
35
|
+
chunkSXSPMRSV_cjs.diag(`[theokit-sdk] Alias "${alias}" collision: was "${previous}", now "${profile.name}".
|
|
36
36
|
`);
|
|
37
37
|
}
|
|
38
38
|
ALIASES.set(alias, profile.name);
|
|
@@ -80,7 +80,7 @@ var catalogModelSchema = zod.z.object({
|
|
|
80
80
|
}).loose();
|
|
81
81
|
|
|
82
82
|
// src/internal/providers/catalog-loader.ts
|
|
83
|
-
var __dirname_resolved = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-
|
|
83
|
+
var __dirname_resolved = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-T6ZCG5AP.cjs', document.baseURI).href))));
|
|
84
84
|
var modelInfoIndex = globalSingleton(
|
|
85
85
|
"theokit-sdk.providers.model-info-index",
|
|
86
86
|
() => /* @__PURE__ */ new Map()
|
|
@@ -114,7 +114,7 @@ function ensureModelIndexLoaded() {
|
|
|
114
114
|
indexEntryModels(entry);
|
|
115
115
|
}
|
|
116
116
|
} catch (err) {
|
|
117
|
-
|
|
117
|
+
chunkSXSPMRSV_cjs.diag(
|
|
118
118
|
`[theokit-sdk] WARN: provider catalog unavailable (${err.message}) \u2014 per-model data disabled
|
|
119
119
|
`
|
|
120
120
|
);
|
|
@@ -125,7 +125,7 @@ function indexEntryModels(entry) {
|
|
|
125
125
|
for (const [modelId, raw] of Object.entries(entry.models)) {
|
|
126
126
|
const parsed = catalogModelSchema.safeParse(raw);
|
|
127
127
|
if (!parsed.success) {
|
|
128
|
-
|
|
128
|
+
chunkSXSPMRSV_cjs.diag(
|
|
129
129
|
`[theokit-sdk] WARN: Skipping malformed catalog model "${entry.id}/${modelId}": ${parsed.error.issues[0]?.message ?? "invalid"}
|
|
130
130
|
`
|
|
131
131
|
);
|
|
@@ -158,7 +158,7 @@ function loadProviderCatalog(opts) {
|
|
|
158
158
|
for (const raw of entries) {
|
|
159
159
|
const validated = validateEntry(raw);
|
|
160
160
|
if (validated === null) {
|
|
161
|
-
|
|
161
|
+
chunkSXSPMRSV_cjs.diag(
|
|
162
162
|
`[theokit-sdk] WARN: Skipping malformed catalog entry: ${JSON.stringify(raw).slice(0, 100)}
|
|
163
163
|
`
|
|
164
164
|
);
|
|
@@ -396,7 +396,7 @@ var OPENAI_OAUTH_CONFIG = {
|
|
|
396
396
|
function codexFetch() {
|
|
397
397
|
return (async (input, init) => {
|
|
398
398
|
const env = process.env;
|
|
399
|
-
const resolved = await
|
|
399
|
+
const resolved = await chunkXK54DTRF_cjs.resolveCredential({
|
|
400
400
|
provider: "openai",
|
|
401
401
|
store: DEFAULT_STORE,
|
|
402
402
|
oauth: OPENAI_OAUTH_CONFIG,
|
|
@@ -407,7 +407,7 @@ function codexFetch() {
|
|
|
407
407
|
'openai-chatgpt: no ChatGPT credential found \u2014 run the OpenAI device login (e.g. "/login openai") first.'
|
|
408
408
|
);
|
|
409
409
|
}
|
|
410
|
-
const accountId =
|
|
410
|
+
const accountId = chunkXK54DTRF_cjs.readStoredOAuth(DEFAULT_STORE, env)?.account_id;
|
|
411
411
|
const headers = new Headers(init?.headers);
|
|
412
412
|
headers.set("authorization", `Bearer ${resolved.apiKey}`);
|
|
413
413
|
if (accountId !== void 0) headers.set("ChatGPT-Account-Id", accountId);
|
|
@@ -565,5 +565,5 @@ exports.resolveBedrockBaseUrl = resolveBedrockBaseUrl;
|
|
|
565
565
|
exports.resolveVertexBaseUrl = resolveVertexBaseUrl;
|
|
566
566
|
exports.stripBedrockPrefix = stripBedrockPrefix;
|
|
567
567
|
exports.stripVertexPrefix = stripVertexPrefix;
|
|
568
|
-
//# sourceMappingURL=chunk-
|
|
569
|
-
//# sourceMappingURL=chunk-
|
|
568
|
+
//# sourceMappingURL=chunk-T6ZCG5AP.cjs.map
|
|
569
|
+
//# sourceMappingURL=chunk-T6ZCG5AP.cjs.map
|