@theokit/sdk 4.63.0 → 4.63.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.
Files changed (317) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/a2a/index.cjs +2 -2
  3. package/dist/a2a/index.js +1 -1
  4. package/dist/agent-2COLCVOJ.js +57 -0
  5. package/dist/{agent-536OOESZ.js.map → agent-2COLCVOJ.js.map} +1 -1
  6. package/dist/agent-EXAN5EYJ.cjs +66 -0
  7. package/dist/{agent-5KYMG63I.cjs.map → agent-EXAN5EYJ.cjs.map} +1 -1
  8. package/dist/agent-session-store-6EL5NPA4.cjs +24 -0
  9. package/dist/{agent-session-store-WAKLOKWK.cjs.map → agent-session-store-6EL5NPA4.cjs.map} +1 -1
  10. package/dist/agent-session-store-OPGRB4XM.js +7 -0
  11. package/dist/{agent-session-store-G2BMUZQV.js.map → agent-session-store-OPGRB4XM.js.map} +1 -1
  12. package/dist/auth/index.cjs +18 -18
  13. package/dist/auth/index.js +6 -6
  14. package/dist/{batch-ND32UKZS.cjs → batch-BXYTW2DP.cjs} +27 -27
  15. package/dist/{batch-ND32UKZS.cjs.map → batch-BXYTW2DP.cjs.map} +1 -1
  16. package/dist/{batch-2TGJMNCJ.js → batch-L4ZXS46M.js} +10 -10
  17. package/dist/{batch-2TGJMNCJ.js.map → batch-L4ZXS46M.js.map} +1 -1
  18. package/dist/{chunk-VF7EWVDG.js → chunk-2GUYQR5P.js} +3 -3
  19. package/dist/{chunk-VF7EWVDG.js.map → chunk-2GUYQR5P.js.map} +1 -1
  20. package/dist/{chunk-J2UROOIG.cjs → chunk-2HVF2WJY.cjs} +8 -8
  21. package/dist/{chunk-J2UROOIG.cjs.map → chunk-2HVF2WJY.cjs.map} +1 -1
  22. package/dist/{chunk-44MBDIHG.js → chunk-2QDYBS2A.js} +3 -3
  23. package/dist/{chunk-44MBDIHG.js.map → chunk-2QDYBS2A.js.map} +1 -1
  24. package/dist/{chunk-NGESVVJN.cjs → chunk-3FFAEAUJ.cjs} +17 -17
  25. package/dist/{chunk-NGESVVJN.cjs.map → chunk-3FFAEAUJ.cjs.map} +1 -1
  26. package/dist/{chunk-GWC3HADL.cjs → chunk-3NSS2OIZ.cjs} +4 -4
  27. package/dist/{chunk-GWC3HADL.cjs.map → chunk-3NSS2OIZ.cjs.map} +1 -1
  28. package/dist/{chunk-CV7XMBHP.js → chunk-3O42QOMJ.js} +3 -3
  29. package/dist/{chunk-CV7XMBHP.js.map → chunk-3O42QOMJ.js.map} +1 -1
  30. package/dist/{chunk-IWBGCBR6.cjs → chunk-4CLOXP6H.cjs} +4 -4
  31. package/dist/{chunk-IWBGCBR6.cjs.map → chunk-4CLOXP6H.cjs.map} +1 -1
  32. package/dist/{chunk-YEXA3PGR.js → chunk-4FFBAUFS.js} +5 -5
  33. package/dist/{chunk-YEXA3PGR.js.map → chunk-4FFBAUFS.js.map} +1 -1
  34. package/dist/{chunk-HY57ULY2.cjs → chunk-4PYZ5AX3.cjs} +5 -5
  35. package/dist/{chunk-HY57ULY2.cjs.map → chunk-4PYZ5AX3.cjs.map} +1 -1
  36. package/dist/{chunk-OC4NTGMN.js → chunk-4QZSA2HJ.js} +3 -3
  37. package/dist/{chunk-OC4NTGMN.js.map → chunk-4QZSA2HJ.js.map} +1 -1
  38. package/dist/{chunk-K3FW2XZD.cjs → chunk-4ZHTIJNE.cjs} +9 -9
  39. package/dist/{chunk-K3FW2XZD.cjs.map → chunk-4ZHTIJNE.cjs.map} +1 -1
  40. package/dist/{chunk-BUIK7GUA.cjs → chunk-5HEG4CQH.cjs} +8 -8
  41. package/dist/{chunk-BUIK7GUA.cjs.map → chunk-5HEG4CQH.cjs.map} +1 -1
  42. package/dist/{chunk-NXH4GPAQ.cjs → chunk-5VB22SYX.cjs} +7 -7
  43. package/dist/{chunk-NXH4GPAQ.cjs.map → chunk-5VB22SYX.cjs.map} +1 -1
  44. package/dist/{chunk-5USCYPPI.cjs → chunk-6HKGLTJI.cjs} +8 -8
  45. package/dist/{chunk-5USCYPPI.cjs.map → chunk-6HKGLTJI.cjs.map} +1 -1
  46. package/dist/{chunk-EHJEZAOO.cjs → chunk-6RKUO7AW.cjs} +32 -32
  47. package/dist/{chunk-EHJEZAOO.cjs.map → chunk-6RKUO7AW.cjs.map} +1 -1
  48. package/dist/{chunk-5JLFFPCH.js → chunk-7YGCPMGB.js} +4 -4
  49. package/dist/{chunk-5JLFFPCH.js.map → chunk-7YGCPMGB.js.map} +1 -1
  50. package/dist/{chunk-SUKXXLWD.js → chunk-A46SWMIF.js} +6 -6
  51. package/dist/{chunk-SUKXXLWD.js.map → chunk-A46SWMIF.js.map} +1 -1
  52. package/dist/{chunk-F7AQV62G.js → chunk-A4JVILQU.js} +5 -5
  53. package/dist/{chunk-F7AQV62G.js.map → chunk-A4JVILQU.js.map} +1 -1
  54. package/dist/{chunk-44JAAH4X.js → chunk-B3IMEBOA.js} +3 -3
  55. package/dist/{chunk-44JAAH4X.js.map → chunk-B3IMEBOA.js.map} +1 -1
  56. package/dist/{chunk-QDED6YO6.cjs → chunk-B4EOJWOY.cjs} +5 -5
  57. package/dist/{chunk-QDED6YO6.cjs.map → chunk-B4EOJWOY.cjs.map} +1 -1
  58. package/dist/{chunk-YJDH3CKG.js → chunk-BP46RJ5X.js} +5 -5
  59. package/dist/{chunk-YJDH3CKG.js.map → chunk-BP46RJ5X.js.map} +1 -1
  60. package/dist/{chunk-QRSUA2CV.js → chunk-BU4KFWXF.js} +3 -3
  61. package/dist/{chunk-QRSUA2CV.js.map → chunk-BU4KFWXF.js.map} +1 -1
  62. package/dist/{chunk-AQLGBKNT.js → chunk-BZRFXJHA.js} +3 -3
  63. package/dist/{chunk-AQLGBKNT.js.map → chunk-BZRFXJHA.js.map} +1 -1
  64. package/dist/{chunk-UQGQFBRL.js → chunk-C3NT3PVE.js} +3 -3
  65. package/dist/{chunk-UQGQFBRL.js.map → chunk-C3NT3PVE.js.map} +1 -1
  66. package/dist/{chunk-UC3HT2S4.js → chunk-C72AZW7J.js} +3 -3
  67. package/dist/{chunk-UC3HT2S4.js.map → chunk-C72AZW7J.js.map} +1 -1
  68. package/dist/{chunk-WNTAPVU5.cjs → chunk-CQBXOJ6U.cjs} +13 -13
  69. package/dist/{chunk-WNTAPVU5.cjs.map → chunk-CQBXOJ6U.cjs.map} +1 -1
  70. package/dist/{chunk-T7XEKOVW.js → chunk-D4CUGAKZ.js} +3 -3
  71. package/dist/{chunk-T7XEKOVW.js.map → chunk-D4CUGAKZ.js.map} +1 -1
  72. package/dist/{chunk-R6TA2DRJ.js → chunk-DKD3MH6A.js} +6 -6
  73. package/dist/{chunk-R6TA2DRJ.js.map → chunk-DKD3MH6A.js.map} +1 -1
  74. package/dist/{chunk-NLXOGBRJ.cjs → chunk-DKK5QY4C.cjs} +358 -326
  75. package/dist/chunk-DKK5QY4C.cjs.map +1 -0
  76. package/dist/{chunk-YXGAW7BB.js → chunk-DN2PACRI.js} +10 -10
  77. package/dist/{chunk-YXGAW7BB.js.map → chunk-DN2PACRI.js.map} +1 -1
  78. package/dist/{chunk-XSX2UU6Y.cjs → chunk-DXGZ2NUG.cjs} +10 -10
  79. package/dist/{chunk-XSX2UU6Y.cjs.map → chunk-DXGZ2NUG.cjs.map} +1 -1
  80. package/dist/{chunk-NUKRL3I6.cjs → chunk-EG4QM7ZJ.cjs} +18 -2
  81. package/dist/chunk-EG4QM7ZJ.cjs.map +1 -0
  82. package/dist/{chunk-C7QPML3L.js → chunk-GEEXBYFL.js} +9 -9
  83. package/dist/{chunk-C7QPML3L.js.map → chunk-GEEXBYFL.js.map} +1 -1
  84. package/dist/{chunk-A4VHSE56.cjs → chunk-GKEFRLU7.cjs} +6 -6
  85. package/dist/{chunk-A4VHSE56.cjs.map → chunk-GKEFRLU7.cjs.map} +1 -1
  86. package/dist/{chunk-NIRE5CNO.cjs → chunk-HCZD23VS.cjs} +13 -13
  87. package/dist/{chunk-NIRE5CNO.cjs.map → chunk-HCZD23VS.cjs.map} +1 -1
  88. package/dist/{chunk-IRCJ7EHV.cjs → chunk-HOXVQ5FR.cjs} +14 -14
  89. package/dist/{chunk-IRCJ7EHV.cjs.map → chunk-HOXVQ5FR.cjs.map} +1 -1
  90. package/dist/{chunk-QARJGQSA.js → chunk-HS7AVXTF.js} +3 -3
  91. package/dist/{chunk-QARJGQSA.js.map → chunk-HS7AVXTF.js.map} +1 -1
  92. package/dist/{chunk-4I55V454.cjs → chunk-I6HGWHPU.cjs} +4 -4
  93. package/dist/{chunk-4I55V454.cjs.map → chunk-I6HGWHPU.cjs.map} +1 -1
  94. package/dist/{chunk-T73NA43R.js → chunk-IPYYKCRC.js} +3 -3
  95. package/dist/{chunk-T73NA43R.js.map → chunk-IPYYKCRC.js.map} +1 -1
  96. package/dist/{chunk-UUP3MUZ6.cjs → chunk-ITYQ6VBP.cjs} +18 -18
  97. package/dist/{chunk-UUP3MUZ6.cjs.map → chunk-ITYQ6VBP.cjs.map} +1 -1
  98. package/dist/chunk-JPBKYL3B.cjs +18 -0
  99. package/dist/{chunk-6HTXPPHK.cjs.map → chunk-JPBKYL3B.cjs.map} +1 -1
  100. package/dist/{chunk-MZ2FGBLZ.js → chunk-K5OJUYSZ.js} +4 -4
  101. package/dist/{chunk-MZ2FGBLZ.js.map → chunk-K5OJUYSZ.js.map} +1 -1
  102. package/dist/{chunk-EI2Q7SJ5.cjs → chunk-KQ7TS7WW.cjs} +10 -10
  103. package/dist/{chunk-EI2Q7SJ5.cjs.map → chunk-KQ7TS7WW.cjs.map} +1 -1
  104. package/dist/{chunk-RUDY2GTT.js → chunk-KQRW2GPZ.js} +5 -5
  105. package/dist/{chunk-RUDY2GTT.js.map → chunk-KQRW2GPZ.js.map} +1 -1
  106. package/dist/{chunk-P5LCASTC.cjs → chunk-KYAJINUI.cjs} +125 -125
  107. package/dist/{chunk-P5LCASTC.cjs.map → chunk-KYAJINUI.cjs.map} +1 -1
  108. package/dist/{chunk-AMFXSENK.js → chunk-LWGSI6M3.js} +4 -4
  109. package/dist/{chunk-AMFXSENK.js.map → chunk-LWGSI6M3.js.map} +1 -1
  110. package/dist/{chunk-ZHPWGIUX.js → chunk-LXI4XZEC.js} +5 -5
  111. package/dist/{chunk-ZHPWGIUX.js.map → chunk-LXI4XZEC.js.map} +1 -1
  112. package/dist/{chunk-AG5JPQIY.js → chunk-LXQ6HKPS.js} +3 -3
  113. package/dist/{chunk-AG5JPQIY.js.map → chunk-LXQ6HKPS.js.map} +1 -1
  114. package/dist/{chunk-ROYPRJH4.cjs → chunk-LYRDF5TQ.cjs} +9 -9
  115. package/dist/{chunk-ROYPRJH4.cjs.map → chunk-LYRDF5TQ.cjs.map} +1 -1
  116. package/dist/{chunk-G24KZRSA.js → chunk-N2U4AZGB.js} +3 -3
  117. package/dist/{chunk-G24KZRSA.js.map → chunk-N2U4AZGB.js.map} +1 -1
  118. package/dist/{chunk-4ERPXIVB.js → chunk-O73J46PV.js} +5 -5
  119. package/dist/{chunk-4ERPXIVB.js.map → chunk-O73J46PV.js.map} +1 -1
  120. package/dist/{chunk-F3YZMOAU.cjs → chunk-O7DK5PBJ.cjs} +4 -4
  121. package/dist/{chunk-F3YZMOAU.cjs.map → chunk-O7DK5PBJ.cjs.map} +1 -1
  122. package/dist/{chunk-Z4BCLVTI.cjs → chunk-O7LZFG6O.cjs} +5 -5
  123. package/dist/{chunk-Z4BCLVTI.cjs.map → chunk-O7LZFG6O.cjs.map} +1 -1
  124. package/dist/{chunk-3YJNUKYN.cjs → chunk-OHLDPWTA.cjs} +15 -15
  125. package/dist/{chunk-3YJNUKYN.cjs.map → chunk-OHLDPWTA.cjs.map} +1 -1
  126. package/dist/{chunk-FUL2I7G5.js → chunk-OSEG74VJ.js} +8 -8
  127. package/dist/{chunk-FUL2I7G5.js.map → chunk-OSEG74VJ.js.map} +1 -1
  128. package/dist/{chunk-UCBJBJ27.js → chunk-PUAEFLYK.js} +4 -4
  129. package/dist/{chunk-UCBJBJ27.js.map → chunk-PUAEFLYK.js.map} +1 -1
  130. package/dist/{chunk-2C72DXQF.cjs → chunk-RFMBZPOW.cjs} +9 -9
  131. package/dist/{chunk-2C72DXQF.cjs.map → chunk-RFMBZPOW.cjs.map} +1 -1
  132. package/dist/{chunk-NLTVXLGT.js → chunk-RFXWMRFC.js} +3 -3
  133. package/dist/{chunk-NLTVXLGT.js.map → chunk-RFXWMRFC.js.map} +1 -1
  134. package/dist/{chunk-MG3SOM3M.js → chunk-RMIWHYC7.js} +6 -6
  135. package/dist/{chunk-MG3SOM3M.js.map → chunk-RMIWHYC7.js.map} +1 -1
  136. package/dist/{chunk-2DG7KW4L.cjs → chunk-RMWTPV3M.cjs} +8 -8
  137. package/dist/{chunk-2DG7KW4L.cjs.map → chunk-RMWTPV3M.cjs.map} +1 -1
  138. package/dist/{chunk-P6A3M6VD.cjs → chunk-SZVR77EJ.cjs} +10 -10
  139. package/dist/{chunk-P6A3M6VD.cjs.map → chunk-SZVR77EJ.cjs.map} +1 -1
  140. package/dist/{chunk-3E77SX4H.js → chunk-TAZVKYWJ.js} +3 -3
  141. package/dist/{chunk-3E77SX4H.js.map → chunk-TAZVKYWJ.js.map} +1 -1
  142. package/dist/{chunk-WKRSH2VR.js → chunk-U6IFHFWK.js} +3 -3
  143. package/dist/{chunk-WKRSH2VR.js.map → chunk-U6IFHFWK.js.map} +1 -1
  144. package/dist/{chunk-I6TGFUCO.cjs → chunk-UALX2J2Q.cjs} +4 -4
  145. package/dist/{chunk-I6TGFUCO.cjs.map → chunk-UALX2J2Q.cjs.map} +1 -1
  146. package/dist/{chunk-XN7NOENA.js → chunk-UBDMVMBZ.js} +5 -5
  147. package/dist/{chunk-XN7NOENA.js.map → chunk-UBDMVMBZ.js.map} +1 -1
  148. package/dist/{chunk-YTR3RBUZ.js → chunk-UEXIJTLC.js} +4 -4
  149. package/dist/{chunk-YTR3RBUZ.js.map → chunk-UEXIJTLC.js.map} +1 -1
  150. package/dist/{chunk-DQKERTND.cjs → chunk-UGT7GK7I.cjs} +11 -11
  151. package/dist/{chunk-DQKERTND.cjs.map → chunk-UGT7GK7I.cjs.map} +1 -1
  152. package/dist/{chunk-FKMUFNQE.js → chunk-UMP6OJIM.js} +4 -4
  153. package/dist/{chunk-FKMUFNQE.js.map → chunk-UMP6OJIM.js.map} +1 -1
  154. package/dist/{chunk-AEOZTXVW.js → chunk-UNYXQ4M5.js} +3 -3
  155. package/dist/{chunk-AEOZTXVW.js.map → chunk-UNYXQ4M5.js.map} +1 -1
  156. package/dist/{chunk-2BDH744Z.js → chunk-UTSRIDKZ.js} +3 -3
  157. package/dist/{chunk-2BDH744Z.js.map → chunk-UTSRIDKZ.js.map} +1 -1
  158. package/dist/{chunk-2ADR2GSO.cjs → chunk-UU36SHFY.cjs} +5 -5
  159. package/dist/{chunk-2ADR2GSO.cjs.map → chunk-UU36SHFY.cjs.map} +1 -1
  160. package/dist/{chunk-R3UPQFKK.cjs → chunk-V2BOS4WF.cjs} +9 -9
  161. package/dist/{chunk-R3UPQFKK.cjs.map → chunk-V2BOS4WF.cjs.map} +1 -1
  162. package/dist/{chunk-CASUEY42.cjs → chunk-VX4SQ2LY.cjs} +24 -24
  163. package/dist/{chunk-CASUEY42.cjs.map → chunk-VX4SQ2LY.cjs.map} +1 -1
  164. package/dist/{chunk-CHKH5452.js → chunk-WHFUZMIC.js} +80 -48
  165. package/dist/chunk-WHFUZMIC.js.map +1 -0
  166. package/dist/{chunk-Q4CMI2LP.js → chunk-WLQNMXQE.js} +5 -5
  167. package/dist/{chunk-Q4CMI2LP.js.map → chunk-WLQNMXQE.js.map} +1 -1
  168. package/dist/{chunk-XWL6O3SW.cjs → chunk-WNO3C3N2.cjs} +4 -4
  169. package/dist/{chunk-XWL6O3SW.cjs.map → chunk-WNO3C3N2.cjs.map} +1 -1
  170. package/dist/{chunk-T7O6K6PX.js → chunk-XEQ3TM66.js} +18 -3
  171. package/dist/chunk-XEQ3TM66.js.map +1 -0
  172. package/dist/{chunk-BVZW2B5V.cjs → chunk-XJCXEKMD.cjs} +4 -4
  173. package/dist/{chunk-BVZW2B5V.cjs.map → chunk-XJCXEKMD.cjs.map} +1 -1
  174. package/dist/{chunk-JTB5Q42C.cjs → chunk-XL7Q7HB2.cjs} +4 -4
  175. package/dist/{chunk-JTB5Q42C.cjs.map → chunk-XL7Q7HB2.cjs.map} +1 -1
  176. package/dist/{chunk-LW7G5DYW.js → chunk-XO4SOZN6.js} +4 -4
  177. package/dist/{chunk-LW7G5DYW.js.map → chunk-XO4SOZN6.js.map} +1 -1
  178. package/dist/{chunk-CQGYNZ3K.cjs → chunk-XWTLIGO3.cjs} +4 -4
  179. package/dist/{chunk-CQGYNZ3K.cjs.map → chunk-XWTLIGO3.cjs.map} +1 -1
  180. package/dist/{chunk-6SBW4QR2.cjs → chunk-YML4ME27.cjs} +11 -11
  181. package/dist/{chunk-6SBW4QR2.cjs.map → chunk-YML4ME27.cjs.map} +1 -1
  182. package/dist/{chunk-P6H23T43.cjs → chunk-ZCIRZE37.cjs} +11 -11
  183. package/dist/{chunk-P6H23T43.cjs.map → chunk-ZCIRZE37.cjs.map} +1 -1
  184. package/dist/{chunk-QI7WE46I.cjs → chunk-ZLQSWSJ2.cjs} +11 -11
  185. package/dist/{chunk-QI7WE46I.cjs.map → chunk-ZLQSWSJ2.cjs.map} +1 -1
  186. package/dist/{chunk-ILCGLTSA.cjs → chunk-ZSWPZ23K.cjs} +4 -4
  187. package/dist/{chunk-ILCGLTSA.cjs.map → chunk-ZSWPZ23K.cjs.map} +1 -1
  188. package/dist/{chunk-IDCKSLYH.js → chunk-ZZMZWBR6.js} +3 -3
  189. package/dist/{chunk-IDCKSLYH.js.map → chunk-ZZMZWBR6.js.map} +1 -1
  190. package/dist/compact-session-QV57KR66.cjs +59 -0
  191. package/dist/{compact-session-GJXJD73F.cjs.map → compact-session-QV57KR66.cjs.map} +1 -1
  192. package/dist/compact-session-RIGOWC4Y.js +22 -0
  193. package/dist/{compact-session-TSMQOIHO.js.map → compact-session-RIGOWC4Y.js.map} +1 -1
  194. package/dist/compaction.cjs +17 -17
  195. package/dist/compaction.js +4 -4
  196. package/dist/concurrency.cjs +7 -7
  197. package/dist/concurrency.js +5 -5
  198. package/dist/context-AGQ3DIZU.cjs +24 -0
  199. package/dist/{context-LCKNH2XI.cjs.map → context-AGQ3DIZU.cjs.map} +1 -1
  200. package/dist/context-GQRFBCLP.js +7 -0
  201. package/dist/{context-MZSKGKYR.js.map → context-GQRFBCLP.js.map} +1 -1
  202. package/dist/cron.cjs +36 -36
  203. package/dist/cron.js +35 -35
  204. package/dist/errors.cjs +22 -22
  205. package/dist/errors.js +3 -3
  206. package/dist/eval.cjs +41 -41
  207. package/dist/eval.js +35 -35
  208. package/dist/{executor-ZERJ3DGN.cjs → executor-6CYCA7MI.cjs} +25 -25
  209. package/dist/{executor-ZERJ3DGN.cjs.map → executor-6CYCA7MI.cjs.map} +1 -1
  210. package/dist/{executor-BMEHFOXZ.js → executor-QSEC3MRH.js} +8 -8
  211. package/dist/{executor-BMEHFOXZ.js.map → executor-QSEC3MRH.js.map} +1 -1
  212. package/dist/filesystem/index.cjs +8 -8
  213. package/dist/filesystem/index.js +4 -4
  214. package/dist/fs-session-store-4PIHT5UX.cjs +20 -0
  215. package/dist/{fs-session-store-XZ7ZTB3A.cjs.map → fs-session-store-4PIHT5UX.cjs.map} +1 -1
  216. package/dist/fs-session-store-NM6YSTE2.js +11 -0
  217. package/dist/{fs-session-store-4BHGIWRE.js.map → fs-session-store-NM6YSTE2.js.map} +1 -1
  218. package/dist/generate-object-GUNAUVBX.js +8 -0
  219. package/dist/{generate-object-N5MDZUJI.js.map → generate-object-GUNAUVBX.js.map} +1 -1
  220. package/dist/generate-object-R7HTMW7F.cjs +21 -0
  221. package/dist/{generate-object-PGSP5U7N.cjs.map → generate-object-R7HTMW7F.cjs.map} +1 -1
  222. package/dist/index-manager-RBDHP3PM.js +17 -0
  223. package/dist/{index-manager-ACEQG3IJ.js.map → index-manager-RBDHP3PM.js.map} +1 -1
  224. package/dist/index-manager-UZBF22BJ.cjs +26 -0
  225. package/dist/{index-manager-6TEEIWM7.cjs.map → index-manager-UZBF22BJ.cjs.map} +1 -1
  226. package/dist/index.cjs +139 -139
  227. package/dist/index.js +50 -50
  228. package/dist/{inject-session-ZCQUB3IT.js → inject-session-DAR7OUCW.js} +8 -8
  229. package/dist/{inject-session-ZCQUB3IT.js.map → inject-session-DAR7OUCW.js.map} +1 -1
  230. package/dist/{inject-session-PPSO2IDD.cjs → inject-session-WQKMC3GR.cjs} +10 -10
  231. package/dist/{inject-session-PPSO2IDD.cjs.map → inject-session-WQKMC3GR.cjs.map} +1 -1
  232. package/dist/internal/memory/adapters/index.cjs +9 -9
  233. package/dist/internal/memory/adapters/index.js +8 -8
  234. package/dist/internal/memory/storage/index.cjs +39 -39
  235. package/dist/internal/memory/storage/index.js +10 -10
  236. package/dist/internal/persistence/index.cjs +21 -21
  237. package/dist/internal/persistence/index.js +7 -7
  238. package/dist/internal/security/index.cjs +14 -14
  239. package/dist/internal/security/index.js +4 -4
  240. package/dist/judge-call-LAHMBJ57.cjs +23 -0
  241. package/dist/{judge-call-EGYRC2RE.cjs.map → judge-call-LAHMBJ57.cjs.map} +1 -1
  242. package/dist/judge-call-LVXABPS3.js +6 -0
  243. package/dist/{judge-call-DWHAJATE.js.map → judge-call-LVXABPS3.js.map} +1 -1
  244. package/dist/mcp-auth.cjs +15 -15
  245. package/dist/mcp-auth.js +5 -5
  246. package/dist/models.cjs +22 -22
  247. package/dist/models.js +7 -7
  248. package/dist/path-safety.cjs +11 -11
  249. package/dist/path-safety.js +4 -4
  250. package/dist/persistence.cjs +28 -28
  251. package/dist/persistence.js +9 -9
  252. package/dist/project.cjs +6 -6
  253. package/dist/project.js +4 -4
  254. package/dist/providers.cjs +9 -9
  255. package/dist/providers.js +7 -7
  256. package/dist/registry-75W33OGL.js +10 -0
  257. package/dist/{registry-VFP3WWQP.js.map → registry-75W33OGL.js.map} +1 -1
  258. package/dist/registry-V7BDQ6QB.cjs +47 -0
  259. package/dist/{registry-VFKX7WOP.cjs.map → registry-V7BDQ6QB.cjs.map} +1 -1
  260. package/dist/retry.cjs +5 -5
  261. package/dist/retry.js +4 -4
  262. package/dist/sandbox/index.cjs +7 -7
  263. package/dist/sandbox/index.js +3 -3
  264. package/dist/server/errors-envelope.cjs +15 -15
  265. package/dist/server/errors-envelope.js +4 -4
  266. package/dist/session-transcript-6WNITKRE.cjs +63 -0
  267. package/dist/{session-transcript-HF5GEU5M.cjs.map → session-transcript-6WNITKRE.cjs.map} +1 -1
  268. package/dist/session-transcript-XAEKW6P7.js +6 -0
  269. package/dist/{session-transcript-P462ZRHX.js.map → session-transcript-XAEKW6P7.js.map} +1 -1
  270. package/dist/skills.cjs +8 -8
  271. package/dist/skills.js +6 -6
  272. package/dist/stream-object-UF4QRQRU.js +8 -0
  273. package/dist/{stream-object-L57K4OFS.js.map → stream-object-UF4QRQRU.js.map} +1 -1
  274. package/dist/stream-object-ZJTQNKY3.cjs +21 -0
  275. package/dist/{stream-object-BUCF5PHO.cjs.map → stream-object-ZJTQNKY3.cjs.map} +1 -1
  276. package/dist/subagents-loader-CRT5LLL3.js +8 -0
  277. package/dist/{subagents-loader-6ATCDEKN.js.map → subagents-loader-CRT5LLL3.js.map} +1 -1
  278. package/dist/subagents-loader-YEUFFPAL.cjs +17 -0
  279. package/dist/{subagents-loader-X5QKODCL.cjs.map → subagents-loader-YEUFFPAL.cjs.map} +1 -1
  280. package/dist/subagents-loader.cjs +7 -7
  281. package/dist/subagents-loader.js +5 -5
  282. package/dist/subscription/index.cjs +4 -4
  283. package/dist/subscription/index.js +3 -3
  284. package/dist/task-store.cjs +8 -8
  285. package/dist/task-store.js +5 -5
  286. package/dist/workflow.cjs +28 -28
  287. package/dist/workflow.js +7 -7
  288. package/package.json +1 -1
  289. package/dist/agent-536OOESZ.js +0 -57
  290. package/dist/agent-5KYMG63I.cjs +0 -66
  291. package/dist/agent-session-store-G2BMUZQV.js +0 -7
  292. package/dist/agent-session-store-WAKLOKWK.cjs +0 -24
  293. package/dist/chunk-6HTXPPHK.cjs +0 -18
  294. package/dist/chunk-CHKH5452.js.map +0 -1
  295. package/dist/chunk-NLXOGBRJ.cjs.map +0 -1
  296. package/dist/chunk-NUKRL3I6.cjs.map +0 -1
  297. package/dist/chunk-T7O6K6PX.js.map +0 -1
  298. package/dist/compact-session-GJXJD73F.cjs +0 -59
  299. package/dist/compact-session-TSMQOIHO.js +0 -22
  300. package/dist/context-LCKNH2XI.cjs +0 -24
  301. package/dist/context-MZSKGKYR.js +0 -7
  302. package/dist/fs-session-store-4BHGIWRE.js +0 -11
  303. package/dist/fs-session-store-XZ7ZTB3A.cjs +0 -20
  304. package/dist/generate-object-N5MDZUJI.js +0 -8
  305. package/dist/generate-object-PGSP5U7N.cjs +0 -21
  306. package/dist/index-manager-6TEEIWM7.cjs +0 -26
  307. package/dist/index-manager-ACEQG3IJ.js +0 -17
  308. package/dist/judge-call-DWHAJATE.js +0 -6
  309. package/dist/judge-call-EGYRC2RE.cjs +0 -23
  310. package/dist/registry-VFKX7WOP.cjs +0 -47
  311. package/dist/registry-VFP3WWQP.js +0 -10
  312. package/dist/session-transcript-HF5GEU5M.cjs +0 -63
  313. package/dist/session-transcript-P462ZRHX.js +0 -6
  314. package/dist/stream-object-BUCF5PHO.cjs +0 -21
  315. package/dist/stream-object-L57K4OFS.js +0 -8
  316. package/dist/subagents-loader-6ATCDEKN.js +0 -8
  317. package/dist/subagents-loader-X5QKODCL.cjs +0 -17
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/memory/dreaming/diary.ts","../src/internal/memory/migrate-sqlite-to-lance.ts"],"names":["join","readFile","replaceFileAtomic","createHash","defaultIndexPath","existsSync","openMemoryDb","resolveMemoryRoot","lanceStoragePath","redactSecrets","ConfigurationError","rmSync","mkdirSync","LanceIndex","renameSync"],"mappings":"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","file":"chunk-IRCJ7EHV.cjs","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { replaceFileAtomic } from \"../../persistence/atomic-write.js\";\nimport type { MemoryRoot } from \"../storage/memory-root.js\";\n\n/**\n * Dream-diary append (ADR D7).\n *\n * Diary lives at `.theokit/memory/dream-diary.md` and grows with one entry\n * per sweep. Each entry is content-hashed so the same input produces the\n * same entry — idempotency contract.\n *\n * Writes go through `replaceFileAtomic` (EC-3 of edge-case review) so a\n * crash mid-write can never leave a half-written diary.\n *\n * Shared with `@theokit/sdk-memory`; see the memory-store barrel.\n */\n\nexport interface DiaryEntry {\n timestampMs: number;\n factsBefore: number;\n factsAfter: number;\n duplicatesRemoved: number;\n clustersCreated: number;\n notesWritten: number;\n}\n\n/** `<memory root>/dream-diary.md`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */\nexport function diaryPath(root: MemoryRoot): string {\n return join(root, \"dream-diary.md\");\n}\n\n/**\n * One diary entry as the markdown that gets appended: a timestamp heading, the short entry hash,\n * and the counts the sweep produced. The hash is what makes a re-run recognisable as the same\n * sweep rather than a new one.\n */\nexport function renderDiaryEntry(entry: DiaryEntry): string {\n const stamp = new Date(entry.timestampMs).toISOString();\n const hash = entryHash(entry).slice(0, 8);\n return [\n `## ${stamp}`,\n \"\",\n `- entry-hash: ${hash}`,\n `- facts before: ${entry.factsBefore}`,\n `- facts after: ${entry.factsAfter}`,\n `- duplicates removed: ${entry.duplicatesRemoved}`,\n `- clusters created: ${entry.clustersCreated}`,\n `- notes written: ${entry.notesWritten}`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Append one sweep's entry to `<memory root>/dream-diary.md`, creating the file with its header\n * when this is the first sweep. The diary is a human-readable record of what dreaming changed —\n * consolidations are otherwise invisible, because they alter the notes rather than announce\n * themselves.\n */\nexport async function appendDiaryEntry(root: MemoryRoot, entry: DiaryEntry): Promise<void> {\n const path = diaryPath(root);\n let raw = \"\";\n try {\n raw = await readFile(path, \"utf8\");\n } catch {\n raw = \"# Dream Diary\\n\\n\";\n }\n const next = `${raw.endsWith(\"\\n\") ? raw : `${raw}\\n`}${renderDiaryEntry(entry)}`;\n await replaceFileAtomic(path, next);\n}\n\n/**\n * A stable hash of one entry's counts, so two sweeps that did the same work read as the same work.\n * Rendered truncated in the entry; the full value is what callers compare.\n */\nexport function entryHash(entry: DiaryEntry): string {\n return createHash(\"sha256\")\n .update(\n [\n entry.factsBefore,\n entry.factsAfter,\n entry.duplicatesRemoved,\n entry.clustersCreated,\n entry.notesWritten,\n ].join(\"|\"),\n )\n .digest(\"hex\");\n}\n","import { existsSync, mkdirSync, renameSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { redactSecrets } from \"../security/index.js\";\nimport { defaultIndexPath, openMemoryDb } from \"./index-db.js\";\nimport { LanceIndex, lanceStoragePath } from \"./lance-index.js\";\nimport { type MemoryRoot, resolveMemoryRoot } from \"./storage/memory-root.js\";\n\n/**\n * Migrate Memory.index from SQLite to LanceDB (ADR D44).\n *\n * EC-3 MUST FIX: validation uses NFC unicode normalization on both sides.\n *\n * Shared with `@theokit/sdk-memory` through the memory-store barrel (#463).\n */\n\nexport interface MigrateOptions {\n cwd: string;\n /**\n * Absolute path (or `~/`-prefixed) of the memory root to migrate, when the agent that wrote it\n * set `memory.directory`. Defaults to `<cwd>/.theokit/memory` (#463).\n */\n directory?: string;\n dryRun?: boolean;\n batchSize?: number;\n /** Inject for tests; defaults to console.log. */\n logger?: (msg: string) => void;\n}\n\nexport interface MigrateResult {\n countSqlite: number;\n countLance: number;\n /** Set when validation succeeded (or dryRun); false on validation failure. */\n validated: boolean;\n /** Per-fact compare results (size = sample count, up to 10). */\n sampleComparisons: ReadonlyArray<{ id: string; match: boolean }>;\n /** Lance storage location (final, after migration). */\n lancePath: string;\n /** Was the migration committed (false for dry-run). */\n committed: boolean;\n}\n\n/**\n * One row of the SQLite memory index, as the Lance migration reads it: the chunk plus the tenant\n * columns the target table needs. Shared with `@theokit/sdk-memory`, which migrates the same table.\n */\nexport interface SqliteFactRow {\n id: string;\n path: string;\n source: \"memory\" | \"sessions\" | \"wiki\";\n start_line: number;\n end_line: number;\n text: string;\n namespace?: string | null;\n scope?: string | null;\n user_id?: string | null;\n}\n\n/**\n * Read all facts from the SQLite memory index. Returns empty array if the\n * SQLite db file does not exist (workspace never used Memory).\n *\n * Shared with `@theokit/sdk-memory` through the memory-store barrel, so it carries no visibility\n * tag — naming that tag in this block would delete the function below it (#463).\n */\nexport async function readAllSqliteFacts(root: MemoryRoot): Promise<SqliteFactRow[]> {\n const dbPath = defaultIndexPath(root);\n if (!existsSync(dbPath)) return [];\n const db = await openMemoryDb({ filePath: dbPath });\n try {\n // SQLite schema: chunks table holds the facts. Schema may vary; we\n // probe column existence and fall back to safe defaults.\n const stmt = db.prepare(\"SELECT id, path, source, start_line, end_line, text FROM chunks\");\n const rows = stmt.all() as Array<{\n id: string;\n path: string;\n source: \"memory\" | \"sessions\" | \"wiki\";\n start_line: number;\n end_line: number;\n text: string;\n }>;\n return rows.map((r) => ({\n ...r,\n namespace: \"default\",\n scope: \"agent\",\n user_id: \"default\",\n }));\n } finally {\n db.close();\n }\n}\n\n/**\n * Compare two strings via NFC normalization (EC-3 MUST FIX). Required\n * because SQLite/Lance native bindings can normalize unicode differently\n * (NFC vs NFD), producing false negatives on facts with accents/emojis.\n *\n * @internal\n */\nfunction nfcEqual(a: string, b: string): boolean {\n return a.normalize(\"NFC\") === b.normalize(\"NFC\");\n}\n\n/**\n * Run the migration. Writes Lance to `<cwd>/.theokit/memory/lance-new/`\n * first, validates round-trip, then renames to `lance/` on success.\n * SQLite db is preserved (CLI prompts user to delete).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: migration is a single transaction (read → write → validate → commit-or-rollback); splitting harms atomicity reasoning.\nexport async function migrateSqliteToLance(opts: MigrateOptions): Promise<MigrateResult> {\n const cwd = opts.cwd;\n // One resolution, reused by every path this migration touches (#463).\n const memoryRoot = resolveMemoryRoot(cwd, { directory: opts.directory });\n const finalPath = lanceStoragePath(memoryRoot);\n const newPath = join(memoryRoot, \"lance-new\");\n // T1.4 (ADR D68): wrap logger so any fact text containing secrets is\n // masked before reaching the destination (console or user-supplied sink).\n // Caller-supplied loggers cannot bypass — by design (D70).\n const rawLog = opts.logger ?? ((m: string) => console.log(m));\n const log = (m: string) => rawLog(redactSecrets(m));\n\n // EC: destination exists → typed error.\n if (existsSync(finalPath)) {\n throw new ConfigurationError(\n `Destination already exists: ${finalPath}. Remove it manually (\\`rm -rf ${finalPath}\\`) and re-run.`,\n { code: \"migration_destination_exists\" },\n );\n }\n if (existsSync(newPath)) {\n // Leftover from a previous failed/interrupted migration. Safe to delete.\n log(`Removing stale ${newPath} from a previous run...`);\n rmSync(newPath, { recursive: true, force: true });\n }\n\n log(`Reading SQLite facts from ${cwd}/.theokit/memory/index.sqlite ...`);\n const sqliteFacts = await readAllSqliteFacts(memoryRoot);\n log(`SQLite has ${sqliteFacts.length} facts.`);\n if (sqliteFacts.length === 0) {\n return {\n countSqlite: 0,\n countLance: 0,\n validated: true,\n sampleComparisons: [],\n lancePath: finalPath,\n committed: false,\n };\n }\n\n // Reuse the embedding runtime configured in the workspace? For v1.2 we\n // require the caller to pass embedding explicitly via env. The migration\n // path is documented as: \"set OPENAI_API_KEY (or similar) before running.\"\n // Here we ship a placeholder embedder that uses a deterministic\n // content-hash — sufficient for migration validation; consumers should\n // re-embed on first real query after migration if needed.\n const placeholderEmbedding = {\n id: \"migration-placeholder\",\n model: \"deterministic-hash\",\n dimension: 8,\n embed: async (texts: ReadonlyArray<string>) => {\n // Simple deterministic hash → 8-dim vector (migration validation only).\n return texts.map((t) => {\n const arr = new Array(8).fill(0);\n for (let i = 0; i < t.length; i += 1) {\n arr[i % 8] += t.charCodeAt(i);\n }\n return arr.map((x) => x / 1000);\n });\n },\n stats: () => ({ cacheHits: 0, cacheMisses: 0, httpCalls: 0, retries: 0 }),\n };\n\n mkdirSync(newPath, { recursive: true });\n log(`Writing Lance index to ${newPath} ...`);\n\n const lance = await LanceIndex.open({\n cwd,\n embedding: placeholderEmbedding,\n storagePath: newPath,\n });\n\n const batchSize = opts.batchSize ?? 100;\n for (let i = 0; i < sqliteFacts.length; i += batchSize) {\n const batch = sqliteFacts.slice(i, i + batchSize).map((f) => ({\n id: f.id,\n text: f.text,\n source: f.source,\n namespace: f.namespace ?? \"default\",\n scope: f.scope ?? \"agent\",\n user_id: f.user_id ?? \"default\",\n timestamp: 0,\n }));\n await lance.addFacts(batch);\n log(` Migrated ${Math.min(i + batchSize, sqliteFacts.length)}/${sqliteFacts.length}`);\n }\n\n // Validation: count + sample compare (10 random) with NFC normalization.\n const lanceCount = await lance.countFacts(\"default\");\n log(`Lance has ${lanceCount} facts (expected ${sqliteFacts.length}).`);\n\n const sampleSize = Math.min(10, sqliteFacts.length);\n const sampleComparisons: { id: string; match: boolean }[] = [];\n for (let i = 0; i < sampleSize; i += 1) {\n const sqliteFact = sqliteFacts[Math.floor((i * sqliteFacts.length) / sampleSize)];\n if (sqliteFact === undefined) continue;\n // Search Lance for this fact's text — best-effort round-trip.\n const hits = await lance.search(sqliteFact.text, {\n namespace: sqliteFact.namespace ?? \"default\",\n limit: 1,\n });\n // EC-3: compare with NFC normalization.\n const hit = hits[0];\n const match = hit !== undefined && nfcEqual(hit.text, sqliteFact.text);\n sampleComparisons.push({ id: sqliteFact.id, match });\n }\n\n const allMatch = sampleComparisons.every((c) => c.match);\n const countMatch = lanceCount === sqliteFacts.length;\n const validated = allMatch && countMatch;\n\n await lance.close();\n\n if (opts.dryRun === true) {\n log(\"Dry-run mode — discarding Lance staging dir.\");\n rmSync(newPath, { recursive: true, force: true });\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated,\n sampleComparisons,\n lancePath: finalPath,\n committed: false,\n };\n }\n\n if (!validated) {\n log(\"Validation FAILED — leaving SQLite intact, removing Lance staging dir.\");\n rmSync(newPath, { recursive: true, force: true });\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated: false,\n sampleComparisons,\n lancePath: finalPath,\n committed: false,\n };\n }\n\n // Atomic commit via rename.\n renameSync(newPath, finalPath);\n log(`Migration committed to ${finalPath}.`);\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated: true,\n sampleComparisons,\n lancePath: finalPath,\n committed: true,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/memory/dreaming/diary.ts","../src/internal/memory/migrate-sqlite-to-lance.ts"],"names":["join","readFile","replaceFileAtomic","createHash","defaultIndexPath","existsSync","openMemoryDb","resolveMemoryRoot","lanceStoragePath","redactSecrets","ConfigurationError","rmSync","mkdirSync","LanceIndex","renameSync"],"mappings":"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","file":"chunk-HOXVQ5FR.cjs","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { replaceFileAtomic } from \"../../persistence/atomic-write.js\";\nimport type { MemoryRoot } from \"../storage/memory-root.js\";\n\n/**\n * Dream-diary append (ADR D7).\n *\n * Diary lives at `.theokit/memory/dream-diary.md` and grows with one entry\n * per sweep. Each entry is content-hashed so the same input produces the\n * same entry — idempotency contract.\n *\n * Writes go through `replaceFileAtomic` (EC-3 of edge-case review) so a\n * crash mid-write can never leave a half-written diary.\n *\n * Shared with `@theokit/sdk-memory`; see the memory-store barrel.\n */\n\nexport interface DiaryEntry {\n timestampMs: number;\n factsBefore: number;\n factsAfter: number;\n duplicatesRemoved: number;\n clustersCreated: number;\n notesWritten: number;\n}\n\n/** `<memory root>/dream-diary.md`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */\nexport function diaryPath(root: MemoryRoot): string {\n return join(root, \"dream-diary.md\");\n}\n\n/**\n * One diary entry as the markdown that gets appended: a timestamp heading, the short entry hash,\n * and the counts the sweep produced. The hash is what makes a re-run recognisable as the same\n * sweep rather than a new one.\n */\nexport function renderDiaryEntry(entry: DiaryEntry): string {\n const stamp = new Date(entry.timestampMs).toISOString();\n const hash = entryHash(entry).slice(0, 8);\n return [\n `## ${stamp}`,\n \"\",\n `- entry-hash: ${hash}`,\n `- facts before: ${entry.factsBefore}`,\n `- facts after: ${entry.factsAfter}`,\n `- duplicates removed: ${entry.duplicatesRemoved}`,\n `- clusters created: ${entry.clustersCreated}`,\n `- notes written: ${entry.notesWritten}`,\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Append one sweep's entry to `<memory root>/dream-diary.md`, creating the file with its header\n * when this is the first sweep. The diary is a human-readable record of what dreaming changed —\n * consolidations are otherwise invisible, because they alter the notes rather than announce\n * themselves.\n */\nexport async function appendDiaryEntry(root: MemoryRoot, entry: DiaryEntry): Promise<void> {\n const path = diaryPath(root);\n let raw = \"\";\n try {\n raw = await readFile(path, \"utf8\");\n } catch {\n raw = \"# Dream Diary\\n\\n\";\n }\n const next = `${raw.endsWith(\"\\n\") ? raw : `${raw}\\n`}${renderDiaryEntry(entry)}`;\n await replaceFileAtomic(path, next);\n}\n\n/**\n * A stable hash of one entry's counts, so two sweeps that did the same work read as the same work.\n * Rendered truncated in the entry; the full value is what callers compare.\n */\nexport function entryHash(entry: DiaryEntry): string {\n return createHash(\"sha256\")\n .update(\n [\n entry.factsBefore,\n entry.factsAfter,\n entry.duplicatesRemoved,\n entry.clustersCreated,\n entry.notesWritten,\n ].join(\"|\"),\n )\n .digest(\"hex\");\n}\n","import { existsSync, mkdirSync, renameSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { redactSecrets } from \"../security/index.js\";\nimport { defaultIndexPath, openMemoryDb } from \"./index-db.js\";\nimport { LanceIndex, lanceStoragePath } from \"./lance-index.js\";\nimport { type MemoryRoot, resolveMemoryRoot } from \"./storage/memory-root.js\";\n\n/**\n * Migrate Memory.index from SQLite to LanceDB (ADR D44).\n *\n * EC-3 MUST FIX: validation uses NFC unicode normalization on both sides.\n *\n * Shared with `@theokit/sdk-memory` through the memory-store barrel (#463).\n */\n\nexport interface MigrateOptions {\n cwd: string;\n /**\n * Absolute path (or `~/`-prefixed) of the memory root to migrate, when the agent that wrote it\n * set `memory.directory`. Defaults to `<cwd>/.theokit/memory` (#463).\n */\n directory?: string;\n dryRun?: boolean;\n batchSize?: number;\n /** Inject for tests; defaults to console.log. */\n logger?: (msg: string) => void;\n}\n\nexport interface MigrateResult {\n countSqlite: number;\n countLance: number;\n /** Set when validation succeeded (or dryRun); false on validation failure. */\n validated: boolean;\n /** Per-fact compare results (size = sample count, up to 10). */\n sampleComparisons: ReadonlyArray<{ id: string; match: boolean }>;\n /** Lance storage location (final, after migration). */\n lancePath: string;\n /** Was the migration committed (false for dry-run). */\n committed: boolean;\n}\n\n/**\n * One row of the SQLite memory index, as the Lance migration reads it: the chunk plus the tenant\n * columns the target table needs. Shared with `@theokit/sdk-memory`, which migrates the same table.\n */\nexport interface SqliteFactRow {\n id: string;\n path: string;\n source: \"memory\" | \"sessions\" | \"wiki\";\n start_line: number;\n end_line: number;\n text: string;\n namespace?: string | null;\n scope?: string | null;\n user_id?: string | null;\n}\n\n/**\n * Read all facts from the SQLite memory index. Returns empty array if the\n * SQLite db file does not exist (workspace never used Memory).\n *\n * Shared with `@theokit/sdk-memory` through the memory-store barrel, so it carries no visibility\n * tag — naming that tag in this block would delete the function below it (#463).\n */\nexport async function readAllSqliteFacts(root: MemoryRoot): Promise<SqliteFactRow[]> {\n const dbPath = defaultIndexPath(root);\n if (!existsSync(dbPath)) return [];\n const db = await openMemoryDb({ filePath: dbPath });\n try {\n // SQLite schema: chunks table holds the facts. Schema may vary; we\n // probe column existence and fall back to safe defaults.\n const stmt = db.prepare(\"SELECT id, path, source, start_line, end_line, text FROM chunks\");\n const rows = stmt.all() as Array<{\n id: string;\n path: string;\n source: \"memory\" | \"sessions\" | \"wiki\";\n start_line: number;\n end_line: number;\n text: string;\n }>;\n return rows.map((r) => ({\n ...r,\n namespace: \"default\",\n scope: \"agent\",\n user_id: \"default\",\n }));\n } finally {\n db.close();\n }\n}\n\n/**\n * Compare two strings via NFC normalization (EC-3 MUST FIX). Required\n * because SQLite/Lance native bindings can normalize unicode differently\n * (NFC vs NFD), producing false negatives on facts with accents/emojis.\n *\n * @internal\n */\nfunction nfcEqual(a: string, b: string): boolean {\n return a.normalize(\"NFC\") === b.normalize(\"NFC\");\n}\n\n/**\n * Run the migration. Writes Lance to `<cwd>/.theokit/memory/lance-new/`\n * first, validates round-trip, then renames to `lance/` on success.\n * SQLite db is preserved (CLI prompts user to delete).\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: migration is a single transaction (read → write → validate → commit-or-rollback); splitting harms atomicity reasoning.\nexport async function migrateSqliteToLance(opts: MigrateOptions): Promise<MigrateResult> {\n const cwd = opts.cwd;\n // One resolution, reused by every path this migration touches (#463).\n const memoryRoot = resolveMemoryRoot(cwd, { directory: opts.directory });\n const finalPath = lanceStoragePath(memoryRoot);\n const newPath = join(memoryRoot, \"lance-new\");\n // T1.4 (ADR D68): wrap logger so any fact text containing secrets is\n // masked before reaching the destination (console or user-supplied sink).\n // Caller-supplied loggers cannot bypass — by design (D70).\n const rawLog = opts.logger ?? ((m: string) => console.log(m));\n const log = (m: string) => rawLog(redactSecrets(m));\n\n // EC: destination exists → typed error.\n if (existsSync(finalPath)) {\n throw new ConfigurationError(\n `Destination already exists: ${finalPath}. Remove it manually (\\`rm -rf ${finalPath}\\`) and re-run.`,\n { code: \"migration_destination_exists\" },\n );\n }\n if (existsSync(newPath)) {\n // Leftover from a previous failed/interrupted migration. Safe to delete.\n log(`Removing stale ${newPath} from a previous run...`);\n rmSync(newPath, { recursive: true, force: true });\n }\n\n log(`Reading SQLite facts from ${cwd}/.theokit/memory/index.sqlite ...`);\n const sqliteFacts = await readAllSqliteFacts(memoryRoot);\n log(`SQLite has ${sqliteFacts.length} facts.`);\n if (sqliteFacts.length === 0) {\n return {\n countSqlite: 0,\n countLance: 0,\n validated: true,\n sampleComparisons: [],\n lancePath: finalPath,\n committed: false,\n };\n }\n\n // Reuse the embedding runtime configured in the workspace? For v1.2 we\n // require the caller to pass embedding explicitly via env. The migration\n // path is documented as: \"set OPENAI_API_KEY (or similar) before running.\"\n // Here we ship a placeholder embedder that uses a deterministic\n // content-hash — sufficient for migration validation; consumers should\n // re-embed on first real query after migration if needed.\n const placeholderEmbedding = {\n id: \"migration-placeholder\",\n model: \"deterministic-hash\",\n dimension: 8,\n embed: async (texts: ReadonlyArray<string>) => {\n // Simple deterministic hash → 8-dim vector (migration validation only).\n return texts.map((t) => {\n const arr = new Array(8).fill(0);\n for (let i = 0; i < t.length; i += 1) {\n arr[i % 8] += t.charCodeAt(i);\n }\n return arr.map((x) => x / 1000);\n });\n },\n stats: () => ({ cacheHits: 0, cacheMisses: 0, httpCalls: 0, retries: 0 }),\n };\n\n mkdirSync(newPath, { recursive: true });\n log(`Writing Lance index to ${newPath} ...`);\n\n const lance = await LanceIndex.open({\n cwd,\n embedding: placeholderEmbedding,\n storagePath: newPath,\n });\n\n const batchSize = opts.batchSize ?? 100;\n for (let i = 0; i < sqliteFacts.length; i += batchSize) {\n const batch = sqliteFacts.slice(i, i + batchSize).map((f) => ({\n id: f.id,\n text: f.text,\n source: f.source,\n namespace: f.namespace ?? \"default\",\n scope: f.scope ?? \"agent\",\n user_id: f.user_id ?? \"default\",\n timestamp: 0,\n }));\n await lance.addFacts(batch);\n log(` Migrated ${Math.min(i + batchSize, sqliteFacts.length)}/${sqliteFacts.length}`);\n }\n\n // Validation: count + sample compare (10 random) with NFC normalization.\n const lanceCount = await lance.countFacts(\"default\");\n log(`Lance has ${lanceCount} facts (expected ${sqliteFacts.length}).`);\n\n const sampleSize = Math.min(10, sqliteFacts.length);\n const sampleComparisons: { id: string; match: boolean }[] = [];\n for (let i = 0; i < sampleSize; i += 1) {\n const sqliteFact = sqliteFacts[Math.floor((i * sqliteFacts.length) / sampleSize)];\n if (sqliteFact === undefined) continue;\n // Search Lance for this fact's text — best-effort round-trip.\n const hits = await lance.search(sqliteFact.text, {\n namespace: sqliteFact.namespace ?? \"default\",\n limit: 1,\n });\n // EC-3: compare with NFC normalization.\n const hit = hits[0];\n const match = hit !== undefined && nfcEqual(hit.text, sqliteFact.text);\n sampleComparisons.push({ id: sqliteFact.id, match });\n }\n\n const allMatch = sampleComparisons.every((c) => c.match);\n const countMatch = lanceCount === sqliteFacts.length;\n const validated = allMatch && countMatch;\n\n await lance.close();\n\n if (opts.dryRun === true) {\n log(\"Dry-run mode — discarding Lance staging dir.\");\n rmSync(newPath, { recursive: true, force: true });\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated,\n sampleComparisons,\n lancePath: finalPath,\n committed: false,\n };\n }\n\n if (!validated) {\n log(\"Validation FAILED — leaving SQLite intact, removing Lance staging dir.\");\n rmSync(newPath, { recursive: true, force: true });\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated: false,\n sampleComparisons,\n lancePath: finalPath,\n committed: false,\n };\n }\n\n // Atomic commit via rename.\n renameSync(newPath, finalPath);\n log(`Migration committed to ${finalPath}.`);\n return {\n countSqlite: sqliteFacts.length,\n countLance: lanceCount,\n validated: true,\n sampleComparisons,\n lancePath: finalPath,\n committed: true,\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { atOrInsideRoot } from './chunk-RIAM53CP.js';
2
- import { ConfigurationError } from './chunk-IDCKSLYH.js';
2
+ import { ConfigurationError } from './chunk-ZZMZWBR6.js';
3
3
  import { createHash } from 'crypto';
