@iowarp/clio-coder 0.4.7 → 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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +111 -0
- package/CONTRIBUTING.md +83 -74
- package/README.md +69 -47
- package/dist/{acp-W3Y3TRHD.js → acp-ZWZCPS2A.js} +15 -15
- package/dist/{agents-J4ICNLQ3.js → agents-LYSOWZUE.js} +61 -59
- package/dist/assets/codewiki.json +1 -1
- package/dist/assets/web-notices/@hono__node-server-LICENSE +21 -0
- package/dist/assets/web-notices/hono-LICENSE +21 -0
- package/dist/{auth-X2UJUMOC.js → auth-WBQGKWQ5.js} +27 -27
- package/dist/background-BUMOBAQN.js +37 -0
- package/dist/{builtins-XLXWWSRF.js → builtins-ODRJOEDI.js} +7 -7
- package/dist/{chunk-C3G5PNUO.js → chunk-2DRRZ57Q.js} +36 -27
- package/dist/chunk-2JBZYUPY.js +105 -0
- package/dist/chunk-2KUBKL63.js +336 -0
- package/dist/chunk-2VBWFXYE.js +198 -0
- package/dist/{chunk-XZ6UFQ52.js → chunk-2X2R3UTH.js} +25 -15
- package/dist/{chunk-M6Z2V6YU.js → chunk-37LW32LZ.js} +5 -5
- package/dist/{chunk-6LRLOJHQ.js → chunk-3AND2EFU.js} +11 -62
- package/dist/{chunk-FQOOGXBH.js → chunk-3RDHGNU7.js} +101 -33
- package/dist/{chunk-D2T66PDV.js → chunk-3SUDBGEI.js} +41 -208
- package/dist/{chunk-5AJPUR72.js → chunk-3TQN67I2.js} +9 -9
- package/dist/{chunk-LBFQMYS3.js → chunk-3U4BL2EV.js} +5 -3
- package/dist/{chunk-UFYNOQK4.js → chunk-3UQADNG2.js} +7 -7
- package/dist/{chunk-V4ZKOKRB.js → chunk-434LRQ6Z.js} +61 -53
- package/dist/{chunk-NO7J45X6.js → chunk-4ATBOFRP.js} +713 -218
- package/dist/{chunk-ECOMVOHP.js → chunk-4DNL43EZ.js} +3 -3
- package/dist/{chunk-WT5TRMO4.js → chunk-4HFJKRGZ.js} +255 -82
- package/dist/{chunk-5A6XR3RJ.js → chunk-555E2WVV.js} +16 -2
- package/dist/{chunk-WURGVOZJ.js → chunk-5RTEFNBP.js} +18 -14
- package/dist/{config-YXARKGTX.js → chunk-64XJW7NT.js} +24 -268
- package/dist/{chunk-24S3DN22.js → chunk-6TKOFFGE.js} +4 -4
- package/dist/{chunk-YKRJTYNP.js → chunk-6UJ7RDI7.js} +4 -4
- package/dist/{chunk-MZNWZUJM.js → chunk-7FBCRWVT.js} +288 -288
- package/dist/{chunk-UA6W4XFJ.js → chunk-7JSOSVLP.js} +2 -2
- package/dist/chunk-7N3OLLSL.js +637 -0
- package/dist/{chunk-KKEYGFX4.js → chunk-7QQRPB2E.js} +11 -11
- package/dist/{chunk-N37QC5FV.js → chunk-A6DULIMH.js} +21 -9
- package/dist/{fleet-decisions-S3PTZV2D.js → chunk-AB3QXXBC.js} +5 -41
- package/dist/chunk-ACCECAH4.js +49 -0
- package/dist/{chunk-CP3HIR5H.js → chunk-AM6CEP2K.js} +20 -19
- package/dist/chunk-AUHVBIDW.js +19 -0
- package/dist/{chunk-BQQ5MXUC.js → chunk-AY4XE6GN.js} +19 -13
- package/dist/{chunk-ISNCCX42.js → chunk-BBA2TAWV.js} +10 -9
- package/dist/chunk-BFPHMSK7.js +169 -0
- package/dist/{chunk-LVFANMOH.js → chunk-BHJMMCXS.js} +4 -4
- package/dist/{chunk-DO7M3LSX.js → chunk-BMNVVKEW.js} +40 -93
- package/dist/{chunk-VTYHHQ57.js → chunk-CTGZCEI6.js} +2 -2
- package/dist/{chunk-RTLNFZMD.js → chunk-DIAJYJYG.js} +3 -3
- package/dist/chunk-EHZRAB4U.js +63 -0
- package/dist/{chunk-KS7ETA3P.js → chunk-EM2DIQAJ.js} +3 -3
- package/dist/{chunk-MNQN3X6Y.js → chunk-ETF4ROKH.js} +5 -5
- package/dist/{chunk-WN552PKN.js → chunk-F2YM3JY4.js} +744 -1134
- package/dist/{chunk-7EEGBKV4.js → chunk-FEBQWHJT.js} +24 -19
- package/dist/{chunk-4QKAITQT.js → chunk-FPS4R2RN.js} +74 -10
- package/dist/chunk-G5R53COU.js +68 -0
- package/dist/chunk-GHXGMFQ5.js +40 -0
- package/dist/{chunk-GFQ7P2UF.js → chunk-GJDRMBDI.js} +10 -10
- package/dist/{chunk-LZBMXNLP.js → chunk-GM4TY6VN.js} +43 -6
- package/dist/{chunk-NCCQ6DVS.js → chunk-GREYLTZ5.js} +8 -8
- package/dist/{chunk-HOX4YZ2K.js → chunk-H2WSCZZT.js} +19 -3
- package/dist/{chunk-3LXJOICS.js → chunk-H6IUCY6Q.js} +2 -2
- package/dist/{chunk-BO4XV5VX.js → chunk-H72DCH3E.js} +95 -206
- package/dist/{chunk-SEDI5K5B.js → chunk-H7TXJR23.js} +20 -20
- package/dist/chunk-HBJQQTKM.js +40 -0
- package/dist/chunk-HCVKSGNZ.js +990 -0
- package/dist/chunk-HVCAY2FH.js +67 -0
- package/dist/{chunk-34XHRVKG.js → chunk-I47EJPYL.js} +3 -3
- package/dist/{chunk-4N2RZ4TO.js → chunk-IALGWTLO.js} +3 -3
- package/dist/{chunk-IQCNVLO6.js → chunk-IALZJE37.js} +10 -7
- package/dist/{chunk-DLIYANB7.js → chunk-IC7JXLOM.js} +16 -11
- package/dist/{chunk-G4LGUTOE.js → chunk-IOSVTTCD.js} +5 -5
- package/dist/{chunk-DBREN25X.js → chunk-IPTPMLA6.js} +8 -3
- package/dist/{chunk-V552WL53.js → chunk-IZZI3SKQ.js} +3 -67
- package/dist/{chunk-TN5FW4XY.js → chunk-JIYWMQZ5.js} +12 -9
- package/dist/{chunk-T7QQBNLH.js → chunk-JKU34ACI.js} +10 -8
- package/dist/chunk-JOBN2YQQ.js +176 -0
- package/dist/chunk-JOL2QRLE.js +152 -0
- package/dist/{chunk-GXIWHXDB.js → chunk-JTR3VSV3.js} +3 -3
- package/dist/{chunk-T334FW2H.js → chunk-JU6MAMRJ.js} +7 -7
- package/dist/chunk-JURMQWVJ.js +15 -0
- package/dist/{chunk-CULVKYTK.js → chunk-K3XN4LZ4.js} +5 -5
- package/dist/{chunk-3T4DXWP6.js → chunk-KIVFXGBM.js} +27 -7
- package/dist/chunk-KKRXONXG.js +43 -0
- package/dist/{chunk-YPRSNUTC.js → chunk-KOAHT3N2.js} +2 -2
- package/dist/chunk-KP5BPXR3.js +73 -0
- package/dist/{chunk-UA3W4FGC.js → chunk-KRJQG47M.js} +6 -6
- package/dist/{chunk-GI5RRMAH.js → chunk-KW6OJ7IY.js} +19 -21
- package/dist/{chunk-A46ILRS2.js → chunk-L3NBWLSN.js} +4 -4
- package/dist/{chunk-MZHXENL7.js → chunk-L4RUTLRL.js} +8 -8
- package/dist/{chunk-P5SC4DLJ.js → chunk-LGTVWPRN.js} +2 -2
- package/dist/{chunk-65BGHOUR.js → chunk-LSQW6KZO.js} +5 -5
- package/dist/{chunk-TZHN5ITX.js → chunk-LWJYOHT3.js} +8 -8
- package/dist/{chunk-U3TPUXOQ.js → chunk-M6YLF66Z.js} +74 -40
- package/dist/chunk-MJ4QEJVT.js +185 -0
- package/dist/{chunk-T4TY5GG3.js → chunk-MO65K5CH.js} +5 -5
- package/dist/{chunk-TZ6UVP2H.js → chunk-MTOZEQHL.js} +8 -8
- package/dist/chunk-MZDPF35R.js +45 -0
- package/dist/{chunk-P4DL2LM5.js → chunk-MZKYVIWW.js} +8 -119
- package/dist/{chunk-KAFMW7WM.js → chunk-NBPA5QM6.js} +2 -2
- package/dist/{chunk-EUEXGXXR.js → chunk-NMBEW4PP.js} +5 -5
- package/dist/{chunk-SELG4SUR.js → chunk-NPLLK3HO.js} +72 -89
- package/dist/{chunk-VCRVEDUC.js → chunk-NSYGPIVW.js} +5 -5
- package/dist/{context-7HJEWTTP.js → chunk-NXTV5OI5.js} +36 -90
- package/dist/{chunk-IMOD2YPV.js → chunk-NYDG4QP4.js} +9 -19
- package/dist/{chunk-6CA2BHOR.js → chunk-OA2GDACN.js} +5 -5
- package/dist/{chunk-A67NR5AT.js → chunk-PJI57EC6.js} +14 -11
- package/dist/{chunk-IJMW64DX.js → chunk-PJJBUGMR.js} +50 -50
- package/dist/{chunk-CPXGJI2A.js → chunk-PNJE7JPS.js} +20 -5
- package/dist/chunk-PP23PZEV.js +1347 -0
- package/dist/chunk-PPIFDSBK.js +409 -0
- package/dist/{chunk-T52TPHYC.js → chunk-PTFISXQO.js} +8 -7
- package/dist/{chunk-C6GEXX4L.js → chunk-QB33Z5S7.js} +5 -5
- package/dist/{chunk-VMATUZB6.js → chunk-QGNII5VC.js} +10 -10
- package/dist/{chunk-TLTGZTXI.js → chunk-QI72X67S.js} +104 -27
- package/dist/chunk-QKHZZFKH.js +98 -0
- package/dist/{chunk-PFLOULP3.js → chunk-RDKX2PME.js} +4 -4
- package/dist/{chunk-B5ZTR6KZ.js → chunk-RL7LJYN4.js} +1 -1
- package/dist/chunk-RPMNIFRY.js +65 -0
- package/dist/{chunk-L3Z5ANCV.js → chunk-RQROVOV3.js} +13 -11
- package/dist/{chunk-6YHPAAZR.js → chunk-S34A7BSP.js} +3 -3
- package/dist/{chunk-5ZSZEYJK.js → chunk-SJETVJHX.js} +2 -2
- package/dist/{chunk-77CZRFTE.js → chunk-SJMNU4ZS.js} +2 -2
- package/dist/chunk-SLLG2G44.js +85 -0
- package/dist/{chunk-GVZFCJHE.js → chunk-SQAZN2V3.js} +48 -22
- package/dist/{chunk-2O2LVQQK.js → chunk-SQBT46OF.js} +2 -2
- package/dist/{chunk-ZGIGW4ZG.js → chunk-TCUOGIL2.js} +5 -5
- package/dist/{chunk-DACXIVVF.js → chunk-TIWPKCVO.js} +5 -5
- package/dist/{chunk-5CMARGPF.js → chunk-UIU7MGZ3.js} +11 -2
- package/dist/chunk-USBBEKHW.js +177 -0
- package/dist/{chunk-WLHX3MM5.js → chunk-V4XJWDHR.js} +21 -11
- package/dist/{chunk-MBJO5VNZ.js → chunk-VPWGADBG.js} +33 -33
- package/dist/{chunk-UUQ5G6EZ.js → chunk-VWFCAK34.js} +6 -6
- package/dist/{chunk-6CB2IL4N.js → chunk-W5RM7YAH.js} +1 -1
- package/dist/{chunk-4MFBBY2Z.js → chunk-W72LLZAV.js} +3 -3
- package/dist/{chunk-4UHVS5T7.js → chunk-WGRDABW7.js} +8 -8
- package/dist/{chunk-XR3XJN2M.js → chunk-WWRV5RJI.js} +4 -4
- package/dist/{chunk-24TOGHPO.js → chunk-X6FLVUM5.js} +4 -4
- package/dist/{chunk-6YA46L5A.js → chunk-XFLSL7BT.js} +2 -2
- package/dist/{chunk-QKWL7SYE.js → chunk-XJZEDCNT.js} +50 -39
- package/dist/{chunk-UWD3JD5X.js → chunk-XLLEL3K7.js} +10 -10
- package/dist/{chunk-2GPGAE5W.js → chunk-XNIZBOFV.js} +15 -1
- package/dist/chunk-XNZWWDC3.js +686 -0
- package/dist/{chunk-WBVKKTUT.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-WB7POC6M.js → chunk-Y3M2T5RO.js} +6 -6
- package/dist/{chunk-RXFPB32E.js → chunk-YJCCMYYT.js} +2 -2
- package/dist/{chunk-EUZRMP4Y.js → chunk-YPQOPFP7.js} +20 -20
- package/dist/{chunk-USZA7GWL.js → chunk-YQ3GFEUY.js} +2 -2
- package/dist/{chunk-BAU36233.js → chunk-YQYINEJI.js} +9 -9
- package/dist/{chunk-CE64C5K5.js → chunk-ZF6CV4WZ.js} +1356 -235
- package/dist/{chunk-3XE4NRLF.js → chunk-ZKJU55MX.js} +4 -2
- package/dist/{chunk-IZYTT3CD.js → chunk-ZKQ63XZ5.js} +16 -39
- package/dist/{chunk-6TAATXXH.js → chunk-ZKVMDNAN.js} +7 -7
- package/dist/chunk-ZST3FRCV.js +132 -0
- package/dist/cli/index.js +50 -38
- package/dist/{clio-L2P7S2R2.js → clio-JUDCLNHC.js} +11 -11
- package/dist/clio-context-tools-FT672MNZ.js +136 -0
- package/dist/{code-nav-B5LNX4KU.js → code-nav-7OVCTP2Z.js} +39 -37
- package/dist/{components-63MMQKUT.js → components-OZXZMDWU.js} +11 -11
- package/dist/config-P3ZQVF63.js +275 -0
- package/dist/config-graph-GKAOBAS5.js +160 -0
- package/dist/{configure-LMVQJ46J.js → configure-25LRYZJG.js} +56 -55
- package/dist/context-72VQV7JX.js +73 -0
- package/dist/{context-YG2LZDYZ.js → context-COWRWXP3.js} +89 -83
- package/dist/{context-XSZKKFBW.js → context-WB3D474G.js} +16 -16
- package/dist/{context-clear-AC5NXHM2.js → context-clear-Y4LYMWC6.js} +88 -82
- package/dist/{context-index-KKTPRV5G.js → context-index-W6A4V7OC.js} +4 -4
- package/dist/{context-map-T7ROR5D5.js → context-map-DKL3QT6D.js} +3 -3
- package/dist/{context-working-set-F4Q4J3NS.js → context-working-set-TMZYVC7A.js} +27 -25
- package/dist/data-tool-KMBWKKWR.js +2987 -0
- package/dist/{detail-H73BEF2N.js → detail-4GLROGY5.js} +89 -83
- package/dist/{dispatch-runner-3RY5FSJC.js → dispatch-runner-TR3LBP5P.js} +113 -107
- package/dist/docs-ZK777W2G.js +101 -0
- package/dist/{doctor-EQYJOGUG.js → doctor-WC4OV2OB.js} +76 -73
- package/dist/{eval-4P7O6CCE.js → eval-FUNWJNUJ.js} +88 -73
- package/dist/{eval-inventory-QOYZERXJ.js → eval-inventory-CX24K2JT.js} +5 -60
- package/dist/evals-L73EBHSU.js +104 -0
- package/dist/{evidence-O76QF7TZ.js → evidence-AVJ2RTHX.js} +99 -93
- package/dist/{evidence-D6GUSYJW.js → evidence-P4NZOAK7.js} +88 -82
- package/dist/evidence-Y55WKYHH.js +246 -0
- package/dist/{evolve-H47BCETS.js → evolve-MAWKRYWF.js} +93 -87
- package/dist/{extensions-NNL3ZOCU.js → extensions-PYQH3H47.js} +14 -13
- package/dist/fleet-M4H2QBWT.js +268 -0
- package/dist/{fleet-KMNS6NOM.js → fleet-ULBXXRDR.js} +146 -138
- package/dist/{fleet-commands-DOWSYCBX.js → fleet-commands-7NNC4JZU.js} +18 -17
- package/dist/fleet-decisions-EHEY7KPU.js +52 -0
- package/dist/{fleet-graph-F2DE2TME.js → fleet-graph-LQ2AHZT3.js} +24 -24
- package/dist/{fleet-inspect-EN2Q6IVB.js → fleet-inspect-5IGBIIYZ.js} +98 -258
- package/dist/{fleet-preflight-FRLETDD3.js → fleet-preflight-UTTNOKMG.js} +9 -4
- package/dist/{fleet-validate-B2XDLS57.js → fleet-validate-YGILJKA6.js} +29 -29
- package/dist/{fleet-verify-4JKUWGJ7.js → fleet-verify-AUWTCDKR.js} +88 -82
- package/dist/{fleet-view-BHJFW26B.js → fleet-view-TGUFCY5A.js} +90 -84
- package/dist/{init-XAT5N5YM.js → init-GFN2Z7NF.js} +111 -104
- package/dist/install-6PIF6F6W.js +19 -0
- package/dist/{interop-FVECHFKG.js → interop-EF5JUWWK.js} +29 -29
- package/dist/{inventory-7KFQGLCV.js → inventory-I6VJDDYF.js} +90 -84
- package/dist/library-3J5IO2OJ.js +75 -0
- package/dist/{library-QAPAO23R.js → library-IJTOFTDS.js} +20 -19
- package/dist/{library-NY2XOT75.js → library-Z3KZSHPE.js} +35 -35
- package/dist/{library-import-5Y6MAS3R.js → library-import-IMKFLDHX.js} +29 -29
- package/dist/{library-inventory-JT6HWITQ.js → library-inventory-CQP5D6GU.js} +21 -21
- package/dist/{library-validation-EQUH25DC.js → library-validation-33UTX7AI.js} +19 -19
- package/dist/mcp-5FWZKUS6.js +26 -0
- package/dist/{memory-DYVTTCP6.js → memory-36CWN67Y.js} +93 -87
- package/dist/{models-MASFJ6Y2.js → models-KQ6PQJJQ.js} +57 -56
- package/dist/{monitor-YMP4NXJJ.js → monitor-XX6ZD6KC.js} +98 -91
- package/dist/{orchestrator-AFQRRTEI.js → orchestrator-SK6HDAOP.js} +1537 -832
- package/dist/{panes-NICTHQCV.js → panes-L27QRXUH.js} +14 -14
- package/dist/{panes-D3Y2KFG5.js → panes-LXZIWNOZ.js} +4 -4
- package/dist/{paths-PSFAUIGO.js → paths-7DJDU4P4.js} +6 -6
- package/dist/{preload-5Q7VXRFJ.js → preload-K2TDYI4Y.js} +88 -82
- package/dist/{reset-7NKZK7JN.js → reset-73VU3XNT.js} +13 -13
- package/dist/{resources-NILSIZAT.js → resources-3BYJYVYL.js} +24 -24
- package/dist/{run-BPZGEBZV.js → run-AR7PY6GW.js} +144 -136
- package/dist/run-script-H2KUDIII.js +41 -0
- package/dist/settings-3VMBMWID.js +72 -0
- package/dist/{share-EZ6ERBMH.js → share-HVWH4FPF.js} +30 -30
- package/dist/{skills-BTVVNYVM.js → skills-5TYEY75Q.js} +30 -30
- package/dist/{skills-eval-UY34MNK7.js → skills-eval-CK3MD63Z.js} +114 -103
- package/dist/{skills-inventory-JDHSIA7I.js → skills-inventory-KSFYG3I6.js} +24 -24
- package/dist/{slash-commands-K4FNQ2AT.js → slash-commands-DMVHML7P.js} +60 -57
- package/dist/{steer-2B3EQGFH.js → steer-XCD6V3EW.js} +4 -4
- package/dist/{support-L2SOL3IE.js → support-JAZL6VBR.js} +7 -7
- package/dist/system-3TDQRMZZ.js +116 -0
- package/dist/{targets-44VKGFLN.js → targets-EZKHAYRN.js} +74 -72
- package/dist/{tasks-3KODKFU7.js → tasks-SNEFHWYW.js} +18 -18
- package/dist/{terminal-lease-LXA7MMPO.js → terminal-lease-BNLFA4OF.js} +8 -7
- package/dist/{tools-3UB7TEVR.js → tools-7IM6BDCE.js} +12 -12
- package/dist/{trace-TAMIU4OJ.js → trace-C3MBGD4S.js} +16 -65
- package/dist/{tui-primitives-A6MUW32V.js → tui-primitives-36BY2774.js} +2 -2
- package/dist/{uninstall-QYVRPEM4.js → uninstall-WWLOSZ42.js} +35 -10
- package/dist/{upgrade-7PMKM2JH.js → upgrade-ETK2JNSJ.js} +42 -51
- package/dist/{usage-6EQV7EQK.js → usage-S2HOB7BZ.js} +112 -102
- package/dist/{verifiers-JLITWSHU.js → verifiers-QDTEVH7R.js} +26 -24
- package/dist/{verify-T7NAEVIG.js → verify-SUKUK6XM.js} +16 -15
- package/dist/web/client/THIRD_PARTY_LICENSES.md +1809 -0
- package/dist/web/client/assets/abnfDiagram-VCTEODGH-BznNOe_I.js +1 -0
- package/dist/web/client/assets/arc-Pry-7G1_.js +1 -0
- 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/array-BifhSqXX.js +1 -0
- package/dist/web/client/assets/atkinson-hyperlegible-next-latin-ext-wght-normal-C6vrW8VD.woff2 +0 -0
- package/dist/web/client/assets/atkinson-hyperlegible-next-latin-wght-normal-BcXVPD7q.woff2 +0 -0
- package/dist/web/client/assets/blockDiagram-I7D4REHJ-prINbpbM.js +129 -0
- package/dist/web/client/assets/c4Diagram-7LVT6UL2-DkVRKtyU.js +38 -0
- package/dist/web/client/assets/channel-c4EeUUqA.js +1 -0
- package/dist/web/client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/web/client/assets/chunk-4HAMMTFA-CV0Z1ApC.js +62 -0
- package/dist/web/client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/web/client/assets/chunk-75Z2AOVW-D34Nohxq.js +2 -0
- package/dist/web/client/assets/chunk-DU6HZSFF-Das_cS-O.js +125 -0
- package/dist/web/client/assets/chunk-F27PBJKO-CmRojxkG.js +1 -0
- package/dist/web/client/assets/chunk-FOHPRMQF-CoA4358r.js +161 -0
- package/dist/web/client/assets/chunk-GMAD6QVW-ClYZmc73.js +72 -0
- package/dist/web/client/assets/chunk-GVQU2GXP-B2BiHFt-.js +1 -0
- package/dist/web/client/assets/chunk-IMKFNOWR-BGApZ8nP.js +231 -0
- package/dist/web/client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/web/client/assets/chunk-L3NEJ4N5-DIzCgpkY.js +1 -0
- package/dist/web/client/assets/chunk-OSK3NFVY-BPT-yePv.js +10 -0
- package/dist/web/client/assets/chunk-P2QGCYS3-BC1WTc2t.js +1 -0
- package/dist/web/client/assets/chunk-POPQ4Y6H-BSjzBmZU.js +1 -0
- package/dist/web/client/assets/chunk-PWAF6VOD-BIjLs86Z.js +1 -0
- package/dist/web/client/assets/chunk-SHT3W25Y-CduDz2cm.js +168 -0
- package/dist/web/client/assets/chunk-SVP7TREG-XUn4pPAP.js +88 -0
- package/dist/web/client/assets/chunk-TICWLB2K-DcBy2DKy.js +206 -0
- package/dist/web/client/assets/chunk-XXDRQBXY-DqAYeljN.js +1 -0
- package/dist/web/client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- 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/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
- package/dist/web/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
- package/dist/web/client/assets/cose-bilkent-JH36ORCC-CVh1icwb.js +1 -0
- package/dist/web/client/assets/cynefin-OW5HDTMX-BecPbyM6.js +1 -0
- package/dist/web/client/assets/cynefinDiagram-5FMLGOSQ-BzMzFwyT.js +62 -0
- package/dist/web/client/assets/cytoscape.esm-c2aL46s-.js +321 -0
- package/dist/web/client/assets/dagre-C-rnJpZF.js +1 -0
- package/dist/web/client/assets/dagre-GXQ25YYZ-CAWJmRMG.js +4 -0
- package/dist/web/client/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/web/client/assets/diagram-S7CK7UJ4--lp0ALmn.js +30 -0
- package/dist/web/client/assets/diagram-UQ7AKVKN-gKUnsjcj.js +41 -0
- package/dist/web/client/assets/diagram-VSXAHHWV-BoN5D1Gh.js +3 -0
- package/dist/web/client/assets/diagram-VX7I27RA-BMtuswQ6.js +24 -0
- package/dist/web/client/assets/diagram-Z3DM3KII-DtU8LXrA.js +24 -0
- package/dist/web/client/assets/dist-v5Q1xZ2K.js +1 -0
- package/dist/web/client/assets/ebnfDiagram-PWID7BFC-DDcnv9BH.js +1 -0
- package/dist/web/client/assets/erDiagram-RLTQ6QDP-DvRb4iDL.js +99 -0
- 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-DjD_AAMj.js +1 -0
- package/dist/web/client/assets/gitGraphDiagram-WWUBYQGX-CDyN9n2u.js +106 -0
- package/dist/web/client/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/web/client/assets/index-CVcy0FAn.js +75 -0
- package/dist/web/client/assets/index-Dsi52apB.css +1 -0
- package/dist/web/client/assets/info-A6RAGUB7-C_XK8Hq0.js +1 -0
- package/dist/web/client/assets/infoDiagram-27XIBGKW-DIe4tJ1i.js +2 -0
- package/dist/web/client/assets/init-C-OQMol4.js +1 -0
- package/dist/web/client/assets/ishikawaDiagram-5VMMS53U-CVl5unKI.js +70 -0
- package/dist/web/client/assets/journeyDiagram-3NMN7TZE-CPEB1Vt8.js +139 -0
- package/dist/web/client/assets/kanban-definition-UXKFOSKX-j-9WXjYQ.js +89 -0
- package/dist/web/client/assets/katex-ZlcWpGUi.js +257 -0
- package/dist/web/client/assets/line-BIDAcEtX.js +1 -0
- package/dist/web/client/assets/linear-SzVmGSw4.js +1 -0
- package/dist/web/client/assets/mermaid-parser.core-Ux4vssh0.js +7 -0
- package/dist/web/client/assets/mermaid.core-CDuwVKeh.js +44 -0
- package/dist/web/client/assets/mindmap-definition-YA3MSWOX-5MlQEeQ6.js +96 -0
- package/dist/web/client/assets/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
- package/dist/web/client/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
- package/dist/web/client/assets/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
- package/dist/web/client/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/web/client/assets/packet-AYTQ26CC-DRO66c9Q.js +1 -0
- package/dist/web/client/assets/path-fybaL0A-.js +1 -0
- package/dist/web/client/assets/pegDiagram-XKGWAZYB-BakZnSjM.js +1 -0
- package/dist/web/client/assets/pie-WAS4IAKB-BEzcmhz8.js +1 -0
- package/dist/web/client/assets/pieDiagram-E7YTZNPT-DpmqgoPt.js +39 -0
- package/dist/web/client/assets/prism-bash-D6zCJ74D.js +1 -0
- package/dist/web/client/assets/prism-c-04YixN25.js +1 -0
- package/dist/web/client/assets/prism-clike-DapgWyxx.js +1 -0
- package/dist/web/client/assets/prism-core-BtsZdCS6.js +1 -0
- package/dist/web/client/assets/prism-cpp-C-lJbC-6.js +1 -0
- package/dist/web/client/assets/prism-css-CPgOzxXp.js +1 -0
- package/dist/web/client/assets/prism-diff-BF8m0_mq.js +3 -0
- package/dist/web/client/assets/prism-docker-DkT6yOFj.js +1 -0
- package/dist/web/client/assets/prism-fortran-Be5x3y1V.js +1 -0
- package/dist/web/client/assets/prism-go-C_8qAH5n.js +1 -0
- package/dist/web/client/assets/prism-ini-BESK3y0r.js +1 -0
- package/dist/web/client/assets/prism-javascript-BnO-swvr.js +1 -0
- package/dist/web/client/assets/prism-json-Dp_-W-Hv.js +1 -0
- package/dist/web/client/assets/prism-jsx-D-D0NWtC.js +1 -0
- package/dist/web/client/assets/prism-julia-DIjOgiAV.js +1 -0
- package/dist/web/client/assets/prism-latex-DNMbqcH_.js +1 -0
- package/dist/web/client/assets/prism-makefile-CRKWLQ8l.js +1 -0
- package/dist/web/client/assets/prism-markdown-C0FPJ1Iz.js +1 -0
- package/dist/web/client/assets/prism-markup-Cp04rIa1.js +1 -0
- package/dist/web/client/assets/prism-matlab-D-nk08al.js +1 -0
- package/dist/web/client/assets/prism-python-CfiSVtM6.js +1 -0
- package/dist/web/client/assets/prism-r-QmNGR8LX.js +1 -0
- package/dist/web/client/assets/prism-rust-Bu9N8X3N.js +1 -0
- package/dist/web/client/assets/prism-shell-session-Be4UuYoM.js +1 -0
- package/dist/web/client/assets/prism-sql-CYhVDMEE.js +1 -0
- package/dist/web/client/assets/prism-toml-DKb54GAT.js +1 -0
- package/dist/web/client/assets/prism-tsx-p6J2Kc_4.js +1 -0
- package/dist/web/client/assets/prism-typescript-BMqONzxf.js +1 -0
- package/dist/web/client/assets/prism-yaml-C0JHu8gr.js +1 -0
- package/dist/web/client/assets/purify.es-ChwZkWde.js +3 -0
- package/dist/web/client/assets/quadrantDiagram-AXDQQJYC-DuMPwwV2.js +7 -0
- package/dist/web/client/assets/radar-RG4KPBEZ-Djm3oXl5.js +1 -0
- package/dist/web/client/assets/railroad-74A4TZTK-Cpvkwsao.js +1 -0
- 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-CgMS4B6R.js +1 -0
- package/dist/web/client/assets/requirementDiagram-BXWQKSXE-CX5eoNdN.js +84 -0
- package/dist/web/client/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/web/client/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dist/web/client/assets/sankeyDiagram-P5KCCOFB-CODBX2N3.js +40 -0
- package/dist/web/client/assets/sequenceDiagram-WJ2MYXX4-eUhhF8OR.js +162 -0
- package/dist/web/client/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/web/client/assets/src-B6xuSHsQ.js +1 -0
- package/dist/web/client/assets/stateDiagram-D77RDMKH-vR6l0NC2.js +1 -0
- package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-BYUm8jLY.js +1 -0
- package/dist/web/client/assets/swimlanes-42K2YHIH-v9Tw_zX7.js +1 -0
- package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-B2jdCvhD.js +8 -0
- package/dist/web/client/assets/timeline-definition-24CTP7MA-DxianbhU.js +120 -0
- package/dist/web/client/assets/treeView-Q6P3EWNA-DLxnZb9R.js +1 -0
- package/dist/web/client/assets/treemap-WGGIJYW6-I7UnZ6xJ.js +1 -0
- package/dist/web/client/assets/vennDiagram-4TSXK5OY-Ar7M8Mrj.js +34 -0
- package/dist/web/client/assets/wardley-WFR3VGLG-B8ZSHeDu.js +1 -0
- package/dist/web/client/assets/wardleyDiagram-VM6X3IG4-Qks2FmIn.js +78 -0
- package/dist/web/client/assets/xychartDiagram-S5SC5T6Z-BBBjRtiE.js +7 -0
- package/dist/web/client/clio-coder-logo.webp +0 -0
- package/dist/web/client/icon-192.png +0 -0
- package/dist/web/client/icon-512.png +0 -0
- package/dist/web/client/index.html +6 -0
- package/dist/web/client/manifest.webmanifest +15 -0
- package/dist/web/client/offline.css +55 -0
- package/dist/web/client/offline.html +5 -0
- package/dist/web/client/offline.js +27 -0
- package/dist/web/client/sw.js +34 -0
- package/dist/web/ops-worker.js +37 -0
- package/dist/web/reads-worker.js +1726 -0
- package/dist/web/server.js +8499 -0
- package/dist/web-EMGUGGEI.js +13 -0
- package/dist/{web-fetch-W4TNW4Q7.js → web-fetch-6BRRVQON.js} +22 -5
- package/dist/{wiki-generate-T2TJQJUA.js → wiki-generate-XHSJTDOP.js} +120 -113
- package/dist/{with-panes-CHFC7HGI.js → with-panes-Z6BTEHMZ.js} +12 -12
- package/dist/worker/entry.js +113 -99
- package/docs/README.md +28 -27
- package/docs/architecture/acp.md +5 -9
- package/docs/architecture/alcf-provider.md +0 -3
- package/docs/architecture/architecture.md +0 -3
- package/docs/architecture/artifact-placement.md +12 -4
- package/docs/architecture/artifact-versions.md +0 -3
- package/docs/architecture/capacity-and-scheduling.md +0 -3
- package/docs/architecture/context-engine.md +0 -3
- package/docs/architecture/context-working-set.md +0 -3
- package/docs/architecture/dispatch-architecture-rationale.md +0 -3
- package/docs/architecture/dispatch-typed-intent.md +0 -3
- package/docs/architecture/evidence-and-memory.md +0 -3
- package/docs/architecture/library.md +1 -3
- package/docs/architecture/middleware-and-components.md +0 -3
- package/docs/architecture/model-catalog.md +1 -4
- package/docs/architecture/observability.md +0 -3
- package/docs/architecture/pi-boundary.md +6 -9
- package/docs/architecture/prompt-envelope-and-tools.md +37 -30
- package/docs/architecture/provider-adapter-cookbook.md +1 -4
- package/docs/architecture/safety-model.md +19 -9
- package/docs/architecture/session-lifecycle.md +10 -7
- package/docs/architecture/time-conventions.md +0 -3
- package/docs/architecture/trace-store.md +28 -20
- package/docs/architecture/tui-design.md +38 -18
- package/docs/architecture/worker-context.md +1 -3
- package/docs/architecture/worker-dispatch-mechanics.md +1 -4
- package/docs/guide/authoring-plugins.md +1 -3
- package/docs/guide/built-in-agents.md +0 -3
- package/docs/guide/commands-and-modes.md +41 -25
- package/docs/guide/configuration-and-targets.md +204 -9
- package/docs/guide/configuration-reference.md +125 -17
- package/docs/guide/environment-variables.md +2 -8
- package/docs/guide/exit-codes-and-output.md +0 -3
- package/docs/guide/extensions-and-sharing.md +0 -3
- package/docs/guide/fleet-dispatch.md +3 -5
- package/docs/guide/glossary.md +1 -4
- package/docs/guide/harness-extensions.md +0 -2
- package/docs/guide/installation-and-lifecycle.md +3 -6
- package/docs/guide/interop.md +0 -2
- package/docs/guide/panes-and-files.md +0 -3
- package/docs/guide/plugins.md +0 -3
- package/docs/guide/proactive-memory.md +0 -3
- package/docs/guide/resource-library.md +1 -3
- package/docs/guide/skills-marketplace.md +0 -2
- package/docs/guide/tool-usage.md +166 -67
- package/docs/guide/troubleshooting.md +1 -4
- package/docs/history/config-knobs-audit.md +0 -3
- package/docs/history/release-cut-checklist.md +0 -3
- package/docs/process/development-pipeline.md +0 -3
- package/docs/process/documentation-coverage.md +3 -3
- package/docs/process/documentation-guide.md +16 -22
- package/docs/process/eval-runner.md +0 -3
- package/docs/process/evals-internal.md +0 -3
- package/docs/process/evolution.md +0 -3
- package/docs/process/fleet-demo-runbook.md +6 -9
- package/docs/process/git-commit-provenance.md +0 -3
- package/docs/process/performance-methodology.md +1 -4
- package/docs/process/release-cut-checklist.md +114 -221
- package/docs/process/scientific-validation.md +23 -6
- package/docs/process/tool-audit-v0.4.9.md +357 -0
- package/library/registry.yaml +2 -2
- package/library/skills/meta/clio-coder-test/SKILL.md +25 -15
- package/library/skills/meta/clio-coder-test/evals.md +16 -2
- package/library/skills/meta/clio-coder-test/plugin.json +1 -1
- package/library/skills/meta/clio-coder-test/references/test-map.md +19 -18
- package/library/skills/registry.yaml +2 -2
- package/library/skills/skill-marketplace.json +1 -1
- package/package.json +13 -11
- package/src/cli/argv.ts +31 -0
- package/src/cli/clio.ts +17 -13
- package/src/cli/config.ts +4 -0
- package/src/cli/docs.ts +65 -328
- package/src/cli/doctor-panes.ts +1 -1
- package/src/cli/doctor.ts +3 -1
- package/src/cli/index.ts +17 -2
- package/src/cli/mcp.ts +106 -0
- package/src/cli/skills-eval.ts +8 -2
- package/src/cli/trace.ts +4 -65
- package/src/cli/uninstall.ts +26 -0
- package/src/cli/upgrade.ts +34 -38
- package/src/cli/usage.ts +3 -0
- package/src/cli/verifiers.ts +1 -1
- package/src/cli/web.ts +79 -0
- package/src/core/bash-exec.ts +27 -4
- package/src/core/bus-events.ts +3 -4
- package/src/core/config.ts +73 -1
- package/src/core/defaults.ts +10 -1
- package/src/core/domain-loader.ts +37 -28
- 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 +16 -12
- package/src/core/tool-names.ts +33 -12
- package/src/domains/agents/contract.ts +1 -1
- package/src/domains/agents/extension.ts +18 -10
- package/src/domains/config/extension.ts +5 -4
- 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 +206 -26
- 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/index.ts +1 -0
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-findings.ts +3 -4
- package/src/domains/dispatch/receipt-integrity.ts +3 -0
- package/src/domains/dispatch/reservation-store.ts +7 -9
- package/src/domains/dispatch/route-facts.ts +1 -1
- package/src/domains/dispatch/state.ts +4 -5
- package/src/domains/dispatch/types.ts +3 -1
- package/src/domains/eval/metrics/tracked.ts +22 -2
- package/src/domains/evidence/build.ts +35 -7
- package/src/domains/extensions/operator-runtime.ts +3 -1
- 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/middleware/hooks.ts +2 -2
- 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/background-memory-usage.ts +3 -0
- package/src/domains/observability/compaction-usage.ts +2 -0
- package/src/domains/observability/contract.ts +7 -3
- package/src/domains/observability/cost.ts +5 -0
- package/src/domains/observability/evidence-index.ts +2 -2
- package/src/domains/observability/extension.ts +1 -0
- package/src/domains/observability/out-of-turn-usage.ts +2 -0
- package/src/domains/observability/projection.ts +10 -0
- package/src/domains/observability/trace-store.ts +65 -5
- package/src/domains/prompts/compiler.ts +41 -23
- package/src/domains/prompts/extension.ts +4 -17
- 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/cache-deployment.ts +147 -0
- package/src/domains/providers/catalog.ts +12 -7
- package/src/domains/providers/extension.ts +27 -5
- package/src/domains/providers/index.ts +6 -1
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +42 -10
- package/src/domains/providers/plugins.ts +3 -13
- package/src/domains/providers/probe/http.ts +44 -59
- package/src/domains/providers/probe/reasoning.ts +2 -0
- package/src/domains/providers/runtimes/common/local-synth.ts +4 -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/providers/types/target-descriptor.ts +17 -0
- 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 +29 -3
- 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/entries.ts +1 -0
- 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/domains/session/usage.ts +6 -7
- package/src/engine/acp/server.ts +11 -0
- package/src/engine/ai.ts +2 -0
- package/src/engine/apis/openai-completions.ts +17 -4
- package/src/engine/loop-guard.ts +7 -0
- package/src/engine/prewarm.ts +232 -0
- package/src/engine/provider-diagnostics.ts +6 -1
- 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 +38 -18
- package/src/interactive/chat-loop-messages.ts +3 -0
- package/src/interactive/chat-loop-policy.ts +1 -0
- package/src/interactive/chat-loop.ts +10 -12
- 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/prewarm.ts +11 -197
- 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/session-usage-reseed.ts +2 -0
- package/src/interactive/side-question.ts +2 -0
- 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-context.ts +1 -0
- package/src/interactive/turn-persistence.ts +9 -1
- package/src/interactive/turn-prewarm.ts +126 -19
- package/src/interactive/turn-recovery.ts +2 -1
- package/src/interactive/turn-runtime.ts +36 -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 +80 -11
- 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 +106 -8
- 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/src/worker/spec-contract.ts +27 -0
- package/dist/chunk-B6UM3OZC.js +0 -15
- package/dist/chunk-GJ24ODAX.js +0 -196
- package/dist/chunk-HRAKUPIH.js +0 -57
- package/dist/docs-BL7ROUFN.js +0 -292
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One local MCP server over stdio: spawn, initialize, list and call tools,
|
|
3
|
+
* close. The client owns exactly one child process and never restarts it; a
|
|
4
|
+
* server that dies stays dead until the gateway decides to create a new
|
|
5
|
+
* client. Every request carries its own timeout, every failure is a typed
|
|
6
|
+
* McpError, and nothing the server prints reaches this process's stdout or
|
|
7
|
+
* stderr. Launching a server here is not sandboxing it: the child gets the
|
|
8
|
+
* allowlisted tool environment and a workspace-contained cwd, nothing more.
|
|
9
|
+
*
|
|
10
|
+
* Both directions are bounded. The framer caps inbound lines; an outbound
|
|
11
|
+
* cap bounds the bytes waiting on a server that stopped reading its stdin,
|
|
12
|
+
* and crossing it is a fatal `overload` that closes the client. Teardown
|
|
13
|
+
* covers the server's whole process group, not only the child it spawned,
|
|
14
|
+
* escalates to SIGKILL for any member that outlives the grace period, and
|
|
15
|
+
* begins the moment the leader is seen to exit, whether or not close() has
|
|
16
|
+
* been called.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
20
|
+
import { realpathSync, statSync } from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { performance } from "node:perf_hooks";
|
|
23
|
+
import { buildSafeToolEnv, resolveSafeCwd } from "../../../core/safe-exec.js";
|
|
24
|
+
import { clampTimerDelayMs, sleep } from "../../../core/timers.js";
|
|
25
|
+
import {
|
|
26
|
+
createLineFramer,
|
|
27
|
+
DEFAULT_MAX_LINE_BYTES,
|
|
28
|
+
encodeJsonRpcMessage,
|
|
29
|
+
JSON_RPC_METHOD_NOT_FOUND,
|
|
30
|
+
type JsonRpcId,
|
|
31
|
+
type JsonRpcMessage,
|
|
32
|
+
MCP_PROTOCOL_VERSION,
|
|
33
|
+
McpError,
|
|
34
|
+
structuredContentJson,
|
|
35
|
+
} from "./protocol.js";
|
|
36
|
+
|
|
37
|
+
export const MCP_CLIENT_NAME = "clio-coder";
|
|
38
|
+
export const DEFAULT_MCP_CLIENT_VERSION = "0.4.9";
|
|
39
|
+
export const DEFAULT_INITIALIZE_TIMEOUT_MS = 15_000;
|
|
40
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
41
|
+
export const DEFAULT_STDERR_TAIL_BYTES = 16 * 1024;
|
|
42
|
+
export const DEFAULT_RESULT_TEXT_BYTES = 1024 * 1024;
|
|
43
|
+
export const DEFAULT_KILL_GRACE_MS = 3_000;
|
|
44
|
+
/** Bytes accepted for the server's stdin but not yet flushed before the client fails with `overload`. */
|
|
45
|
+
export const DEFAULT_MAX_OUTBOUND_BYTES = 4 * 1024 * 1024;
|
|
46
|
+
/** Tools accepted from one server before pagination stops. */
|
|
47
|
+
export const MCP_TOOL_LIST_CAP = 500;
|
|
48
|
+
/** Pages followed before a cursor that never ends is abandoned. */
|
|
49
|
+
const MCP_TOOL_LIST_PAGE_CAP = 100;
|
|
50
|
+
/** Smallest outbound cap a caller may configure; initialize alone needs a few hundred bytes. */
|
|
51
|
+
const MIN_OUTBOUND_BYTES = 4096;
|
|
52
|
+
/** How long a teardown waits for a SIGKILLed group to disappear before it gives up and says so. */
|
|
53
|
+
export const DEFAULT_TEARDOWN_BOUND_MS = 2_000;
|
|
54
|
+
|
|
55
|
+
export interface McpServerSpec {
|
|
56
|
+
id: string;
|
|
57
|
+
command: string;
|
|
58
|
+
args?: ReadonlyArray<string>;
|
|
59
|
+
cwd?: string;
|
|
60
|
+
env?: Readonly<Record<string, string>>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface McpClientOptions {
|
|
64
|
+
/** Containment root for the server's cwd; a cwd outside it, by path or by symlink, is refused before spawn. */
|
|
65
|
+
workspaceRoot: string;
|
|
66
|
+
clientVersion?: string;
|
|
67
|
+
initializeTimeoutMs?: number;
|
|
68
|
+
requestTimeoutMs?: number;
|
|
69
|
+
stderrTailBytes?: number;
|
|
70
|
+
maxLineBytes?: number;
|
|
71
|
+
/** Cap on the plain-text rendering of one tool result. */
|
|
72
|
+
maxResultTextBytes?: number;
|
|
73
|
+
/** Cap on bytes queued for a server that is not reading its stdin; crossing it fails the client. */
|
|
74
|
+
maxOutboundBytes?: number;
|
|
75
|
+
/** Delay between SIGTERM and SIGKILL on close. */
|
|
76
|
+
killGraceMs?: number;
|
|
77
|
+
/** Wait for the group to disappear after SIGKILL before the teardown is reported incomplete. */
|
|
78
|
+
teardownBoundMs?: number;
|
|
79
|
+
/** Aborting this signal closes the client. */
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type McpClientStatus = "idle" | "starting" | "ready" | "closed" | "failed";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* How the process-group teardown ended. `complete: false` means members of
|
|
87
|
+
* the group were still present when the post-SIGKILL bound passed; the id was
|
|
88
|
+
* released all the same (see GroupPhase), so `pgid` is for the operator's
|
|
89
|
+
* report, never for another signal.
|
|
90
|
+
*/
|
|
91
|
+
export type McpTeardownOutcome =
|
|
92
|
+
| { complete: true }
|
|
93
|
+
| { complete: false; reason: "group-survived-sigkill"; pgid: number; boundMs: number };
|
|
94
|
+
|
|
95
|
+
export type McpClientState = ({ status: Exclude<McpClientStatus, "failed"> } | { status: "failed"; reason: string }) & {
|
|
96
|
+
/** Group members were still present when the post-SIGKILL bound passed; the client stopped waiting for them. */
|
|
97
|
+
cleanupIncomplete: boolean;
|
|
98
|
+
/** The teardown's outcome, null until one has run. Kept alongside an earlier fatal error, never in place of it. */
|
|
99
|
+
teardown: McpTeardownOutcome | null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export interface McpServerInfo {
|
|
103
|
+
protocolVersion: string;
|
|
104
|
+
serverInfo: { name: string; version: string } | null;
|
|
105
|
+
capabilities: Record<string, unknown>;
|
|
106
|
+
instructions: string | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface McpToolDescriptor {
|
|
110
|
+
name: string;
|
|
111
|
+
title: string | null;
|
|
112
|
+
description: string;
|
|
113
|
+
inputSchema: Record<string, unknown>;
|
|
114
|
+
/** Server-declared hints; advisory only, never a safety input. */
|
|
115
|
+
annotations: Record<string, unknown> | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface McpToolListing {
|
|
119
|
+
tools: McpToolDescriptor[];
|
|
120
|
+
/** True when the server offered more tools than the cap admits. */
|
|
121
|
+
truncated: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type McpContentBlock =
|
|
125
|
+
| { type: "text"; text: string }
|
|
126
|
+
| { type: "image"; mimeType: string; data: string }
|
|
127
|
+
| { type: "audio"; mimeType: string; data: string }
|
|
128
|
+
| { type: "resource"; uri: string; text?: string; mimeType?: string }
|
|
129
|
+
| { type: "other"; raw: unknown };
|
|
130
|
+
|
|
131
|
+
export interface McpToolCallResult {
|
|
132
|
+
content: McpContentBlock[];
|
|
133
|
+
isError: boolean;
|
|
134
|
+
/** JSON-safe values; numeric tokens that do not round-trip use {$literal: source}. */
|
|
135
|
+
structuredContent?: unknown;
|
|
136
|
+
/** Normalized structured JSON retaining numeric source tokens, bounded by the incoming-line cap. */
|
|
137
|
+
structuredContentJson?: string;
|
|
138
|
+
/** Bounded plain rendering of the content blocks. */
|
|
139
|
+
text: string;
|
|
140
|
+
textTruncated: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface McpCallToolOptions {
|
|
144
|
+
signal?: AbortSignal;
|
|
145
|
+
timeoutMs?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface McpClient {
|
|
149
|
+
readonly id: string;
|
|
150
|
+
readonly pid: number | undefined;
|
|
151
|
+
initialize(): Promise<McpServerInfo>;
|
|
152
|
+
listTools(): Promise<McpToolListing>;
|
|
153
|
+
callTool(name: string, args: Record<string, unknown>, options?: McpCallToolOptions): Promise<McpToolCallResult>;
|
|
154
|
+
/** Resolves with the teardown outcome; an incomplete teardown is flagged there and in state(), not thrown. */
|
|
155
|
+
close(): Promise<McpTeardownOutcome>;
|
|
156
|
+
/** Synchronous exit backstop. Signals only a currently owned group; use close() for awaited teardown. */
|
|
157
|
+
killOwnedOnExit(): void;
|
|
158
|
+
state(): McpClientState;
|
|
159
|
+
serverInfo(): McpServerInfo | null;
|
|
160
|
+
/** Set by `notifications/tools/list_changed`; cleared by acknowledgeToolsChanged. */
|
|
161
|
+
toolsChanged(): boolean;
|
|
162
|
+
acknowledgeToolsChanged(): void;
|
|
163
|
+
notificationCount(): number;
|
|
164
|
+
/** Bounded tail of everything the server wrote to stderr. */
|
|
165
|
+
stderrTail(): string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface PendingRequest {
|
|
169
|
+
method: string;
|
|
170
|
+
resolve(value: unknown): void;
|
|
171
|
+
reject(error: McpError): void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
type OutboundMessage = Parameters<typeof encodeJsonRpcMessage>[0];
|
|
175
|
+
|
|
176
|
+
interface OutboundEntry {
|
|
177
|
+
payload: Buffer;
|
|
178
|
+
/** The request this line carries, so a request that settles unsent can be withdrawn. */
|
|
179
|
+
requestId: number | null;
|
|
180
|
+
onError: ((error: Error) => void) | undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type EnqueueOutcome = { kind: "queued" } | { kind: "oversized"; bytes: number } | { kind: "overloaded" };
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Lifecycle of the server's process group as this client tracks it. The
|
|
187
|
+
* leader's pid doubles as the group id because the child is spawned detached.
|
|
188
|
+
* The kernel keeps a group id reserved only while some member of that group
|
|
189
|
+
* exists; once the last member is reaped, the number can be handed to an
|
|
190
|
+
* unrelated process, and a signal-0 probe would then report a stranger as
|
|
191
|
+
* alive. Two rules keep this client from ever signalling a stranger:
|
|
192
|
+
*
|
|
193
|
+
* 1. Cleanup starts the moment the leader is seen to exit, not when close()
|
|
194
|
+
* happens to be called. At that instant the group is still ours or is
|
|
195
|
+
* already empty, so an arbitrarily late close() never gets to signal a
|
|
196
|
+
* number that has had time to change hands.
|
|
197
|
+
* 2. Once the group has been observed gone (ESRCH), or cleanup has run to
|
|
198
|
+
* its bound, the id is `released` and is never probed or signalled
|
|
199
|
+
* again, whatever a later probe would say. A group that survives SIGKILL
|
|
200
|
+
* for the confirm window is unreaped zombies or something we cannot end
|
|
201
|
+
* either way; keeping the number is worth nothing and risks a stranger.
|
|
202
|
+
*
|
|
203
|
+
* The residual exposure is a reuse of the number between two 10 ms polls of
|
|
204
|
+
* one active cleanup, the same window every group-signalling spawner in this
|
|
205
|
+
* codebase accepts. Direct-child signals go through Node's own handle, which
|
|
206
|
+
* knows whether that pid has been reaped, so they carry no such risk.
|
|
207
|
+
*/
|
|
208
|
+
type GroupPhase = "unspawned" | "live" | "cleaning" | "released";
|
|
209
|
+
|
|
210
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
211
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function errorMessage(error: unknown): string {
|
|
215
|
+
return error instanceof Error ? error.message : String(error);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Cut a string at a UTF-8 byte boundary at or below `maxBytes`. */
|
|
219
|
+
function cutUtf8(text: string, maxBytes: number): string {
|
|
220
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
221
|
+
const bytes = Buffer.from(text, "utf8");
|
|
222
|
+
let cut = Math.max(0, maxBytes);
|
|
223
|
+
while (cut > 0) {
|
|
224
|
+
const byte = bytes[cut];
|
|
225
|
+
if (byte === undefined || (byte & 0xc0) !== 0x80) break;
|
|
226
|
+
cut -= 1;
|
|
227
|
+
}
|
|
228
|
+
return bytes.subarray(0, cut).toString("utf8");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The canonical directory the server starts in, or why there is none. The cwd
|
|
233
|
+
* must stay inside the workspace root lexically and after every symbolic link
|
|
234
|
+
* is followed, and it must exist as a directory. Checked once at creation for
|
|
235
|
+
* early feedback and again at spawn, which is the boundary that matters.
|
|
236
|
+
*/
|
|
237
|
+
function containedCwd(spec: McpServerSpec, workspaceRoot: string): string | Error {
|
|
238
|
+
let lexical: string;
|
|
239
|
+
try {
|
|
240
|
+
lexical = resolveSafeCwd(spec.cwd, workspaceRoot);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
243
|
+
}
|
|
244
|
+
let realRoot: string;
|
|
245
|
+
let real: string;
|
|
246
|
+
try {
|
|
247
|
+
realRoot = realpathSync(path.resolve(workspaceRoot));
|
|
248
|
+
real = realpathSync(lexical);
|
|
249
|
+
if (!statSync(real).isDirectory()) return new Error(`cwd is not a directory: ${lexical}`);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return new Error(`cwd cannot be resolved: ${lexical} (${errorMessage(error)})`);
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
resolveSafeCwd(real, realRoot);
|
|
255
|
+
} catch {
|
|
256
|
+
return new Error(`cwd escapes workspace root through a symbolic link: ${lexical} -> ${real}`);
|
|
257
|
+
}
|
|
258
|
+
return real;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseServerInfo(value: unknown): McpServerInfo | McpError {
|
|
262
|
+
if (!isRecord(value)) return new McpError("protocol", "initialize result is not an object");
|
|
263
|
+
if (typeof value.protocolVersion !== "string" || value.protocolVersion.length === 0) {
|
|
264
|
+
return new McpError("protocol", "initialize result lacks a protocolVersion string");
|
|
265
|
+
}
|
|
266
|
+
const capabilities = isRecord(value.capabilities) ? value.capabilities : {};
|
|
267
|
+
let serverInfo: McpServerInfo["serverInfo"] = null;
|
|
268
|
+
if (isRecord(value.serverInfo)) {
|
|
269
|
+
serverInfo = {
|
|
270
|
+
name: typeof value.serverInfo.name === "string" ? value.serverInfo.name : "",
|
|
271
|
+
version: typeof value.serverInfo.version === "string" ? value.serverInfo.version : "",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
protocolVersion: value.protocolVersion,
|
|
276
|
+
serverInfo,
|
|
277
|
+
capabilities,
|
|
278
|
+
instructions: typeof value.instructions === "string" ? value.instructions : null,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseToolDescriptor(value: unknown): McpToolDescriptor | McpError {
|
|
283
|
+
if (!isRecord(value)) return new McpError("protocol", "tools/list entry is not an object");
|
|
284
|
+
if (typeof value.name !== "string" || value.name.length === 0) {
|
|
285
|
+
return new McpError("protocol", "tools/list entry lacks a name");
|
|
286
|
+
}
|
|
287
|
+
const inputSchema = isRecord(value.inputSchema) ? value.inputSchema : { type: "object" };
|
|
288
|
+
return {
|
|
289
|
+
name: value.name,
|
|
290
|
+
title: typeof value.title === "string" ? value.title : null,
|
|
291
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
292
|
+
inputSchema,
|
|
293
|
+
annotations: isRecord(value.annotations) ? value.annotations : null,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeContentBlock(value: unknown): McpContentBlock {
|
|
298
|
+
if (!isRecord(value) || typeof value.type !== "string") return { type: "other", raw: value };
|
|
299
|
+
switch (value.type) {
|
|
300
|
+
case "text":
|
|
301
|
+
return typeof value.text === "string" ? { type: "text", text: value.text } : { type: "other", raw: value };
|
|
302
|
+
case "image":
|
|
303
|
+
case "audio":
|
|
304
|
+
if (typeof value.data === "string" && typeof value.mimeType === "string") {
|
|
305
|
+
return { type: value.type, mimeType: value.mimeType, data: value.data };
|
|
306
|
+
}
|
|
307
|
+
return { type: "other", raw: value };
|
|
308
|
+
case "resource": {
|
|
309
|
+
const resource = value.resource;
|
|
310
|
+
if (!isRecord(resource) || typeof resource.uri !== "string") return { type: "other", raw: value };
|
|
311
|
+
return {
|
|
312
|
+
type: "resource",
|
|
313
|
+
uri: resource.uri,
|
|
314
|
+
...(typeof resource.text === "string" ? { text: resource.text } : {}),
|
|
315
|
+
...(typeof resource.mimeType === "string" ? { mimeType: resource.mimeType } : {}),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
case "resource_link":
|
|
319
|
+
if (typeof value.uri !== "string") return { type: "other", raw: value };
|
|
320
|
+
return {
|
|
321
|
+
type: "resource",
|
|
322
|
+
uri: value.uri,
|
|
323
|
+
...(typeof value.mimeType === "string" ? { mimeType: value.mimeType } : {}),
|
|
324
|
+
};
|
|
325
|
+
default:
|
|
326
|
+
return { type: "other", raw: value };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function renderContentBlock(block: McpContentBlock): string {
|
|
331
|
+
switch (block.type) {
|
|
332
|
+
case "text":
|
|
333
|
+
return block.text;
|
|
334
|
+
case "image":
|
|
335
|
+
case "audio":
|
|
336
|
+
return `[${block.type} ${block.mimeType}, ${Buffer.byteLength(block.data, "base64")} bytes]`;
|
|
337
|
+
case "resource":
|
|
338
|
+
return block.text === undefined ? `[resource ${block.uri}]` : `[resource ${block.uri}]\n${block.text}`;
|
|
339
|
+
case "other": {
|
|
340
|
+
const type = isRecord(block.raw) && typeof block.raw.type === "string" ? block.raw.type : "unknown";
|
|
341
|
+
return `[unsupported content type ${type}]`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Render every block as plain text and bound the whole rendering. */
|
|
347
|
+
export function renderMcpContent(
|
|
348
|
+
blocks: ReadonlyArray<McpContentBlock>,
|
|
349
|
+
maxBytes: number = DEFAULT_RESULT_TEXT_BYTES,
|
|
350
|
+
): { text: string; truncated: boolean } {
|
|
351
|
+
const joined = blocks.map(renderContentBlock).join("\n");
|
|
352
|
+
if (Buffer.byteLength(joined, "utf8") <= maxBytes) return { text: joined, truncated: false };
|
|
353
|
+
const marker = `\n[mcp result text truncated at ${maxBytes} bytes]`;
|
|
354
|
+
const budget = Math.max(0, maxBytes - Buffer.byteLength(marker, "utf8"));
|
|
355
|
+
return { text: `${cutUtf8(joined, budget)}${marker}`, truncated: true };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function parseToolCallResult(value: unknown, maxTextBytes: number): McpToolCallResult | McpError {
|
|
359
|
+
if (!isRecord(value)) return new McpError("protocol", "tools/call result is not an object");
|
|
360
|
+
if (!Array.isArray(value.content)) return new McpError("protocol", "tools/call result.content must be an array");
|
|
361
|
+
if (value.isError !== undefined && typeof value.isError !== "boolean") {
|
|
362
|
+
return new McpError("protocol", "tools/call result.isError must be a boolean");
|
|
363
|
+
}
|
|
364
|
+
if (value.structuredContent !== undefined && !isRecord(value.structuredContent)) {
|
|
365
|
+
return new McpError("protocol", "tools/call result.structuredContent must be an object");
|
|
366
|
+
}
|
|
367
|
+
const content = value.content.map(normalizeContentBlock);
|
|
368
|
+
const structuredText = structuredContentJson(value);
|
|
369
|
+
// Use transport-retained numeric tokens, not reserialized JS numbers.
|
|
370
|
+
// The evidence object uses explicit $literal tags for non-round-trips.
|
|
371
|
+
const rendered = renderMcpContent(
|
|
372
|
+
content.length === 0 && value.structuredContent !== undefined
|
|
373
|
+
? [{ type: "text", text: structuredText ?? JSON.stringify(value.structuredContent) }]
|
|
374
|
+
: content,
|
|
375
|
+
maxTextBytes,
|
|
376
|
+
);
|
|
377
|
+
const result: McpToolCallResult = {
|
|
378
|
+
content,
|
|
379
|
+
isError: value.isError === true,
|
|
380
|
+
text: rendered.text,
|
|
381
|
+
textTruncated: rendered.truncated,
|
|
382
|
+
};
|
|
383
|
+
if (value.structuredContent !== undefined) result.structuredContent = value.structuredContent;
|
|
384
|
+
if (structuredText !== undefined) result.structuredContentJson = structuredText;
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function createMcpStdioClient(spec: McpServerSpec, options: McpClientOptions): McpClient {
|
|
389
|
+
const initialCwd = containedCwd(spec, options.workspaceRoot);
|
|
390
|
+
if (initialCwd instanceof Error) throw new McpError("spawn", `mcp server ${spec.id}: ${initialCwd.message}`);
|
|
391
|
+
const initializeTimeoutMs = clampTimerDelayMs(options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS);
|
|
392
|
+
const requestTimeoutMs = clampTimerDelayMs(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
|
|
393
|
+
const stderrTailBytes = Math.max(0, options.stderrTailBytes ?? DEFAULT_STDERR_TAIL_BYTES);
|
|
394
|
+
const maxResultTextBytes = Math.max(64, options.maxResultTextBytes ?? DEFAULT_RESULT_TEXT_BYTES);
|
|
395
|
+
const maxOutboundBytes = Math.max(MIN_OUTBOUND_BYTES, options.maxOutboundBytes ?? DEFAULT_MAX_OUTBOUND_BYTES);
|
|
396
|
+
const killGraceMs = clampTimerDelayMs(options.killGraceMs ?? DEFAULT_KILL_GRACE_MS);
|
|
397
|
+
const teardownBoundMs = clampTimerDelayMs(options.teardownBoundMs ?? DEFAULT_TEARDOWN_BOUND_MS);
|
|
398
|
+
const clientVersion = options.clientVersion ?? DEFAULT_MCP_CLIENT_VERSION;
|
|
399
|
+
const framer = createLineFramer(options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES);
|
|
400
|
+
|
|
401
|
+
let status: McpClientStatus = "idle";
|
|
402
|
+
let failure: McpError | null = null;
|
|
403
|
+
let child: ChildProcess | null = null;
|
|
404
|
+
let spawned: Promise<boolean> | null = null;
|
|
405
|
+
let exited: Promise<void> | null = null;
|
|
406
|
+
let leaderExited = false;
|
|
407
|
+
let groupPhase: GroupPhase = "unspawned";
|
|
408
|
+
let groupCleanup: Promise<McpTeardownOutcome> | null = null;
|
|
409
|
+
let teardown: McpTeardownOutcome | null = null;
|
|
410
|
+
let closing = false;
|
|
411
|
+
let initializePromise: Promise<McpServerInfo> | null = null;
|
|
412
|
+
let closePromise: Promise<McpTeardownOutcome> | null = null;
|
|
413
|
+
let info: McpServerInfo | null = null;
|
|
414
|
+
let nextId = 1;
|
|
415
|
+
const pending = new Map<number, PendingRequest>();
|
|
416
|
+
const outbound: OutboundEntry[] = [];
|
|
417
|
+
let outboundQueuedBytes = 0;
|
|
418
|
+
let awaitingDrain = false;
|
|
419
|
+
let notifications = 0;
|
|
420
|
+
let toolsChangedFlag = false;
|
|
421
|
+
let stderrTailBuffer = Buffer.alloc(0);
|
|
422
|
+
|
|
423
|
+
const isAlive = (): boolean => child !== null && child.exitCode === null && child.signalCode === null;
|
|
424
|
+
|
|
425
|
+
/** The error every later request gets once the client cannot serve it. */
|
|
426
|
+
const closedError = (): McpError => failure ?? new McpError("closed", `mcp server ${spec.id} is closed`);
|
|
427
|
+
|
|
428
|
+
const rejectAllPending = (error: McpError): void => {
|
|
429
|
+
for (const entry of [...pending.values()]) entry.reject(error);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
/** Record the first fatal error; later ones never overwrite the cause a caller sees. */
|
|
433
|
+
const fail = (error: McpError): void => {
|
|
434
|
+
if (status === "closed" || status === "failed") return;
|
|
435
|
+
status = "failed";
|
|
436
|
+
failure = error;
|
|
437
|
+
rejectAllPending(error);
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/** The group id may be signalled only while the group is known to be ours (see GroupPhase). */
|
|
441
|
+
const groupOwned = (): boolean => groupPhase === "live" || groupPhase === "cleaning";
|
|
442
|
+
|
|
443
|
+
const signalGroup = (signalName: NodeJS.Signals): void => {
|
|
444
|
+
const pid = child?.pid;
|
|
445
|
+
if (pid !== undefined && process.platform !== "win32" && groupOwned()) {
|
|
446
|
+
try {
|
|
447
|
+
process.kill(-pid, signalName);
|
|
448
|
+
return;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
// ESRCH: the group is gone, and the number is no longer ours to touch.
|
|
451
|
+
if ((error as NodeJS.ErrnoException).code === "ESRCH") groupPhase = "released";
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Node's handle knows whether this pid was reaped, so the direct child is always safe to address.
|
|
455
|
+
try {
|
|
456
|
+
child?.kill(signalName);
|
|
457
|
+
} catch {
|
|
458
|
+
// Already exited.
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
/** True while any member of the server's process group exists, whether or not the leader does. */
|
|
463
|
+
const groupAlive = (): boolean => {
|
|
464
|
+
const pid = child?.pid;
|
|
465
|
+
if (pid === undefined || !groupOwned()) return false;
|
|
466
|
+
if (process.platform !== "win32") {
|
|
467
|
+
try {
|
|
468
|
+
process.kill(-pid, 0);
|
|
469
|
+
return true;
|
|
470
|
+
} catch (error) {
|
|
471
|
+
// ESRCH means every member is gone; the number is released for good.
|
|
472
|
+
// EPERM means a member exists that this process may not signal;
|
|
473
|
+
// anything else is treated as alive so cleanup never reports a
|
|
474
|
+
// teardown it did not observe.
|
|
475
|
+
if ((error as NodeJS.ErrnoException).code !== "ESRCH") return true;
|
|
476
|
+
groupPhase = "released";
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return isAlive();
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const terminationObserved = (): boolean => leaderExited && !groupAlive();
|
|
484
|
+
|
|
485
|
+
/** Poll until the leader has been reaped and its group is empty, or the bound elapses. */
|
|
486
|
+
const waitForTermination = async (timeoutMs: number): Promise<boolean> => {
|
|
487
|
+
const deadline = performance.now() + timeoutMs;
|
|
488
|
+
while (!terminationObserved()) {
|
|
489
|
+
const remaining = deadline - performance.now();
|
|
490
|
+
if (remaining <= 0) return terminationObserved();
|
|
491
|
+
const nap = sleep(Math.min(10, remaining));
|
|
492
|
+
await (leaderExited || exited === null ? nap : Promise.race([exited, nap]));
|
|
493
|
+
}
|
|
494
|
+
return true;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const destroyStdio = (): void => {
|
|
498
|
+
for (const stream of [child?.stdin, child?.stdout, child?.stderr]) stream?.destroy();
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Tear the group down once: SIGTERM, the grace period, SIGKILL for whatever
|
|
503
|
+
* is left, a bounded wait for it to disappear, then release the id for
|
|
504
|
+
* good. Runs from close() and from the leader's exit, whichever comes first;
|
|
505
|
+
* the second caller shares the first run. When something survives even
|
|
506
|
+
* SIGKILL the outcome says so, with the group id, and our pipe ends are
|
|
507
|
+
* dropped so the survivor cannot pin this process's event loop; otherwise
|
|
508
|
+
* the pipes drain naturally and the stderr tail stays whole. The outcome is
|
|
509
|
+
* flagged rather than thrown: a caller's shutdown loop cannot do anything
|
|
510
|
+
* about a survivor except report it, and the flag carries what to report.
|
|
511
|
+
*/
|
|
512
|
+
const cleanupGroup = (): Promise<McpTeardownOutcome> => {
|
|
513
|
+
if (groupCleanup !== null) return groupCleanup;
|
|
514
|
+
groupCleanup = (async () => {
|
|
515
|
+
let terminated = true;
|
|
516
|
+
const pgid = child?.pid;
|
|
517
|
+
if (groupPhase === "live" && pgid !== undefined) {
|
|
518
|
+
groupPhase = "cleaning";
|
|
519
|
+
if (groupAlive()) signalGroup("SIGTERM");
|
|
520
|
+
terminated = await waitForTermination(killGraceMs);
|
|
521
|
+
if (!terminated) {
|
|
522
|
+
if (groupAlive()) signalGroup("SIGKILL");
|
|
523
|
+
terminated = await waitForTermination(teardownBoundMs);
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
await (exited ?? Promise.resolve());
|
|
527
|
+
}
|
|
528
|
+
groupPhase = "released";
|
|
529
|
+
const outcome: McpTeardownOutcome =
|
|
530
|
+
terminated || pgid === undefined
|
|
531
|
+
? { complete: true }
|
|
532
|
+
: { complete: false, reason: "group-survived-sigkill", pgid, boundMs: killGraceMs + teardownBoundMs };
|
|
533
|
+
teardown = outcome;
|
|
534
|
+
if (!outcome.complete) destroyStdio();
|
|
535
|
+
return outcome;
|
|
536
|
+
})();
|
|
537
|
+
return groupCleanup;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
/** Bytes accepted for the server but not yet flushed: this queue plus the stream's own buffer. */
|
|
541
|
+
const pendingOutboundBytes = (): number => outboundQueuedBytes + (child?.stdin?.writableLength ?? 0);
|
|
542
|
+
|
|
543
|
+
const flushOutbound = (): void => {
|
|
544
|
+
while (!awaitingDrain) {
|
|
545
|
+
const entry = outbound.shift();
|
|
546
|
+
if (entry === undefined) return;
|
|
547
|
+
outboundQueuedBytes -= entry.payload.length;
|
|
548
|
+
const stdin = child?.stdin;
|
|
549
|
+
if (!stdin || stdin.destroyed || !stdin.writable) {
|
|
550
|
+
entry.onError?.(new Error("server stdin is not writable"));
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const accepted = stdin.write(entry.payload, (error) => {
|
|
554
|
+
if (error) entry.onError?.(error);
|
|
555
|
+
});
|
|
556
|
+
if (!accepted) {
|
|
557
|
+
awaitingDrain = true;
|
|
558
|
+
stdin.once("drain", () => {
|
|
559
|
+
awaitingDrain = false;
|
|
560
|
+
flushOutbound();
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Queue one line for the server. A line larger than the cap is refused on
|
|
568
|
+
* its own; a line that would push the unflushed total over the cap means the
|
|
569
|
+
* server stopped reading, which fails the client and closes it.
|
|
570
|
+
*/
|
|
571
|
+
const enqueueOutbound = (
|
|
572
|
+
message: OutboundMessage,
|
|
573
|
+
requestId: number | null,
|
|
574
|
+
onError?: (error: Error) => void,
|
|
575
|
+
): EnqueueOutcome => {
|
|
576
|
+
const payload = encodeJsonRpcMessage(message);
|
|
577
|
+
if (payload.length > maxOutboundBytes) return { kind: "oversized", bytes: payload.length };
|
|
578
|
+
const queued = pendingOutboundBytes();
|
|
579
|
+
if (queued + payload.length > maxOutboundBytes) {
|
|
580
|
+
fail(
|
|
581
|
+
new McpError(
|
|
582
|
+
"overload",
|
|
583
|
+
`mcp server ${spec.id} stopped reading its stdin: ${queued} bytes are queued for it and the outbound cap is ${maxOutboundBytes} bytes`,
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
void close();
|
|
587
|
+
return { kind: "overloaded" };
|
|
588
|
+
}
|
|
589
|
+
outbound.push({ payload, requestId, onError });
|
|
590
|
+
outboundQueuedBytes += payload.length;
|
|
591
|
+
flushOutbound();
|
|
592
|
+
return { kind: "queued" };
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/** Drop a request's line if it is still waiting in the queue. */
|
|
596
|
+
const withdrawOutbound = (requestId: number): void => {
|
|
597
|
+
const index = outbound.findIndex((entry) => entry.requestId === requestId);
|
|
598
|
+
if (index === -1) return;
|
|
599
|
+
const [entry] = outbound.splice(index, 1);
|
|
600
|
+
if (entry !== undefined) outboundQueuedBytes -= entry.payload.length;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const dispatch = (frame: JsonRpcMessage): void => {
|
|
604
|
+
if (frame.kind === "response") {
|
|
605
|
+
const id = frame.message.id;
|
|
606
|
+
if (typeof id !== "number") return;
|
|
607
|
+
const entry = pending.get(id);
|
|
608
|
+
if (entry === undefined) return;
|
|
609
|
+
if (frame.message.error !== undefined) {
|
|
610
|
+
const error = frame.message.error;
|
|
611
|
+
entry.reject(new McpError("server", `${entry.method}: ${error.message} (code ${error.code})`, error));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
entry.resolve(frame.message.result);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (frame.kind === "request") {
|
|
618
|
+
const request = frame.message;
|
|
619
|
+
const outcome = enqueueOutbound(
|
|
620
|
+
{
|
|
621
|
+
jsonrpc: "2.0",
|
|
622
|
+
id: request.id as JsonRpcId,
|
|
623
|
+
error: { code: JSON_RPC_METHOD_NOT_FOUND, message: `method not found: ${request.method}` },
|
|
624
|
+
},
|
|
625
|
+
null,
|
|
626
|
+
);
|
|
627
|
+
if (outcome.kind === "oversized") {
|
|
628
|
+
fail(
|
|
629
|
+
new McpError(
|
|
630
|
+
"overload",
|
|
631
|
+
`mcp server ${spec.id} sent a request whose ${outcome.bytes}-byte reply exceeds the ${maxOutboundBytes}-byte outbound cap`,
|
|
632
|
+
),
|
|
633
|
+
);
|
|
634
|
+
void close();
|
|
635
|
+
}
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
notifications += 1;
|
|
639
|
+
if (frame.message.method === "notifications/tools/list_changed") toolsChangedFlag = true;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const consumeFrames = (frames: ReturnType<typeof framer.push>): void => {
|
|
643
|
+
for (const frame of frames) {
|
|
644
|
+
if (status === "closed" || status === "failed") return;
|
|
645
|
+
if (frame.kind === "message") {
|
|
646
|
+
dispatch(frame.message);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
fail(new McpError("protocol", `protocol error from mcp server ${spec.id}: ${frame.error.message}`));
|
|
650
|
+
void close();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const spawnChild = (): boolean => {
|
|
656
|
+
const cwd = containedCwd(spec, options.workspaceRoot);
|
|
657
|
+
if (cwd instanceof Error) {
|
|
658
|
+
fail(new McpError("spawn", `mcp server ${spec.id} failed to start: ${cwd.message}`));
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
let proc: ChildProcess;
|
|
662
|
+
try {
|
|
663
|
+
proc = spawn(spec.command, [...(spec.args ?? [])], {
|
|
664
|
+
cwd,
|
|
665
|
+
env: buildSafeToolEnv({ ...(spec.env ?? {}) }),
|
|
666
|
+
detached: process.platform !== "win32",
|
|
667
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
668
|
+
});
|
|
669
|
+
} catch (error) {
|
|
670
|
+
fail(new McpError("spawn", `mcp server ${spec.id} failed to start: ${errorMessage(error)}`));
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
child = proc;
|
|
674
|
+
if (proc.pid !== undefined) groupPhase = "live";
|
|
675
|
+
spawned = new Promise<boolean>((resolve) => {
|
|
676
|
+
proc.once("spawn", () => resolve(true));
|
|
677
|
+
proc.once("error", (error) => {
|
|
678
|
+
fail(new McpError("spawn", `mcp server ${spec.id} failed to start: ${error.message}`));
|
|
679
|
+
resolve(false);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
exited = new Promise<void>((resolve) => {
|
|
683
|
+
proc.once("exit", (code, signal) => {
|
|
684
|
+
leaderExited = true;
|
|
685
|
+
if (!closing && status !== "failed") {
|
|
686
|
+
fail(
|
|
687
|
+
new McpError(
|
|
688
|
+
"closed",
|
|
689
|
+
`mcp server ${spec.id} exited with ${signal !== null ? `signal ${signal}` : `code ${code ?? "?"}`} before the client closed it`,
|
|
690
|
+
),
|
|
691
|
+
);
|
|
692
|
+
} else {
|
|
693
|
+
rejectAllPending(closedError());
|
|
694
|
+
}
|
|
695
|
+
resolve();
|
|
696
|
+
// Whatever the leader left in its group is torn down now, while the id is still ours.
|
|
697
|
+
void cleanupGroup();
|
|
698
|
+
});
|
|
699
|
+
proc.once("error", () => {
|
|
700
|
+
if (proc.pid === undefined) {
|
|
701
|
+
leaderExited = true;
|
|
702
|
+
resolve();
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
proc.stdin?.on("error", () => {
|
|
707
|
+
// Surfaced through the write callbacks; a bare listener keeps the event from throwing.
|
|
708
|
+
});
|
|
709
|
+
proc.stdout?.on("data", (chunk: Buffer) => consumeFrames(framer.push(chunk)));
|
|
710
|
+
proc.stdout?.on("end", () => consumeFrames(framer.flush()));
|
|
711
|
+
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
712
|
+
if (stderrTailBytes === 0) return;
|
|
713
|
+
const joined = Buffer.concat([stderrTailBuffer, chunk]);
|
|
714
|
+
stderrTailBuffer =
|
|
715
|
+
joined.length > stderrTailBytes ? Buffer.from(joined.subarray(joined.length - stderrTailBytes)) : joined;
|
|
716
|
+
});
|
|
717
|
+
return true;
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const request = (method: string, params: unknown, timeoutMs: number, signal?: AbortSignal): Promise<unknown> =>
|
|
721
|
+
new Promise<unknown>((resolve, reject) => {
|
|
722
|
+
if (signal?.aborted) {
|
|
723
|
+
reject(new McpError("aborted", `${method} aborted before it was sent`));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (child === null || status === "closed" || status === "failed") {
|
|
727
|
+
reject(closedError());
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const id = nextId++;
|
|
731
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
732
|
+
const settle = (finish: () => void): void => {
|
|
733
|
+
if (!pending.has(id)) return;
|
|
734
|
+
pending.delete(id);
|
|
735
|
+
withdrawOutbound(id);
|
|
736
|
+
if (timer !== null) clearTimeout(timer);
|
|
737
|
+
signal?.removeEventListener("abort", onAbort);
|
|
738
|
+
finish();
|
|
739
|
+
};
|
|
740
|
+
const onAbort = (): void => settle(() => reject(new McpError("aborted", `${method} aborted`)));
|
|
741
|
+
pending.set(id, {
|
|
742
|
+
method,
|
|
743
|
+
resolve: (value) => settle(() => resolve(value)),
|
|
744
|
+
reject: (error) => settle(() => reject(error)),
|
|
745
|
+
});
|
|
746
|
+
if (timeoutMs > 0) {
|
|
747
|
+
timer = setTimeout(
|
|
748
|
+
() => settle(() => reject(new McpError("timeout", `${method} timed out after ${timeoutMs}ms`))),
|
|
749
|
+
timeoutMs,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
753
|
+
const outcome = enqueueOutbound(
|
|
754
|
+
{ jsonrpc: "2.0", id, method, ...(params === undefined ? {} : { params }) },
|
|
755
|
+
id,
|
|
756
|
+
(error) => {
|
|
757
|
+
settle(() => reject(failure ?? new McpError("closed", `write to mcp server ${spec.id} failed: ${error.message}`)));
|
|
758
|
+
},
|
|
759
|
+
);
|
|
760
|
+
if (outcome.kind === "oversized") {
|
|
761
|
+
settle(() =>
|
|
762
|
+
reject(
|
|
763
|
+
new McpError(
|
|
764
|
+
"overload",
|
|
765
|
+
`${method} request of ${outcome.bytes} bytes exceeds the ${maxOutboundBytes}-byte outbound cap`,
|
|
766
|
+
),
|
|
767
|
+
),
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
// An overloaded queue already failed the client, which rejected this request with the cause.
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const initialize = (): Promise<McpServerInfo> => {
|
|
774
|
+
if (status === "closed" || status === "failed") return Promise.reject(closedError());
|
|
775
|
+
if (initializePromise !== null) return initializePromise;
|
|
776
|
+
initializePromise = (async () => {
|
|
777
|
+
status = "starting";
|
|
778
|
+
if (!spawnChild()) {
|
|
779
|
+
await close();
|
|
780
|
+
throw closedError();
|
|
781
|
+
}
|
|
782
|
+
const started = await (spawned ?? Promise.resolve(false));
|
|
783
|
+
if (!started) {
|
|
784
|
+
await close();
|
|
785
|
+
throw closedError();
|
|
786
|
+
}
|
|
787
|
+
let result: unknown;
|
|
788
|
+
try {
|
|
789
|
+
result = await request(
|
|
790
|
+
"initialize",
|
|
791
|
+
{
|
|
792
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
793
|
+
capabilities: {},
|
|
794
|
+
clientInfo: { name: MCP_CLIENT_NAME, version: clientVersion },
|
|
795
|
+
},
|
|
796
|
+
initializeTimeoutMs,
|
|
797
|
+
);
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const cause = error instanceof McpError ? error : new McpError("protocol", errorMessage(error));
|
|
800
|
+
fail(new McpError(cause.code, `initialize failed: ${cause.message}`, cause.data));
|
|
801
|
+
await close();
|
|
802
|
+
throw closedError();
|
|
803
|
+
}
|
|
804
|
+
const parsed = parseServerInfo(result);
|
|
805
|
+
if (parsed instanceof McpError) {
|
|
806
|
+
fail(new McpError("protocol", `initialize failed: ${parsed.message}`));
|
|
807
|
+
await close();
|
|
808
|
+
throw closedError();
|
|
809
|
+
}
|
|
810
|
+
info = parsed;
|
|
811
|
+
enqueueOutbound({ jsonrpc: "2.0", method: "notifications/initialized" }, null);
|
|
812
|
+
if (status === "starting") status = "ready";
|
|
813
|
+
return parsed;
|
|
814
|
+
})();
|
|
815
|
+
return initializePromise;
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const ensureReady = async (): Promise<void> => {
|
|
819
|
+
if (status === "ready") return;
|
|
820
|
+
await initialize();
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const listTools = async (): Promise<McpToolListing> => {
|
|
824
|
+
await ensureReady();
|
|
825
|
+
const tools: McpToolDescriptor[] = [];
|
|
826
|
+
let cursor: string | undefined;
|
|
827
|
+
let pages = 0;
|
|
828
|
+
for (;;) {
|
|
829
|
+
pages += 1;
|
|
830
|
+
const result = await request("tools/list", cursor === undefined ? {} : { cursor }, requestTimeoutMs);
|
|
831
|
+
if (!isRecord(result) || !Array.isArray(result.tools)) {
|
|
832
|
+
throw new McpError("protocol", "tools/list result lacks a tools array");
|
|
833
|
+
}
|
|
834
|
+
for (const entry of result.tools) {
|
|
835
|
+
if (tools.length >= MCP_TOOL_LIST_CAP) return { tools, truncated: true };
|
|
836
|
+
const descriptor = parseToolDescriptor(entry);
|
|
837
|
+
if (descriptor instanceof McpError) throw descriptor;
|
|
838
|
+
tools.push(descriptor);
|
|
839
|
+
}
|
|
840
|
+
const next = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : undefined;
|
|
841
|
+
if (next === undefined) return { tools, truncated: false };
|
|
842
|
+
if (tools.length >= MCP_TOOL_LIST_CAP || pages >= MCP_TOOL_LIST_PAGE_CAP) return { tools, truncated: true };
|
|
843
|
+
cursor = next;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
const callTool = async (
|
|
848
|
+
name: string,
|
|
849
|
+
args: Record<string, unknown>,
|
|
850
|
+
callOptions: McpCallToolOptions = {},
|
|
851
|
+
): Promise<McpToolCallResult> => {
|
|
852
|
+
await ensureReady();
|
|
853
|
+
const timeoutMs = clampTimerDelayMs(callOptions.timeoutMs ?? requestTimeoutMs);
|
|
854
|
+
const result = await request("tools/call", { name, arguments: args }, timeoutMs, callOptions.signal);
|
|
855
|
+
const parsed = parseToolCallResult(result, maxResultTextBytes);
|
|
856
|
+
if (parsed instanceof McpError) throw parsed;
|
|
857
|
+
return parsed;
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* End the server: close its stdin, then run (or join) the group teardown.
|
|
862
|
+
* The leader's exit is not the end of it; a descendant that outlives the
|
|
863
|
+
* leader is signalled and waited for the same way. Our ends of the pipes
|
|
864
|
+
* are dropped last, since nobody reads them after close.
|
|
865
|
+
*/
|
|
866
|
+
const close = (): Promise<McpTeardownOutcome> => {
|
|
867
|
+
if (closePromise !== null) return closePromise;
|
|
868
|
+
closePromise = (async () => {
|
|
869
|
+
closing = true;
|
|
870
|
+
if (status !== "failed") status = "closed";
|
|
871
|
+
rejectAllPending(closedError());
|
|
872
|
+
outbound.length = 0;
|
|
873
|
+
outboundQueuedBytes = 0;
|
|
874
|
+
if (child === null) {
|
|
875
|
+
teardown ??= { complete: true };
|
|
876
|
+
return teardown;
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
child.stdin?.end();
|
|
880
|
+
} catch {
|
|
881
|
+
// Nothing to flush on a dead pipe.
|
|
882
|
+
}
|
|
883
|
+
const outcome = await cleanupGroup();
|
|
884
|
+
destroyStdio();
|
|
885
|
+
return outcome;
|
|
886
|
+
})();
|
|
887
|
+
return closePromise;
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
if (options.signal !== undefined) {
|
|
891
|
+
if (options.signal.aborted) void close();
|
|
892
|
+
else options.signal.addEventListener("abort", () => void close(), { once: true });
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return {
|
|
896
|
+
id: spec.id,
|
|
897
|
+
get pid() {
|
|
898
|
+
return child?.pid;
|
|
899
|
+
},
|
|
900
|
+
initialize,
|
|
901
|
+
listTools,
|
|
902
|
+
callTool,
|
|
903
|
+
close,
|
|
904
|
+
killOwnedOnExit: () => {
|
|
905
|
+
// ESRCH releases ownership before the asynchronous teardown record settles.
|
|
906
|
+
// Check the live ownership state, never the retained pid or final outcome.
|
|
907
|
+
if (groupOwned()) signalGroup("SIGKILL");
|
|
908
|
+
},
|
|
909
|
+
state: () => {
|
|
910
|
+
const cleanup = { cleanupIncomplete: teardown?.complete === false, teardown };
|
|
911
|
+
return status === "failed"
|
|
912
|
+
? { status, reason: failure?.message ?? `mcp server ${spec.id} failed`, ...cleanup }
|
|
913
|
+
: { status, ...cleanup };
|
|
914
|
+
},
|
|
915
|
+
serverInfo: () => info,
|
|
916
|
+
toolsChanged: () => toolsChangedFlag,
|
|
917
|
+
acknowledgeToolsChanged: () => {
|
|
918
|
+
toolsChangedFlag = false;
|
|
919
|
+
},
|
|
920
|
+
notificationCount: () => notifications,
|
|
921
|
+
stderrTail: () => stderrTailBuffer.toString("utf8"),
|
|
922
|
+
};
|
|
923
|
+
}
|