@theokit/sdk 4.39.0 → 4.39.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 +61 -0
- package/dist/agent-KGKKAGXY.js +46 -0
- package/dist/{agent-KKZYLY5D.js.map → agent-KGKKAGXY.js.map} +1 -1
- package/dist/agent-TOWHPM7B.cjs +55 -0
- package/dist/{agent-I3EZZR6S.cjs.map → agent-TOWHPM7B.cjs.map} +1 -1
- package/dist/agent-session-store-EGTTYSKS.js +6 -0
- package/dist/{agent-session-store-RHMHP5DG.js.map → agent-session-store-EGTTYSKS.js.map} +1 -1
- package/dist/agent-session-store-RHTLZ5OX.cjs +23 -0
- package/dist/{agent-session-store-N4TVKHBU.cjs.map → agent-session-store-RHTLZ5OX.cjs.map} +1 -1
- package/dist/auth/index.cjs +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/{batch-X6JKGDPK.cjs → batch-FO2KZN7W.cjs} +26 -26
- package/dist/{batch-X6JKGDPK.cjs.map → batch-FO2KZN7W.cjs.map} +1 -1
- package/dist/{batch-6KQM3EBA.js → batch-WX2HLXX2.js} +9 -9
- package/dist/{batch-6KQM3EBA.js.map → batch-WX2HLXX2.js.map} +1 -1
- package/dist/{chunk-ECHGWE47.cjs → chunk-27E4EMR6.cjs} +4 -4
- package/dist/{chunk-ECHGWE47.cjs.map → chunk-27E4EMR6.cjs.map} +1 -1
- package/dist/{chunk-DGLLVCNB.cjs → chunk-33B2BSME.cjs} +4 -4
- package/dist/{chunk-DGLLVCNB.cjs.map → chunk-33B2BSME.cjs.map} +1 -1
- package/dist/{chunk-KE6XKRLQ.js → chunk-3CAQCFVZ.js} +3 -3
- package/dist/{chunk-KE6XKRLQ.js.map → chunk-3CAQCFVZ.js.map} +1 -1
- package/dist/{chunk-XYOHKCYG.cjs → chunk-3LK3ZDL5.cjs} +5 -5
- package/dist/{chunk-XYOHKCYG.cjs.map → chunk-3LK3ZDL5.cjs.map} +1 -1
- package/dist/{chunk-I53ZQHU6.cjs → chunk-4E3HLSYE.cjs} +4 -4
- package/dist/{chunk-I53ZQHU6.cjs.map → chunk-4E3HLSYE.cjs.map} +1 -1
- package/dist/{chunk-AR5MQZ3J.js → chunk-4GGXF7YM.js} +3 -3
- package/dist/{chunk-AR5MQZ3J.js.map → chunk-4GGXF7YM.js.map} +1 -1
- package/dist/{chunk-4JSZYSGW.js → chunk-4H2DCO6P.js} +3 -3
- package/dist/{chunk-4JSZYSGW.js.map → chunk-4H2DCO6P.js.map} +1 -1
- package/dist/{chunk-75DSEWTJ.cjs → chunk-4H2QARXM.cjs} +7 -7
- package/dist/{chunk-75DSEWTJ.cjs.map → chunk-4H2QARXM.cjs.map} +1 -1
- package/dist/{chunk-JZUPMTJY.js → chunk-4HKAZRTC.js} +3 -3
- package/dist/{chunk-JZUPMTJY.js.map → chunk-4HKAZRTC.js.map} +1 -1
- package/dist/{chunk-CEIHCKTO.cjs → chunk-5COPO3H4.cjs} +4 -4
- package/dist/{chunk-CEIHCKTO.cjs.map → chunk-5COPO3H4.cjs.map} +1 -1
- package/dist/{chunk-KX7JV5KM.js → chunk-5XPKYDK7.js} +3 -3
- package/dist/{chunk-KX7JV5KM.js.map → chunk-5XPKYDK7.js.map} +1 -1
- package/dist/{chunk-E2FFG33C.js → chunk-7HP6N5X3.js} +5 -5
- package/dist/{chunk-E2FFG33C.js.map → chunk-7HP6N5X3.js.map} +1 -1
- package/dist/{chunk-3I4B3OIX.js → chunk-7PBAJ2WA.js} +3 -3
- package/dist/{chunk-3I4B3OIX.js.map → chunk-7PBAJ2WA.js.map} +1 -1
- package/dist/{chunk-KGRIHEDT.js → chunk-7WXT5M6P.js} +5 -5
- package/dist/{chunk-KGRIHEDT.js.map → chunk-7WXT5M6P.js.map} +1 -1
- package/dist/{chunk-BCQ3E2ZG.cjs → chunk-A76OFN2B.cjs} +8 -4
- package/dist/chunk-A76OFN2B.cjs.map +1 -0
- package/dist/{chunk-TOTP24P6.js → chunk-AFJAQ5JV.js} +4 -4
- package/dist/{chunk-TOTP24P6.js.map → chunk-AFJAQ5JV.js.map} +1 -1
- package/dist/{chunk-DKFGUOWL.js → chunk-AKMPQJPX.js} +3 -3
- package/dist/{chunk-DKFGUOWL.js.map → chunk-AKMPQJPX.js.map} +1 -1
- package/dist/{chunk-HIQFVHC6.cjs → chunk-ALDFVGQQ.cjs} +9 -9
- package/dist/{chunk-HIQFVHC6.cjs.map → chunk-ALDFVGQQ.cjs.map} +1 -1
- package/dist/{chunk-INSFS5GT.cjs → chunk-AYDSWY35.cjs} +293 -293
- package/dist/{chunk-INSFS5GT.cjs.map → chunk-AYDSWY35.cjs.map} +1 -1
- package/dist/{chunk-YRXY2A5L.js → chunk-BBMN4O6E.js} +4 -4
- package/dist/{chunk-YRXY2A5L.js.map → chunk-BBMN4O6E.js.map} +1 -1
- package/dist/{chunk-6EEYRFJC.js → chunk-BM7VYZED.js} +5 -5
- package/dist/{chunk-6EEYRFJC.js.map → chunk-BM7VYZED.js.map} +1 -1
- package/dist/{chunk-ML5A5DOJ.js → chunk-C64E2EJS.js} +4 -4
- package/dist/{chunk-ML5A5DOJ.js.map → chunk-C64E2EJS.js.map} +1 -1
- package/dist/{chunk-E4IUN3JB.cjs → chunk-CJHBSSAF.cjs} +15 -15
- package/dist/{chunk-E4IUN3JB.cjs.map → chunk-CJHBSSAF.cjs.map} +1 -1
- package/dist/{chunk-IHFNRNTJ.cjs → chunk-E7WWIX34.cjs} +8 -8
- package/dist/{chunk-IHFNRNTJ.cjs.map → chunk-E7WWIX34.cjs.map} +1 -1
- package/dist/{chunk-C7OP3FAK.cjs → chunk-EJNFZET4.cjs} +11 -11
- package/dist/{chunk-C7OP3FAK.cjs.map → chunk-EJNFZET4.cjs.map} +1 -1
- package/dist/{chunk-ECW2MLEW.cjs → chunk-ELYGL52R.cjs} +4 -4
- package/dist/{chunk-ECW2MLEW.cjs.map → chunk-ELYGL52R.cjs.map} +1 -1
- package/dist/{chunk-GIRD3RWH.js → chunk-ERBH54V3.js} +35 -35
- package/dist/{chunk-GIRD3RWH.js.map → chunk-ERBH54V3.js.map} +1 -1
- package/dist/{chunk-JRLXEYR4.js → chunk-EYUZZXHZ.js} +4 -4
- package/dist/{chunk-JRLXEYR4.js.map → chunk-EYUZZXHZ.js.map} +1 -1
- package/dist/{chunk-PI4RO4GR.js → chunk-F4SQTWVR.js} +3 -3
- package/dist/{chunk-PI4RO4GR.js.map → chunk-F4SQTWVR.js.map} +1 -1
- package/dist/{chunk-LALG2K33.cjs → chunk-FMDZZRYE.cjs} +8 -8
- package/dist/{chunk-LALG2K33.cjs.map → chunk-FMDZZRYE.cjs.map} +1 -1
- package/dist/{chunk-SCFW5MJZ.js → chunk-FUP3ZA5T.js} +4 -4
- package/dist/{chunk-SCFW5MJZ.js.map → chunk-FUP3ZA5T.js.map} +1 -1
- package/dist/{chunk-DWY25VLP.cjs → chunk-H2V6CXM7.cjs} +9 -9
- package/dist/{chunk-DWY25VLP.cjs.map → chunk-H2V6CXM7.cjs.map} +1 -1
- package/dist/{chunk-DU7DE543.js → chunk-HCRCNI7A.js} +3 -3
- package/dist/{chunk-DU7DE543.js.map → chunk-HCRCNI7A.js.map} +1 -1
- package/dist/{chunk-QKE556YD.js → chunk-HJMFPIRM.js} +5 -5
- package/dist/{chunk-QKE556YD.js.map → chunk-HJMFPIRM.js.map} +1 -1
- package/dist/{chunk-J3UK2GMN.js → chunk-IEKSEAIV.js} +3 -3
- package/dist/{chunk-J3UK2GMN.js.map → chunk-IEKSEAIV.js.map} +1 -1
- package/dist/{chunk-DMTZ3FII.cjs → chunk-JERO4TP6.cjs} +5 -5
- package/dist/{chunk-DMTZ3FII.cjs.map → chunk-JERO4TP6.cjs.map} +1 -1
- package/dist/{chunk-6LGQECT7.cjs → chunk-K6TWHNT4.cjs} +11 -11
- package/dist/{chunk-6LGQECT7.cjs.map → chunk-K6TWHNT4.cjs.map} +1 -1
- package/dist/{chunk-4F7JQZOX.js → chunk-KRPEPZQY.js} +7 -7
- package/dist/{chunk-4F7JQZOX.js.map → chunk-KRPEPZQY.js.map} +1 -1
- package/dist/{chunk-CN6UX5EV.cjs → chunk-LNTQRD23.cjs} +26 -26
- package/dist/{chunk-CN6UX5EV.cjs.map → chunk-LNTQRD23.cjs.map} +1 -1
- package/dist/{chunk-W4WKVOOQ.cjs → chunk-LOTWA65Z.cjs} +11 -11
- package/dist/{chunk-W4WKVOOQ.cjs.map → chunk-LOTWA65Z.cjs.map} +1 -1
- package/dist/{chunk-BPN5ONSW.cjs → chunk-LR3VKB7A.cjs} +124 -120
- package/dist/chunk-LR3VKB7A.cjs.map +1 -0
- package/dist/{chunk-ET6PHZRX.js → chunk-LWVKI75U.js} +3 -3
- package/dist/{chunk-ET6PHZRX.js.map → chunk-LWVKI75U.js.map} +1 -1
- package/dist/{chunk-G2Q3ZC6C.cjs → chunk-LY4KEOOY.cjs} +4 -4
- package/dist/{chunk-G2Q3ZC6C.cjs.map → chunk-LY4KEOOY.cjs.map} +1 -1
- package/dist/{chunk-42IFCKQZ.js → chunk-LYD3ALDT.js} +5 -5
- package/dist/{chunk-42IFCKQZ.js.map → chunk-LYD3ALDT.js.map} +1 -1
- package/dist/{chunk-KWUNN5J2.js → chunk-MDN44YMZ.js} +5 -5
- package/dist/{chunk-KWUNN5J2.js.map → chunk-MDN44YMZ.js.map} +1 -1
- package/dist/{chunk-HWNVAUK4.js → chunk-MI3OKONE.js} +3 -3
- package/dist/{chunk-HWNVAUK4.js.map → chunk-MI3OKONE.js.map} +1 -1
- package/dist/{chunk-VJYSQXN6.cjs → chunk-MM2JKFQV.cjs} +10 -10
- package/dist/{chunk-VJYSQXN6.cjs.map → chunk-MM2JKFQV.cjs.map} +1 -1
- package/dist/{chunk-74D7KSBP.cjs → chunk-MWVLE5RY.cjs} +11 -11
- package/dist/{chunk-74D7KSBP.cjs.map → chunk-MWVLE5RY.cjs.map} +1 -1
- package/dist/{chunk-V2RBCCN5.js → chunk-NHXPQN44.js} +3 -3
- package/dist/{chunk-V2RBCCN5.js.map → chunk-NHXPQN44.js.map} +1 -1
- package/dist/{chunk-Z2CUEBXV.cjs → chunk-NJVV5OAV.cjs} +8 -8
- package/dist/{chunk-Z2CUEBXV.cjs.map → chunk-NJVV5OAV.cjs.map} +1 -1
- package/dist/{chunk-J6T5LWJ4.cjs → chunk-OVGNXGCK.cjs} +9 -9
- package/dist/{chunk-J6T5LWJ4.cjs.map → chunk-OVGNXGCK.cjs.map} +1 -1
- package/dist/{chunk-TIMPJL5V.cjs → chunk-PDOLUFGY.cjs} +4 -4
- package/dist/{chunk-TIMPJL5V.cjs.map → chunk-PDOLUFGY.cjs.map} +1 -1
- package/dist/{chunk-FWBSDQZ7.cjs → chunk-PIBXDEZM.cjs} +5 -5
- package/dist/{chunk-FWBSDQZ7.cjs.map → chunk-PIBXDEZM.cjs.map} +1 -1
- package/dist/{chunk-USZTVNUQ.cjs → chunk-PY7G7YWK.cjs} +5 -5
- package/dist/{chunk-USZTVNUQ.cjs.map → chunk-PY7G7YWK.cjs.map} +1 -1
- package/dist/{chunk-N475ETD6.cjs → chunk-QMIG7TKG.cjs} +17 -17
- package/dist/{chunk-N475ETD6.cjs.map → chunk-QMIG7TKG.cjs.map} +1 -1
- package/dist/{chunk-D7IULBF2.js → chunk-RA2UZBYP.js} +3 -3
- package/dist/{chunk-D7IULBF2.js.map → chunk-RA2UZBYP.js.map} +1 -1
- package/dist/{chunk-7OLG7VI4.cjs → chunk-RHQJX3EI.cjs} +4 -4
- package/dist/{chunk-7OLG7VI4.cjs.map → chunk-RHQJX3EI.cjs.map} +1 -1
- package/dist/{chunk-YP67XZES.js → chunk-RMCHJZNZ.js} +4 -4
- package/dist/{chunk-YP67XZES.js.map → chunk-RMCHJZNZ.js.map} +1 -1
- package/dist/{chunk-ZYB6B7QJ.js → chunk-RMO7HAIF.js} +13 -9
- package/dist/chunk-RMO7HAIF.js.map +1 -0
- package/dist/{chunk-IVSD7L22.js → chunk-RNCN2TTP.js} +8 -4
- package/dist/chunk-RNCN2TTP.js.map +1 -0
- package/dist/{chunk-BQDUEG4C.js → chunk-RTDT2UMT.js} +3 -3
- package/dist/{chunk-BQDUEG4C.js.map → chunk-RTDT2UMT.js.map} +1 -1
- package/dist/{chunk-FRUBE44B.cjs → chunk-TZ47DTM2.cjs} +24 -24
- package/dist/{chunk-FRUBE44B.cjs.map → chunk-TZ47DTM2.cjs.map} +1 -1
- package/dist/{chunk-6YNYA7GD.cjs → chunk-U53AYSHS.cjs} +11 -11
- package/dist/{chunk-6YNYA7GD.cjs.map → chunk-U53AYSHS.cjs.map} +1 -1
- package/dist/{chunk-G4XUXSRN.cjs → chunk-UDSGSPUY.cjs} +12 -12
- package/dist/{chunk-G4XUXSRN.cjs.map → chunk-UDSGSPUY.cjs.map} +1 -1
- package/dist/{chunk-LJX2SQV2.js → chunk-UIPTINIF.js} +6 -6
- package/dist/{chunk-LJX2SQV2.js.map → chunk-UIPTINIF.js.map} +1 -1
- package/dist/{chunk-6NEHIHFU.cjs → chunk-V7CA3CX5.cjs} +4 -4
- package/dist/{chunk-6NEHIHFU.cjs.map → chunk-V7CA3CX5.cjs.map} +1 -1
- package/dist/{chunk-SG5ZWT6C.js → chunk-VQ33XLC6.js} +3 -3
- package/dist/{chunk-SG5ZWT6C.js.map → chunk-VQ33XLC6.js.map} +1 -1
- package/dist/{chunk-AECV3G5Z.js → chunk-VTDQRUAJ.js} +4 -4
- package/dist/{chunk-AECV3G5Z.js.map → chunk-VTDQRUAJ.js.map} +1 -1
- package/dist/{chunk-MC5EINBV.js → chunk-WJOLIPEM.js} +8 -8
- package/dist/{chunk-MC5EINBV.js.map → chunk-WJOLIPEM.js.map} +1 -1
- package/dist/{chunk-7U2HAAWB.cjs → chunk-X2LDDKFM.cjs} +10 -10
- package/dist/{chunk-7U2HAAWB.cjs.map → chunk-X2LDDKFM.cjs.map} +1 -1
- package/dist/{chunk-LGILIGV3.js → chunk-XKGOIGTJ.js} +3 -3
- package/dist/{chunk-LGILIGV3.js.map → chunk-XKGOIGTJ.js.map} +1 -1
- package/dist/{chunk-65ONFZWM.js → chunk-XZ7PO2FM.js} +4 -4
- package/dist/{chunk-65ONFZWM.js.map → chunk-XZ7PO2FM.js.map} +1 -1
- package/dist/{chunk-DCXRHCZJ.cjs → chunk-Z5DYGBPV.cjs} +8 -8
- package/dist/{chunk-DCXRHCZJ.cjs.map → chunk-Z5DYGBPV.cjs.map} +1 -1
- package/dist/{chunk-7YBJZS5P.cjs → chunk-ZAKTZ5B4.cjs} +8 -8
- package/dist/{chunk-7YBJZS5P.cjs.map → chunk-ZAKTZ5B4.cjs.map} +1 -1
- package/dist/compact-session-FCEFK2CG.cjs +55 -0
- package/dist/{compact-session-VVT6IBJG.cjs.map → compact-session-FCEFK2CG.cjs.map} +1 -1
- package/dist/compact-session-KPYRNTQN.js +18 -0
- package/dist/{compact-session-PYEC3NZV.js.map → compact-session-KPYRNTQN.js.map} +1 -1
- package/dist/compaction.cjs +16 -16
- package/dist/compaction.js +3 -3
- package/dist/concurrency.cjs +6 -6
- package/dist/concurrency.js +4 -4
- package/dist/context-DNFT4B5D.cjs +22 -0
- package/dist/{context-2FZSC6VQ.cjs.map → context-DNFT4B5D.cjs.map} +1 -1
- package/dist/context-LYE6TJL5.js +5 -0
- package/dist/{context-ATJJAFOS.js.map → context-LYE6TJL5.js.map} +1 -1
- package/dist/cron.cjs +31 -31
- package/dist/cron.js +30 -30
- package/dist/errors.cjs +21 -21
- package/dist/errors.js +2 -2
- package/dist/eval.cjs +36 -36
- package/dist/eval.js +30 -30
- package/dist/{executor-T65PPOPT.js → executor-PCRX3533.js} +7 -7
- package/dist/{executor-T65PPOPT.js.map → executor-PCRX3533.js.map} +1 -1
- package/dist/{executor-BMRICN46.cjs → executor-TQ3FSZ7F.cjs} +24 -24
- package/dist/{executor-BMRICN46.cjs.map → executor-TQ3FSZ7F.cjs.map} +1 -1
- package/dist/filesystem/index.cjs +7 -7
- package/dist/filesystem/index.js +3 -3
- package/dist/fs-session-store-V32XJ63Z.cjs +19 -0
- package/dist/{fs-session-store-J5MCFFUM.cjs.map → fs-session-store-V32XJ63Z.cjs.map} +1 -1
- package/dist/fs-session-store-YPZIOLJM.js +10 -0
- package/dist/{fs-session-store-O6FNHECD.js.map → fs-session-store-YPZIOLJM.js.map} +1 -1
- package/dist/generate-object-DLHT5RXH.js +7 -0
- package/dist/{generate-object-XDCBYVK3.js.map → generate-object-DLHT5RXH.js.map} +1 -1
- package/dist/generate-object-QILSGTEJ.cjs +20 -0
- package/dist/{generate-object-UF6WIZLF.cjs.map → generate-object-QILSGTEJ.cjs.map} +1 -1
- package/dist/index-manager-LEMQ73A7.cjs +20 -0
- package/dist/{index-manager-N7RQYUKT.cjs.map → index-manager-LEMQ73A7.cjs.map} +1 -1
- package/dist/index-manager-SXF3FAQV.js +11 -0
- package/dist/{index-manager-ZVZUHPP7.js.map → index-manager-SXF3FAQV.js.map} +1 -1
- package/dist/index.cjs +129 -129
- package/dist/index.js +43 -43
- package/dist/{inject-session-LFCYVSLJ.cjs → inject-session-BIVVFWGL.cjs} +9 -9
- package/dist/{inject-session-LFCYVSLJ.cjs.map → inject-session-BIVVFWGL.cjs.map} +1 -1
- package/dist/{inject-session-GQMTDZUN.js → inject-session-OLWZ2NJN.js} +7 -7
- package/dist/{inject-session-GQMTDZUN.js.map → inject-session-OLWZ2NJN.js.map} +1 -1
- package/dist/internal/memory/adapters/index.cjs +8 -8
- package/dist/internal/memory/adapters/index.js +7 -7
- package/dist/internal/persistence/index.cjs +25 -25
- package/dist/internal/persistence/index.js +6 -6
- package/dist/internal/security/index.cjs +13 -13
- package/dist/internal/security/index.js +3 -3
- package/dist/judge-call-AIT3EULV.cjs +22 -0
- package/dist/{judge-call-U2D33JOW.cjs.map → judge-call-AIT3EULV.cjs.map} +1 -1
- package/dist/judge-call-NPRU7JIQ.js +5 -0
- package/dist/{judge-call-NA2G6ZMJ.js.map → judge-call-NPRU7JIQ.js.map} +1 -1
- package/dist/models.cjs +20 -20
- package/dist/models.js +5 -5
- package/dist/path-safety.cjs +10 -10
- package/dist/path-safety.js +3 -3
- package/dist/persistence.cjs +26 -26
- package/dist/persistence.js +7 -7
- package/dist/project.cjs +5 -5
- package/dist/project.js +3 -3
- package/dist/registry-AXQXPLFH.js +9 -0
- package/dist/{registry-CAS6Y5ZY.js.map → registry-AXQXPLFH.js.map} +1 -1
- package/dist/registry-VKDFTK6L.cjs +46 -0
- package/dist/{registry-JSDI4WKA.cjs.map → registry-VKDFTK6L.cjs.map} +1 -1
- package/dist/retry.cjs +4 -4
- package/dist/retry.js +3 -3
- package/dist/sandbox/index.cjs +6 -6
- package/dist/sandbox/index.js +2 -2
- package/dist/server/errors-envelope.cjs +14 -14
- package/dist/server/errors-envelope.js +3 -3
- package/dist/session-transcript-SLQRKQ72.cjs +50 -0
- package/dist/{session-transcript-H7NUFXXM.cjs.map → session-transcript-SLQRKQ72.cjs.map} +1 -1
- package/dist/session-transcript-X3UMSVL2.js +5 -0
- package/dist/{session-transcript-3B52DKTG.js.map → session-transcript-X3UMSVL2.js.map} +1 -1
- package/dist/skills.cjs +7 -7
- package/dist/skills.js +5 -5
- package/dist/stream-object-3HEUGMQY.cjs +20 -0
- package/dist/{stream-object-4FDU76YW.cjs.map → stream-object-3HEUGMQY.cjs.map} +1 -1
- package/dist/stream-object-VWYW7FJ2.js +7 -0
- package/dist/{stream-object-HWDEEQN4.js.map → stream-object-VWYW7FJ2.js.map} +1 -1
- package/dist/subagents-loader-F52ARQ62.cjs +15 -0
- package/dist/{subagents-loader-A4W6C3QP.cjs.map → subagents-loader-F52ARQ62.cjs.map} +1 -1
- package/dist/subagents-loader-QUIK6GMV.js +6 -0
- package/dist/{subagents-loader-X33KH2SZ.js.map → subagents-loader-QUIK6GMV.js.map} +1 -1
- package/dist/subagents-loader.cjs +6 -6
- package/dist/subagents-loader.js +4 -4
- package/dist/subscription/index.cjs +3 -3
- package/dist/subscription/index.js +2 -2
- package/dist/task-store.cjs +7 -7
- package/dist/task-store.js +4 -4
- package/dist/workflow.cjs +27 -27
- package/dist/workflow.js +6 -6
- package/package.json +1 -1
- package/dist/agent-I3EZZR6S.cjs +0 -55
- package/dist/agent-KKZYLY5D.js +0 -46
- package/dist/agent-session-store-N4TVKHBU.cjs +0 -23
- package/dist/agent-session-store-RHMHP5DG.js +0 -6
- package/dist/chunk-BCQ3E2ZG.cjs.map +0 -1
- package/dist/chunk-BPN5ONSW.cjs.map +0 -1
- package/dist/chunk-IVSD7L22.js.map +0 -1
- package/dist/chunk-ZYB6B7QJ.js.map +0 -1
- package/dist/compact-session-PYEC3NZV.js +0 -18
- package/dist/compact-session-VVT6IBJG.cjs +0 -55
- package/dist/context-2FZSC6VQ.cjs +0 -22
- package/dist/context-ATJJAFOS.js +0 -5
- package/dist/fs-session-store-J5MCFFUM.cjs +0 -19
- package/dist/fs-session-store-O6FNHECD.js +0 -10
- package/dist/generate-object-UF6WIZLF.cjs +0 -20
- package/dist/generate-object-XDCBYVK3.js +0 -7
- package/dist/index-manager-N7RQYUKT.cjs +0 -20
- package/dist/index-manager-ZVZUHPP7.js +0 -11
- package/dist/judge-call-NA2G6ZMJ.js +0 -5
- package/dist/judge-call-U2D33JOW.cjs +0 -22
- package/dist/registry-CAS6Y5ZY.js +0 -9
- package/dist/registry-JSDI4WKA.cjs +0 -46
- package/dist/session-transcript-3B52DKTG.js +0 -5
- package/dist/session-transcript-H7NUFXXM.cjs +0 -50
- package/dist/stream-object-4FDU76YW.cjs +0 -20
- package/dist/stream-object-HWDEEQN4.js +0 -7
- package/dist/subagents-loader-A4W6C3QP.cjs +0 -15
- package/dist/subagents-loader-X33KH2SZ.js +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/batch.ts"],"names":[],"mappings":"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","file":"batch-6KQM3EBA.js","sourcesContent":["/**\n * `Agent.batch(prompts, options)` core (ADRs D134-D140).\n *\n * Fans out N prompts via bounded concurrency, isolates failures per-prompt,\n * preserves input order in the result array, supports streaming callbacks\n * + AbortSignal. The credential pool (D131) is shared across all\n * in-flight agents via `withCredentialPool` ALS scope (EC-A fix).\n *\n * @internal\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { ConfigurationError, TheokitAgentError } from \"./errors.js\";\nimport { diag } from \"./internal/diagnostics.js\";\nimport { CredentialPool, newPooledCredential } from \"./internal/llm/credential-pool.js\";\nimport { withCredentialPool } from \"./internal/llm/credential-pool-context.js\";\nimport type { CredentialPoolStrategy } from \"./internal/llm/credential-pool-types.js\";\nimport { createSemaphore } from \"./internal/runtime/concurrency/async-semaphore.js\";\nimport { get as taskRegistryGet, submit as taskRegistrySubmit } from \"./internal/task/registry.js\";\nimport type { AgentOptions, SDKAgent } from \"./types/agent.js\";\nimport type { BatchItem, BatchOptions, BatchProgress, BatchResult } from \"./types/batch.js\";\n\n/** DI contract — keeps batch.ts cycle-free from agent.ts. @internal */\ninterface BatchDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Pre-flight boundary validation for `Agent.batch` (arch-review Gap 3).\n *\n * Mirrors the in-repo `validateAgentOptions` / `validateCronExpression`\n * pattern: throws `ConfigurationError` with a stable code on the first\n * violation, returns silently otherwise. Pure — no I/O.\n *\n * Rules:\n * - `concurrency`, when provided, must be a positive integer (rejects\n * 0/negative/non-integer/NaN/Infinity). Previously this was caught only\n * incidentally deep inside `createSemaphore` with a leaky \"permits\"\n * message, AND after task wrapping (dangling Task on invalid input).\n * - every prompt item must carry a non-empty string prompt. Previously\n * `normalizeItem` passed `prompt: \"\"` / non-string straight to\n * `agent.send`. (Whitespace-only strings are intentionally NOT rejected —\n * an empty string is a structural error; judging content is not this\n * validator's job, and the repo's property tests treat any length>=1\n * string as a valid prompt.)\n *\n * @internal\n */\nexport function validateBatchInput(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n): void {\n const { concurrency } = options;\n if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {\n throw new ConfigurationError(\n `Agent.batch concurrency must be a positive integer, got ${concurrency}`,\n { code: \"invalid_concurrency\" },\n );\n }\n for (let i = 0; i < prompts.length; i++) {\n const item = prompts[i];\n const prompt = typeof item === \"string\" ? item : item?.prompt;\n if (typeof prompt !== \"string\" || prompt.length === 0) {\n throw new ConfigurationError(`Agent.batch prompt at index ${i} must be a non-empty string`, {\n code: \"invalid_batch_item\",\n });\n }\n }\n}\n\n/**\n * Run N prompts in parallel with bounded concurrency.\n *\n * @internal\n */\nexport async function batchImpl(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult[]> {\n // EC-1: empty array → no work, no agents created.\n if (prompts.length === 0) return [];\n\n // Pre-flight boundary validation (arch-review Gap 3). MUST run before pool\n // build AND before wrapBatchAsTask so invalid input never registers a\n // dangling Task or builds a credential pool (fail-fast, Rule 8).\n validateBatchInput(prompts, options);\n\n // EC-A fix: build credential pools ONCE and share via AsyncLocalStorage.\n // Without this each Agent.create() would build its own pool from identical\n // apiKeys → 4× rate-limit wastage per concurrency window.\n const sharedPools = buildPoolsFromApiKeys(\n options.providers?.apiKeys,\n options.providers?.credentialPoolStrategy,\n );\n const exec = (): Promise<BatchResult[]> => runBatch(prompts, options, deps);\n const withPool = (): Promise<BatchResult[]> =>\n sharedPools.size > 0 ? withCredentialPool(sharedPools, exec) : exec();\n // T3.3: opt-in Task wrapping (ADRs D363, D374). Register the whole\n // batch as a single Task (kind=\"batch\") with `b-` prefix (D368/EC-5).\n if (options.task !== undefined) {\n return wrapBatchAsTask(prompts.length, options.task, withPool);\n }\n return withPool();\n}\n\nasync function wrapBatchAsTask(\n total: number,\n taskOpt: true | { id?: string; meta?: Record<string, unknown> },\n exec: () => Promise<BatchResult[]>,\n): Promise<BatchResult[]> {\n const opts = taskOpt === true ? {} : taskOpt;\n const id = opts.id ?? `b-${randomUUID()}`;\n const meta: Record<string, unknown> = {\n total,\n ...(opts.meta ?? {}),\n };\n let results: BatchResult[] = [];\n await taskRegistrySubmit({\n kind: \"batch\",\n work: async (ctx) => {\n results = await exec();\n const succeeded = results.filter((r) => r.ok).length;\n const failed = results.length - succeeded;\n ctx.emit({ total: results.length, succeeded, failed });\n return { total: results.length, succeeded, failed };\n },\n id,\n meta,\n allowReservedPrefix: true,\n });\n // submit returns immediately with a queued handle. Wait for terminal.\n for (let i = 0; i < 5000; i++) {\n const h = await taskRegistryGet(id);\n if (h?.state === \"finished\" || h?.state === \"error\" || h?.state === \"cancelled\") break;\n await new Promise((r) => setTimeout(r, 5));\n }\n return results;\n}\n\nasync function runBatch(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult[]> {\n const requested = options.concurrency ?? 4;\n // createSemaphore throws on invalid (EC-2).\n // EC-3: cap to prompts.length to avoid idle workers.\n const effective = Math.min(requested, prompts.length);\n const semaphore = createSemaphore(effective);\n const items = prompts.map(normalizeItem);\n const results: BatchResult[] = new Array(items.length);\n const counters = { completed: 0, failed: 0 };\n let aborted = false;\n\n const onAbort = (): void => {\n aborted = true;\n };\n options.signal?.addEventListener(\"abort\", onAbort);\n // EC-C: pre-aborted signal — treat as already-aborted before any work.\n if (options.signal?.aborted === true) aborted = true;\n\n try {\n await Promise.all(\n items.map(async (item, index) => {\n const release = await semaphore.acquire();\n try {\n if (aborted) {\n results[index] = abortResult(item, index, options.signal);\n } else {\n results[index] = await runOne(item, index, options, deps);\n }\n } finally {\n release();\n }\n if (results[index].ok) counters.completed += 1;\n else counters.failed += 1;\n await safeCallResult(options.onResult, results[index]);\n safeCallProgress(options.onProgress, {\n total: items.length,\n completed: counters.completed,\n failed: counters.failed,\n inFlight: semaphore.inFlight(),\n pending: Math.max(0, semaphore.pending() - semaphore.inFlight()),\n });\n }),\n );\n } finally {\n options.signal?.removeEventListener(\"abort\", onAbort);\n }\n\n return options.filter ? results.filter(options.filter) : results;\n}\n\nasync function runOne(\n item: BatchItem,\n index: number,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult> {\n const t0 = Date.now();\n const agentOpts = buildAgentOptions(item, options);\n\n try {\n const agent = await deps.create(agentOpts);\n try {\n const run = await agent.send(item.prompt);\n const result = await run.wait();\n return {\n ok: true,\n index,\n prompt: item.prompt,\n result,\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: Date.now() - t0,\n };\n } finally {\n // EC-8: dispose failure → stderr warn, don't fail the result.\n try {\n await agent.dispose();\n } catch (disposeErr) {\n // theokit#147 — the interceptable channel; a batch run must not write onto a TUI's frame.\n diag(\n `[theokit-sdk] batch: agent.dispose failed for prompt ${index}: ${\n disposeErr instanceof Error ? disposeErr.message : String(disposeErr)\n }\\n`,\n );\n }\n }\n } catch (err) {\n return {\n ok: false,\n index,\n prompt: item.prompt,\n error: toTheokitError(err),\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: Date.now() - t0,\n };\n }\n}\n\nfunction buildAgentOptions(item: BatchItem, options: BatchOptions): AgentOptions {\n const {\n concurrency: _c,\n filter: _f,\n onResult: _or,\n onProgress: _op,\n signal: _s,\n task: _t,\n ...rest\n } = options;\n void _c;\n void _f;\n void _or;\n void _op;\n void _s;\n void _t;\n const agentOpts = rest as AgentOptions;\n if (item.systemPrompt !== undefined) {\n return { ...agentOpts, systemPrompt: item.systemPrompt };\n }\n return agentOpts;\n}\n\nfunction normalizeItem(promptOrItem: string | BatchItem): BatchItem {\n return typeof promptOrItem === \"string\" ? { prompt: promptOrItem } : promptOrItem;\n}\n\nfunction abortResult(item: BatchItem, index: number, signal?: AbortSignal): BatchResult {\n // EC-D: propagate signal.reason when set (preserves caller's error).\n let error: TheokitAgentError;\n if (signal?.reason instanceof TheokitAgentError) {\n error = signal.reason;\n } else if (signal?.reason instanceof Error) {\n error = new TheokitAgentError(signal.reason.message, {\n code: \"aborted\",\n cause: signal.reason,\n });\n } else {\n error = new TheokitAgentError(\"Batch aborted via AbortSignal\", { code: \"aborted\" });\n }\n return {\n ok: false,\n index,\n prompt: item.prompt,\n error,\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: 0,\n };\n}\n\nfunction toTheokitError(err: unknown): TheokitAgentError {\n if (err instanceof TheokitAgentError) return err;\n if (err instanceof Error) {\n return new TheokitAgentError(err.message, { code: \"unknown\", cause: err });\n }\n return new TheokitAgentError(String(err), { code: \"unknown\" });\n}\n\nasync function safeCallResult(cb: BatchOptions[\"onResult\"], result: BatchResult): Promise<void> {\n if (cb === undefined) return;\n try {\n await cb(result);\n } catch (err) {\n diag(\n `[theokit-sdk] batch: onResult callback threw: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n }\n}\n\nfunction safeCallProgress(cb: BatchOptions[\"onProgress\"], progress: BatchProgress): void {\n if (cb === undefined) return;\n try {\n cb(progress);\n } catch (err) {\n diag(\n `[theokit-sdk] batch: onProgress callback threw: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n }\n}\n\n/**\n * EC-A: construct shared pools from options.apiKeys. Empty/invalid → empty Map.\n *\n * @internal\n */\nfunction buildPoolsFromApiKeys(\n apiKeys: Record<string, string[]> | undefined,\n strategy: Record<string, CredentialPoolStrategy> | undefined,\n): Map<string, CredentialPool> {\n const pools = new Map<string, CredentialPool>();\n if (apiKeys === undefined) return pools;\n for (const [provider, keys] of Object.entries(apiKeys)) {\n if (!Array.isArray(keys)) continue;\n const filtered = keys.filter((k): k is string => typeof k === \"string\" && k.length > 0);\n if (filtered.length < 2) continue; // 1-key pools aren't pools\n const entries = filtered.map((token, i) =>\n newPooledCredential({ provider, accessToken: token, priority: i, source: \"manual\" }),\n );\n pools.set(\n provider,\n new CredentialPool(provider, entries, strategy?.[provider] ?? \"fill_first\"),\n );\n }\n return pools;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/batch.ts"],"names":[],"mappings":"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","file":"batch-WX2HLXX2.js","sourcesContent":["/**\n * `Agent.batch(prompts, options)` core (ADRs D134-D140).\n *\n * Fans out N prompts via bounded concurrency, isolates failures per-prompt,\n * preserves input order in the result array, supports streaming callbacks\n * + AbortSignal. The credential pool (D131) is shared across all\n * in-flight agents via `withCredentialPool` ALS scope (EC-A fix).\n *\n * @internal\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { ConfigurationError, TheokitAgentError } from \"./errors.js\";\nimport { diag } from \"./internal/diagnostics.js\";\nimport { CredentialPool, newPooledCredential } from \"./internal/llm/credential-pool.js\";\nimport { withCredentialPool } from \"./internal/llm/credential-pool-context.js\";\nimport type { CredentialPoolStrategy } from \"./internal/llm/credential-pool-types.js\";\nimport { createSemaphore } from \"./internal/runtime/concurrency/async-semaphore.js\";\nimport { get as taskRegistryGet, submit as taskRegistrySubmit } from \"./internal/task/registry.js\";\nimport type { AgentOptions, SDKAgent } from \"./types/agent.js\";\nimport type { BatchItem, BatchOptions, BatchProgress, BatchResult } from \"./types/batch.js\";\n\n/** DI contract — keeps batch.ts cycle-free from agent.ts. @internal */\ninterface BatchDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Pre-flight boundary validation for `Agent.batch` (arch-review Gap 3).\n *\n * Mirrors the in-repo `validateAgentOptions` / `validateCronExpression`\n * pattern: throws `ConfigurationError` with a stable code on the first\n * violation, returns silently otherwise. Pure — no I/O.\n *\n * Rules:\n * - `concurrency`, when provided, must be a positive integer (rejects\n * 0/negative/non-integer/NaN/Infinity). Previously this was caught only\n * incidentally deep inside `createSemaphore` with a leaky \"permits\"\n * message, AND after task wrapping (dangling Task on invalid input).\n * - every prompt item must carry a non-empty string prompt. Previously\n * `normalizeItem` passed `prompt: \"\"` / non-string straight to\n * `agent.send`. (Whitespace-only strings are intentionally NOT rejected —\n * an empty string is a structural error; judging content is not this\n * validator's job, and the repo's property tests treat any length>=1\n * string as a valid prompt.)\n *\n * @internal\n */\nexport function validateBatchInput(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n): void {\n const { concurrency } = options;\n if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {\n throw new ConfigurationError(\n `Agent.batch concurrency must be a positive integer, got ${concurrency}`,\n { code: \"invalid_concurrency\" },\n );\n }\n for (let i = 0; i < prompts.length; i++) {\n const item = prompts[i];\n const prompt = typeof item === \"string\" ? item : item?.prompt;\n if (typeof prompt !== \"string\" || prompt.length === 0) {\n throw new ConfigurationError(`Agent.batch prompt at index ${i} must be a non-empty string`, {\n code: \"invalid_batch_item\",\n });\n }\n }\n}\n\n/**\n * Run N prompts in parallel with bounded concurrency.\n *\n * @internal\n */\nexport async function batchImpl(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult[]> {\n // EC-1: empty array → no work, no agents created.\n if (prompts.length === 0) return [];\n\n // Pre-flight boundary validation (arch-review Gap 3). MUST run before pool\n // build AND before wrapBatchAsTask so invalid input never registers a\n // dangling Task or builds a credential pool (fail-fast, Rule 8).\n validateBatchInput(prompts, options);\n\n // EC-A fix: build credential pools ONCE and share via AsyncLocalStorage.\n // Without this each Agent.create() would build its own pool from identical\n // apiKeys → 4× rate-limit wastage per concurrency window.\n const sharedPools = buildPoolsFromApiKeys(\n options.providers?.apiKeys,\n options.providers?.credentialPoolStrategy,\n );\n const exec = (): Promise<BatchResult[]> => runBatch(prompts, options, deps);\n const withPool = (): Promise<BatchResult[]> =>\n sharedPools.size > 0 ? withCredentialPool(sharedPools, exec) : exec();\n // T3.3: opt-in Task wrapping (ADRs D363, D374). Register the whole\n // batch as a single Task (kind=\"batch\") with `b-` prefix (D368/EC-5).\n if (options.task !== undefined) {\n return wrapBatchAsTask(prompts.length, options.task, withPool);\n }\n return withPool();\n}\n\nasync function wrapBatchAsTask(\n total: number,\n taskOpt: true | { id?: string; meta?: Record<string, unknown> },\n exec: () => Promise<BatchResult[]>,\n): Promise<BatchResult[]> {\n const opts = taskOpt === true ? {} : taskOpt;\n const id = opts.id ?? `b-${randomUUID()}`;\n const meta: Record<string, unknown> = {\n total,\n ...(opts.meta ?? {}),\n };\n let results: BatchResult[] = [];\n await taskRegistrySubmit({\n kind: \"batch\",\n work: async (ctx) => {\n results = await exec();\n const succeeded = results.filter((r) => r.ok).length;\n const failed = results.length - succeeded;\n ctx.emit({ total: results.length, succeeded, failed });\n return { total: results.length, succeeded, failed };\n },\n id,\n meta,\n allowReservedPrefix: true,\n });\n // submit returns immediately with a queued handle. Wait for terminal.\n for (let i = 0; i < 5000; i++) {\n const h = await taskRegistryGet(id);\n if (h?.state === \"finished\" || h?.state === \"error\" || h?.state === \"cancelled\") break;\n await new Promise((r) => setTimeout(r, 5));\n }\n return results;\n}\n\nasync function runBatch(\n prompts: ReadonlyArray<string | BatchItem>,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult[]> {\n const requested = options.concurrency ?? 4;\n // createSemaphore throws on invalid (EC-2).\n // EC-3: cap to prompts.length to avoid idle workers.\n const effective = Math.min(requested, prompts.length);\n const semaphore = createSemaphore(effective);\n const items = prompts.map(normalizeItem);\n const results: BatchResult[] = new Array(items.length);\n const counters = { completed: 0, failed: 0 };\n let aborted = false;\n\n const onAbort = (): void => {\n aborted = true;\n };\n options.signal?.addEventListener(\"abort\", onAbort);\n // EC-C: pre-aborted signal — treat as already-aborted before any work.\n if (options.signal?.aborted === true) aborted = true;\n\n try {\n await Promise.all(\n items.map(async (item, index) => {\n const release = await semaphore.acquire();\n try {\n if (aborted) {\n results[index] = abortResult(item, index, options.signal);\n } else {\n results[index] = await runOne(item, index, options, deps);\n }\n } finally {\n release();\n }\n if (results[index].ok) counters.completed += 1;\n else counters.failed += 1;\n await safeCallResult(options.onResult, results[index]);\n safeCallProgress(options.onProgress, {\n total: items.length,\n completed: counters.completed,\n failed: counters.failed,\n inFlight: semaphore.inFlight(),\n pending: Math.max(0, semaphore.pending() - semaphore.inFlight()),\n });\n }),\n );\n } finally {\n options.signal?.removeEventListener(\"abort\", onAbort);\n }\n\n return options.filter ? results.filter(options.filter) : results;\n}\n\nasync function runOne(\n item: BatchItem,\n index: number,\n options: BatchOptions,\n deps: BatchDeps,\n): Promise<BatchResult> {\n const t0 = Date.now();\n const agentOpts = buildAgentOptions(item, options);\n\n try {\n const agent = await deps.create(agentOpts);\n try {\n const run = await agent.send(item.prompt);\n const result = await run.wait();\n return {\n ok: true,\n index,\n prompt: item.prompt,\n result,\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: Date.now() - t0,\n };\n } finally {\n // EC-8: dispose failure → stderr warn, don't fail the result.\n try {\n await agent.dispose();\n } catch (disposeErr) {\n // theokit#147 — the interceptable channel; a batch run must not write onto a TUI's frame.\n diag(\n `[theokit-sdk] batch: agent.dispose failed for prompt ${index}: ${\n disposeErr instanceof Error ? disposeErr.message : String(disposeErr)\n }\\n`,\n );\n }\n }\n } catch (err) {\n return {\n ok: false,\n index,\n prompt: item.prompt,\n error: toTheokitError(err),\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: Date.now() - t0,\n };\n }\n}\n\nfunction buildAgentOptions(item: BatchItem, options: BatchOptions): AgentOptions {\n const {\n concurrency: _c,\n filter: _f,\n onResult: _or,\n onProgress: _op,\n signal: _s,\n task: _t,\n ...rest\n } = options;\n void _c;\n void _f;\n void _or;\n void _op;\n void _s;\n void _t;\n const agentOpts = rest as AgentOptions;\n if (item.systemPrompt !== undefined) {\n return { ...agentOpts, systemPrompt: item.systemPrompt };\n }\n return agentOpts;\n}\n\nfunction normalizeItem(promptOrItem: string | BatchItem): BatchItem {\n return typeof promptOrItem === \"string\" ? { prompt: promptOrItem } : promptOrItem;\n}\n\nfunction abortResult(item: BatchItem, index: number, signal?: AbortSignal): BatchResult {\n // EC-D: propagate signal.reason when set (preserves caller's error).\n let error: TheokitAgentError;\n if (signal?.reason instanceof TheokitAgentError) {\n error = signal.reason;\n } else if (signal?.reason instanceof Error) {\n error = new TheokitAgentError(signal.reason.message, {\n code: \"aborted\",\n cause: signal.reason,\n });\n } else {\n error = new TheokitAgentError(\"Batch aborted via AbortSignal\", { code: \"aborted\" });\n }\n return {\n ok: false,\n index,\n prompt: item.prompt,\n error,\n ...(item.metadata !== undefined ? { metadata: item.metadata } : {}),\n durationMs: 0,\n };\n}\n\nfunction toTheokitError(err: unknown): TheokitAgentError {\n if (err instanceof TheokitAgentError) return err;\n if (err instanceof Error) {\n return new TheokitAgentError(err.message, { code: \"unknown\", cause: err });\n }\n return new TheokitAgentError(String(err), { code: \"unknown\" });\n}\n\nasync function safeCallResult(cb: BatchOptions[\"onResult\"], result: BatchResult): Promise<void> {\n if (cb === undefined) return;\n try {\n await cb(result);\n } catch (err) {\n diag(\n `[theokit-sdk] batch: onResult callback threw: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n }\n}\n\nfunction safeCallProgress(cb: BatchOptions[\"onProgress\"], progress: BatchProgress): void {\n if (cb === undefined) return;\n try {\n cb(progress);\n } catch (err) {\n diag(\n `[theokit-sdk] batch: onProgress callback threw: ${\n err instanceof Error ? err.message : String(err)\n }\\n`,\n );\n }\n}\n\n/**\n * EC-A: construct shared pools from options.apiKeys. Empty/invalid → empty Map.\n *\n * @internal\n */\nfunction buildPoolsFromApiKeys(\n apiKeys: Record<string, string[]> | undefined,\n strategy: Record<string, CredentialPoolStrategy> | undefined,\n): Map<string, CredentialPool> {\n const pools = new Map<string, CredentialPool>();\n if (apiKeys === undefined) return pools;\n for (const [provider, keys] of Object.entries(apiKeys)) {\n if (!Array.isArray(keys)) continue;\n const filtered = keys.filter((k): k is string => typeof k === \"string\" && k.length > 0);\n if (filtered.length < 2) continue; // 1-key pools aren't pools\n const entries = filtered.map((token, i) =>\n newPooledCredential({ provider, accessToken: token, priority: i, source: \"manual\" }),\n );\n pools.set(\n provider,\n new CredentialPool(provider, entries, strategy?.[provider] ?? \"fill_first\"),\n );\n }\n return pools;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkE7WWIX34_cjs = require('./chunk-E7WWIX34.cjs');
|
|
4
4
|
|
|
5
5
|
// src/internal/judge/parse-verdict.ts
|
|
6
6
|
var DONE_PREFIX = "DONE:";
|
|
@@ -45,7 +45,7 @@ function parseVerdict(text) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/internal/judge/judge-call.ts
|
|
48
|
-
var JudgeCredentialError = class extends
|
|
48
|
+
var JudgeCredentialError = class extends chunkE7WWIX34_cjs.TheokitAgentError {
|
|
49
49
|
constructor(httpStatus, judgeModel, cause) {
|
|
50
50
|
super(
|
|
51
51
|
`judge unavailable: model "${judgeModel}" returned ${String(httpStatus)}. Pass \`judgeModel\`/\`apiKey\` that resolve for this provider, or omit \`judgeModel\` to derive it from the agent being driven.`,
|
|
@@ -125,5 +125,5 @@ Be strict. If unclear, prefer CONTINUE.`;
|
|
|
125
125
|
exports.JudgeCredentialError = JudgeCredentialError;
|
|
126
126
|
exports.composeJudgePrompt = composeJudgePrompt;
|
|
127
127
|
exports.judgeCallImpl = judgeCallImpl;
|
|
128
|
-
//# sourceMappingURL=chunk-
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
128
|
+
//# sourceMappingURL=chunk-27E4EMR6.cjs.map
|
|
129
|
+
//# sourceMappingURL=chunk-27E4EMR6.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/judge/parse-verdict.ts","../src/internal/judge/judge-call.ts"],"names":["TheokitAgentError"],"mappings":"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","file":"chunk-ECHGWE47.cjs","sourcesContent":["/**\n * Pure verdict parser (T2.1, ADRs D120-D121).\n *\n * Strict prefix matching against `DONE:`, `CONTINUE:`, `SKIPPED:`. Reason\n * is the suffix, trimmed. Anything else is fail-safe: verdict = `continue`,\n * `parseFailed: true` — caller's max-consecutive-failure cap stops the\n * loop after N flakes.\n *\n * @internal\n */\n\nimport type { JudgeResult } from \"../../types/goal-events.js\";\n\nconst DONE_PREFIX = \"DONE:\";\nconst CONTINUE_PREFIX = \"CONTINUE:\";\nconst SKIPPED_PREFIX = \"SKIPPED:\";\n/** M80 — the judge can now declare impossibility, not just \"continue\". */\nconst BLOCKED_PREFIX = \"BLOCKED:\";\n\nexport function parseVerdict(text: string): JudgeResult {\n const trimmed = text.trim();\n\n if (trimmed.startsWith(DONE_PREFIX)) {\n return {\n verdict: \"done\",\n reason: trimmed.slice(DONE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(CONTINUE_PREFIX)) {\n return {\n verdict: \"continue\",\n reason: trimmed.slice(CONTINUE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(BLOCKED_PREFIX)) {\n return {\n verdict: \"blocked\",\n reason: trimmed.slice(BLOCKED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(SKIPPED_PREFIX)) {\n return {\n verdict: \"skipped\",\n reason: trimmed.slice(SKIPPED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n\n // Fail-safe: ADR D121 — prefer \"continue\" so we don't stop prematurely.\n // The runUntil loop counts consecutive parseFailed responses and bails\n // after `maxConsecutiveJudgeFailures` (default 3).\n return {\n verdict: \"continue\",\n reason: `judge response malformed: \"${trimmed.slice(0, 100)}\"`,\n parseFailed: true,\n };\n}\n","import { TheokitAgentError } from \"../../errors.js\";\n/**\n * Judge call primitive (T2.2, ADRs D119-D121).\n *\n * Instantiates a short-lived auxiliary agent that evaluates whether a\n * goal is satisfied. The auxiliary judge runs with `tools: []` and a\n * cheap model (default `openai/gpt-4o-mini`) — see ADR D119. The judge\n * model needs only API access; we read `OPENROUTER_API_KEY` directly\n * from the environment (EC-A — single source of truth) and let the\n * caller override via {@link JudgeOptions.apiKey} for Anthropic or\n * direct-OpenAI environments.\n *\n * NOTE: judge aux agents created from inside a `forkAgent` context will\n * inherit the parent fork's whitelist via AsyncLocalStorage (EC-J).\n * `tools: []` keeps this benign today; future callers that add tools to\n * a judge should be aware.\n *\n * @internal\n */\n\nimport type { AgentOptions, SDKAgent } from \"../../types/agent.js\";\nimport type { JudgeResult } from \"../../types/goal-events.js\";\nimport { parseVerdict } from \"./parse-verdict.js\";\n\n/** Inputs to the judge — pure data. */\nexport interface JudgeContext {\n goal: string;\n lastResponse: string;\n subgoals?: string[];\n}\n\n/** Caller-supplied tuning knobs for the judge call. */\nexport interface JudgeOptions {\n /**\n * Judge model identifier. When absent, it DERIVES from {@link agentModel} — and only falls back to the literal\n * `\"openai/gpt-4o-mini\"` when neither is provided.\n *\n * M80 — the fixed default was provider-blind: it only resolves on OpenRouter. With an Anthropic key it gives\n * 404, with an OAuth bearer it gives 401, and in both the goal burned 3 whole turns before failing with\n * a misleading reason. The agent-builder already worked around this by deriving on its own; the knowledge\n * belongs here.\n */\n judgeModel?: string;\n /**\n * M80 — the DRIVEN agent's model. It is the basis of the derivation: a judge running on the same model as the\n * chat works wherever chat works.\n */\n agentModel?: string;\n /** Override env. Default `process.env.OPENROUTER_API_KEY` (EC-A). */\n apiKey?: string;\n}\n\n/**\n * M80 — the judge's credential or model does not work: 401/404.\n *\n * Fails FAST by design. `rules/error-handling.md` § 2 separates recoverable from unrecoverable, and a\n * nonexistent model does not start existing on retry — folding that into `{parseFailed: true}` made the loop\n * try three times and report \"failed\" on a consecutive-failure limit, hiding that the cause was the\n * credential. A slow, opaque failure traded for a fast, clear one.\n *\n * PARSE failures and network errors stay folded: they are recoverable, and the loop already decides on\n * consecutivas.\n */\nexport class JudgeCredentialError extends TheokitAgentError {\n override readonly name = \"JudgeCredentialError\";\n\n constructor(\n readonly httpStatus: number,\n readonly judgeModel: string,\n cause: unknown,\n ) {\n super(\n `judge unavailable: model \"${judgeModel}\" returned ${String(httpStatus)}. ` +\n \"Pass `judgeModel`/`apiKey` that resolve for this provider, or omit `judgeModel` to derive \" +\n \"it from the agent being driven.\",\n { code: \"judge_credential\", cause, isRetryable: false },\n );\n }\n}\n\n/** Extracts the HTTP status from a provider error, when it carries one. */\nfunction statusHttpDe(err: unknown): number | undefined {\n const s =\n (err as { status?: unknown; statusCode?: unknown }).status ??\n (err as { statusCode?: unknown }).statusCode;\n if (typeof s === \"number\") return s;\n const m = /\\b(401|403|404)\\b/.exec(err instanceof Error ? err.message : String(err));\n return m?.[1] !== undefined ? Number(m[1]) : undefined;\n}\n\n/** Dependencies injected so `judge-call.ts` stays free of `Agent` import. */\nexport interface JudgeDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Run the judge auxiliary agent and parse the verdict. Always returns a\n * `JudgeResult` — failures are folded into `{ parseFailed: true,\n * verdict: \"continue\" }` so the loop can decide based on consecutive\n * failures (ADR D121).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: judge call must (1) check env or override, (2) catch errors as fail-safe, (3) ensure aux dispose runs once regardless of outcome — three concerns linearly arranged, harm clarity to extract.\nexport async function judgeCallImpl(\n ctx: JudgeContext,\n options: JudgeOptions | undefined,\n deps: JudgeDeps,\n): Promise<JudgeResult> {\n const prompt = composeJudgePrompt(ctx);\n // EC-A: single env source — OpenRouter only. No multi-provider fallback.\n const apiKey = options?.apiKey ?? process.env.OPENROUTER_API_KEY;\n if (apiKey === undefined) {\n return {\n verdict: \"continue\",\n reason:\n \"judge unavailable: OPENROUTER_API_KEY missing and no override passed via options.apiKey\",\n parseFailed: true,\n };\n }\n\n // M80 — precedence: explicit > driven agent's model > the historical literal. The literal only\n // survives as a last resort, so as not to break callers who never passed either.\n const judgeModel = options?.judgeModel ?? options?.agentModel ?? \"openai/gpt-4o-mini\";\n let auxAgent: SDKAgent | undefined;\n try {\n auxAgent = await deps.create({\n apiKey,\n model: { id: judgeModel },\n tools: [],\n local: {},\n metadata: { forkOrigin: \"judge\" },\n } as AgentOptions);\n const run = await auxAgent.send(prompt);\n const result = await run.wait();\n return parseVerdict(result.result ?? \"\");\n } catch (err) {\n // M80 — 401/403/404 are credential/model errors: unrecoverable, failing fast and typed. Everything\n // else (network, timeout, 5xx) stays folded, because it IS recoverable and the loop already decides on\n // consecutivas.\n const status = statusHttpDe(err);\n if (status === 401 || status === 403 || status === 404) {\n throw new JudgeCredentialError(status, judgeModel, err);\n }\n return {\n verdict: \"continue\",\n reason: `judge call failed: ${err instanceof Error ? err.message : String(err)}`,\n parseFailed: true,\n };\n } finally {\n if (auxAgent !== undefined) {\n try {\n await auxAgent.dispose();\n } catch {\n // dispose errors are non-fatal; judge result is already prepared\n }\n }\n }\n}\n\n/**\n * Build the strict-format prompt the judge expects.\n *\n * @internal\n */\nexport function composeJudgePrompt(ctx: JudgeContext): string {\n const subgoals =\n ctx.subgoals !== undefined && ctx.subgoals.length > 0 ? ctx.subgoals.join(\", \") : \"(none)\";\n return `You are a goal judge. Determine if this goal is satisfied.\n\nGoal: ${ctx.goal}\nSubgoals: ${subgoals}\nLast agent response: ${ctx.lastResponse}\n\nRespond with EXACTLY one of:\n- DONE: <reason>\n- CONTINUE: <what's left>\n- SKIPPED: <why not applicable>\n\nBe strict. If unclear, prefer CONTINUE.`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/judge/parse-verdict.ts","../src/internal/judge/judge-call.ts"],"names":["TheokitAgentError"],"mappings":"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","file":"chunk-27E4EMR6.cjs","sourcesContent":["/**\n * Pure verdict parser (T2.1, ADRs D120-D121).\n *\n * Strict prefix matching against `DONE:`, `CONTINUE:`, `SKIPPED:`. Reason\n * is the suffix, trimmed. Anything else is fail-safe: verdict = `continue`,\n * `parseFailed: true` — caller's max-consecutive-failure cap stops the\n * loop after N flakes.\n *\n * @internal\n */\n\nimport type { JudgeResult } from \"../../types/goal-events.js\";\n\nconst DONE_PREFIX = \"DONE:\";\nconst CONTINUE_PREFIX = \"CONTINUE:\";\nconst SKIPPED_PREFIX = \"SKIPPED:\";\n/** M80 — the judge can now declare impossibility, not just \"continue\". */\nconst BLOCKED_PREFIX = \"BLOCKED:\";\n\nexport function parseVerdict(text: string): JudgeResult {\n const trimmed = text.trim();\n\n if (trimmed.startsWith(DONE_PREFIX)) {\n return {\n verdict: \"done\",\n reason: trimmed.slice(DONE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(CONTINUE_PREFIX)) {\n return {\n verdict: \"continue\",\n reason: trimmed.slice(CONTINUE_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(BLOCKED_PREFIX)) {\n return {\n verdict: \"blocked\",\n reason: trimmed.slice(BLOCKED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n if (trimmed.startsWith(SKIPPED_PREFIX)) {\n return {\n verdict: \"skipped\",\n reason: trimmed.slice(SKIPPED_PREFIX.length).trim(),\n parseFailed: false,\n };\n }\n\n // Fail-safe: ADR D121 — prefer \"continue\" so we don't stop prematurely.\n // The runUntil loop counts consecutive parseFailed responses and bails\n // after `maxConsecutiveJudgeFailures` (default 3).\n return {\n verdict: \"continue\",\n reason: `judge response malformed: \"${trimmed.slice(0, 100)}\"`,\n parseFailed: true,\n };\n}\n","import { TheokitAgentError } from \"../../errors.js\";\n/**\n * Judge call primitive (T2.2, ADRs D119-D121).\n *\n * Instantiates a short-lived auxiliary agent that evaluates whether a\n * goal is satisfied. The auxiliary judge runs with `tools: []` and a\n * cheap model (default `openai/gpt-4o-mini`) — see ADR D119. The judge\n * model needs only API access; we read `OPENROUTER_API_KEY` directly\n * from the environment (EC-A — single source of truth) and let the\n * caller override via {@link JudgeOptions.apiKey} for Anthropic or\n * direct-OpenAI environments.\n *\n * NOTE: judge aux agents created from inside a `forkAgent` context will\n * inherit the parent fork's whitelist via AsyncLocalStorage (EC-J).\n * `tools: []` keeps this benign today; future callers that add tools to\n * a judge should be aware.\n *\n * @internal\n */\n\nimport type { AgentOptions, SDKAgent } from \"../../types/agent.js\";\nimport type { JudgeResult } from \"../../types/goal-events.js\";\nimport { parseVerdict } from \"./parse-verdict.js\";\n\n/** Inputs to the judge — pure data. */\nexport interface JudgeContext {\n goal: string;\n lastResponse: string;\n subgoals?: string[];\n}\n\n/** Caller-supplied tuning knobs for the judge call. */\nexport interface JudgeOptions {\n /**\n * Judge model identifier. When absent, it DERIVES from {@link agentModel} — and only falls back to the literal\n * `\"openai/gpt-4o-mini\"` when neither is provided.\n *\n * M80 — the fixed default was provider-blind: it only resolves on OpenRouter. With an Anthropic key it gives\n * 404, with an OAuth bearer it gives 401, and in both the goal burned 3 whole turns before failing with\n * a misleading reason. The agent-builder already worked around this by deriving on its own; the knowledge\n * belongs here.\n */\n judgeModel?: string;\n /**\n * M80 — the DRIVEN agent's model. It is the basis of the derivation: a judge running on the same model as the\n * chat works wherever chat works.\n */\n agentModel?: string;\n /** Override env. Default `process.env.OPENROUTER_API_KEY` (EC-A). */\n apiKey?: string;\n}\n\n/**\n * M80 — the judge's credential or model does not work: 401/404.\n *\n * Fails FAST by design. `rules/error-handling.md` § 2 separates recoverable from unrecoverable, and a\n * nonexistent model does not start existing on retry — folding that into `{parseFailed: true}` made the loop\n * try three times and report \"failed\" on a consecutive-failure limit, hiding that the cause was the\n * credential. A slow, opaque failure traded for a fast, clear one.\n *\n * PARSE failures and network errors stay folded: they are recoverable, and the loop already decides on\n * consecutivas.\n */\nexport class JudgeCredentialError extends TheokitAgentError {\n override readonly name = \"JudgeCredentialError\";\n\n constructor(\n readonly httpStatus: number,\n readonly judgeModel: string,\n cause: unknown,\n ) {\n super(\n `judge unavailable: model \"${judgeModel}\" returned ${String(httpStatus)}. ` +\n \"Pass `judgeModel`/`apiKey` that resolve for this provider, or omit `judgeModel` to derive \" +\n \"it from the agent being driven.\",\n { code: \"judge_credential\", cause, isRetryable: false },\n );\n }\n}\n\n/** Extracts the HTTP status from a provider error, when it carries one. */\nfunction statusHttpDe(err: unknown): number | undefined {\n const s =\n (err as { status?: unknown; statusCode?: unknown }).status ??\n (err as { statusCode?: unknown }).statusCode;\n if (typeof s === \"number\") return s;\n const m = /\\b(401|403|404)\\b/.exec(err instanceof Error ? err.message : String(err));\n return m?.[1] !== undefined ? Number(m[1]) : undefined;\n}\n\n/** Dependencies injected so `judge-call.ts` stays free of `Agent` import. */\nexport interface JudgeDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n}\n\n/**\n * Run the judge auxiliary agent and parse the verdict. Always returns a\n * `JudgeResult` — failures are folded into `{ parseFailed: true,\n * verdict: \"continue\" }` so the loop can decide based on consecutive\n * failures (ADR D121).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: judge call must (1) check env or override, (2) catch errors as fail-safe, (3) ensure aux dispose runs once regardless of outcome — three concerns linearly arranged, harm clarity to extract.\nexport async function judgeCallImpl(\n ctx: JudgeContext,\n options: JudgeOptions | undefined,\n deps: JudgeDeps,\n): Promise<JudgeResult> {\n const prompt = composeJudgePrompt(ctx);\n // EC-A: single env source — OpenRouter only. No multi-provider fallback.\n const apiKey = options?.apiKey ?? process.env.OPENROUTER_API_KEY;\n if (apiKey === undefined) {\n return {\n verdict: \"continue\",\n reason:\n \"judge unavailable: OPENROUTER_API_KEY missing and no override passed via options.apiKey\",\n parseFailed: true,\n };\n }\n\n // M80 — precedence: explicit > driven agent's model > the historical literal. The literal only\n // survives as a last resort, so as not to break callers who never passed either.\n const judgeModel = options?.judgeModel ?? options?.agentModel ?? \"openai/gpt-4o-mini\";\n let auxAgent: SDKAgent | undefined;\n try {\n auxAgent = await deps.create({\n apiKey,\n model: { id: judgeModel },\n tools: [],\n local: {},\n metadata: { forkOrigin: \"judge\" },\n } as AgentOptions);\n const run = await auxAgent.send(prompt);\n const result = await run.wait();\n return parseVerdict(result.result ?? \"\");\n } catch (err) {\n // M80 — 401/403/404 are credential/model errors: unrecoverable, failing fast and typed. Everything\n // else (network, timeout, 5xx) stays folded, because it IS recoverable and the loop already decides on\n // consecutivas.\n const status = statusHttpDe(err);\n if (status === 401 || status === 403 || status === 404) {\n throw new JudgeCredentialError(status, judgeModel, err);\n }\n return {\n verdict: \"continue\",\n reason: `judge call failed: ${err instanceof Error ? err.message : String(err)}`,\n parseFailed: true,\n };\n } finally {\n if (auxAgent !== undefined) {\n try {\n await auxAgent.dispose();\n } catch {\n // dispose errors are non-fatal; judge result is already prepared\n }\n }\n }\n}\n\n/**\n * Build the strict-format prompt the judge expects.\n *\n * @internal\n */\nexport function composeJudgePrompt(ctx: JudgeContext): string {\n const subgoals =\n ctx.subgoals !== undefined && ctx.subgoals.length > 0 ? ctx.subgoals.join(\", \") : \"(none)\";\n return `You are a goal judge. Determine if this goal is satisfied.\n\nGoal: ${ctx.goal}\nSubgoals: ${subgoals}\nLast agent response: ${ctx.lastResponse}\n\nRespond with EXACTLY one of:\n- DONE: <reason>\n- CONTINUE: <what's left>\n- SKIPPED: <why not applicable>\n\nBe strict. If unclear, prefer CONTINUE.`;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkA76OFN2B_cjs = require('./chunk-A76OFN2B.cjs');
|
|
4
4
|
var crypto = require('crypto');
|
|
5
5
|
var promises = require('fs/promises');
|
|
6
6
|
var path = require('path');
|
|
@@ -23,7 +23,7 @@ async function warnOnNetworkFsOnce(dirPath, label) {
|
|
|
23
23
|
const info = await promises.statfs(dirPath);
|
|
24
24
|
const fsName = detectNetworkFsName(info.type);
|
|
25
25
|
if (fsName === null) return;
|
|
26
|
-
|
|
26
|
+
chunkA76OFN2B_cjs.diag(
|
|
27
27
|
`[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} \u2014 rename() atomicity guarantees may be weaker than expected.
|
|
28
28
|
`
|
|
29
29
|
);
|
|
@@ -69,5 +69,5 @@ async function atomicWriteText(filePath, content) {
|
|
|
69
69
|
exports.atomicWriteJson = atomicWriteJson;
|
|
70
70
|
exports.atomicWriteText = atomicWriteText;
|
|
71
71
|
exports.replaceFileAtomic = replaceFileAtomic;
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
73
|
-
//# sourceMappingURL=chunk-
|
|
72
|
+
//# sourceMappingURL=chunk-33B2BSME.cjs.map
|
|
73
|
+
//# sourceMappingURL=chunk-33B2BSME.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/atomic-write.ts"],"names":["statfs","diag","dirname","randomBytes","open","rename","unlink","mkdir"],"mappings":"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","file":"chunk-DGLLVCNB.cjs","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, open, rename, statfs, unlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { diag } from \"../diagnostics.js\";\n\n// T5.8 — Linux filesystem magic numbers (from `<linux/magic.h>`).\n// Used by `detectNetworkFsName` to identify the parent directory's\n// filesystem type from a `statfs()` return value. The four entries\n// below cover the network/FUSE cases where `rename()` is best-effort\n// rather than strictly atomic; everything else is treated as local.\nconst NETWORK_FS_MAGIC: ReadonlyMap<number, string> = new Map([\n [0x6969, \"nfs\"],\n [0x517b, \"smb\"],\n [0xff534d42, \"cifs\"],\n [0x65735546, \"fuse\"],\n]);\n\n/**\n * T5.8 — Map a `statfs().type` magic number to a network-FS label, or\n * `null` for local filesystems. Pure function — exported via the\n * `__TESTING__` seam so unit tests can drive the parse logic without\n * needing a network mount.\n *\n * @internal\n */\nfunction detectNetworkFsName(typeMagic: number): string | null {\n return NETWORK_FS_MAGIC.get(typeMagic) ?? null;\n}\n\nconst warnedNfsDirs = new Set<string>();\n\n/**\n * T5.8 — Best-effort one-shot stderr warning when `dirPath` lives on a\n * network/FUSE filesystem. Silent no-op on local filesystems, on\n * statfs failure (Windows / Node < 18.15 / EACCES), or after the\n * first warning per (dir + label) pair. Mirrors the `sqlite-wal.ts`\n * warn-once-per-label pattern (D63).\n *\n * @internal\n */\nasync function warnOnNetworkFsOnce(dirPath: string, label: string): Promise<void> {\n const key = `${dirPath}\\0${label}`;\n if (warnedNfsDirs.has(key)) return;\n warnedNfsDirs.add(key);\n try {\n const info = await statfs(dirPath);\n const fsName = detectNetworkFsName(info.type);\n if (fsName === null) return;\n diag(\n `[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} — ` +\n \"rename() atomicity guarantees may be weaker than expected.\\n\",\n );\n } catch {\n // statfs unavailable (Windows / Node < 18.15) or unreadable —\n // silent fallback. The warning is purely informational.\n }\n}\n\n/**\n * T5.8 — Test seam exposing the pure detection function so unit tests\n * can assert magic-number coverage without spinning up a network FS.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__detectNetworkFsName(typeMagic: number): string | null {\n return detectNetworkFsName(typeMagic);\n}\n\n/**\n * T5.8 — Test seam: clear the per-directory warn-once registry between\n * tests so warning-emission tests stay deterministic.\n *\n * @internal\n */\nexport function __TESTING__resetNfsWarnings(): void {\n warnedNfsDirs.clear();\n}\n\n/**\n * M107 — temp-file creation control, shared by `replaceFileAtomic`,\n * `atomicWriteJson` e `atomicWriteText`.\n *\n * Both fields are OPTIONAL and the default is byte-identical to the behavior\n * before M107. See `replaceFileAtomic` for why the mode reassertion\n * is conditional.\n *\n * @internal\n */\nexport interface AtomicWriteFileOptions {\n /**\n * Permission bits of the created file. Default: `0o600`, the previous fixed\n * literal — filtered by the `umask`, as it always was. When provided, the mode is\n * reasserted on the descriptor, so the `umask` cannot silently\n * clearing bits the caller asked for.\n */\n mode?: number;\n /**\n * Create the temp file with `wx` (exclusive creation) instead of `w`. Default:\n * `false` — the previous flag. With `true`, a pre-existing temp file becomes\n * `EEXIST` em vez de ser truncado.\n */\n exclusive?: boolean;\n}\n\n/**\n * Atomic file replacement: write content to a per-call unique tmp path,\n * fsync, then rename over the target. Crash mid-write leaves either the old\n * file intact or the new file complete — never a half-written file.\n *\n * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent\n * burst writes within one process) never collide on the same tmp path — a\n * race that would manifest as `ENOENT` on `rename` after the rival process\n * already moved its tmp into place.\n *\n * Mirrors peer-project's `replaceFileAtomic` from\n * `referencia/peer-project/packages/memory-host-sdk/src/host/fs-utils.ts` with\n * the multi-writer robustness fix.\n *\n * ## M107 — `options` is optional, and the default is byte-identical\n *\n * The third parameter is additive: every earlier caller still compiles and\n * writing exactly the same bytes, with the same mode, to the same path.\n *\n * The mode reassertion (`handle.chmod`) is **conditional on `mode !== undefined`**,\n * and that is not cosmetic. `open`'s mode argument is filtered by the `umask`,\n * which only CLEARS bits — measured in this codebase before the change:\n *\n * ```\n * umask 0o002 -> 0o600 umask 0o022 -> 0o600 umask 0o200 -> 0o400\n * ```\n *\n * Um `chmod` incondicional levaria o terceiro caso de `0o400` para `0o600` — uma\n * an on-disk change for every caller that asked for nothing, including external\n * consumers. When the caller DOES ask for a mode, however, letting the `umask` decide\n * silently is the defect this parameter exists to close; hence the reassertion.\n *\n * It goes on the DESCRIPTOR, before the `rename`, never after: chmod-ing the final\n * final one would leave a window where it carries the `umask`'s mode — the anti-pattern\n * de `opencode/packages/core/src/fs-util.ts:110-114`. A forma escolhida (modo como\n * `open` argument) is that of `codex-rs/network-proxy/src/certs.rs:687,783-791`.\n *\n * @internal\n */\nexport async function replaceFileAtomic(\n filePath: string,\n content: string,\n options?: AtomicWriteFileOptions,\n): Promise<void> {\n // T5.8 — warn once per parent directory if it lives on a network /\n // FUSE filesystem where `rename()` atomicity is best-effort. The\n // write proceeds unchanged; the warning is purely informational so\n // operators can spot the case in stderr / log aggregators.\n await warnOnNetworkFsOnce(dirname(filePath), \"atomic-write\");\n // T5.7 — crypto-random tmp suffix (CSPRNG, 64 bits of entropy)\n // replaces the predictable `Math.random().toString(36)` source. An\n // attacker observing the process can no longer predict the next\n // tmp path and pre-stage a hostile file to be renamed into place.\n const suffix = randomBytes(8).toString(\"hex\");\n const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;\n // T5.7 — mode 0o600 on the tmp file (owner read+write only). The\n // tmp file holds the FULL in-flight content (credential snapshots,\n // OAuth tokens) before the rename. World-readable default would\n // expose secrets during the ms-window between open and rename\n // (TOCTOU). On modern Linux the post-rename target inherits the\n // tmp's permission bits, so the final file is also 0o600.\n const handle = await open(tmp, options?.exclusive === true ? \"wx\" : \"w\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(content, \"utf8\");\n await handle.sync();\n // Conditional by measurement, not by taste — see this function's docblock.\n if (options?.mode !== undefined) await handle.chmod(options.mode);\n } finally {\n await handle.close();\n }\n try {\n await rename(tmp, filePath);\n } catch (cause) {\n // Cleanup tmp on rename failure so we don't leak stale .tmp files.\n await unlink(tmp).catch(() => undefined);\n throw cause;\n }\n}\n\n/**\n * Options for `atomicWriteJson`.\n *\n * @internal\n */\nexport interface AtomicWriteJsonOptions extends AtomicWriteFileOptions {\n /** Indent passed to `JSON.stringify`. Default: 2. */\n indent?: number;\n /** Whether to append a trailing newline (POSIX convention). Default: true. */\n trailingNewline?: boolean;\n}\n\n/**\n * Typed JSON atomic write helper.\n *\n * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The\n * parent directory is auto-created (recursive `mkdir`) to make this helper\n * safe for callers who haven't ensured the directory exists (EC-4 in the\n * persistence-state-hardening plan).\n *\n * Throws `TypeError` on circular refs or `undefined` data (propagates from\n * `JSON.stringify`).\n *\n * @internal\n */\nexport async function atomicWriteJson<T>(\n filePath: string,\n data: T,\n options?: AtomicWriteJsonOptions,\n): Promise<void> {\n const indent = options?.indent ?? 2;\n const trailingNewline = options?.trailingNewline ?? true;\n const json = JSON.stringify(data, null, indent);\n if (json === undefined) {\n throw new TypeError(\"atomicWriteJson: cannot serialize undefined\");\n }\n const content = trailingNewline ? `${json}\\n` : json;\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content, options);\n}\n\n/**\n * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +\n * auto-mkdir of the parent directory. Used by `theokit-migrate-config`\n * (T4.1, EC-2 MUST FIX) so a crash mid-migration leaves previous MD files\n * intact rather than corrupting them.\n *\n * @internal\n */\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/atomic-write.ts"],"names":["statfs","diag","dirname","randomBytes","open","rename","unlink","mkdir"],"mappings":"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","file":"chunk-33B2BSME.cjs","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, open, rename, statfs, unlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { diag } from \"../diagnostics.js\";\n\n// T5.8 — Linux filesystem magic numbers (from `<linux/magic.h>`).\n// Used by `detectNetworkFsName` to identify the parent directory's\n// filesystem type from a `statfs()` return value. The four entries\n// below cover the network/FUSE cases where `rename()` is best-effort\n// rather than strictly atomic; everything else is treated as local.\nconst NETWORK_FS_MAGIC: ReadonlyMap<number, string> = new Map([\n [0x6969, \"nfs\"],\n [0x517b, \"smb\"],\n [0xff534d42, \"cifs\"],\n [0x65735546, \"fuse\"],\n]);\n\n/**\n * T5.8 — Map a `statfs().type` magic number to a network-FS label, or\n * `null` for local filesystems. Pure function — exported via the\n * `__TESTING__` seam so unit tests can drive the parse logic without\n * needing a network mount.\n *\n * @internal\n */\nfunction detectNetworkFsName(typeMagic: number): string | null {\n return NETWORK_FS_MAGIC.get(typeMagic) ?? null;\n}\n\nconst warnedNfsDirs = new Set<string>();\n\n/**\n * T5.8 — Best-effort one-shot stderr warning when `dirPath` lives on a\n * network/FUSE filesystem. Silent no-op on local filesystems, on\n * statfs failure (Windows / Node < 18.15 / EACCES), or after the\n * first warning per (dir + label) pair. Mirrors the `sqlite-wal.ts`\n * warn-once-per-label pattern (D63).\n *\n * @internal\n */\nasync function warnOnNetworkFsOnce(dirPath: string, label: string): Promise<void> {\n const key = `${dirPath}\\0${label}`;\n if (warnedNfsDirs.has(key)) return;\n warnedNfsDirs.add(key);\n try {\n const info = await statfs(dirPath);\n const fsName = detectNetworkFsName(info.type);\n if (fsName === null) return;\n diag(\n `[theokit-sdk] ${label}: detected network fs (${fsName}) at ${dirPath} — ` +\n \"rename() atomicity guarantees may be weaker than expected.\\n\",\n );\n } catch {\n // statfs unavailable (Windows / Node < 18.15) or unreadable —\n // silent fallback. The warning is purely informational.\n }\n}\n\n/**\n * T5.8 — Test seam exposing the pure detection function so unit tests\n * can assert magic-number coverage without spinning up a network FS.\n * NOT included in the public barrel.\n *\n * @internal\n */\nexport function __TESTING__detectNetworkFsName(typeMagic: number): string | null {\n return detectNetworkFsName(typeMagic);\n}\n\n/**\n * T5.8 — Test seam: clear the per-directory warn-once registry between\n * tests so warning-emission tests stay deterministic.\n *\n * @internal\n */\nexport function __TESTING__resetNfsWarnings(): void {\n warnedNfsDirs.clear();\n}\n\n/**\n * M107 — temp-file creation control, shared by `replaceFileAtomic`,\n * `atomicWriteJson` e `atomicWriteText`.\n *\n * Both fields are OPTIONAL and the default is byte-identical to the behavior\n * before M107. See `replaceFileAtomic` for why the mode reassertion\n * is conditional.\n *\n * @internal\n */\nexport interface AtomicWriteFileOptions {\n /**\n * Permission bits of the created file. Default: `0o600`, the previous fixed\n * literal — filtered by the `umask`, as it always was. When provided, the mode is\n * reasserted on the descriptor, so the `umask` cannot silently\n * clearing bits the caller asked for.\n */\n mode?: number;\n /**\n * Create the temp file with `wx` (exclusive creation) instead of `w`. Default:\n * `false` — the previous flag. With `true`, a pre-existing temp file becomes\n * `EEXIST` em vez de ser truncado.\n */\n exclusive?: boolean;\n}\n\n/**\n * Atomic file replacement: write content to a per-call unique tmp path,\n * fsync, then rename over the target. Crash mid-write leaves either the old\n * file intact or the new file complete — never a half-written file.\n *\n * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent\n * burst writes within one process) never collide on the same tmp path — a\n * race that would manifest as `ENOENT` on `rename` after the rival process\n * already moved its tmp into place.\n *\n * Mirrors peer-project's `replaceFileAtomic` from\n * `referencia/peer-project/packages/memory-host-sdk/src/host/fs-utils.ts` with\n * the multi-writer robustness fix.\n *\n * ## M107 — `options` is optional, and the default is byte-identical\n *\n * The third parameter is additive: every earlier caller still compiles and\n * writing exactly the same bytes, with the same mode, to the same path.\n *\n * The mode reassertion (`handle.chmod`) is **conditional on `mode !== undefined`**,\n * and that is not cosmetic. `open`'s mode argument is filtered by the `umask`,\n * which only CLEARS bits — measured in this codebase before the change:\n *\n * ```\n * umask 0o002 -> 0o600 umask 0o022 -> 0o600 umask 0o200 -> 0o400\n * ```\n *\n * Um `chmod` incondicional levaria o terceiro caso de `0o400` para `0o600` — uma\n * an on-disk change for every caller that asked for nothing, including external\n * consumers. When the caller DOES ask for a mode, however, letting the `umask` decide\n * silently is the defect this parameter exists to close; hence the reassertion.\n *\n * It goes on the DESCRIPTOR, before the `rename`, never after: chmod-ing the final\n * final one would leave a window where it carries the `umask`'s mode — the anti-pattern\n * de `opencode/packages/core/src/fs-util.ts:110-114`. A forma escolhida (modo como\n * `open` argument) is that of `codex-rs/network-proxy/src/certs.rs:687,783-791`.\n *\n * @internal\n */\nexport async function replaceFileAtomic(\n filePath: string,\n content: string,\n options?: AtomicWriteFileOptions,\n): Promise<void> {\n // T5.8 — warn once per parent directory if it lives on a network /\n // FUSE filesystem where `rename()` atomicity is best-effort. The\n // write proceeds unchanged; the warning is purely informational so\n // operators can spot the case in stderr / log aggregators.\n await warnOnNetworkFsOnce(dirname(filePath), \"atomic-write\");\n // T5.7 — crypto-random tmp suffix (CSPRNG, 64 bits of entropy)\n // replaces the predictable `Math.random().toString(36)` source. An\n // attacker observing the process can no longer predict the next\n // tmp path and pre-stage a hostile file to be renamed into place.\n const suffix = randomBytes(8).toString(\"hex\");\n const tmp = `${filePath}.${process.pid}.${suffix}.tmp`;\n // T5.7 — mode 0o600 on the tmp file (owner read+write only). The\n // tmp file holds the FULL in-flight content (credential snapshots,\n // OAuth tokens) before the rename. World-readable default would\n // expose secrets during the ms-window between open and rename\n // (TOCTOU). On modern Linux the post-rename target inherits the\n // tmp's permission bits, so the final file is also 0o600.\n const handle = await open(tmp, options?.exclusive === true ? \"wx\" : \"w\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(content, \"utf8\");\n await handle.sync();\n // Conditional by measurement, not by taste — see this function's docblock.\n if (options?.mode !== undefined) await handle.chmod(options.mode);\n } finally {\n await handle.close();\n }\n try {\n await rename(tmp, filePath);\n } catch (cause) {\n // Cleanup tmp on rename failure so we don't leak stale .tmp files.\n await unlink(tmp).catch(() => undefined);\n throw cause;\n }\n}\n\n/**\n * Options for `atomicWriteJson`.\n *\n * @internal\n */\nexport interface AtomicWriteJsonOptions extends AtomicWriteFileOptions {\n /** Indent passed to `JSON.stringify`. Default: 2. */\n indent?: number;\n /** Whether to append a trailing newline (POSIX convention). Default: true. */\n trailingNewline?: boolean;\n}\n\n/**\n * Typed JSON atomic write helper.\n *\n * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The\n * parent directory is auto-created (recursive `mkdir`) to make this helper\n * safe for callers who haven't ensured the directory exists (EC-4 in the\n * persistence-state-hardening plan).\n *\n * Throws `TypeError` on circular refs or `undefined` data (propagates from\n * `JSON.stringify`).\n *\n * @internal\n */\nexport async function atomicWriteJson<T>(\n filePath: string,\n data: T,\n options?: AtomicWriteJsonOptions,\n): Promise<void> {\n const indent = options?.indent ?? 2;\n const trailingNewline = options?.trailingNewline ?? true;\n const json = JSON.stringify(data, null, indent);\n if (json === undefined) {\n throw new TypeError(\"atomicWriteJson: cannot serialize undefined\");\n }\n const content = trailingNewline ? `${json}\\n` : json;\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content, options);\n}\n\n/**\n * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +\n * auto-mkdir of the parent directory. Used by `theokit-migrate-config`\n * (T4.1, EC-2 MUST FIX) so a crash mid-migration leaves previous MD files\n * intact rather than corrupting them.\n *\n * @internal\n */\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n await replaceFileAtomic(filePath, content);\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { setupStructuredOutput, makeOutputTool, buildTransientAgentOptions, buildToolPrompt, extractUsage, disposeAndDeleteTransient } from './chunk-
|
|
1
|
+
import { setupStructuredOutput, makeOutputTool, buildTransientAgentOptions, buildToolPrompt, extractUsage, disposeAndDeleteTransient } from './chunk-4HKAZRTC.js';
|
|
2
2
|
|
|
3
3
|
// src/generate-object.ts
|
|
4
4
|
var GenerateObjectError = class extends Error {
|
|
@@ -138,5 +138,5 @@ async function generateObjectImpl(options, deps) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export { GenerateObjectError, generateObjectImpl };
|
|
141
|
-
//# sourceMappingURL=chunk-
|
|
142
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-3CAQCFVZ.js.map
|
|
142
|
+
//# sourceMappingURL=chunk-3CAQCFVZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate-object.ts"],"names":[],"mappings":"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","file":"chunk-KE6XKRLQ.js","sourcesContent":["import type { z as ZodNamespace, ZodType } from \"zod\";\n\nimport {\n buildToolPrompt,\n buildTransientAgentOptions,\n disposeAndDeleteTransient,\n extractUsage,\n makeOutputTool,\n setupStructuredOutput,\n} from \"./internal/structured-output-helpers.js\";\nimport type { AgentOptions, LocalOptions, ModelSelection, SDKAgent } from \"./types/agent.js\";\nimport type { ProviderRoutingSettings } from \"./types/providers.js\";\n\n/**\n * Options accepted by {@link Agent.generateObject}. Returns a typed object\n * matching the supplied Zod schema. See ADR D33.\n *\n * @public\n */\nexport interface GenerateObjectOptions<T extends ZodType> {\n /** Zod schema describing the expected object shape. */\n schema: T;\n /** User prompt — the model is asked to fill the schema given this prompt. */\n prompt: string;\n /** Optional system prompt steering the model. */\n systemPrompt?: string;\n /** Model selection. Required (transient agents need a model). SE8 — accepts a\n * bare-string id shorthand (`\"openai/gpt-4o-mini\"`) or a {@link ModelSelection}. */\n model: string | ModelSelection;\n /**\n * M21 — optional separate model for the STRUCTURING step. When set, `model` first produces a\n * free-text reasoned answer to the prompt (phase 1), then `structuringModel` extracts the\n * schema-matched object by calling the `output` tool over that answer (phase 2). Lets a large\n * model reason while a cheap fast model does the extraction. Absent ⇒ today's single-model flow.\n */\n structuringModel?: string | ModelSelection;\n /** API key. Falls back to env (THEOKIT_API_KEY etc). */\n apiKey?: string;\n /** Local runtime config (cwd, sandbox). Required to keep the transient agent local-only. */\n local: LocalOptions;\n /**\n * Retry budget on parse failures. Default 1 (initial attempt + 1 retry).\n * The transient agent is REUSED across retries so the registry sees a\n * single entry (EC-3).\n */\n maxRetries?: number;\n /**\n * Optional provider routing forwarded to the transient agent. Required when\n * `model.id` uses a prefix (e.g. `openai/gpt-4o-mini`) but the credential is\n * for a unified gateway (e.g. OpenRouter). Without this, the SDK infers\n * provider from the prefix and may fail with `provider_unresolved` even\n * when the right env key is set under a different provider name.\n */\n providers?: ProviderRoutingSettings;\n /**\n * What to do when the model's output fails schema validation after all\n * retries are exhausted (M14):\n * - `'throw'` (default) — throw {@link GenerateObjectError} `parse_failed`.\n * - `'return-raw'` — resolve with the raw, UNVALIDATED input the model sent\n * (`object` may not match the schema; inspect `raw` too).\n * - `'return-partial'` — for object schemas, resolve with only the fields that\n * individually validate (best-effort salvage); non-object schemas fall back\n * to raw.\n */\n errorStrategy?: \"throw\" | \"return-partial\" | \"return-raw\";\n}\n\n/**\n * Successful return from {@link Agent.generateObject}.\n *\n * @public\n */\nexport interface GenerateObjectResult<T> {\n /** Typed object parsed via the Zod schema. */\n object: T;\n /** Raw input the model passed to the synthetic tool, before Zod parse. */\n raw: unknown;\n /** Token usage of the LLM call(s) that produced the result. */\n usage: { inputTokens: number; outputTokens: number };\n /** Stop reason of the underlying agent run. */\n finishReason: \"tool_use\" | \"error\";\n}\n\n/**\n * Typed error thrown by {@link Agent.generateObject} when the model refuses\n * to call the synthetic `output` tool or when retries are exhausted.\n *\n * @public\n */\nexport class GenerateObjectError extends Error {\n override readonly name = \"GenerateObjectError\";\n readonly code: \"no_tool_call\" | \"parse_failed\";\n override readonly cause?: unknown;\n constructor(code: \"no_tool_call\" | \"parse_failed\", message: string, cause?: unknown) {\n super(message);\n this.code = code;\n if (cause !== undefined) this.cause = cause;\n }\n}\n\ninterface GenerateObjectDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n /** Hard-delete the transient agent from the registry after dispose. */\n delete: (agentId: string) => Promise<void>;\n}\n\n/**\n * M14 — best-effort partial salvage for `errorStrategy: 'return-partial'`. For an object schema,\n * keep only the fields that individually validate; anything else (non-object schema or non-object\n * raw) falls back to the raw value. Works across Zod v3/v4 via the `.shape` record both expose.\n */\nfunction salvagePartial<T extends ZodType>(schema: T, raw: unknown): unknown {\n if (typeof raw !== \"object\" || raw === null) return raw;\n const shape = (schema as { shape?: Record<string, ZodType> }).shape;\n if (shape === undefined || typeof shape !== \"object\") return raw;\n const rawObj = raw as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [key, fieldSchema] of Object.entries(shape)) {\n if (typeof fieldSchema?.safeParse !== \"function\") continue;\n const parsed = fieldSchema.safeParse(rawObj[key]);\n if (parsed.success) out[key] = parsed.data;\n }\n return out;\n}\n\n/**\n * Implementation of `Agent.generateObject`. Receives the `Agent.create`\n * factory as a callback to keep the dependency graph acyclic (mirrors the\n * AgentBuilder pattern in D25 / agent.ts injection).\n *\n * Algorithm:\n * 1. Convert the consumer's Zod schema to JSON Schema.\n * 2. Build a single synthetic `output` CustomTool whose handler captures\n * the raw input and short-circuits the agent loop. The handler\n * THROWS a sentinel so the loop terminates immediately without a\n * second LLM round-trip.\n * 3. Create ONE transient agent (per the entire generateObject call,\n * not per retry — EC-3). Each retry re-sends through the same agent,\n * driving fresh LLM rounds.\n * 4. On each attempt, send a wrapper prompt instructing the model to\n * call the `output` tool. Capture the raw input via the sentinel.\n * 5. Parse the raw input via schema.parse. On success, return typed.\n * On failure, retry until the budget is exhausted.\n * 6. Always dispose the transient agent in `finally`.\n *\n * @internal\n */\n/**\n * M21 — phase 1: run `options.model` as a plain reasoning agent (no output tool) and return its\n * free-text answer, which phase 2's structuring model then extracts. The transient reasoning agent\n * is disposed + hard-deleted so the registry count stays stable (EC-3, mirrors the phase-2 finally).\n */\nasync function runReasoningPhase<T extends ZodType>(\n options: GenerateObjectOptions<T>,\n deps: GenerateObjectDeps,\n): Promise<string> {\n const reasoningOptions: AgentOptions = {\n model: options.model,\n local: options.local,\n ...(options.systemPrompt !== undefined ? { systemPrompt: options.systemPrompt } : {}),\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.providers !== undefined ? { providers: options.providers } : {}),\n };\n const reasoningAgent = await deps.create(reasoningOptions);\n try {\n const run = await reasoningAgent.send(options.prompt);\n const result = await run.wait();\n const text = (result as { result?: unknown }).result;\n return typeof text === \"string\" ? text : options.prompt;\n } finally {\n await disposeAndDeleteTransient(reasoningAgent, deps.delete);\n }\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: retry loop + capture sentinel + dispose-and-delete is a single transaction; splitting harms locality and the finally block.\nexport async function generateObjectImpl<T extends ZodType>(\n options: GenerateObjectOptions<T>,\n deps: GenerateObjectDeps,\n): Promise<GenerateObjectResult<ZodNamespace.infer<T>>> {\n const { jsonSchema, maxRetries, initialUsage } = setupStructuredOutput(\n options.schema,\n options.maxRetries,\n );\n let capturedRaw: unknown;\n let lastUsage = initialUsage;\n const sentinel = Symbol(\"generate-object-sentinel\");\n class CaptureSentinel extends Error {\n readonly [sentinel] = true;\n constructor(public readonly captured: unknown) {\n super(\"generate-object-capture\");\n }\n }\n\n const outputTool = makeOutputTool(jsonSchema, (input) => {\n // EC-D10 (parallel tool use): first capture wins. Subsequent calls\n // in the same response are ignored.\n if (capturedRaw === undefined) {\n capturedRaw = input;\n }\n throw new CaptureSentinel(input);\n });\n\n // M21 — when a separate structuring model is set, `model` first reasons in free text (phase 1),\n // and `structuringModel` extracts the object over that text (phase 2). Absent ⇒ single-model flow.\n const reasoningText =\n options.structuringModel !== undefined ? await runReasoningPhase(options, deps) : undefined;\n const structuringModel = options.structuringModel ?? options.model;\n const structuringPrompt = reasoningText ?? options.prompt;\n\n const agentOptions: AgentOptions = buildTransientAgentOptions({\n model: structuringModel,\n local: options.local,\n outputTool,\n ...(options.systemPrompt !== undefined ? { systemPrompt: options.systemPrompt } : {}),\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.providers !== undefined ? { providers: options.providers } : {}),\n });\n const agent = await deps.create(agentOptions);\n\n try {\n const userMessage = buildToolPrompt(structuringPrompt);\n let lastParseError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt += 1) {\n capturedRaw = undefined;\n const run = await agent.send(userMessage);\n const result = await run.wait();\n lastUsage = extractUsage(result);\n\n if (capturedRaw === undefined) {\n // EC: when the underlying run errored (e.g. provider_unresolved),\n // surface the original cause instead of the misleading \"model\n // returned text\" message — there was no model call at all.\n if (result.status === \"error\" && result.error !== undefined) {\n throw new GenerateObjectError(\n \"no_tool_call\",\n `Agent run failed before the model could reply: ${result.error.message ?? \"unknown error\"} [${result.error.code ?? \"?\"}]`,\n result.error,\n );\n }\n // Model didn't call the output tool.\n if (attempt === maxRetries) {\n throw new GenerateObjectError(\n \"no_tool_call\",\n \"The model returned text instead of calling the `output` tool.\",\n );\n }\n continue;\n }\n\n const parsed = options.schema.safeParse(capturedRaw);\n if (parsed.success) {\n return {\n object: parsed.data,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n lastParseError = parsed.error;\n }\n\n // M14 — all retries exhausted with a parse failure. `errorStrategy` decides the outcome; the\n // tool WAS called (capturedRaw is set), so `finishReason` stays `tool_use`.\n if (options.errorStrategy === \"return-raw\") {\n return {\n object: capturedRaw as ZodNamespace.infer<T>,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n if (options.errorStrategy === \"return-partial\") {\n return {\n object: salvagePartial(options.schema, capturedRaw) as ZodNamespace.infer<T>,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n throw new GenerateObjectError(\n \"parse_failed\",\n \"Schema parse failed after all retries.\",\n lastParseError,\n );\n } finally {\n // EC-3: dispose AND hard-delete the transient agent so registry count\n // stays stable across retries.\n await disposeAndDeleteTransient(agent, deps.delete);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/generate-object.ts"],"names":[],"mappings":"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","file":"chunk-3CAQCFVZ.js","sourcesContent":["import type { z as ZodNamespace, ZodType } from \"zod\";\n\nimport {\n buildToolPrompt,\n buildTransientAgentOptions,\n disposeAndDeleteTransient,\n extractUsage,\n makeOutputTool,\n setupStructuredOutput,\n} from \"./internal/structured-output-helpers.js\";\nimport type { AgentOptions, LocalOptions, ModelSelection, SDKAgent } from \"./types/agent.js\";\nimport type { ProviderRoutingSettings } from \"./types/providers.js\";\n\n/**\n * Options accepted by {@link Agent.generateObject}. Returns a typed object\n * matching the supplied Zod schema. See ADR D33.\n *\n * @public\n */\nexport interface GenerateObjectOptions<T extends ZodType> {\n /** Zod schema describing the expected object shape. */\n schema: T;\n /** User prompt — the model is asked to fill the schema given this prompt. */\n prompt: string;\n /** Optional system prompt steering the model. */\n systemPrompt?: string;\n /** Model selection. Required (transient agents need a model). SE8 — accepts a\n * bare-string id shorthand (`\"openai/gpt-4o-mini\"`) or a {@link ModelSelection}. */\n model: string | ModelSelection;\n /**\n * M21 — optional separate model for the STRUCTURING step. When set, `model` first produces a\n * free-text reasoned answer to the prompt (phase 1), then `structuringModel` extracts the\n * schema-matched object by calling the `output` tool over that answer (phase 2). Lets a large\n * model reason while a cheap fast model does the extraction. Absent ⇒ today's single-model flow.\n */\n structuringModel?: string | ModelSelection;\n /** API key. Falls back to env (THEOKIT_API_KEY etc). */\n apiKey?: string;\n /** Local runtime config (cwd, sandbox). Required to keep the transient agent local-only. */\n local: LocalOptions;\n /**\n * Retry budget on parse failures. Default 1 (initial attempt + 1 retry).\n * The transient agent is REUSED across retries so the registry sees a\n * single entry (EC-3).\n */\n maxRetries?: number;\n /**\n * Optional provider routing forwarded to the transient agent. Required when\n * `model.id` uses a prefix (e.g. `openai/gpt-4o-mini`) but the credential is\n * for a unified gateway (e.g. OpenRouter). Without this, the SDK infers\n * provider from the prefix and may fail with `provider_unresolved` even\n * when the right env key is set under a different provider name.\n */\n providers?: ProviderRoutingSettings;\n /**\n * What to do when the model's output fails schema validation after all\n * retries are exhausted (M14):\n * - `'throw'` (default) — throw {@link GenerateObjectError} `parse_failed`.\n * - `'return-raw'` — resolve with the raw, UNVALIDATED input the model sent\n * (`object` may not match the schema; inspect `raw` too).\n * - `'return-partial'` — for object schemas, resolve with only the fields that\n * individually validate (best-effort salvage); non-object schemas fall back\n * to raw.\n */\n errorStrategy?: \"throw\" | \"return-partial\" | \"return-raw\";\n}\n\n/**\n * Successful return from {@link Agent.generateObject}.\n *\n * @public\n */\nexport interface GenerateObjectResult<T> {\n /** Typed object parsed via the Zod schema. */\n object: T;\n /** Raw input the model passed to the synthetic tool, before Zod parse. */\n raw: unknown;\n /** Token usage of the LLM call(s) that produced the result. */\n usage: { inputTokens: number; outputTokens: number };\n /** Stop reason of the underlying agent run. */\n finishReason: \"tool_use\" | \"error\";\n}\n\n/**\n * Typed error thrown by {@link Agent.generateObject} when the model refuses\n * to call the synthetic `output` tool or when retries are exhausted.\n *\n * @public\n */\nexport class GenerateObjectError extends Error {\n override readonly name = \"GenerateObjectError\";\n readonly code: \"no_tool_call\" | \"parse_failed\";\n override readonly cause?: unknown;\n constructor(code: \"no_tool_call\" | \"parse_failed\", message: string, cause?: unknown) {\n super(message);\n this.code = code;\n if (cause !== undefined) this.cause = cause;\n }\n}\n\ninterface GenerateObjectDeps {\n create: (options: AgentOptions) => Promise<SDKAgent>;\n /** Hard-delete the transient agent from the registry after dispose. */\n delete: (agentId: string) => Promise<void>;\n}\n\n/**\n * M14 — best-effort partial salvage for `errorStrategy: 'return-partial'`. For an object schema,\n * keep only the fields that individually validate; anything else (non-object schema or non-object\n * raw) falls back to the raw value. Works across Zod v3/v4 via the `.shape` record both expose.\n */\nfunction salvagePartial<T extends ZodType>(schema: T, raw: unknown): unknown {\n if (typeof raw !== \"object\" || raw === null) return raw;\n const shape = (schema as { shape?: Record<string, ZodType> }).shape;\n if (shape === undefined || typeof shape !== \"object\") return raw;\n const rawObj = raw as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [key, fieldSchema] of Object.entries(shape)) {\n if (typeof fieldSchema?.safeParse !== \"function\") continue;\n const parsed = fieldSchema.safeParse(rawObj[key]);\n if (parsed.success) out[key] = parsed.data;\n }\n return out;\n}\n\n/**\n * Implementation of `Agent.generateObject`. Receives the `Agent.create`\n * factory as a callback to keep the dependency graph acyclic (mirrors the\n * AgentBuilder pattern in D25 / agent.ts injection).\n *\n * Algorithm:\n * 1. Convert the consumer's Zod schema to JSON Schema.\n * 2. Build a single synthetic `output` CustomTool whose handler captures\n * the raw input and short-circuits the agent loop. The handler\n * THROWS a sentinel so the loop terminates immediately without a\n * second LLM round-trip.\n * 3. Create ONE transient agent (per the entire generateObject call,\n * not per retry — EC-3). Each retry re-sends through the same agent,\n * driving fresh LLM rounds.\n * 4. On each attempt, send a wrapper prompt instructing the model to\n * call the `output` tool. Capture the raw input via the sentinel.\n * 5. Parse the raw input via schema.parse. On success, return typed.\n * On failure, retry until the budget is exhausted.\n * 6. Always dispose the transient agent in `finally`.\n *\n * @internal\n */\n/**\n * M21 — phase 1: run `options.model` as a plain reasoning agent (no output tool) and return its\n * free-text answer, which phase 2's structuring model then extracts. The transient reasoning agent\n * is disposed + hard-deleted so the registry count stays stable (EC-3, mirrors the phase-2 finally).\n */\nasync function runReasoningPhase<T extends ZodType>(\n options: GenerateObjectOptions<T>,\n deps: GenerateObjectDeps,\n): Promise<string> {\n const reasoningOptions: AgentOptions = {\n model: options.model,\n local: options.local,\n ...(options.systemPrompt !== undefined ? { systemPrompt: options.systemPrompt } : {}),\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.providers !== undefined ? { providers: options.providers } : {}),\n };\n const reasoningAgent = await deps.create(reasoningOptions);\n try {\n const run = await reasoningAgent.send(options.prompt);\n const result = await run.wait();\n const text = (result as { result?: unknown }).result;\n return typeof text === \"string\" ? text : options.prompt;\n } finally {\n await disposeAndDeleteTransient(reasoningAgent, deps.delete);\n }\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: retry loop + capture sentinel + dispose-and-delete is a single transaction; splitting harms locality and the finally block.\nexport async function generateObjectImpl<T extends ZodType>(\n options: GenerateObjectOptions<T>,\n deps: GenerateObjectDeps,\n): Promise<GenerateObjectResult<ZodNamespace.infer<T>>> {\n const { jsonSchema, maxRetries, initialUsage } = setupStructuredOutput(\n options.schema,\n options.maxRetries,\n );\n let capturedRaw: unknown;\n let lastUsage = initialUsage;\n const sentinel = Symbol(\"generate-object-sentinel\");\n class CaptureSentinel extends Error {\n readonly [sentinel] = true;\n constructor(public readonly captured: unknown) {\n super(\"generate-object-capture\");\n }\n }\n\n const outputTool = makeOutputTool(jsonSchema, (input) => {\n // EC-D10 (parallel tool use): first capture wins. Subsequent calls\n // in the same response are ignored.\n if (capturedRaw === undefined) {\n capturedRaw = input;\n }\n throw new CaptureSentinel(input);\n });\n\n // M21 — when a separate structuring model is set, `model` first reasons in free text (phase 1),\n // and `structuringModel` extracts the object over that text (phase 2). Absent ⇒ single-model flow.\n const reasoningText =\n options.structuringModel !== undefined ? await runReasoningPhase(options, deps) : undefined;\n const structuringModel = options.structuringModel ?? options.model;\n const structuringPrompt = reasoningText ?? options.prompt;\n\n const agentOptions: AgentOptions = buildTransientAgentOptions({\n model: structuringModel,\n local: options.local,\n outputTool,\n ...(options.systemPrompt !== undefined ? { systemPrompt: options.systemPrompt } : {}),\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.providers !== undefined ? { providers: options.providers } : {}),\n });\n const agent = await deps.create(agentOptions);\n\n try {\n const userMessage = buildToolPrompt(structuringPrompt);\n let lastParseError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt += 1) {\n capturedRaw = undefined;\n const run = await agent.send(userMessage);\n const result = await run.wait();\n lastUsage = extractUsage(result);\n\n if (capturedRaw === undefined) {\n // EC: when the underlying run errored (e.g. provider_unresolved),\n // surface the original cause instead of the misleading \"model\n // returned text\" message — there was no model call at all.\n if (result.status === \"error\" && result.error !== undefined) {\n throw new GenerateObjectError(\n \"no_tool_call\",\n `Agent run failed before the model could reply: ${result.error.message ?? \"unknown error\"} [${result.error.code ?? \"?\"}]`,\n result.error,\n );\n }\n // Model didn't call the output tool.\n if (attempt === maxRetries) {\n throw new GenerateObjectError(\n \"no_tool_call\",\n \"The model returned text instead of calling the `output` tool.\",\n );\n }\n continue;\n }\n\n const parsed = options.schema.safeParse(capturedRaw);\n if (parsed.success) {\n return {\n object: parsed.data,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n lastParseError = parsed.error;\n }\n\n // M14 — all retries exhausted with a parse failure. `errorStrategy` decides the outcome; the\n // tool WAS called (capturedRaw is set), so `finishReason` stays `tool_use`.\n if (options.errorStrategy === \"return-raw\") {\n return {\n object: capturedRaw as ZodNamespace.infer<T>,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n if (options.errorStrategy === \"return-partial\") {\n return {\n object: salvagePartial(options.schema, capturedRaw) as ZodNamespace.infer<T>,\n raw: capturedRaw,\n usage: lastUsage,\n finishReason: \"tool_use\",\n };\n }\n throw new GenerateObjectError(\n \"parse_failed\",\n \"Schema parse failed after all retries.\",\n lastParseError,\n );\n } finally {\n // EC-3: dispose AND hard-delete the transient agent so registry count\n // stays stable across retries.\n await disposeAndDeleteTransient(agent, deps.delete);\n }\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkNJVV5OAV_cjs = require('./chunk-NJVV5OAV.cjs');
|
|
4
4
|
|
|
5
5
|
// src/internal/session/agent-session-store.ts
|
|
6
6
|
function seedTranscript(prior, opts) {
|
|
7
|
-
return
|
|
7
|
+
return chunkNJVV5OAV_cjs.SessionTranscript.fromRecords(prior, opts);
|
|
8
8
|
}
|
|
9
9
|
function applyThinkingStep(assistant, message) {
|
|
10
10
|
assistant.thinking = message.text;
|
|
@@ -46,7 +46,7 @@ function appendConversation(transcript, conversation) {
|
|
|
46
46
|
}
|
|
47
47
|
async function readSessionMessages(store, agentId) {
|
|
48
48
|
const records = await store.readRecords(agentId);
|
|
49
|
-
return
|
|
49
|
+
return chunkNJVV5OAV_cjs.reconstructMessages(records).map(narrowToSessionMessage);
|
|
50
50
|
}
|
|
51
51
|
function partToText(p) {
|
|
52
52
|
if (p.type === "text") return p.text ?? "";
|
|
@@ -105,5 +105,5 @@ async function appendCompactBoundaryRecord(store, loc, sessionId, meta) {
|
|
|
105
105
|
exports.appendCompactBoundaryRecord = appendCompactBoundaryRecord;
|
|
106
106
|
exports.persistTurn = persistTurn;
|
|
107
107
|
exports.readSessionMessages = readSessionMessages;
|
|
108
|
-
//# sourceMappingURL=chunk-
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
108
|
+
//# sourceMappingURL=chunk-3LK3ZDL5.cjs.map
|
|
109
|
+
//# sourceMappingURL=chunk-3LK3ZDL5.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/session/agent-session-store.ts"],"names":["SessionTranscript","reconstructMessages"],"mappings":"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","file":"chunk-XYOHKCYG.cjs","sourcesContent":["/**\n * SE40 — native session store over the Claude-shaped `.jsonl` transcript.\n *\n * The theokit session format IS the Claude Code record shape (see\n * `../../persistence/session-transcript.ts`). This module owns the FS lifecycle:\n * read existing records, seed a {@link SessionTranscript}'s parent chain, append\n * the new turn (user + assistant/tool blocks), and write append-only. Compaction\n * appends a `compact_boundary` record (a new root) — it NEVER shrinks the line\n * set (that would break the DAG). Reads reconstruct `LlmMessage[]` via\n * {@link reconstructMessages}, narrowed to the in-memory {@link SessionMessage}.\n *\n * @internal\n */\n\nimport type { ToolResultContentBlock } from \"../../types/content-blocks.js\";\nimport type { ConversationStep, ConversationTurn } from \"../../types/conversation.js\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type AssistantTurn,\n reconstructMessages,\n type SessionRecord,\n SessionTranscript,\n} from \"../persistence/session-transcript.js\";\nimport type { SessionMessage, SessionMessagePart } from \"./session-types.js\";\n\n/**\n * Seed a {@link SessionTranscript} from the already-on-disk records so a new\n * append parents correctly on the existing DAG leaf. The transcript re-emits the\n * seeded records verbatim before the new turn, so the whole file rewrites atomically\n * as one append-only line set. Returns the transcript primed with the prior records.\n */\nfunction seedTranscript(\n prior: readonly SessionRecord[],\n opts: { cwd: string; sessionId: string; model: string },\n): SessionTranscript {\n return SessionTranscript.fromRecords(prior, opts);\n}\n\n/** The turn to persist: the user's text plus the rich per-turn conversation view. */\nexport interface PersistTurnInput {\n userText: string;\n conversation: readonly ConversationTurn[];\n /**\n * M50 — when supplied, size-driven auto-compaction runs in the SAME write chain after the turn\n * persists (usage real vs the model's context window; summarizer injected by the caller).\n */\n autoCompact?: {\n usageTotal?: number | undefined;\n contextWindow?: number | undefined;\n summarize: (\n messages: readonly import(\"../../compaction.js\").CompressibleMessage[],\n ) => Promise<string>;\n };\n}\n\ninterface MappedTurn {\n assistant: AssistantTurn;\n toolResults: Array<{ toolUseId: string; content: string; isError?: boolean }>;\n}\n\n/**\n * theokit#122 — record the turn's thinking block, signature included.\n *\n * The signature is what makes the block replayable: without it the resumed turn is rejected by\n * Anthropic with `400 \"thinking blocks cannot be modified\"`.\n */\nfunction applyThinkingStep(\n assistant: AssistantTurn,\n message: { text: string; signature?: string },\n): void {\n assistant.thinking = message.text;\n if (message.signature !== undefined) assistant.thinkingSignature = message.signature;\n}\n\n/** Fold one agent turn's steps into an assistant record + paired tool results. */\nfunction mapAgentTurn(steps: readonly ConversationStep[]): MappedTurn {\n const assistant: AssistantTurn = {};\n const toolResults: MappedTurn[\"toolResults\"] = [];\n const toolCalls: NonNullable<AssistantTurn[\"toolCalls\"]> = [];\n for (const step of steps) {\n if (step.type === \"thinkingMessage\") applyThinkingStep(assistant, step.message);\n else if (step.type === \"assistantMessage\") assistant.text = step.message.text;\n else if (step.type === \"toolCall\")\n toolCalls.push({\n id: step.message.callId,\n name: step.message.name,\n input: (step.message.args as Record<string, unknown> | undefined) ?? {},\n });\n else\n toolResults.push({\n toolUseId: step.message.callId,\n content: step.message.result,\n isError: step.message.isError,\n });\n }\n if (toolCalls.length > 0) assistant.toolCalls = toolCalls;\n return { assistant, toolResults };\n}\n\n/** True when the assistant record carries any content worth emitting. */\nfunction hasAssistantContent(a: AssistantTurn): boolean {\n return a.text !== undefined || a.thinking !== undefined || (a.toolCalls?.length ?? 0) > 0;\n}\n\n/**\n * Map the rich `ConversationTurn[]` (from `run.conversation()`) into the native\n * assistant/tool records. One transcript turn is: an `assistant` record carrying\n * thinking + text + tool_use blocks, then (when present) a `user` record carrying\n * the paired tool_result blocks. Tool results pair with tool_use by `callId`.\n */\nfunction appendConversation(\n transcript: SessionTranscript,\n conversation: readonly ConversationTurn[],\n): void {\n for (const ct of conversation) {\n if (ct.type !== \"agentConversationTurn\") continue;\n const { assistant, toolResults } = mapAgentTurn(ct.turn.steps);\n if (hasAssistantContent(assistant)) transcript.appendAssistantTurn(assistant);\n if (toolResults.length > 0) transcript.appendToolResults(toolResults);\n }\n}\n\n/**\n * The per-agent transcript metadata used to seed a {@link SessionTranscript}. The\n * actual record I/O goes through the injected {@link SessionStore} (SE41), so this\n * no longer carries a `baseDir` — the store is already bound to its location.\n */\nexport interface TranscriptLocation {\n cwd: string;\n agentId: string;\n model: string;\n}\n\n/**\n * Read the session's records via the {@link SessionStore} and reconstruct the\n * resumable history, narrowed to the in-memory {@link SessionMessage} shape\n * (user/assistant text). Tool turns fold into assistant-role context so resume\n * keeps tool history. Works identically for the FS default and an external store.\n */\nexport async function readSessionMessages(\n store: SessionStore,\n agentId: string,\n): Promise<SessionMessage[]> {\n const records = await store.readRecords(agentId);\n return reconstructMessages(records).map(narrowToSessionMessage);\n}\n\ntype NarrowPart = {\n type: string;\n text?: string;\n content?: unknown;\n name?: string;\n id?: string;\n toolUseId?: string;\n input?: unknown;\n isError?: boolean;\n};\n\n/** Render one reconstructed content part as folded session text. */\nfunction partToText(p: NarrowPart): string {\n if (p.type === \"text\") return p.text ?? \"\";\n if (p.type === \"tool_use\") return `[tool call] ${p.name ?? \"\"}`;\n if (p.type === \"tool_result\") {\n const body = typeof p.content === \"string\" ? p.content : JSON.stringify(p.content);\n return `[tool result] ${body}`;\n }\n return \"\";\n}\n\n/**\n * theokit#146 — the same part, kept as STRUCTURE.\n *\n * `partToText` above is what the model replay wants; a host rendering tool cards needs the call id,\n * the tool name and the arguments, none of which survive `[tool call] NAME`. Both projections are\n * produced from the one reconstructed part, so they cannot drift apart.\n *\n * Returns `undefined` for a part with no display meaning (e.g. an image placeholder the session\n * projection does not model), which the caller filters out.\n */\nfunction partToStructured(p: NarrowPart): SessionMessagePart | undefined {\n if (p.type === \"text\") return { type: \"text\", text: p.text ?? \"\" };\n if (p.type === \"tool_use\") {\n return {\n type: \"tool_use\",\n id: p.id ?? \"\",\n name: p.name ?? \"\",\n input: (p.input ?? {}) as Record<string, unknown>,\n };\n }\n if (p.type === \"tool_result\") {\n return {\n type: \"tool_result\",\n toolUseId: p.toolUseId ?? \"\",\n // Reconstructed tool results carry either a string or the SE7 content blocks; both are\n // already the session shape, so this narrows `unknown` rather than converting anything.\n content: p.content as string | ReadonlyArray<ToolResultContentBlock>,\n ...(p.isError === true ? { isError: true } : {}),\n };\n }\n return undefined;\n}\n\n/** Flatten a reconstructed `LlmMessage` to the narrowed in-memory `SessionMessage`. */\nfunction narrowToSessionMessage(m: {\n role: \"system\" | \"user\" | \"assistant\";\n content: NarrowPart[];\n}): SessionMessage {\n const role = m.role === \"user\" ? \"user\" : \"assistant\";\n const text = m.content\n .map(partToText)\n .filter((s) => s.length > 0)\n .join(\"\\n\");\n const parts = m.content\n .map(partToStructured)\n .filter((p): p is SessionMessagePart => p !== undefined);\n return { role, text, parts };\n}\n\n/** The records appended by the last turn (the delta beyond the seeded prior). */\nfunction deltaRecords(\n transcript: SessionTranscript,\n priorLength: number,\n): readonly SessionRecord[] {\n return transcript.records().slice(priorLength);\n}\n\n/**\n * Persist one conversation turn (user + assistant/tool records) to the session\n * store, append-only. Reads the prior records to seed the parent chain, folds the\n * new turn, and appends ONLY the delta via {@link SessionStore.appendRecords}\n * (the store owns the append atomicity — the FS default serializes under a file\n * lock). Within one process, turns for an agent are already chained upstream, so\n * the read→append is ordered; cross-host ordering is the external store's contract.\n */\nexport async function persistTurn(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendUserTurn(turn.userText);\n appendConversation(transcript, turn.conversation);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n\n/**\n * Append-only compaction: add a `compact_boundary` system record (a new root) so\n * resume replays only the post-boundary continuation. Appends only the boundary\n * record via the store — NEVER rewrites or shrinks the prior records.\n */\nexport async function appendCompactBoundaryRecord(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n meta: { preTokens: number; trigger: string },\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendCompactBoundary(meta);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/session/agent-session-store.ts"],"names":["SessionTranscript","reconstructMessages"],"mappings":";;;;;AA+BA,SAAS,cAAA,CACP,OACA,IAAA,EACmB;AACnB,EAAA,OAAOA,mCAAA,CAAkB,WAAA,CAAY,KAAA,EAAO,IAAI,CAAA;AAClD;AA8BA,SAAS,iBAAA,CACP,WACA,OAAA,EACM;AACN,EAAA,SAAA,CAAU,WAAW,OAAA,CAAQ,IAAA;AAC7B,EAAA,IAAI,OAAA,CAAQ,SAAA,KAAc,MAAA,EAAW,SAAA,CAAU,oBAAoB,OAAA,CAAQ,SAAA;AAC7E;AAGA,SAAS,aAAa,KAAA,EAAgD;AACpE,EAAA,MAAM,YAA2B,EAAC;AAClC,EAAA,MAAM,cAAyC,EAAC;AAChD,EAAA,MAAM,YAAqD,EAAC;AAC5D,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,KAAK,IAAA,KAAS,iBAAA,EAAmB,iBAAA,CAAkB,SAAA,EAAW,KAAK,OAAO,CAAA;AAAA,SAAA,IACrE,KAAK,IAAA,KAAS,kBAAA,EAAoB,SAAA,CAAU,IAAA,GAAO,KAAK,OAAA,CAAQ,IAAA;AAAA,SAAA,IAChE,KAAK,IAAA,KAAS,UAAA;AACrB,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,EAAA,EAAI,KAAK,OAAA,CAAQ,MAAA;AAAA,QACjB,IAAA,EAAM,KAAK,OAAA,CAAQ,IAAA;AAAA,QACnB,KAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,IAAgD;AAAC,OACvE,CAAA;AAAA;AAED,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,SAAA,EAAW,KAAK,OAAA,CAAQ,MAAA;AAAA,QACxB,OAAA,EAAS,KAAK,OAAA,CAAQ,MAAA;AAAA,QACtB,OAAA,EAAS,KAAK,OAAA,CAAQ;AAAA,OACvB,CAAA;AAAA,EACL;AACA,EAAA,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,SAAA,CAAU,SAAA,GAAY,SAAA;AAChD,EAAA,OAAO,EAAE,WAAW,WAAA,EAAY;AAClC;AAGA,SAAS,oBAAoB,CAAA,EAA2B;AACtD,EAAA,OAAO,CAAA,CAAE,SAAS,MAAA,IAAa,CAAA,CAAE,aAAa,MAAA,IAAA,CAAc,CAAA,CAAE,SAAA,EAAW,MAAA,IAAU,CAAA,IAAK,CAAA;AAC1F;AAQA,SAAS,kBAAA,CACP,YACA,YAAA,EACM;AACN,EAAA,KAAA,MAAW,MAAM,YAAA,EAAc;AAC7B,IAAA,IAAI,EAAA,CAAG,SAAS,uBAAA,EAAyB;AACzC,IAAA,MAAM,EAAE,SAAA,EAAW,WAAA,KAAgB,YAAA,CAAa,EAAA,CAAG,KAAK,KAAK,CAAA;AAC7D,IAAA,IAAI,mBAAA,CAAoB,SAAS,CAAA,EAAG,UAAA,CAAW,oBAAoB,SAAS,CAAA;AAC5E,IAAA,IAAI,WAAA,CAAY,MAAA,GAAS,CAAA,EAAG,UAAA,CAAW,kBAAkB,WAAW,CAAA;AAAA,EACtE;AACF;AAmBA,eAAsB,mBAAA,CACpB,OACA,OAAA,EAC2B;AAC3B,EAAA,MAAM,OAAA,GAAU,MAAM,KAAA,CAAM,WAAA,CAAY,OAAO,CAAA;AAC/C,EAAA,OAAOC,qCAAA,CAAoB,OAAO,CAAA,CAAE,GAAA,CAAI,sBAAsB,CAAA;AAChE;AAcA,SAAS,WAAW,CAAA,EAAuB;AACzC,EAAA,IAAI,CAAA,CAAE,IAAA,KAAS,MAAA,EAAQ,OAAO,EAAE,IAAA,IAAQ,EAAA;AACxC,EAAA,IAAI,EAAE,IAAA,KAAS,UAAA,SAAmB,CAAA,YAAA,EAAe,CAAA,CAAE,QAAQ,EAAE,CAAA,CAAA;AAC7D,EAAA,IAAI,CAAA,CAAE,SAAS,aAAA,EAAe;AAC5B,IAAA,MAAM,IAAA,GAAO,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,OAAO,CAAA;AACjF,IAAA,OAAO,iBAAiB,IAAI,CAAA,CAAA;AAAA,EAC9B;AACA,EAAA,OAAO,EAAA;AACT;AAYA,SAAS,iBAAiB,CAAA,EAA+C;AACvE,EAAA,IAAI,CAAA,CAAE,IAAA,KAAS,MAAA,EAAQ,OAAO,EAAE,MAAM,MAAA,EAAQ,IAAA,EAAM,CAAA,CAAE,IAAA,IAAQ,EAAA,EAAG;AACjE,EAAA,IAAI,CAAA,CAAE,SAAS,UAAA,EAAY;AACzB,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,UAAA;AAAA,MACN,EAAA,EAAI,EAAE,EAAA,IAAM,EAAA;AAAA,MACZ,IAAA,EAAM,EAAE,IAAA,IAAQ,EAAA;AAAA,MAChB,KAAA,EAAQ,CAAA,CAAE,KAAA,IAAS;AAAC,KACtB;AAAA,EACF;AACA,EAAA,IAAI,CAAA,CAAE,SAAS,aAAA,EAAe;AAC5B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,aAAA;AAAA,MACN,SAAA,EAAW,EAAE,SAAA,IAAa,EAAA;AAAA;AAAA;AAAA,MAG1B,SAAS,CAAA,CAAE,OAAA;AAAA,MACX,GAAI,EAAE,OAAA,KAAY,IAAA,GAAO,EAAE,OAAA,EAAS,IAAA,KAAS;AAAC,KAChD;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAGA,SAAS,uBAAuB,CAAA,EAGb;AACjB,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,IAAA,KAAS,MAAA,GAAS,MAAA,GAAS,WAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,CAAA,CAAE,OAAA,CACZ,GAAA,CAAI,UAAU,CAAA,CACd,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,CAAC,CAAA,CAC1B,KAAK,IAAI,CAAA;AACZ,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CACb,GAAA,CAAI,gBAAgB,EACpB,MAAA,CAAO,CAAC,CAAA,KAA+B,CAAA,KAAM,MAAS,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAM;AAC7B;AAGA,SAAS,YAAA,CACP,YACA,WAAA,EAC0B;AAC1B,EAAA,OAAO,UAAA,CAAW,OAAA,EAAQ,CAAE,KAAA,CAAM,WAAW,CAAA;AAC/C;AAUA,eAAsB,WAAA,CACpB,KAAA,EACA,GAAA,EACA,SAAA,EACA,IAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,WAAA,CAAY,IAAI,OAAO,CAAA;AACjD,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,KAAA,EAAO,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,EAAK,SAAA,EAAW,KAAA,EAAO,GAAA,CAAI,KAAA,EAAO,CAAA;AACtF,EAAA,UAAA,CAAW,cAAA,CAAe,KAAK,QAAQ,CAAA;AACvC,EAAA,kBAAA,CAAmB,UAAA,EAAY,KAAK,YAAY,CAAA;AAChD,EAAA,MAAM,KAAA,CAAM,cAAc,GAAA,CAAI,OAAA,EAAS,aAAa,UAAA,EAAY,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/E;AAOA,eAAsB,2BAAA,CACpB,KAAA,EACA,GAAA,EACA,SAAA,EACA,IAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,WAAA,CAAY,IAAI,OAAO,CAAA;AACjD,EAAA,MAAM,UAAA,GAAa,cAAA,CAAe,KAAA,EAAO,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,EAAK,SAAA,EAAW,KAAA,EAAO,GAAA,CAAI,KAAA,EAAO,CAAA;AACtF,EAAA,UAAA,CAAW,sBAAsB,IAAI,CAAA;AACrC,EAAA,MAAM,KAAA,CAAM,cAAc,GAAA,CAAI,OAAA,EAAS,aAAa,UAAA,EAAY,KAAA,CAAM,MAAM,CAAC,CAAA;AAC/E","file":"chunk-3LK3ZDL5.cjs","sourcesContent":["/**\n * SE40 — native session store over the Claude-shaped `.jsonl` transcript.\n *\n * The theokit session format IS the Claude Code record shape (see\n * `../../persistence/session-transcript.ts`). This module owns the FS lifecycle:\n * read existing records, seed a {@link SessionTranscript}'s parent chain, append\n * the new turn (user + assistant/tool blocks), and write append-only. Compaction\n * appends a `compact_boundary` record (a new root) — it NEVER shrinks the line\n * set (that would break the DAG). Reads reconstruct `LlmMessage[]` via\n * {@link reconstructMessages}, narrowed to the in-memory {@link SessionMessage}.\n *\n * @internal\n */\n\nimport type { ToolResultContentBlock } from \"../../types/content-blocks.js\";\nimport type { ConversationStep, ConversationTurn } from \"../../types/conversation.js\";\nimport type { SessionStore } from \"../../types/session-store.js\";\nimport {\n type AssistantTurn,\n reconstructMessages,\n type SessionRecord,\n SessionTranscript,\n} from \"../persistence/session-transcript.js\";\nimport type { SessionMessage, SessionMessagePart } from \"./session-types.js\";\n\n/**\n * Seed a {@link SessionTranscript} from the already-on-disk records so a new\n * append parents correctly on the existing DAG leaf. The transcript re-emits the\n * seeded records verbatim before the new turn, so the whole file rewrites atomically\n * as one append-only line set. Returns the transcript primed with the prior records.\n */\nfunction seedTranscript(\n prior: readonly SessionRecord[],\n opts: { cwd: string; sessionId: string; model: string },\n): SessionTranscript {\n return SessionTranscript.fromRecords(prior, opts);\n}\n\n/** The turn to persist: the user's text plus the rich per-turn conversation view. */\nexport interface PersistTurnInput {\n userText: string;\n conversation: readonly ConversationTurn[];\n /**\n * M50 — when supplied, size-driven auto-compaction runs in the SAME write chain after the turn\n * persists (usage real vs the model's context window; summarizer injected by the caller).\n */\n autoCompact?: {\n usageTotal?: number | undefined;\n contextWindow?: number | undefined;\n summarize: (\n messages: readonly import(\"../../compaction.js\").CompressibleMessage[],\n ) => Promise<string>;\n };\n}\n\ninterface MappedTurn {\n assistant: AssistantTurn;\n toolResults: Array<{ toolUseId: string; content: string; isError?: boolean }>;\n}\n\n/**\n * theokit#122 — record the turn's thinking block, signature included.\n *\n * The signature is what makes the block replayable: without it the resumed turn is rejected by\n * Anthropic with `400 \"thinking blocks cannot be modified\"`.\n */\nfunction applyThinkingStep(\n assistant: AssistantTurn,\n message: { text: string; signature?: string },\n): void {\n assistant.thinking = message.text;\n if (message.signature !== undefined) assistant.thinkingSignature = message.signature;\n}\n\n/** Fold one agent turn's steps into an assistant record + paired tool results. */\nfunction mapAgentTurn(steps: readonly ConversationStep[]): MappedTurn {\n const assistant: AssistantTurn = {};\n const toolResults: MappedTurn[\"toolResults\"] = [];\n const toolCalls: NonNullable<AssistantTurn[\"toolCalls\"]> = [];\n for (const step of steps) {\n if (step.type === \"thinkingMessage\") applyThinkingStep(assistant, step.message);\n else if (step.type === \"assistantMessage\") assistant.text = step.message.text;\n else if (step.type === \"toolCall\")\n toolCalls.push({\n id: step.message.callId,\n name: step.message.name,\n input: (step.message.args as Record<string, unknown> | undefined) ?? {},\n });\n else\n toolResults.push({\n toolUseId: step.message.callId,\n content: step.message.result,\n isError: step.message.isError,\n });\n }\n if (toolCalls.length > 0) assistant.toolCalls = toolCalls;\n return { assistant, toolResults };\n}\n\n/** True when the assistant record carries any content worth emitting. */\nfunction hasAssistantContent(a: AssistantTurn): boolean {\n return a.text !== undefined || a.thinking !== undefined || (a.toolCalls?.length ?? 0) > 0;\n}\n\n/**\n * Map the rich `ConversationTurn[]` (from `run.conversation()`) into the native\n * assistant/tool records. One transcript turn is: an `assistant` record carrying\n * thinking + text + tool_use blocks, then (when present) a `user` record carrying\n * the paired tool_result blocks. Tool results pair with tool_use by `callId`.\n */\nfunction appendConversation(\n transcript: SessionTranscript,\n conversation: readonly ConversationTurn[],\n): void {\n for (const ct of conversation) {\n if (ct.type !== \"agentConversationTurn\") continue;\n const { assistant, toolResults } = mapAgentTurn(ct.turn.steps);\n if (hasAssistantContent(assistant)) transcript.appendAssistantTurn(assistant);\n if (toolResults.length > 0) transcript.appendToolResults(toolResults);\n }\n}\n\n/**\n * The per-agent transcript metadata used to seed a {@link SessionTranscript}. The\n * actual record I/O goes through the injected {@link SessionStore} (SE41), so this\n * no longer carries a `baseDir` — the store is already bound to its location.\n */\nexport interface TranscriptLocation {\n cwd: string;\n agentId: string;\n model: string;\n}\n\n/**\n * Read the session's records via the {@link SessionStore} and reconstruct the\n * resumable history, narrowed to the in-memory {@link SessionMessage} shape\n * (user/assistant text). Tool turns fold into assistant-role context so resume\n * keeps tool history. Works identically for the FS default and an external store.\n */\nexport async function readSessionMessages(\n store: SessionStore,\n agentId: string,\n): Promise<SessionMessage[]> {\n const records = await store.readRecords(agentId);\n return reconstructMessages(records).map(narrowToSessionMessage);\n}\n\ntype NarrowPart = {\n type: string;\n text?: string;\n content?: unknown;\n name?: string;\n id?: string;\n toolUseId?: string;\n input?: unknown;\n isError?: boolean;\n};\n\n/** Render one reconstructed content part as folded session text. */\nfunction partToText(p: NarrowPart): string {\n if (p.type === \"text\") return p.text ?? \"\";\n if (p.type === \"tool_use\") return `[tool call] ${p.name ?? \"\"}`;\n if (p.type === \"tool_result\") {\n const body = typeof p.content === \"string\" ? p.content : JSON.stringify(p.content);\n return `[tool result] ${body}`;\n }\n return \"\";\n}\n\n/**\n * theokit#146 — the same part, kept as STRUCTURE.\n *\n * `partToText` above is what the model replay wants; a host rendering tool cards needs the call id,\n * the tool name and the arguments, none of which survive `[tool call] NAME`. Both projections are\n * produced from the one reconstructed part, so they cannot drift apart.\n *\n * Returns `undefined` for a part with no display meaning (e.g. an image placeholder the session\n * projection does not model), which the caller filters out.\n */\nfunction partToStructured(p: NarrowPart): SessionMessagePart | undefined {\n if (p.type === \"text\") return { type: \"text\", text: p.text ?? \"\" };\n if (p.type === \"tool_use\") {\n return {\n type: \"tool_use\",\n id: p.id ?? \"\",\n name: p.name ?? \"\",\n input: (p.input ?? {}) as Record<string, unknown>,\n };\n }\n if (p.type === \"tool_result\") {\n return {\n type: \"tool_result\",\n toolUseId: p.toolUseId ?? \"\",\n // Reconstructed tool results carry either a string or the SE7 content blocks; both are\n // already the session shape, so this narrows `unknown` rather than converting anything.\n content: p.content as string | ReadonlyArray<ToolResultContentBlock>,\n ...(p.isError === true ? { isError: true } : {}),\n };\n }\n return undefined;\n}\n\n/** Flatten a reconstructed `LlmMessage` to the narrowed in-memory `SessionMessage`. */\nfunction narrowToSessionMessage(m: {\n role: \"system\" | \"user\" | \"assistant\";\n content: NarrowPart[];\n}): SessionMessage {\n const role = m.role === \"user\" ? \"user\" : \"assistant\";\n const text = m.content\n .map(partToText)\n .filter((s) => s.length > 0)\n .join(\"\\n\");\n const parts = m.content\n .map(partToStructured)\n .filter((p): p is SessionMessagePart => p !== undefined);\n return { role, text, parts };\n}\n\n/** The records appended by the last turn (the delta beyond the seeded prior). */\nfunction deltaRecords(\n transcript: SessionTranscript,\n priorLength: number,\n): readonly SessionRecord[] {\n return transcript.records().slice(priorLength);\n}\n\n/**\n * Persist one conversation turn (user + assistant/tool records) to the session\n * store, append-only. Reads the prior records to seed the parent chain, folds the\n * new turn, and appends ONLY the delta via {@link SessionStore.appendRecords}\n * (the store owns the append atomicity — the FS default serializes under a file\n * lock). Within one process, turns for an agent are already chained upstream, so\n * the read→append is ordered; cross-host ordering is the external store's contract.\n */\nexport async function persistTurn(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n turn: PersistTurnInput,\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendUserTurn(turn.userText);\n appendConversation(transcript, turn.conversation);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n\n/**\n * Append-only compaction: add a `compact_boundary` system record (a new root) so\n * resume replays only the post-boundary continuation. Appends only the boundary\n * record via the store — NEVER rewrites or shrinks the prior records.\n */\nexport async function appendCompactBoundaryRecord(\n store: SessionStore,\n loc: TranscriptLocation,\n sessionId: string,\n meta: { preTokens: number; trigger: string },\n): Promise<void> {\n const prior = await store.readRecords(loc.agentId);\n const transcript = seedTranscript(prior, { cwd: loc.cwd, sessionId, model: loc.model });\n transcript.appendCompactBoundary(meta);\n await store.appendRecords(loc.agentId, deltaRecords(transcript, prior.length));\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkE7WWIX34_cjs = require('./chunk-E7WWIX34.cjs');
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var os = require('os');
|
|
6
6
|
|
|
7
|
-
var SessionBusyError = class extends
|
|
7
|
+
var SessionBusyError = class extends chunkE7WWIX34_cjs.TheokitAgentError {
|
|
8
8
|
constructor(sessionPath) {
|
|
9
9
|
super(
|
|
10
10
|
`another process is already writing this session: ${sessionPath}. Fork it to a new id instead of appending \u2014 two writers interleave lines into a sequence that parses but is not what either process wrote.`,
|
|
@@ -107,5 +107,5 @@ function createLease(sessionPath, lockPath) {
|
|
|
107
107
|
exports.SessionBusyError = SessionBusyError;
|
|
108
108
|
exports.acquireSessionWriter = acquireSessionWriter;
|
|
109
109
|
exports.sessionHasWriter = sessionHasWriter;
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
110
|
+
//# sourceMappingURL=chunk-4E3HLSYE.cjs.map
|
|
111
|
+
//# sourceMappingURL=chunk-4E3HLSYE.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","hostname","openSync","writeSync","fchmodSync","closeSync","readFileSync","existsSync","rmSync"],"mappings":"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","file":"chunk-I53ZQHU6.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n * @internal\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/session-writer.ts"],"names":["TheokitAgentError","hostname","openSync","writeSync","fchmodSync","closeSync","readFileSync","existsSync","rmSync"],"mappings":"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","file":"chunk-4E3HLSYE.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n * @internal\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
|