@theokit/sdk 5.15.1 → 5.15.2
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 +6 -0
- package/dist/a2a/index.cjs +3 -3
- package/dist/a2a/index.js +1 -1
- package/dist/agent-D6WBBNGR.cjs +72 -0
- package/dist/{agent-CB27KF6O.cjs.map → agent-D6WBBNGR.cjs.map} +1 -1
- package/dist/agent-QRIFX2SY.js +63 -0
- package/dist/{agent-66U5QCPF.js.map → agent-QRIFX2SY.js.map} +1 -1
- package/dist/agent-session-store-2LQEPS4H.js +6 -0
- package/dist/{agent-session-store-22GKA7O4.js.map → agent-session-store-2LQEPS4H.js.map} +1 -1
- package/dist/agent-session-store-IOH6BREF.cjs +23 -0
- package/dist/{agent-session-store-WP6NGDVX.cjs.map → agent-session-store-IOH6BREF.cjs.map} +1 -1
- package/dist/auth/index.cjs +15 -15
- package/dist/auth/index.js +3 -3
- package/dist/{batch-N2XDYZED.js → batch-7IGV2OKA.js} +8 -8
- package/dist/{batch-N2XDYZED.js.map → batch-7IGV2OKA.js.map} +1 -1
- package/dist/{batch-VFR2MAUE.cjs → batch-FIWQZ3JD.cjs} +14 -14
- package/dist/{batch-VFR2MAUE.cjs.map → batch-FIWQZ3JD.cjs.map} +1 -1
- package/dist/{chunk-AT2PESXB.cjs → chunk-2UYAL4N7.cjs} +5 -5
- package/dist/chunk-2UYAL4N7.cjs.map +1 -0
- package/dist/{chunk-K2BQQ445.js → chunk-3GQYMKVJ.js} +3 -3
- package/dist/{chunk-K2BQQ445.js.map → chunk-3GQYMKVJ.js.map} +1 -1
- package/dist/{chunk-YSTXFWQE.cjs → chunk-3HL43XBD.cjs} +4 -4
- package/dist/{chunk-YSTXFWQE.cjs.map → chunk-3HL43XBD.cjs.map} +1 -1
- package/dist/{chunk-XESYJ4JC.js → chunk-3VX7JHJI.js} +7 -7
- package/dist/{chunk-XESYJ4JC.js.map → chunk-3VX7JHJI.js.map} +1 -1
- package/dist/{chunk-6XQLTQB6.cjs → chunk-3YCNBOX7.cjs} +3 -3
- package/dist/{chunk-6XQLTQB6.cjs.map → chunk-3YCNBOX7.cjs.map} +1 -1
- package/dist/{chunk-CHKV2L2Y.cjs → chunk-46YYJIHL.cjs} +30 -30
- package/dist/chunk-46YYJIHL.cjs.map +1 -0
- package/dist/{chunk-73IITAF7.js → chunk-4C37ELQL.js} +9 -9
- package/dist/{chunk-73IITAF7.js.map → chunk-4C37ELQL.js.map} +1 -1
- package/dist/{chunk-AGO5S5WD.js → chunk-4E4D6H77.js} +3 -3
- package/dist/{chunk-AGO5S5WD.js.map → chunk-4E4D6H77.js.map} +1 -1
- package/dist/{chunk-R35ZJAYT.cjs → chunk-4JF52NED.cjs} +12 -12
- package/dist/{chunk-R35ZJAYT.cjs.map → chunk-4JF52NED.cjs.map} +1 -1
- package/dist/{chunk-INVEGVGO.js → chunk-4TA47IK4.js} +8 -8
- package/dist/{chunk-INVEGVGO.js.map → chunk-4TA47IK4.js.map} +1 -1
- package/dist/{chunk-MLGETTYX.js → chunk-4YUIYSZO.js} +6 -6
- package/dist/{chunk-MLGETTYX.js.map → chunk-4YUIYSZO.js.map} +1 -1
- package/dist/{chunk-RZL2W4C3.js → chunk-5L7S2VOL.js} +5 -5
- package/dist/{chunk-RZL2W4C3.js.map → chunk-5L7S2VOL.js.map} +1 -1
- package/dist/{chunk-3WKWBYPO.cjs → chunk-6EHOJLJ3.cjs} +20 -20
- package/dist/chunk-6EHOJLJ3.cjs.map +1 -0
- package/dist/{chunk-XOU3CL7S.cjs → chunk-6HQFPX7N.cjs} +28 -28
- package/dist/chunk-6HQFPX7N.cjs.map +1 -0
- package/dist/{chunk-3E3VSMB6.cjs → chunk-6XT5LAZG.cjs} +58 -58
- package/dist/chunk-6XT5LAZG.cjs.map +1 -0
- package/dist/{chunk-R427EOZE.cjs → chunk-7EFT6N5C.cjs} +13 -13
- package/dist/{chunk-R427EOZE.cjs.map → chunk-7EFT6N5C.cjs.map} +1 -1
- package/dist/{chunk-L7V4DDIE.cjs → chunk-7VNFOGRD.cjs} +12 -12
- package/dist/chunk-7VNFOGRD.cjs.map +1 -0
- package/dist/{chunk-YKNBTHMD.js → chunk-AQ23NII7.js} +3 -3
- package/dist/{chunk-YKNBTHMD.js.map → chunk-AQ23NII7.js.map} +1 -1
- package/dist/{chunk-O2RCS3SJ.js → chunk-ATGSV4GS.js} +4 -4
- package/dist/{chunk-O2RCS3SJ.js.map → chunk-ATGSV4GS.js.map} +1 -1
- package/dist/{chunk-H7HD6RXO.cjs → chunk-AW72G4TC.cjs} +5 -5
- package/dist/{chunk-H7HD6RXO.cjs.map → chunk-AW72G4TC.cjs.map} +1 -1
- package/dist/{chunk-M4U24U6G.js → chunk-AZK3QM36.js} +5 -5
- package/dist/{chunk-M4U24U6G.js.map → chunk-AZK3QM36.js.map} +1 -1
- package/dist/{chunk-QK2ERTO3.js → chunk-BH6M2DGV.js} +4 -4
- package/dist/{chunk-QK2ERTO3.js.map → chunk-BH6M2DGV.js.map} +1 -1
- package/dist/{chunk-26UQG242.cjs → chunk-BTGYRM4J.cjs} +264 -264
- package/dist/chunk-BTGYRM4J.cjs.map +1 -0
- package/dist/{chunk-ZQWRUJHE.js → chunk-CG64VPPU.js} +50 -50
- package/dist/{chunk-ZQWRUJHE.js.map → chunk-CG64VPPU.js.map} +1 -1
- package/dist/{chunk-CBBT42VX.js → chunk-CMDM4VF6.js} +3 -3
- package/dist/{chunk-CBBT42VX.js.map → chunk-CMDM4VF6.js.map} +1 -1
- package/dist/{chunk-OLJOI4LK.js → chunk-CN732Z5C.js} +4 -4
- package/dist/{chunk-OLJOI4LK.js.map → chunk-CN732Z5C.js.map} +1 -1
- package/dist/{chunk-SETHRLQS.cjs → chunk-D3BL3EEB.cjs} +9 -9
- package/dist/chunk-D3BL3EEB.cjs.map +1 -0
- package/dist/{chunk-LCCT5ZXJ.js → chunk-DB5RYCXX.js} +13 -13
- package/dist/{chunk-LCCT5ZXJ.js.map → chunk-DB5RYCXX.js.map} +1 -1
- package/dist/{chunk-SV63WTA2.js → chunk-DBH2WA2U.js} +4 -4
- package/dist/{chunk-SV63WTA2.js.map → chunk-DBH2WA2U.js.map} +1 -1
- package/dist/{chunk-XE3CAY6Z.cjs → chunk-DEHPRORL.cjs} +4 -4
- package/dist/{chunk-XE3CAY6Z.cjs.map → chunk-DEHPRORL.cjs.map} +1 -1
- package/dist/{chunk-UKMBRMGT.js → chunk-DGO4LPLP.js} +4 -4
- package/dist/{chunk-UKMBRMGT.js.map → chunk-DGO4LPLP.js.map} +1 -1
- package/dist/{chunk-RS7YQR4X.cjs → chunk-EBX6XUN7.cjs} +7 -7
- package/dist/{chunk-RS7YQR4X.cjs.map → chunk-EBX6XUN7.cjs.map} +1 -1
- package/dist/chunk-EDIYR4VR.cjs +18 -0
- package/dist/{chunk-SPEN7RJU.cjs.map → chunk-EDIYR4VR.cjs.map} +1 -1
- package/dist/{chunk-QGDPMM3C.js → chunk-EF5IONLL.js} +5 -5
- package/dist/{chunk-QGDPMM3C.js.map → chunk-EF5IONLL.js.map} +1 -1
- package/dist/{chunk-MSW7LB2V.cjs → chunk-EOWIUDKG.cjs} +5 -5
- package/dist/{chunk-MSW7LB2V.cjs.map → chunk-EOWIUDKG.cjs.map} +1 -1
- package/dist/{chunk-L7JPXZ2C.cjs → chunk-ER3BF23R.cjs} +19 -19
- package/dist/{chunk-L7JPXZ2C.cjs.map → chunk-ER3BF23R.cjs.map} +1 -1
- package/dist/{chunk-BNEUB52I.cjs → chunk-ERBKPA2N.cjs} +4 -4
- package/dist/{chunk-BNEUB52I.cjs.map → chunk-ERBKPA2N.cjs.map} +1 -1
- package/dist/{chunk-4QCJAJAG.js → chunk-F54PXZIA.js} +5 -5
- package/dist/{chunk-4QCJAJAG.js.map → chunk-F54PXZIA.js.map} +1 -1
- package/dist/{chunk-2XWAYCFM.cjs → chunk-FBT2SF2S.cjs} +12 -12
- package/dist/{chunk-2XWAYCFM.cjs.map → chunk-FBT2SF2S.cjs.map} +1 -1
- package/dist/{chunk-AUH2EVWY.cjs → chunk-FZSPVIFK.cjs} +16 -16
- package/dist/chunk-FZSPVIFK.cjs.map +1 -0
- package/dist/{chunk-CYWOJA6B.js → chunk-GA27FFT5.js} +3 -3
- package/dist/{chunk-CYWOJA6B.js.map → chunk-GA27FFT5.js.map} +1 -1
- package/dist/{chunk-HWMGR3HA.js → chunk-GEQLHBJM.js} +6 -6
- package/dist/{chunk-HWMGR3HA.js.map → chunk-GEQLHBJM.js.map} +1 -1
- package/dist/{chunk-IJ7M6GOG.cjs → chunk-GKERJ52A.cjs} +4 -4
- package/dist/{chunk-IJ7M6GOG.cjs.map → chunk-GKERJ52A.cjs.map} +1 -1
- package/dist/{chunk-NEH3BPFD.cjs → chunk-HI5TJWQE.cjs} +19 -19
- package/dist/{chunk-NEH3BPFD.cjs.map → chunk-HI5TJWQE.cjs.map} +1 -1
- package/dist/{chunk-ZOFRNNZD.cjs → chunk-IZV72OEI.cjs} +11 -11
- package/dist/chunk-IZV72OEI.cjs.map +1 -0
- package/dist/{chunk-KU5O7WPK.js → chunk-JD3EKKKB.js} +4 -4
- package/dist/{chunk-KU5O7WPK.js.map → chunk-JD3EKKKB.js.map} +1 -1
- package/dist/{chunk-DFBWYETS.js → chunk-JRP2W2DR.js} +3 -3
- package/dist/{chunk-DFBWYETS.js.map → chunk-JRP2W2DR.js.map} +1 -1
- package/dist/{chunk-26GI3OYA.js → chunk-JRQJMZKY.js} +13 -13
- package/dist/{chunk-26GI3OYA.js.map → chunk-JRQJMZKY.js.map} +1 -1
- package/dist/{chunk-YQ3XC6C2.cjs → chunk-JVH3NNJ3.cjs} +13 -13
- package/dist/chunk-JVH3NNJ3.cjs.map +1 -0
- package/dist/{chunk-VWYFGE27.cjs → chunk-KC2HKUPT.cjs} +56 -56
- package/dist/chunk-KC2HKUPT.cjs.map +1 -0
- package/dist/{chunk-PDOXYXWX.cjs → chunk-KDJTECE6.cjs} +4 -4
- package/dist/{chunk-PDOXYXWX.cjs.map → chunk-KDJTECE6.cjs.map} +1 -1
- package/dist/{chunk-QD6B6W6I.js → chunk-L2KC2G7H.js} +5 -5
- package/dist/{chunk-QD6B6W6I.js.map → chunk-L2KC2G7H.js.map} +1 -1
- package/dist/{chunk-3BBTHFGJ.js → chunk-L5B7EWWH.js} +5 -5
- package/dist/{chunk-3BBTHFGJ.js.map → chunk-L5B7EWWH.js.map} +1 -1
- package/dist/{chunk-X7CE7Y5W.js → chunk-LG4JCJEI.js} +11 -11
- package/dist/{chunk-X7CE7Y5W.js.map → chunk-LG4JCJEI.js.map} +1 -1
- package/dist/{chunk-2HXTNVLM.js → chunk-LSURFRJA.js} +6 -6
- package/dist/{chunk-2HXTNVLM.js.map → chunk-LSURFRJA.js.map} +1 -1
- package/dist/{chunk-55LLQFHW.cjs → chunk-MJGY3E3C.cjs} +4 -4
- package/dist/{chunk-55LLQFHW.cjs.map → chunk-MJGY3E3C.cjs.map} +1 -1
- package/dist/{chunk-EQ5IDL6W.cjs → chunk-MKS5FFH5.cjs} +15 -15
- package/dist/chunk-MKS5FFH5.cjs.map +1 -0
- package/dist/{chunk-WM646HEN.js → chunk-MTNCJ7MQ.js} +6 -6
- package/dist/{chunk-WM646HEN.js.map → chunk-MTNCJ7MQ.js.map} +1 -1
- package/dist/{chunk-SA6K24NB.cjs → chunk-MXJJMP4U.cjs} +7 -7
- package/dist/chunk-MXJJMP4U.cjs.map +1 -0
- package/dist/{chunk-FTXY4D44.cjs → chunk-N2ORTDSO.cjs} +20 -20
- package/dist/chunk-N2ORTDSO.cjs.map +1 -0
- package/dist/{chunk-7YUPSBDV.cjs → chunk-NDXZXGVP.cjs} +6 -6
- package/dist/{chunk-7YUPSBDV.cjs.map → chunk-NDXZXGVP.cjs.map} +1 -1
- package/dist/{chunk-3UMPP553.cjs → chunk-NMRNE6QX.cjs} +11 -11
- package/dist/{chunk-3UMPP553.cjs.map → chunk-NMRNE6QX.cjs.map} +1 -1
- package/dist/{chunk-PKRPV3R7.js → chunk-NO6AG22P.js} +6 -6
- package/dist/{chunk-PKRPV3R7.js.map → chunk-NO6AG22P.js.map} +1 -1
- package/dist/{chunk-NK5EOTDY.js → chunk-OIOSY5G6.js} +3 -3
- package/dist/{chunk-NK5EOTDY.js.map → chunk-OIOSY5G6.js.map} +1 -1
- package/dist/{chunk-RGVMJU57.js → chunk-P7B64NFK.js} +8 -8
- package/dist/{chunk-RGVMJU57.js.map → chunk-P7B64NFK.js.map} +1 -1
- package/dist/{chunk-TDED5XES.js → chunk-PCPYTBVV.js} +6 -6
- package/dist/chunk-PCPYTBVV.js.map +1 -0
- package/dist/{chunk-FBIDOOJM.cjs → chunk-PDFSLD5P.cjs} +6 -6
- package/dist/{chunk-FBIDOOJM.cjs.map → chunk-PDFSLD5P.cjs.map} +1 -1
- package/dist/{chunk-YMVTUV5K.js → chunk-PISZRLES.js} +5 -5
- package/dist/{chunk-YMVTUV5K.js.map → chunk-PISZRLES.js.map} +1 -1
- package/dist/{chunk-BUUUWQMB.cjs → chunk-PNL63WDP.cjs} +4 -4
- package/dist/{chunk-BUUUWQMB.cjs.map → chunk-PNL63WDP.cjs.map} +1 -1
- package/dist/{chunk-VNRNZ3WI.js → chunk-PSMWZYW6.js} +4 -4
- package/dist/{chunk-VNRNZ3WI.js.map → chunk-PSMWZYW6.js.map} +1 -1
- package/dist/{chunk-5645PMVU.cjs → chunk-PXYINWBC.cjs} +20 -20
- package/dist/chunk-PXYINWBC.cjs.map +1 -0
- package/dist/{chunk-RTDVSHJR.cjs → chunk-Q4UOCMAO.cjs} +5 -5
- package/dist/{chunk-RTDVSHJR.cjs.map → chunk-Q4UOCMAO.cjs.map} +1 -1
- package/dist/{chunk-5IRDK5BC.js → chunk-QH6MWGOA.js} +4 -4
- package/dist/{chunk-5IRDK5BC.js.map → chunk-QH6MWGOA.js.map} +1 -1
- package/dist/{chunk-3LMVJTNJ.js → chunk-QPUKIZOT.js} +3 -3
- package/dist/{chunk-3LMVJTNJ.js.map → chunk-QPUKIZOT.js.map} +1 -1
- package/dist/{chunk-SWALPLFQ.cjs → chunk-RTCO3USA.cjs} +16 -16
- package/dist/chunk-RTCO3USA.cjs.map +1 -0
- package/dist/{chunk-SAGRF4IB.js → chunk-S2CMEJ5F.js} +3 -3
- package/dist/{chunk-SAGRF4IB.js.map → chunk-S2CMEJ5F.js.map} +1 -1
- package/dist/{chunk-VZGE7NAI.js → chunk-S5ERB3CH.js} +3 -3
- package/dist/{chunk-VZGE7NAI.js.map → chunk-S5ERB3CH.js.map} +1 -1
- package/dist/{chunk-XJ3WU5CZ.js → chunk-S7IZEVXX.js} +6 -6
- package/dist/{chunk-XJ3WU5CZ.js.map → chunk-S7IZEVXX.js.map} +1 -1
- package/dist/{chunk-Q7UG242V.js → chunk-TIYQDJFK.js} +3 -3
- package/dist/{chunk-Q7UG242V.js.map → chunk-TIYQDJFK.js.map} +1 -1
- package/dist/{chunk-POBZXULR.cjs → chunk-TTNJOM5W.cjs} +16 -16
- package/dist/{chunk-POBZXULR.cjs.map → chunk-TTNJOM5W.cjs.map} +1 -1
- package/dist/{chunk-VCQ37TC5.js → chunk-TTXRNCXY.js} +3 -3
- package/dist/{chunk-VCQ37TC5.js.map → chunk-TTXRNCXY.js.map} +1 -1
- package/dist/{chunk-3HA7N2BB.cjs → chunk-TYKBAKAS.cjs} +5 -5
- package/dist/{chunk-3HA7N2BB.cjs.map → chunk-TYKBAKAS.cjs.map} +1 -1
- package/dist/{chunk-A77UMVMD.cjs → chunk-UMCCYXFZ.cjs} +8 -8
- package/dist/{chunk-A77UMVMD.cjs.map → chunk-UMCCYXFZ.cjs.map} +1 -1
- package/dist/{chunk-3GTN4HOO.js → chunk-UNP4NCMZ.js} +3 -3
- package/dist/{chunk-3GTN4HOO.js.map → chunk-UNP4NCMZ.js.map} +1 -1
- package/dist/{chunk-BPL5CVZ5.js → chunk-VFT63FJF.js} +3 -3
- package/dist/{chunk-BPL5CVZ5.js.map → chunk-VFT63FJF.js.map} +1 -1
- package/dist/{chunk-3KIUY67P.cjs → chunk-VHY6NVCJ.cjs} +30 -30
- package/dist/chunk-VHY6NVCJ.cjs.map +1 -0
- package/dist/{chunk-TRPRTZEH.cjs → chunk-VT3NWHFH.cjs} +6 -6
- package/dist/{chunk-TRPRTZEH.cjs.map → chunk-VT3NWHFH.cjs.map} +1 -1
- package/dist/{chunk-FJWQP6EE.js → chunk-WJ6OGKAO.js} +3 -3
- package/dist/{chunk-FJWQP6EE.js.map → chunk-WJ6OGKAO.js.map} +1 -1
- package/dist/{chunk-FLDHYF5C.cjs → chunk-X2XLSE2N.cjs} +9 -9
- package/dist/chunk-X2XLSE2N.cjs.map +1 -0
- package/dist/{chunk-UDAKAE4Z.cjs → chunk-X4ZYEZPD.cjs} +12 -12
- package/dist/chunk-X4ZYEZPD.cjs.map +1 -0
- package/dist/{chunk-QG4SXZ2A.cjs → chunk-XR6O6G3B.cjs} +27 -27
- package/dist/chunk-XR6O6G3B.cjs.map +1 -0
- package/dist/{chunk-BWYC7RZG.cjs → chunk-XTQWTVVI.cjs} +11 -11
- package/dist/chunk-XTQWTVVI.cjs.map +1 -0
- package/dist/{chunk-MLJDW2YK.js → chunk-YSGS57DA.js} +4 -4
- package/dist/{chunk-MLJDW2YK.js.map → chunk-YSGS57DA.js.map} +1 -1
- package/dist/{chunk-KCM3RMKR.js → chunk-YUQNHLKN.js} +8 -8
- package/dist/{chunk-KCM3RMKR.js.map → chunk-YUQNHLKN.js.map} +1 -1
- package/dist/{chunk-P7QYG4RZ.cjs → chunk-Z5MB2HAN.cjs} +8 -8
- package/dist/{chunk-P7QYG4RZ.cjs.map → chunk-Z5MB2HAN.cjs.map} +1 -1
- package/dist/{chunk-HJ2LV464.js → chunk-ZUWVO442.js} +4 -4
- package/dist/{chunk-HJ2LV464.js.map → chunk-ZUWVO442.js.map} +1 -1
- package/dist/{compact-session-CHBJOYWP.cjs → compact-session-5DWFHMW4.cjs} +21 -21
- package/dist/{compact-session-CHBJOYWP.cjs.map → compact-session-5DWFHMW4.cjs.map} +1 -1
- package/dist/{compact-session-F3J3FMEU.js → compact-session-AL65PEMX.js} +12 -12
- package/dist/{compact-session-F3J3FMEU.js.map → compact-session-AL65PEMX.js.map} +1 -1
- package/dist/context/index.cjs +11 -11
- package/dist/context/index.js +6 -6
- package/dist/context-4XSD2V3G.js +7 -0
- package/dist/{context-7EBZST3L.js.map → context-4XSD2V3G.js.map} +1 -1
- package/dist/context-ZX2VBGSX.cjs +24 -0
- package/dist/{context-4RSI3AXT.cjs.map → context-ZX2VBGSX.cjs.map} +1 -1
- package/dist/cron.cjs +40 -40
- package/dist/cron.js +39 -39
- package/dist/eval.cjs +51 -51
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +41 -41
- package/dist/{executor-THUXNAFR.js → executor-ZHIUSHRV.js} +7 -7
- package/dist/{executor-THUXNAFR.js.map → executor-ZHIUSHRV.js.map} +1 -1
- package/dist/{executor-5QL5AXIZ.cjs → executor-ZTLJZ5LK.cjs} +24 -24
- package/dist/{executor-5QL5AXIZ.cjs.map → executor-ZTLJZ5LK.cjs.map} +1 -1
- package/dist/filesystem/index.cjs +14 -14
- package/dist/filesystem/index.cjs.map +1 -1
- package/dist/filesystem/index.js +4 -4
- package/dist/{fork-agent-S2EPIML7.js → fork-agent-BHVTALT5.js} +3 -3
- package/dist/{fork-agent-S2EPIML7.js.map → fork-agent-BHVTALT5.js.map} +1 -1
- package/dist/{fork-agent-WAEQI2DL.cjs → fork-agent-DCOPPXM2.cjs} +4 -4
- package/dist/{fork-agent-WAEQI2DL.cjs.map → fork-agent-DCOPPXM2.cjs.map} +1 -1
- package/dist/fs-session-store-EZ32TFGB.js +11 -0
- package/dist/{fs-session-store-MYQEVUFL.js.map → fs-session-store-EZ32TFGB.js.map} +1 -1
- package/dist/fs-session-store-G664LDDN.cjs +20 -0
- package/dist/{fs-session-store-CYNTEPJZ.cjs.map → fs-session-store-G664LDDN.cjs.map} +1 -1
- package/dist/generate-object-JFZTK567.cjs +20 -0
- package/dist/{generate-object-A5F576IT.cjs.map → generate-object-JFZTK567.cjs.map} +1 -1
- package/dist/{generate-object-TBDOBBYW.js → generate-object-RPBXY2U3.js} +4 -4
- package/dist/{generate-object-TBDOBBYW.js.map → generate-object-RPBXY2U3.js.map} +1 -1
- package/dist/index-manager-2MJEEN2O.js +18 -0
- package/dist/{index-manager-NF2WRLOU.js.map → index-manager-2MJEEN2O.js.map} +1 -1
- package/dist/index-manager-CT6MMG7S.cjs +27 -0
- package/dist/{index-manager-2KH75BRW.cjs.map → index-manager-CT6MMG7S.cjs.map} +1 -1
- package/dist/index.cjs +130 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +56 -56
- package/dist/{inject-session-LC2TROHM.js → inject-session-E2SZ4A35.js} +6 -6
- package/dist/{inject-session-LC2TROHM.js.map → inject-session-E2SZ4A35.js.map} +1 -1
- package/dist/{inject-session-T4RXANG3.cjs → inject-session-QZULG65O.cjs} +8 -8
- package/dist/{inject-session-T4RXANG3.cjs.map → inject-session-QZULG65O.cjs.map} +1 -1
- package/dist/internal/memory/adapters/index.cjs +4 -4
- package/dist/internal/memory/adapters/index.js +3 -3
- package/dist/internal/memory/storage/index.cjs +45 -45
- package/dist/internal/memory/storage/index.js +9 -9
- package/dist/internal/persistence/index.cjs +22 -22
- package/dist/internal/persistence/index.js +6 -6
- package/dist/internal/security/index.cjs +9 -9
- package/dist/internal/security/index.js +2 -2
- package/dist/mcp-auth.cjs +8 -8
- package/dist/mcp-auth.js +3 -3
- package/dist/models.cjs +30 -30
- package/dist/models.cjs.map +1 -1
- package/dist/models.js +8 -8
- package/dist/{oauth-transaction-store-U34YLEGP.js → oauth-transaction-store-QQBWGNE2.js} +3 -3
- package/dist/{oauth-transaction-store-U34YLEGP.js.map → oauth-transaction-store-QQBWGNE2.js.map} +1 -1
- package/dist/{oauth-transaction-store-OMX436YB.cjs → oauth-transaction-store-YF7WOA6B.cjs} +10 -10
- package/dist/{oauth-transaction-store-OMX436YB.cjs.map → oauth-transaction-store-YF7WOA6B.cjs.map} +1 -1
- package/dist/path-safety.cjs +9 -9
- package/dist/path-safety.js +2 -2
- package/dist/persistence.cjs +41 -41
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.js +11 -11
- package/dist/project.cjs +7 -7
- package/dist/project.cjs.map +1 -1
- package/dist/project.js +4 -4
- package/dist/providers.cjs +6 -6
- package/dist/providers.js +4 -4
- package/dist/{registry-5TDAF7RK.js → registry-DOYBR237.js} +6 -6
- package/dist/{registry-5TDAF7RK.js.map → registry-DOYBR237.js.map} +1 -1
- package/dist/{registry-W7TSCLQO.cjs → registry-PB7JFVUJ.cjs} +14 -14
- package/dist/{registry-W7TSCLQO.cjs.map → registry-PB7JFVUJ.cjs.map} +1 -1
- package/dist/sandbox/index.cjs +41 -41
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +7 -7
- package/dist/sanitize/index.cjs +2 -2
- package/dist/sanitize/index.js +1 -1
- package/dist/server/auth/index.cjs +9 -9
- package/dist/server/auth/index.cjs.map +1 -1
- package/dist/server/auth/index.js +4 -4
- package/dist/{session-transcript-7TZ6HXSX.cjs → session-transcript-EBVGLZ6B.cjs} +17 -17
- package/dist/{session-transcript-7TZ6HXSX.cjs.map → session-transcript-EBVGLZ6B.cjs.map} +1 -1
- package/dist/{session-transcript-BSHZYYY6.js → session-transcript-GU2VE7JX.js} +4 -4
- package/dist/{session-transcript-BSHZYYY6.js.map → session-transcript-GU2VE7JX.js.map} +1 -1
- package/dist/skills.cjs +7 -7
- package/dist/skills.js +4 -4
- package/dist/stream-object-3AU7QDHT.js +7 -0
- package/dist/{stream-object-EKM4WGTI.js.map → stream-object-3AU7QDHT.js.map} +1 -1
- package/dist/stream-object-SLGR2KIA.cjs +20 -0
- package/dist/{stream-object-AS4YYM4B.cjs.map → stream-object-SLGR2KIA.cjs.map} +1 -1
- package/dist/subagents-loader-4PQS7ULK.cjs +16 -0
- package/dist/{subagents-loader-KLWF4QPD.cjs.map → subagents-loader-4PQS7ULK.cjs.map} +1 -1
- package/dist/subagents-loader-CPFVLWLH.js +7 -0
- package/dist/{subagents-loader-SXETS2F2.js.map → subagents-loader-CPFVLWLH.js.map} +1 -1
- package/dist/subagents-loader.cjs +4 -4
- package/dist/subagents-loader.js +3 -3
- package/dist/subagents.cjs +2 -2
- package/dist/subagents.js +1 -1
- package/dist/task-store.cjs +5 -5
- package/dist/task-store.js +2 -2
- package/dist/workflow.cjs +28 -28
- package/dist/workflow.js +5 -5
- package/package.json +1 -1
- package/dist/agent-66U5QCPF.js +0 -63
- package/dist/agent-CB27KF6O.cjs +0 -72
- package/dist/agent-session-store-22GKA7O4.js +0 -6
- package/dist/agent-session-store-WP6NGDVX.cjs +0 -23
- package/dist/chunk-26UQG242.cjs.map +0 -1
- package/dist/chunk-3E3VSMB6.cjs.map +0 -1
- package/dist/chunk-3KIUY67P.cjs.map +0 -1
- package/dist/chunk-3WKWBYPO.cjs.map +0 -1
- package/dist/chunk-5645PMVU.cjs.map +0 -1
- package/dist/chunk-AT2PESXB.cjs.map +0 -1
- package/dist/chunk-AUH2EVWY.cjs.map +0 -1
- package/dist/chunk-BWYC7RZG.cjs.map +0 -1
- package/dist/chunk-CHKV2L2Y.cjs.map +0 -1
- package/dist/chunk-EQ5IDL6W.cjs.map +0 -1
- package/dist/chunk-FLDHYF5C.cjs.map +0 -1
- package/dist/chunk-FTXY4D44.cjs.map +0 -1
- package/dist/chunk-L7V4DDIE.cjs.map +0 -1
- package/dist/chunk-QG4SXZ2A.cjs.map +0 -1
- package/dist/chunk-SA6K24NB.cjs.map +0 -1
- package/dist/chunk-SETHRLQS.cjs.map +0 -1
- package/dist/chunk-SPEN7RJU.cjs +0 -18
- package/dist/chunk-SWALPLFQ.cjs.map +0 -1
- package/dist/chunk-TDED5XES.js.map +0 -1
- package/dist/chunk-UDAKAE4Z.cjs.map +0 -1
- package/dist/chunk-VWYFGE27.cjs.map +0 -1
- package/dist/chunk-XOU3CL7S.cjs.map +0 -1
- package/dist/chunk-YQ3XC6C2.cjs.map +0 -1
- package/dist/chunk-ZOFRNNZD.cjs.map +0 -1
- package/dist/context-4RSI3AXT.cjs +0 -24
- package/dist/context-7EBZST3L.js +0 -7
- package/dist/fs-session-store-CYNTEPJZ.cjs +0 -20
- package/dist/fs-session-store-MYQEVUFL.js +0 -11
- package/dist/generate-object-A5F576IT.cjs +0 -20
- package/dist/index-manager-2KH75BRW.cjs +0 -27
- package/dist/index-manager-NF2WRLOU.js +0 -18
- package/dist/stream-object-AS4YYM4B.cjs +0 -20
- package/dist/stream-object-EKM4WGTI.js +0 -7
- package/dist/subagents-loader-KLWF4QPD.cjs +0 -16
- package/dist/subagents-loader-SXETS2F2.js +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/runtime/skills/skill-frontmatter.ts","../src/internal/runtime/skills/discover-skills.ts","../src/internal/runtime/system-prompt/escape.ts","../src/internal/runtime/skills/skills-block.ts"],"names":[],"mappings":"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","file":"chunk-2HXTNVLM.js","sourcesContent":["import { ConfigurationError } from \"../../../errors.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\n\ntype StringFields = Record<string, string | undefined>;\n\n/** Narrow a FrontmatterValue to string; non-strings + undefined → undefined. */\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\n/** Coerce parser output to legacy string-only shape (skill schema is all-string). */\nfunction toStringFields(raw: Record<string, FrontmatterValue | undefined>): StringFields {\n const out: StringFields = {};\n for (const [k, v] of Object.entries(raw)) out[k] = asString(v);\n return out;\n}\n\n/**\n * Strict skill frontmatter schema (ADR D10).\n *\n * Required: `name`, `description`.\n * Optional: `category`, `dependencies` (comma-separated string in the\n * simple-YAML dialect — parsed to `string[]`).\n *\n * Unknown fields are ignored (forward-compat). Malformed YAML or missing\n * required fields surface as `ConfigurationError` with one of the typed\n * codes below.\n *\n * @internal\n */\nexport interface SkillFrontmatter {\n name: string;\n description: string;\n category?: string;\n dependencies?: string[];\n /**\n * `requires-bins: git, rg` (B-546) — executables the skill needs on PATH. A skill whose binary is\n * absent is not offered to the model; `skills.get(name)` still resolves it.\n */\n requiresBins?: string[];\n /**\n * `disable-model-invocation: true` — the model is not told this skill exists.\n *\n * The SDK owns this boundary: a skill reaches the model through exactly one place,\n * `buildSystemPromptContext`, so the declaration is enforceable here. It is a DISCLOSURE rule,\n * not an execution rule — `skills.get(name)` still resolves it, because a caller naming a skill\n * has already made the decision the field exists to keep away from the model.\n */\n disableModelInvocation?: boolean;\n /**\n * `user-invocable: false` — carried, deliberately not enforced here.\n *\n * This SDK has no user-facing invocation surface for skills; there is no slash command. Reading\n * `agent.skills.list()` as \"the user\" would be a guess — a host may call it to build a picker or\n * to introspect, and the two want opposite answers. So the declaration travels to the host that\n * does know, instead of being discarded (the defect) or enforced against an invented boundary.\n */\n userInvocable?: boolean;\n}\n\n/**\n * Parse a SKILL.md file body into validated frontmatter.\n *\n * @throws ConfigurationError(code: \"missing_frontmatter\") — no `---` block at file head.\n * @throws ConfigurationError(code: \"schema_invalid\") — YAML malformed OR required field missing.\n *\n * @internal\n */\nexport function parseSkillFrontmatter(raw: string, fallbackName: string): SkillFrontmatter {\n const parsed = extractAndParseFrontmatter(raw, fallbackName);\n const fields = toStringFields(parsed);\n const name = resolveName(fields, fallbackName);\n ensureRequiredFields(fields, name);\n return buildFrontmatter(fields, parsed, name);\n}\n\n/**\n * SE20 — return a SKILL.md's BODY (everything after the frontmatter block), trimmed.\n * When there is no frontmatter block, the whole file is the body. Reuses the same\n * frontmatter regex as {@link parseSkillFrontmatter} (DRY).\n */\nexport function stripSkillFrontmatter(raw: string): string {\n const match = /^---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/.exec(raw);\n return (match === null ? raw : raw.slice(match[0].length)).trim();\n}\n\nfunction extractAndParseFrontmatter(\n raw: string,\n fallbackName: string,\n): Record<string, FrontmatterValue | undefined> {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {\n code: \"missing_frontmatter\",\n });\n }\n const frontmatter = match[1] ?? \"\";\n // EC-5: guard against syntactically invalid frontmatter so the loader\n // surfaces schema_invalid rather than crashing.\n try {\n return parseSimpleYaml(frontmatter);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConfigurationError(\n `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,\n { code: \"schema_invalid\", cause },\n );\n }\n}\n\nfunction resolveName(fields: StringFields, fallbackName: string): string {\n if (hasContent(fields.name)) return fields.name;\n if (hasContent(fallbackName)) return fallbackName;\n throw new ConfigurationError(\"Skill at unknown path is missing required field: name\", {\n code: \"schema_invalid\",\n });\n}\n\nfunction ensureRequiredFields(fields: StringFields, name: string): void {\n if (!hasContent(fields.description)) {\n throw new ConfigurationError(`Skill ${name} is missing required field: description`, {\n code: \"schema_invalid\",\n });\n }\n}\n\nfunction buildFrontmatter(\n fields: StringFields,\n parsed: Record<string, FrontmatterValue | undefined>,\n name: string,\n): SkillFrontmatter {\n const description = fields.description;\n if (description === undefined) {\n // ensureRequiredFields already threw; this is unreachable but satisfies TS\n throw new ConfigurationError(`Skill ${name} missing description`, { code: \"schema_invalid\" });\n }\n const result: SkillFrontmatter = { name, description };\n if (hasContent(fields.category)) result.category = fields.category;\n const deps = parseDependencies(fields.dependencies);\n if (deps !== undefined) result.dependencies = deps;\n const bins = parseDependencies(fields[\"requires-bins\"]);\n if (bins !== undefined) result.requiresBins = bins;\n const disable = readAuthorizationFlag(parsed, \"disable-model-invocation\", name);\n if (disable !== undefined) result.disableModelInvocation = disable;\n const invocable = readAuthorizationFlag(parsed, \"user-invocable\", name);\n if (invocable !== undefined) result.userInvocable = invocable;\n return result;\n}\n\n/**\n * Read one boolean authorization flag, refusing a value this parser cannot represent.\n *\n * Unknown frontmatter keys are ignored here for forward compatibility, and that is right for\n * metadata. It is wrong for these two: the simple-YAML dialect coerces only the literals `true`\n * and `false`, so `disable-model-invocation: yes` — a valid YAML boolean — arrives as the STRING\n * `\"yes\"`, compares unequal to `true`, and the skill is disclosed to the model. The author wrote a\n * restriction and got the default.\n *\n * A restriction that fails open is worse than one that is absent, because the author stops looking.\n * So the value is refused and named, and the run stops on the line that caused it.\n */\nfunction readAuthorizationFlag(\n parsed: Record<string, FrontmatterValue | undefined>,\n key: string,\n name: string,\n): boolean | undefined {\n const value = parsed[key];\n if (value === undefined) return undefined;\n if (typeof value === \"boolean\") return value;\n throw new ConfigurationError(\n `Skill ${name}: \"${key}\" must be true or false (got ${JSON.stringify(value)}) — ` +\n `this dialect reads only the literals \\`true\\` and \\`false\\`, and a value it cannot read ` +\n `would leave the skill disclosed`,\n { code: \"schema_invalid\" },\n );\n}\n\nfunction parseDependencies(raw: string | undefined): string[] | undefined {\n if (!hasContent(raw)) return undefined;\n const deps = (raw as string)\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return deps.length > 0 ? deps : undefined;\n}\n\nfunction hasContent(value: string | undefined): value is string {\n return value !== undefined && value.trim().length > 0;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport { assertNoSymlinkEscape, safePathJoin } from \"../../security/path-guard.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { parseSkillFrontmatter, stripSkillFrontmatter } from \"./skill-frontmatter.js\";\n\n/**\n * A discovered skill's metadata. The skill BODY is never included — only the\n * strict frontmatter fields plus the resolved `source` path.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path to the discovered `SKILL.md`. */\n source: string;\n category?: string;\n dependencies?: string[];\n /** `requires-bins` — executables that must be on PATH for the model to be offered this skill. */\n requiresBins?: string[];\n /** `disable-model-invocation: true` — the model is never told this skill exists. */\n disableModelInvocation?: boolean;\n /** `user-invocable: false` — carried for a host that has a picker; this SDK has no such surface. */\n userInvocable?: boolean;\n}\n\n/**\n * Information passed to `onInvalidSkill` when a `SKILL.md` is present but its\n * frontmatter is malformed (missing required field or invalid YAML).\n *\n * @public\n */\nexport interface InvalidSkillInfo {\n /** The skill directory name (used as the fallback skill name). */\n name: string;\n /** Absolute path to the offending `SKILL.md`. */\n source: string;\n /** Typed reason: `missing_frontmatter` or `schema_invalid`. */\n code: string;\n message: string;\n}\n\n/**\n * Options for {@link discoverSkills}.\n *\n * @public\n */\nexport interface DiscoverSkillsOptions {\n /**\n * Called once per directory that contains a `SKILL.md` with malformed\n * frontmatter. The skill is excluded from the result; discovery continues\n * (strict-frontmatter ADR / EC-5). A directory WITHOUT a `SKILL.md` is NOT a\n * malformed skill and does not trigger this callback.\n *\n * Default: no-op (a library primitive must not write to the consumer's\n * stderr by default).\n */\n onInvalidSkill?: (info: InvalidSkillInfo) => void;\n}\n\n/**\n * Discover `SKILL.md` skills under an arbitrary directory.\n *\n * For each immediate subdirectory `<dir>/<name>/` containing a `SKILL.md`, the\n * file's strict YAML frontmatter is parsed (`name`/`description` required;\n * `category`/`dependencies` optional). Malformed skills are skipped (optionally\n * reported via {@link DiscoverSkillsOptions.onInvalidSkill}); a subdirectory\n * whose realpath escapes `dir` (via symlink) is skipped (symlink-escape guard,\n * reusing `@theokit/sdk/path-safety`).\n *\n * NEVER throws: a missing, unreadable, or non-directory `dir` yields `[]`.\n *\n * Discovery order follows the filesystem `readdir` order (OS-dependent). Sort\n * the result before {@link buildSkillsBlock} if a stable block order matters.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport async function discoverSkills(\n dir: string,\n options?: DiscoverSkillsOptions,\n): Promise<Skill[]> {\n let entries: Awaited<ReturnType<typeof readWorkspaceDir>>;\n try {\n entries = await readWorkspaceDir(dir, \"skills_read_error\", \"skills directory\");\n } catch {\n // never-throw contract: unreadable / not-a-directory → no skills (EC-1)\n return [];\n }\n\n const skills: Skill[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n let skillDir: string;\n try {\n skillDir = safePathJoin(dir, entry.name);\n assertNoSymlinkEscape(skillDir, dir);\n } catch {\n continue;\n }\n const skillPath = join(skillDir, \"SKILL.md\");\n let raw: string;\n try {\n raw = await readFile(skillPath, \"utf8\");\n } catch {\n // no SKILL.md in this subdir → not a skill, not an error (EC-2)\n continue;\n }\n const skill = tryParseSkill(raw, entry.name, skillPath, options);\n if (skill !== undefined) skills.push(skill);\n }\n return skills;\n}\n\nfunction tryParseSkill(\n raw: string,\n fallbackName: string,\n source: string,\n options: DiscoverSkillsOptions | undefined,\n): Skill | undefined {\n try {\n const frontmatter = parseSkillFrontmatter(raw, fallbackName);\n const skill: Skill = {\n name: frontmatter.name,\n description: frontmatter.description,\n source,\n };\n if (frontmatter.category !== undefined) skill.category = frontmatter.category;\n if (frontmatter.dependencies !== undefined) skill.dependencies = frontmatter.dependencies;\n // The authorization flags travel with the record. Dropping them here would reproduce the defect\n // one layer down: the parser would read the declaration and the discovery result would not\n // carry it, which is indistinguishable from never having parsed it.\n if (frontmatter.requiresBins !== undefined) skill.requiresBins = frontmatter.requiresBins;\n if (frontmatter.disableModelInvocation !== undefined) {\n skill.disableModelInvocation = frontmatter.disableModelInvocation;\n }\n if (frontmatter.userInvocable !== undefined) skill.userInvocable = frontmatter.userInvocable;\n return skill;\n } catch (cause) {\n if (cause instanceof ConfigurationError) {\n options?.onInvalidSkill?.({\n name: fallbackName,\n source,\n code: cause.code ?? \"unknown\",\n message: cause.message,\n });\n return undefined;\n }\n throw cause;\n }\n}\n\n/**\n * Read the BODY of a discovered skill — everything after its frontmatter.\n *\n * A thin selector over {@link discoverSkills} rather than a second reader, which is the same\n * relationship `loadSubagentDefinition` has to `discoverSubagents` in the sibling domain: one\n * parser is the point.\n *\n * ## Why this exists rather than a field on `Skill`\n *\n * `Skill` documents that *\"the skill BODY is never included\"*. That is a written contract with no\n * written reason, and widening it on a guess about the reason is not a trade worth making — a\n * catalog you can put in a prompt without carrying every body is the likely intent, and this keeps\n * that shape intact for whoever relied on it.\n *\n * The body was never expensive to obtain: `discoverSkills` already reads each file in full and\n * discards everything but the frontmatter. What was missing was a door that hands it over.\n *\n * ## What it is for\n *\n * Turning a discovered skill into an inline one — `SkillsSettings.inline` requires `instructions`,\n * and without this the only route was to open `source` and split the frontmatter by hand. That is a\n * second implementation of this module's own convention, and it would fail SILENTLY if the format\n * moved: the frontmatter would land inside the instructions and nothing would say so.\n *\n * Reported by the `theocode` session, which needed exactly that to give an operator's\n * `~/.theokit/skills/` to an agent through the SDK's own parser.\n *\n * @param skill - a record returned by {@link discoverSkills}; its `source` is read.\n * @returns the trimmed body. A file that is all frontmatter yields an empty string.\n * @throws if `source` is unreadable — unlike discovery, which skips what it cannot read, a caller\n * naming ONE skill has asked about that skill and an empty string would answer a question it did\n * not ask.\n * @public\n */\nexport async function loadSkillInstructions(skill: Skill): Promise<string> {\n return stripSkillFrontmatter(await readFile(skill.source, \"utf8\"));\n}\n","/**\n * Block-body XML escape (ADR D9 — prompt-injection defence).\n *\n * Order matters: `&` MUST be escaped first so subsequent `<`/`>` replacements\n * do not double-encode the `&` characters they introduce.\n *\n * @internal\n */\nexport const escapeBlockBody = (s: string): string =>\n s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n","import { escapeBlockBody } from \"../system-prompt/escape.js\";\n\n/**\n * Render the `<skills>` system-prompt block from a skill list.\n *\n * Input is the structural subset `{ name, description }` — the skill BODY is\n * NOT in the type, so it cannot leak into the prompt. Both fields are passed\n * through `escapeBlockBody` to neutralise prompt-injection vectors hidden in\n * user-controlled SKILL.md frontmatter (injection-escape ADR).\n *\n * Returns `undefined` for an empty list so the caller can omit the block.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport function buildSkillsBlock(\n skills: ReadonlyArray<{ name: string; description: string }>,\n): string | undefined {\n if (skills.length === 0) return undefined;\n const lines = skills.map(\n (skill) => ` - ${escapeBlockBody(skill.name)}: ${escapeBlockBody(skill.description)}`,\n );\n return `<skills>\\n${lines.join(\"\\n\")}\\n</skills>`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/runtime/skills/skill-frontmatter.ts","../src/internal/runtime/skills/discover-skills.ts","../src/internal/runtime/system-prompt/escape.ts","../src/internal/runtime/skills/skills-block.ts"],"names":[],"mappings":"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","file":"chunk-LSURFRJA.js","sourcesContent":["import { ConfigurationError } from \"../../../errors.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\n\ntype StringFields = Record<string, string | undefined>;\n\n/** Narrow a FrontmatterValue to string; non-strings + undefined → undefined. */\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n return typeof v === \"string\" ? v : undefined;\n}\n\n/** Coerce parser output to legacy string-only shape (skill schema is all-string). */\nfunction toStringFields(raw: Record<string, FrontmatterValue | undefined>): StringFields {\n const out: StringFields = {};\n for (const [k, v] of Object.entries(raw)) out[k] = asString(v);\n return out;\n}\n\n/**\n * Strict skill frontmatter schema (ADR D10).\n *\n * Required: `name`, `description`.\n * Optional: `category`, `dependencies` (comma-separated string in the\n * simple-YAML dialect — parsed to `string[]`).\n *\n * Unknown fields are ignored (forward-compat). Malformed YAML or missing\n * required fields surface as `ConfigurationError` with one of the typed\n * codes below.\n *\n * @internal\n */\nexport interface SkillFrontmatter {\n name: string;\n description: string;\n category?: string;\n dependencies?: string[];\n /**\n * `requires-bins: git, rg` (B-546) — executables the skill needs on PATH. A skill whose binary is\n * absent is not offered to the model; `skills.get(name)` still resolves it.\n */\n requiresBins?: string[];\n /**\n * `disable-model-invocation: true` — the model is not told this skill exists.\n *\n * The SDK owns this boundary: a skill reaches the model through exactly one place,\n * `buildSystemPromptContext`, so the declaration is enforceable here. It is a DISCLOSURE rule,\n * not an execution rule — `skills.get(name)` still resolves it, because a caller naming a skill\n * has already made the decision the field exists to keep away from the model.\n */\n disableModelInvocation?: boolean;\n /**\n * `user-invocable: false` — carried, deliberately not enforced here.\n *\n * This SDK has no user-facing invocation surface for skills; there is no slash command. Reading\n * `agent.skills.list()` as \"the user\" would be a guess — a host may call it to build a picker or\n * to introspect, and the two want opposite answers. So the declaration travels to the host that\n * does know, instead of being discarded (the defect) or enforced against an invented boundary.\n */\n userInvocable?: boolean;\n}\n\n/**\n * Parse a SKILL.md file body into validated frontmatter.\n *\n * @throws ConfigurationError(code: \"missing_frontmatter\") — no `---` block at file head.\n * @throws ConfigurationError(code: \"schema_invalid\") — YAML malformed OR required field missing.\n *\n * @internal\n */\nexport function parseSkillFrontmatter(raw: string, fallbackName: string): SkillFrontmatter {\n const parsed = extractAndParseFrontmatter(raw, fallbackName);\n const fields = toStringFields(parsed);\n const name = resolveName(fields, fallbackName);\n ensureRequiredFields(fields, name);\n return buildFrontmatter(fields, parsed, name);\n}\n\n/**\n * SE20 — return a SKILL.md's BODY (everything after the frontmatter block), trimmed.\n * When there is no frontmatter block, the whole file is the body. Reuses the same\n * frontmatter regex as {@link parseSkillFrontmatter} (DRY).\n */\nexport function stripSkillFrontmatter(raw: string): string {\n const match = /^---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/.exec(raw);\n return (match === null ? raw : raw.slice(match[0].length)).trim();\n}\n\nfunction extractAndParseFrontmatter(\n raw: string,\n fallbackName: string,\n): Record<string, FrontmatterValue | undefined> {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {\n code: \"missing_frontmatter\",\n });\n }\n const frontmatter = match[1] ?? \"\";\n // EC-5: guard against syntactically invalid frontmatter so the loader\n // surfaces schema_invalid rather than crashing.\n try {\n return parseSimpleYaml(frontmatter);\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConfigurationError(\n `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,\n { code: \"schema_invalid\", cause },\n );\n }\n}\n\nfunction resolveName(fields: StringFields, fallbackName: string): string {\n if (hasContent(fields.name)) return fields.name;\n if (hasContent(fallbackName)) return fallbackName;\n throw new ConfigurationError(\"Skill at unknown path is missing required field: name\", {\n code: \"schema_invalid\",\n });\n}\n\nfunction ensureRequiredFields(fields: StringFields, name: string): void {\n if (!hasContent(fields.description)) {\n throw new ConfigurationError(`Skill ${name} is missing required field: description`, {\n code: \"schema_invalid\",\n });\n }\n}\n\nfunction buildFrontmatter(\n fields: StringFields,\n parsed: Record<string, FrontmatterValue | undefined>,\n name: string,\n): SkillFrontmatter {\n const description = fields.description;\n if (description === undefined) {\n // ensureRequiredFields already threw; this is unreachable but satisfies TS\n throw new ConfigurationError(`Skill ${name} missing description`, { code: \"schema_invalid\" });\n }\n const result: SkillFrontmatter = { name, description };\n if (hasContent(fields.category)) result.category = fields.category;\n const deps = parseDependencies(fields.dependencies);\n if (deps !== undefined) result.dependencies = deps;\n const bins = parseDependencies(fields[\"requires-bins\"]);\n if (bins !== undefined) result.requiresBins = bins;\n const disable = readAuthorizationFlag(parsed, \"disable-model-invocation\", name);\n if (disable !== undefined) result.disableModelInvocation = disable;\n const invocable = readAuthorizationFlag(parsed, \"user-invocable\", name);\n if (invocable !== undefined) result.userInvocable = invocable;\n return result;\n}\n\n/**\n * Read one boolean authorization flag, refusing a value this parser cannot represent.\n *\n * Unknown frontmatter keys are ignored here for forward compatibility, and that is right for\n * metadata. It is wrong for these two: the simple-YAML dialect coerces only the literals `true`\n * and `false`, so `disable-model-invocation: yes` — a valid YAML boolean — arrives as the STRING\n * `\"yes\"`, compares unequal to `true`, and the skill is disclosed to the model. The author wrote a\n * restriction and got the default.\n *\n * A restriction that fails open is worse than one that is absent, because the author stops looking.\n * So the value is refused and named, and the run stops on the line that caused it.\n */\nfunction readAuthorizationFlag(\n parsed: Record<string, FrontmatterValue | undefined>,\n key: string,\n name: string,\n): boolean | undefined {\n const value = parsed[key];\n if (value === undefined) return undefined;\n if (typeof value === \"boolean\") return value;\n throw new ConfigurationError(\n `Skill ${name}: \"${key}\" must be true or false (got ${JSON.stringify(value)}) — ` +\n `this dialect reads only the literals \\`true\\` and \\`false\\`, and a value it cannot read ` +\n `would leave the skill disclosed`,\n { code: \"schema_invalid\" },\n );\n}\n\nfunction parseDependencies(raw: string | undefined): string[] | undefined {\n if (!hasContent(raw)) return undefined;\n const deps = (raw as string)\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return deps.length > 0 ? deps : undefined;\n}\n\nfunction hasContent(value: string | undefined): value is string {\n return value !== undefined && value.trim().length > 0;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport { assertNoSymlinkEscape, safePathJoin } from \"../../security/path-guard.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { parseSkillFrontmatter, stripSkillFrontmatter } from \"./skill-frontmatter.js\";\n\n/**\n * A discovered skill's metadata. The skill BODY is never included — only the\n * strict frontmatter fields plus the resolved `source` path.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport interface Skill {\n name: string;\n description: string;\n /** Absolute path to the discovered `SKILL.md`. */\n source: string;\n category?: string;\n dependencies?: string[];\n /** `requires-bins` — executables that must be on PATH for the model to be offered this skill. */\n requiresBins?: string[];\n /** `disable-model-invocation: true` — the model is never told this skill exists. */\n disableModelInvocation?: boolean;\n /** `user-invocable: false` — carried for a host that has a picker; this SDK has no such surface. */\n userInvocable?: boolean;\n}\n\n/**\n * Information passed to `onInvalidSkill` when a `SKILL.md` is present but its\n * frontmatter is malformed (missing required field or invalid YAML).\n *\n * @public\n */\nexport interface InvalidSkillInfo {\n /** The skill directory name (used as the fallback skill name). */\n name: string;\n /** Absolute path to the offending `SKILL.md`. */\n source: string;\n /** Typed reason: `missing_frontmatter` or `schema_invalid`. */\n code: string;\n message: string;\n}\n\n/**\n * Options for {@link discoverSkills}.\n *\n * @public\n */\nexport interface DiscoverSkillsOptions {\n /**\n * Called once per directory that contains a `SKILL.md` with malformed\n * frontmatter. The skill is excluded from the result; discovery continues\n * (strict-frontmatter ADR / EC-5). A directory WITHOUT a `SKILL.md` is NOT a\n * malformed skill and does not trigger this callback.\n *\n * Default: no-op (a library primitive must not write to the consumer's\n * stderr by default).\n */\n onInvalidSkill?: (info: InvalidSkillInfo) => void;\n}\n\n/**\n * Discover `SKILL.md` skills under an arbitrary directory.\n *\n * For each immediate subdirectory `<dir>/<name>/` containing a `SKILL.md`, the\n * file's strict YAML frontmatter is parsed (`name`/`description` required;\n * `category`/`dependencies` optional). Malformed skills are skipped (optionally\n * reported via {@link DiscoverSkillsOptions.onInvalidSkill}); a subdirectory\n * whose realpath escapes `dir` (via symlink) is skipped (symlink-escape guard,\n * reusing `@theokit/sdk/path-safety`).\n *\n * NEVER throws: a missing, unreadable, or non-directory `dir` yields `[]`.\n *\n * Discovery order follows the filesystem `readdir` order (OS-dependent). Sort\n * the result before {@link buildSkillsBlock} if a stable block order matters.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport async function discoverSkills(\n dir: string,\n options?: DiscoverSkillsOptions,\n): Promise<Skill[]> {\n let entries: Awaited<ReturnType<typeof readWorkspaceDir>>;\n try {\n entries = await readWorkspaceDir(dir, \"skills_read_error\", \"skills directory\");\n } catch {\n // never-throw contract: unreadable / not-a-directory → no skills (EC-1)\n return [];\n }\n\n const skills: Skill[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n let skillDir: string;\n try {\n skillDir = safePathJoin(dir, entry.name);\n assertNoSymlinkEscape(skillDir, dir);\n } catch {\n continue;\n }\n const skillPath = join(skillDir, \"SKILL.md\");\n let raw: string;\n try {\n raw = await readFile(skillPath, \"utf8\");\n } catch {\n // no SKILL.md in this subdir → not a skill, not an error (EC-2)\n continue;\n }\n const skill = tryParseSkill(raw, entry.name, skillPath, options);\n if (skill !== undefined) skills.push(skill);\n }\n return skills;\n}\n\nfunction tryParseSkill(\n raw: string,\n fallbackName: string,\n source: string,\n options: DiscoverSkillsOptions | undefined,\n): Skill | undefined {\n try {\n const frontmatter = parseSkillFrontmatter(raw, fallbackName);\n const skill: Skill = {\n name: frontmatter.name,\n description: frontmatter.description,\n source,\n };\n if (frontmatter.category !== undefined) skill.category = frontmatter.category;\n if (frontmatter.dependencies !== undefined) skill.dependencies = frontmatter.dependencies;\n // The authorization flags travel with the record. Dropping them here would reproduce the defect\n // one layer down: the parser would read the declaration and the discovery result would not\n // carry it, which is indistinguishable from never having parsed it.\n if (frontmatter.requiresBins !== undefined) skill.requiresBins = frontmatter.requiresBins;\n if (frontmatter.disableModelInvocation !== undefined) {\n skill.disableModelInvocation = frontmatter.disableModelInvocation;\n }\n if (frontmatter.userInvocable !== undefined) skill.userInvocable = frontmatter.userInvocable;\n return skill;\n } catch (cause) {\n if (cause instanceof ConfigurationError) {\n options?.onInvalidSkill?.({\n name: fallbackName,\n source,\n code: cause.code ?? \"unknown\",\n message: cause.message,\n });\n return undefined;\n }\n throw cause;\n }\n}\n\n/**\n * Read the BODY of a discovered skill — everything after its frontmatter.\n *\n * A thin selector over {@link discoverSkills} rather than a second reader, which is the same\n * relationship `loadSubagentDefinition` has to `discoverSubagents` in the sibling domain: one\n * parser is the point.\n *\n * ## Why this exists rather than a field on `Skill`\n *\n * `Skill` documents that *\"the skill BODY is never included\"*. That is a written contract with no\n * written reason, and widening it on a guess about the reason is not a trade worth making — a\n * catalog you can put in a prompt without carrying every body is the likely intent, and this keeps\n * that shape intact for whoever relied on it.\n *\n * The body was never expensive to obtain: `discoverSkills` already reads each file in full and\n * discards everything but the frontmatter. What was missing was a door that hands it over.\n *\n * ## What it is for\n *\n * Turning a discovered skill into an inline one — `SkillsSettings.inline` requires `instructions`,\n * and without this the only route was to open `source` and split the frontmatter by hand. That is a\n * second implementation of this module's own convention, and it would fail SILENTLY if the format\n * moved: the frontmatter would land inside the instructions and nothing would say so.\n *\n * Reported by the `theocode` session, which needed exactly that to give an operator's\n * `~/.theokit/skills/` to an agent through the SDK's own parser.\n *\n * @param skill - a record returned by {@link discoverSkills}; its `source` is read.\n * @returns the trimmed body. A file that is all frontmatter yields an empty string.\n * @throws if `source` is unreadable — unlike discovery, which skips what it cannot read, a caller\n * naming ONE skill has asked about that skill and an empty string would answer a question it did\n * not ask.\n * @public\n */\nexport async function loadSkillInstructions(skill: Skill): Promise<string> {\n return stripSkillFrontmatter(await readFile(skill.source, \"utf8\"));\n}\n","/**\n * Block-body XML escape (ADR D9 — prompt-injection defence).\n *\n * Order matters: `&` MUST be escaped first so subsequent `<`/`>` replacements\n * do not double-encode the `&` characters they introduce.\n *\n * @internal\n */\nexport const escapeBlockBody = (s: string): string =>\n s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n","import { escapeBlockBody } from \"../system-prompt/escape.js\";\n\n/**\n * Render the `<skills>` system-prompt block from a skill list.\n *\n * Input is the structural subset `{ name, description }` — the skill BODY is\n * NOT in the type, so it cannot leak into the prompt. Both fields are passed\n * through `escapeBlockBody` to neutralise prompt-injection vectors hidden in\n * user-controlled SKILL.md frontmatter (injection-escape ADR).\n *\n * Returns `undefined` for an empty list so the caller can omit the block.\n *\n * Public via `@theokit/sdk/skills`.\n *\n * @public\n */\nexport function buildSkillsBlock(\n skills: ReadonlyArray<{ name: string; description: string }>,\n): string | undefined {\n if (skills.length === 0) return undefined;\n const lines = skills.map(\n (skill) => ` - ${escapeBlockBody(skill.name)}: ${escapeBlockBody(skill.description)}`,\n );\n return `<skills>\\n${lines.join(\"\\n\")}\\n</skills>`;\n}\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var chunkQOGNZMHD_cjs = require('./chunk-QOGNZMHD.cjs');
|
|
4
4
|
var chunkU3DW3WDE_cjs = require('./chunk-U3DW3WDE.cjs');
|
|
5
5
|
var chunkCEGU5AVD_cjs = require('./chunk-CEGU5AVD.cjs');
|
|
6
|
-
var
|
|
6
|
+
var node_async_hooks = require('node:async_hooks');
|
|
7
7
|
|
|
8
8
|
// src/internal/llm/credential-pool-types.ts
|
|
9
9
|
var COOLDOWN_MS = {
|
|
@@ -264,7 +264,7 @@ function dedupeByToken(entries) {
|
|
|
264
264
|
}
|
|
265
265
|
return out;
|
|
266
266
|
}
|
|
267
|
-
var credentialPoolStore = new
|
|
267
|
+
var credentialPoolStore = new node_async_hooks.AsyncLocalStorage();
|
|
268
268
|
async function withCredentialPool(pools, fn) {
|
|
269
269
|
return credentialPoolStore.run(pools, fn);
|
|
270
270
|
}
|
|
@@ -276,5 +276,5 @@ exports.CredentialPool = CredentialPool;
|
|
|
276
276
|
exports.currentCredentialPool = currentCredentialPool;
|
|
277
277
|
exports.newPooledCredential = newPooledCredential;
|
|
278
278
|
exports.withCredentialPool = withCredentialPool;
|
|
279
|
-
//# sourceMappingURL=chunk-
|
|
280
|
-
//# sourceMappingURL=chunk-
|
|
279
|
+
//# sourceMappingURL=chunk-MJGY3E3C.cjs.map
|
|
280
|
+
//# sourceMappingURL=chunk-MJGY3E3C.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/llm/credential-pool-types.ts","../src/internal/llm/credential-pool.ts","../src/internal/llm/credential-pool-context.ts"],"names":["ConfigurationError","sleepWithAbort","withCwdMutex","picked","AsyncLocalStorage"],"mappings":"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","file":"chunk-55LLQFHW.cjs","sourcesContent":["/**\n * Types for the credential pool subsystem (ADRs D123-D133).\n *\n * Pool a developer's multiple API keys for the SAME provider so the SDK\n * can rotate transparently on HTTP 429/402/401 instead of jumping\n * straight to a different provider via `FallbackLlmClient`.\n *\n * @internal\n */\n\n/**\n * Rotation strategy when picking the next credential from a pool.\n *\n * - `fill_first` (default): use entries[0] until exhausted, then [1]…\n * - `round_robin`: rotate after each `select()`\n * - `least_used`: pick the entry with the lowest `requestCount`\n * - `random`: random pick among healthy entries\n *\n * Closed enum per ADR D124 — adding a strategy is an explicit semver\n * minor that updates `selectByStrategy` exhaustively.\n *\n * @internal\n */\nexport type CredentialPoolStrategy = \"fill_first\" | \"round_robin\" | \"least_used\" | \"random\";\n\n/** Health flag tracked per credential. */\nexport type CredentialStatus = \"ok\" | \"exhausted\";\n\n/**\n * Provenance of a credential — where it came from when seeded.\n * Free-form string; conventional prefixes:\n *\n * - `env:<VAR_NAME>` — auto-discovered from environment variable\n * - `manual` — added via explicit `apiKeys` config\n * - `explicit-apikey` — wrapped from `AgentOptions.apiKey` single-key path (D132)\n */\nexport type CredentialSource = string;\n\n/**\n * Single credential entry inside a {@link CredentialPool}.\n *\n * `accessToken` is the sensitive field — must never be logged unredacted.\n * The lint gate `tests/lint/no-unredacted-pool-token.test.ts` enforces this.\n *\n * @internal\n */\nexport interface PooledCredential {\n /** Stable identifier (uuid v4) for log lines + telemetry. */\n id: string;\n /** Human-readable label for `Theokit.credentialPool.list()`. */\n label: string;\n /** Provider name (matches `ProviderProfile.name`). */\n provider: string;\n /** Sort key; lower = earlier in fill_first. Unique within a pool. */\n priority: number;\n /** Provenance string. */\n source: CredentialSource;\n /** Sensitive API key. NEVER LOG UNREDACTED. */\n accessToken: string;\n /** Current health. */\n lastStatus: CredentialStatus;\n /** Epoch ms of the last status change. */\n lastStatusAt: number | undefined;\n /** HTTP code that caused exhaustion (401/402/429). */\n lastErrorCode: number | undefined;\n /** Provider-supplied \"retry after this epoch ms\" hint. Overrides cooldown defaults. */\n lastErrorResetAt: number | undefined;\n /** Bumped per successful `select()` for `least_used` strategy. Lazy-persisted. */\n requestCount: number;\n}\n\n/**\n * Serializable snapshot of a single provider's pool. Used by the\n * persistence layer (T2.1) to read/write `~/.theokit/credential-pool.json`.\n *\n * @internal\n */\nexport interface CredentialPoolSnapshot {\n provider: string;\n strategy: CredentialPoolStrategy;\n entries: PooledCredential[];\n}\n\n/**\n * Cooldown ladder by HTTP error code (ADR D125). Provider-supplied\n * `lastErrorResetAt` overrides these defaults when present.\n *\n * @internal\n */\nexport const COOLDOWN_MS: Readonly<Record<number, number>> = {\n 401: 5 * 60 * 1000, // 5 minutes — OAuth refresh can recover quickly\n 402: 60 * 60 * 1000, // 1 hour — billing quota typically hourly+\n 429: 60 * 60 * 1000, // 1 hour — daily-rate-limit windows\n};\n\n/** Default cooldown when no specific code matches. */\nexport const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;\n","/**\n * In-memory credential pool with strategy-based rotation (ADRs D123-D133).\n *\n * Pure logic. No I/O — persistence lives in `internal/persistence/\n * credential-pool-store.ts`. The pool is the single source of truth for\n * rotation state within a process; if it diverges from disk (e.g.,\n * process killed mid-rotation per EC-I), the next save heals the\n * inconsistency — at the cost of one extra HTTP 429 per crash.\n *\n * Thread safety: the pool uses an in-process async-aware mutex\n * (`cwd-mutex` D9 keyed by `credential-pool:${provider}`). Cross-process\n * safety happens at the persistence layer via `withFileLock` (D61).\n * Two Node processes sharing the same file may double-pick momentarily\n * (worst case = 1 extra 429 per race) — acceptable per ADR D128.\n *\n * @internal\n */\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { withCwdMutex } from \"../persistence/cwd-mutex.js\";\nimport {\n COOLDOWN_MS,\n type CredentialPoolSnapshot,\n type CredentialPoolStrategy,\n DEFAULT_COOLDOWN_MS,\n type PooledCredential,\n} from \"./credential-pool-types.js\";\nimport { sleepWithAbort } from \"./retry.js\";\n\n/**\n * Construct a `PooledCredential` with sane defaults.\n *\n * @internal\n */\nexport function newPooledCredential(args: {\n provider: string;\n accessToken: string;\n priority: number;\n source: string;\n label?: string;\n}): PooledCredential {\n return {\n id: globalThis.crypto.randomUUID(),\n label: args.label ?? labelFromSource(args.source, args.accessToken),\n provider: args.provider,\n priority: args.priority,\n source: args.source,\n accessToken: args.accessToken,\n lastStatus: \"ok\",\n lastStatusAt: undefined,\n lastErrorCode: undefined,\n lastErrorResetAt: undefined,\n requestCount: 0,\n };\n}\n\n/**\n * Build a human-readable label from the credential source. Used for\n * `Theokit.credentialPool.list()` UX. NEVER includes the raw token.\n *\n * @internal\n */\nfunction labelFromSource(source: string, accessToken: string): string {\n if (source.startsWith(\"env:\")) return source.slice(4);\n const masked =\n accessToken.length > 8 ? `${accessToken.slice(0, 4)}…${accessToken.slice(-4)}` : \"***\";\n return `${source}:${masked}`;\n}\n\n/**\n * Per-provider credential pool. Construct with one or more entries\n * sorted by priority. Throws `ConfigurationError` on empty input.\n *\n * EC-C: identical `accessToken`s are deduplicated during construction\n * (3 copies of \"k1\" → 1 entry). Otherwise an exhaustion event would\n * mark \"k1\" exhausted 3× wastefully.\n *\n * @internal\n */\nexport class CredentialPool {\n readonly provider: string;\n private readonly strategy: CredentialPoolStrategy;\n private entries: PooledCredential[];\n\n constructor(\n provider: string,\n entries: PooledCredential[],\n strategy: CredentialPoolStrategy = \"fill_first\",\n ) {\n if (entries.length === 0) {\n throw new ConfigurationError(\n `credential-pool: cannot construct empty pool for provider \"${provider}\"`,\n { code: \"credential_pool_empty\" },\n );\n }\n this.provider = provider;\n this.strategy = strategy;\n // EC-C: dedupe by accessToken before sort.\n this.entries = dedupeByToken(entries).sort((a, b) => a.priority - b.priority);\n }\n\n /** True if the pool was seeded with at least one entry. */\n hasCredentials(): boolean {\n return this.entries.length > 0;\n }\n\n /** True iff at least one entry is healthy (auto-healed if cooldown expired). */\n hasAvailable(): boolean {\n return this.availableEntries().length > 0;\n }\n\n /**\n * Earliest `lastErrorResetAt` across all entries, or `undefined` if no\n * entry is in cooldown. Used by `waitForAvailable` to compute the\n * upper bound of each jittered sleep window.\n *\n * @internal\n */\n earliestResetAt(): number | undefined {\n let earliest: number | undefined;\n for (const entry of this.entries) {\n if (entry.lastStatus === \"ok\") continue;\n if (entry.lastErrorResetAt === undefined) continue;\n if (earliest === undefined || entry.lastErrorResetAt < earliest) {\n earliest = entry.lastErrorResetAt;\n }\n }\n return earliest;\n }\n\n /**\n * Wait until at least one entry becomes healthy OR `maxWaitMs` elapses\n * OR `signal` aborts. Returns `true` if a healthy entry is available\n * when the call returns, `false` otherwise.\n *\n * T3.9 — Reconnect storm prevention. Concurrent callers each sleep a\n * random fraction of the window to the earliest cooldown reset (full\n * jitter — AWS Brooker 2015) so they do not all wake at the exact\n * instant cooldown expires and hammer the upstream simultaneously.\n *\n * The `sleeper` parameter is a dependency-injection seam for tests:\n * callers in production omit it and the default `setTimeout`-based\n * sleeper is used. The seam avoids the `vi.useFakeTimers()` mismatch\n * that blocked T3.4's wiring.\n *\n * @internal\n */\n async waitForAvailable(\n signal: AbortSignal,\n opts: {\n maxWaitMs: number;\n sleeper?: (ms: number, signal: AbortSignal) => Promise<void>;\n /**\n * Clock seam, the companion to `sleeper`. Production omits it and gets `Date.now`.\n *\n * `sleeper` alone was not enough to make this loop testable: a test could replace the SLEEP\n * but the deadline check still read real wall-clock, so the one case that exercises timeout\n * had to set `maxWaitMs: 5` and race the scheduler — with a comment in the committed file\n * saying so (\"we can't easily ... spec is wall-clock based\"). A sleeper that also advances a\n * virtual clock closes the loop deterministically, which is what `rules/testing.md` § 6 asks\n * for when it says to inject the clock rather than sleep.\n */\n now?: () => number;\n },\n ): Promise<boolean> {\n if (signal.aborted) return false;\n if (this.hasAvailable()) return true;\n const now = opts.now ?? Date.now;\n return await this.pollUntilAvailable(\n signal,\n now() + opts.maxWaitMs,\n opts.sleeper ?? sleepWithAbort,\n now,\n );\n }\n\n /**\n * The polling half of `waitForAvailable`, with both seams already resolved.\n *\n * Its own method because resolving two optional seams and running the loop in one body put the\n * function over the cognitive-complexity gate. The split is also the honest one: everything above\n * is a guard, everything here is the wait.\n */\n private async pollUntilAvailable(\n signal: AbortSignal,\n deadline: number,\n sleeper: (ms: number, signal: AbortSignal) => Promise<void>,\n now: () => number,\n ): Promise<boolean> {\n while (now() < deadline) {\n if (signal.aborted) return false;\n await sleeper(this.computeJitteredSleepMs(deadline, now), signal);\n if (signal.aborted) return false;\n if (this.hasAvailable()) return true;\n }\n return this.hasAvailable();\n }\n\n /**\n * Compute the next jittered sleep window (ms) for `waitForAvailable`.\n * Full-jitter cap: at most (a) time until the earliest reset (plus a\n * 50 ms grace so the entry actually heals before re-probe), or (b) the\n * remaining wait window, whichever is smaller. When no entry has a\n * known reset (rare — only if `markExhaustedAndRotate` was called\n * without `resetAtMs`), fall back to a 250 ms ceiling so we stay\n * responsive without hot-looping.\n */\n private computeJitteredSleepMs(deadline: number, now: () => number = Date.now): number {\n const resetAt = this.earliestResetAt();\n const remaining = deadline - now();\n const ceiling =\n resetAt === undefined\n ? Math.min(remaining, 250)\n : Math.min(remaining, Math.max(0, resetAt - now()) + 50);\n return Math.max(0, Math.floor(Math.random() * ceiling));\n }\n\n /** Live (frozen) view of all entries. */\n list(): readonly PooledCredential[] {\n return this.entries;\n }\n\n /**\n * Pick a healthy entry according to the configured strategy.\n * Mutates `requestCount` and (for round_robin) entry order.\n * Returns null if every entry is in cooldown.\n *\n * @internal\n */\n async select(): Promise<PooledCredential | null> {\n return withCwdMutex(`credential-pool:${this.provider}`, async () => this.selectLocked());\n }\n\n /**\n * Mark the entry whose id matches `entryId` as exhausted with the\n * appropriate cooldown, then return the next healthy entry (or null).\n * If `entryId` is undefined, marks all currently-active entries (rare —\n * only used by admin reset scenarios).\n *\n * @internal\n */\n async markExhaustedAndRotate(args: {\n entryId: string;\n statusCode: number;\n resetAtMs?: number;\n }): Promise<PooledCredential | null> {\n return withCwdMutex(`credential-pool:${this.provider}`, async () => {\n const entry = this.entries.find((e) => e.id === args.entryId);\n if (entry !== undefined) {\n const now = Date.now();\n const cooldown = COOLDOWN_MS[args.statusCode] ?? DEFAULT_COOLDOWN_MS;\n entry.lastStatus = \"exhausted\";\n entry.lastStatusAt = now;\n entry.lastErrorCode = args.statusCode;\n // EC-5: negative or stale resetAt → ignore, fall through to default.\n entry.lastErrorResetAt =\n args.resetAtMs !== undefined && args.resetAtMs > now ? args.resetAtMs : now + cooldown;\n }\n return this.selectLocked();\n });\n }\n\n /** Admin escape hatch: clear all cooldowns. */\n async resetAll(): Promise<void> {\n await withCwdMutex(`credential-pool:${this.provider}`, async () => {\n for (const e of this.entries) {\n e.lastStatus = \"ok\";\n e.lastStatusAt = undefined;\n e.lastErrorCode = undefined;\n e.lastErrorResetAt = undefined;\n }\n });\n }\n\n /** Materialize for persistence. */\n toSnapshot(): CredentialPoolSnapshot {\n return {\n provider: this.provider,\n strategy: this.strategy,\n entries: this.entries.map((e) => ({ ...e })),\n };\n }\n\n /** Hydrate from a previously-saved snapshot. */\n static fromSnapshot(snapshot: CredentialPoolSnapshot): CredentialPool {\n return new CredentialPool(snapshot.provider, snapshot.entries, snapshot.strategy);\n }\n\n // ─── Internal helpers (always called inside the mutex) ───\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: strategy dispatch must branch on 4 enum values (random/least_used/round_robin/fill_first) with 1-entry short-circuits per strategy. Each branch is 5-10 lines of focused logic; extracting per-strategy methods fragments the linear narrative.\n private selectLocked(): PooledCredential | null {\n const available = this.availableEntries();\n if (available.length === 0) return null;\n\n if (this.strategy === \"random\") {\n const idx = Math.floor(Math.random() * available.length);\n const picked = available[idx];\n if (picked === undefined) return null; // unreachable; satisfies strict\n picked.requestCount += 1;\n return picked;\n }\n\n if (this.strategy === \"least_used\" && available.length > 1) {\n // EC-3: tie-break by priority ascending (first wins).\n const picked = [...available].sort(\n (a, b) => a.requestCount - b.requestCount || a.priority - b.priority,\n )[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n return picked;\n }\n\n if (this.strategy === \"round_robin\" && available.length > 1) {\n // Pick head; move it to the end of `entries` so next call picks the next.\n const picked = available[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n const rest = this.entries.filter((e) => e.id !== picked.id);\n // Re-assign priorities: rotated entry gets the highest priority value\n // so it sorts last after `fromSnapshot` rehydration. This makes EC-F\n // (round-robin state survives save/load) work without a separate field.\n const maxPriority = this.entries.length - 1;\n rest.forEach((e, i) => {\n e.priority = i;\n });\n picked.priority = maxPriority;\n this.entries = [...rest, picked];\n return picked;\n }\n\n // fill_first (default) and 1-entry edge case for round_robin/least_used\n const picked = available[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n return picked;\n }\n\n private availableEntries(): PooledCredential[] {\n const now = Date.now();\n const out: PooledCredential[] = [];\n for (const entry of this.entries) {\n if (entry.lastStatus === \"ok\") {\n out.push(entry);\n continue;\n }\n // EC-1: auto-heal expired cooldowns inline.\n const resetAt = entry.lastErrorResetAt;\n if (resetAt !== undefined && now >= resetAt) {\n entry.lastStatus = \"ok\";\n entry.lastStatusAt = undefined;\n entry.lastErrorCode = undefined;\n entry.lastErrorResetAt = undefined;\n out.push(entry);\n }\n }\n return out;\n }\n}\n\n/**\n * Drop duplicates by `accessToken`. Keeps the first occurrence (lowest\n * priority wins after sort). EC-C from the edge-case review.\n */\nfunction dedupeByToken(entries: PooledCredential[]): PooledCredential[] {\n const seen = new Set<string>();\n const out: PooledCredential[] = [];\n for (const e of entries) {\n if (seen.has(e.accessToken)) continue;\n seen.add(e.accessToken);\n out.push(e);\n }\n return out;\n}\n","/**\n * Per-fork credential-pool inheritance via `AsyncLocalStorage` (ADR D131).\n *\n * Mirrors the whitelist context pattern from D111\n * (`runtime/async-local-storage.ts`). Forked sub-agents inherit the\n * parent's pool *by reference* so concurrent rotations all observe\n * the same cooldown state — the desired behavior per Hermes parity.\n *\n * @internal\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type { CredentialPool } from \"./credential-pool.js\";\n\n/** ALS slot — keyed by provider name → pool. */\nconst credentialPoolStore = new AsyncLocalStorage<Map<string, CredentialPool>>();\n\n/**\n * Run `fn` with `pools` as the active credential-pool registry for the\n * current async context. Nested calls shadow the outer map (parent\n * pools are not visible to children when the child sets its own).\n *\n * @internal\n */\nexport async function withCredentialPool<T>(\n pools: Map<string, CredentialPool>,\n fn: () => Promise<T>,\n): Promise<T> {\n return credentialPoolStore.run(pools, fn);\n}\n\n/**\n * Pool registered for `provider` in the current async context, or\n * `undefined` when no `withCredentialPool(...)` scope is active.\n *\n * @internal\n */\nexport function currentCredentialPool(provider: string): CredentialPool | undefined {\n return credentialPoolStore.getStore()?.get(provider);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/llm/credential-pool-types.ts","../src/internal/llm/credential-pool.ts","../src/internal/llm/credential-pool-context.ts"],"names":["ConfigurationError","sleepWithAbort","withCwdMutex","picked","AsyncLocalStorage"],"mappings":";;;;;;;;AAyFO,IAAM,WAAA,GAAgD;AAAA,EAC3D,GAAA,EAAK,IAAI,EAAA,GAAK,GAAA;AAAA;AAAA,EACd,GAAA,EAAK,KAAK,EAAA,GAAK,GAAA;AAAA;AAAA,EACf,GAAA,EAAK,KAAK,EAAA,GAAK;AAAA;AACjB,CAAA;AAGO,IAAM,mBAAA,GAAsB,KAAK,EAAA,GAAK,GAAA;;;AC9DtC,SAAS,oBAAoB,IAAA,EAMf;AACnB,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,UAAA,CAAW,MAAA,CAAO,UAAA,EAAW;AAAA,IACjC,OAAO,IAAA,CAAK,KAAA,IAAS,gBAAgB,IAAA,CAAK,MAAA,EAAQ,KAAK,WAAW,CAAA;AAAA,IAClE,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,UAAA,EAAY,IAAA;AAAA,IACZ,YAAA,EAAc,MAAA;AAAA,IACd,aAAA,EAAe,MAAA;AAAA,IACf,gBAAA,EAAkB,MAAA;AAAA,IAClB,YAAA,EAAc;AAAA,GAChB;AACF;AAQA,SAAS,eAAA,CAAgB,QAAgB,WAAA,EAA6B;AACpE,EAAA,IAAI,OAAO,UAAA,CAAW,MAAM,GAAG,OAAO,MAAA,CAAO,MAAM,CAAC,CAAA;AACpD,EAAA,MAAM,MAAA,GACJ,WAAA,CAAY,MAAA,GAAS,CAAA,GAAI,GAAG,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,MAAA,EAAI,WAAA,CAAY,KAAA,CAAM,EAAE,CAAC,CAAA,CAAA,GAAK,KAAA;AACnF,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAC5B;AAYO,IAAM,cAAA,GAAN,MAAM,eAAA,CAAe;AAAA,EACjB,QAAA;AAAA,EACQ,QAAA;AAAA,EACT,OAAA;AAAA,EAER,WAAA,CACE,QAAA,EACA,OAAA,EACA,QAAA,GAAmC,YAAA,EACnC;AACA,IAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,MAAA,MAAM,IAAIA,oCAAA;AAAA,QACR,8DAA8D,QAAQ,CAAA,CAAA,CAAA;AAAA,QACtE,EAAE,MAAM,uBAAA;AAAwB,OAClC;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAEhB,IAAA,IAAA,CAAK,OAAA,GAAU,aAAA,CAAc,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,QAAA,GAAW,CAAA,CAAE,QAAQ,CAAA;AAAA,EAC9E;AAAA;AAAA,EAGA,cAAA,GAA0B;AACxB,IAAA,OAAO,IAAA,CAAK,QAAQ,MAAA,GAAS,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,YAAA,GAAwB;AACtB,IAAA,OAAO,IAAA,CAAK,gBAAA,EAAiB,CAAE,MAAA,GAAS,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAA,GAAsC;AACpC,IAAA,IAAI,QAAA;AACJ,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,OAAA,EAAS;AAChC,MAAA,IAAI,KAAA,CAAM,eAAe,IAAA,EAAM;AAC/B,MAAA,IAAI,KAAA,CAAM,qBAAqB,MAAA,EAAW;AAC1C,MAAA,IAAI,QAAA,KAAa,MAAA,IAAa,KAAA,CAAM,gBAAA,GAAmB,QAAA,EAAU;AAC/D,QAAA,QAAA,GAAW,KAAA,CAAM,gBAAA;AAAA,MACnB;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,gBAAA,CACJ,MAAA,EACA,IAAA,EAekB;AAClB,IAAA,IAAI,MAAA,CAAO,SAAS,OAAO,KAAA;AAC3B,IAAA,IAAI,IAAA,CAAK,YAAA,EAAa,EAAG,OAAO,IAAA;AAChC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,IAAO,IAAA,CAAK,GAAA;AAC7B,IAAA,OAAO,MAAM,IAAA,CAAK,kBAAA;AAAA,MAChB,MAAA;AAAA,MACA,GAAA,KAAQ,IAAA,CAAK,SAAA;AAAA,MACb,KAAK,OAAA,IAAWC,gCAAA;AAAA,MAChB;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,kBAAA,CACZ,MAAA,EACA,QAAA,EACA,SACA,GAAA,EACkB;AAClB,IAAA,OAAO,GAAA,KAAQ,QAAA,EAAU;AACvB,MAAA,IAAI,MAAA,CAAO,SAAS,OAAO,KAAA;AAC3B,MAAA,MAAM,QAAQ,IAAA,CAAK,sBAAA,CAAuB,QAAA,EAAU,GAAG,GAAG,MAAM,CAAA;AAChE,MAAA,IAAI,MAAA,CAAO,SAAS,OAAO,KAAA;AAC3B,MAAA,IAAI,IAAA,CAAK,YAAA,EAAa,EAAG,OAAO,IAAA;AAAA,IAClC;AACA,IAAA,OAAO,KAAK,YAAA,EAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,sBAAA,CAAuB,QAAA,EAAkB,GAAA,GAAoB,IAAA,CAAK,GAAA,EAAa;AACrF,IAAA,MAAM,OAAA,GAAU,KAAK,eAAA,EAAgB;AACrC,IAAA,MAAM,SAAA,GAAY,WAAW,GAAA,EAAI;AACjC,IAAA,MAAM,UACJ,OAAA,KAAY,MAAA,GACR,KAAK,GAAA,CAAI,SAAA,EAAW,GAAG,CAAA,GACvB,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,KAAK,GAAA,CAAI,CAAA,EAAG,UAAU,GAAA,EAAK,IAAI,EAAE,CAAA;AAC3D,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,MAAM,IAAA,CAAK,MAAA,EAAO,GAAI,OAAO,CAAC,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,IAAA,GAAoC;AAClC,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAA,GAA2C;AAC/C,IAAA,OAAOC,8BAAA,CAAa,mBAAmB,IAAA,CAAK,QAAQ,IAAI,YAAY,IAAA,CAAK,cAAc,CAAA;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,uBAAuB,IAAA,EAIQ;AACnC,IAAA,OAAOA,8BAAA,CAAa,CAAA,gBAAA,EAAmB,IAAA,CAAK,QAAQ,IAAI,YAAY;AAClE,MAAA,MAAM,KAAA,GAAQ,KAAK,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,IAAA,CAAK,OAAO,CAAA;AAC5D,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,QAAA,MAAM,QAAA,GAAW,WAAA,CAAY,IAAA,CAAK,UAAU,CAAA,IAAK,mBAAA;AACjD,QAAA,KAAA,CAAM,UAAA,GAAa,WAAA;AACnB,QAAA,KAAA,CAAM,YAAA,GAAe,GAAA;AACrB,QAAA,KAAA,CAAM,gBAAgB,IAAA,CAAK,UAAA;AAE3B,QAAA,KAAA,CAAM,gBAAA,GACJ,KAAK,SAAA,KAAc,MAAA,IAAa,KAAK,SAAA,GAAY,GAAA,GAAM,IAAA,CAAK,SAAA,GAAY,GAAA,GAAM,QAAA;AAAA,MAClF;AACA,MAAA,OAAO,KAAK,YAAA,EAAa;AAAA,IAC3B,CAAC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAA,GAA0B;AAC9B,IAAA,MAAMA,8BAAA,CAAa,CAAA,gBAAA,EAAmB,IAAA,CAAK,QAAQ,IAAI,YAAY;AACjE,MAAA,KAAA,MAAW,CAAA,IAAK,KAAK,OAAA,EAAS;AAC5B,QAAA,CAAA,CAAE,UAAA,GAAa,IAAA;AACf,QAAA,CAAA,CAAE,YAAA,GAAe,MAAA;AACjB,QAAA,CAAA,CAAE,aAAA,GAAgB,MAAA;AAClB,QAAA,CAAA,CAAE,gBAAA,GAAmB,MAAA;AAAA,MACvB;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,UAAA,GAAqC;AACnC,IAAA,OAAO;AAAA,MACL,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,OAAA,EAAS,KAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAE,CAAE;AAAA,KAC7C;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,aAAa,QAAA,EAAkD;AACpE,IAAA,OAAO,IAAI,eAAA,CAAe,QAAA,CAAS,UAAU,QAAA,CAAS,OAAA,EAAS,SAAS,QAAQ,CAAA;AAAA,EAClF;AAAA;AAAA;AAAA,EAKQ,YAAA,GAAwC;AAC9C,IAAA,MAAM,SAAA,GAAY,KAAK,gBAAA,EAAiB;AACxC,IAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAEnC,IAAA,IAAI,IAAA,CAAK,aAAa,QAAA,EAAU;AAC9B,MAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,KAAK,MAAA,EAAO,GAAI,UAAU,MAAM,CAAA;AACvD,MAAA,MAAMC,OAAAA,GAAS,UAAU,GAAG,CAAA;AAC5B,MAAA,IAAIA,OAAAA,KAAW,QAAW,OAAO,IAAA;AACjC,MAAAA,QAAO,YAAA,IAAgB,CAAA;AACvB,MAAA,OAAOA,OAAAA;AAAA,IACT;AAEA,IAAA,IAAI,IAAA,CAAK,QAAA,KAAa,YAAA,IAAgB,SAAA,CAAU,SAAS,CAAA,EAAG;AAE1D,MAAA,MAAMA,OAAAA,GAAS,CAAC,GAAG,SAAS,CAAA,CAAE,IAAA;AAAA,QAC5B,CAAC,GAAG,CAAA,KAAM,CAAA,CAAE,eAAe,CAAA,CAAE,YAAA,IAAgB,CAAA,CAAE,QAAA,GAAW,CAAA,CAAE;AAAA,QAC5D,CAAC,CAAA;AACH,MAAA,IAAIA,OAAAA,KAAW,QAAW,OAAO,IAAA;AACjC,MAAAA,QAAO,YAAA,IAAgB,CAAA;AACvB,MAAA,OAAOA,OAAAA;AAAA,IACT;AAEA,IAAA,IAAI,IAAA,CAAK,QAAA,KAAa,aAAA,IAAiB,SAAA,CAAU,SAAS,CAAA,EAAG;AAE3D,MAAA,MAAMA,OAAAA,GAAS,UAAU,CAAC,CAAA;AAC1B,MAAA,IAAIA,OAAAA,KAAW,QAAW,OAAO,IAAA;AACjC,MAAAA,QAAO,YAAA,IAAgB,CAAA;AACvB,MAAA,MAAM,IAAA,GAAO,KAAK,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAOA,OAAAA,CAAO,EAAE,CAAA;AAI1D,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,MAAA,GAAS,CAAA;AAC1C,MAAA,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AACrB,QAAA,CAAA,CAAE,QAAA,GAAW,CAAA;AAAA,MACf,CAAC,CAAA;AACD,MAAAA,QAAO,QAAA,GAAW,WAAA;AAClB,MAAA,IAAA,CAAK,OAAA,GAAU,CAAC,GAAG,IAAA,EAAMA,OAAM,CAAA;AAC/B,MAAA,OAAOA,OAAAA;AAAA,IACT;AAGA,IAAA,MAAM,MAAA,GAAS,UAAU,CAAC,CAAA;AAC1B,IAAA,IAAI,MAAA,KAAW,QAAW,OAAO,IAAA;AACjC,IAAA,MAAA,CAAO,YAAA,IAAgB,CAAA;AACvB,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,gBAAA,GAAuC;AAC7C,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,MAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,OAAA,EAAS;AAChC,MAAA,IAAI,KAAA,CAAM,eAAe,IAAA,EAAM;AAC7B,QAAA,GAAA,CAAI,KAAK,KAAK,CAAA;AACd,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,UAAU,KAAA,CAAM,gBAAA;AACtB,MAAA,IAAI,OAAA,KAAY,MAAA,IAAa,GAAA,IAAO,OAAA,EAAS;AAC3C,QAAA,KAAA,CAAM,UAAA,GAAa,IAAA;AACnB,QAAA,KAAA,CAAM,YAAA,GAAe,MAAA;AACrB,QAAA,KAAA,CAAM,aAAA,GAAgB,MAAA;AACtB,QAAA,KAAA,CAAM,gBAAA,GAAmB,MAAA;AACzB,QAAA,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,MAChB;AAAA,IACF;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACF;AAMA,SAAS,cAAc,OAAA,EAAiD;AACtE,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,MAAM,MAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA,EAAG;AAC7B,IAAA,IAAA,CAAK,GAAA,CAAI,EAAE,WAAW,CAAA;AACtB,IAAA,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,EACZ;AACA,EAAA,OAAO,GAAA;AACT;ACrWA,IAAM,mBAAA,GAAsB,IAAIC,kCAAA,EAA+C;AAS/E,eAAsB,kBAAA,CACpB,OACA,EAAA,EACY;AACZ,EAAA,OAAO,mBAAA,CAAoB,GAAA,CAAI,KAAA,EAAO,EAAE,CAAA;AAC1C;AAQO,SAAS,sBAAsB,QAAA,EAA8C;AAClF,EAAA,OAAO,mBAAA,CAAoB,QAAA,EAAS,EAAG,GAAA,CAAI,QAAQ,CAAA;AACrD","file":"chunk-MJGY3E3C.cjs","sourcesContent":["/**\n * Types for the credential pool subsystem (ADRs D123-D133).\n *\n * Pool a developer's multiple API keys for the SAME provider so the SDK\n * can rotate transparently on HTTP 429/402/401 instead of jumping\n * straight to a different provider via `FallbackLlmClient`.\n *\n * @internal\n */\n\n/**\n * Rotation strategy when picking the next credential from a pool.\n *\n * - `fill_first` (default): use entries[0] until exhausted, then [1]…\n * - `round_robin`: rotate after each `select()`\n * - `least_used`: pick the entry with the lowest `requestCount`\n * - `random`: random pick among healthy entries\n *\n * Closed enum per ADR D124 — adding a strategy is an explicit semver\n * minor that updates `selectByStrategy` exhaustively.\n *\n * @internal\n */\nexport type CredentialPoolStrategy = \"fill_first\" | \"round_robin\" | \"least_used\" | \"random\";\n\n/** Health flag tracked per credential. */\nexport type CredentialStatus = \"ok\" | \"exhausted\";\n\n/**\n * Provenance of a credential — where it came from when seeded.\n * Free-form string; conventional prefixes:\n *\n * - `env:<VAR_NAME>` — auto-discovered from environment variable\n * - `manual` — added via explicit `apiKeys` config\n * - `explicit-apikey` — wrapped from `AgentOptions.apiKey` single-key path (D132)\n */\nexport type CredentialSource = string;\n\n/**\n * Single credential entry inside a {@link CredentialPool}.\n *\n * `accessToken` is the sensitive field — must never be logged unredacted.\n * The lint gate `tests/lint/no-unredacted-pool-token.test.ts` enforces this.\n *\n * @internal\n */\nexport interface PooledCredential {\n /** Stable identifier (uuid v4) for log lines + telemetry. */\n id: string;\n /** Human-readable label for `Theokit.credentialPool.list()`. */\n label: string;\n /** Provider name (matches `ProviderProfile.name`). */\n provider: string;\n /** Sort key; lower = earlier in fill_first. Unique within a pool. */\n priority: number;\n /** Provenance string. */\n source: CredentialSource;\n /** Sensitive API key. NEVER LOG UNREDACTED. */\n accessToken: string;\n /** Current health. */\n lastStatus: CredentialStatus;\n /** Epoch ms of the last status change. */\n lastStatusAt: number | undefined;\n /** HTTP code that caused exhaustion (401/402/429). */\n lastErrorCode: number | undefined;\n /** Provider-supplied \"retry after this epoch ms\" hint. Overrides cooldown defaults. */\n lastErrorResetAt: number | undefined;\n /** Bumped per successful `select()` for `least_used` strategy. Lazy-persisted. */\n requestCount: number;\n}\n\n/**\n * Serializable snapshot of a single provider's pool. Used by the\n * persistence layer (T2.1) to read/write `~/.theokit/credential-pool.json`.\n *\n * @internal\n */\nexport interface CredentialPoolSnapshot {\n provider: string;\n strategy: CredentialPoolStrategy;\n entries: PooledCredential[];\n}\n\n/**\n * Cooldown ladder by HTTP error code (ADR D125). Provider-supplied\n * `lastErrorResetAt` overrides these defaults when present.\n *\n * @internal\n */\nexport const COOLDOWN_MS: Readonly<Record<number, number>> = {\n 401: 5 * 60 * 1000, // 5 minutes — OAuth refresh can recover quickly\n 402: 60 * 60 * 1000, // 1 hour — billing quota typically hourly+\n 429: 60 * 60 * 1000, // 1 hour — daily-rate-limit windows\n};\n\n/** Default cooldown when no specific code matches. */\nexport const DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;\n","/**\n * In-memory credential pool with strategy-based rotation (ADRs D123-D133).\n *\n * Pure logic. No I/O — persistence lives in `internal/persistence/\n * credential-pool-store.ts`. The pool is the single source of truth for\n * rotation state within a process; if it diverges from disk (e.g.,\n * process killed mid-rotation per EC-I), the next save heals the\n * inconsistency — at the cost of one extra HTTP 429 per crash.\n *\n * Thread safety: the pool uses an in-process async-aware mutex\n * (`cwd-mutex` D9 keyed by `credential-pool:${provider}`). Cross-process\n * safety happens at the persistence layer via `withFileLock` (D61).\n * Two Node processes sharing the same file may double-pick momentarily\n * (worst case = 1 extra 429 per race) — acceptable per ADR D128.\n *\n * @internal\n */\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { withCwdMutex } from \"../persistence/cwd-mutex.js\";\nimport {\n COOLDOWN_MS,\n type CredentialPoolSnapshot,\n type CredentialPoolStrategy,\n DEFAULT_COOLDOWN_MS,\n type PooledCredential,\n} from \"./credential-pool-types.js\";\nimport { sleepWithAbort } from \"./retry.js\";\n\n/**\n * Construct a `PooledCredential` with sane defaults.\n *\n * @internal\n */\nexport function newPooledCredential(args: {\n provider: string;\n accessToken: string;\n priority: number;\n source: string;\n label?: string;\n}): PooledCredential {\n return {\n id: globalThis.crypto.randomUUID(),\n label: args.label ?? labelFromSource(args.source, args.accessToken),\n provider: args.provider,\n priority: args.priority,\n source: args.source,\n accessToken: args.accessToken,\n lastStatus: \"ok\",\n lastStatusAt: undefined,\n lastErrorCode: undefined,\n lastErrorResetAt: undefined,\n requestCount: 0,\n };\n}\n\n/**\n * Build a human-readable label from the credential source. Used for\n * `Theokit.credentialPool.list()` UX. NEVER includes the raw token.\n *\n * @internal\n */\nfunction labelFromSource(source: string, accessToken: string): string {\n if (source.startsWith(\"env:\")) return source.slice(4);\n const masked =\n accessToken.length > 8 ? `${accessToken.slice(0, 4)}…${accessToken.slice(-4)}` : \"***\";\n return `${source}:${masked}`;\n}\n\n/**\n * Per-provider credential pool. Construct with one or more entries\n * sorted by priority. Throws `ConfigurationError` on empty input.\n *\n * EC-C: identical `accessToken`s are deduplicated during construction\n * (3 copies of \"k1\" → 1 entry). Otherwise an exhaustion event would\n * mark \"k1\" exhausted 3× wastefully.\n *\n * @internal\n */\nexport class CredentialPool {\n readonly provider: string;\n private readonly strategy: CredentialPoolStrategy;\n private entries: PooledCredential[];\n\n constructor(\n provider: string,\n entries: PooledCredential[],\n strategy: CredentialPoolStrategy = \"fill_first\",\n ) {\n if (entries.length === 0) {\n throw new ConfigurationError(\n `credential-pool: cannot construct empty pool for provider \"${provider}\"`,\n { code: \"credential_pool_empty\" },\n );\n }\n this.provider = provider;\n this.strategy = strategy;\n // EC-C: dedupe by accessToken before sort.\n this.entries = dedupeByToken(entries).sort((a, b) => a.priority - b.priority);\n }\n\n /** True if the pool was seeded with at least one entry. */\n hasCredentials(): boolean {\n return this.entries.length > 0;\n }\n\n /** True iff at least one entry is healthy (auto-healed if cooldown expired). */\n hasAvailable(): boolean {\n return this.availableEntries().length > 0;\n }\n\n /**\n * Earliest `lastErrorResetAt` across all entries, or `undefined` if no\n * entry is in cooldown. Used by `waitForAvailable` to compute the\n * upper bound of each jittered sleep window.\n *\n * @internal\n */\n earliestResetAt(): number | undefined {\n let earliest: number | undefined;\n for (const entry of this.entries) {\n if (entry.lastStatus === \"ok\") continue;\n if (entry.lastErrorResetAt === undefined) continue;\n if (earliest === undefined || entry.lastErrorResetAt < earliest) {\n earliest = entry.lastErrorResetAt;\n }\n }\n return earliest;\n }\n\n /**\n * Wait until at least one entry becomes healthy OR `maxWaitMs` elapses\n * OR `signal` aborts. Returns `true` if a healthy entry is available\n * when the call returns, `false` otherwise.\n *\n * T3.9 — Reconnect storm prevention. Concurrent callers each sleep a\n * random fraction of the window to the earliest cooldown reset (full\n * jitter — AWS Brooker 2015) so they do not all wake at the exact\n * instant cooldown expires and hammer the upstream simultaneously.\n *\n * The `sleeper` parameter is a dependency-injection seam for tests:\n * callers in production omit it and the default `setTimeout`-based\n * sleeper is used. The seam avoids the `vi.useFakeTimers()` mismatch\n * that blocked T3.4's wiring.\n *\n * @internal\n */\n async waitForAvailable(\n signal: AbortSignal,\n opts: {\n maxWaitMs: number;\n sleeper?: (ms: number, signal: AbortSignal) => Promise<void>;\n /**\n * Clock seam, the companion to `sleeper`. Production omits it and gets `Date.now`.\n *\n * `sleeper` alone was not enough to make this loop testable: a test could replace the SLEEP\n * but the deadline check still read real wall-clock, so the one case that exercises timeout\n * had to set `maxWaitMs: 5` and race the scheduler — with a comment in the committed file\n * saying so (\"we can't easily ... spec is wall-clock based\"). A sleeper that also advances a\n * virtual clock closes the loop deterministically, which is what `rules/testing.md` § 6 asks\n * for when it says to inject the clock rather than sleep.\n */\n now?: () => number;\n },\n ): Promise<boolean> {\n if (signal.aborted) return false;\n if (this.hasAvailable()) return true;\n const now = opts.now ?? Date.now;\n return await this.pollUntilAvailable(\n signal,\n now() + opts.maxWaitMs,\n opts.sleeper ?? sleepWithAbort,\n now,\n );\n }\n\n /**\n * The polling half of `waitForAvailable`, with both seams already resolved.\n *\n * Its own method because resolving two optional seams and running the loop in one body put the\n * function over the cognitive-complexity gate. The split is also the honest one: everything above\n * is a guard, everything here is the wait.\n */\n private async pollUntilAvailable(\n signal: AbortSignal,\n deadline: number,\n sleeper: (ms: number, signal: AbortSignal) => Promise<void>,\n now: () => number,\n ): Promise<boolean> {\n while (now() < deadline) {\n if (signal.aborted) return false;\n await sleeper(this.computeJitteredSleepMs(deadline, now), signal);\n if (signal.aborted) return false;\n if (this.hasAvailable()) return true;\n }\n return this.hasAvailable();\n }\n\n /**\n * Compute the next jittered sleep window (ms) for `waitForAvailable`.\n * Full-jitter cap: at most (a) time until the earliest reset (plus a\n * 50 ms grace so the entry actually heals before re-probe), or (b) the\n * remaining wait window, whichever is smaller. When no entry has a\n * known reset (rare — only if `markExhaustedAndRotate` was called\n * without `resetAtMs`), fall back to a 250 ms ceiling so we stay\n * responsive without hot-looping.\n */\n private computeJitteredSleepMs(deadline: number, now: () => number = Date.now): number {\n const resetAt = this.earliestResetAt();\n const remaining = deadline - now();\n const ceiling =\n resetAt === undefined\n ? Math.min(remaining, 250)\n : Math.min(remaining, Math.max(0, resetAt - now()) + 50);\n return Math.max(0, Math.floor(Math.random() * ceiling));\n }\n\n /** Live (frozen) view of all entries. */\n list(): readonly PooledCredential[] {\n return this.entries;\n }\n\n /**\n * Pick a healthy entry according to the configured strategy.\n * Mutates `requestCount` and (for round_robin) entry order.\n * Returns null if every entry is in cooldown.\n *\n * @internal\n */\n async select(): Promise<PooledCredential | null> {\n return withCwdMutex(`credential-pool:${this.provider}`, async () => this.selectLocked());\n }\n\n /**\n * Mark the entry whose id matches `entryId` as exhausted with the\n * appropriate cooldown, then return the next healthy entry (or null).\n * If `entryId` is undefined, marks all currently-active entries (rare —\n * only used by admin reset scenarios).\n *\n * @internal\n */\n async markExhaustedAndRotate(args: {\n entryId: string;\n statusCode: number;\n resetAtMs?: number;\n }): Promise<PooledCredential | null> {\n return withCwdMutex(`credential-pool:${this.provider}`, async () => {\n const entry = this.entries.find((e) => e.id === args.entryId);\n if (entry !== undefined) {\n const now = Date.now();\n const cooldown = COOLDOWN_MS[args.statusCode] ?? DEFAULT_COOLDOWN_MS;\n entry.lastStatus = \"exhausted\";\n entry.lastStatusAt = now;\n entry.lastErrorCode = args.statusCode;\n // EC-5: negative or stale resetAt → ignore, fall through to default.\n entry.lastErrorResetAt =\n args.resetAtMs !== undefined && args.resetAtMs > now ? args.resetAtMs : now + cooldown;\n }\n return this.selectLocked();\n });\n }\n\n /** Admin escape hatch: clear all cooldowns. */\n async resetAll(): Promise<void> {\n await withCwdMutex(`credential-pool:${this.provider}`, async () => {\n for (const e of this.entries) {\n e.lastStatus = \"ok\";\n e.lastStatusAt = undefined;\n e.lastErrorCode = undefined;\n e.lastErrorResetAt = undefined;\n }\n });\n }\n\n /** Materialize for persistence. */\n toSnapshot(): CredentialPoolSnapshot {\n return {\n provider: this.provider,\n strategy: this.strategy,\n entries: this.entries.map((e) => ({ ...e })),\n };\n }\n\n /** Hydrate from a previously-saved snapshot. */\n static fromSnapshot(snapshot: CredentialPoolSnapshot): CredentialPool {\n return new CredentialPool(snapshot.provider, snapshot.entries, snapshot.strategy);\n }\n\n // ─── Internal helpers (always called inside the mutex) ───\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: strategy dispatch must branch on 4 enum values (random/least_used/round_robin/fill_first) with 1-entry short-circuits per strategy. Each branch is 5-10 lines of focused logic; extracting per-strategy methods fragments the linear narrative.\n private selectLocked(): PooledCredential | null {\n const available = this.availableEntries();\n if (available.length === 0) return null;\n\n if (this.strategy === \"random\") {\n const idx = Math.floor(Math.random() * available.length);\n const picked = available[idx];\n if (picked === undefined) return null; // unreachable; satisfies strict\n picked.requestCount += 1;\n return picked;\n }\n\n if (this.strategy === \"least_used\" && available.length > 1) {\n // EC-3: tie-break by priority ascending (first wins).\n const picked = [...available].sort(\n (a, b) => a.requestCount - b.requestCount || a.priority - b.priority,\n )[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n return picked;\n }\n\n if (this.strategy === \"round_robin\" && available.length > 1) {\n // Pick head; move it to the end of `entries` so next call picks the next.\n const picked = available[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n const rest = this.entries.filter((e) => e.id !== picked.id);\n // Re-assign priorities: rotated entry gets the highest priority value\n // so it sorts last after `fromSnapshot` rehydration. This makes EC-F\n // (round-robin state survives save/load) work without a separate field.\n const maxPriority = this.entries.length - 1;\n rest.forEach((e, i) => {\n e.priority = i;\n });\n picked.priority = maxPriority;\n this.entries = [...rest, picked];\n return picked;\n }\n\n // fill_first (default) and 1-entry edge case for round_robin/least_used\n const picked = available[0];\n if (picked === undefined) return null;\n picked.requestCount += 1;\n return picked;\n }\n\n private availableEntries(): PooledCredential[] {\n const now = Date.now();\n const out: PooledCredential[] = [];\n for (const entry of this.entries) {\n if (entry.lastStatus === \"ok\") {\n out.push(entry);\n continue;\n }\n // EC-1: auto-heal expired cooldowns inline.\n const resetAt = entry.lastErrorResetAt;\n if (resetAt !== undefined && now >= resetAt) {\n entry.lastStatus = \"ok\";\n entry.lastStatusAt = undefined;\n entry.lastErrorCode = undefined;\n entry.lastErrorResetAt = undefined;\n out.push(entry);\n }\n }\n return out;\n }\n}\n\n/**\n * Drop duplicates by `accessToken`. Keeps the first occurrence (lowest\n * priority wins after sort). EC-C from the edge-case review.\n */\nfunction dedupeByToken(entries: PooledCredential[]): PooledCredential[] {\n const seen = new Set<string>();\n const out: PooledCredential[] = [];\n for (const e of entries) {\n if (seen.has(e.accessToken)) continue;\n seen.add(e.accessToken);\n out.push(e);\n }\n return out;\n}\n","/**\n * Per-fork credential-pool inheritance via `AsyncLocalStorage` (ADR D131).\n *\n * Mirrors the whitelist context pattern from D111\n * (`runtime/async-local-storage.ts`). Forked sub-agents inherit the\n * parent's pool *by reference* so concurrent rotations all observe\n * the same cooldown state — the desired behavior per Hermes parity.\n *\n * @internal\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type { CredentialPool } from \"./credential-pool.js\";\n\n/** ALS slot — keyed by provider name → pool. */\nconst credentialPoolStore = new AsyncLocalStorage<Map<string, CredentialPool>>();\n\n/**\n * Run `fn` with `pools` as the active credential-pool registry for the\n * current async context. Nested calls shadow the outer map (parent\n * pools are not visible to children when the child sets its own).\n *\n * @internal\n */\nexport async function withCredentialPool<T>(\n pools: Map<string, CredentialPool>,\n fn: () => Promise<T>,\n): Promise<T> {\n return credentialPoolStore.run(pools, fn);\n}\n\n/**\n * Pool registered for `provider` in the current async context, or\n * `undefined` when no `withCredentialPool(...)` scope is active.\n *\n * @internal\n */\nexport function currentCredentialPool(provider: string): CredentialPool | undefined {\n return credentialPoolStore.getStore()?.get(provider);\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkCEGU5AVD_cjs = require('./chunk-CEGU5AVD.cjs');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var node_fs = require('node:fs');
|
|
5
|
+
var node_os = require('node:os');
|
|
6
6
|
|
|
7
7
|
var SessionBusyError = class extends chunkCEGU5AVD_cjs.TheokitAgentError {
|
|
8
8
|
constructor(sessionPath) {
|
|
@@ -17,21 +17,21 @@ var SessionBusyError = class extends chunkCEGU5AVD_cjs.TheokitAgentError {
|
|
|
17
17
|
};
|
|
18
18
|
var HEARTBEAT_WINDOW_MS = 3e4;
|
|
19
19
|
function ownerNow() {
|
|
20
|
-
return { pid: process.pid, hostname:
|
|
20
|
+
return { pid: process.pid, hostname: node_os.hostname(), mtime: Date.now() };
|
|
21
21
|
}
|
|
22
22
|
function writeOwner(lockPath, owner) {
|
|
23
|
-
const fd =
|
|
23
|
+
const fd = node_fs.openSync(lockPath, "w", 384);
|
|
24
24
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
node_fs.writeSync(fd, JSON.stringify(owner));
|
|
26
|
+
node_fs.fchmodSync(fd, 384);
|
|
27
27
|
} finally {
|
|
28
|
-
|
|
28
|
+
node_fs.closeSync(fd);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
function readOwner(lockPath) {
|
|
32
32
|
let raw;
|
|
33
33
|
try {
|
|
34
|
-
raw =
|
|
34
|
+
raw = node_fs.readFileSync(lockPath, "utf8");
|
|
35
35
|
} catch (err) {
|
|
36
36
|
const code = err.code;
|
|
37
37
|
if (code === "ENOENT") return void 0;
|
|
@@ -58,14 +58,14 @@ function processAlive(pid) {
|
|
|
58
58
|
}
|
|
59
59
|
function reclaimable(owner) {
|
|
60
60
|
if (owner === void 0) return true;
|
|
61
|
-
if (owner.hostname !==
|
|
61
|
+
if (owner.hostname !== node_os.hostname()) {
|
|
62
62
|
return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;
|
|
63
63
|
}
|
|
64
64
|
return !processAlive(owner.pid);
|
|
65
65
|
}
|
|
66
66
|
function sessionHasWriter(sessionPath) {
|
|
67
67
|
const lockPath = `${sessionPath}.writer.lock`;
|
|
68
|
-
if (!
|
|
68
|
+
if (!node_fs.existsSync(lockPath)) return false;
|
|
69
69
|
try {
|
|
70
70
|
return !reclaimable(readOwner(lockPath));
|
|
71
71
|
} catch {
|
|
@@ -77,14 +77,14 @@ async function acquireSessionWriter(sessionPath) {
|
|
|
77
77
|
const mine = ownerNow();
|
|
78
78
|
let fd;
|
|
79
79
|
try {
|
|
80
|
-
fd =
|
|
80
|
+
fd = node_fs.openSync(lockPath, "wx", 384);
|
|
81
81
|
} catch (err) {
|
|
82
82
|
if (err.code !== "EEXIST") throw err;
|
|
83
83
|
if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);
|
|
84
84
|
writeOwner(lockPath, mine);
|
|
85
85
|
return createLease(sessionPath, lockPath);
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
node_fs.closeSync(fd);
|
|
88
88
|
writeOwner(lockPath, mine);
|
|
89
89
|
return createLease(sessionPath, lockPath);
|
|
90
90
|
}
|
|
@@ -95,7 +95,7 @@ function createLease(sessionPath, lockPath) {
|
|
|
95
95
|
release: async () => {
|
|
96
96
|
if (released) return;
|
|
97
97
|
released = true;
|
|
98
|
-
|
|
98
|
+
node_fs.rmSync(lockPath, { force: true });
|
|
99
99
|
},
|
|
100
100
|
renew: () => {
|
|
101
101
|
if (released) return;
|
|
@@ -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-MKS5FFH5.cjs.map
|
|
111
|
+
//# sourceMappingURL=chunk-MKS5FFH5.cjs.map
|
|
@@ -0,0 +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;AA0CO,IAAM,mBAAA,GAAsB,GAAA;AAUnC,SAAS,QAAA,GAAsB;AAC7B,EAAA,OAAO,EAAE,GAAA,EAAK,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAUC,kBAAS,EAAG,KAAA,EAAO,IAAA,CAAK,GAAA,EAAI,EAAE;AACrE;AAWA,SAAS,UAAA,CAAW,UAAkB,KAAA,EAAwB;AAC5D,EAAA,MAAM,EAAA,GAAKC,gBAAA,CAAS,QAAA,EAAU,GAAA,EAAK,GAAK,CAAA;AACxC,EAAA,IAAI;AACF,IAAAC,iBAAA,CAAU,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAInC,IAAAC,kBAAA,CAAW,IAAI,GAAK,CAAA;AAAA,EACtB,CAAA,SAAE;AACA,IAAAC,iBAAA,CAAU,EAAE,CAAA;AAAA,EACd;AACF;AAGA,SAAS,UAAU,QAAA,EAAyC;AAC1D,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAMC,oBAAA,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,gBAAA,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;AAcO,SAAS,iBAAiB,WAAA,EAA8B;AAC7D,EAAA,MAAM,QAAA,GAAW,GAAG,WAAW,CAAA,YAAA,CAAA;AAC/B,EAAA,IAAI,CAACM,kBAAA,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;AASA,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,gBAAA,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,iBAAA,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,cAAA,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-MKS5FFH5.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 * 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 */\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\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 */\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,9 +1,9 @@
|
|
|
1
|
-
import { pluginBundleRoots, projectConfigRoots } from './chunk-
|
|
2
|
-
import { readWorkspaceDir, parseSimpleYaml } from './chunk-
|
|
1
|
+
import { pluginBundleRoots, projectConfigRoots } from './chunk-PISZRLES.js';
|
|
2
|
+
import { readWorkspaceDir, parseSimpleYaml } from './chunk-TIYQDJFK.js';
|
|
3
3
|
import { ConfigurationError } from './chunk-TNFOU6SB.js';
|
|
4
4
|
import { diag } from './chunk-3OY6H3I7.js';
|
|
5
|
-
import { readdir, readFile } from 'fs/promises';
|
|
6
|
-
import { join } from 'path';
|
|
5
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
7
|
|
|
8
8
|
async function pluginBundleDirs(cwd, compatSources = []) {
|
|
9
9
|
const dirs = [];
|
|
@@ -203,5 +203,5 @@ function parseFrontmatterFields(frontmatter) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
export { loadSubagents, pluginBundleDirs };
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
207
|
-
//# sourceMappingURL=chunk-
|
|
206
|
+
//# sourceMappingURL=chunk-MTNCJ7MQ.js.map
|
|
207
|
+
//# sourceMappingURL=chunk-MTNCJ7MQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/runtime/plugin-loader/plugin-bundles.ts","../src/internal/runtime/skills/subagents-loader.ts"],"names":["join"],"mappings":"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","file":"chunk-WM646HEN.js","sourcesContent":["/**\n * Locating the plugin bundles a project carries.\n *\n * Shared by the skills and subagents loaders, which both need the same answer to \"which folders in\n * this project are plugins\" and would otherwise each grow their own copy of the directory walk.\n *\n * @internal\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { pluginBundleRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\n\n/**\n * Every plugin folder under the project's plugin roots.\n *\n * Returns the FOLDERS, not their contents — what a bundle contributes (`skills/`, `agents/`) is the\n * caller's business, and a loader that also knew the layout would have to change whenever the other\n * one did.\n *\n * A missing root is not an error: most projects carry no plugins, and treating their absence as a\n * failure would make \"none installed\" indistinguishable from \"the directory could not be read\".\n */\nexport async function pluginBundleDirs(\n cwd: string,\n /** Declared foreign dialects (#524). Empty reads `.theokit/plugins` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<string[]> {\n const dirs: string[] = [];\n for (const root of pluginBundleRoots(cwd, compatSources)) {\n let entries: Dirent[];\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) dirs.push(join(root, entry.name));\n }\n }\n return dirs;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport type { AgentDefinition } from \"../../../types/agent.js\";\nimport type { ModelSelection } from \"../../../types/agent-prims.js\";\nimport { diag } from \"../../diagnostics.js\";\nimport { projectConfigRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\nimport { pluginBundleDirs } from \"../plugin-loader/plugin-bundles.js\";\n\n/**\n * Load file-based subagents from `.theokit/agents/*.md` and merge with\n * inline definitions. Inline overrides file-based on name conflict.\n *\n * Each markdown file has YAML frontmatter (description + optional model)\n * and a body that becomes the subagent prompt.\n *\n * @internal\n */\nexport async function loadSubagents(\n cwd: string,\n settingSourcesIncludeProject: boolean,\n inline: Record<string, AgentDefinition> | undefined,\n /** Declared foreign dialects (#524). Empty reads `.theokit/` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<Record<string, AgentDefinition>> {\n const result: Record<string, AgentDefinition> = {};\n if (settingSourcesIncludeProject) {\n const projectAgents = await readProjectSubagents(cwd, compatSources);\n for (const [name, definition] of Object.entries(projectAgents)) {\n result[name] = definition;\n }\n }\n if (inline !== undefined) {\n for (const [name, definition] of Object.entries(inline)) {\n result[name] = definition;\n }\n }\n return result;\n}\n\n/**\n * Read agent declarations from every project config root (`.theokit`, then `.claude`).\n *\n * FIRST occurrence of a name wins, which is what makes `projectConfigRoots`' order a contract rather\n * than a detail: a project declaring the same agent in both means the explicit namespace.\n */\nasync function readProjectSubagents(\n cwd: string,\n compatSources: readonly CompatSourceDeclaration[],\n): Promise<Record<string, AgentDefinition>> {\n const subagents: Record<string, AgentDefinition> = {};\n for (const configRoot of projectConfigRoots(cwd, compatSources, \"subagents\")) {\n await readSubagentsFrom(join(configRoot, \"agents\"), subagents);\n }\n // A Claude Code plugin is a BUNDLE, and its `agents/` is what it exists to contribute. Read after\n // the project's own, so a project can shadow an agent a plugin ships without editing the plugin.\n for (const bundle of await pluginBundleDirs(cwd, compatSources)) {\n await readSubagentsFrom(join(bundle, \"agents\"), subagents);\n }\n return subagents;\n}\n\n/**\n * Parse an agent file, or return null when it was written for another runtime.\n *\n * ## What this changed, and what it deliberately did not\n *\n * The failure used to take the whole directory, and that was a decision rather than an oversight:\n * \"a file that HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is\n * what keeps a typo'd `sandbox` from returning as a silent gate through this door. Isolating the\n * failure per file would hand that risk back.\"\n *\n * The risk named there is real and is NOT handed back, because the isolation is narrower than the\n * one that argument refuses. Only `KNOWN_CLAUDE_CODE_FIELDS` takes this door — the set that already\n * encodes \"this key belongs to another runtime and carries no behaviour here\". A misspelling of one\n * of OUR keys carries `subagent_unknown_field` and still propagates, so `sandboxx` remains fatal\n * and cannot return as a silent gate. `a-claude-code-field-names-itself.test.ts` pins both halves.\n *\n * What moved the decision was the cost, measured in a live TUI on 2026-09-15: one ported\n * `.claude/agents/*.md` carrying `memory:` stopped every sibling agent from loading and the turn\n * produced no answer at all — on an agent the task never used. Beside it, on the same prompt and\n * the same project, another runtime answered normally. The diagnosis fix this file shipped tells a\n * user which key is foreign; it does not give them back the turn.\n *\n * The loader already draws exactly this line for a file with no frontmatter, and for the same\n * stated reason: one of them must not stop every agent in the directory from loading.\n */\nfunction parseOrSkipForeignRuntime(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } | null {\n try {\n return parseSubagentMarkdown(raw, filename);\n } catch (error) {\n if (error instanceof ConfigurationError && error.code === \"subagent_foreign_runtime_field\") {\n diag(`[theokit-sdk] ${filename}: ${error.message} — skipping this agent`);\n return null;\n }\n throw error;\n }\n}\n\nasync function readSubagentsFrom(\n root: string,\n subagents: Record<string, AgentDefinition>,\n): Promise<void> {\n const entries = await readWorkspaceDir(root, \"subagents_read_error\", \"subagents directory\");\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue;\n const path = join(root, entry.name);\n const raw = await readFile(path, \"utf8\");\n // A markdown file with NO frontmatter is not an agent declaration — a directory of agents\n // written for the Claude Code CLI conventionally carries documentation beside them, and\n // `.claude/agents/README.md` exists in this repository. Throwing on it made ONE such file stop\n // every agent in the directory from loading.\n //\n // Skipped with a warn rather than in silence, and ONLY for the no-frontmatter case: a file that\n // HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is what\n // keeps a typo'd `sandbox` from returning as a silent gate through this door.\n if (!hasFrontmatter(raw)) {\n diag(`[theokit-sdk] ${entry.name} has no frontmatter — not an agent declaration, skipping`);\n continue;\n }\n const definition = parseOrSkipForeignRuntime(raw, entry.name);\n if (definition === null) continue;\n if (subagents[definition.name] === undefined) {\n // `path` is computed above to read the file and was then dropped. Keeping it is the whole\n // visibility fix (#524): without it a listing cannot say which root an agent came from.\n subagents[definition.name] = { ...definition.definition, source: path };\n }\n }\n}\n\n// The frontmatter keys a disk subagent may declare. Any other key is a typed load\n// error rather than a silent drop — a dropped `sandbox` an operator wrote believing\n// it confines the child is exactly the silent-gate failure class this guards against.\nconst ACCEPTED_FIELDS = new Set([\n \"name\",\n \"description\",\n \"model\",\n \"tools\",\n \"reasoning_effort\",\n \"mcp\",\n \"sandbox\",\n \"memory\",\n]);\n\n// Fields the Claude Code CLI writes that carry NO behaviour for this runtime. Accepted and ignored,\n// so an agent authored for the CLI loads here unchanged — measured 2026-08-26 across the 59 agent\n// files on one machine, where `color` appeared in 38 of them and made every one of those a\n// `subagent_unknown_field` load error.\n//\n// Named explicitly instead of loosening the check above, because that check's reason is sound: a\n// dropped `sandbox` an operator wrote believing it confines the child is a silent gate. A field that\n// COULD change behaviour must still fail loudly. This set is the difference between \"we know this\n// one and it does nothing\" and \"we have never heard of this\" — two facts a bare allow-everything\n// would collapse into one.\n//\n// Anything added here needs the same justification: inert for THIS runtime, not merely unfamiliar.\nconst INERT_CLAUDE_CODE_FIELDS = new Set([\n /** The CLI's label colour for the agent. Presentation only. */\n \"color\",\n]);\n\nfunction parseSubagentMarkdown(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } {\n const { frontmatter, body } = splitFrontmatter(raw, filename);\n const fields = parseFrontmatterFields(frontmatter);\n rejectUnknownFields(fields, filename);\n rejectMcp(fields, filename);\n\n const definition: AgentDefinition = {\n description: asString(fields.description) ?? \"\",\n prompt: body,\n };\n const model = resolveModel(fields, filename);\n if (model !== undefined) definition.model = model;\n const tools = toStringList(fields.tools);\n if (tools.length > 0) definition.tools = tools;\n const sandbox = resolveSandbox(fields, filename);\n if (sandbox !== undefined) definition.sandbox = sandbox;\n // Carried unjudged — see `AgentDefinition.memory`. Which root a scope names, and whether the\n // name is one at all, is `@theokit/agents`' decision and refusing it twice means two lists.\n const memory = asString(fields.memory);\n if (memory !== undefined) definition.memory = memory;\n\n const name = asString(fields.name) ?? filename.replace(/\\.md$/, \"\");\n return { name, definition };\n}\n\n/**\n * Frontmatter keys that Claude Code documents for a subagent and this runtime does not accept.\n *\n * Not an allow-list — every one of these still fails the load. It exists so the FAILURE can say\n * which kind of unknown it met. A user migrating a `.claude/agents/` tree used to learn one key per\n * round trip: fix `memory`, meet `permissionMode`, fix that, meet `maxTurns` — with nothing saying\n * the set was finite or that the tree was simply written for another runtime.\n *\n * The distinction this file already draws for `INERT_CLAUDE_CODE_FIELDS` is the same one: \"the\n * difference between 'we know this one and it does nothing' and 'we have never heard of this' — two\n * facts a bare allow-everything would collapse into one.\" This is the third fact beside them —\n * \"another runtime's field\" — and it changes only the message, never the verdict.\n */\nconst KNOWN_CLAUDE_CODE_FIELDS = new Set([\n \"disallowedTools\",\n \"permissionMode\",\n \"maxTurns\",\n \"skills\",\n \"background\",\n \"effort\",\n \"isolation\",\n \"initialPrompt\",\n \"hooks\",\n \"experimental\",\n \"mcpServers\",\n]);\n\n/**\n * The typed error for a key we do not accept. Built here rather than inline so `rejectUnknownFields`\n * stays a plain loop: the message has two shapes and the code has two values, and computing all four\n * at the throw site is what pushed that function past the cognitive-complexity gate.\n */\nfunction unknownFieldError(key: string, filename: string): ConfigurationError {\n const foreign = KNOWN_CLAUDE_CODE_FIELDS.has(key);\n const siblings = [...KNOWN_CLAUDE_CODE_FIELDS].filter((f) => f !== key);\n const origin = foreign\n ? ` — \"${key}\" is a Claude Code subagent field that this runtime does not support. ` +\n `The same applies to: ${siblings.join(\", \")}. Removing them one at a time will meet each ` +\n `in turn; the tree is written for another runtime`\n : \"\";\n // The two codes are written as LITERALS, one per branch, rather than as a ternary inside the\n // options object. `tools/generate-error-codes.mjs` walks the source for the literal assigned to\n // `code:`, so a ternary there is not a literal and it extracted NEITHER — measured 2026-09-15,\n // when `quality:docs-errors` went red and the regenerated reference had lost\n // `subagent_unknown_field` without ever gaining `subagent_foreign_runtime_field`. Two real,\n // throwable codes were absent from the published error reference, which is the one place a\n // consumer looks to find out what they can catch.\n if (foreign)\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_foreign_runtime_field\" },\n );\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_unknown_field\" },\n );\n}\n\nfunction rejectUnknownFields(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): void {\n for (const key of Object.keys(fields)) {\n if (INERT_CLAUDE_CODE_FIELDS.has(key)) continue;\n if (!ACCEPTED_FIELDS.has(key)) throw unknownFieldError(key, filename);\n }\n}\n\n// mcp: a known field, but not yet honored on the LOCAL delegation path. The frontmatter YAML can only\n// express server NAMES (parseSimpleYaml has no nested-object support), while a child's `Agent.create`\n// needs `mcpServers` as a Record<name, config>; resolving names→config per-subagent in local delegation\n// is its own follow-up. Rather than silently drop it (the M26/M32 silent-gate class), it is a typed load\n// error that names the field and points at the alternative.\nfunction rejectMcp(fields: Record<string, FrontmatterValue | undefined>, filename: string): void {\n if (fields.mcp !== undefined) {\n throw new ConfigurationError(\n `Subagent ${filename}: per-subagent \"mcp\" is not yet supported on the local delegation path; declare MCP servers in .theokit/mcp.json (or the parent) instead`,\n { code: \"subagent_mcp_unsupported_local\" },\n );\n }\n}\n\n// model + reasoning_effort — effort rides inside `model.params[thinking]`, so it requires a concrete\n// model id to attach to (a child inheriting the parent's model cannot carry the parent's provider-\n// specific effort param safely).\nfunction resolveModel(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): ModelSelection | \"inherit\" | undefined {\n const modelId = asString(fields.model);\n // An empty/blank effort (`reasoning_effort: \"\"`) names nothing: treated as absent, never as a\n // `{ id: \"thinking\", value: \"\" }` param the provider would drop or reject.\n const rawEffort = asString(fields.reasoning_effort)?.trim();\n const effort = rawEffort === \"\" ? undefined : rawEffort;\n // reasoning_effort rides in model.params[thinking], so it needs a CONCRETE model id to attach to.\n // Neither an absent model NOR `model: inherit` can carry it (the inherited id is unknown at load), so\n // both are typed errors rather than a silently-dropped effort — the silent-gate class this guards.\n if (effort !== undefined && (modelId === undefined || modelId === \"inherit\")) {\n throw new ConfigurationError(\n `Subagent ${filename}: reasoning_effort requires a concrete model (effort is a model parameter; an absent model or \"inherit\" cannot carry it)`,\n { code: \"subagent_reasoning_effort_without_model\" },\n );\n }\n if (modelId === undefined) return undefined;\n if (modelId === \"inherit\") return \"inherit\";\n return effort !== undefined\n ? { id: modelId, params: [{ id: \"thinking\", value: effort }] }\n : { id: modelId };\n}\n\n// sandbox: boolean only. A granular mode string (read-only/…) is unsupported by the SDK runtime and is\n// a typed error rather than a silent coercion to a boolean.\nfunction resolveSandbox(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): boolean | undefined {\n if (fields.sandbox === undefined) return undefined;\n if (typeof fields.sandbox !== \"boolean\") {\n throw new ConfigurationError(\n `Subagent ${filename}: sandbox must be a boolean (got \"${String(fields.sandbox)}\"); granular sandbox modes are not supported by the runtime`,\n { code: \"subagent_sandbox_not_boolean\" },\n );\n }\n return fields.sandbox;\n}\n\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n if (typeof v !== \"string\") return undefined;\n // parseSimpleYaml does not strip quotes (documented), and `model`/`reasoning_effort` are fields users\n // habitually quote (`model: \"openai/gpt-4o\"`). Strip a single matching surrounding quote pair so a\n // quoted id/effort does not slip past validation and fail only at the provider.\n const m = /^([\"'])(.*)\\1$/.exec(v);\n return m ? m[2] : v;\n}\n\n/** Accept a YAML list (`string[]`) or a comma/space-separated scalar; trim + drop empties. */\nfunction toStringList(v: FrontmatterValue | undefined): string[] {\n if (Array.isArray(v)) return v.map((t) => t.trim()).filter((t) => t.length > 0);\n if (typeof v === \"string\") {\n return v\n .split(/[\\s,]+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n }\n return [];\n}\n\n/** Does this file open with a frontmatter block at all? Its ABSENCE means \"not an agent\". */\nfunction hasFrontmatter(raw: string): boolean {\n return /^---\\s*\\n/.test(raw);\n}\n\nfunction splitFrontmatter(raw: string, filename: string): { frontmatter: string; body: string } {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n([\\s\\S]*)$/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Subagent ${filename} is missing frontmatter`, {\n code: \"subagent_missing_frontmatter\",\n });\n }\n return { frontmatter: match[1] ?? \"\", body: (match[2] ?? \"\").trim() };\n}\n\nfunction parseFrontmatterFields(frontmatter: string): Record<string, FrontmatterValue | undefined> {\n // Preserve the rich YAML value types (boolean/number/string[]): `sandbox: true` and\n // `mcp: [a, b]` are meaningful here, so narrowing everything to string (as the\n // pre-M33 loader did) would drop them. Per-field validation happens in parseSubagentMarkdown.\n return parseSimpleYaml(frontmatter);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/runtime/plugin-loader/plugin-bundles.ts","../src/internal/runtime/skills/subagents-loader.ts"],"names":["join"],"mappings":"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","file":"chunk-MTNCJ7MQ.js","sourcesContent":["/**\n * Locating the plugin bundles a project carries.\n *\n * Shared by the skills and subagents loaders, which both need the same answer to \"which folders in\n * this project are plugins\" and would otherwise each grow their own copy of the directory walk.\n *\n * @internal\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { pluginBundleRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\n\n/**\n * Every plugin folder under the project's plugin roots.\n *\n * Returns the FOLDERS, not their contents — what a bundle contributes (`skills/`, `agents/`) is the\n * caller's business, and a loader that also knew the layout would have to change whenever the other\n * one did.\n *\n * A missing root is not an error: most projects carry no plugins, and treating their absence as a\n * failure would make \"none installed\" indistinguishable from \"the directory could not be read\".\n */\nexport async function pluginBundleDirs(\n cwd: string,\n /** Declared foreign dialects (#524). Empty reads `.theokit/plugins` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<string[]> {\n const dirs: string[] = [];\n for (const root of pluginBundleRoots(cwd, compatSources)) {\n let entries: Dirent[];\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) dirs.push(join(root, entry.name));\n }\n }\n return dirs;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport type { AgentDefinition } from \"../../../types/agent.js\";\nimport type { ModelSelection } from \"../../../types/agent-prims.js\";\nimport { diag } from \"../../diagnostics.js\";\nimport { projectConfigRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\nimport { pluginBundleDirs } from \"../plugin-loader/plugin-bundles.js\";\n\n/**\n * Load file-based subagents from `.theokit/agents/*.md` and merge with\n * inline definitions. Inline overrides file-based on name conflict.\n *\n * Each markdown file has YAML frontmatter (description + optional model)\n * and a body that becomes the subagent prompt.\n *\n * @internal\n */\nexport async function loadSubagents(\n cwd: string,\n settingSourcesIncludeProject: boolean,\n inline: Record<string, AgentDefinition> | undefined,\n /** Declared foreign dialects (#524). Empty reads `.theokit/` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<Record<string, AgentDefinition>> {\n const result: Record<string, AgentDefinition> = {};\n if (settingSourcesIncludeProject) {\n const projectAgents = await readProjectSubagents(cwd, compatSources);\n for (const [name, definition] of Object.entries(projectAgents)) {\n result[name] = definition;\n }\n }\n if (inline !== undefined) {\n for (const [name, definition] of Object.entries(inline)) {\n result[name] = definition;\n }\n }\n return result;\n}\n\n/**\n * Read agent declarations from every project config root (`.theokit`, then `.claude`).\n *\n * FIRST occurrence of a name wins, which is what makes `projectConfigRoots`' order a contract rather\n * than a detail: a project declaring the same agent in both means the explicit namespace.\n */\nasync function readProjectSubagents(\n cwd: string,\n compatSources: readonly CompatSourceDeclaration[],\n): Promise<Record<string, AgentDefinition>> {\n const subagents: Record<string, AgentDefinition> = {};\n for (const configRoot of projectConfigRoots(cwd, compatSources, \"subagents\")) {\n await readSubagentsFrom(join(configRoot, \"agents\"), subagents);\n }\n // A Claude Code plugin is a BUNDLE, and its `agents/` is what it exists to contribute. Read after\n // the project's own, so a project can shadow an agent a plugin ships without editing the plugin.\n for (const bundle of await pluginBundleDirs(cwd, compatSources)) {\n await readSubagentsFrom(join(bundle, \"agents\"), subagents);\n }\n return subagents;\n}\n\n/**\n * Parse an agent file, or return null when it was written for another runtime.\n *\n * ## What this changed, and what it deliberately did not\n *\n * The failure used to take the whole directory, and that was a decision rather than an oversight:\n * \"a file that HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is\n * what keeps a typo'd `sandbox` from returning as a silent gate through this door. Isolating the\n * failure per file would hand that risk back.\"\n *\n * The risk named there is real and is NOT handed back, because the isolation is narrower than the\n * one that argument refuses. Only `KNOWN_CLAUDE_CODE_FIELDS` takes this door — the set that already\n * encodes \"this key belongs to another runtime and carries no behaviour here\". A misspelling of one\n * of OUR keys carries `subagent_unknown_field` and still propagates, so `sandboxx` remains fatal\n * and cannot return as a silent gate. `a-claude-code-field-names-itself.test.ts` pins both halves.\n *\n * What moved the decision was the cost, measured in a live TUI on 2026-09-15: one ported\n * `.claude/agents/*.md` carrying `memory:` stopped every sibling agent from loading and the turn\n * produced no answer at all — on an agent the task never used. Beside it, on the same prompt and\n * the same project, another runtime answered normally. The diagnosis fix this file shipped tells a\n * user which key is foreign; it does not give them back the turn.\n *\n * The loader already draws exactly this line for a file with no frontmatter, and for the same\n * stated reason: one of them must not stop every agent in the directory from loading.\n */\nfunction parseOrSkipForeignRuntime(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } | null {\n try {\n return parseSubagentMarkdown(raw, filename);\n } catch (error) {\n if (error instanceof ConfigurationError && error.code === \"subagent_foreign_runtime_field\") {\n diag(`[theokit-sdk] ${filename}: ${error.message} — skipping this agent`);\n return null;\n }\n throw error;\n }\n}\n\nasync function readSubagentsFrom(\n root: string,\n subagents: Record<string, AgentDefinition>,\n): Promise<void> {\n const entries = await readWorkspaceDir(root, \"subagents_read_error\", \"subagents directory\");\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue;\n const path = join(root, entry.name);\n const raw = await readFile(path, \"utf8\");\n // A markdown file with NO frontmatter is not an agent declaration — a directory of agents\n // written for the Claude Code CLI conventionally carries documentation beside them, and\n // `.claude/agents/README.md` exists in this repository. Throwing on it made ONE such file stop\n // every agent in the directory from loading.\n //\n // Skipped with a warn rather than in silence, and ONLY for the no-frontmatter case: a file that\n // HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is what\n // keeps a typo'd `sandbox` from returning as a silent gate through this door.\n if (!hasFrontmatter(raw)) {\n diag(`[theokit-sdk] ${entry.name} has no frontmatter — not an agent declaration, skipping`);\n continue;\n }\n const definition = parseOrSkipForeignRuntime(raw, entry.name);\n if (definition === null) continue;\n if (subagents[definition.name] === undefined) {\n // `path` is computed above to read the file and was then dropped. Keeping it is the whole\n // visibility fix (#524): without it a listing cannot say which root an agent came from.\n subagents[definition.name] = { ...definition.definition, source: path };\n }\n }\n}\n\n// The frontmatter keys a disk subagent may declare. Any other key is a typed load\n// error rather than a silent drop — a dropped `sandbox` an operator wrote believing\n// it confines the child is exactly the silent-gate failure class this guards against.\nconst ACCEPTED_FIELDS = new Set([\n \"name\",\n \"description\",\n \"model\",\n \"tools\",\n \"reasoning_effort\",\n \"mcp\",\n \"sandbox\",\n \"memory\",\n]);\n\n// Fields the Claude Code CLI writes that carry NO behaviour for this runtime. Accepted and ignored,\n// so an agent authored for the CLI loads here unchanged — measured 2026-08-26 across the 59 agent\n// files on one machine, where `color` appeared in 38 of them and made every one of those a\n// `subagent_unknown_field` load error.\n//\n// Named explicitly instead of loosening the check above, because that check's reason is sound: a\n// dropped `sandbox` an operator wrote believing it confines the child is a silent gate. A field that\n// COULD change behaviour must still fail loudly. This set is the difference between \"we know this\n// one and it does nothing\" and \"we have never heard of this\" — two facts a bare allow-everything\n// would collapse into one.\n//\n// Anything added here needs the same justification: inert for THIS runtime, not merely unfamiliar.\nconst INERT_CLAUDE_CODE_FIELDS = new Set([\n /** The CLI's label colour for the agent. Presentation only. */\n \"color\",\n]);\n\nfunction parseSubagentMarkdown(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } {\n const { frontmatter, body } = splitFrontmatter(raw, filename);\n const fields = parseFrontmatterFields(frontmatter);\n rejectUnknownFields(fields, filename);\n rejectMcp(fields, filename);\n\n const definition: AgentDefinition = {\n description: asString(fields.description) ?? \"\",\n prompt: body,\n };\n const model = resolveModel(fields, filename);\n if (model !== undefined) definition.model = model;\n const tools = toStringList(fields.tools);\n if (tools.length > 0) definition.tools = tools;\n const sandbox = resolveSandbox(fields, filename);\n if (sandbox !== undefined) definition.sandbox = sandbox;\n // Carried unjudged — see `AgentDefinition.memory`. Which root a scope names, and whether the\n // name is one at all, is `@theokit/agents`' decision and refusing it twice means two lists.\n const memory = asString(fields.memory);\n if (memory !== undefined) definition.memory = memory;\n\n const name = asString(fields.name) ?? filename.replace(/\\.md$/, \"\");\n return { name, definition };\n}\n\n/**\n * Frontmatter keys that Claude Code documents for a subagent and this runtime does not accept.\n *\n * Not an allow-list — every one of these still fails the load. It exists so the FAILURE can say\n * which kind of unknown it met. A user migrating a `.claude/agents/` tree used to learn one key per\n * round trip: fix `memory`, meet `permissionMode`, fix that, meet `maxTurns` — with nothing saying\n * the set was finite or that the tree was simply written for another runtime.\n *\n * The distinction this file already draws for `INERT_CLAUDE_CODE_FIELDS` is the same one: \"the\n * difference between 'we know this one and it does nothing' and 'we have never heard of this' — two\n * facts a bare allow-everything would collapse into one.\" This is the third fact beside them —\n * \"another runtime's field\" — and it changes only the message, never the verdict.\n */\nconst KNOWN_CLAUDE_CODE_FIELDS = new Set([\n \"disallowedTools\",\n \"permissionMode\",\n \"maxTurns\",\n \"skills\",\n \"background\",\n \"effort\",\n \"isolation\",\n \"initialPrompt\",\n \"hooks\",\n \"experimental\",\n \"mcpServers\",\n]);\n\n/**\n * The typed error for a key we do not accept. Built here rather than inline so `rejectUnknownFields`\n * stays a plain loop: the message has two shapes and the code has two values, and computing all four\n * at the throw site is what pushed that function past the cognitive-complexity gate.\n */\nfunction unknownFieldError(key: string, filename: string): ConfigurationError {\n const foreign = KNOWN_CLAUDE_CODE_FIELDS.has(key);\n const siblings = [...KNOWN_CLAUDE_CODE_FIELDS].filter((f) => f !== key);\n const origin = foreign\n ? ` — \"${key}\" is a Claude Code subagent field that this runtime does not support. ` +\n `The same applies to: ${siblings.join(\", \")}. Removing them one at a time will meet each ` +\n `in turn; the tree is written for another runtime`\n : \"\";\n // The two codes are written as LITERALS, one per branch, rather than as a ternary inside the\n // options object. `tools/generate-error-codes.mjs` walks the source for the literal assigned to\n // `code:`, so a ternary there is not a literal and it extracted NEITHER — measured 2026-09-15,\n // when `quality:docs-errors` went red and the regenerated reference had lost\n // `subagent_unknown_field` without ever gaining `subagent_foreign_runtime_field`. Two real,\n // throwable codes were absent from the published error reference, which is the one place a\n // consumer looks to find out what they can catch.\n if (foreign)\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_foreign_runtime_field\" },\n );\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_unknown_field\" },\n );\n}\n\nfunction rejectUnknownFields(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): void {\n for (const key of Object.keys(fields)) {\n if (INERT_CLAUDE_CODE_FIELDS.has(key)) continue;\n if (!ACCEPTED_FIELDS.has(key)) throw unknownFieldError(key, filename);\n }\n}\n\n// mcp: a known field, but not yet honored on the LOCAL delegation path. The frontmatter YAML can only\n// express server NAMES (parseSimpleYaml has no nested-object support), while a child's `Agent.create`\n// needs `mcpServers` as a Record<name, config>; resolving names→config per-subagent in local delegation\n// is its own follow-up. Rather than silently drop it (the M26/M32 silent-gate class), it is a typed load\n// error that names the field and points at the alternative.\nfunction rejectMcp(fields: Record<string, FrontmatterValue | undefined>, filename: string): void {\n if (fields.mcp !== undefined) {\n throw new ConfigurationError(\n `Subagent ${filename}: per-subagent \"mcp\" is not yet supported on the local delegation path; declare MCP servers in .theokit/mcp.json (or the parent) instead`,\n { code: \"subagent_mcp_unsupported_local\" },\n );\n }\n}\n\n// model + reasoning_effort — effort rides inside `model.params[thinking]`, so it requires a concrete\n// model id to attach to (a child inheriting the parent's model cannot carry the parent's provider-\n// specific effort param safely).\nfunction resolveModel(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): ModelSelection | \"inherit\" | undefined {\n const modelId = asString(fields.model);\n // An empty/blank effort (`reasoning_effort: \"\"`) names nothing: treated as absent, never as a\n // `{ id: \"thinking\", value: \"\" }` param the provider would drop or reject.\n const rawEffort = asString(fields.reasoning_effort)?.trim();\n const effort = rawEffort === \"\" ? undefined : rawEffort;\n // reasoning_effort rides in model.params[thinking], so it needs a CONCRETE model id to attach to.\n // Neither an absent model NOR `model: inherit` can carry it (the inherited id is unknown at load), so\n // both are typed errors rather than a silently-dropped effort — the silent-gate class this guards.\n if (effort !== undefined && (modelId === undefined || modelId === \"inherit\")) {\n throw new ConfigurationError(\n `Subagent ${filename}: reasoning_effort requires a concrete model (effort is a model parameter; an absent model or \"inherit\" cannot carry it)`,\n { code: \"subagent_reasoning_effort_without_model\" },\n );\n }\n if (modelId === undefined) return undefined;\n if (modelId === \"inherit\") return \"inherit\";\n return effort !== undefined\n ? { id: modelId, params: [{ id: \"thinking\", value: effort }] }\n : { id: modelId };\n}\n\n// sandbox: boolean only. A granular mode string (read-only/…) is unsupported by the SDK runtime and is\n// a typed error rather than a silent coercion to a boolean.\nfunction resolveSandbox(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): boolean | undefined {\n if (fields.sandbox === undefined) return undefined;\n if (typeof fields.sandbox !== \"boolean\") {\n throw new ConfigurationError(\n `Subagent ${filename}: sandbox must be a boolean (got \"${String(fields.sandbox)}\"); granular sandbox modes are not supported by the runtime`,\n { code: \"subagent_sandbox_not_boolean\" },\n );\n }\n return fields.sandbox;\n}\n\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n if (typeof v !== \"string\") return undefined;\n // parseSimpleYaml does not strip quotes (documented), and `model`/`reasoning_effort` are fields users\n // habitually quote (`model: \"openai/gpt-4o\"`). Strip a single matching surrounding quote pair so a\n // quoted id/effort does not slip past validation and fail only at the provider.\n const m = /^([\"'])(.*)\\1$/.exec(v);\n return m ? m[2] : v;\n}\n\n/** Accept a YAML list (`string[]`) or a comma/space-separated scalar; trim + drop empties. */\nfunction toStringList(v: FrontmatterValue | undefined): string[] {\n if (Array.isArray(v)) return v.map((t) => t.trim()).filter((t) => t.length > 0);\n if (typeof v === \"string\") {\n return v\n .split(/[\\s,]+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n }\n return [];\n}\n\n/** Does this file open with a frontmatter block at all? Its ABSENCE means \"not an agent\". */\nfunction hasFrontmatter(raw: string): boolean {\n return /^---\\s*\\n/.test(raw);\n}\n\nfunction splitFrontmatter(raw: string, filename: string): { frontmatter: string; body: string } {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n([\\s\\S]*)$/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Subagent ${filename} is missing frontmatter`, {\n code: \"subagent_missing_frontmatter\",\n });\n }\n return { frontmatter: match[1] ?? \"\", body: (match[2] ?? \"\").trim() };\n}\n\nfunction parseFrontmatterFields(frontmatter: string): Record<string, FrontmatterValue | undefined> {\n // Preserve the rich YAML value types (boolean/number/string[]): `sandbox: true` and\n // `mcp: [a, b]` are meaningful here, so narrowing everything to string (as the\n // pre-M33 loader did) would drop them. Per-field validation happens in parseSubagentMarkdown.\n return parseSimpleYaml(frontmatter);\n}\n"]}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var node_path = require('node:path');
|
|
5
5
|
|
|
6
6
|
// src/internal/security/path-containment.ts
|
|
7
7
|
function realOrResolved(path) {
|
|
8
8
|
try {
|
|
9
|
-
return
|
|
9
|
+
return node_fs.realpathSync(path);
|
|
10
10
|
} catch {
|
|
11
11
|
return path;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
function insideRoot(target, root) {
|
|
15
|
-
const rel =
|
|
16
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
15
|
+
const rel = node_path.relative(realOrResolved(root), realOrResolved(target));
|
|
16
|
+
return rel !== "" && !rel.startsWith("..") && !node_path.isAbsolute(rel);
|
|
17
17
|
}
|
|
18
18
|
function atOrInsideRoot(target, root) {
|
|
19
19
|
return insideRoot(target, root) || realOrResolved(target) === realOrResolved(root);
|
|
@@ -21,5 +21,5 @@ function atOrInsideRoot(target, root) {
|
|
|
21
21
|
|
|
22
22
|
exports.atOrInsideRoot = atOrInsideRoot;
|
|
23
23
|
exports.insideRoot = insideRoot;
|
|
24
|
-
//# sourceMappingURL=chunk-
|
|
25
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-MXJJMP4U.cjs.map
|
|
25
|
+
//# sourceMappingURL=chunk-MXJJMP4U.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/security/path-containment.ts"],"names":["realpathSync","relative","isAbsolute"],"mappings":";;;;;;AAoCO,SAAS,eAAe,IAAA,EAAsB;AACnD,EAAA,IAAI;AACF,IAAA,OAAOA,qBAAa,IAAI,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUO,SAAS,UAAA,CAAW,QAAgB,IAAA,EAAuB;AAChE,EAAA,MAAM,MAAMC,kBAAA,CAAS,cAAA,CAAe,IAAI,CAAA,EAAG,cAAA,CAAe,MAAM,CAAC,CAAA;AACjE,EAAA,OAAO,GAAA,KAAQ,MAAM,CAAC,GAAA,CAAI,WAAW,IAAI,CAAA,IAAK,CAACC,oBAAA,CAAW,GAAG,CAAA;AAC/D;AAgBO,SAAS,cAAA,CAAe,QAAgB,IAAA,EAAuB;AACpE,EAAA,OAAO,UAAA,CAAW,QAAQ,IAAI,CAAA,IAAK,eAAe,MAAM,CAAA,KAAM,eAAe,IAAI,CAAA;AACnF","file":"chunk-MXJJMP4U.cjs","sourcesContent":["/**\n * The one rule for \"is this resolved path inside that root\".\n *\n * Extracted because the package held it twice, at different strengths, for the same question. The\n * correct version lived as a private function in `context-import-resolver.ts`, written for the\n * 4.41.1 patch; the context manager had its own `absolute.startsWith(resolvePath(cwd))`, which\n * admits two escapes:\n *\n * - **No separator boundary.** With `root = /home/user/proj`, the path `/home/user/proj-evil/x`\n * starts with the root string and is NOT inside it. A sibling directory whose name merely\n * extends the project's is enough; no `..` past the parent is needed.\n * - **Lexical, not real.** A symlink whose name sits inside the root and whose target does not\n * passes any comparison made before symlink resolution.\n *\n * The obvious escapes (`../../etc/passwd`, an absolute path) ARE refused by a prefix test, which is\n * why the weaker version survived review: it looks like it works on the inputs a reader tries.\n *\n * Both readers of repository-supplied paths now share this module. That is DRY about the RULE\n * rather than about line count — one piece of knowledge, one representation, so the two cannot\n * drift apart again.\n *\n * @internal\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { isAbsolute, relative } from \"node:path\";\n\n/**\n * The real path, or the lexical one when it cannot be resolved.\n *\n * The fallback is load-bearing rather than defensive: the context manager checks containment BEFORE\n * it stats the file, so a source that is legitimately absent must still be judged on its declared\n * location instead of being refused for the wrong reason.\n *\n * @internal\n */\nexport function realOrResolved(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Whether `target` is inside `root`, compared AFTER symlink resolution.\n *\n * `relative()` supplies the separator boundary that a prefix test lacks: it answers `..` for a\n * sibling, `''` for the root itself, and an absolute path when the two are on different roots.\n *\n * @internal\n */\nexport function insideRoot(target: string, root: string): boolean {\n const rel = relative(realOrResolved(root), realOrResolved(target));\n return rel !== \"\" && !rel.startsWith(\"..\") && !isAbsolute(rel);\n}\n\n/**\n * Whether `target` is the root ITSELF or inside it, compared after symlink resolution.\n *\n * Separate from {@link insideRoot} because the two answer a genuinely different question and their\n * callers need different ones. The context manager asks \"is this source under the project?\", where\n * the project directory itself is not a source. A path JOIN asks \"did this stay within its base?\",\n * where joining nothing and getting the base back is the ordinary case — `safePathJoin(base)` must\n * return `base`.\n *\n * Named here rather than written out at each call site: it appeared twice within one change, which\n * is how this rule ended up with three copies at three strengths the first time.\n *\n * @internal\n */\nexport function atOrInsideRoot(target: string, root: string): boolean {\n return insideRoot(target, root) || realOrResolved(target) === realOrResolved(root);\n}\n"]}
|