@iowarp/clio-coder 0.4.8 → 0.4.9

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 (513) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +7 -6
  3. package/dist/{acp-JWPQZM2D.js → acp-ZWZCPS2A.js} +13 -13
  4. package/dist/{agents-4GCDIRVX.js → agents-LYSOWZUE.js} +53 -52
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-SYAKTDQE.js → auth-WBQGKWQ5.js} +27 -27
  7. package/dist/{background-OW67WMYR.js → background-BUMOBAQN.js} +4 -4
  8. package/dist/{builtins-TKS63VNV.js → builtins-ODRJOEDI.js} +7 -7
  9. package/dist/{chunk-JLBVCROW.js → chunk-2DRRZ57Q.js} +33 -24
  10. package/dist/chunk-2JBZYUPY.js +105 -0
  11. package/dist/chunk-2KUBKL63.js +336 -0
  12. package/dist/{chunk-K45LHSCI.js → chunk-2VBWFXYE.js} +7 -3
  13. package/dist/{chunk-XTLJDC2X.js → chunk-2X2R3UTH.js} +23 -18
  14. package/dist/{chunk-5HULHKT2.js → chunk-37LW32LZ.js} +5 -5
  15. package/dist/{chunk-W7KNI3F5.js → chunk-3AND2EFU.js} +5 -5
  16. package/dist/{chunk-36XSBPXL.js → chunk-3RDHGNU7.js} +95 -27
  17. package/dist/{chunk-BIY22UUK.js → chunk-3SUDBGEI.js} +11 -10
  18. package/dist/{chunk-3IQOZRQ6.js → chunk-3TQN67I2.js} +7 -7
  19. package/dist/{chunk-KWJXCL2X.js → chunk-3U4BL2EV.js} +3 -3
  20. package/dist/{chunk-RBES4TCZ.js → chunk-3UQADNG2.js} +3 -3
  21. package/dist/{chunk-V4ZKOKRB.js → chunk-434LRQ6Z.js} +61 -53
  22. package/dist/{chunk-MWMW53WW.js → chunk-4ATBOFRP.js} +649 -193
  23. package/dist/{chunk-QGIC5SX5.js → chunk-4DNL43EZ.js} +3 -3
  24. package/dist/{chunk-GVU4CXPJ.js → chunk-4HFJKRGZ.js} +113 -54
  25. package/dist/{chunk-5A6XR3RJ.js → chunk-555E2WVV.js} +16 -2
  26. package/dist/{chunk-LZDLYSUN.js → chunk-5RTEFNBP.js} +3 -3
  27. package/dist/{chunk-HMGAKB7E.js → chunk-64XJW7NT.js} +15 -15
  28. package/dist/{chunk-EXKUF426.js → chunk-7FBCRWVT.js} +280 -280
  29. package/dist/{chunk-K3W6BF5C.js → chunk-7JSOSVLP.js} +2 -2
  30. package/dist/{chunk-WFTSK7N3.js → chunk-7N3OLLSL.js} +4 -4
  31. package/dist/{chunk-JRA6OBHY.js → chunk-A6DULIMH.js} +17 -5
  32. package/dist/{chunk-R5NRX5N4.js → chunk-AB3QXXBC.js} +2 -2
  33. package/dist/chunk-ACCECAH4.js +49 -0
  34. package/dist/{chunk-GJBJHJNY.js → chunk-AM6CEP2K.js} +5 -5
  35. package/dist/{chunk-MUXNDNMZ.js → chunk-AY4XE6GN.js} +19 -13
  36. package/dist/{chunk-MATCRH3M.js → chunk-BBA2TAWV.js} +9 -9
  37. package/dist/{chunk-PMUUAABD.js → chunk-BMNVVKEW.js} +9 -9
  38. package/dist/{chunk-TOPICHGR.js → chunk-CTGZCEI6.js} +2 -2
  39. package/dist/{chunk-L75H5B3B.js → chunk-DIAJYJYG.js} +2 -2
  40. package/dist/{chunk-JIMWEYWO.js → chunk-EHZRAB4U.js} +2 -2
  41. package/dist/{chunk-YXANQGCX.js → chunk-EM2DIQAJ.js} +6 -6
  42. package/dist/{chunk-JCVIL6TY.js → chunk-ETF4ROKH.js} +4 -4
  43. package/dist/{chunk-5W4FRA5Q.js → chunk-F2YM3JY4.js} +483 -253
  44. package/dist/{chunk-YGIV24MF.js → chunk-FEBQWHJT.js} +21 -16
  45. package/dist/{chunk-PGEFOTJB.js → chunk-FPS4R2RN.js} +5 -4
  46. package/dist/{chunk-XC5POIL4.js → chunk-G5R53COU.js} +1 -1
  47. package/dist/chunk-GHXGMFQ5.js +40 -0
  48. package/dist/{chunk-QLKCCC2J.js → chunk-GJDRMBDI.js} +14 -14
  49. package/dist/{chunk-2ZWLMA3T.js → chunk-GREYLTZ5.js} +7 -7
  50. package/dist/{chunk-YXFKLMX2.js → chunk-H2WSCZZT.js} +22 -6
  51. package/dist/{chunk-FNVPQQYT.js → chunk-H6IUCY6Q.js} +2 -2
  52. package/dist/{chunk-5C2OQHLR.js → chunk-H72DCH3E.js} +92 -203
  53. package/dist/{chunk-JYKUMHCQ.js → chunk-H7TXJR23.js} +21 -21
  54. package/dist/chunk-HBJQQTKM.js +40 -0
  55. package/dist/chunk-HCVKSGNZ.js +990 -0
  56. package/dist/{chunk-ARRUMZ7X.js → chunk-I47EJPYL.js} +3 -3
  57. package/dist/{chunk-YKIIWURD.js → chunk-IALGWTLO.js} +3 -3
  58. package/dist/{chunk-FB3KTE2J.js → chunk-IALZJE37.js} +4 -4
  59. package/dist/{chunk-RAUOKUZV.js → chunk-IC7JXLOM.js} +15 -10
  60. package/dist/{chunk-LLCGGVSW.js → chunk-IOSVTTCD.js} +7 -7
  61. package/dist/{chunk-DBREN25X.js → chunk-IPTPMLA6.js} +8 -3
  62. package/dist/{chunk-V552WL53.js → chunk-IZZI3SKQ.js} +3 -67
  63. package/dist/{chunk-UDAK6GR2.js → chunk-JIYWMQZ5.js} +2 -2
  64. package/dist/{chunk-O3HSB2YL.js → chunk-JKU34ACI.js} +2 -2
  65. package/dist/{chunk-6X7VOCDY.js → chunk-JTR3VSV3.js} +3 -3
  66. package/dist/{chunk-O2QJDRWP.js → chunk-JU6MAMRJ.js} +4 -4
  67. package/dist/chunk-JURMQWVJ.js +15 -0
  68. package/dist/{chunk-STC6CWUZ.js → chunk-K3XN4LZ4.js} +5 -5
  69. package/dist/{chunk-ATB3Q7YO.js → chunk-KIVFXGBM.js} +27 -2
  70. package/dist/{chunk-WJWNZXRM.js → chunk-KOAHT3N2.js} +2 -2
  71. package/dist/chunk-KP5BPXR3.js +73 -0
  72. package/dist/{chunk-K53E2PTW.js → chunk-KRJQG47M.js} +3 -3
  73. package/dist/{chunk-IILXVWJY.js → chunk-KW6OJ7IY.js} +19 -21
  74. package/dist/{chunk-A46ILRS2.js → chunk-L3NBWLSN.js} +4 -4
  75. package/dist/{chunk-2U2U5POA.js → chunk-L4RUTLRL.js} +5 -5
  76. package/dist/{chunk-QYSAZMSH.js → chunk-LGTVWPRN.js} +2 -2
  77. package/dist/{chunk-KGP3UQGJ.js → chunk-LSQW6KZO.js} +5 -5
  78. package/dist/{chunk-P4JS3MH5.js → chunk-LWJYOHT3.js} +8 -8
  79. package/dist/{chunk-U3TPUXOQ.js → chunk-M6YLF66Z.js} +74 -40
  80. package/dist/{chunk-5D2FNBMG.js → chunk-MJ4QEJVT.js} +2 -2
  81. package/dist/{chunk-RM3XJX7P.js → chunk-MO65K5CH.js} +5 -5
  82. package/dist/{chunk-H5DPSUBB.js → chunk-MTOZEQHL.js} +3 -3
  83. package/dist/{chunk-6R5MBXBJ.js → chunk-NBPA5QM6.js} +2 -2
  84. package/dist/{chunk-H77XBUBM.js → chunk-NMBEW4PP.js} +3 -3
  85. package/dist/{chunk-QWUSS2QP.js → chunk-NPLLK3HO.js} +67 -87
  86. package/dist/{chunk-K5UJD6MM.js → chunk-NSYGPIVW.js} +5 -5
  87. package/dist/{context-EZZPT2ZV.js → chunk-NXTV5OI5.js} +30 -84
  88. package/dist/{chunk-F7Y6QTPL.js → chunk-NYDG4QP4.js} +4 -4
  89. package/dist/{chunk-PFZJMXIY.js → chunk-OA2GDACN.js} +2 -2
  90. package/dist/{chunk-Q32C6AWI.js → chunk-PJI57EC6.js} +4 -4
  91. package/dist/{chunk-EFXMQYI6.js → chunk-PJJBUGMR.js} +52 -52
  92. package/dist/{chunk-ICYIUNO4.js → chunk-PNJE7JPS.js} +18 -3
  93. package/dist/chunk-PP23PZEV.js +1347 -0
  94. package/dist/chunk-PPIFDSBK.js +409 -0
  95. package/dist/{chunk-RFULVPBX.js → chunk-PTFISXQO.js} +6 -5
  96. package/dist/{chunk-JV25I7ID.js → chunk-QB33Z5S7.js} +5 -5
  97. package/dist/{chunk-D55WO5J7.js → chunk-QGNII5VC.js} +8 -8
  98. package/dist/{chunk-RKFNU5UA.js → chunk-QI72X67S.js} +104 -27
  99. package/dist/chunk-QKHZZFKH.js +98 -0
  100. package/dist/{chunk-5UG6EZI3.js → chunk-RDKX2PME.js} +4 -4
  101. package/dist/{chunk-B5ZTR6KZ.js → chunk-RL7LJYN4.js} +1 -1
  102. package/dist/{chunk-BE4I3UJ2.js → chunk-RQROVOV3.js} +15 -13
  103. package/dist/{chunk-TEHVMHOK.js → chunk-S34A7BSP.js} +3 -3
  104. package/dist/{chunk-4P6M2TTE.js → chunk-SJETVJHX.js} +2 -2
  105. package/dist/{chunk-OPC4DQD6.js → chunk-SJMNU4ZS.js} +2 -2
  106. package/dist/{chunk-2JSCCEKO.js → chunk-SLLG2G44.js} +2 -2
  107. package/dist/{chunk-EPYATQMG.js → chunk-SQAZN2V3.js} +42 -16
  108. package/dist/{chunk-E2NSPYLC.js → chunk-SQBT46OF.js} +5 -5
  109. package/dist/{chunk-WTZVSMQZ.js → chunk-TCUOGIL2.js} +5 -5
  110. package/dist/{chunk-RRZFRIJP.js → chunk-TIWPKCVO.js} +5 -5
  111. package/dist/{chunk-QLAGXTTV.js → chunk-UIU7MGZ3.js} +5 -1
  112. package/dist/{chunk-WLHX3MM5.js → chunk-V4XJWDHR.js} +21 -11
  113. package/dist/{chunk-PJ5H5B7A.js → chunk-VPWGADBG.js} +18 -18
  114. package/dist/{chunk-GEYX2NZ5.js → chunk-VWFCAK34.js} +6 -6
  115. package/dist/{chunk-44U2OUSM.js → chunk-W72LLZAV.js} +2 -2
  116. package/dist/{chunk-ORRNGQDV.js → chunk-WGRDABW7.js} +4 -4
  117. package/dist/{chunk-RNLAPBJL.js → chunk-XFLSL7BT.js} +2 -2
  118. package/dist/{chunk-2SXGG2IV.js → chunk-XJZEDCNT.js} +6 -6
  119. package/dist/{chunk-UB33F3YW.js → chunk-XLLEL3K7.js} +5 -5
  120. package/dist/{chunk-2GPGAE5W.js → chunk-XNIZBOFV.js} +15 -1
  121. package/dist/{chunk-WS4FOGLY.js → chunk-XNZWWDC3.js} +2 -2
  122. package/dist/{chunk-P7GTE4FX.js → chunk-XRECG65F.js} +2 -2
  123. package/dist/chunk-XTQ7MEJG.js +457 -0
  124. package/dist/{chunk-GEVQNST4.js → chunk-XZWSP67B.js} +2 -2
  125. package/dist/{chunk-OEMFEL26.js → chunk-Y3M2T5RO.js} +3 -3
  126. package/dist/{chunk-HP7KJ5O2.js → chunk-YPQOPFP7.js} +6 -6
  127. package/dist/{chunk-MEHBW2SP.js → chunk-YQYINEJI.js} +6 -6
  128. package/dist/{chunk-RXI7K4J5.js → chunk-ZF6CV4WZ.js} +1338 -217
  129. package/dist/{chunk-3XE4NRLF.js → chunk-ZKJU55MX.js} +4 -2
  130. package/dist/{chunk-526ABLHD.js → chunk-ZKQ63XZ5.js} +12 -35
  131. package/dist/{chunk-PAQVETLA.js → chunk-ZKVMDNAN.js} +5 -5
  132. package/dist/cli/index.js +47 -37
  133. package/dist/{clio-BPBAPZGA.js → clio-JUDCLNHC.js} +7 -7
  134. package/dist/clio-context-tools-FT672MNZ.js +136 -0
  135. package/dist/{code-nav-KBQ3RIFK.js → code-nav-7OVCTP2Z.js} +28 -26
  136. package/dist/{components-6DKAXWFN.js → components-OZXZMDWU.js} +5 -5
  137. package/dist/{config-FWL5JK2H.js → config-P3ZQVF63.js} +80 -75
  138. package/dist/{config-graph-ACDB26L2.js → config-graph-GKAOBAS5.js} +74 -70
  139. package/dist/{configure-ZIZ4YV62.js → configure-25LRYZJG.js} +45 -44
  140. package/dist/context-72VQV7JX.js +73 -0
  141. package/dist/{context-VIL5MFRH.js → context-COWRWXP3.js} +74 -70
  142. package/dist/{context-Z3GOBHDK.js → context-WB3D474G.js} +12 -12
  143. package/dist/{context-clear-ZPB3L7MN.js → context-clear-Y4LYMWC6.js} +70 -66
  144. package/dist/{context-working-set-2GLBVF7L.js → context-working-set-TMZYVC7A.js} +15 -14
  145. package/dist/data-tool-KMBWKKWR.js +2987 -0
  146. package/dist/{detail-VSBWYSGZ.js → detail-4GLROGY5.js} +71 -67
  147. package/dist/{dispatch-runner-EEEHGKX4.js → dispatch-runner-TR3LBP5P.js} +92 -88
  148. package/dist/{docs-VMMWFVHL.js → docs-ZK777W2G.js} +3 -3
  149. package/dist/{doctor-V5L4IQGP.js → doctor-WC4OV2OB.js} +61 -59
  150. package/dist/{eval-VJYJ2BV3.js → eval-FUNWJNUJ.js} +45 -45
  151. package/dist/{evals-J2H5V6DD.js → evals-L73EBHSU.js} +4 -4
  152. package/dist/{evidence-Y35YSXH6.js → evidence-AVJ2RTHX.js} +80 -76
  153. package/dist/{evidence-POWAEBEW.js → evidence-P4NZOAK7.js} +70 -66
  154. package/dist/{evidence-XCCCLS36.js → evidence-Y55WKYHH.js} +79 -75
  155. package/dist/{evolve-GPG72VB6.js → evolve-MAWKRYWF.js} +75 -71
  156. package/dist/{extensions-YIZ7W3DA.js → extensions-PYQH3H47.js} +8 -8
  157. package/dist/{fleet-2MDCQ2VK.js → fleet-M4H2QBWT.js} +84 -80
  158. package/dist/{fleet-W2AF3TOJ.js → fleet-ULBXXRDR.js} +123 -119
  159. package/dist/{fleet-commands-WS56G26H.js → fleet-commands-7NNC4JZU.js} +17 -16
  160. package/dist/{fleet-decisions-MYGZ3JW7.js → fleet-decisions-EHEY7KPU.js} +9 -9
  161. package/dist/{fleet-graph-ELTBXBAV.js → fleet-graph-LQ2AHZT3.js} +15 -15
  162. package/dist/{fleet-inspect-45IK73L4.js → fleet-inspect-5IGBIIYZ.js} +76 -72
  163. package/dist/{fleet-preflight-T5NCZKHE.js → fleet-preflight-UTTNOKMG.js} +7 -2
  164. package/dist/{fleet-validate-VG4JBLA4.js → fleet-validate-YGILJKA6.js} +20 -20
  165. package/dist/{fleet-verify-VTSABAYO.js → fleet-verify-AUWTCDKR.js} +70 -66
  166. package/dist/{fleet-view-XC7WMKSS.js → fleet-view-TGUFCY5A.js} +73 -69
  167. package/dist/{init-VTJNNTIP.js → init-GFN2Z7NF.js} +94 -90
  168. package/dist/{interop-IU6BNA7C.js → interop-EF5JUWWK.js} +21 -21
  169. package/dist/{inventory-QPQUTUUZ.js → inventory-I6VJDDYF.js} +73 -69
  170. package/dist/{library-Q3NWL5CM.js → library-3J5IO2OJ.js} +13 -13
  171. package/dist/{library-LF7DLU6S.js → library-IJTOFTDS.js} +16 -15
  172. package/dist/{library-HUTAS57T.js → library-Z3KZSHPE.js} +27 -27
  173. package/dist/{library-import-PPW4YUC5.js → library-import-IMKFLDHX.js} +21 -21
  174. package/dist/{library-inventory-DIRVFRZU.js → library-inventory-CQP5D6GU.js} +13 -13
  175. package/dist/{library-validation-XHUZ3INE.js → library-validation-33UTX7AI.js} +11 -11
  176. package/dist/mcp-5FWZKUS6.js +26 -0
  177. package/dist/{memory-Z2LSKNV6.js → memory-36CWN67Y.js} +75 -71
  178. package/dist/{models-3FTREYPY.js → models-KQ6PQJJQ.js} +49 -49
  179. package/dist/{monitor-HF2MQRB3.js → monitor-XX6ZD6KC.js} +83 -79
  180. package/dist/{orchestrator-UYPA6W4S.js → orchestrator-SK6HDAOP.js} +1337 -772
  181. package/dist/{panes-736XB73K.js → panes-L27QRXUH.js} +12 -12
  182. package/dist/{panes-C4P3PIWT.js → panes-LXZIWNOZ.js} +4 -4
  183. package/dist/{paths-JQ4QYIYB.js → paths-7DJDU4P4.js} +5 -5
  184. package/dist/{preload-XSLRYH3Y.js → preload-K2TDYI4Y.js} +70 -66
  185. package/dist/{reset-V7QSPZYE.js → reset-73VU3XNT.js} +10 -10
  186. package/dist/{resources-OJGDFHYT.js → resources-3BYJYVYL.js} +16 -16
  187. package/dist/{run-T354SMDW.js → run-AR7PY6GW.js} +132 -128
  188. package/dist/run-script-H2KUDIII.js +41 -0
  189. package/dist/{settings-MWZXO6RV.js → settings-3VMBMWID.js} +4 -4
  190. package/dist/{share-Y5ZKJPND.js → share-HVWH4FPF.js} +21 -21
  191. package/dist/{skills-4GLZC4LN.js → skills-5TYEY75Q.js} +22 -22
  192. package/dist/{skills-eval-L5T7GODM.js → skills-eval-CK3MD63Z.js} +96 -87
  193. package/dist/{skills-inventory-LASKXEZI.js → skills-inventory-KSFYG3I6.js} +16 -16
  194. package/dist/{slash-commands-OIMRU36D.js → slash-commands-DMVHML7P.js} +47 -44
  195. package/dist/{steer-KMAM2Y2C.js → steer-XCD6V3EW.js} +4 -4
  196. package/dist/{support-AWC3XKPX.js → support-JAZL6VBR.js} +7 -7
  197. package/dist/{system-CD75NYBU.js → system-3TDQRMZZ.js} +26 -26
  198. package/dist/{targets-VSZX2J2S.js → targets-EZKHAYRN.js} +66 -65
  199. package/dist/{tasks-AN567LYH.js → tasks-SNEFHWYW.js} +12 -12
  200. package/dist/{terminal-lease-6KTWNHVY.js → terminal-lease-BNLFA4OF.js} +8 -7
  201. package/dist/{tools-AGRK3V5J.js → tools-7IM6BDCE.js} +12 -12
  202. package/dist/{trace-P4MQZD33.js → trace-C3MBGD4S.js} +8 -8
  203. package/dist/{tui-primitives-A6MUW32V.js → tui-primitives-36BY2774.js} +2 -2
  204. package/dist/{uninstall-BRUTEPIC.js → uninstall-WWLOSZ42.js} +10 -10
  205. package/dist/{upgrade-SBCGHQGJ.js → upgrade-ETK2JNSJ.js} +37 -47
  206. package/dist/{usage-BIDVTO6V.js → usage-S2HOB7BZ.js} +93 -89
  207. package/dist/{verifiers-LTGCAMXS.js → verifiers-QDTEVH7R.js} +24 -22
  208. package/dist/{verify-OZU4LLNP.js → verify-SUKUK6XM.js} +15 -14
  209. package/dist/web/client/assets/{abnfDiagram-VCTEODGH-C5wfoHm3.js → abnfDiagram-VCTEODGH-BznNOe_I.js} +1 -1
  210. package/dist/web/client/assets/{arc-oH91sfLl.js → arc-Pry-7G1_.js} +1 -1
  211. package/dist/web/client/assets/architecture-7GRP2DOG-BQO9DL_y.js +1 -0
  212. package/dist/web/client/assets/architectureDiagram-5GKGNRK7-CKPbdvyx.js +36 -0
  213. package/dist/web/client/assets/{blockDiagram-I7D4REHJ-wi0_CYHL.js → blockDiagram-I7D4REHJ-prINbpbM.js} +1 -1
  214. package/dist/web/client/assets/{c4Diagram-7LVT6UL2-DV7NYc2W.js → c4Diagram-7LVT6UL2-DkVRKtyU.js} +1 -1
  215. package/dist/web/client/assets/channel-c4EeUUqA.js +1 -0
  216. package/dist/web/client/assets/{chunk-4HAMMTFA-Bhsegh3F.js → chunk-4HAMMTFA-CV0Z1ApC.js} +1 -1
  217. package/dist/web/client/assets/{chunk-75Z2AOVW-RQCckcQG.js → chunk-75Z2AOVW-D34Nohxq.js} +1 -1
  218. package/dist/web/client/assets/chunk-DU6HZSFF-Das_cS-O.js +125 -0
  219. package/dist/web/client/assets/{chunk-F27PBJKO-BcNqv2x1.js → chunk-F27PBJKO-CmRojxkG.js} +1 -1
  220. package/dist/web/client/assets/{chunk-GMAD6QVW-Bq8JWiZT.js → chunk-GMAD6QVW-ClYZmc73.js} +1 -1
  221. package/dist/web/client/assets/{chunk-GVQU2GXP-DbchrAeJ.js → chunk-GVQU2GXP-B2BiHFt-.js} +1 -1
  222. package/dist/web/client/assets/{chunk-IMKFNOWR-DrpvjocK.js → chunk-IMKFNOWR-BGApZ8nP.js} +1 -1
  223. package/dist/web/client/assets/{chunk-L3NEJ4N5-DHLmTnCe.js → chunk-L3NEJ4N5-DIzCgpkY.js} +1 -1
  224. package/dist/web/client/assets/{chunk-OSK3NFVY-CsRbtN9n.js → chunk-OSK3NFVY-BPT-yePv.js} +1 -1
  225. package/dist/web/client/assets/{chunk-P2QGCYS3-aqPbxoMh.js → chunk-P2QGCYS3-BC1WTc2t.js} +1 -1
  226. package/dist/web/client/assets/{chunk-POPQ4Y6H-Dl5vw0h0.js → chunk-POPQ4Y6H-BSjzBmZU.js} +1 -1
  227. package/dist/web/client/assets/{chunk-PWAF6VOD-D7OBRSdO.js → chunk-PWAF6VOD-BIjLs86Z.js} +1 -1
  228. package/dist/web/client/assets/{chunk-SHT3W25Y-CdKCIwuB.js → chunk-SHT3W25Y-CduDz2cm.js} +1 -1
  229. package/dist/web/client/assets/{chunk-SVP7TREG-B94Nu_1-.js → chunk-SVP7TREG-XUn4pPAP.js} +1 -1
  230. package/dist/web/client/assets/{chunk-TICWLB2K-jjRQI_hk.js → chunk-TICWLB2K-DcBy2DKy.js} +1 -1
  231. package/dist/web/client/assets/{chunk-XXDRQBXY-azkbKE37.js → chunk-XXDRQBXY-DqAYeljN.js} +1 -1
  232. package/dist/web/client/assets/classDiagram-ZZMXUADV-C0HR7YKN.js +1 -0
  233. package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-C0HR7YKN.js +1 -0
  234. package/dist/web/client/assets/cose-bilkent-JH36ORCC-CVh1icwb.js +1 -0
  235. package/dist/web/client/assets/{cynefin-OW5HDTMX--i9RDU5f.js → cynefin-OW5HDTMX-BecPbyM6.js} +1 -1
  236. package/dist/web/client/assets/{cynefinDiagram-5FMLGOSQ-cRcvyBdp.js → cynefinDiagram-5FMLGOSQ-BzMzFwyT.js} +1 -1
  237. package/dist/web/client/assets/{dagre-GXQ25YYZ-C9mZmAPe.js → dagre-GXQ25YYZ-CAWJmRMG.js} +1 -1
  238. package/dist/web/client/assets/{diagram-S7CK7UJ4-Oj_zlLDY.js → diagram-S7CK7UJ4--lp0ALmn.js} +1 -1
  239. package/dist/web/client/assets/{diagram-UQ7AKVKN-BWKbbfcR.js → diagram-UQ7AKVKN-gKUnsjcj.js} +1 -1
  240. package/dist/web/client/assets/{diagram-VSXAHHWV-C3N3f7DO.js → diagram-VSXAHHWV-BoN5D1Gh.js} +1 -1
  241. package/dist/web/client/assets/{diagram-VX7I27RA-CtDKDAWe.js → diagram-VX7I27RA-BMtuswQ6.js} +1 -1
  242. package/dist/web/client/assets/{diagram-Z3DM3KII-B8bkBRCz.js → diagram-Z3DM3KII-DtU8LXrA.js} +1 -1
  243. package/dist/web/client/assets/dist-v5Q1xZ2K.js +1 -0
  244. package/dist/web/client/assets/{ebnfDiagram-PWID7BFC-CskW-Whx.js → ebnfDiagram-PWID7BFC-DDcnv9BH.js} +1 -1
  245. package/dist/web/client/assets/{erDiagram-RLTQ6QDP-C-83cgCW.js → erDiagram-RLTQ6QDP-DvRb4iDL.js} +1 -1
  246. package/dist/web/client/assets/eventmodeling-NTZA5JFV-01h4SbTc.js +1 -0
  247. package/dist/web/client/assets/flowDiagram-HODETNUW-Bll_YZ6T.js +1 -0
  248. package/dist/web/client/assets/framework-YGoR34M4.js +9 -0
  249. package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-DdFZ9hvH.js +292 -0
  250. package/dist/web/client/assets/{gitGraph-4MIJSDKK-B40cQtBE.js → gitGraph-4MIJSDKK-DjD_AAMj.js} +1 -1
  251. package/dist/web/client/assets/{gitGraphDiagram-WWUBYQGX-CH-7P7Di.js → gitGraphDiagram-WWUBYQGX-CDyN9n2u.js} +1 -1
  252. package/dist/web/client/assets/index-CVcy0FAn.js +75 -0
  253. package/dist/web/client/assets/{info-A6RAGUB7-eCCE9D_Q.js → info-A6RAGUB7-C_XK8Hq0.js} +1 -1
  254. package/dist/web/client/assets/{infoDiagram-27XIBGKW-DRnvqMjM.js → infoDiagram-27XIBGKW-DIe4tJ1i.js} +1 -1
  255. package/dist/web/client/assets/{ishikawaDiagram-5VMMS53U-DBA_55wp.js → ishikawaDiagram-5VMMS53U-CVl5unKI.js} +1 -1
  256. package/dist/web/client/assets/{journeyDiagram-3NMN7TZE-C-ewpNl7.js → journeyDiagram-3NMN7TZE-CPEB1Vt8.js} +1 -1
  257. package/dist/web/client/assets/{kanban-definition-UXKFOSKX-B1taGsu0.js → kanban-definition-UXKFOSKX-j-9WXjYQ.js} +1 -1
  258. package/dist/web/client/assets/{line-lZ0kyxDI.js → line-BIDAcEtX.js} +1 -1
  259. package/dist/web/client/assets/{linear-DIc8FgZj.js → linear-SzVmGSw4.js} +1 -1
  260. package/dist/web/client/assets/{mermaid-parser.core-SXolxpXw.js → mermaid-parser.core-Ux4vssh0.js} +3 -3
  261. package/dist/web/client/assets/{mermaid.core-DXgQtkNg.js → mermaid.core-CDuwVKeh.js} +4 -4
  262. package/dist/web/client/assets/{mindmap-definition-YA3MSWOX-DXJas9K7.js → mindmap-definition-YA3MSWOX-5MlQEeQ6.js} +1 -1
  263. package/dist/web/client/assets/{packet-AYTQ26CC-OqVl_07o.js → packet-AYTQ26CC-DRO66c9Q.js} +1 -1
  264. package/dist/web/client/assets/{pegDiagram-XKGWAZYB-CPE_zdfN.js → pegDiagram-XKGWAZYB-BakZnSjM.js} +1 -1
  265. package/dist/web/client/assets/{pie-WAS4IAKB-DAxnMBLe.js → pie-WAS4IAKB-BEzcmhz8.js} +1 -1
  266. package/dist/web/client/assets/{pieDiagram-E7YTZNPT-Uwjpufxv.js → pieDiagram-E7YTZNPT-DpmqgoPt.js} +1 -1
  267. package/dist/web/client/assets/prism-core-BtsZdCS6.js +1 -0
  268. package/dist/web/client/assets/{quadrantDiagram-AXDQQJYC-Bjs4yjgs.js → quadrantDiagram-AXDQQJYC-DuMPwwV2.js} +1 -1
  269. package/dist/web/client/assets/{radar-RG4KPBEZ-CuCKjHM3.js → radar-RG4KPBEZ-Djm3oXl5.js} +1 -1
  270. package/dist/web/client/assets/{railroad-74A4TZTK-CnQK-Cc1.js → railroad-74A4TZTK-Cpvkwsao.js} +1 -1
  271. package/dist/web/client/assets/railroad-abnf-HS5TGJTU-CpRQ3ruG.js +1 -0
  272. package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-1Rm5a5eJ.js +1 -0
  273. package/dist/web/client/assets/railroad-peg-WCYAUIDC-C8344cTh.js +1 -0
  274. package/dist/web/client/assets/{railroadDiagram-O6MQD6OU-BDDIPNLL.js → railroadDiagram-O6MQD6OU-CgMS4B6R.js} +1 -1
  275. package/dist/web/client/assets/{requirementDiagram-BXWQKSXE-B5Ydyxc1.js → requirementDiagram-BXWQKSXE-CX5eoNdN.js} +1 -1
  276. package/dist/web/client/assets/rolldown-runtime-hePW80VL.js +1 -0
  277. package/dist/web/client/assets/{sankeyDiagram-P5KCCOFB-Bw2Qym75.js → sankeyDiagram-P5KCCOFB-CODBX2N3.js} +1 -1
  278. package/dist/web/client/assets/{sequenceDiagram-WJ2MYXX4-BZf36nO0.js → sequenceDiagram-WJ2MYXX4-eUhhF8OR.js} +1 -1
  279. package/dist/web/client/assets/src-B6xuSHsQ.js +1 -0
  280. package/dist/web/client/assets/{stateDiagram-D77RDMKH-DnH6JRkp.js → stateDiagram-D77RDMKH-vR6l0NC2.js} +1 -1
  281. package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-BYUm8jLY.js +1 -0
  282. package/dist/web/client/assets/{swimlanes-42K2YHIH-D9PZmtZg.js → swimlanes-42K2YHIH-v9Tw_zX7.js} +1 -1
  283. package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-B2jdCvhD.js +8 -0
  284. package/dist/web/client/assets/{timeline-definition-24CTP7MA-CrkZZBo7.js → timeline-definition-24CTP7MA-DxianbhU.js} +1 -1
  285. package/dist/web/client/assets/{treeView-Q6P3EWNA-C9zDShoC.js → treeView-Q6P3EWNA-DLxnZb9R.js} +1 -1
  286. package/dist/web/client/assets/{treemap-WGGIJYW6-B-plj9yK.js → treemap-WGGIJYW6-I7UnZ6xJ.js} +1 -1
  287. package/dist/web/client/assets/{vennDiagram-4TSXK5OY-VeOdaphN.js → vennDiagram-4TSXK5OY-Ar7M8Mrj.js} +1 -1
  288. package/dist/web/client/assets/{wardley-WFR3VGLG-Wl1P_m6L.js → wardley-WFR3VGLG-B8ZSHeDu.js} +1 -1
  289. package/dist/web/client/assets/{wardleyDiagram-VM6X3IG4-B85-R9t2.js → wardleyDiagram-VM6X3IG4-Qks2FmIn.js} +1 -1
  290. package/dist/web/client/assets/{xychartDiagram-S5SC5T6Z-BrD8OW8m.js → xychartDiagram-S5SC5T6Z-BBBjRtiE.js} +1 -1
  291. package/dist/web/client/index.html +3 -1
  292. package/dist/web/reads-worker.js +17 -17
  293. package/dist/web/server.js +25 -25
  294. package/dist/{web-EU6ATJWA.js → web-EMGUGGEI.js} +3 -3
  295. package/dist/{web-fetch-U22Q7HV4.js → web-fetch-6BRRVQON.js} +22 -5
  296. package/dist/{wiki-generate-LZRLFG6I.js → wiki-generate-XHSJTDOP.js} +104 -100
  297. package/dist/{with-panes-X5CZOGML.js → with-panes-Z6BTEHMZ.js} +11 -11
  298. package/dist/worker/entry.js +101 -87
  299. package/docs/README.md +1 -1
  300. package/docs/architecture/artifact-placement.md +12 -1
  301. package/docs/architecture/library.md +1 -1
  302. package/docs/architecture/prompt-envelope-and-tools.md +37 -27
  303. package/docs/architecture/safety-model.md +19 -6
  304. package/docs/architecture/session-lifecycle.md +10 -4
  305. package/docs/architecture/tui-design.md +37 -14
  306. package/docs/guide/commands-and-modes.md +39 -19
  307. package/docs/guide/configuration-and-targets.md +7 -5
  308. package/docs/guide/configuration-reference.md +116 -13
  309. package/docs/guide/environment-variables.md +2 -5
  310. package/docs/guide/fleet-dispatch.md +3 -2
  311. package/docs/guide/glossary.md +1 -1
  312. package/docs/guide/installation-and-lifecycle.md +2 -2
  313. package/docs/guide/resource-library.md +1 -1
  314. package/docs/guide/tool-usage.md +166 -64
  315. package/docs/guide/troubleshooting.md +1 -1
  316. package/docs/process/documentation-guide.md +1 -0
  317. package/docs/process/fleet-demo-runbook.md +6 -6
  318. package/docs/process/scientific-validation.md +22 -2
  319. package/docs/process/tool-audit-v0.4.9.md +357 -0
  320. package/package.json +1 -1
  321. package/src/cli/argv.ts +31 -0
  322. package/src/cli/clio.ts +17 -13
  323. package/src/cli/config.ts +4 -0
  324. package/src/cli/doctor-panes.ts +1 -1
  325. package/src/cli/doctor.ts +3 -1
  326. package/src/cli/index.ts +14 -1
  327. package/src/cli/mcp.ts +106 -0
  328. package/src/cli/skills-eval.ts +8 -2
  329. package/src/cli/upgrade.ts +34 -38
  330. package/src/cli/verifiers.ts +1 -1
  331. package/src/core/bash-exec.ts +27 -4
  332. package/src/core/config.ts +2 -1
  333. package/src/core/defaults.ts +10 -1
  334. package/src/core/event-bus.ts +1 -1
  335. package/src/core/run-records.ts +438 -0
  336. package/src/core/safe-exec.ts +506 -56
  337. package/src/core/skill-activation.ts +5 -3
  338. package/src/core/termination.ts +3 -3
  339. package/src/core/tool-names.ts +33 -12
  340. package/src/domains/context/clio-md.ts +1 -1
  341. package/src/domains/context/worker/pressure.ts +6 -2
  342. package/src/domains/context/working-set/path-index.ts +7 -2
  343. package/src/domains/dispatch/code-step.ts +7 -2
  344. package/src/domains/dispatch/contract.ts +7 -0
  345. package/src/domains/dispatch/execution-role.ts +3 -3
  346. package/src/domains/dispatch/execution-scheduler.ts +204 -79
  347. package/src/domains/dispatch/extension.ts +131 -14
  348. package/src/domains/dispatch/fleet-preflight.ts +18 -3
  349. package/src/domains/dispatch/fleet-run.ts +1 -0
  350. package/src/domains/dispatch/gate-decisions.ts +3 -3
  351. package/src/domains/dispatch/heartbeat.ts +1 -1
  352. package/src/domains/dispatch/host-verification.ts +16 -14
  353. package/src/domains/dispatch/receipt-findings.ts +3 -4
  354. package/src/domains/dispatch/reservation-store.ts +7 -9
  355. package/src/domains/dispatch/route-facts.ts +1 -1
  356. package/src/domains/dispatch/state.ts +3 -3
  357. package/src/domains/dispatch/types.ts +1 -1
  358. package/src/domains/evidence/build.ts +35 -7
  359. package/src/domains/gateway/mcp/client.ts +923 -0
  360. package/src/domains/gateway/mcp/config.ts +456 -0
  361. package/src/domains/gateway/mcp/index.ts +88 -0
  362. package/src/domains/gateway/mcp/protocol.ts +273 -0
  363. package/src/domains/gateway/mcp/trust.ts +263 -0
  364. package/src/domains/lifecycle/doctor.ts +17 -3
  365. package/src/domains/mux/contract.ts +5 -9
  366. package/src/domains/mux/detect.ts +1 -1
  367. package/src/domains/mux/extension.ts +1 -1
  368. package/src/domains/mux/protocol.ts +5 -7
  369. package/src/domains/mux/socket-client.ts +3 -3
  370. package/src/domains/observability/accountability.ts +2 -2
  371. package/src/domains/observability/contract.ts +7 -3
  372. package/src/domains/observability/evidence-index.ts +2 -2
  373. package/src/domains/observability/projection.ts +10 -0
  374. package/src/domains/prompts/compiler.ts +41 -23
  375. package/src/domains/prompts/fragments/identity/docs-routing.md +2 -2
  376. package/src/domains/prompts/fragments/operating/skills.md +12 -10
  377. package/src/domains/providers/index.ts +5 -1
  378. package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +42 -10
  379. package/src/domains/providers/probe/http.ts +44 -59
  380. package/src/domains/providers/probe/reasoning.ts +2 -0
  381. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +20 -31
  382. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +9 -4
  383. package/src/domains/safety/action-classifier.ts +17 -1
  384. package/src/domains/safety/call-target.ts +14 -0
  385. package/src/domains/safety/contract.ts +1 -1
  386. package/src/domains/safety/finish-contract.ts +9 -3
  387. package/src/domains/safety/loop-detector.ts +1 -1
  388. package/src/domains/safety/policy-engine.ts +7 -0
  389. package/src/domains/safety/protected-artifacts.ts +3 -4
  390. package/src/domains/safety/rejection-feedback.ts +1 -1
  391. package/src/domains/safety/scope.ts +2 -3
  392. package/src/domains/safety/validation-contract.ts +3 -2
  393. package/src/domains/scheduling/budget.ts +5 -3
  394. package/src/domains/scheduling/contract.ts +2 -1
  395. package/src/domains/scheduling/extension.ts +3 -3
  396. package/src/domains/session/compaction/branch-summary.ts +1 -1
  397. package/src/domains/session/compaction/compact.ts +25 -2
  398. package/src/domains/session/compaction/cut-point.ts +1 -1
  399. package/src/domains/session/compaction/tokens.ts +5 -10
  400. package/src/domains/session/contract.ts +1 -1
  401. package/src/domains/session/cwd-fallback.ts +2 -2
  402. package/src/domains/session/handoff.ts +5 -1
  403. package/src/domains/session/history.ts +6 -0
  404. package/src/domains/session/manager.ts +14 -7
  405. package/src/domains/session/retry.ts +2 -1
  406. package/src/domains/session/session-artifacts.ts +12 -7
  407. package/src/domains/session/task-board.ts +2 -3
  408. package/src/engine/apis/openai-completions.ts +8 -0
  409. package/src/engine/loop-guard.ts +7 -0
  410. package/src/engine/provider-error-body.ts +65 -0
  411. package/src/engine/session.ts +118 -29
  412. package/src/engine/worker-runtime.ts +3 -0
  413. package/src/entry/boot-options.ts +2 -0
  414. package/src/entry/orchestrator.ts +18 -2
  415. package/src/interactive/chat-loop-policy.ts +1 -0
  416. package/src/interactive/chat-loop.ts +8 -4
  417. package/src/interactive/chat-panel.ts +23 -9
  418. package/src/interactive/chat-renderer.ts +33 -11
  419. package/src/interactive/clio-editor.ts +13 -5
  420. package/src/interactive/dispatch-board.ts +70 -30
  421. package/src/interactive/editor-submit.ts +30 -25
  422. package/src/interactive/footer/widgets.ts +12 -9
  423. package/src/interactive/interactive-presentation.ts +2 -3
  424. package/src/interactive/mux-bridge.ts +3 -7
  425. package/src/interactive/overlay-general-openers.ts +14 -7
  426. package/src/interactive/overlays/cwd-fallback.ts +1 -1
  427. package/src/interactive/overlays/library-model.ts +114 -35
  428. package/src/interactive/overlays/library.ts +52 -5
  429. package/src/interactive/overlays/list-overlay.ts +4 -1
  430. package/src/interactive/overlays/session-selector.ts +28 -23
  431. package/src/interactive/overlays/settings.ts +20 -1
  432. package/src/interactive/overlays/tree-selector.ts +47 -10
  433. package/src/interactive/panes-runtime.ts +1 -1
  434. package/src/interactive/renderers/compaction-summary.ts +1 -1
  435. package/src/interactive/renderers/provider-error.ts +118 -0
  436. package/src/interactive/renderers/retry-status.ts +59 -23
  437. package/src/interactive/renderers/tool-execution.ts +67 -7
  438. package/src/interactive/renderers/worker-answer.ts +198 -0
  439. package/src/interactive/renderers/worker-entry.ts +57 -163
  440. package/src/interactive/slash-commands.ts +26 -1
  441. package/src/interactive/slash-spec.ts +2 -2
  442. package/src/interactive/theme/labels.ts +66 -30
  443. package/src/interactive/turn-persistence.ts +6 -1
  444. package/src/interactive/turn-recovery.ts +2 -1
  445. package/src/interactive/turn-runtime.ts +22 -5
  446. package/src/interactive/view/artifacts.ts +3 -2
  447. package/src/interactive/view/view-overlay.ts +96 -20
  448. package/src/tools/agent-tools.ts +21 -7
  449. package/src/tools/artifact.ts +16 -8
  450. package/src/tools/bash.ts +90 -11
  451. package/src/tools/bootstrap.ts +42 -4
  452. package/src/tools/builtin-tool-catalog.ts +72 -7
  453. package/src/tools/context/index.ts +27 -31
  454. package/src/tools/context/library.ts +3 -1
  455. package/src/tools/context/surface.ts +12 -26
  456. package/src/tools/core-bootstrap.ts +72 -1
  457. package/src/tools/data/csv.ts +1189 -0
  458. package/src/tools/data/index.ts +308 -0
  459. package/src/tools/data/json.ts +1799 -0
  460. package/src/tools/data/jsonl.ts +561 -0
  461. package/src/tools/data/shared.ts +452 -0
  462. package/src/tools/edit-diff.ts +0 -7
  463. package/src/tools/edit.ts +115 -18
  464. package/src/tools/file-mutation-queue.ts +114 -4
  465. package/src/tools/find.ts +108 -29
  466. package/src/tools/gateway/caps.ts +11 -0
  467. package/src/tools/gateway/clio-context-surface.ts +41 -0
  468. package/src/tools/gateway/clio-context-tools.ts +77 -0
  469. package/src/tools/gateway/data-surface.ts +138 -0
  470. package/src/tools/gateway/data-tool.ts +201 -0
  471. package/src/tools/gateway/index.ts +342 -0
  472. package/src/tools/gateway/mcp-capabilities.ts +469 -0
  473. package/src/tools/grep.ts +88 -33
  474. package/src/tools/harness-extensions.ts +3 -0
  475. package/src/tools/ignore-policy.ts +7 -5
  476. package/src/tools/ls.ts +111 -20
  477. package/src/tools/policy.ts +35 -13
  478. package/src/tools/presentation.ts +6 -0
  479. package/src/tools/read.ts +634 -156
  480. package/src/tools/registry.ts +94 -6
  481. package/src/tools/run-script.ts +997 -0
  482. package/src/tools/spawn-hygiene.ts +157 -5
  483. package/src/tools/surface.ts +163 -0
  484. package/src/tools/verify/authoring.ts +8 -3
  485. package/src/tools/verify/catalog.ts +14 -1
  486. package/src/tools/verify/numeric.ts +367 -59
  487. package/src/tools/verify/perf.ts +171 -9
  488. package/src/tools/verify/scripts.ts +178 -26
  489. package/src/tools/web-fetch-surface.ts +22 -0
  490. package/src/tools/web-fetch.ts +26 -1
  491. package/src/tools/write.ts +45 -19
  492. package/dist/chunk-B6UM3OZC.js +0 -15
  493. package/dist/chunk-GJ24ODAX.js +0 -196
  494. package/dist/chunk-LZCCBLRC.js +0 -57
  495. package/dist/web/client/assets/architecture-7GRP2DOG-EGozsYEO.js +0 -1
  496. package/dist/web/client/assets/architectureDiagram-5GKGNRK7-z9FokbJl.js +0 -36
  497. package/dist/web/client/assets/channel-CiUWbHN9.js +0 -1
  498. package/dist/web/client/assets/chunk-DU6HZSFF-C5USiOAy.js +0 -125
  499. package/dist/web/client/assets/classDiagram-ZZMXUADV-CMODalmo.js +0 -1
  500. package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-CMODalmo.js +0 -1
  501. package/dist/web/client/assets/cose-bilkent-JH36ORCC-RbQL23qS.js +0 -1
  502. package/dist/web/client/assets/dist-D0_q-A2b.js +0 -1
  503. package/dist/web/client/assets/eventmodeling-NTZA5JFV-DjVq8wKU.js +0 -1
  504. package/dist/web/client/assets/flowDiagram-HODETNUW-BUiCBGXt.js +0 -1
  505. package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-B2lb8lvF.js +0 -292
  506. package/dist/web/client/assets/index-FSIC94pi.js +0 -83
  507. package/dist/web/client/assets/prism-core-BngLtbUd.js +0 -1
  508. package/dist/web/client/assets/railroad-abnf-HS5TGJTU-BcW01NZG.js +0 -1
  509. package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-B7YCEDDd.js +0 -1
  510. package/dist/web/client/assets/railroad-peg-WCYAUIDC-DjNZbSTZ.js +0 -1
  511. package/dist/web/client/assets/src-yeptRXpt.js +0 -1
  512. package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-C7QtHeK4.js +0 -1
  513. package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-CBZlPFky.js +0 -8