4
4
  import { realpathSync, lstatSync, readlinkSync } from 'fs';
5
5
  import { resolve, dirname } from 'path';
@@ -218,5 +218,5 @@ function safeFilenameForId(id, options) {
218
218
  }
219
219
 
220
220
  export { ForbiddenPathError, PathTraversalError, assertNoSymlinkEscape, isForbiddenPath, safeFilenameForId, safePathJoin, sanitizeIdentifier, validateArtifactPath };
221
- //# sourceMappingURL=chunk-QARJGQSA.js.map
222
- //# sourceMappingURL=chunk-QARJGQSA.js.map
221
+ //# sourceMappingURL=chunk-HS7AVXTF.js.map
222
+ //# sourceMappingURL=chunk-HS7AVXTF.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/security/path-guard.ts"],"names":[],"mappings":"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","file":"chunk-QARJGQSA.js","sourcesContent":["/**\n * Canonical path-guard module (ADRs D79-D81).\n *\n * Three primitives + one typed error:\n * - `safePathJoin(base, ...parts)` — resolve THEN prefix-check (ADR D80).\n * - `assertNoSymlinkEscape(path, base)` — `realpathSync` resolves entire\n * symlink chain (EC-1 fix; Hermes v0.2 #386, #61).\n * - `sanitizeIdentifier(input, { maxLen })` — strict grammar\n * `^[a-z0-9][a-z0-9-_]*$` (ADR D81; case-insensitive on input,\n * lowercase on output).\n * - `PathTraversalError` — extends ConfigurationError with code\n * `path_traversal` (ADR D65: no new hierarchy).\n *\n * Wire at all sites where user input becomes a path. CI lint gate\n * `tests/lint/no-unguarded-path-input.test.ts` prevents regression\n * (ADR D85).\n *\n * @internal\n */\n\nimport { createHash } from \"node:crypto\";\nimport { lstatSync, readlinkSync, realpathSync, type Stats } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { atOrInsideRoot } from \"../runtime/context/path-containment.js\";\n\n/**\n * Thrown when a path operation would escape its allowed base directory.\n * Extends `ConfigurationError` (no new error hierarchy per ADR D65).\n *\n */\nexport class PathTraversalError extends ConfigurationError {\n override readonly name: string = \"PathTraversalError\";\n\n constructor(input: string, resolvedPath: string) {\n super(`Path traversal attempt: ${input} → ${resolvedPath}`, {\n code: \"path_traversal\",\n });\n }\n}\n\n/**\n * Thrown when an agent tool is asked to read or write a sensitive path\n * that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,\n * lock files). Distinct from `PathTraversalError` because the path is\n * lexically inside the project — it is just sensitive.\n *\n * Extends `ConfigurationError` (no new error hierarchy per ADR D65).\n *\n * @public\n */\nexport class ForbiddenPathError extends ConfigurationError {\n override readonly name: string = \"ForbiddenPathError\";\n\n constructor(path: string) {\n super(\n `Path '${path}' is in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files)`,\n {\n code: \"forbidden_path\",\n },\n );\n }\n}\n\n/**\n * Is `target` the same as `base`, or strictly under it?\n *\n * The prefix must not double the separator. `resolve` strips a trailing `sep` from every base\n * EXCEPT the filesystem root, where `resolve(sep) === sep`; there `base + sep` would be `//`, which\n * no absolute path starts with, so a root base rejected EVERY path (#149). Only the root takes that\n * branch — for any other base the prefix is unchanged, so containment is not weakened.\n *\n * Both containment checks in this module go through here: they had the same defect and were fixed\n * one at a time, which is exactly how the two drifted apart in the first place.\n *\n * Both arguments MUST already be resolved absolute paths.\n */\nfunction isInside(target: string, base: string): boolean {\n // B-117 — compared after symlink resolution, via the shared rule. A prefix test judges a link by\n // its NAME: `<base>/link/x` where `link` points outside is lexically inside and physically not.\n // Measured reachable from the plugin manager and the MCP client before this was changed.\n //\n // The root itself stays allowed, which `insideRoot` alone does not answer: it returns false for\n // `target === base` (correct for its own caller, wrong here, where `safePathJoin(base)` with no\n // parts must return `base`). Kept as an explicit second clause rather than by weakening the\n // shared rule for everyone.\n return atOrInsideRoot(target, base);\n}\n\n/**\n * Join `base` with `...parts` and ensure the resolved absolute path stays\n * under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents\n * normalized-escape bypasses like `subdir/.\\\\./bar`.\n *\n * Returns the safe absolute path. Throws `PathTraversalError` if escape.\n *\n */\nexport function safePathJoin(base: string, ...parts: string[]): string {\n if (base === \"\") {\n throw new Error(\"safePathJoin: base must be non-empty\");\n }\n // T5.5 — NUL byte + C0/DEL control char rejection at the boundary.\n // Apply before path resolution so a malicious input never reaches\n // `resolve` (which on some platforms behaved unexpectedly with NUL\n // and in N-API callers historically silently truncated).\n rejectNulAndControlChars(base, \"base\");\n for (const part of parts) {\n rejectNulAndControlChars(part, \"path segment\");\n }\n const baseResolved = resolve(base);\n const target = resolve(base, ...parts);\n if (!isInside(target, baseResolved)) {\n throw new PathTraversalError(parts.join(\"/\"), target);\n }\n return target;\n}\n\n/**\n * T5.5 — the operator-legible label for the first NUL (`\\x00`), C0 control char (`\\x01-\\x1F`) or\n * DEL (`\\x7F`) in `input`, or `undefined` when it carries none. Centralizes the scan so every\n * public path-guard / sanitize entrypoint shares one definition of \"control character\".\n *\n * Detection is separated from the throw so the two callers can share the scan and the precise\n * diagnostic while each raising the error ITS OWN contract documents (#368) — a path guard reports\n * a traversal, an identifier validator reports an invalid identifier. Before that split both threw\n * `PathTraversalError`, which made an identifier validator's error class depend on the bytes of the\n * input it was handed.\n *\n * @internal\n */\nfunction firstControlCharLabel(input: string): string | undefined {\n for (let i = 0; i < input.length; i++) {\n const code = input.charCodeAt(i);\n if (code === 0x00 || (code >= 0x01 && code <= 0x1f) || code === 0x7f) {\n return code === 0x00 ? \"<nul-byte>\" : `<control-char-0x${code.toString(16)}>`;\n }\n }\n return undefined;\n}\n\n/**\n * Reject any path-shaped input carrying a NUL or control character, as `PathTraversalError` — the\n * same shape as every other path-shape rejection, so callers do not learn a second error class.\n *\n * @internal\n */\nfunction rejectNulAndControlChars(input: string, role: string): void {\n const label = firstControlCharLabel(input);\n if (label !== undefined) throw new PathTraversalError(`${role}: ${input}`, label);\n}\n\n/**\n * Assert that `path` — including every directory component in the chain —\n * stays under `base` after symlink resolution. No-op when nothing on the\n * path exists yet.\n *\n * Two-bug history:\n * 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C\n * must be resolved end-to-end. `realpathSync` does this in 1 syscall.\n * 2. **Defence-in-depth** (added v1.x): the previous implementation only\n * called `lstatSync(path)` on the terminal component. If an INTERMEDIATE\n * directory was a symlink (`base/inner-symlink → /outside`), `lstat` on\n * `base/inner-symlink/file.txt` followed the symlink and reported the\n * regular file — escape went undetected. Fix: walk up to the nearest\n * existing ancestor and `realpath` THAT, then re-attach the suffix and\n * check the result against the canonical base.\n *\n */\nexport function assertNoSymlinkEscape(path: string, base: string): void {\n // T5.5 — reject NUL / control chars before any FS call (a NUL byte\n // in the path used to silently truncate at the C boundary on legacy\n // libc — defense in depth even on modern Node).\n rejectNulAndControlChars(path, \"path\");\n rejectNulAndControlChars(base, \"base\");\n // Canonical base — symlinks in the base path itself are absorbed once here.\n let baseResolved: string;\n try {\n baseResolved = realpathSync(base);\n } catch {\n // base doesn't exist as a real directory yet — fall back to lexical resolve.\n baseResolved = resolve(base);\n }\n\n // Find the deepest ancestor of `path` that exists, then realpath it.\n // Anything from there onward is \"not yet on disk\" and contributes only\n // its lexical suffix. This covers three cases:\n // - path exists (regular file or symlink at any depth) → realpath the full path\n // - path doesn't exist but intermediate dir is a symlink → realpath the ancestor\n // - nothing on the path exists → no escape risk (return)\n const resolved = realpathOfDeepestExisting(path);\n if (resolved === undefined) return; // path has no existing prefix — nothing to attack\n\n if (!isInside(resolved, baseResolved)) {\n throw new PathTraversalError(`symlink ${path}`, resolved);\n }\n}\n\n/**\n * Find the deepest ancestor of `path` that exists on disk, resolve all\n * symlinks in that ancestor via `realpathSync`, and re-attach the\n * lexical suffix. Returns `undefined` when no ancestor exists.\n *\n * Handles dangling symlinks: if the terminal IS a symlink but its target\n * is missing, we still detect escape via `readlinkSync` + parent resolve.\n */\nfunction realpathOfDeepestExisting(path: string): string | undefined {\n // First try the full path — the common case.\n try {\n return realpathSync(path);\n } catch {\n // Not resolvable. Two sub-cases.\n }\n\n // Sub-case A: terminal is a dangling symlink.\n try {\n const stat: Stats = lstatSync(path);\n if (stat.isSymbolicLink()) {\n const target = readlinkSync(path);\n // Resolve target relative to the REAL parent dir, so intermediate\n // symlinks in the parent chain are absorbed.\n const parentReal = realpathOfDeepestExisting(dirname(path));\n const parentBase = parentReal ?? dirname(path);\n return resolve(parentBase, target);\n }\n } catch {\n // lstat failed too — terminal doesn't exist at all.\n }\n\n // Sub-case B: walk up to the nearest existing ancestor, then re-attach\n // the suffix lexically.\n let cursor = dirname(path);\n let suffix = path.slice(cursor.length);\n while (cursor !== dirname(cursor)) {\n try {\n const real = realpathSync(cursor);\n // Reconstruct: ancestor's realpath + remaining (still-lexical) suffix\n return resolve(real, `.${suffix}`);\n } catch {\n suffix = path.slice(dirname(cursor).length);\n cursor = dirname(cursor);\n }\n }\n // Reached filesystem root without finding any existing ancestor.\n return undefined;\n}\n\nconst LOCK_FILES = new Set([\"pnpm-lock.yaml\", \"package-lock.json\", \"yarn.lock\", \"bun.lockb\"]);\n\n// T5.6 — top-level credential dot-dirs / dot-files. Matching against\n// the FIRST path segment (lowercase). Adding any entry here costs a\n// CHANGELOG note + an explicit case-fold test (entries are lowercased\n// at module load).\nconst SENSITIVE_FIRST_SEGMENTS = new Set([\n \".ssh\",\n \".aws\",\n \".docker\",\n \".kube\",\n \".npmrc\",\n \".netrc\",\n \".pgpass\",\n]);\n\n// T5.6 — credential basenames blocked at ANY depth (lowercase). Catches\n// the developer-laptop case where an agent recurses into a subdir.\nconst SENSITIVE_BASENAMES = new Set([\n \"id_rsa\",\n \"id_ed25519\",\n \"id_ecdsa\",\n \"id_dsa\",\n \"authorized_keys\",\n \"known_hosts\",\n \".npmrc\",\n \".netrc\",\n \".pgpass\",\n]);\n\n// T5.6 — extension suffixes blocked at ANY depth (lowercase). Covers\n// the entire `*.pem` / `*.key` private-material family.\nconst SENSITIVE_SUFFIXES = [\".pem\", \".key\", \".p12\", \".pfx\"];\n\n/**\n * Decide whether a project-relative path points to a known-sensitive file\n * that a coding agent must not read or write.\n *\n * Universal blocklist (works for any agent operating on a project tree):\n *\n * - `.env`, `.env.<anything>` — except `.env.example` (template safe to read)\n * - `.git/` — version control internals\n * - `node_modules/` — dependency cache (changes don't belong to the user)\n * - `.theo/` — TheoKit build artefacts / state\n * - Lock files at any depth: `pnpm-lock.yaml`, `package-lock.json`,\n * `yarn.lock`, `bun.lockb`\n *\n * Operates on path segments (forward-slash normalized). Cross-platform safe.\n *\n * Use together with `safePathJoin` + `assertNoSymlinkEscape`: the former two\n * defeat traversal, this one defeats reading a file that is lexically inside\n * the project but should not be agent-visible.\n *\n * @public\n */\nexport function isForbiddenPath(input: string): boolean {\n // T5.6 — lowercase normalization defeats case-only bypass on\n // case-insensitive filesystems (Windows/macOS-default) where `.ENV`\n // and `.env` map to the same inode but a case-sensitive string\n // check passes the former.\n const normalized = input.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\").toLowerCase();\n if (normalized.length === 0) return false;\n\n const segments = normalized.split(\"/\").filter((s) => s.length > 0);\n if (segments.length === 0) return false;\n\n if (isForbiddenFirstSegment(segments[0]!)) return true;\n if (isForbiddenBasename(segments[segments.length - 1]!)) return true;\n return false;\n}\n\nfunction isForbiddenFirstSegment(first: string): boolean {\n // .env.example is explicitly allowlisted (template safe to read)\n if (first === \".env.example\") return false;\n if (first === \".env\") return true;\n if (/^\\.env\\./.test(first)) return true;\n if (first === \".git\" || first === \"node_modules\" || first === \".theo\") return true;\n return SENSITIVE_FIRST_SEGMENTS.has(first);\n}\n\nfunction isForbiddenBasename(basename: string): boolean {\n if (LOCK_FILES.has(basename)) return true;\n if (SENSITIVE_BASENAMES.has(basename)) return true;\n for (const suffix of SENSITIVE_SUFFIXES) {\n if (basename.endsWith(suffix)) return true;\n }\n return false;\n}\n\nconst IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\\-_]*$/i;\n\n/**\n * T1.4 — validate a relative artifact path string BEFORE it is used to look\n * up a fixture or to fetch from PaaS. Rejects every well-known traversal\n * vector at the boundary, throwing `PathTraversalError`.\n *\n * Vectors rejected:\n * - classic `..` parent-directory traversal (any segment).\n * - backslash separators (Windows-style `..\\\\windows`).\n * - URL-encoded `%2e%2e` / `%2E%2E` (double-decoded traversal).\n * - NUL byte injection (`\\x00`).\n * - Windows drive letter prefix (`C:`, `D:\\\\...`).\n * - Home-tilde expansion (`~/`, `~root/...`).\n * - Absolute paths starting with `/`.\n *\n * Does NOT touch the filesystem — the call is shape-only. Live symlink\n * traversal protection happens via `assertNoSymlinkEscape` at the FS-resolve\n * boundary.\n *\n * @param input - Caller-supplied artifact path.\n * @throws `PathTraversalError` on any rejection.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/security` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport function validateArtifactPath(input: string): void {\n rejectKnownPrefixVectors(input);\n const normalized = decodeAndNormalize(input);\n rejectParentTraversal(input, normalized);\n}\n\nfunction rejectKnownPrefixVectors(input: string): void {\n if (input.includes(\"\\x00\")) {\n throw new PathTraversalError(input, \"<nul-byte>\");\n }\n if (input.startsWith(\"/\") || input.startsWith(\"~\")) {\n throw new PathTraversalError(input, input);\n }\n if (/^[A-Za-z]:[\\\\/]?/.test(input)) {\n throw new PathTraversalError(input, input);\n }\n}\n\nfunction decodeAndNormalize(input: string): string {\n // URL-encoded traversal — 2 passes catches `%252e%252e`.\n // Malformed sequences (decodeURIComponent throws) are themselves a rejection.\n let decoded = input;\n for (let i = 0; i < 2; i += 1) {\n try {\n const next = decodeURIComponent(decoded);\n if (next === decoded) break;\n decoded = next;\n } catch {\n throw new PathTraversalError(input, \"<malformed-url-encoding>\");\n }\n }\n // Normalize backslash to forward slash before segment-walking.\n return decoded.replace(/\\\\/g, \"/\");\n}\n\nfunction rejectParentTraversal(input: string, normalized: string): void {\n for (const segment of normalized.split(\"/\")) {\n if (segment === \"..\" || segment === \"..%00\") {\n throw new PathTraversalError(input, normalized);\n }\n }\n // Defense in depth: literal `..` anywhere in the normalized string.\n if (normalized.includes(\"..\")) {\n throw new PathTraversalError(input, normalized);\n }\n}\n\n/**\n * Validate that `input` is a safe path component (skill name, agent ID,\n * namespace, etc.) and return its lowercase form. Strict grammar\n * `^[a-z0-9][a-z0-9-_]*$` rejects path separators, dots, null bytes,\n * whitespace, unicode invisible chars, and any leading `-`/`_`.\n *\n * ONE error class leaves this function: `ConfigurationError` with code\n * `invalid_identifier`, for every rejection — length out of range, a path\n * separator, `..`, a space, a leading `-`, and a NUL / C0 control char / DEL\n * alike. A caller can branch on that code and know it has covered the whole\n * rejection surface.\n *\n * Before #368 the control-char branch threw `PathTraversalError` instead, which\n * made the error CLASS a function of the attacker's bytes; the message still names\n * the offending byte, which is the part that was worth keeping.\n *\n * @param input - User-supplied identifier candidate.\n * @param options.maxLen - Maximum allowed length (default 64).\n * @returns Lowercase form of `input`.\n * @throws `ConfigurationError` with code `invalid_identifier` on every rejection.\n */\nexport function sanitizeIdentifier(input: string, options?: { maxLen?: number }): string {\n const maxLen = options?.maxLen ?? 64;\n if (input.length === 0 || input.length > maxLen) {\n throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n // T5.5 — explicit NUL / control char rejection ahead of the generic pattern check. The\n // IDENTIFIER_PATTERN regex already excludes these (they are not in `[a-z0-9\\-_]`); naming the\n // offending byte gives operators a precise diagnostic instead of the generic \"invalid characters\"\n // message, which is what makes a prompt-injection trace legible (Rule 3).\n //\n // #368 — the diagnostic used to arrive as a `PathTraversalError`, because the branch reused\n // `safePathJoin`'s thrower. That made the error CLASS a function of the attacker's bytes: a\n // caller branching on the documented `invalid_identifier` rethrew for any input carrying a\n // control char, so a rejection surfaced as a 500 and the 400/500 split became an oracle. The\n // detection is shared; the error each contract documents is not.\n const controlChar = firstControlCharLabel(input);\n if (controlChar !== undefined) {\n throw new ConfigurationError(`Identifier contains ${controlChar}: \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n if (!IDENTIFIER_PATTERN.test(input)) {\n throw new ConfigurationError(`Identifier contains invalid characters: \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n return input.toLowerCase();\n}\n\n/**\n * Convert ANY opaque id (agent id, run id, conversation id, namespace, email,\n * arbitrary string) into a deterministic, filesystem-safe filename component.\n *\n * Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),\n * this is a total function: it NEVER throws on a non-empty string. It returns\n * the lowercased id verbatim when it already matches the safe grammar\n * `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay\n * human-readable), otherwise a deterministic `h-<16 hex>` sha256 token\n * (collision-resistant and always a valid filename). The output charset is\n * always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.\n *\n * @param id - any opaque identifier (must be a non-empty string)\n * @param options.maxLen - max length for the passthrough branch (default 128).\n * Ids longer than this are hashed; the hash token itself is always short.\n * @throws ConfigurationError (code `invalid_filename_id`) only on empty input.\n *\n * @example\n * safeFilenameForId(\"550e8400-e29b-41d4-a716-446655440000\") // passthrough\n * safeFilenameForId(\"user@example.com\") // \"h-<16hex>\"\n *\n */\nexport function safeFilenameForId(id: string, options?: { maxLen?: number }): string {\n if (id.length === 0) {\n throw new ConfigurationError(\"Filename id must be a non-empty string\", {\n code: \"invalid_filename_id\",\n });\n }\n const maxLen = options?.maxLen ?? 128;\n const lower = id.toLowerCase();\n if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {\n return lower;\n }\n return `h-${createHash(\"sha256\").update(id).digest(\"hex\").slice(0, 16)}`;\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/security/path-guard.ts"],"names":[],"mappings":"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","file":"chunk-HS7AVXTF.js","sourcesContent":["/**\n * Canonical path-guard module (ADRs D79-D81).\n *\n * Three primitives + one typed error:\n * - `safePathJoin(base, ...parts)` — resolve THEN prefix-check (ADR D80).\n * - `assertNoSymlinkEscape(path, base)` — `realpathSync` resolves entire\n * symlink chain (EC-1 fix; Hermes v0.2 #386, #61).\n * - `sanitizeIdentifier(input, { maxLen })` — strict grammar\n * `^[a-z0-9][a-z0-9-_]*$` (ADR D81; case-insensitive on input,\n * lowercase on output).\n * - `PathTraversalError` — extends ConfigurationError with code\n * `path_traversal` (ADR D65: no new hierarchy).\n *\n * Wire at all sites where user input becomes a path. CI lint gate\n * `tests/lint/no-unguarded-path-input.test.ts` prevents regression\n * (ADR D85).\n *\n * @internal\n */\n\nimport { createHash } from \"node:crypto\";\nimport { lstatSync, readlinkSync, realpathSync, type Stats } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { atOrInsideRoot } from \"../runtime/context/path-containment.js\";\n\n/**\n * Thrown when a path operation would escape its allowed base directory.\n * Extends `ConfigurationError` (no new error hierarchy per ADR D65).\n *\n */\nexport class PathTraversalError extends ConfigurationError {\n override readonly name: string = \"PathTraversalError\";\n\n constructor(input: string, resolvedPath: string) {\n super(`Path traversal attempt: ${input} → ${resolvedPath}`, {\n code: \"path_traversal\",\n });\n }\n}\n\n/**\n * Thrown when an agent tool is asked to read or write a sensitive path\n * that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,\n * lock files). Distinct from `PathTraversalError` because the path is\n * lexically inside the project — it is just sensitive.\n *\n * Extends `ConfigurationError` (no new error hierarchy per ADR D65).\n *\n * @public\n */\nexport class ForbiddenPathError extends ConfigurationError {\n override readonly name: string = \"ForbiddenPathError\";\n\n constructor(path: string) {\n super(\n `Path '${path}' is in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files)`,\n {\n code: \"forbidden_path\",\n },\n );\n }\n}\n\n/**\n * Is `target` the same as `base`, or strictly under it?\n *\n * The prefix must not double the separator. `resolve` strips a trailing `sep` from every base\n * EXCEPT the filesystem root, where `resolve(sep) === sep`; there `base + sep` would be `//`, which\n * no absolute path starts with, so a root base rejected EVERY path (#149). Only the root takes that\n * branch — for any other base the prefix is unchanged, so containment is not weakened.\n *\n * Both containment checks in this module go through here: they had the same defect and were fixed\n * one at a time, which is exactly how the two drifted apart in the first place.\n *\n * Both arguments MUST already be resolved absolute paths.\n */\nfunction isInside(target: string, base: string): boolean {\n // B-117 — compared after symlink resolution, via the shared rule. A prefix test judges a link by\n // its NAME: `<base>/link/x` where `link` points outside is lexically inside and physically not.\n // Measured reachable from the plugin manager and the MCP client before this was changed.\n //\n // The root itself stays allowed, which `insideRoot` alone does not answer: it returns false for\n // `target === base` (correct for its own caller, wrong here, where `safePathJoin(base)` with no\n // parts must return `base`). Kept as an explicit second clause rather than by weakening the\n // shared rule for everyone.\n return atOrInsideRoot(target, base);\n}\n\n/**\n * Join `base` with `...parts` and ensure the resolved absolute path stays\n * under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents\n * normalized-escape bypasses like `subdir/.\\\\./bar`.\n *\n * Returns the safe absolute path. Throws `PathTraversalError` if escape.\n *\n */\nexport function safePathJoin(base: string, ...parts: string[]): string {\n if (base === \"\") {\n throw new Error(\"safePathJoin: base must be non-empty\");\n }\n // T5.5 — NUL byte + C0/DEL control char rejection at the boundary.\n // Apply before path resolution so a malicious input never reaches\n // `resolve` (which on some platforms behaved unexpectedly with NUL\n // and in N-API callers historically silently truncated).\n rejectNulAndControlChars(base, \"base\");\n for (const part of parts) {\n rejectNulAndControlChars(part, \"path segment\");\n }\n const baseResolved = resolve(base);\n const target = resolve(base, ...parts);\n if (!isInside(target, baseResolved)) {\n throw new PathTraversalError(parts.join(\"/\"), target);\n }\n return target;\n}\n\n/**\n * T5.5 — the operator-legible label for the first NUL (`\\x00`), C0 control char (`\\x01-\\x1F`) or\n * DEL (`\\x7F`) in `input`, or `undefined` when it carries none. Centralizes the scan so every\n * public path-guard / sanitize entrypoint shares one definition of \"control character\".\n *\n * Detection is separated from the throw so the two callers can share the scan and the precise\n * diagnostic while each raising the error ITS OWN contract documents (#368) — a path guard reports\n * a traversal, an identifier validator reports an invalid identifier. Before that split both threw\n * `PathTraversalError`, which made an identifier validator's error class depend on the bytes of the\n * input it was handed.\n *\n * @internal\n */\nfunction firstControlCharLabel(input: string): string | undefined {\n for (let i = 0; i < input.length; i++) {\n const code = input.charCodeAt(i);\n if (code === 0x00 || (code >= 0x01 && code <= 0x1f) || code === 0x7f) {\n return code === 0x00 ? \"<nul-byte>\" : `<control-char-0x${code.toString(16)}>`;\n }\n }\n return undefined;\n}\n\n/**\n * Reject any path-shaped input carrying a NUL or control character, as `PathTraversalError` — the\n * same shape as every other path-shape rejection, so callers do not learn a second error class.\n *\n * @internal\n */\nfunction rejectNulAndControlChars(input: string, role: string): void {\n const label = firstControlCharLabel(input);\n if (label !== undefined) throw new PathTraversalError(`${role}: ${input}`, label);\n}\n\n/**\n * Assert that `path` — including every directory component in the chain —\n * stays under `base` after symlink resolution. No-op when nothing on the\n * path exists yet.\n *\n * Two-bug history:\n * 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C\n * must be resolved end-to-end. `realpathSync` does this in 1 syscall.\n * 2. **Defence-in-depth** (added v1.x): the previous implementation only\n * called `lstatSync(path)` on the terminal component. If an INTERMEDIATE\n * directory was a symlink (`base/inner-symlink → /outside`), `lstat` on\n * `base/inner-symlink/file.txt` followed the symlink and reported the\n * regular file — escape went undetected. Fix: walk up to the nearest\n * existing ancestor and `realpath` THAT, then re-attach the suffix and\n * check the result against the canonical base.\n *\n */\nexport function assertNoSymlinkEscape(path: string, base: string): void {\n // T5.5 — reject NUL / control chars before any FS call (a NUL byte\n // in the path used to silently truncate at the C boundary on legacy\n // libc — defense in depth even on modern Node).\n rejectNulAndControlChars(path, \"path\");\n rejectNulAndControlChars(base, \"base\");\n // Canonical base — symlinks in the base path itself are absorbed once here.\n let baseResolved: string;\n try {\n baseResolved = realpathSync(base);\n } catch {\n // base doesn't exist as a real directory yet — fall back to lexical resolve.\n baseResolved = resolve(base);\n }\n\n // Find the deepest ancestor of `path` that exists, then realpath it.\n // Anything from there onward is \"not yet on disk\" and contributes only\n // its lexical suffix. This covers three cases:\n // - path exists (regular file or symlink at any depth) → realpath the full path\n // - path doesn't exist but intermediate dir is a symlink → realpath the ancestor\n // - nothing on the path exists → no escape risk (return)\n const resolved = realpathOfDeepestExisting(path);\n if (resolved === undefined) return; // path has no existing prefix — nothing to attack\n\n if (!isInside(resolved, baseResolved)) {\n throw new PathTraversalError(`symlink ${path}`, resolved);\n }\n}\n\n/**\n * Find the deepest ancestor of `path` that exists on disk, resolve all\n * symlinks in that ancestor via `realpathSync`, and re-attach the\n * lexical suffix. Returns `undefined` when no ancestor exists.\n *\n * Handles dangling symlinks: if the terminal IS a symlink but its target\n * is missing, we still detect escape via `readlinkSync` + parent resolve.\n */\nfunction realpathOfDeepestExisting(path: string): string | undefined {\n // First try the full path — the common case.\n try {\n return realpathSync(path);\n } catch {\n // Not resolvable. Two sub-cases.\n }\n\n // Sub-case A: terminal is a dangling symlink.\n try {\n const stat: Stats = lstatSync(path);\n if (stat.isSymbolicLink()) {\n const target = readlinkSync(path);\n // Resolve target relative to the REAL parent dir, so intermediate\n // symlinks in the parent chain are absorbed.\n const parentReal = realpathOfDeepestExisting(dirname(path));\n const parentBase = parentReal ?? dirname(path);\n return resolve(parentBase, target);\n }\n } catch {\n // lstat failed too — terminal doesn't exist at all.\n }\n\n // Sub-case B: walk up to the nearest existing ancestor, then re-attach\n // the suffix lexically.\n let cursor = dirname(path);\n let suffix = path.slice(cursor.length);\n while (cursor !== dirname(cursor)) {\n try {\n const real = realpathSync(cursor);\n // Reconstruct: ancestor's realpath + remaining (still-lexical) suffix\n return resolve(real, `.${suffix}`);\n } catch {\n suffix = path.slice(dirname(cursor).length);\n cursor = dirname(cursor);\n }\n }\n // Reached filesystem root without finding any existing ancestor.\n return undefined;\n}\n\nconst LOCK_FILES = new Set([\"pnpm-lock.yaml\", \"package-lock.json\", \"yarn.lock\", \"bun.lockb\"]);\n\n// T5.6 — top-level credential dot-dirs / dot-files. Matching against\n// the FIRST path segment (lowercase). Adding any entry here costs a\n// CHANGELOG note + an explicit case-fold test (entries are lowercased\n// at module load).\nconst SENSITIVE_FIRST_SEGMENTS = new Set([\n \".ssh\",\n \".aws\",\n \".docker\",\n \".kube\",\n \".npmrc\",\n \".netrc\",\n \".pgpass\",\n]);\n\n// T5.6 — credential basenames blocked at ANY depth (lowercase). Catches\n// the developer-laptop case where an agent recurses into a subdir.\nconst SENSITIVE_BASENAMES = new Set([\n \"id_rsa\",\n \"id_ed25519\",\n \"id_ecdsa\",\n \"id_dsa\",\n \"authorized_keys\",\n \"known_hosts\",\n \".npmrc\",\n \".netrc\",\n \".pgpass\",\n]);\n\n// T5.6 — extension suffixes blocked at ANY depth (lowercase). Covers\n// the entire `*.pem` / `*.key` private-material family.\nconst SENSITIVE_SUFFIXES = [\".pem\", \".key\", \".p12\", \".pfx\"];\n\n/**\n * Decide whether a project-relative path points to a known-sensitive file\n * that a coding agent must not read or write.\n *\n * Universal blocklist (works for any agent operating on a project tree):\n *\n * - `.env`, `.env.<anything>` — except `.env.example` (template safe to read)\n * - `.git/` — version control internals\n * - `node_modules/` — dependency cache (changes don't belong to the user)\n * - `.theo/` — TheoKit build artefacts / state\n * - Lock files at any depth: `pnpm-lock.yaml`, `package-lock.json`,\n * `yarn.lock`, `bun.lockb`\n *\n * Operates on path segments (forward-slash normalized). Cross-platform safe.\n *\n * Use together with `safePathJoin` + `assertNoSymlinkEscape`: the former two\n * defeat traversal, this one defeats reading a file that is lexically inside\n * the project but should not be agent-visible.\n *\n * @public\n */\nexport function isForbiddenPath(input: string): boolean {\n // T5.6 — lowercase normalization defeats case-only bypass on\n // case-insensitive filesystems (Windows/macOS-default) where `.ENV`\n // and `.env` map to the same inode but a case-sensitive string\n // check passes the former.\n const normalized = input.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\").toLowerCase();\n if (normalized.length === 0) return false;\n\n const segments = normalized.split(\"/\").filter((s) => s.length > 0);\n if (segments.length === 0) return false;\n\n if (isForbiddenFirstSegment(segments[0]!)) return true;\n if (isForbiddenBasename(segments[segments.length - 1]!)) return true;\n return false;\n}\n\nfunction isForbiddenFirstSegment(first: string): boolean {\n // .env.example is explicitly allowlisted (template safe to read)\n if (first === \".env.example\") return false;\n if (first === \".env\") return true;\n if (/^\\.env\\./.test(first)) return true;\n if (first === \".git\" || first === \"node_modules\" || first === \".theo\") return true;\n return SENSITIVE_FIRST_SEGMENTS.has(first);\n}\n\nfunction isForbiddenBasename(basename: string): boolean {\n if (LOCK_FILES.has(basename)) return true;\n if (SENSITIVE_BASENAMES.has(basename)) return true;\n for (const suffix of SENSITIVE_SUFFIXES) {\n if (basename.endsWith(suffix)) return true;\n }\n return false;\n}\n\nconst IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9\\-_]*$/i;\n\n/**\n * T1.4 — validate a relative artifact path string BEFORE it is used to look\n * up a fixture or to fetch from PaaS. Rejects every well-known traversal\n * vector at the boundary, throwing `PathTraversalError`.\n *\n * Vectors rejected:\n * - classic `..` parent-directory traversal (any segment).\n * - backslash separators (Windows-style `..\\\\windows`).\n * - URL-encoded `%2e%2e` / `%2E%2E` (double-decoded traversal).\n * - NUL byte injection (`\\x00`).\n * - Windows drive letter prefix (`C:`, `D:\\\\...`).\n * - Home-tilde expansion (`~/`, `~root/...`).\n * - Absolute paths starting with `/`.\n *\n * Does NOT touch the filesystem — the call is shape-only. Live symlink\n * traversal protection happens via `assertNoSymlinkEscape` at the FS-resolve\n * boundary.\n *\n * @param input - Caller-supplied artifact path.\n * @throws `PathTraversalError` on any rejection.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/security` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport function validateArtifactPath(input: string): void {\n rejectKnownPrefixVectors(input);\n const normalized = decodeAndNormalize(input);\n rejectParentTraversal(input, normalized);\n}\n\nfunction rejectKnownPrefixVectors(input: string): void {\n if (input.includes(\"\\x00\")) {\n throw new PathTraversalError(input, \"<nul-byte>\");\n }\n if (input.startsWith(\"/\") || input.startsWith(\"~\")) {\n throw new PathTraversalError(input, input);\n }\n if (/^[A-Za-z]:[\\\\/]?/.test(input)) {\n throw new PathTraversalError(input, input);\n }\n}\n\nfunction decodeAndNormalize(input: string): string {\n // URL-encoded traversal — 2 passes catches `%252e%252e`.\n // Malformed sequences (decodeURIComponent throws) are themselves a rejection.\n let decoded = input;\n for (let i = 0; i < 2; i += 1) {\n try {\n const next = decodeURIComponent(decoded);\n if (next === decoded) break;\n decoded = next;\n } catch {\n throw new PathTraversalError(input, \"<malformed-url-encoding>\");\n }\n }\n // Normalize backslash to forward slash before segment-walking.\n return decoded.replace(/\\\\/g, \"/\");\n}\n\nfunction rejectParentTraversal(input: string, normalized: string): void {\n for (const segment of normalized.split(\"/\")) {\n if (segment === \"..\" || segment === \"..%00\") {\n throw new PathTraversalError(input, normalized);\n }\n }\n // Defense in depth: literal `..` anywhere in the normalized string.\n if (normalized.includes(\"..\")) {\n throw new PathTraversalError(input, normalized);\n }\n}\n\n/**\n * Validate that `input` is a safe path component (skill name, agent ID,\n * namespace, etc.) and return its lowercase form. Strict grammar\n * `^[a-z0-9][a-z0-9-_]*$` rejects path separators, dots, null bytes,\n * whitespace, unicode invisible chars, and any leading `-`/`_`.\n *\n * ONE error class leaves this function: `ConfigurationError` with code\n * `invalid_identifier`, for every rejection — length out of range, a path\n * separator, `..`, a space, a leading `-`, and a NUL / C0 control char / DEL\n * alike. A caller can branch on that code and know it has covered the whole\n * rejection surface.\n *\n * Before #368 the control-char branch threw `PathTraversalError` instead, which\n * made the error CLASS a function of the attacker's bytes; the message still names\n * the offending byte, which is the part that was worth keeping.\n *\n * @param input - User-supplied identifier candidate.\n * @param options.maxLen - Maximum allowed length (default 64).\n * @returns Lowercase form of `input`.\n * @throws `ConfigurationError` with code `invalid_identifier` on every rejection.\n */\nexport function sanitizeIdentifier(input: string, options?: { maxLen?: number }): string {\n const maxLen = options?.maxLen ?? 64;\n if (input.length === 0 || input.length > maxLen) {\n throw new ConfigurationError(`Identifier length out of range (1-${maxLen}): \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n // T5.5 — explicit NUL / control char rejection ahead of the generic pattern check. The\n // IDENTIFIER_PATTERN regex already excludes these (they are not in `[a-z0-9\\-_]`); naming the\n // offending byte gives operators a precise diagnostic instead of the generic \"invalid characters\"\n // message, which is what makes a prompt-injection trace legible (Rule 3).\n //\n // #368 — the diagnostic used to arrive as a `PathTraversalError`, because the branch reused\n // `safePathJoin`'s thrower. That made the error CLASS a function of the attacker's bytes: a\n // caller branching on the documented `invalid_identifier` rethrew for any input carrying a\n // control char, so a rejection surfaced as a 500 and the 400/500 split became an oracle. The\n // detection is shared; the error each contract documents is not.\n const controlChar = firstControlCharLabel(input);\n if (controlChar !== undefined) {\n throw new ConfigurationError(`Identifier contains ${controlChar}: \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n if (!IDENTIFIER_PATTERN.test(input)) {\n throw new ConfigurationError(`Identifier contains invalid characters: \"${input}\"`, {\n code: \"invalid_identifier\",\n });\n }\n return input.toLowerCase();\n}\n\n/**\n * Convert ANY opaque id (agent id, run id, conversation id, namespace, email,\n * arbitrary string) into a deterministic, filesystem-safe filename component.\n *\n * Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),\n * this is a total function: it NEVER throws on a non-empty string. It returns\n * the lowercased id verbatim when it already matches the safe grammar\n * `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay\n * human-readable), otherwise a deterministic `h-<16 hex>` sha256 token\n * (collision-resistant and always a valid filename). The output charset is\n * always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.\n *\n * @param id - any opaque identifier (must be a non-empty string)\n * @param options.maxLen - max length for the passthrough branch (default 128).\n * Ids longer than this are hashed; the hash token itself is always short.\n * @throws ConfigurationError (code `invalid_filename_id`) only on empty input.\n *\n * @example\n * safeFilenameForId(\"550e8400-e29b-41d4-a716-446655440000\") // passthrough\n * safeFilenameForId(\"user@example.com\") // \"h-<16hex>\"\n *\n */\nexport function safeFilenameForId(id: string, options?: { maxLen?: number }): string {\n if (id.length === 0) {\n throw new ConfigurationError(\"Filename id must be a non-empty string\", {\n code: \"invalid_filename_id\",\n });\n }\n const maxLen = options?.maxLen ?? 128;\n const lower = id.toLowerCase();\n if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {\n return lower;\n }\n return `h-${createHash(\"sha256\").update(id).digest(\"hex\").slice(0, 16)}`;\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var chunkK3FW2XZD_cjs = require('./chunk-K3FW2XZD.cjs');
3
+ var chunk4ZHTIJNE_cjs = require('./chunk-4ZHTIJNE.cjs');
4
4
  var fs = require('fs');
