@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
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
import { closeSync, openSync, realpathSync, statSync, writeSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import {
|
|
5
|
+
captureFileRef,
|
|
6
|
+
captureOutputRef,
|
|
7
|
+
createRunRecord,
|
|
8
|
+
observeFileHash,
|
|
9
|
+
RUN_RECORDS_DEFAULT_KEEP,
|
|
10
|
+
type RunFileRef,
|
|
11
|
+
type RunManifest,
|
|
12
|
+
type RunOutcome,
|
|
13
|
+
type RunOutputRef,
|
|
14
|
+
type RunRecordPaths,
|
|
15
|
+
sweepRunRecords,
|
|
16
|
+
workspaceRelativePath,
|
|
17
|
+
writeRunManifest,
|
|
18
|
+
} from "../core/run-records.js";
|
|
19
|
+
import {
|
|
20
|
+
createRetainedStreamWindow,
|
|
21
|
+
resolveSafeCwd,
|
|
22
|
+
runCommandVector,
|
|
23
|
+
SAFE_EXEC_GROUP_TEARDOWN_BOUND_MS,
|
|
24
|
+
type SafeCommandResult,
|
|
25
|
+
} from "../core/safe-exec.js";
|
|
26
|
+
import type { DynamicToolName } from "../core/tool-names.js";
|
|
27
|
+
import { isSecretArgKey } from "../domains/safety/redaction.js";
|
|
28
|
+
import { findExecutableOnPath } from "../domains/toolchain/resolve.js";
|
|
29
|
+
import { StringEnum } from "../engine/ai.js";
|
|
30
|
+
import type { ToolSurface } from "./lazy-tool.js";
|
|
31
|
+
import type { ToolInvokeOptions, ToolResult, ToolSpec } from "./registry.js";
|
|
32
|
+
import { formatSize } from "./truncate.js";
|
|
33
|
+
import { byteLength } from "./truncate-utf8.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The run_script tool: observable scientific processing. One explicit
|
|
37
|
+
* interpreter runs one script from the workspace with explicit arguments and
|
|
38
|
+
* working directory. The complete stdout and stderr stream to log files under
|
|
39
|
+
* `.clio-coder/runs/<runId>/`, a manifest records what ran (script hash,
|
|
40
|
+
* argv, cwd, declared environment keys, timing, outcome, declared inputs and
|
|
41
|
+
* outputs with their observed state), and the model receives a bounded
|
|
42
|
+
* summary with the tails of both streams. Nothing is installed, retried, or
|
|
43
|
+
* changed in the environment beyond the declared variables and the recorded
|
|
44
|
+
* interpreter defaults.
|
|
45
|
+
*
|
|
46
|
+
* Declared inputs and outputs are provenance, not isolation: they say what the
|
|
47
|
+
* caller expected the script to read and write so the result can report what
|
|
48
|
+
* actually appeared. The safety policy still admits the run as a shell
|
|
49
|
+
* execution through the `safetyCall` projection, and the operating system is
|
|
50
|
+
* the only thing bounding what the script itself touches.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** Interpreters run_script will resolve on PATH. Anything else is refused by name. */
|
|
54
|
+
export const RUN_SCRIPT_INTERPRETERS = [
|
|
55
|
+
"python3",
|
|
56
|
+
"python",
|
|
57
|
+
"node",
|
|
58
|
+
"bash",
|
|
59
|
+
"sh",
|
|
60
|
+
"Rscript",
|
|
61
|
+
"julia",
|
|
62
|
+
"perl",
|
|
63
|
+
"ruby",
|
|
64
|
+
"octave",
|
|
65
|
+
] as const;
|
|
66
|
+
|
|
67
|
+
export type RunScriptInterpreter = (typeof RUN_SCRIPT_INTERPRETERS)[number];
|
|
68
|
+
|
|
69
|
+
export const RUN_SCRIPT_CAPS = Object.freeze({
|
|
70
|
+
argEntries: 64,
|
|
71
|
+
argBytes: 4096,
|
|
72
|
+
declaredRefs: 64,
|
|
73
|
+
envEntries: 32,
|
|
74
|
+
envValueBytes: 4096,
|
|
75
|
+
defaultTimeoutMs: 600_000,
|
|
76
|
+
maxTimeoutMs: 21_600_000,
|
|
77
|
+
/** Bytes of each stream kept in memory for the result tail. */
|
|
78
|
+
resultTailBytes: 8 * 1024,
|
|
79
|
+
/** Bytes of each stream shown in a live progress snapshot. */
|
|
80
|
+
progressTailBytes: 2 * 1024,
|
|
81
|
+
progressThrottleMs: 250,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Interpreter arguments applied when the caller names none. Recorded in the
|
|
86
|
+
* manifest's argv so the run never carries an invisible flag; `-u` keeps
|
|
87
|
+
* Python's streams unbuffered so the live tail and the logs reflect progress
|
|
88
|
+
* as it happens rather than at exit.
|
|
89
|
+
*/
|
|
90
|
+
export const RUN_SCRIPT_DEFAULT_INTERPRETER_ARGS: Readonly<Record<RunScriptInterpreter, ReadonlyArray<string>>> = {
|
|
91
|
+
python3: ["-u"],
|
|
92
|
+
python: ["-u"],
|
|
93
|
+
node: [],
|
|
94
|
+
bash: [],
|
|
95
|
+
sh: [],
|
|
96
|
+
Rscript: [],
|
|
97
|
+
julia: [],
|
|
98
|
+
perl: [],
|
|
99
|
+
ruby: [],
|
|
100
|
+
octave: [],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The canonical name. Widened through `string` because the branded dynamic
|
|
105
|
+
* name is the only ToolName a module outside core/tool-names.ts can mint; the
|
|
106
|
+
* bootstrap replaces it with the builtin constant when it registers the tool.
|
|
107
|
+
*/
|
|
108
|
+
export const RUN_SCRIPT_TOOL_NAME = "run_script" as string as DynamicToolName;
|
|
109
|
+
|
|
110
|
+
const ENV_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/u;
|
|
111
|
+
|
|
112
|
+
function parseStringArray(value: unknown): string[] | null {
|
|
113
|
+
if (value === undefined || value === null) return [];
|
|
114
|
+
if (!Array.isArray(value)) return null;
|
|
115
|
+
const out: string[] = [];
|
|
116
|
+
for (const entry of value) {
|
|
117
|
+
if (typeof entry !== "string") return null;
|
|
118
|
+
out.push(entry);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Tolerate the weak-model shapes: an array argument sent as a JSON string, and
|
|
125
|
+
* a single string where an array was expected. Pure and idempotent; wired as
|
|
126
|
+
* the registry `prepareArguments` hook and repeated at the top of `run` so
|
|
127
|
+
* direct callers get the same normalization.
|
|
128
|
+
*/
|
|
129
|
+
export function prepareRunScriptArguments(args: Record<string, unknown>): Record<string, unknown> {
|
|
130
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return args;
|
|
131
|
+
const next: Record<string, unknown> = { ...args };
|
|
132
|
+
for (const key of ["args", "interpreter_args", "inputs", "outputs"] as const) {
|
|
133
|
+
const value = next[key];
|
|
134
|
+
if (typeof value !== "string") continue;
|
|
135
|
+
const trimmed = value.trim();
|
|
136
|
+
if (trimmed.startsWith("[")) {
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(trimmed) as unknown;
|
|
139
|
+
if (Array.isArray(parsed)) {
|
|
140
|
+
next[key] = parsed;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Not JSON; fall through to the single-entry reading.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
next[key] = trimmed.length === 0 ? [] : [value];
|
|
148
|
+
}
|
|
149
|
+
if (typeof next.env === "string") {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(next.env) as unknown;
|
|
152
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) next.env = parsed;
|
|
153
|
+
} catch {
|
|
154
|
+
// Leave the malformed string; run() reports the shape error.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return next;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface DeclaredRef {
|
|
161
|
+
/** The argument exactly as declared. */
|
|
162
|
+
declared: string;
|
|
163
|
+
/** Workspace-relative rendering for the manifest and the result. */
|
|
164
|
+
relative: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface RunScriptRequest {
|
|
168
|
+
/** The canonical (symlink-resolved) workspace root every later path is confined to and rendered against. */
|
|
169
|
+
workspaceRoot: string;
|
|
170
|
+
interpreter: RunScriptInterpreter;
|
|
171
|
+
interpreterPath: string;
|
|
172
|
+
interpreterArgs: string[];
|
|
173
|
+
scriptDeclared: string;
|
|
174
|
+
scriptRelative: string;
|
|
175
|
+
scriptRealPath: string;
|
|
176
|
+
scriptBytes: number;
|
|
177
|
+
args: string[];
|
|
178
|
+
cwdRelative: string;
|
|
179
|
+
cwdExecution: string;
|
|
180
|
+
timeoutMs: number;
|
|
181
|
+
inputs: DeclaredRef[];
|
|
182
|
+
outputs: DeclaredRef[];
|
|
183
|
+
env: Record<string, string>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type Validation = { ok: true; request: RunScriptRequest } | { ok: false; message: string };
|
|
187
|
+
|
|
188
|
+
function invalid(message: string): Validation {
|
|
189
|
+
return { ok: false, message: `run_script: ${message}` };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isInterpreter(value: string): value is RunScriptInterpreter {
|
|
193
|
+
return (RUN_SCRIPT_INTERPRETERS as ReadonlyArray<string>).includes(value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function validateArgVector(value: unknown, field: string): string[] | string {
|
|
197
|
+
const entries = parseStringArray(value);
|
|
198
|
+
if (entries === null) return `${field} must be an array of strings`;
|
|
199
|
+
if (entries.length > RUN_SCRIPT_CAPS.argEntries) {
|
|
200
|
+
return `${field} exceeds the ${RUN_SCRIPT_CAPS.argEntries}-entry cap`;
|
|
201
|
+
}
|
|
202
|
+
for (const [index, entry] of entries.entries()) {
|
|
203
|
+
if (entry.includes("\0")) return `${field}[${index}] must not contain a NUL byte`;
|
|
204
|
+
if (byteLength(entry) > RUN_SCRIPT_CAPS.argBytes) {
|
|
205
|
+
return `${field}[${index}] exceeds the ${RUN_SCRIPT_CAPS.argBytes}-byte cap`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return entries;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function validateDeclaredRefs(value: unknown, field: string, workspaceRoot: string): DeclaredRef[] | string {
|
|
212
|
+
const entries = parseStringArray(value);
|
|
213
|
+
if (entries === null) return `${field} must be an array of workspace-relative path strings`;
|
|
214
|
+
if (entries.length > RUN_SCRIPT_CAPS.declaredRefs) {
|
|
215
|
+
return `${field} exceeds the ${RUN_SCRIPT_CAPS.declaredRefs}-entry cap`;
|
|
216
|
+
}
|
|
217
|
+
const refs: DeclaredRef[] = [];
|
|
218
|
+
const seen = new Set<string>();
|
|
219
|
+
for (const [index, entry] of entries.entries()) {
|
|
220
|
+
const location = `${field}[${index}]`;
|
|
221
|
+
if (entry.trim().length === 0) return `${location} must be a non-empty path`;
|
|
222
|
+
if (entry.includes("\0")) return `${location} must not contain a NUL byte`;
|
|
223
|
+
if (path.isAbsolute(entry) || path.win32.isAbsolute(entry)) {
|
|
224
|
+
return `${location} must be workspace-relative; absolute path '${entry}' is not allowed`;
|
|
225
|
+
}
|
|
226
|
+
const relative = workspaceRelativePath(workspaceRoot, entry);
|
|
227
|
+
if (relative === null) return `${location} escapes the workspace root: '${entry}'`;
|
|
228
|
+
if (seen.has(relative)) continue;
|
|
229
|
+
seen.add(relative);
|
|
230
|
+
refs.push({ declared: entry, relative });
|
|
231
|
+
}
|
|
232
|
+
return refs;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function validateEnv(value: unknown): Record<string, string> | string {
|
|
236
|
+
if (value === undefined || value === null) return {};
|
|
237
|
+
if (typeof value !== "object" || Array.isArray(value)) return "env must be an object of string values";
|
|
238
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
239
|
+
if (entries.length > RUN_SCRIPT_CAPS.envEntries) return `env exceeds the ${RUN_SCRIPT_CAPS.envEntries}-entry cap`;
|
|
240
|
+
const env: Record<string, string> = {};
|
|
241
|
+
for (const [key, raw] of entries) {
|
|
242
|
+
if (!ENV_KEY_PATTERN.test(key)) return `env key '${key}' must match /^[A-Z_][A-Z0-9_]*$/`;
|
|
243
|
+
if (typeof raw !== "string") return `env.${key} must be a string`;
|
|
244
|
+
if (raw.includes("\0")) return `env.${key} must not contain a NUL byte`;
|
|
245
|
+
if (byteLength(raw) > RUN_SCRIPT_CAPS.envValueBytes) {
|
|
246
|
+
return `env.${key} exceeds the ${RUN_SCRIPT_CAPS.envValueBytes}-byte cap`;
|
|
247
|
+
}
|
|
248
|
+
env[key] = raw;
|
|
249
|
+
}
|
|
250
|
+
return env;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function insideRoot(realRoot: string, candidate: string): boolean {
|
|
254
|
+
const relative = path.relative(realRoot, candidate);
|
|
255
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function validateRequest(args: Record<string, unknown>, workspaceRoot: string): Validation {
|
|
259
|
+
const interpreterArg = typeof args.interpreter === "string" ? args.interpreter.trim() : "";
|
|
260
|
+
if (interpreterArg.length === 0) return invalid("missing interpreter argument");
|
|
261
|
+
if (!isInterpreter(interpreterArg)) {
|
|
262
|
+
return invalid(
|
|
263
|
+
`interpreter must be one of ${RUN_SCRIPT_INTERPRETERS.join(", ")}; got '${interpreterArg}'. Paths and other executables are not accepted.`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const interpreter = interpreterArg;
|
|
267
|
+
const interpreterPath = findExecutableOnPath(interpreter);
|
|
268
|
+
if (interpreterPath === null) return invalid(`interpreter '${interpreter}' was not found on PATH`);
|
|
269
|
+
|
|
270
|
+
const scriptArg = typeof args.script === "string" ? args.script.trim() : "";
|
|
271
|
+
if (scriptArg.length === 0) return invalid("missing script argument");
|
|
272
|
+
if (scriptArg.includes("\0")) return invalid("script must not contain a NUL byte");
|
|
273
|
+
// One root for everything that follows. The configured root may reach the
|
|
274
|
+
// workspace through a symbolic link; confinement checks and the runner's
|
|
275
|
+
// own cwd check compare canonical paths, so the root must be canonical too.
|
|
276
|
+
let realRoot: string;
|
|
277
|
+
try {
|
|
278
|
+
realRoot = realpathSync(workspaceRoot);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return invalid(`workspace root cannot be resolved (${error instanceof Error ? error.message : String(error)})`);
|
|
281
|
+
}
|
|
282
|
+
const scriptAbsolute = path.isAbsolute(scriptArg) ? path.resolve(scriptArg) : path.resolve(realRoot, scriptArg);
|
|
283
|
+
let scriptRealPath: string;
|
|
284
|
+
let scriptBytes: number;
|
|
285
|
+
try {
|
|
286
|
+
scriptRealPath = realpathSync(scriptAbsolute);
|
|
287
|
+
const stat = statSync(scriptRealPath);
|
|
288
|
+
if (!stat.isFile()) return invalid(`script is not a regular file: ${scriptArg}`);
|
|
289
|
+
scriptBytes = stat.size;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
292
|
+
if (code === "ENOENT") return invalid(`script not found: ${scriptArg}`);
|
|
293
|
+
return invalid(`script cannot be resolved: ${error instanceof Error ? error.message : String(error)}`);
|
|
294
|
+
}
|
|
295
|
+
if (!insideRoot(realRoot, scriptRealPath)) {
|
|
296
|
+
return invalid(`script resolves outside the workspace root: ${scriptArg} -> ${scriptRealPath}`);
|
|
297
|
+
}
|
|
298
|
+
const scriptRelative = workspaceRelativePath(realRoot, scriptRealPath) ?? scriptArg;
|
|
299
|
+
|
|
300
|
+
const scriptArgs = validateArgVector(args.args, "args");
|
|
301
|
+
if (typeof scriptArgs === "string") return invalid(scriptArgs);
|
|
302
|
+
let interpreterArgs: string[];
|
|
303
|
+
if (args.interpreter_args === undefined || args.interpreter_args === null) {
|
|
304
|
+
interpreterArgs = [...RUN_SCRIPT_DEFAULT_INTERPRETER_ARGS[interpreter]];
|
|
305
|
+
} else {
|
|
306
|
+
const explicit = validateArgVector(args.interpreter_args, "interpreter_args");
|
|
307
|
+
if (typeof explicit === "string") return invalid(explicit);
|
|
308
|
+
interpreterArgs = explicit;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const cwdArg = typeof args.cwd === "string" && args.cwd.trim().length > 0 ? args.cwd.trim() : undefined;
|
|
312
|
+
let cwdExecution: string;
|
|
313
|
+
try {
|
|
314
|
+
const resolved = resolveSafeCwd(cwdArg, realRoot);
|
|
315
|
+
const stat = statSync(resolved);
|
|
316
|
+
if (!stat.isDirectory()) return invalid(`cwd is not a directory: ${cwdArg ?? "."}`);
|
|
317
|
+
cwdExecution = realpathSync(resolved);
|
|
318
|
+
if (!insideRoot(realRoot, cwdExecution)) {
|
|
319
|
+
return invalid(`cwd escapes the workspace root through a symbolic link: ${cwdArg ?? "."}`);
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
if (message.includes("escapes workspace root")) return invalid(`cwd escapes the workspace root: ${cwdArg ?? "."}`);
|
|
324
|
+
return invalid(`cwd cannot be resolved: ${cwdArg ?? "."} (${message})`);
|
|
325
|
+
}
|
|
326
|
+
const cwdRelative = workspaceRelativePath(realRoot, cwdExecution) ?? ".";
|
|
327
|
+
|
|
328
|
+
// A fractional value would floor to zero, which the runner reads as no
|
|
329
|
+
// limit at all; only whole milliseconds from 1 up are accepted.
|
|
330
|
+
let timeoutMs: number = RUN_SCRIPT_CAPS.defaultTimeoutMs;
|
|
331
|
+
if (args.timeout_ms !== undefined && args.timeout_ms !== null) {
|
|
332
|
+
if (typeof args.timeout_ms !== "number" || !Number.isInteger(args.timeout_ms) || args.timeout_ms < 1) {
|
|
333
|
+
return invalid("timeout_ms must be a positive integer number of milliseconds");
|
|
334
|
+
}
|
|
335
|
+
timeoutMs = Math.min(Math.max(1, args.timeout_ms), RUN_SCRIPT_CAPS.maxTimeoutMs);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const inputs = validateDeclaredRefs(args.inputs, "inputs", realRoot);
|
|
339
|
+
if (typeof inputs === "string") return invalid(inputs);
|
|
340
|
+
const outputs = validateDeclaredRefs(args.outputs, "outputs", realRoot);
|
|
341
|
+
if (typeof outputs === "string") return invalid(outputs);
|
|
342
|
+
const env = validateEnv(args.env);
|
|
343
|
+
if (typeof env === "string") return invalid(env);
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
request: {
|
|
348
|
+
workspaceRoot: realRoot,
|
|
349
|
+
interpreter,
|
|
350
|
+
interpreterPath,
|
|
351
|
+
interpreterArgs,
|
|
352
|
+
scriptDeclared: scriptArg,
|
|
353
|
+
scriptRelative,
|
|
354
|
+
scriptRealPath,
|
|
355
|
+
scriptBytes,
|
|
356
|
+
args: scriptArgs,
|
|
357
|
+
cwdRelative,
|
|
358
|
+
cwdExecution,
|
|
359
|
+
timeoutMs,
|
|
360
|
+
inputs,
|
|
361
|
+
outputs,
|
|
362
|
+
env,
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Serialize a fixed argv vector for the shell safety classifier only. No shell executes this text. */
|
|
368
|
+
function quoted(value: string): string {
|
|
369
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The shell-shaped projection the policy engine scans: the same interpreter,
|
|
374
|
+
* flags, script, and arguments, quoted as one command. Built from the request
|
|
375
|
+
* arguments without touching the filesystem, so admission sees the run the
|
|
376
|
+
* caller asked for whether or not it later validates.
|
|
377
|
+
*/
|
|
378
|
+
export function runScriptSafetyProjection(args: Record<string, unknown>): { command: string; cwd?: string } {
|
|
379
|
+
const prepared = prepareRunScriptArguments(args);
|
|
380
|
+
const interpreter = typeof prepared.interpreter === "string" ? prepared.interpreter.trim() : "";
|
|
381
|
+
const script = typeof prepared.script === "string" ? prepared.script.trim() : "";
|
|
382
|
+
const interpreterArgs =
|
|
383
|
+
prepared.interpreter_args === undefined || prepared.interpreter_args === null
|
|
384
|
+
? isInterpreter(interpreter)
|
|
385
|
+
? [...RUN_SCRIPT_DEFAULT_INTERPRETER_ARGS[interpreter]]
|
|
386
|
+
: []
|
|
387
|
+
: (parseStringArray(prepared.interpreter_args) ?? []);
|
|
388
|
+
const scriptArgs = parseStringArray(prepared.args) ?? [];
|
|
389
|
+
// Arguments are kept verbatim, empty ones included, so the command the
|
|
390
|
+
// policy inspects has exactly the argv shape execution spawns. Only the
|
|
391
|
+
// two positions a valid request cannot leave empty are dropped when empty.
|
|
392
|
+
const vector = [
|
|
393
|
+
...(interpreter.length > 0 ? [interpreter] : []),
|
|
394
|
+
...interpreterArgs,
|
|
395
|
+
...(script.length > 0 ? [script] : []),
|
|
396
|
+
...scriptArgs,
|
|
397
|
+
];
|
|
398
|
+
const command = vector.map(quoted).join(" ");
|
|
399
|
+
const cwd = typeof prepared.cwd === "string" && prepared.cwd.trim().length > 0 ? prepared.cwd.trim() : undefined;
|
|
400
|
+
return cwd === undefined ? { command } : { command, cwd };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface RunScriptScheduler {
|
|
404
|
+
now(): number;
|
|
405
|
+
setTimeout(callback: () => void, delayMs: number): unknown;
|
|
406
|
+
clearTimeout(timer: unknown): void;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const SYSTEM_SCHEDULER: RunScriptScheduler = {
|
|
410
|
+
now: Date.now,
|
|
411
|
+
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
412
|
+
clearTimeout: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>),
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export interface RunScriptProgressController {
|
|
416
|
+
/** Publish the initial snapshot. */
|
|
417
|
+
start(): void;
|
|
418
|
+
/** Fold one chunk of a stream into the live tails and schedule a bounded update. */
|
|
419
|
+
append(target: "stdout" | "stderr", chunk: Buffer): void;
|
|
420
|
+
/** Publish the final snapshot when one is pending and stop scheduling. */
|
|
421
|
+
settle(): void;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export interface RunScriptProgressInput {
|
|
425
|
+
onUpdate: (partial: ToolResult) => void;
|
|
426
|
+
/** One line naming the run, for example `python3 -u scripts/fit.py`. */
|
|
427
|
+
label: string;
|
|
428
|
+
scheduler?: RunScriptScheduler;
|
|
429
|
+
throttleMs?: number;
|
|
430
|
+
tailBytes?: number;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Live progress for a running script: elapsed time, exact byte counts, and
|
|
435
|
+
* the last few kilobytes of each stream, separately. Snapshots are advisory
|
|
436
|
+
* and throttled; the terminal result never depends on them. The scheduler is
|
|
437
|
+
* injectable so the throttle can be tested without wall-clock timing.
|
|
438
|
+
*/
|
|
439
|
+
export function createRunScriptProgressController(input: RunScriptProgressInput): RunScriptProgressController {
|
|
440
|
+
const scheduler = input.scheduler ?? SYSTEM_SCHEDULER;
|
|
441
|
+
const throttleMs = input.throttleMs ?? RUN_SCRIPT_CAPS.progressThrottleMs;
|
|
442
|
+
const tailBytes = input.tailBytes ?? RUN_SCRIPT_CAPS.progressTailBytes;
|
|
443
|
+
const stdoutTail = createRetainedStreamWindow(0, tailBytes);
|
|
444
|
+
const stderrTail = createRetainedStreamWindow(0, tailBytes);
|
|
445
|
+
let phase: "idle" | "active" | "settled" = "idle";
|
|
446
|
+
let startedAt = 0;
|
|
447
|
+
let lastEmitAt = Number.NEGATIVE_INFINITY;
|
|
448
|
+
let dirty = false;
|
|
449
|
+
let timer: unknown = null;
|
|
450
|
+
|
|
451
|
+
const snapshot = (): ToolResult => {
|
|
452
|
+
const elapsedMs = Math.max(0, scheduler.now() - startedAt);
|
|
453
|
+
const out = stdoutTail.render();
|
|
454
|
+
const err = stderrTail.render();
|
|
455
|
+
const lines = [
|
|
456
|
+
`run_script: running ${input.label} (${formatElapsed(elapsedMs)} elapsed)`,
|
|
457
|
+
`stdout: ${out.totalBytes} bytes | stderr: ${err.totalBytes} bytes`,
|
|
458
|
+
"--- stdout tail ---",
|
|
459
|
+
out.text.length > 0 ? out.text : "(no output yet)",
|
|
460
|
+
"--- stderr tail ---",
|
|
461
|
+
err.text.length > 0 ? err.text : "(no output yet)",
|
|
462
|
+
];
|
|
463
|
+
return {
|
|
464
|
+
kind: "ok",
|
|
465
|
+
output: lines.join("\n"),
|
|
466
|
+
details: { progress: { elapsedMs, stdoutBytes: out.totalBytes, stderrBytes: err.totalBytes } },
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const emit = (): void => {
|
|
471
|
+
if (!dirty) return;
|
|
472
|
+
dirty = false;
|
|
473
|
+
lastEmitAt = scheduler.now();
|
|
474
|
+
try {
|
|
475
|
+
input.onUpdate(snapshot());
|
|
476
|
+
} catch {
|
|
477
|
+
// Rendering progress is advisory and must never change the run.
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const clearTimer = (): void => {
|
|
482
|
+
if (timer === null) return;
|
|
483
|
+
scheduler.clearTimeout(timer);
|
|
484
|
+
timer = null;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const schedule = (): void => {
|
|
488
|
+
if (phase !== "active") return;
|
|
489
|
+
dirty = true;
|
|
490
|
+
const delay = throttleMs - (scheduler.now() - lastEmitAt);
|
|
491
|
+
if (delay <= 0) {
|
|
492
|
+
clearTimer();
|
|
493
|
+
emit();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
timer ??= scheduler.setTimeout(() => {
|
|
497
|
+
timer = null;
|
|
498
|
+
// A timer the event loop already queued can still fire after
|
|
499
|
+
// clearTimeout; the phase check makes that callback inert.
|
|
500
|
+
if (phase === "active") emit();
|
|
501
|
+
}, delay);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
start() {
|
|
506
|
+
if (phase !== "idle") return;
|
|
507
|
+
phase = "active";
|
|
508
|
+
startedAt = scheduler.now();
|
|
509
|
+
dirty = true;
|
|
510
|
+
emit();
|
|
511
|
+
},
|
|
512
|
+
append(target, chunk) {
|
|
513
|
+
if (phase !== "active") return;
|
|
514
|
+
(target === "stdout" ? stdoutTail : stderrTail).append(chunk);
|
|
515
|
+
schedule();
|
|
516
|
+
},
|
|
517
|
+
settle() {
|
|
518
|
+
if (phase !== "active") return;
|
|
519
|
+
clearTimer();
|
|
520
|
+
emit();
|
|
521
|
+
phase = "settled";
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function formatElapsed(ms: number): string {
|
|
527
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
528
|
+
const seconds = ms / 1000;
|
|
529
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
530
|
+
const minutes = Math.floor(seconds / 60);
|
|
531
|
+
return `${minutes}m${Math.round(seconds - minutes * 60)}s`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function classifyOutcome(result: {
|
|
535
|
+
aborted: boolean;
|
|
536
|
+
timedOut: boolean;
|
|
537
|
+
exitCode: number | null;
|
|
538
|
+
signal: string | null;
|
|
539
|
+
sinkError?: string;
|
|
540
|
+
cleanupIncomplete?: boolean;
|
|
541
|
+
pipeDrainIncomplete?: boolean;
|
|
542
|
+
leaderExit?: { code: number | null; signal: NodeJS.Signals | null } | null;
|
|
543
|
+
}): RunOutcome {
|
|
544
|
+
if (result.aborted) return "aborted";
|
|
545
|
+
if (result.timedOut) return "timed-out";
|
|
546
|
+
if (result.pipeDrainIncomplete) return "pipe-drain-incomplete";
|
|
547
|
+
if (result.sinkError !== undefined) return "failed";
|
|
548
|
+
if (result.exitCode === null && result.signal === null) return "spawn-failed";
|
|
549
|
+
if (result.cleanupIncomplete && result.leaderExit?.code === 0) return "cleanup-incomplete";
|
|
550
|
+
if (result.exitCode !== 0) return "failed";
|
|
551
|
+
// The script exited 0, but the run is only over when its process group is
|
|
552
|
+
// gone; survivors after SIGKILL and the bounded wait make it unsuccessful.
|
|
553
|
+
return result.cleanupIncomplete === true ? "cleanup-incomplete" : "succeeded";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* The result shape for a runner that threw or rejected instead of resolving:
|
|
558
|
+
* no process outcome exists, so the run is recorded as never started, with
|
|
559
|
+
* whatever bytes reached the logs before the failure.
|
|
560
|
+
*/
|
|
561
|
+
function runnerFailureResult(
|
|
562
|
+
file: string,
|
|
563
|
+
args: string[],
|
|
564
|
+
cwd: string,
|
|
565
|
+
startedAt: Date,
|
|
566
|
+
written: { stdout: number; stderr: number },
|
|
567
|
+
aborted: boolean,
|
|
568
|
+
): SafeCommandResult {
|
|
569
|
+
return {
|
|
570
|
+
file,
|
|
571
|
+
args,
|
|
572
|
+
cwd,
|
|
573
|
+
stdout: "",
|
|
574
|
+
stderr: "",
|
|
575
|
+
exitCode: null,
|
|
576
|
+
signal: null,
|
|
577
|
+
aborted,
|
|
578
|
+
timedOut: false,
|
|
579
|
+
outputCapped: false,
|
|
580
|
+
durationMs: Math.max(0, Date.now() - startedAt.getTime()),
|
|
581
|
+
startedAt: startedAt.getTime(),
|
|
582
|
+
stdoutBytes: written.stdout,
|
|
583
|
+
stderrBytes: written.stderr,
|
|
584
|
+
stdoutRetained: written.stdout === 0,
|
|
585
|
+
stderrRetained: written.stderr === 0,
|
|
586
|
+
descendantsCleaned: false,
|
|
587
|
+
cleanupIncomplete: false,
|
|
588
|
+
failure: null,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function streamLine(
|
|
593
|
+
name: "stdout" | "stderr",
|
|
594
|
+
bytes: number,
|
|
595
|
+
retained: boolean,
|
|
596
|
+
logRelative: string,
|
|
597
|
+
tailBytes: number,
|
|
598
|
+
): string {
|
|
599
|
+
if (bytes === 0) return `${name}: 0 bytes (empty)`;
|
|
600
|
+
if (retained) return `${name}: ${bytes} bytes (complete below)`;
|
|
601
|
+
return `${name}: ${bytes} bytes (last ${formatSize(tailBytes)} below; full log: ${logRelative})`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function refsTable(heading: string, refs: ReadonlyArray<RunFileRef | RunOutputRef>): string[] {
|
|
605
|
+
if (refs.length === 0) return [];
|
|
606
|
+
const lines = [`${heading}:`];
|
|
607
|
+
for (const ref of refs) {
|
|
608
|
+
const status = "status" in ref ? ref.status : ref.exists ? "present" : "absent";
|
|
609
|
+
const size = ref.bytes === undefined ? "" : ` ${ref.bytes} bytes`;
|
|
610
|
+
const omission = ref.hashOmitted === undefined ? "" : ` (hash omitted: ${ref.hashOmitted})`;
|
|
611
|
+
lines.push(` ${ref.path} ${status}${size}${omission}`);
|
|
612
|
+
}
|
|
613
|
+
return lines;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* The retained window already holds at most `resultTailBytes` of the stream
|
|
618
|
+
* plus its own omission marker, so the text goes out as rendered: cutting it
|
|
619
|
+
* again from the front would drop the stream's final bytes, which are the
|
|
620
|
+
* ones a tail exists to show.
|
|
621
|
+
*/
|
|
622
|
+
function tailSection(name: "stdout" | "stderr", text: string): string[] {
|
|
623
|
+
return [`--- ${name} tail ---`, text.length > 0 ? text : "(empty)"];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export interface RunScriptToolDeps {
|
|
627
|
+
/** The workspace root every path resolves against; defaults to the process working directory. */
|
|
628
|
+
getWorkspaceRoot?: () => string;
|
|
629
|
+
scheduler?: RunScriptScheduler;
|
|
630
|
+
now?: () => Date;
|
|
631
|
+
/** Run directories kept after each run's sweep. */
|
|
632
|
+
keepRuns?: number;
|
|
633
|
+
/** The process runner; defaults to `runCommandVector`. Tests inject one that fails to start. */
|
|
634
|
+
runCommand?: typeof runCommandVector;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const DESCRIPTION =
|
|
638
|
+
"Run one script from the workspace with an explicit interpreter, arguments, and working directory, as an observable scientific step. " +
|
|
639
|
+
"Complete stdout and stderr stream to .clio-coder/runs/<runId>/stdout.log and stderr.log and a run.json manifest records the script hash, exact argv, cwd, declared env keys, timing, outcome, and the observed state of declared inputs and outputs; the result carries bounded tails of both streams and names the run directory. " +
|
|
640
|
+
"Declared inputs and outputs are provenance only and do not restrict what the script may touch. No dependency installation, no retries, no hidden environment changes. " +
|
|
641
|
+
`Default timeout ${RUN_SCRIPT_CAPS.defaultTimeoutMs}ms; a nonzero exit, timeout, or cancellation returns an error that still names the logs and any partial outputs.`;
|
|
642
|
+
|
|
643
|
+
export const runScriptToolSurface = {
|
|
644
|
+
name: RUN_SCRIPT_TOOL_NAME,
|
|
645
|
+
description: DESCRIPTION,
|
|
646
|
+
parameters: Type.Object({
|
|
647
|
+
interpreter: StringEnum(RUN_SCRIPT_INTERPRETERS, {
|
|
648
|
+
description: "Interpreter resolved on PATH. Only these names are accepted; never a path.",
|
|
649
|
+
}),
|
|
650
|
+
script: Type.String({
|
|
651
|
+
description: "Script file inside the workspace (relative to the workspace root or absolute).",
|
|
652
|
+
}),
|
|
653
|
+
args: Type.Optional(Type.Array(Type.String(), { description: "Arguments passed to the script, in order." })),
|
|
654
|
+
interpreter_args: Type.Optional(
|
|
655
|
+
Type.Array(Type.String(), {
|
|
656
|
+
description:
|
|
657
|
+
"Flags placed before the script. Omit for the recorded default (-u for python/python3, none otherwise).",
|
|
658
|
+
}),
|
|
659
|
+
),
|
|
660
|
+
cwd: Type.Optional(
|
|
661
|
+
Type.String({ description: "Working directory, relative to the workspace root (default: the root)." }),
|
|
662
|
+
),
|
|
663
|
+
timeout_ms: Type.Optional(
|
|
664
|
+
Type.Number({ description: `Wall-clock limit in ms (default ${RUN_SCRIPT_CAPS.defaultTimeoutMs}).` }),
|
|
665
|
+
),
|
|
666
|
+
inputs: Type.Optional(
|
|
667
|
+
Type.Array(Type.String(), {
|
|
668
|
+
description: "Workspace-relative files the script reads; recorded with size and hash before the run.",
|
|
669
|
+
}),
|
|
670
|
+
),
|
|
671
|
+
outputs: Type.Optional(
|
|
672
|
+
Type.Array(Type.String(), {
|
|
673
|
+
description:
|
|
674
|
+
"Workspace-relative files the script should produce; reported as created, modified, unchanged, or absent after the run.",
|
|
675
|
+
}),
|
|
676
|
+
),
|
|
677
|
+
env: Type.Optional(
|
|
678
|
+
Type.Record(Type.String(), Type.String(), {
|
|
679
|
+
description: "Extra environment variables for the script; keys are recorded, values never are.",
|
|
680
|
+
}),
|
|
681
|
+
),
|
|
682
|
+
}),
|
|
683
|
+
baseActionClass: "execute",
|
|
684
|
+
executionMode: "sequential",
|
|
685
|
+
prepareArguments: prepareRunScriptArguments,
|
|
686
|
+
safetyCall(args) {
|
|
687
|
+
const projection = runScriptSafetyProjection(args);
|
|
688
|
+
return {
|
|
689
|
+
tool: "bash",
|
|
690
|
+
args: { command: projection.command, ...(projection.cwd === undefined ? {} : { cwd: projection.cwd }) },
|
|
691
|
+
};
|
|
692
|
+
},
|
|
693
|
+
} satisfies ToolSurface;
|
|
694
|
+
|
|
695
|
+
interface OpenLog {
|
|
696
|
+
fd: number;
|
|
697
|
+
path: string;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function openLogs(paths: RunRecordPaths): { stdout: OpenLog; stderr: OpenLog } {
|
|
701
|
+
const stdout: OpenLog = { fd: openSync(paths.stdoutPath, "w"), path: paths.stdoutPath };
|
|
702
|
+
let stderr: OpenLog;
|
|
703
|
+
try {
|
|
704
|
+
stderr = { fd: openSync(paths.stderrPath, "w"), path: paths.stderrPath };
|
|
705
|
+
} catch (error) {
|
|
706
|
+
closeSync(stdout.fd);
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
return { stdout, stderr };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function writeAll(fd: number, chunk: Buffer): void {
|
|
713
|
+
let offset = 0;
|
|
714
|
+
while (offset < chunk.byteLength) {
|
|
715
|
+
offset += writeSync(fd, chunk, offset, chunk.byteLength - offset, null);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function closeQuietly(fd: number): void {
|
|
720
|
+
try {
|
|
721
|
+
closeSync(fd);
|
|
722
|
+
} catch {
|
|
723
|
+
// The descriptor is already gone; the log holds whatever reached it.
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function createRunScriptTool(deps: RunScriptToolDeps = {}): ToolSpec {
|
|
728
|
+
const getWorkspaceRoot = deps.getWorkspaceRoot ?? (() => process.cwd());
|
|
729
|
+
const now = deps.now ?? (() => new Date());
|
|
730
|
+
const runCommand = deps.runCommand ?? runCommandVector;
|
|
731
|
+
return {
|
|
732
|
+
...runScriptToolSurface,
|
|
733
|
+
async run(rawArgs, options): Promise<ToolResult> {
|
|
734
|
+
const args = prepareRunScriptArguments(rawArgs);
|
|
735
|
+
const validation = validateRequest(args, path.resolve(getWorkspaceRoot()));
|
|
736
|
+
if (!validation.ok) return { kind: "error", message: validation.message };
|
|
737
|
+
const request = validation.request;
|
|
738
|
+
const workspaceRoot = request.workspaceRoot;
|
|
739
|
+
const signal = options?.signal;
|
|
740
|
+
const abortedBeforeExecution = (): ToolResult => ({
|
|
741
|
+
kind: "error",
|
|
742
|
+
message: "run_script: aborted before execution",
|
|
743
|
+
});
|
|
744
|
+
if (signal?.aborted) return abortedBeforeExecution();
|
|
745
|
+
|
|
746
|
+
// Pre-run provenance. Reads are bounded and asynchronous, and a
|
|
747
|
+
// cancellation here ends the run before any record exists.
|
|
748
|
+
const scriptHash = await observeFileHash(request.scriptRealPath, request.scriptBytes, signal);
|
|
749
|
+
if (scriptHash.hashOmitted === "cancelled") return abortedBeforeExecution();
|
|
750
|
+
if (scriptHash.hashOmitted === "unreadable") {
|
|
751
|
+
return { kind: "error", message: `run_script: script cannot be read: ${request.scriptDeclared}` };
|
|
752
|
+
}
|
|
753
|
+
const inputRefs: RunFileRef[] = [];
|
|
754
|
+
for (const ref of request.inputs) inputRefs.push(await captureFileRef(workspaceRoot, ref.declared, { signal }));
|
|
755
|
+
const outputsBefore: RunFileRef[] = [];
|
|
756
|
+
for (const ref of request.outputs) {
|
|
757
|
+
outputsBefore.push(await captureFileRef(workspaceRoot, ref.declared, { signal }));
|
|
758
|
+
}
|
|
759
|
+
if (signal?.aborted) return abortedBeforeExecution();
|
|
760
|
+
|
|
761
|
+
let paths: RunRecordPaths;
|
|
762
|
+
let logs: { stdout: OpenLog; stderr: OpenLog };
|
|
763
|
+
try {
|
|
764
|
+
paths = createRunRecord(workspaceRoot);
|
|
765
|
+
logs = openLogs(paths);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
return {
|
|
768
|
+
kind: "error",
|
|
769
|
+
message: `run_script: cannot create the run record: ${error instanceof Error ? error.message : String(error)}`,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const label = [request.interpreter, ...request.interpreterArgs, request.scriptRelative, ...request.args].join(" ");
|
|
774
|
+
const progress =
|
|
775
|
+
options?.onUpdate === undefined
|
|
776
|
+
? null
|
|
777
|
+
: createRunScriptProgressController({
|
|
778
|
+
onUpdate: options.onUpdate,
|
|
779
|
+
label,
|
|
780
|
+
...(deps.scheduler ? { scheduler: deps.scheduler } : {}),
|
|
781
|
+
});
|
|
782
|
+
const vector = [...request.interpreterArgs, request.scriptRealPath, ...request.args];
|
|
783
|
+
const written = { stdout: 0, stderr: 0 };
|
|
784
|
+
const startedAt = now();
|
|
785
|
+
let result: SafeCommandResult;
|
|
786
|
+
let failure: string | null = null;
|
|
787
|
+
progress?.start();
|
|
788
|
+
try {
|
|
789
|
+
result = await runCommand(request.interpreterPath, vector, {
|
|
790
|
+
cwd: request.cwdExecution,
|
|
791
|
+
workspaceRoot,
|
|
792
|
+
timeoutMs: request.timeoutMs,
|
|
793
|
+
env: request.env,
|
|
794
|
+
...(signal ? { signal } : {}),
|
|
795
|
+
output: {
|
|
796
|
+
retainHeadBytes: 0,
|
|
797
|
+
retainTailBytes: RUN_SCRIPT_CAPS.resultTailBytes,
|
|
798
|
+
onStdout(chunk) {
|
|
799
|
+
writeAll(logs.stdout.fd, chunk);
|
|
800
|
+
written.stdout += chunk.byteLength;
|
|
801
|
+
progress?.append("stdout", chunk);
|
|
802
|
+
},
|
|
803
|
+
onStderr(chunk) {
|
|
804
|
+
writeAll(logs.stderr.fd, chunk);
|
|
805
|
+
written.stderr += chunk.byteLength;
|
|
806
|
+
progress?.append("stderr", chunk);
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
} catch (error) {
|
|
811
|
+
// The runner threw or rejected instead of resolving: the run never
|
|
812
|
+
// produced a process outcome, but its directory exists and the
|
|
813
|
+
// manifest below says so instead of leaving an unexplained gap.
|
|
814
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
815
|
+
result = runnerFailureResult(
|
|
816
|
+
request.interpreterPath,
|
|
817
|
+
vector,
|
|
818
|
+
request.cwdExecution,
|
|
819
|
+
startedAt,
|
|
820
|
+
written,
|
|
821
|
+
signal?.aborted === true,
|
|
822
|
+
);
|
|
823
|
+
} finally {
|
|
824
|
+
// Whatever happened above, the descriptors close exactly once here.
|
|
825
|
+
progress?.settle();
|
|
826
|
+
closeQuietly(logs.stdout.fd);
|
|
827
|
+
closeQuietly(logs.stderr.fd);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const finishedAt = now();
|
|
831
|
+
const outcome = classifyOutcome(result);
|
|
832
|
+
const outputRefs: RunOutputRef[] = [];
|
|
833
|
+
for (const [index, ref] of request.outputs.entries()) {
|
|
834
|
+
outputRefs.push(
|
|
835
|
+
await captureOutputRef(workspaceRoot, ref.declared, outputsBefore[index] as RunFileRef, { signal }),
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const declaredKeys = Object.keys(request.env).sort();
|
|
839
|
+
const manifest: RunManifest = {
|
|
840
|
+
version: 1,
|
|
841
|
+
runId: paths.runId,
|
|
842
|
+
tool: "run_script",
|
|
843
|
+
startedAt: startedAt.toISOString(),
|
|
844
|
+
finishedAt: finishedAt.toISOString(),
|
|
845
|
+
durationMs: result.durationMs,
|
|
846
|
+
script: {
|
|
847
|
+
path: request.scriptRelative,
|
|
848
|
+
realPath: request.scriptRealPath,
|
|
849
|
+
sha256: scriptHash.sha256,
|
|
850
|
+
...(scriptHash.hashOmitted === undefined ? {} : { hashOmitted: scriptHash.hashOmitted }),
|
|
851
|
+
bytes: request.scriptBytes,
|
|
852
|
+
},
|
|
853
|
+
interpreter: {
|
|
854
|
+
name: request.interpreter,
|
|
855
|
+
resolvedPath: request.interpreterPath,
|
|
856
|
+
args: [...request.interpreterArgs],
|
|
857
|
+
},
|
|
858
|
+
argv: [result.file, ...result.args],
|
|
859
|
+
cwd: request.cwdRelative,
|
|
860
|
+
env: { declaredKeys, redactedKeys: declaredKeys.filter((key) => isSecretArgKey(key)) },
|
|
861
|
+
timeoutMs: request.timeoutMs,
|
|
862
|
+
outcome,
|
|
863
|
+
exitCode: result.exitCode,
|
|
864
|
+
leaderExit: result.leaderExit ?? null,
|
|
865
|
+
pipeDrainIncomplete: result.pipeDrainIncomplete === true,
|
|
866
|
+
signal: result.signal,
|
|
867
|
+
stdoutBytes: result.stdoutBytes ?? 0,
|
|
868
|
+
stderrBytes: result.stderrBytes ?? 0,
|
|
869
|
+
logs: { stdout: path.basename(paths.stdoutPath), stderr: path.basename(paths.stderrPath) },
|
|
870
|
+
inputs: inputRefs,
|
|
871
|
+
outputs: outputRefs,
|
|
872
|
+
cleanup: {
|
|
873
|
+
descendantsCleaned: result.descendantsCleaned === true,
|
|
874
|
+
incomplete: result.cleanupIncomplete === true,
|
|
875
|
+
},
|
|
876
|
+
...(failure === null ? {} : { failure }),
|
|
877
|
+
};
|
|
878
|
+
let manifestError: string | null = null;
|
|
879
|
+
try {
|
|
880
|
+
writeRunManifest(paths, manifest);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
manifestError = error instanceof Error ? error.message : String(error);
|
|
883
|
+
}
|
|
884
|
+
const sweep = sweepRunRecords(workspaceRoot, { keep: deps.keepRuns ?? RUN_RECORDS_DEFAULT_KEEP });
|
|
885
|
+
|
|
886
|
+
const runRelative = workspaceRelativePath(workspaceRoot, paths.dir) ?? paths.dir;
|
|
887
|
+
const stdoutRelative = workspaceRelativePath(workspaceRoot, paths.stdoutPath) ?? paths.stdoutPath;
|
|
888
|
+
const stderrRelative = workspaceRelativePath(workspaceRoot, paths.stderrPath) ?? paths.stderrPath;
|
|
889
|
+
const reportedExit = result.leaderExit ?? { code: result.exitCode, signal: result.signal };
|
|
890
|
+
const ending =
|
|
891
|
+
reportedExit.code !== null
|
|
892
|
+
? `exit ${reportedExit.code}`
|
|
893
|
+
: reportedExit.signal !== null
|
|
894
|
+
? `signal ${reportedExit.signal}`
|
|
895
|
+
: "did not start";
|
|
896
|
+
const headline = `run_script ${outcome}: ${label} (${ending}, ${formatElapsed(result.durationMs)})`;
|
|
897
|
+
const lines = [
|
|
898
|
+
headline,
|
|
899
|
+
`run: ${runRelative}`,
|
|
900
|
+
streamLine(
|
|
901
|
+
"stdout",
|
|
902
|
+
manifest.stdoutBytes,
|
|
903
|
+
result.stdoutRetained === true,
|
|
904
|
+
stdoutRelative,
|
|
905
|
+
RUN_SCRIPT_CAPS.resultTailBytes,
|
|
906
|
+
),
|
|
907
|
+
streamLine(
|
|
908
|
+
"stderr",
|
|
909
|
+
manifest.stderrBytes,
|
|
910
|
+
result.stderrRetained === true,
|
|
911
|
+
stderrRelative,
|
|
912
|
+
RUN_SCRIPT_CAPS.resultTailBytes,
|
|
913
|
+
),
|
|
914
|
+
...refsTable("inputs", inputRefs),
|
|
915
|
+
...refsTable("outputs", outputRefs),
|
|
916
|
+
...tailSection("stdout", result.stdout),
|
|
917
|
+
...tailSection("stderr", result.stderr),
|
|
918
|
+
];
|
|
919
|
+
if (outcome === "timed-out") {
|
|
920
|
+
lines.push(
|
|
921
|
+
`the run timed out after ${request.timeoutMs}ms (SIGTERM, then SIGKILL); the logs end where the script was stopped`,
|
|
922
|
+
);
|
|
923
|
+
} else if (outcome === "aborted") {
|
|
924
|
+
lines.push("the run was cancelled; the logs end where the script was stopped");
|
|
925
|
+
} else if (outcome === "spawn-failed") {
|
|
926
|
+
lines.push(`the interpreter did not start: ${failure ?? (result.stderr.trim() || "unknown spawn failure")}`);
|
|
927
|
+
}
|
|
928
|
+
if (result.sinkError !== undefined) {
|
|
929
|
+
lines.push(`log write failed (${result.sinkError}); the script was stopped and the logs are incomplete`);
|
|
930
|
+
}
|
|
931
|
+
if (manifest.cleanup.descendantsCleaned) {
|
|
932
|
+
lines.push(
|
|
933
|
+
"processes the script started were still running in its process group after it ended; they were sent SIGTERM, then SIGKILL",
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
if (result.pipeDrainIncomplete) {
|
|
937
|
+
lines.push(
|
|
938
|
+
"output pipe draining incomplete after leader exit; logs may be incomplete and escaped processes are not contained",
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
if (manifest.cleanup.incomplete) {
|
|
942
|
+
lines.push(
|
|
943
|
+
`${result.failure ?? `process group cleanup incomplete: members were still present ${SAFE_EXEC_GROUP_TEARDOWN_BOUND_MS}ms after SIGKILL`}; check for stuck processes before trusting the outputs`,
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
if ((outcome === "cleanup-incomplete" || result.pipeDrainIncomplete) && outputRefs.length > 0) {
|
|
947
|
+
lines.push(
|
|
948
|
+
result.pipeDrainIncomplete
|
|
949
|
+
? "outputs listed above may still change; processes outside the original group may still be running"
|
|
950
|
+
: "outputs listed above may still change; processes the script started are still present",
|
|
951
|
+
);
|
|
952
|
+
} else if (outcome !== "succeeded" && outputRefs.length > 0) {
|
|
953
|
+
lines.push(
|
|
954
|
+
reportedExit.code === 0
|
|
955
|
+
? "outputs listed above may be partial; the run did not complete successfully"
|
|
956
|
+
: "outputs listed above may be partial; the script did not exit 0",
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
if (manifestError !== null) lines.push(`manifest write failed: ${manifestError}`);
|
|
960
|
+
const text = lines.join("\n");
|
|
961
|
+
|
|
962
|
+
const details = {
|
|
963
|
+
runId: paths.runId,
|
|
964
|
+
runDir: paths.dir,
|
|
965
|
+
manifestPath: paths.manifestPath,
|
|
966
|
+
outcome,
|
|
967
|
+
exitCode: result.exitCode,
|
|
968
|
+
leaderExit: result.leaderExit ?? null,
|
|
969
|
+
pipeDrainIncomplete: result.pipeDrainIncomplete === true,
|
|
970
|
+
signal: result.signal,
|
|
971
|
+
timedOut: result.timedOut,
|
|
972
|
+
aborted: result.aborted,
|
|
973
|
+
durationMs: result.durationMs,
|
|
974
|
+
stdoutBytes: manifest.stdoutBytes,
|
|
975
|
+
stderrBytes: manifest.stderrBytes,
|
|
976
|
+
stdoutPath: paths.stdoutPath,
|
|
977
|
+
stderrPath: paths.stderrPath,
|
|
978
|
+
script: { path: request.scriptRelative, sha256: scriptHash.sha256 },
|
|
979
|
+
argv: manifest.argv,
|
|
980
|
+
cwd: request.cwdRelative,
|
|
981
|
+
inputs: inputRefs,
|
|
982
|
+
outputs: outputRefs,
|
|
983
|
+
cleanup: manifest.cleanup,
|
|
984
|
+
sweep,
|
|
985
|
+
...(failure === null ? {} : { failure }),
|
|
986
|
+
...(manifestError === null ? {} : { manifestError }),
|
|
987
|
+
};
|
|
988
|
+
if (outcome === "succeeded" && manifestError === null) return { kind: "ok", output: text, details };
|
|
989
|
+
return { kind: "error", message: text, details };
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export const runScriptTool: ToolSpec = createRunScriptTool();
|
|
995
|
+
|
|
996
|
+
/** Exported for the bootstrap: the options a wrapped runner forwards unchanged. */
|
|
997
|
+
export type RunScriptInvokeOptions = ToolInvokeOptions;
|