package/src/tools/read.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { readFileSync, statSync } from "node:fs";
1
+ import { isUtf8 } from "node:buffer";
2
+ import { type FileHandle, open, stat } from "node:fs/promises";
2
3
  import { Type } from "typebox";
3
4
  import { detectSupportedImageMimeType, prepareBoundedImage } from "../core/file-references.js";
4
5
  import { GUARDRAIL_DEFAULTS, resolveGuardrail } from "../core/guardrails.js";
@@ -6,12 +7,13 @@ import { ToolNames } from "../core/tool-names.js";
6
7
  import {
7
8
  commitObservationReservation,
8
9
  finalizeObservation,
10
+ type ObservationReservation,
9
11
  observationBudgetExhausted,
10
12
  releaseObservation,
11
13
  reserveObservation,
12
14
  } from "./observation.js";
13
15
  import { resolveReadPath } from "./path-utils.js";
14
- import type { ToolResult, ToolSpec } from "./registry.js";
16
+ import type { ToolInvokeOptions, ToolResult, ToolSpec } from "./registry.js";
15
17
  import { isSessionOffloadPath } from "./result-shaping.js";
16
18
  import {
17
19
  DEFAULT_MAX_LINES,
@@ -31,6 +33,58 @@ import { truncateUtf8 } from "./truncate-utf8.js";
31
33
  export const DEFAULT_READ_MAX_BYTES = GUARDRAIL_DEFAULTS.readMaxBytes;
32
34
  const MIN_READ_CAP_BYTES = 1024;
33
35
 
36
+ /**
37
+ * Scan granularity. The newline scans (forward to `offset`, backward for
38
+ * `tail`) hold one chunk at a time, so a call's peak memory is this chunk plus
39
+ * the returned window, never the file.
40
+ */
41
+ export const READ_SCAN_CHUNK_BYTES = 64 * 1024;
42
+
43
+ /**
44
+ * Files up to this size get an exact physical line count on every call (one
45
+ * sequential newline scan in O(chunk) memory). Larger files skip the count and
46
+ * report `totalCount: null`, which the envelope renders as `N+`, together with
47
+ * their byte size, so a call on a multi-gigabyte log stays O(window).
48
+ */
49
+ export const READ_LINE_COUNT_BUDGET_BYTES = 32 * 1024 * 1024;
50
+
51
+ /**
52
+ * Images are decoded whole because the encoder needs every byte, so they keep
53
+ * the ceiling every read had before the text reader became windowed.
54
+ */
55
+ const IMAGE_MAX_BYTES = 20_000_000;
56
+
57
+ /**
58
+ * Bytes read past the cap so that a window cut inside a multibyte sequence can
59
+ * drop that sequence (at most three bytes) and still hold enough bytes for the
60
+ * shared truncation to decide on the window exactly as it would on the whole
61
+ * selection. A forward window needs cap+1 bytes: its partial last line then
62
+ * never fits. A tail window needs cap+2: the file's trailing terminator is not
63
+ * part of the joined text, so with cap+1 bytes a cut first line could fit
64
+ * exactly at the cap and be shown as complete.
65
+ */
66
+ export const READ_WINDOW_SLACK_BYTES = 5;
67
+
68
+ /**
69
+ * Test-only seams; production code never sets them. `afterIdentity` runs once
70
+ * the descriptor's identity is recorded and before any window read, so a test
71
+ * can change the file in flight deterministically. `beforeChunk` runs before
72
+ * every scan chunk with its byte position, so a test can abort mid-scan.
73
+ */
74
+ export const readTestSeams: {
75
+ afterIdentity?: ((file: ReadFileIdentity, path: string) => Promise<void> | void) | undefined;
76
+ beforeChunk?: ((position: number) => void) | undefined;
77
+ } = {};
78
+
79
+ const NON_TEXT_HINT =
80
+ "For CSV, TSV, JSON, or JSONL use the data capability through the gateway; for binary or other encodings use run_script with a suitable library. Do not edit it as text.";
81
+
82
+ /** Identity of the file as it was read, from fstat on the open descriptor. */
83
+ export interface ReadFileIdentity {
84
+ bytes: number;
85
+ mtimeMs: number;
86
+ }
87
+
34
88
  export function readMaxBytes(): number {
35
89
  return Math.max(MIN_READ_CAP_BYTES, resolveGuardrail("readMaxBytes"));
36
90
  }
@@ -52,11 +106,524 @@ function numberedPrefixBytes(totalLines: number): number {
52
106
  return bytes;
53
107
  }
54
108
 
109
+ class ReadCancelled extends Error {
110
+ constructor(readonly position: number) {
111
+ super("read cancelled");
112
+ }
113
+ }
114
+
115
+ /** Every scan calls this once per chunk: the seam sees the position, then an aborted signal stops the scan. */
116
+ function chunkBoundary(signal: AbortSignal | undefined, position: number): void {
117
+ readTestSeams.beforeChunk?.(position);
118
+ if (signal?.aborted) throw new ReadCancelled(position);
119
+ }
120
+
121
+ /** Fill `buffer[0, length)` from `position`; the view is shorter only at EOF. */
122
+ async function readInto(handle: FileHandle, buffer: Buffer, position: number, length: number): Promise<Buffer> {
123
+ let filled = 0;
124
+ while (filled < length) {
125
+ const { bytesRead } = await handle.read(buffer, filled, length - filled, position + filled);
126
+ if (bytesRead === 0) break;
127
+ filled += bytesRead;
128
+ }
129
+ return buffer.subarray(0, filled);
130
+ }
131
+
132
+ async function readExact(handle: FileHandle, position: number, length: number): Promise<Buffer> {
133
+ const size = Math.max(0, length);
134
+ return readInto(handle, Buffer.allocUnsafe(size), position, size);
135
+ }
136
+
137
+ interface ForwardScan {
138
+ /** Byte position right after the requested newline, or null when the file has fewer. */
139
+ target: number | null;
140
+ /** Every newline when counting the file, otherwise only those up to the target. */
141
+ newlines: number;
142
+ }
143
+
144
+ /**
145
+ * Walk the file from the start one chunk at a time. Finds where line
146
+ * `skipNewlines + 1` begins and, when `countAll` is set, keeps going to EOF so
147
+ * the caller has the exact newline count. A target that is never found means
148
+ * the whole file was scanned, so `newlines` is exact in that case too.
149
+ */
150
+ async function scanForward(
151
+ handle: FileHandle,
152
+ size: number,
153
+ skipNewlines: number,
154
+ countAll: boolean,
155
+ signal: AbortSignal | undefined,
156
+ ): Promise<ForwardScan> {
157
+ const chunk = Buffer.allocUnsafe(READ_SCAN_CHUNK_BYTES);
158
+ let position = 0;
159
+ let newlines = 0;
160
+ let target: number | null = skipNewlines === 0 ? 0 : null;
161
+ while (position < size && (countAll || target === null)) {
162
+ chunkBoundary(signal, position);
163
+ const view = await readInto(handle, chunk, position, Math.min(READ_SCAN_CHUNK_BYTES, size - position));
164
+ if (view.length === 0) break;
165
+ let index = view.indexOf(0x0a);
166
+ while (index >= 0) {
167
+ newlines += 1;
168
+ if (target === null && newlines === skipNewlines) {
169
+ target = position + index + 1;
170
+ if (!countAll) break;
171
+ }
172
+ index = view.indexOf(0x0a, index + 1);
173
+ }
174
+ position += view.length;
175
+ }
176
+ return { target, newlines };
177
+ }
178
+
179
+ /**
180
+ * Walk the file from the end one chunk at a time, looking for the
181
+ * `skipNewlines`-th newline from EOF. Returns the byte position after it, 0
182
+ * when the file holds fewer newlines, or null once `retainBytes` have been
183
+ * scanned without finding it: the selection then starts before the window the
184
+ * caller will keep, and the byte cap alone decides what is shown.
185
+ */
186
+ async function scanBackward(
187
+ handle: FileHandle,
188
+ size: number,
189
+ skipNewlines: number,
190
+ retainBytes: number,
191
+ signal: AbortSignal | undefined,
192
+ ): Promise<number | null> {
193
+ const chunk = Buffer.allocUnsafe(READ_SCAN_CHUNK_BYTES);
194
+ let position = size;
195
+ let found = 0;
196
+ while (position > 0) {
197
+ if (size - position >= retainBytes) return null;
198
+ chunkBoundary(signal, position);
199
+ const start = Math.max(0, position - READ_SCAN_CHUNK_BYTES);
200
+ const view = await readInto(handle, chunk, start, position - start);
201
+ let index = view.lastIndexOf(0x0a);
202
+ while (index >= 0) {
203
+ found += 1;
204
+ if (found === skipNewlines) return start + index + 1;
205
+ index = index === 0 ? -1 : view.lastIndexOf(0x0a, index - 1);
206
+ }
207
+ position = start;
208
+ }
209
+ return 0;
210
+ }
211
+
212
+ /** Absolute index of the next newline at or after `from`, `size` at EOF, or null past the scan budget. */
213
+ async function findLineEnd(
214
+ handle: FileHandle,
215
+ from: number,
216
+ size: number,
217
+ budget: number,
218
+ signal: AbortSignal | undefined,
219
+ ): Promise<number | null> {
220
+ const chunk = Buffer.allocUnsafe(READ_SCAN_CHUNK_BYTES);
221
+ let position = from;
222
+ while (position < size) {
223
+ if (position - from >= budget) return null;
224
+ chunkBoundary(signal, position);
225
+ const view = await readInto(handle, chunk, position, Math.min(READ_SCAN_CHUNK_BYTES, size - position));
226
+ if (view.length === 0) break;
227
+ const index = view.indexOf(0x0a);
228
+ if (index >= 0) return position + index;
229
+ position += view.length;
230
+ }
231
+ return size;
232
+ }
233
+
234
+ /** Index of the n-th newline in `bytes`, or -1 when it holds fewer. */
235
+ function nthNewline(bytes: Buffer, n: number): number {
236
+ let index = -1;
237
+ for (let found = 0; found < n; found += 1) {
238
+ index = bytes.indexOf(0x0a, index + 1);
239
+ if (index < 0) return -1;
240
+ }
241
+ return index;
242
+ }
243
+
244
+ /** Drop a multibyte sequence that a byte-cut window ends inside, so decoding stays exact. */
245
+ function trimIncompleteSequence(bytes: Buffer): Buffer {
246
+ let index = bytes.length - 1;
247
+ let continuation = 0;
248
+ while (index >= 0 && continuation < 3 && ((bytes[index] as number) & 0xc0) === 0x80) {
249
+ index -= 1;
250
+ continuation += 1;
251
+ }
252
+ if (index < 0) return bytes;
253
+ const lead = bytes[index] as number;
254
+ const need = lead >= 0xf0 ? 3 : lead >= 0xe0 ? 2 : lead >= 0xc0 ? 1 : 0;
255
+ return need > continuation ? bytes.subarray(0, index) : bytes;
256
+ }
257
+
258
+ /** Continuation bytes a byte-cut window starts with; they belong to a character before it. */
259
+ function leadingContinuationBytes(bytes: Buffer): number {
260
+ let count = 0;
261
+ while (count < 3 && count < bytes.length && ((bytes[count] as number) & 0xc0) === 0x80) count += 1;
262
+ return count;
263
+ }
264
+
265
+ /**
266
+ * Index of the first byte that cannot start or continue a well-formed UTF-8
267
+ * sequence (overlongs, surrogates, and code points above U+10FFFF included),
268
+ * or -1. A sequence cut off by the end of the buffer reports its lead byte.
269
+ */
270
+ function firstInvalidUtf8(bytes: Buffer): number {
271
+ if (isUtf8(bytes)) return -1;
272
+ let index = 0;
273
+ while (index < bytes.length) {
274
+ const lead = bytes[index] as number;
275
+ if (lead < 0x80) {
276
+ index += 1;
277
+ continue;
278
+ }
279
+ let need: number;
280
+ let low = 0x80;
281
+ let high = 0xbf;
282
+ if (lead >= 0xc2 && lead <= 0xdf) need = 1;
283
+ else if (lead >= 0xe0 && lead <= 0xef) {
284
+ need = 2;
285
+ if (lead === 0xe0) low = 0xa0;
286
+ else if (lead === 0xed) high = 0x9f;
287
+ } else if (lead >= 0xf0 && lead <= 0xf4) {
288
+ need = 3;
289
+ if (lead === 0xf0) low = 0x90;
290
+ else if (lead === 0xf4) high = 0x8f;
291
+ } else return index;
292
+ if (index + need >= bytes.length) return index;
293
+ const first = bytes[index + 1] as number;
294
+ if (first < low || first > high) return index + 1;
295
+ for (let step = 2; step <= need; step += 1) {
296
+ const byte = bytes[index + step] as number;
297
+ if (byte < 0x80 || byte > 0xbf) return index + step;
298
+ }
299
+ index += need + 1;
300
+ }
301
+ return -1;
302
+ }
303
+
304
+ function lastLineOf(text: string): string {
305
+ const lines = splitLinesForCounting(text);
306
+ return lines[lines.length - 1] ?? "";
307
+ }
308
+
309
+ function refuseBinary(pathArg: string, offset: number, file: ReadFileIdentity): ToolResult {
310
+ return {
311
+ kind: "error",
312
+ message: `read: ${pathArg} looks binary: NUL byte at byte offset ${offset} of ${formatSize(file.bytes)}. ${NON_TEXT_HINT}`,
313
+ details: { file },
314
+ };
315
+ }
316
+
317
+ function refuseEncoding(pathArg: string, offset: number, byte: number, file: ReadFileIdentity): ToolResult {
318
+ const hex = byte.toString(16).padStart(2, "0").toUpperCase();
319
+ return {
320
+ kind: "error",
321
+ message: `read: ${pathArg} is not valid UTF-8 text: byte 0x${hex} at byte offset ${offset} of ${formatSize(file.bytes)} cannot be decoded. ${NON_TEXT_HINT}`,
322
+ details: { file },
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Refuse NUL bytes and malformed UTF-8 inside the window that would be shown,
328
+ * naming the failing absolute byte offset; never substitute U+FFFD and present
329
+ * the result as text.
330
+ */
331
+ function decodeWindow(
332
+ window: Buffer,
333
+ absoluteStart: number,
334
+ pathArg: string,
335
+ file: ReadFileIdentity,
336
+ ): string | ToolResult {
337
+ const nul = window.indexOf(0);
338
+ if (nul >= 0) return refuseBinary(pathArg, absoluteStart + nul, file);
339
+ const invalid = firstInvalidUtf8(window);
340
+ if (invalid >= 0) return refuseEncoding(pathArg, absoluteStart + invalid, window[invalid] as number, file);
341
+ return window.toString("utf8");
342
+ }
343
+
344
+ function beyondEndOfFile(offset: number, totalLines: number, file: ReadFileIdentity): ToolResult {
345
+ // The anchor matters for weak models: a bare "beyond end of file" reads as
346
+ // a paging mistake and triggers a tail-re-reading walk. Say plainly that
347
+ // nothing exists past the last line and that re-reading cannot produce new
348
+ // content.
349
+ return {
350
+ kind: "error",
351
+ message:
352
+ `read: offset ${offset} is beyond end of file (${totalLines} lines total). The file ends at line ` +
353
+ `${totalLines} and has no further content; do not page past it or re-read the tail. A read covering ` +
354
+ `line ${totalLines} has already returned everything.`,
355
+ details: { file },
356
+ };
357
+ }
358
+
359
+ /**
360
+ * One notice line for a file whose identity changed between the descriptor's
361
+ * fstat and the end of the reads. It carries the envelope's own fields
362
+ * (counts, sizes, continuation) computed from the body alone, so the shown
363
+ * and total sizes keep describing file content, and the envelope's notice is
364
+ * suppressed in favour of this one.
365
+ */
366
+ function changedFileNotice(view: ReadView, pathArg: string, before: ReadFileIdentity, after: ReadFileIdentity): string {
367
+ const stamp = (identity: ReadFileIdentity) => `${identity.bytes}B (mtime ${new Date(identity.mtimeMs).toISOString()})`;
368
+ const changed =
369
+ `${pathArg} changed while being read: ${stamp(before)} when the read began, ${stamp(after)} when it finished; ` +
370
+ "the lines and counts above may mix the two states, re-read to see the current content";
371
+ if (!view.truncated || view.omitNotice) return `[read: ${changed}]`;
372
+ const total = view.totalCount === null ? `${view.shownCount}+` : String(view.totalCount);
373
+ const parts = [
374
+ `read: ${view.shownCount}/${total} lines shown (${formatSize(Buffer.byteLength(view.output))} of ${formatSize(view.totalBytes)})`,
375
+ ...(view.next !== undefined ? [`next: ${view.next}`] : []),
376
+ changed,
377
+ ];
378
+ return `[${parts.join(" | ")}]`;
379
+ }
380
+
381
+ interface ReadRequest {
382
+ pathArg: string;
383
+ numbered: boolean;
384
+ offset: number;
385
+ limit: number | null;
386
+ tail: number | null;
387
+ signal: AbortSignal | undefined;
388
+ }
389
+
390
+ /** A selected, bounded rendering ready for the observation envelope. */
391
+ interface ReadView {
392
+ output: string;
393
+ shownCount: number;
394
+ totalCount: number | null;
395
+ totalBytes: number;
396
+ truncated: boolean;
397
+ next?: string;
398
+ omitNotice?: boolean;
399
+ }
400
+
401
+ interface ReadPlan {
402
+ file: ReadFileIdentity;
403
+ cap: number;
404
+ /** Exact line counting is affordable for this file (size within the budget). */
405
+ counted: boolean;
406
+ endsWithNewline: boolean;
407
+ }
408
+
409
+ async function firstLineSizeText(
410
+ handle: FileHandle,
411
+ lineStart: number,
412
+ window: Buffer,
413
+ windowEnd: number,
414
+ size: number,
415
+ labelBytes: number,
416
+ signal: AbortSignal | undefined,
417
+ ): Promise<string> {
418
+ const newline = window.indexOf(0x0a);
419
+ if (newline >= 0) return formatSize(labelBytes + newline);
420
+ if (windowEnd >= size) return formatSize(labelBytes + (size - lineStart));
421
+ const end = await findLineEnd(handle, windowEnd, size, READ_LINE_COUNT_BUDGET_BYTES, signal);
422
+ if (end === null) return `at least ${formatSize(labelBytes + (windowEnd - lineStart) + READ_LINE_COUNT_BUDGET_BYTES)}`;
423
+ return formatSize(labelBytes + (end - lineStart));
424
+ }
425
+
426
+ /**
427
+ * Forward path: locate line `offset` with a newline scan, read one window of
428
+ * cap+slack bytes (or up to the `limit`-th terminator) from there, and apply
429
+ * the shared head truncation to that window. The window always holds cap+1
430
+ * decidable bytes when the selection is longer, so the truncation decides
431
+ * exactly as it would over the whole selection.
432
+ */
433
+ async function readHead(handle: FileHandle, request: ReadRequest, plan: ReadPlan): Promise<ReadView | ToolResult> {
434
+ const { pathArg, offset, limit, numbered, signal } = request;
435
+ const { file, cap, counted, endsWithNewline } = plan;
436
+ const size = file.bytes;
437
+ const unterminated = size > 0 && !endsWithNewline ? 1 : 0;
438
+ const scan = await scanForward(handle, size, offset - 1, counted, signal);
439
+ if (scan.target === null || (offset > 1 && scan.target >= size)) {
440
+ // Both cases scanned to EOF, so the newline count is exact whatever the size.
441
+ return beyondEndOfFile(offset, scan.newlines + unterminated, file);
442
+ }
443
+ const totalLines = counted ? scan.newlines + unterminated : null;
444
+ const startByte = scan.target;
445
+ const startIndex = offset - 1;
446
+ let window = await readExact(handle, startByte, Math.min(cap + READ_WINDOW_SLACK_BYTES, size - startByte));
447
+ let cut = startByte + window.length < size;
448
+ if (limit !== null) {
449
+ const stop = nthNewline(window, limit);
450
+ if (stop >= 0) {
451
+ window = window.subarray(0, stop + 1);
452
+ cut = false;
453
+ }
454
+ }
455
+ if (cut) window = trimIncompleteSequence(window);
456
+ const windowEnd = startByte + window.length;
457
+ const decoded = decodeWindow(window, startByte, pathArg, file);
458
+ if (typeof decoded !== "string") return decoded;
459
+ const linesInWindow = splitLinesForCounting(decoded).length;
460
+ const totalBytes = size + (numbered && totalLines !== null ? numberedPrefixBytes(totalLines) : 0);
461
+ const sourceTruncation = truncateHead(decoded, { maxBytes: cap });
462
+ const truncation: TruncationResult =
463
+ numbered && !sourceTruncation.firstLineExceedsLimit
464
+ ? truncateHead(numberSourceLines(sourceTruncation.content, startIndex + 1, sourceTruncation.outputLines), {
465
+ maxBytes: cap,
466
+ })
467
+ : sourceTruncation;
468
+ if (truncation.firstLineExceedsLimit) {
469
+ const label = numbered ? `${startIndex + 1} | ` : "";
470
+ const labelBytes = Buffer.byteLength(label);
471
+ const firstLine = decoded.split("\n", 1)[0] ?? "";
472
+ const lineSize = await firstLineSizeText(handle, startByte, window, windowEnd, size, labelBytes, signal);
473
+ const linePrefix = label + truncateUtf8(firstLine, cap - labelBytes, "\n[line truncated]");
474
+ const output = `${linePrefix}\n\n[${numbered ? "Numbered line" : "Line"} ${startIndex + 1} is ${lineSize}, exceeding the ${formatSize(cap)} read limit. Showing the UTF-8 prefix only. Use grep with a narrower literal/regex or edit with exact surrounding text; use shell access only when byte-level inspection is required.]`;
475
+ return { output, shownCount: 0, totalCount: totalLines, totalBytes, truncated: true, omitNotice: true };
476
+ }
477
+ const endDisplay = startIndex + truncation.outputLines;
478
+ // More lines follow when the truncation kept fewer lines than the window
479
+ // holds, or when the window stopped short of EOF (at the limit-th
480
+ // terminator or at the byte cap).
481
+ const moreAfter = truncation.outputLines < linesInWindow || windowEnd < size;
482
+ const truncated = sourceTruncation.truncated || truncation.truncated || (limit !== null && moreAfter);
483
+ return {
484
+ output: truncation.content,
485
+ shownCount: truncation.outputLines,
486
+ totalCount: totalLines,
487
+ totalBytes,
488
+ truncated,
489
+ ...(truncated && moreAfter ? { next: `offset=${endDisplay + 1}${numbered ? " line_numbers=true" : ""}` } : {}),
490
+ };
491
+ }
492
+
493
+ /**
494
+ * Tail path: scan backward from EOF for the terminator that starts the last N
495
+ * lines, keep at most cap+slack bytes of that selection, and apply the shared
496
+ * tail truncation so the very end of the file always survives the byte cap.
497
+ */
498
+ async function readTail(handle: FileHandle, request: ReadRequest, plan: ReadPlan): Promise<ReadView | ToolResult> {
499
+ const { pathArg, numbered, signal } = request;
500
+ const tail = request.tail ?? 1;
501
+ const { file, cap, counted, endsWithNewline } = plan;
502
+ const size = file.bytes;
503
+ if (numbered && !counted) {
504
+ return {
505
+ kind: "error",
506
+ message:
507
+ `read: line_numbers with tail needs a physical line count, and ${pathArg} is ${formatSize(size)}, above the ` +
508
+ `${formatSize(READ_LINE_COUNT_BUDGET_BYTES)} counting budget. Use tail without line_numbers, use offset/limit ` +
509
+ "(line numbers stay exact there), or grep to locate the region first.",
510
+ details: { file },
511
+ };
512
+ }
513
+ const unterminated = size > 0 && !endsWithNewline ? 1 : 0;
514
+ const totalLines = counted ? (await scanForward(handle, size, 0, true, signal)).newlines + unterminated : null;
515
+ const retain = cap + READ_WINDOW_SLACK_BYTES;
516
+ // A trailing terminator ends the last line rather than starting a phantom
517
+ // one, so it is one more newline to skip before the selection begins.
518
+ const selectionStart = await scanBackward(handle, size, tail + (endsWithNewline ? 1 : 0), retain, signal);
519
+ const windowStart = Math.max(selectionStart ?? 0, size - retain, 0);
520
+ const cutFront = selectionStart === null || windowStart > selectionStart;
521
+ let window = await readExact(handle, windowStart, size - windowStart);
522
+ let skipped = 0;
523
+ if (cutFront) {
524
+ skipped = leadingContinuationBytes(window);
525
+ window = window.subarray(skipped);
526
+ }
527
+ const decoded = decodeWindow(window, windowStart + skipped, pathArg, file);
528
+ if (typeof decoded !== "string") return decoded;
529
+ const sourceTruncation = truncateTail(decoded, { maxBytes: cap, maxLines: tail });
530
+ let truncation = sourceTruncation;
531
+ if (numbered && totalLines !== null) {
532
+ const numberedContent = numberSourceLines(
533
+ sourceTruncation.content,
534
+ totalLines - sourceTruncation.outputLines + 1,
535
+ sourceTruncation.outputLines,
536
+ );
537
+ truncation = truncateTail(numberedContent, { maxBytes: cap, maxLines: tail });
538
+ if (sourceTruncation.lastLinePartial || truncation.lastLinePartial) {
539
+ // Never return a tail fragment with a lost or fabricated complete
540
+ // line label. Keep the suffix within the cap and mark it partial.
541
+ const label = `${totalLines} | [partial line suffix] `;
542
+ const suffix = truncateTail(lastLineOf(decoded), { maxBytes: cap - Buffer.byteLength(label) });
543
+ truncation = { ...truncation, content: label + suffix.content, outputLines: 0, truncated: true };
544
+ }
545
+ }
546
+ const shownLines = truncation.outputLines;
547
+ // A front cut means the byte cap, not the tail count, bounded this call.
548
+ const truncated = cutFront || selectionStart !== 0 || sourceTruncation.truncated || truncation.truncated;
549
+ const totalBytes = size + (numbered && totalLines !== null ? numberedPrefixBytes(totalLines) : 0);
550
+ let next: string | undefined;
551
+ if (truncated && shownLines > 0) {
552
+ if (totalLines !== null) {
553
+ const firstShown = Math.max(1, totalLines - shownLines + 1);
554
+ next = `offset=${Math.max(1, firstShown - shownLines)} limit=${shownLines}${numbered ? " line_numbers=true" : ""}`;
555
+ } else if (!cutFront && !sourceTruncation.truncated && !truncation.truncated) {
556
+ // Without a line count the earlier lines have no known offset; a
557
+ // larger tail is the only exact continuation, and only while the
558
+ // tail count rather than the byte cap bounded this call.
559
+ next = `tail=${tail * 2}`;
560
+ }
561
+ }
562
+ return {
563
+ output: truncation.content,
564
+ shownCount: shownLines,
565
+ totalCount: totalLines,
566
+ totalBytes,
567
+ truncated,
568
+ ...(next !== undefined ? { next } : {}),
569
+ };
570
+ }
571
+
572
+ async function readImage(
573
+ handle: FileHandle,
574
+ pathArg: string,
575
+ file: ReadFileIdentity,
576
+ reservation: ObservationReservation,
577
+ options: ToolInvokeOptions | undefined,
578
+ ): Promise<ToolResult> {
579
+ if (file.bytes > IMAGE_MAX_BYTES) {
580
+ return {
581
+ kind: "error",
582
+ message: `read: image too large (${file.bytes}B > 20MB); images are decoded whole. Downscale it first, for example with run_script and an image library.`,
583
+ details: { file },
584
+ };
585
+ }
586
+ if (options?.supportsImages !== true) {
587
+ return {
588
+ kind: "error",
589
+ message:
590
+ "IMAGE_INPUT_UNSUPPORTED: the routed model does not support image input. Choose a vision-capable model to inspect this file.",
591
+ details: { file },
592
+ };
593
+ }
594
+ const bytes = await readExact(handle, 0, file.bytes);
595
+ const output = `Image: ${pathArg}`;
596
+ commitObservationReservation(reservation);
597
+ const image = await prepareBoundedImage(
598
+ bytes,
599
+ Math.min(reservation.callCapBytes, options.toolResultMaxBytes ?? readMaxBytes()) - Buffer.byteLength(output) - 512,
600
+ );
601
+ if (image === null) {
602
+ return {
603
+ kind: "error",
604
+ message: "IMAGE_RESULT_TOO_LARGE: image could not be encoded within the tool result byte cap.",
605
+ details: { file },
606
+ };
607
+ }
608
+ return finalizeObservation({
609
+ tool: ToolNames.Read,
610
+ unit: "results",
611
+ output,
612
+ images: [image],
613
+ shownCount: 1,
614
+ totalCount: 1,
615
+ truncated: false,
616
+ details: { file },
617
+ reservation,
618
+ ...(options ? { options } : {}),
619
+ });
620
+ }
621
+
55
622
  export const readTool: ToolSpec = {
56
623
  name: ToolNames.Read,
57
624
  description: `Read a UTF-8 text file or a PNG, JPEG, GIF, or WebP image when the routed model supports vision. Output is capped at ${DEFAULT_MAX_LINES} lines or ${
58
625
  DEFAULT_READ_MAX_BYTES / 1024
59
- }KB per call; truncated results say how to continue with offset/limit. Pass tail=N to read the last N lines (jump to EOF) instead of paging from the top. Set line_numbers=true for citations: each source line is prefixed with its physical 1-based line number and " | "; these labels are not file content.`,
626
+ }KB per call; truncated results say how to continue with offset/limit. Files of any size are read through one bounded window, so offset and tail stay cheap; files over 32MB report their line total as N+. Binary or non-UTF-8 files are refused with the failing byte offset. Pass tail=N to read the last N lines (jump to EOF) instead of paging from the top. Set line_numbers=true for citations: each source line is prefixed with its physical 1-based line number and " | "; these labels are not file content.`,
60
627
  parameters: Type.Object({
61
628
  path: Type.String({ description: "File path (relative or absolute)." }),
62
629
  line_numbers: Type.Optional(
@@ -76,10 +643,14 @@ export const readTool: ToolSpec = {
76
643
  const pathArg = typeof args.path === "string" ? args.path : null;
77
644
  if (!pathArg) return { kind: "error", message: "read: missing path argument" };
78
645
  const filePath = resolveReadPath(pathArg);
79
- const numbered = args.line_numbers === true;
80
- const offset = typeof args.offset === "number" && args.offset > 0 ? Math.floor(args.offset) : 1;
81
- const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : null;
82
- const tail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : null;
646
+ const request: ReadRequest = {
647
+ pathArg,
648
+ numbered: args.line_numbers === true,
649
+ offset: typeof args.offset === "number" && args.offset > 0 ? Math.floor(args.offset) : 1,
650
+ limit: typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : null,
651
+ tail: typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : null,
652
+ signal: options?.signal,
653
+ };
83
654
  // Reading the session's own offload files bypasses the shared turn pool
84
655
  // (an optionless reservation is untracked): the "full: <path>" escape
85
656
  // hatch appears exactly when the pool is exhausted, so charging these
@@ -88,171 +659,77 @@ export const readTool: ToolSpec = {
88
659
  const reservation = isSessionOffloadPath(filePath, options?.sessionId)
89
660
  ? reserveObservation(readMaxBytes())
90
661
  : reserveObservation(readMaxBytes(), options);
662
+ let handle: FileHandle | null = null;
663
+ let file: ReadFileIdentity | null = null;
91
664
  try {
92
- const stat = statSync(filePath);
93
- if (!stat.isFile()) return { kind: "error", message: `read: not a file: ${filePath}` };
94
- if (stat.size > 20_000_000) {
95
- return {
96
- kind: "error",
97
- message: `read: file too large (${stat.size}B > 20MB). Use grep/find to locate the relevant section or read a smaller generated/source file; use shell access only when byte-level inspection is explicitly needed.`,
98
- };
99
- }
665
+ // stat before open: open() on a FIFO without a writer blocks a
666
+ // threadpool thread forever and no signal can interrupt it, so only
667
+ // a regular file is ever opened. The stat identity stamps the
668
+ // results below that never open the file.
669
+ const entry = await stat(filePath);
670
+ file = { bytes: entry.size, mtimeMs: entry.mtimeMs };
671
+ if (!entry.isFile()) return { kind: "error", message: `read: not a file: ${filePath}`, details: { file } };
100
672
  if (reservation.exhausted) {
101
- return observationBudgetExhausted({
673
+ const exhausted = observationBudgetExhausted({
102
674
  tool: ToolNames.Read,
103
675
  unit: "lines",
104
676
  reservation,
105
677
  subject: `reading ${pathArg}`,
106
678
  hint: "Use offset/limit in a follow-up turn or grep/find for a narrower section.",
107
679
  });
680
+ return { ...exhausted, details: { ...(exhausted.details ?? {}), file } };
108
681
  }
109
- const bytes = readFileSync(filePath);
110
- if (detectSupportedImageMimeType(bytes) !== null) {
111
- if (options?.supportsImages !== true)
112
- return {
113
- kind: "error",
114
- message:
115
- "IMAGE_INPUT_UNSUPPORTED: the routed model does not support image input. Choose a vision-capable model to inspect this file.",
116
- };
117
- const output = `Image: ${pathArg}`;
118
- commitObservationReservation(reservation);
119
- const image = await prepareBoundedImage(
120
- bytes,
121
- Math.min(reservation.callCapBytes, options.toolResultMaxBytes ?? readMaxBytes()) - Buffer.byteLength(output) - 512,
122
- );
123
- if (image === null)
124
- return {
125
- kind: "error",
126
- message: "IMAGE_RESULT_TOO_LARGE: image could not be encoded within the tool result byte cap.",
127
- };
128
- return finalizeObservation({
129
- tool: ToolNames.Read,
130
- unit: "results",
131
- output,
132
- images: [image],
133
- shownCount: 1,
134
- totalCount: 1,
135
- truncated: false,
136
- reservation,
137
- ...(options ? { options } : {}),
138
- });
139
- }
140
- const content = bytes.toString("utf8");
141
- // Slice from the raw split (keeps the trailing newline on selections that
142
- // reach EOF); count lines honestly (a trailing "\n" is a terminator, not
143
- // a phantom extra line) so continuation notices never over-report by one.
144
- const allLines = content.split("\n");
145
- const totalLines = splitLinesForCounting(content).length;
146
- const totalBytes = Buffer.byteLength(content, "utf8") + (numbered ? numberedPrefixBytes(totalLines) : 0);
147
- const startIndex = Math.min(offset - 1, totalLines);
148
- if (tail === null && offset > 1 && startIndex >= totalLines) {
149
- // The anchor matters for weak models: a bare "beyond end of file"
150
- // reads as a paging mistake and triggers a tail-re-reading walk. Say
151
- // plainly that nothing exists past the last line and that re-reading
152
- // cannot produce new content.
153
- return {
154
- kind: "error",
155
- message:
156
- `read: offset ${offset} is beyond end of file (${totalLines} lines total). The file ends at line ` +
157
- `${totalLines} and has no further content; do not page past it or re-read the tail — a read covering ` +
158
- `line ${totalLines} has already returned everything.`,
159
- };
160
- }
161
- const cap = reservation.callCapBytes;
162
-
163
- if (tail !== null) {
164
- // Jump to EOF: keep the last N lines, then bound by the byte cap from
165
- // the end (reusing truncateTail) so the very tail always survives.
166
- const startLine = Math.max(0, totalLines - tail);
167
- const tailContent = allLines.slice(startLine).join("\n");
168
- const sourceTruncation = truncateTail(tailContent, { maxBytes: cap, maxLines: tail });
169
- let truncation = sourceTruncation;
170
- if (numbered) {
171
- const numberedContent = numberSourceLines(
172
- sourceTruncation.content,
173
- totalLines - sourceTruncation.outputLines + 1,
174
- sourceTruncation.outputLines,
175
- );
176
- truncation = truncateTail(numberedContent, { maxBytes: cap, maxLines: tail });
177
- if (sourceTruncation.lastLinePartial || truncation.lastLinePartial) {
178
- // Never return a tail fragment with a lost or fabricated complete
179
- // line label. Keep the suffix within the cap and mark it partial.
180
- const label = `${totalLines} | [partial line suffix] `;
181
- const suffix = truncateTail(allLines[totalLines - 1] ?? "", { maxBytes: cap - Buffer.byteLength(label) });
182
- truncation = { ...truncation, content: label + suffix.content, outputLines: 0, truncated: true };
183
- }
184
- }
185
- const shownLines = truncation.outputLines;
186
- const firstShown = Math.max(1, totalLines - shownLines + 1);
187
- const truncated = startLine > 0 || sourceTruncation.truncated || truncation.truncated;
188
- return finalizeObservation({
189
- tool: ToolNames.Read,
190
- unit: "lines",
191
- output: truncation.content,
192
- shownCount: shownLines,
193
- totalCount: totalLines,
194
- totalBytes,
195
- truncated,
196
- ...(truncated && shownLines > 0
197
- ? {
198
- next: `offset=${Math.max(1, firstShown - shownLines)} limit=${shownLines}${numbered ? " line_numbers=true" : ""}`,
199
- }
200
- : {}),
201
- reservation,
202
- ...(options ? { options } : {}),
203
- });
204
- }
205
-
206
- const endIndex = limit !== null ? Math.min(startIndex + limit, totalLines) : totalLines;
207
- // Preserve the last selected source line's terminator when it exists.
208
- // Joining a limited slice alone turns ["x", ""] into "x\n", which
209
- // truncateHead counts as one line; [""] even becomes an empty file.
210
- // The raw split's final entry tells us whether that newline is real,
211
- // without inventing a line at EOF or changing shared text counting.
212
- const selected =
213
- allLines.slice(startIndex, endIndex).join("\n") + (endIndex > startIndex && endIndex < allLines.length ? "\n" : "");
214
- const sourceTruncation = truncateHead(selected, { maxBytes: cap });
215
- const truncation: TruncationResult =
216
- numbered && !sourceTruncation.firstLineExceedsLimit
217
- ? truncateHead(numberSourceLines(sourceTruncation.content, startIndex + 1, sourceTruncation.outputLines), {
218
- maxBytes: cap,
219
- })
220
- : sourceTruncation;
221
- if (truncation.firstLineExceedsLimit) {
222
- const label = numbered ? `${startIndex + 1} | ` : "";
223
- const firstLineSize = formatSize(Buffer.byteLength(label + (allLines[startIndex] ?? ""), "utf8"));
224
- const linePrefix =
225
- label + truncateUtf8(allLines[startIndex] ?? "", cap - Buffer.byteLength(label), "\n[line truncated]");
226
- const output = `${linePrefix}\n\n[${numbered ? "Numbered line" : "Line"} ${startIndex + 1} is ${firstLineSize}, exceeding the ${formatSize(cap)} read limit. Showing the UTF-8 prefix only. Use grep with a narrower literal/regex or edit with exact surrounding text; use shell access only when byte-level inspection is required.]`;
227
- return finalizeObservation({
228
- tool: ToolNames.Read,
229
- unit: "lines",
230
- output,
231
- shownCount: 0,
232
- totalCount: totalLines,
233
- totalBytes,
234
- truncated: true,
235
- omitNotice: true,
236
- reservation,
237
- ...(options ? { options } : {}),
238
- });
239
- }
240
- const endDisplay = startIndex + truncation.outputLines;
241
- const moreAfter = endDisplay < totalLines;
242
- const truncated = sourceTruncation.truncated || truncation.truncated || (limit !== null && moreAfter);
682
+ // Every path below awaits descriptor reads, so charge the cap up
683
+ // front for concurrent OBSERVE siblings; finalize reconciles it to
684
+ // the bytes returned and the finally block refunds an error path.
685
+ commitObservationReservation(reservation);
686
+ handle = await open(filePath, "r");
687
+ // The descriptor's own identity is what every read below describes.
688
+ const opened = await handle.stat();
689
+ file = { bytes: opened.size, mtimeMs: opened.mtimeMs };
690
+ if (!opened.isFile()) return { kind: "error", message: `read: not a file: ${filePath}`, details: { file } };
691
+ await readTestSeams.afterIdentity?.(file, filePath);
692
+ const first = await readExact(handle, 0, Math.min(file.bytes, READ_SCAN_CHUNK_BYTES));
693
+ if (detectSupportedImageMimeType(first) !== null)
694
+ return await readImage(handle, pathArg, file, reservation, options);
695
+ const nul = first.indexOf(0);
696
+ if (nul >= 0) return refuseBinary(pathArg, nul, file);
697
+ const lastByte =
698
+ file.bytes <= first.length ? first[file.bytes - 1] : (await readExact(handle, file.bytes - 1, 1))[0];
699
+ const plan: ReadPlan = {
700
+ file,
701
+ cap: reservation.callCapBytes,
702
+ counted: file.bytes <= READ_LINE_COUNT_BUDGET_BYTES,
703
+ endsWithNewline: file.bytes > 0 && lastByte === 0x0a,
704
+ };
705
+ const view = request.tail === null ? await readHead(handle, request, plan) : await readTail(handle, request, plan);
706
+ if ("kind" in view) return view;
707
+ const after = await handle.stat();
708
+ const change: ReadFileIdentity | null =
709
+ after.size !== file.bytes || after.mtimeMs !== file.mtimeMs ? { bytes: after.size, mtimeMs: after.mtimeMs } : null;
710
+ const output = change === null ? view.output : `${view.output}\n\n${changedFileNotice(view, pathArg, file, change)}`;
243
711
  return finalizeObservation({
244
712
  tool: ToolNames.Read,
245
713
  unit: "lines",
246
- output: truncation.content,
247
- shownCount: truncation.outputLines,
248
- totalCount: totalLines,
249
- totalBytes,
250
- truncated,
251
- ...(truncated && moreAfter ? { next: `offset=${endDisplay + 1}${numbered ? " line_numbers=true" : ""}` } : {}),
714
+ output,
715
+ shownCount: view.shownCount,
716
+ totalCount: view.totalCount,
717
+ totalBytes: view.totalBytes,
718
+ truncated: view.truncated,
719
+ ...(view.next !== undefined ? { next: view.next } : {}),
720
+ ...(view.omitNotice || change !== null ? { omitNotice: true } : {}),
721
+ details: { file, ...(change !== null ? { fileChange: change } : {}) },
252
722
  reservation,
253
723
  ...(options ? { options } : {}),
254
724
  });
255
725
  } catch (err) {
726
+ if (err instanceof ReadCancelled) {
727
+ return {
728
+ kind: "error",
729
+ message: `read: cancelled while scanning ${pathArg} at byte ${err.position}`,
730
+ ...(file !== null ? { details: { file } } : {}),
731
+ };
732
+ }
256
733
  const msg = err instanceof Error ? err.message : String(err);
257
734
  const code = (err as NodeJS.ErrnoException | undefined)?.code;
258
735
  if (code === "ENOENT") {
@@ -264,6 +741,7 @@ export const readTool: ToolSpec = {
264
741
  return { kind: "error", message: `read: ${msg}` };
265
742
  } finally {
266
743
  releaseObservation(reservation);
744
+ if (handle !== null) await handle.close().catch(() => undefined);
267
745
  }
268
746
  },
269
747
  };