@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.
- package/CHANGELOG.md +82 -0
- package/README.md +7 -6
- package/dist/{acp-JWPQZM2D.js → acp-ZWZCPS2A.js} +13 -13
- package/dist/{agents-4GCDIRVX.js → agents-LYSOWZUE.js} +53 -52
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-SYAKTDQE.js → auth-WBQGKWQ5.js} +27 -27
- package/dist/{background-OW67WMYR.js → background-BUMOBAQN.js} +4 -4
- package/dist/{builtins-TKS63VNV.js → builtins-ODRJOEDI.js} +7 -7
- package/dist/{chunk-JLBVCROW.js → chunk-2DRRZ57Q.js} +33 -24
- package/dist/chunk-2JBZYUPY.js +105 -0
- package/dist/chunk-2KUBKL63.js +336 -0
- package/dist/{chunk-K45LHSCI.js → chunk-2VBWFXYE.js} +7 -3
- package/dist/{chunk-XTLJDC2X.js → chunk-2X2R3UTH.js} +23 -18
- package/dist/{chunk-5HULHKT2.js → chunk-37LW32LZ.js} +5 -5
- package/dist/{chunk-W7KNI3F5.js → chunk-3AND2EFU.js} +5 -5
- package/dist/{chunk-36XSBPXL.js → chunk-3RDHGNU7.js} +95 -27
- package/dist/{chunk-BIY22UUK.js → chunk-3SUDBGEI.js} +11 -10
- package/dist/{chunk-3IQOZRQ6.js → chunk-3TQN67I2.js} +7 -7
- package/dist/{chunk-KWJXCL2X.js → chunk-3U4BL2EV.js} +3 -3
- package/dist/{chunk-RBES4TCZ.js → chunk-3UQADNG2.js} +3 -3
- package/dist/{chunk-V4ZKOKRB.js → chunk-434LRQ6Z.js} +61 -53
- package/dist/{chunk-MWMW53WW.js → chunk-4ATBOFRP.js} +649 -193
- package/dist/{chunk-QGIC5SX5.js → chunk-4DNL43EZ.js} +3 -3
- package/dist/{chunk-GVU4CXPJ.js → chunk-4HFJKRGZ.js} +113 -54
- package/dist/{chunk-5A6XR3RJ.js → chunk-555E2WVV.js} +16 -2
- package/dist/{chunk-LZDLYSUN.js → chunk-5RTEFNBP.js} +3 -3
- package/dist/{chunk-HMGAKB7E.js → chunk-64XJW7NT.js} +15 -15
- package/dist/{chunk-EXKUF426.js → chunk-7FBCRWVT.js} +280 -280
- package/dist/{chunk-K3W6BF5C.js → chunk-7JSOSVLP.js} +2 -2
- package/dist/{chunk-WFTSK7N3.js → chunk-7N3OLLSL.js} +4 -4
- package/dist/{chunk-JRA6OBHY.js → chunk-A6DULIMH.js} +17 -5
- package/dist/{chunk-R5NRX5N4.js → chunk-AB3QXXBC.js} +2 -2
- package/dist/chunk-ACCECAH4.js +49 -0
- package/dist/{chunk-GJBJHJNY.js → chunk-AM6CEP2K.js} +5 -5
- package/dist/{chunk-MUXNDNMZ.js → chunk-AY4XE6GN.js} +19 -13
- package/dist/{chunk-MATCRH3M.js → chunk-BBA2TAWV.js} +9 -9
- package/dist/{chunk-PMUUAABD.js → chunk-BMNVVKEW.js} +9 -9
- package/dist/{chunk-TOPICHGR.js → chunk-CTGZCEI6.js} +2 -2
- package/dist/{chunk-L75H5B3B.js → chunk-DIAJYJYG.js} +2 -2
- package/dist/{chunk-JIMWEYWO.js → chunk-EHZRAB4U.js} +2 -2
- package/dist/{chunk-YXANQGCX.js → chunk-EM2DIQAJ.js} +6 -6
- package/dist/{chunk-JCVIL6TY.js → chunk-ETF4ROKH.js} +4 -4
- package/dist/{chunk-5W4FRA5Q.js → chunk-F2YM3JY4.js} +483 -253
- package/dist/{chunk-YGIV24MF.js → chunk-FEBQWHJT.js} +21 -16
- package/dist/{chunk-PGEFOTJB.js → chunk-FPS4R2RN.js} +5 -4
- package/dist/{chunk-XC5POIL4.js → chunk-G5R53COU.js} +1 -1
- package/dist/chunk-GHXGMFQ5.js +40 -0
- package/dist/{chunk-QLKCCC2J.js → chunk-GJDRMBDI.js} +14 -14
- package/dist/{chunk-2ZWLMA3T.js → chunk-GREYLTZ5.js} +7 -7
- package/dist/{chunk-YXFKLMX2.js → chunk-H2WSCZZT.js} +22 -6
- package/dist/{chunk-FNVPQQYT.js → chunk-H6IUCY6Q.js} +2 -2
- package/dist/{chunk-5C2OQHLR.js → chunk-H72DCH3E.js} +92 -203
- package/dist/{chunk-JYKUMHCQ.js → chunk-H7TXJR23.js} +21 -21
- package/dist/chunk-HBJQQTKM.js +40 -0
- package/dist/chunk-HCVKSGNZ.js +990 -0
- package/dist/{chunk-ARRUMZ7X.js → chunk-I47EJPYL.js} +3 -3
- package/dist/{chunk-YKIIWURD.js → chunk-IALGWTLO.js} +3 -3
- package/dist/{chunk-FB3KTE2J.js → chunk-IALZJE37.js} +4 -4
- package/dist/{chunk-RAUOKUZV.js → chunk-IC7JXLOM.js} +15 -10
- package/dist/{chunk-LLCGGVSW.js → chunk-IOSVTTCD.js} +7 -7
- package/dist/{chunk-DBREN25X.js → chunk-IPTPMLA6.js} +8 -3
- package/dist/{chunk-V552WL53.js → chunk-IZZI3SKQ.js} +3 -67
- package/dist/{chunk-UDAK6GR2.js → chunk-JIYWMQZ5.js} +2 -2
- package/dist/{chunk-O3HSB2YL.js → chunk-JKU34ACI.js} +2 -2
- package/dist/{chunk-6X7VOCDY.js → chunk-JTR3VSV3.js} +3 -3
- package/dist/{chunk-O2QJDRWP.js → chunk-JU6MAMRJ.js} +4 -4
- package/dist/chunk-JURMQWVJ.js +15 -0
- package/dist/{chunk-STC6CWUZ.js → chunk-K3XN4LZ4.js} +5 -5
- package/dist/{chunk-ATB3Q7YO.js → chunk-KIVFXGBM.js} +27 -2
- package/dist/{chunk-WJWNZXRM.js → chunk-KOAHT3N2.js} +2 -2
- package/dist/chunk-KP5BPXR3.js +73 -0
- package/dist/{chunk-K53E2PTW.js → chunk-KRJQG47M.js} +3 -3
- package/dist/{chunk-IILXVWJY.js → chunk-KW6OJ7IY.js} +19 -21
- package/dist/{chunk-A46ILRS2.js → chunk-L3NBWLSN.js} +4 -4
- package/dist/{chunk-2U2U5POA.js → chunk-L4RUTLRL.js} +5 -5
- package/dist/{chunk-QYSAZMSH.js → chunk-LGTVWPRN.js} +2 -2
- package/dist/{chunk-KGP3UQGJ.js → chunk-LSQW6KZO.js} +5 -5
- package/dist/{chunk-P4JS3MH5.js → chunk-LWJYOHT3.js} +8 -8
- package/dist/{chunk-U3TPUXOQ.js → chunk-M6YLF66Z.js} +74 -40
- package/dist/{chunk-5D2FNBMG.js → chunk-MJ4QEJVT.js} +2 -2
- package/dist/{chunk-RM3XJX7P.js → chunk-MO65K5CH.js} +5 -5
- package/dist/{chunk-H5DPSUBB.js → chunk-MTOZEQHL.js} +3 -3
- package/dist/{chunk-6R5MBXBJ.js → chunk-NBPA5QM6.js} +2 -2
- package/dist/{chunk-H77XBUBM.js → chunk-NMBEW4PP.js} +3 -3
- package/dist/{chunk-QWUSS2QP.js → chunk-NPLLK3HO.js} +67 -87
- package/dist/{chunk-K5UJD6MM.js → chunk-NSYGPIVW.js} +5 -5
- package/dist/{context-EZZPT2ZV.js → chunk-NXTV5OI5.js} +30 -84
- package/dist/{chunk-F7Y6QTPL.js → chunk-NYDG4QP4.js} +4 -4
- package/dist/{chunk-PFZJMXIY.js → chunk-OA2GDACN.js} +2 -2
- package/dist/{chunk-Q32C6AWI.js → chunk-PJI57EC6.js} +4 -4
- package/dist/{chunk-EFXMQYI6.js → chunk-PJJBUGMR.js} +52 -52
- package/dist/{chunk-ICYIUNO4.js → chunk-PNJE7JPS.js} +18 -3
- package/dist/chunk-PP23PZEV.js +1347 -0
- package/dist/chunk-PPIFDSBK.js +409 -0
- package/dist/{chunk-RFULVPBX.js → chunk-PTFISXQO.js} +6 -5
- package/dist/{chunk-JV25I7ID.js → chunk-QB33Z5S7.js} +5 -5
- package/dist/{chunk-D55WO5J7.js → chunk-QGNII5VC.js} +8 -8
- package/dist/{chunk-RKFNU5UA.js → chunk-QI72X67S.js} +104 -27
- package/dist/chunk-QKHZZFKH.js +98 -0
- package/dist/{chunk-5UG6EZI3.js → chunk-RDKX2PME.js} +4 -4
- package/dist/{chunk-B5ZTR6KZ.js → chunk-RL7LJYN4.js} +1 -1
- package/dist/{chunk-BE4I3UJ2.js → chunk-RQROVOV3.js} +15 -13
- package/dist/{chunk-TEHVMHOK.js → chunk-S34A7BSP.js} +3 -3
- package/dist/{chunk-4P6M2TTE.js → chunk-SJETVJHX.js} +2 -2
- package/dist/{chunk-OPC4DQD6.js → chunk-SJMNU4ZS.js} +2 -2
- package/dist/{chunk-2JSCCEKO.js → chunk-SLLG2G44.js} +2 -2
- package/dist/{chunk-EPYATQMG.js → chunk-SQAZN2V3.js} +42 -16
- package/dist/{chunk-E2NSPYLC.js → chunk-SQBT46OF.js} +5 -5
- package/dist/{chunk-WTZVSMQZ.js → chunk-TCUOGIL2.js} +5 -5
- package/dist/{chunk-RRZFRIJP.js → chunk-TIWPKCVO.js} +5 -5
- package/dist/{chunk-QLAGXTTV.js → chunk-UIU7MGZ3.js} +5 -1
- package/dist/{chunk-WLHX3MM5.js → chunk-V4XJWDHR.js} +21 -11
- package/dist/{chunk-PJ5H5B7A.js → chunk-VPWGADBG.js} +18 -18
- package/dist/{chunk-GEYX2NZ5.js → chunk-VWFCAK34.js} +6 -6
- package/dist/{chunk-44U2OUSM.js → chunk-W72LLZAV.js} +2 -2
- package/dist/{chunk-ORRNGQDV.js → chunk-WGRDABW7.js} +4 -4
- package/dist/{chunk-RNLAPBJL.js → chunk-XFLSL7BT.js} +2 -2
- package/dist/{chunk-2SXGG2IV.js → chunk-XJZEDCNT.js} +6 -6
- package/dist/{chunk-UB33F3YW.js → chunk-XLLEL3K7.js} +5 -5
- package/dist/{chunk-2GPGAE5W.js → chunk-XNIZBOFV.js} +15 -1
- package/dist/{chunk-WS4FOGLY.js → chunk-XNZWWDC3.js} +2 -2
- package/dist/{chunk-P7GTE4FX.js → chunk-XRECG65F.js} +2 -2
- package/dist/chunk-XTQ7MEJG.js +457 -0
- package/dist/{chunk-GEVQNST4.js → chunk-XZWSP67B.js} +2 -2
- package/dist/{chunk-OEMFEL26.js → chunk-Y3M2T5RO.js} +3 -3
- package/dist/{chunk-HP7KJ5O2.js → chunk-YPQOPFP7.js} +6 -6
- package/dist/{chunk-MEHBW2SP.js → chunk-YQYINEJI.js} +6 -6
- package/dist/{chunk-RXI7K4J5.js → chunk-ZF6CV4WZ.js} +1338 -217
- package/dist/{chunk-3XE4NRLF.js → chunk-ZKJU55MX.js} +4 -2
- package/dist/{chunk-526ABLHD.js → chunk-ZKQ63XZ5.js} +12 -35
- package/dist/{chunk-PAQVETLA.js → chunk-ZKVMDNAN.js} +5 -5
- package/dist/cli/index.js +47 -37
- package/dist/{clio-BPBAPZGA.js → clio-JUDCLNHC.js} +7 -7
- package/dist/clio-context-tools-FT672MNZ.js +136 -0
- package/dist/{code-nav-KBQ3RIFK.js → code-nav-7OVCTP2Z.js} +28 -26
- package/dist/{components-6DKAXWFN.js → components-OZXZMDWU.js} +5 -5
- package/dist/{config-FWL5JK2H.js → config-P3ZQVF63.js} +80 -75
- package/dist/{config-graph-ACDB26L2.js → config-graph-GKAOBAS5.js} +74 -70
- package/dist/{configure-ZIZ4YV62.js → configure-25LRYZJG.js} +45 -44
- package/dist/context-72VQV7JX.js +73 -0
- package/dist/{context-VIL5MFRH.js → context-COWRWXP3.js} +74 -70
- package/dist/{context-Z3GOBHDK.js → context-WB3D474G.js} +12 -12
- package/dist/{context-clear-ZPB3L7MN.js → context-clear-Y4LYMWC6.js} +70 -66
- package/dist/{context-working-set-2GLBVF7L.js → context-working-set-TMZYVC7A.js} +15 -14
- package/dist/data-tool-KMBWKKWR.js +2987 -0
- package/dist/{detail-VSBWYSGZ.js → detail-4GLROGY5.js} +71 -67
- package/dist/{dispatch-runner-EEEHGKX4.js → dispatch-runner-TR3LBP5P.js} +92 -88
- package/dist/{docs-VMMWFVHL.js → docs-ZK777W2G.js} +3 -3
- package/dist/{doctor-V5L4IQGP.js → doctor-WC4OV2OB.js} +61 -59
- package/dist/{eval-VJYJ2BV3.js → eval-FUNWJNUJ.js} +45 -45
- package/dist/{evals-J2H5V6DD.js → evals-L73EBHSU.js} +4 -4
- package/dist/{evidence-Y35YSXH6.js → evidence-AVJ2RTHX.js} +80 -76
- package/dist/{evidence-POWAEBEW.js → evidence-P4NZOAK7.js} +70 -66
- package/dist/{evidence-XCCCLS36.js → evidence-Y55WKYHH.js} +79 -75
- package/dist/{evolve-GPG72VB6.js → evolve-MAWKRYWF.js} +75 -71
- package/dist/{extensions-YIZ7W3DA.js → extensions-PYQH3H47.js} +8 -8
- package/dist/{fleet-2MDCQ2VK.js → fleet-M4H2QBWT.js} +84 -80
- package/dist/{fleet-W2AF3TOJ.js → fleet-ULBXXRDR.js} +123 -119
- package/dist/{fleet-commands-WS56G26H.js → fleet-commands-7NNC4JZU.js} +17 -16
- package/dist/{fleet-decisions-MYGZ3JW7.js → fleet-decisions-EHEY7KPU.js} +9 -9
- package/dist/{fleet-graph-ELTBXBAV.js → fleet-graph-LQ2AHZT3.js} +15 -15
- package/dist/{fleet-inspect-45IK73L4.js → fleet-inspect-5IGBIIYZ.js} +76 -72
- package/dist/{fleet-preflight-T5NCZKHE.js → fleet-preflight-UTTNOKMG.js} +7 -2
- package/dist/{fleet-validate-VG4JBLA4.js → fleet-validate-YGILJKA6.js} +20 -20
- package/dist/{fleet-verify-VTSABAYO.js → fleet-verify-AUWTCDKR.js} +70 -66
- package/dist/{fleet-view-XC7WMKSS.js → fleet-view-TGUFCY5A.js} +73 -69
- package/dist/{init-VTJNNTIP.js → init-GFN2Z7NF.js} +94 -90
- package/dist/{interop-IU6BNA7C.js → interop-EF5JUWWK.js} +21 -21
- package/dist/{inventory-QPQUTUUZ.js → inventory-I6VJDDYF.js} +73 -69
- package/dist/{library-Q3NWL5CM.js → library-3J5IO2OJ.js} +13 -13
- package/dist/{library-LF7DLU6S.js → library-IJTOFTDS.js} +16 -15
- package/dist/{library-HUTAS57T.js → library-Z3KZSHPE.js} +27 -27
- package/dist/{library-import-PPW4YUC5.js → library-import-IMKFLDHX.js} +21 -21
- package/dist/{library-inventory-DIRVFRZU.js → library-inventory-CQP5D6GU.js} +13 -13
- package/dist/{library-validation-XHUZ3INE.js → library-validation-33UTX7AI.js} +11 -11
- package/dist/mcp-5FWZKUS6.js +26 -0
- package/dist/{memory-Z2LSKNV6.js → memory-36CWN67Y.js} +75 -71
- package/dist/{models-3FTREYPY.js → models-KQ6PQJJQ.js} +49 -49
- package/dist/{monitor-HF2MQRB3.js → monitor-XX6ZD6KC.js} +83 -79
- package/dist/{orchestrator-UYPA6W4S.js → orchestrator-SK6HDAOP.js} +1337 -772
- package/dist/{panes-736XB73K.js → panes-L27QRXUH.js} +12 -12
- package/dist/{panes-C4P3PIWT.js → panes-LXZIWNOZ.js} +4 -4
- package/dist/{paths-JQ4QYIYB.js → paths-7DJDU4P4.js} +5 -5
- package/dist/{preload-XSLRYH3Y.js → preload-K2TDYI4Y.js} +70 -66
- package/dist/{reset-V7QSPZYE.js → reset-73VU3XNT.js} +10 -10
- package/dist/{resources-OJGDFHYT.js → resources-3BYJYVYL.js} +16 -16
- package/dist/{run-T354SMDW.js → run-AR7PY6GW.js} +132 -128
- package/dist/run-script-H2KUDIII.js +41 -0
- package/dist/{settings-MWZXO6RV.js → settings-3VMBMWID.js} +4 -4
- package/dist/{share-Y5ZKJPND.js → share-HVWH4FPF.js} +21 -21
- package/dist/{skills-4GLZC4LN.js → skills-5TYEY75Q.js} +22 -22
- package/dist/{skills-eval-L5T7GODM.js → skills-eval-CK3MD63Z.js} +96 -87
- package/dist/{skills-inventory-LASKXEZI.js → skills-inventory-KSFYG3I6.js} +16 -16
- package/dist/{slash-commands-OIMRU36D.js → slash-commands-DMVHML7P.js} +47 -44
- package/dist/{steer-KMAM2Y2C.js → steer-XCD6V3EW.js} +4 -4
- package/dist/{support-AWC3XKPX.js → support-JAZL6VBR.js} +7 -7
- package/dist/{system-CD75NYBU.js → system-3TDQRMZZ.js} +26 -26
- package/dist/{targets-VSZX2J2S.js → targets-EZKHAYRN.js} +66 -65
- package/dist/{tasks-AN567LYH.js → tasks-SNEFHWYW.js} +12 -12
- package/dist/{terminal-lease-6KTWNHVY.js → terminal-lease-BNLFA4OF.js} +8 -7
- package/dist/{tools-AGRK3V5J.js → tools-7IM6BDCE.js} +12 -12
- package/dist/{trace-P4MQZD33.js → trace-C3MBGD4S.js} +8 -8
- package/dist/{tui-primitives-A6MUW32V.js → tui-primitives-36BY2774.js} +2 -2
- package/dist/{uninstall-BRUTEPIC.js → uninstall-WWLOSZ42.js} +10 -10
- package/dist/{upgrade-SBCGHQGJ.js → upgrade-ETK2JNSJ.js} +37 -47
- package/dist/{usage-BIDVTO6V.js → usage-S2HOB7BZ.js} +93 -89
- package/dist/{verifiers-LTGCAMXS.js → verifiers-QDTEVH7R.js} +24 -22
- package/dist/{verify-OZU4LLNP.js → verify-SUKUK6XM.js} +15 -14
- package/dist/web/client/assets/{abnfDiagram-VCTEODGH-C5wfoHm3.js → abnfDiagram-VCTEODGH-BznNOe_I.js} +1 -1
- package/dist/web/client/assets/{arc-oH91sfLl.js → arc-Pry-7G1_.js} +1 -1
- package/dist/web/client/assets/architecture-7GRP2DOG-BQO9DL_y.js +1 -0
- package/dist/web/client/assets/architectureDiagram-5GKGNRK7-CKPbdvyx.js +36 -0
- package/dist/web/client/assets/{blockDiagram-I7D4REHJ-wi0_CYHL.js → blockDiagram-I7D4REHJ-prINbpbM.js} +1 -1
- package/dist/web/client/assets/{c4Diagram-7LVT6UL2-DV7NYc2W.js → c4Diagram-7LVT6UL2-DkVRKtyU.js} +1 -1
- package/dist/web/client/assets/channel-c4EeUUqA.js +1 -0
- package/dist/web/client/assets/{chunk-4HAMMTFA-Bhsegh3F.js → chunk-4HAMMTFA-CV0Z1ApC.js} +1 -1
- package/dist/web/client/assets/{chunk-75Z2AOVW-RQCckcQG.js → chunk-75Z2AOVW-D34Nohxq.js} +1 -1
- package/dist/web/client/assets/chunk-DU6HZSFF-Das_cS-O.js +125 -0
- package/dist/web/client/assets/{chunk-F27PBJKO-BcNqv2x1.js → chunk-F27PBJKO-CmRojxkG.js} +1 -1
- package/dist/web/client/assets/{chunk-GMAD6QVW-Bq8JWiZT.js → chunk-GMAD6QVW-ClYZmc73.js} +1 -1
- package/dist/web/client/assets/{chunk-GVQU2GXP-DbchrAeJ.js → chunk-GVQU2GXP-B2BiHFt-.js} +1 -1
- package/dist/web/client/assets/{chunk-IMKFNOWR-DrpvjocK.js → chunk-IMKFNOWR-BGApZ8nP.js} +1 -1
- package/dist/web/client/assets/{chunk-L3NEJ4N5-DHLmTnCe.js → chunk-L3NEJ4N5-DIzCgpkY.js} +1 -1
- package/dist/web/client/assets/{chunk-OSK3NFVY-CsRbtN9n.js → chunk-OSK3NFVY-BPT-yePv.js} +1 -1
- package/dist/web/client/assets/{chunk-P2QGCYS3-aqPbxoMh.js → chunk-P2QGCYS3-BC1WTc2t.js} +1 -1
- package/dist/web/client/assets/{chunk-POPQ4Y6H-Dl5vw0h0.js → chunk-POPQ4Y6H-BSjzBmZU.js} +1 -1
- package/dist/web/client/assets/{chunk-PWAF6VOD-D7OBRSdO.js → chunk-PWAF6VOD-BIjLs86Z.js} +1 -1
- package/dist/web/client/assets/{chunk-SHT3W25Y-CdKCIwuB.js → chunk-SHT3W25Y-CduDz2cm.js} +1 -1
- package/dist/web/client/assets/{chunk-SVP7TREG-B94Nu_1-.js → chunk-SVP7TREG-XUn4pPAP.js} +1 -1
- package/dist/web/client/assets/{chunk-TICWLB2K-jjRQI_hk.js → chunk-TICWLB2K-DcBy2DKy.js} +1 -1
- package/dist/web/client/assets/{chunk-XXDRQBXY-azkbKE37.js → chunk-XXDRQBXY-DqAYeljN.js} +1 -1
- package/dist/web/client/assets/classDiagram-ZZMXUADV-C0HR7YKN.js +1 -0
- package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-C0HR7YKN.js +1 -0
- package/dist/web/client/assets/cose-bilkent-JH36ORCC-CVh1icwb.js +1 -0
- package/dist/web/client/assets/{cynefin-OW5HDTMX--i9RDU5f.js → cynefin-OW5HDTMX-BecPbyM6.js} +1 -1
- package/dist/web/client/assets/{cynefinDiagram-5FMLGOSQ-cRcvyBdp.js → cynefinDiagram-5FMLGOSQ-BzMzFwyT.js} +1 -1
- package/dist/web/client/assets/{dagre-GXQ25YYZ-C9mZmAPe.js → dagre-GXQ25YYZ-CAWJmRMG.js} +1 -1
- package/dist/web/client/assets/{diagram-S7CK7UJ4-Oj_zlLDY.js → diagram-S7CK7UJ4--lp0ALmn.js} +1 -1
- package/dist/web/client/assets/{diagram-UQ7AKVKN-BWKbbfcR.js → diagram-UQ7AKVKN-gKUnsjcj.js} +1 -1
- package/dist/web/client/assets/{diagram-VSXAHHWV-C3N3f7DO.js → diagram-VSXAHHWV-BoN5D1Gh.js} +1 -1
- package/dist/web/client/assets/{diagram-VX7I27RA-CtDKDAWe.js → diagram-VX7I27RA-BMtuswQ6.js} +1 -1
- package/dist/web/client/assets/{diagram-Z3DM3KII-B8bkBRCz.js → diagram-Z3DM3KII-DtU8LXrA.js} +1 -1
- package/dist/web/client/assets/dist-v5Q1xZ2K.js +1 -0
- package/dist/web/client/assets/{ebnfDiagram-PWID7BFC-CskW-Whx.js → ebnfDiagram-PWID7BFC-DDcnv9BH.js} +1 -1
- package/dist/web/client/assets/{erDiagram-RLTQ6QDP-C-83cgCW.js → erDiagram-RLTQ6QDP-DvRb4iDL.js} +1 -1
- package/dist/web/client/assets/eventmodeling-NTZA5JFV-01h4SbTc.js +1 -0
- package/dist/web/client/assets/flowDiagram-HODETNUW-Bll_YZ6T.js +1 -0
- package/dist/web/client/assets/framework-YGoR34M4.js +9 -0
- package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-DdFZ9hvH.js +292 -0
- package/dist/web/client/assets/{gitGraph-4MIJSDKK-B40cQtBE.js → gitGraph-4MIJSDKK-DjD_AAMj.js} +1 -1
- package/dist/web/client/assets/{gitGraphDiagram-WWUBYQGX-CH-7P7Di.js → gitGraphDiagram-WWUBYQGX-CDyN9n2u.js} +1 -1
- package/dist/web/client/assets/index-CVcy0FAn.js +75 -0
- package/dist/web/client/assets/{info-A6RAGUB7-eCCE9D_Q.js → info-A6RAGUB7-C_XK8Hq0.js} +1 -1
- package/dist/web/client/assets/{infoDiagram-27XIBGKW-DRnvqMjM.js → infoDiagram-27XIBGKW-DIe4tJ1i.js} +1 -1
- package/dist/web/client/assets/{ishikawaDiagram-5VMMS53U-DBA_55wp.js → ishikawaDiagram-5VMMS53U-CVl5unKI.js} +1 -1
- package/dist/web/client/assets/{journeyDiagram-3NMN7TZE-C-ewpNl7.js → journeyDiagram-3NMN7TZE-CPEB1Vt8.js} +1 -1
- package/dist/web/client/assets/{kanban-definition-UXKFOSKX-B1taGsu0.js → kanban-definition-UXKFOSKX-j-9WXjYQ.js} +1 -1
- package/dist/web/client/assets/{line-lZ0kyxDI.js → line-BIDAcEtX.js} +1 -1
- package/dist/web/client/assets/{linear-DIc8FgZj.js → linear-SzVmGSw4.js} +1 -1
- package/dist/web/client/assets/{mermaid-parser.core-SXolxpXw.js → mermaid-parser.core-Ux4vssh0.js} +3 -3
- package/dist/web/client/assets/{mermaid.core-DXgQtkNg.js → mermaid.core-CDuwVKeh.js} +4 -4
- package/dist/web/client/assets/{mindmap-definition-YA3MSWOX-DXJas9K7.js → mindmap-definition-YA3MSWOX-5MlQEeQ6.js} +1 -1
- package/dist/web/client/assets/{packet-AYTQ26CC-OqVl_07o.js → packet-AYTQ26CC-DRO66c9Q.js} +1 -1
- package/dist/web/client/assets/{pegDiagram-XKGWAZYB-CPE_zdfN.js → pegDiagram-XKGWAZYB-BakZnSjM.js} +1 -1
- package/dist/web/client/assets/{pie-WAS4IAKB-DAxnMBLe.js → pie-WAS4IAKB-BEzcmhz8.js} +1 -1
- package/dist/web/client/assets/{pieDiagram-E7YTZNPT-Uwjpufxv.js → pieDiagram-E7YTZNPT-DpmqgoPt.js} +1 -1
- package/dist/web/client/assets/prism-core-BtsZdCS6.js +1 -0
- package/dist/web/client/assets/{quadrantDiagram-AXDQQJYC-Bjs4yjgs.js → quadrantDiagram-AXDQQJYC-DuMPwwV2.js} +1 -1
- package/dist/web/client/assets/{radar-RG4KPBEZ-CuCKjHM3.js → radar-RG4KPBEZ-Djm3oXl5.js} +1 -1
- package/dist/web/client/assets/{railroad-74A4TZTK-CnQK-Cc1.js → railroad-74A4TZTK-Cpvkwsao.js} +1 -1
- package/dist/web/client/assets/railroad-abnf-HS5TGJTU-CpRQ3ruG.js +1 -0
- package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-1Rm5a5eJ.js +1 -0
- package/dist/web/client/assets/railroad-peg-WCYAUIDC-C8344cTh.js +1 -0
- package/dist/web/client/assets/{railroadDiagram-O6MQD6OU-BDDIPNLL.js → railroadDiagram-O6MQD6OU-CgMS4B6R.js} +1 -1
- package/dist/web/client/assets/{requirementDiagram-BXWQKSXE-B5Ydyxc1.js → requirementDiagram-BXWQKSXE-CX5eoNdN.js} +1 -1
- package/dist/web/client/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/web/client/assets/{sankeyDiagram-P5KCCOFB-Bw2Qym75.js → sankeyDiagram-P5KCCOFB-CODBX2N3.js} +1 -1
- package/dist/web/client/assets/{sequenceDiagram-WJ2MYXX4-BZf36nO0.js → sequenceDiagram-WJ2MYXX4-eUhhF8OR.js} +1 -1
- package/dist/web/client/assets/src-B6xuSHsQ.js +1 -0
- package/dist/web/client/assets/{stateDiagram-D77RDMKH-DnH6JRkp.js → stateDiagram-D77RDMKH-vR6l0NC2.js} +1 -1
- package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-BYUm8jLY.js +1 -0
- package/dist/web/client/assets/{swimlanes-42K2YHIH-D9PZmtZg.js → swimlanes-42K2YHIH-v9Tw_zX7.js} +1 -1
- package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-B2jdCvhD.js +8 -0
- package/dist/web/client/assets/{timeline-definition-24CTP7MA-CrkZZBo7.js → timeline-definition-24CTP7MA-DxianbhU.js} +1 -1
- package/dist/web/client/assets/{treeView-Q6P3EWNA-C9zDShoC.js → treeView-Q6P3EWNA-DLxnZb9R.js} +1 -1
- package/dist/web/client/assets/{treemap-WGGIJYW6-B-plj9yK.js → treemap-WGGIJYW6-I7UnZ6xJ.js} +1 -1
- package/dist/web/client/assets/{vennDiagram-4TSXK5OY-VeOdaphN.js → vennDiagram-4TSXK5OY-Ar7M8Mrj.js} +1 -1
- package/dist/web/client/assets/{wardley-WFR3VGLG-Wl1P_m6L.js → wardley-WFR3VGLG-B8ZSHeDu.js} +1 -1
- package/dist/web/client/assets/{wardleyDiagram-VM6X3IG4-B85-R9t2.js → wardleyDiagram-VM6X3IG4-Qks2FmIn.js} +1 -1
- package/dist/web/client/assets/{xychartDiagram-S5SC5T6Z-BrD8OW8m.js → xychartDiagram-S5SC5T6Z-BBBjRtiE.js} +1 -1
- package/dist/web/client/index.html +3 -1
- package/dist/web/reads-worker.js +17 -17
- package/dist/web/server.js +25 -25
- package/dist/{web-EU6ATJWA.js → web-EMGUGGEI.js} +3 -3
- package/dist/{web-fetch-U22Q7HV4.js → web-fetch-6BRRVQON.js} +22 -5
- package/dist/{wiki-generate-LZRLFG6I.js → wiki-generate-XHSJTDOP.js} +104 -100
- package/dist/{with-panes-X5CZOGML.js → with-panes-Z6BTEHMZ.js} +11 -11
- package/dist/worker/entry.js +101 -87
- package/docs/README.md +1 -1
- package/docs/architecture/artifact-placement.md +12 -1
- package/docs/architecture/library.md +1 -1
- package/docs/architecture/prompt-envelope-and-tools.md +37 -27
- package/docs/architecture/safety-model.md +19 -6
- package/docs/architecture/session-lifecycle.md +10 -4
- package/docs/architecture/tui-design.md +37 -14
- package/docs/guide/commands-and-modes.md +39 -19
- package/docs/guide/configuration-and-targets.md +7 -5
- package/docs/guide/configuration-reference.md +116 -13
- package/docs/guide/environment-variables.md +2 -5
- package/docs/guide/fleet-dispatch.md +3 -2
- package/docs/guide/glossary.md +1 -1
- package/docs/guide/installation-and-lifecycle.md +2 -2
- package/docs/guide/resource-library.md +1 -1
- package/docs/guide/tool-usage.md +166 -64
- package/docs/guide/troubleshooting.md +1 -1
- package/docs/process/documentation-guide.md +1 -0
- package/docs/process/fleet-demo-runbook.md +6 -6
- package/docs/process/scientific-validation.md +22 -2
- package/docs/process/tool-audit-v0.4.9.md +357 -0
- package/package.json +1 -1
- package/src/cli/argv.ts +31 -0
- package/src/cli/clio.ts +17 -13
- package/src/cli/config.ts +4 -0
- package/src/cli/doctor-panes.ts +1 -1
- package/src/cli/doctor.ts +3 -1
- package/src/cli/index.ts +14 -1
- package/src/cli/mcp.ts +106 -0
- package/src/cli/skills-eval.ts +8 -2
- package/src/cli/upgrade.ts +34 -38
- package/src/cli/verifiers.ts +1 -1
- package/src/core/bash-exec.ts +27 -4
- package/src/core/config.ts +2 -1
- package/src/core/defaults.ts +10 -1
- package/src/core/event-bus.ts +1 -1
- package/src/core/run-records.ts +438 -0
- package/src/core/safe-exec.ts +506 -56
- package/src/core/skill-activation.ts +5 -3
- package/src/core/termination.ts +3 -3
- package/src/core/tool-names.ts +33 -12
- package/src/domains/context/clio-md.ts +1 -1
- package/src/domains/context/worker/pressure.ts +6 -2
- package/src/domains/context/working-set/path-index.ts +7 -2
- package/src/domains/dispatch/code-step.ts +7 -2
- package/src/domains/dispatch/contract.ts +7 -0
- package/src/domains/dispatch/execution-role.ts +3 -3
- package/src/domains/dispatch/execution-scheduler.ts +204 -79
- package/src/domains/dispatch/extension.ts +131 -14
- package/src/domains/dispatch/fleet-preflight.ts +18 -3
- package/src/domains/dispatch/fleet-run.ts +1 -0
- package/src/domains/dispatch/gate-decisions.ts +3 -3
- package/src/domains/dispatch/heartbeat.ts +1 -1
- package/src/domains/dispatch/host-verification.ts +16 -14
- package/src/domains/dispatch/receipt-findings.ts +3 -4
- package/src/domains/dispatch/reservation-store.ts +7 -9
- package/src/domains/dispatch/route-facts.ts +1 -1
- package/src/domains/dispatch/state.ts +3 -3
- package/src/domains/dispatch/types.ts +1 -1
- package/src/domains/evidence/build.ts +35 -7
- package/src/domains/gateway/mcp/client.ts +923 -0
- package/src/domains/gateway/mcp/config.ts +456 -0
- package/src/domains/gateway/mcp/index.ts +88 -0
- package/src/domains/gateway/mcp/protocol.ts +273 -0
- package/src/domains/gateway/mcp/trust.ts +263 -0
- package/src/domains/lifecycle/doctor.ts +17 -3
- package/src/domains/mux/contract.ts +5 -9
- package/src/domains/mux/detect.ts +1 -1
- package/src/domains/mux/extension.ts +1 -1
- package/src/domains/mux/protocol.ts +5 -7
- package/src/domains/mux/socket-client.ts +3 -3
- package/src/domains/observability/accountability.ts +2 -2
- package/src/domains/observability/contract.ts +7 -3
- package/src/domains/observability/evidence-index.ts +2 -2
- package/src/domains/observability/projection.ts +10 -0
- package/src/domains/prompts/compiler.ts +41 -23
- package/src/domains/prompts/fragments/identity/docs-routing.md +2 -2
- package/src/domains/prompts/fragments/operating/skills.md +12 -10
- package/src/domains/providers/index.ts +5 -1
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +42 -10
- package/src/domains/providers/probe/http.ts +44 -59
- package/src/domains/providers/probe/reasoning.ts +2 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +20 -31
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +9 -4
- package/src/domains/safety/action-classifier.ts +17 -1
- package/src/domains/safety/call-target.ts +14 -0
- package/src/domains/safety/contract.ts +1 -1
- package/src/domains/safety/finish-contract.ts +9 -3
- package/src/domains/safety/loop-detector.ts +1 -1
- package/src/domains/safety/policy-engine.ts +7 -0
- package/src/domains/safety/protected-artifacts.ts +3 -4
- package/src/domains/safety/rejection-feedback.ts +1 -1
- package/src/domains/safety/scope.ts +2 -3
- package/src/domains/safety/validation-contract.ts +3 -2
- package/src/domains/scheduling/budget.ts +5 -3
- package/src/domains/scheduling/contract.ts +2 -1
- package/src/domains/scheduling/extension.ts +3 -3
- package/src/domains/session/compaction/branch-summary.ts +1 -1
- package/src/domains/session/compaction/compact.ts +25 -2
- package/src/domains/session/compaction/cut-point.ts +1 -1
- package/src/domains/session/compaction/tokens.ts +5 -10
- package/src/domains/session/contract.ts +1 -1
- package/src/domains/session/cwd-fallback.ts +2 -2
- package/src/domains/session/handoff.ts +5 -1
- package/src/domains/session/history.ts +6 -0
- package/src/domains/session/manager.ts +14 -7
- package/src/domains/session/retry.ts +2 -1
- package/src/domains/session/session-artifacts.ts +12 -7
- package/src/domains/session/task-board.ts +2 -3
- package/src/engine/apis/openai-completions.ts +8 -0
- package/src/engine/loop-guard.ts +7 -0
- package/src/engine/provider-error-body.ts +65 -0
- package/src/engine/session.ts +118 -29
- package/src/engine/worker-runtime.ts +3 -0
- package/src/entry/boot-options.ts +2 -0
- package/src/entry/orchestrator.ts +18 -2
- package/src/interactive/chat-loop-policy.ts +1 -0
- package/src/interactive/chat-loop.ts +8 -4
- package/src/interactive/chat-panel.ts +23 -9
- package/src/interactive/chat-renderer.ts +33 -11
- package/src/interactive/clio-editor.ts +13 -5
- package/src/interactive/dispatch-board.ts +70 -30
- package/src/interactive/editor-submit.ts +30 -25
- package/src/interactive/footer/widgets.ts +12 -9
- package/src/interactive/interactive-presentation.ts +2 -3
- package/src/interactive/mux-bridge.ts +3 -7
- package/src/interactive/overlay-general-openers.ts +14 -7
- package/src/interactive/overlays/cwd-fallback.ts +1 -1
- package/src/interactive/overlays/library-model.ts +114 -35
- package/src/interactive/overlays/library.ts +52 -5
- package/src/interactive/overlays/list-overlay.ts +4 -1
- package/src/interactive/overlays/session-selector.ts +28 -23
- package/src/interactive/overlays/settings.ts +20 -1
- package/src/interactive/overlays/tree-selector.ts +47 -10
- package/src/interactive/panes-runtime.ts +1 -1
- package/src/interactive/renderers/compaction-summary.ts +1 -1
- package/src/interactive/renderers/provider-error.ts +118 -0
- package/src/interactive/renderers/retry-status.ts +59 -23
- package/src/interactive/renderers/tool-execution.ts +67 -7
- package/src/interactive/renderers/worker-answer.ts +198 -0
- package/src/interactive/renderers/worker-entry.ts +57 -163
- package/src/interactive/slash-commands.ts +26 -1
- package/src/interactive/slash-spec.ts +2 -2
- package/src/interactive/theme/labels.ts +66 -30
- package/src/interactive/turn-persistence.ts +6 -1
- package/src/interactive/turn-recovery.ts +2 -1
- package/src/interactive/turn-runtime.ts +22 -5
- package/src/interactive/view/artifacts.ts +3 -2
- package/src/interactive/view/view-overlay.ts +96 -20
- package/src/tools/agent-tools.ts +21 -7
- package/src/tools/artifact.ts +16 -8
- package/src/tools/bash.ts +90 -11
- package/src/tools/bootstrap.ts +42 -4
- package/src/tools/builtin-tool-catalog.ts +72 -7
- package/src/tools/context/index.ts +27 -31
- package/src/tools/context/library.ts +3 -1
- package/src/tools/context/surface.ts +12 -26
- package/src/tools/core-bootstrap.ts +72 -1
- package/src/tools/data/csv.ts +1189 -0
- package/src/tools/data/index.ts +308 -0
- package/src/tools/data/json.ts +1799 -0
- package/src/tools/data/jsonl.ts +561 -0
- package/src/tools/data/shared.ts +452 -0
- package/src/tools/edit-diff.ts +0 -7
- package/src/tools/edit.ts +115 -18
- package/src/tools/file-mutation-queue.ts +114 -4
- package/src/tools/find.ts +108 -29
- package/src/tools/gateway/caps.ts +11 -0
- package/src/tools/gateway/clio-context-surface.ts +41 -0
- package/src/tools/gateway/clio-context-tools.ts +77 -0
- package/src/tools/gateway/data-surface.ts +138 -0
- package/src/tools/gateway/data-tool.ts +201 -0
- package/src/tools/gateway/index.ts +342 -0
- package/src/tools/gateway/mcp-capabilities.ts +469 -0
- package/src/tools/grep.ts +88 -33
- package/src/tools/harness-extensions.ts +3 -0
- package/src/tools/ignore-policy.ts +7 -5
- package/src/tools/ls.ts +111 -20
- package/src/tools/policy.ts +35 -13
- package/src/tools/presentation.ts +6 -0
- package/src/tools/read.ts +634 -156
- package/src/tools/registry.ts +94 -6
- package/src/tools/run-script.ts +997 -0
- package/src/tools/spawn-hygiene.ts +157 -5
- package/src/tools/surface.ts +163 -0
- package/src/tools/verify/authoring.ts +8 -3
- package/src/tools/verify/catalog.ts +14 -1
- package/src/tools/verify/numeric.ts +367 -59
- package/src/tools/verify/perf.ts +171 -9
- package/src/tools/verify/scripts.ts +178 -26
- package/src/tools/web-fetch-surface.ts +22 -0
- package/src/tools/web-fetch.ts +26 -1
- package/src/tools/write.ts +45 -19
- package/dist/chunk-B6UM3OZC.js +0 -15
- package/dist/chunk-GJ24ODAX.js +0 -196
- package/dist/chunk-LZCCBLRC.js +0 -57
- package/dist/web/client/assets/architecture-7GRP2DOG-EGozsYEO.js +0 -1
- package/dist/web/client/assets/architectureDiagram-5GKGNRK7-z9FokbJl.js +0 -36
- package/dist/web/client/assets/channel-CiUWbHN9.js +0 -1
- package/dist/web/client/assets/chunk-DU6HZSFF-C5USiOAy.js +0 -125
- package/dist/web/client/assets/classDiagram-ZZMXUADV-CMODalmo.js +0 -1
- package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-CMODalmo.js +0 -1
- package/dist/web/client/assets/cose-bilkent-JH36ORCC-RbQL23qS.js +0 -1
- package/dist/web/client/assets/dist-D0_q-A2b.js +0 -1
- package/dist/web/client/assets/eventmodeling-NTZA5JFV-DjVq8wKU.js +0 -1
- package/dist/web/client/assets/flowDiagram-HODETNUW-BUiCBGXt.js +0 -1
- package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-B2lb8lvF.js +0 -292
- package/dist/web/client/assets/index-FSIC94pi.js +0 -83
- package/dist/web/client/assets/prism-core-BngLtbUd.js +0 -1
- package/dist/web/client/assets/railroad-abnf-HS5TGJTU-BcW01NZG.js +0 -1
- package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-B7YCEDDd.js +0 -1
- package/dist/web/client/assets/railroad-peg-WCYAUIDC-DjNZbSTZ.js +0 -1
- package/dist/web/client/assets/src-yeptRXpt.js +0 -1
- package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-C7QtHeK4.js +0 -1
- package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-CBZlPFky.js +0 -8
package/src/tools/read.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
247
|
-
shownCount:
|
|
248
|
-
totalCount:
|
|
249
|
-
totalBytes,
|
|
250
|
-
truncated,
|
|
251
|
-
...(
|
|
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
|
};
|