5
5
  var os = require('os');
6
6
 
7
- var SessionBusyError = class extends chunkK3FW2XZD_cjs.TheokitAgentError {
7
+ var SessionBusyError = class extends chunk4ZHTIJNE_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-4I55V454.cjs.map
111
- //# sourceMappingURL=chunk-4I55V454.cjs.map
110
+ //# sourceMappingURL=chunk-I6HGWHPU.cjs.map
111
+ //# sourceMappingURL=chunk-I6HGWHPU.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-4I55V454.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
1
+ {"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-I6HGWHPU.cjs","sourcesContent":["/**\n * M81 — single-writer lease for a session transcript.\n *\n * ## The problem\n *\n * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec\n * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only\n * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing\n * reports it, because every line parses.\n *\n * ## Why an exclusive lockfile rather than `withFileLock`\n *\n * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a\n * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is\n * scope-based — it holds the lock for the duration of a callback. A session lease is **held across\n * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the\n * whole session lifetime in a callback would invert control of the entire agent loop.\n *\n * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,\n * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —\n * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the\n * plan's rationale (no second mechanism) still holds; only its shape assumption did not.\n *\n * ## Fail fast, never wait\n *\n * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed\n * error lets the caller choose: fork to a new id, or give up with a real diagnosis.\n *\n * @internal\n */\n\nimport {\n closeSync,\n existsSync,\n fchmodSync,\n openSync,\n readFileSync,\n rmSync,\n writeSync,\n} from \"node:fs\";\nimport { hostname } from \"node:os\";\n\nimport { TheokitAgentError } from \"../../errors.js\";\n\n/**\n * M81 — another process already holds the writer lease for this session.\n *\n * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between\n * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough\n * to act on).\n */\nexport class SessionBusyError extends TheokitAgentError {\n override readonly name = \"SessionBusyError\";\n\n constructor(readonly sessionPath: string) {\n super(\n `another process is already writing this session: ${sessionPath}. ` +\n \"Fork it to a new id instead of appending — two writers interleave lines into a sequence \" +\n \"that parses but is not what either process wrote.\",\n { code: \"session_busy\", isRetryable: false },\n );\n }\n}\n\n/** A held writer lease. `release()` is idempotent. */\nexport interface SessionWriterLease {\n readonly sessionPath: string;\n release(): Promise<void>;\n /**\n * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.\n *\n * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to\n * the session — appending a turn — and the cross-host window stops being a lie about liveness.\n *\n * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and\n * hand this process ownership it gave up. That is the one direction of this API that could\n * *create* the double-writer it exists to prevent.\n */\n renew(): void;\n}\n\n/**\n * Staleness window **between machines** — and only between them.\n *\n * 30 s from ACQUISITION. Calling it a \"heartbeat\" would be a lie: the record is written once, when\n * the lease is taken, and is **not** renewed on every write. An earlier version of this comment\n * claimed \"the owner touches the file on every acquisition, so a live process never crosses the\n * window\" — false twice over, and adversarial review measured both.\n *\n * On the **same host** this does not matter: `reclaimable` decides by `pid`, which is exact, and age\n * never enters the calculation. Across hosts it does matter, and it is a real limit: a **live**\n * remote owner loses the lease after 30 s, because there is no way to ask another machine whether\n * its process still exists.\n *\n * **The residue is now closable by the caller** (`agent-builder#118`). `SessionWriterLease.renew()`\n * re-stamps the record; calling it on the path that already writes to the session — appending a turn\n * — keeps a live owner from ever crossing the window. It costs one `write` of ~80 bytes on a file\n * that was previously written once per session.\n *\n * It is `renew()` and not an internal timer on purpose. A timer inside the lease would keep the event\n * loop alive (or need `unref` plus its own teardown), and it would renew a lease belonging to a\n * process that is hung rather than working — which is precisely the state the window exists to\n * detect. Tying the renewal to a real write means the record tracks **progress**, not mere existence.\n */\nexport const HEARTBEAT_WINDOW_MS = 30_000;\n\n/** Who holds the lock. Written as JSON into `.writer.lock`. */\ninterface LockOwner {\n pid: number;\n hostname: string;\n mtime: number;\n}\n\n/** The ownership record for THIS process, stamped now. One place, so acquire and renew cannot drift. */\nfunction ownerNow(): LockOwner {\n return { pid: process.pid, hostname: hostname(), mtime: Date.now() };\n}\n\n/**\n * Writes the owner with user-only permissions.\n *\n * `0600` because the lock is an assertion of OWNERSHIP: with the `0664` the usual umask produces,\n * another user in the same group can overwrite the file and forge ownership of the session — and\n * from then on it is the legitimate owner who starts receiving `SessionBusyError`. The content\n * (`pid`, `hostname`) is low-sensitivity; what the permission protects is the signal's\n * **integrity**, not its secrecy.\n */\nfunction writeOwner(lockPath: string, owner: LockOwner): void {\n const fd = openSync(lockPath, \"w\", 0o600);\n try {\n writeSync(fd, JSON.stringify(owner));\n // `open`'s `mode` only applies on CREATION. A `.writer.lock` inherited from an earlier version —\n // or left behind by a process with a different umask — would stay `0664` after being reclaimed,\n // and the forgery window the mode closes for new locks would remain open for old ones.\n fchmodSync(fd, 0o600);\n } finally {\n closeSync(fd);\n }\n}\n\n/** Reads the lock's owner. `undefined` when the file vanished or the content is unreadable. */\nfunction readOwner(lockPath: string): LockOwner | undefined {\n let raw: string;\n try {\n raw = readFileSync(lockPath, \"utf8\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n // `ENOENT` — vanished between the `EEXIST` and the read. Benign race: the lock is gone.\n if (code === \"ENOENT\") return undefined;\n // `EISDIR` — the lock path is a DIRECTORY. No process in this library creates one; it is debris\n // from something else, and it will never become a readable lock. Failing closed here would lock\n // the session out forever. Treated as owner-less: acquisition proceeds and fails with the real\n // FS error, which says what is wrong — instead of a permanent SessionBusyError that says\n // nothing. It is NOT \"reclaimable\": the lock is not removed, and the caller proceeds without a\n // lease.\n if (code === \"EISDIR\") return undefined;\n // Any other read failure (`EACCES` in a shared directory, `EIO`) differs in kind: the lock\n // **exists** and it is we who cannot read the owner. Treating it as free would let two writers\n // coexist — precisely what the lease exists to prevent — and the `0600` that protects the lock\n // against forgery WIDENS that surface: in a shared directory, another user's lock is unreadable\n // by design.\n //\n // Not knowing who the owner is differs from there being no owner. Fail closed.\n throw new SessionBusyError(lockPath.replace(/\\.writer\\.lock$/, \"\"));\n }\n try {\n const d = JSON.parse(raw) as Partial<LockOwner>;\n if (\n typeof d.pid !== \"number\" ||\n typeof d.hostname !== \"string\" ||\n typeof d.mtime !== \"number\"\n ) {\n return undefined;\n }\n return { pid: d.pid, hostname: d.hostname, mtime: d.mtime };\n } catch {\n // Unreadable JSON: a lock nobody can interpret must not lock the session out forever. Treating\n // it as stale is the recoverable choice; the cost is the same as that of an old lock.\n return undefined;\n }\n}\n\n/** Does the process exist? `signal 0` sends nothing — it only queries permission/existence. */\nfunction processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n // EPERM means it EXISTS and belongs to another user.\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/**\n * Can the lock be taken from whoever holds it?\n *\n * ADR-2 of the plan: reclaiming by `pid` alone has a false positive across machines — the same\n * number exists on another host, pointing at an unrelated process. So:\n *\n * - **same host:** the `pid` is authoritative, and it **alone**. Dead process => reclaimable at\n * once; live process => never, however old the lock is.\n * - **other host:** the `pid` says nothing here. Only the heartbeat window counts, because it is\n * the one signal that does not lie across machines.\n *\n * ## Why age does NOT count on the same host\n *\n * The first version did `stale || !processAlive(pid)`, and that was a serious defect: `mtime` is\n * written at **acquisition** and is not touched on each append, so any session lasting longer than\n * the window — that is, **every real session** — became stealable by another process. Two writers\n * on the same transcript is exactly what the lease exists to prevent.\n *\n * On the same host the question \"does the owner still exist?\" has an exact answer, and age adds no\n * information to it — only a way to be wrong. Keeping the window there would be heuristic layered\n * on top of a fact.\n */\nfunction reclaimable(owner: LockOwner | undefined): boolean {\n if (owner === undefined) return true; // unreadable or vanished\n if (owner.hostname !== hostname()) {\n return Date.now() - owner.mtime > HEARTBEAT_WINDOW_MS;\n }\n return !processAlive(owner.pid);\n}\n\n/**\n * Does the session have a writer **right now**? A query that does NOT take the lease.\n *\n * M95 — it exists because asking by taking creates the very contention it meant to detect: two\n * processes querying a **free** session at the same time made one of them lose, and the consumer\n * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.\n *\n * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take\n * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to\n * **decide an id before opening anything** use this, and handle the race where it shows up.\n *\n */\nexport function sessionHasWriter(sessionPath: string): boolean {\n const lockPath = `${sessionPath}.writer.lock`;\n if (!existsSync(lockPath)) return false;\n try {\n return !reclaimable(readOwner(lockPath));\n } catch {\n // `readOwner` throws when the lock exists and cannot be read — fail closed, for the same reason\n // as there: not knowing who the owner is differs from there being no owner.\n return true;\n }\n}\n\n/**\n * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.\n *\n * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the\n * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across\n * processes, not just across async tasks in one process.\n */\nexport async function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease> {\n // M95 — `.writer.lock`, NOT `.lock`.\n //\n // `withFileLock(path, fn)` already uses `<path>.lock` as its companion. While the lease was called\n // from nowhere (the defect this milestone fixes) the collision was theoretical; wiring it to the\n // same file would make the long-lived lease block every short critical section on the same path.\n //\n // Two files because they are two things: `withFileLock` protects a section with a start and an\n // end; the lease is OWNERSHIP, held across turns, with an explicit `release()` — the distinction\n // the M81 docstring above already explains.\n const lockPath = `${sessionPath}.writer.lock`;\n const mine: LockOwner = ownerNow();\n let fd: number;\n try {\n // The mode applies on CREATION — the `w` in `writeOwner` does not alter an existing file.\n fd = openSync(lockPath, \"wx\", 0o600);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n // M95 — the lock exists. Until now that alone was enough to refuse, and that was the defect: a\n // TUI killed by SIGKILL locked the user out of their own session PERMANENTLY, with no documented\n // recovery path. Now the lock says who the owner is, and a dead owner yields its place.\n if (!reclaimable(readOwner(lockPath))) throw new SessionBusyError(sessionPath);\n writeOwner(lockPath, mine);\n return createLease(sessionPath, lockPath);\n }\n closeSync(fd);\n writeOwner(lockPath, mine);\n\n return createLease(sessionPath, lockPath);\n}\n\n/** The lease itself — idempotent `release()`. Extracted because acquisition has two exit paths. */\nfunction createLease(sessionPath: string, lockPath: string): SessionWriterLease {\n let released = false;\n return {\n sessionPath,\n release: async (): Promise<void> => {\n if (released) return;\n released = true;\n rmSync(lockPath, { force: true });\n },\n renew: (): void => {\n // A released lease does not own the lock. Re-stamping here would RE-CREATE the file and give\n // this process ownership it explicitly gave up — the one way this method could manufacture the\n // double-writer the lease exists to prevent.\n if (released) return;\n writeOwner(lockPath, ownerNow());\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { TheokitAgentError } from './chunk-IDCKSLYH.js';
1
+ import { TheokitAgentError } from './chunk-ZZMZWBR6.js';
2
2
 
3
3
  // src/internal/judge/parse-verdict.ts
4
4
  var DONE_PREFIX = "DONE:";
@@ -121,5 +121,5 @@ Be strict. If unclear, prefer CONTINUE.`;
121
121
  }
122
122
 
123
123
  export { JudgeCredentialError, composeJudgePrompt, judgeCallImpl };
124
- //# sourceMappingURL=chunk-T73NA43R.js.map
125
- //# sourceMappingURL=chunk-T73NA43R.js.map
124
+ //# sourceMappingURL=chunk-IPYYKCRC.js.map
125
+ //# sourceMappingURL=chunk-IPYYKCRC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/judge/parse-verdict.ts","../src/internal/judge/judge-call.ts"],"names":[],"mappings":"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","file":"chunk-T73NA43R.js","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 * in a row.\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 // in a row.\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":[],"mappings":"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","file":"chunk-IPYYKCRC.js","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 * in a row.\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 // in a row.\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,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkGHX4P3V2_cjs = require('./chunk-GHX4P3V2.cjs');
4
- var chunkEHJEZAOO_cjs = require('./chunk-EHJEZAOO.cjs');
4
+ var chunk6RKUO7AW_cjs = require('./chunk-6RKUO7AW.cjs');
5
5
  var chunkA4RAL2ER_cjs = require('./chunk-A4RAL2ER.cjs');
6
- var chunkK3FW2XZD_cjs = require('./chunk-K3FW2XZD.cjs');
6
+ var chunk4ZHTIJNE_cjs = require('./chunk-4ZHTIJNE.cjs');
7
7
  var crypto = require('crypto');
8
8
  var promises = require('fs/promises');
9
9
 
@@ -99,7 +99,7 @@ function computeTermOverlapScore(query, text) {
99
99
  var VALID_BACKENDS = ["sqlite-vec", "lance"];
100
100
  function assertValidBackend(backend) {
101
101
  if (!VALID_BACKENDS.includes(backend)) {
102
- throw new chunkK3FW2XZD_cjs.ConfigurationError(
102
+ throw new chunk4ZHTIJNE_cjs.ConfigurationError(
103
103
  `Invalid memory backend "${backend}". Valid values: ${VALID_BACKENDS.join(", ")}.`,
104
104
  { code: "invalid_memory_backend" }
105
105
  );
@@ -107,12 +107,12 @@ function assertValidBackend(backend) {
107
107
  }
108
108
  async function openLanceIndex(opts) {
109
109
  if (opts.embedding === void 0) {
110
- throw new chunkK3FW2XZD_cjs.ConfigurationError(
110
+ throw new chunk4ZHTIJNE_cjs.ConfigurationError(
111
111
  "Lance backend requires an embedding runtime. Pass `embedding: { provider, model, apiKey }` in Memory.create options (or omit `backend` to use SQLite default).",
112
112
  { code: "lance_requires_embedding" }
113
113
  );
114
114
  }
115
- const lance = await chunkEHJEZAOO_cjs.LanceIndex.open({
115
+ const lance = await chunk6RKUO7AW_cjs.LanceIndex.open({
116
116
  cwd: opts.cwd,
117
117
  embedding: opts.embedding,
118
118
  ...opts.filePath !== void 0 ? { storagePath: opts.filePath } : {}
@@ -131,7 +131,7 @@ async function loadSqliteVecExtension(db) {
131
131
  loadFn(db);
132
132
  } catch (cause) {
133
133
  const message = cause instanceof Error ? cause.message : String(cause);
134
- throw new chunkK3FW2XZD_cjs.ConfigurationError(
134
+ throw new chunk4ZHTIJNE_cjs.ConfigurationError(
135
135
  `sqlite-vec extension unavailable. Install \`sqlite-vec\` and ensure the native binary matches your Node + OS. Cause: ${message}`,
136
136
  { code: "sqlite_vec_unavailable", cause }
137
137
  );
@@ -308,9 +308,9 @@ var IndexManager = class _IndexManager {
308
308
  }
309
309
  /** Internal SQLite-path open. Renamed from previous public `open`. */
310
310
  static async openSqliteInternal(opts) {
311
- const memoryRoot = opts.memoryRoot ?? chunkEHJEZAOO_cjs.resolveMemoryRoot(opts.cwd);
312
- const filePath = opts.filePath ?? chunkEHJEZAOO_cjs.defaultIndexPath(chunkEHJEZAOO_cjs.projectMemoryDir(opts.cwd));
313
- const db = await chunkEHJEZAOO_cjs.openMemoryDb({ filePath });
311
+ const memoryRoot = opts.memoryRoot ?? chunk6RKUO7AW_cjs.resolveMemoryRoot(opts.cwd);
312
+ const filePath = opts.filePath ?? chunk6RKUO7AW_cjs.defaultIndexPath(chunk6RKUO7AW_cjs.projectMemoryDir(opts.cwd));
313
+ const db = await chunk6RKUO7AW_cjs.openMemoryDb({ filePath });
314
314
  const manager = new _IndexManager(memoryRoot, db, opts.embedding);
315
315
  if (opts.embedding !== void 0) await manager.initVectorBackend(opts.embedding);
316
316
  return manager;
@@ -332,13 +332,13 @@ var IndexManager = class _IndexManager {
332
332
  }
333
333
  /** Walk the memory corpus + (re)index changed files. */
334
334
  async sync() {
335
- const files = await chunkEHJEZAOO_cjs.collectMarkdownFiles(this.memoryRoot);
335
+ const files = await chunk6RKUO7AW_cjs.collectMarkdownFiles(this.memoryRoot);
336
336
  let filesUpdated = 0;
337
337
  let chunksWritten = 0;
338
338
  const existingByPath = this.loadFilesIndex();
339
339
  for (const entry of files) {
340
340
  const raw = await promises.readFile(entry.absolutePath, "utf8");
341
- const hash = chunkEHJEZAOO_cjs.sha256(raw);
341
+ const hash = chunk6RKUO7AW_cjs.sha256(raw);
342
342
  const existing = existingByPath.get(entry.absolutePath);
343
343
  if (existing !== void 0 && existing.hash === hash) continue;
344
344
  const stats = await promises.stat(entry.absolutePath);
@@ -416,7 +416,7 @@ var IndexManager = class _IndexManager {
416
416
  endLine,
417
417
  score: textScore,
418
418
  textScore,
419
- snippet: chunkEHJEZAOO_cjs.truncateSnippet(String(row.text ?? "")),
419
+ snippet: chunk6RKUO7AW_cjs.truncateSnippet(String(row.text ?? "")),
420
420
  source: String(row.source),
421
421
  citation: `${path}:${startLine}-${endLine}`,
422
422
  // sneak the chunk id through for the hybrid join below
@@ -454,7 +454,7 @@ var IndexManager = class _IndexManager {
454
454
  score: 0.5,
455
455
  // LIKE has no relevance score — use a neutral midpoint
456
456
  textScore: 0.5,
457
- snippet: chunkEHJEZAOO_cjs.truncateSnippet(String(row.text ?? "")),
457
+ snippet: chunk6RKUO7AW_cjs.truncateSnippet(String(row.text ?? "")),
458
458
  source: String(row.source),
459
459
  citation: `${path}:${startLine}-${endLine}`,
460
460
  chunkId: Number(row.id)
@@ -477,10 +477,10 @@ var IndexManager = class _IndexManager {
477
477
  return out;
478
478
  }
479
479
  combineHybridScores(textHits, vectorHitsById, options) {
480
- const weights = chunkEHJEZAOO_cjs.resolveWeights(options);
480
+ const weights = chunk6RKUO7AW_cjs.resolveWeights(options);
481
481
  const merged = this.mergeHits(textHits, vectorHitsById);
482
482
  const combined = [...merged.values()].map(
483
- (hit) => chunkEHJEZAOO_cjs.blendScores(hit, vectorHitsById.get(hit.chunkId)?.vectorScore ?? 0, weights)
483
+ (hit) => chunk6RKUO7AW_cjs.blendScores(hit, vectorHitsById.get(hit.chunkId)?.vectorScore ?? 0, weights)
484
484
  );
485
485
  return combined.sort((a, b) => b.score - a.score);
486
486
  }
@@ -516,7 +516,7 @@ var IndexManager = class _IndexManager {
516
516
  endLine,
517
517
  score: 0,
518
518
  textScore: 0,
519
- snippet: chunkEHJEZAOO_cjs.truncateSnippet(String(row.text ?? "")),
519
+ snippet: chunk6RKUO7AW_cjs.truncateSnippet(String(row.text ?? "")),
520
520
  source: String(row.source),
521
521
  citation: `${path}:${startLine}-${endLine}`
522
522
  };
@@ -550,5 +550,5 @@ var IndexManager = class _IndexManager {
550
550
  };
551
551
 
552
552
  exports.IndexManager = IndexManager;
553
- //# sourceMappingURL=chunk-UUP3MUZ6.cjs.map
554
- //# sourceMappingURL=chunk-UUP3MUZ6.cjs.map
553
+ //# sourceMappingURL=chunk-ITYQ6VBP.cjs.map
554
+ //# sourceMappingURL=chunk-ITYQ6VBP.cjs.map