@ken-jo/agent-connector 0.4.7 → 0.4.26
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/README.md +36 -24
- package/dist/{action-YCU53FNE.js → action-AZRZ3V7E.js} +14 -14
- package/dist/amazon-q-BWSOHTW5.js +571 -0
- package/dist/amazon-q-BWSOHTW5.js.map +1 -0
- package/dist/{amp-TULUPXXL.js → amp-BQZOL5GR.js} +2 -2
- package/dist/{amp-JPBQJJVM.js → amp-TF5X6OGH.js} +18 -50
- package/dist/amp-TF5X6OGH.js.map +1 -0
- package/dist/antigravity-3XHBPXZK.js +19 -0
- package/dist/{antigravity-STHZWMP3.js → antigravity-EA4L225L.js} +2 -2
- package/dist/{antigravity-cli-P6HADOAO.js → antigravity-cli-B2HXZFYQ.js} +2 -2
- package/dist/{antigravity-cli-D4PCCUG2.js → antigravity-cli-TJGOBUPK.js} +8 -7
- package/dist/{antigravity-cli-D4PCCUG2.js.map → antigravity-cli-TJGOBUPK.js.map} +1 -1
- package/dist/{chunk-CYOAH23F.js → chunk-25BNMXPP.js} +18 -18
- package/dist/{chunk-NLFPH4S2.js → chunk-2RPHDVHW.js} +2 -2
- package/dist/{chunk-PFDUGDOA.js → chunk-3I7UG52L.js} +25 -6
- package/dist/chunk-3I7UG52L.js.map +1 -0
- package/dist/{chunk-F5RX62T7.js → chunk-7QRUZAUG.js} +2 -2
- package/dist/{chunk-BCHWFTV4.js → chunk-F4DSMVZ7.js} +2 -2
- package/dist/chunk-FBJQGRET.js +38 -0
- package/dist/chunk-FBJQGRET.js.map +1 -0
- package/dist/{chunk-WZA4CSGJ.js → chunk-H53QZQJI.js} +15 -2
- package/dist/chunk-H53QZQJI.js.map +1 -0
- package/dist/{chunk-2F5QH3D4.js → chunk-HU6DZZBG.js} +36 -36
- package/dist/{chunk-7ECYDK5T.js → chunk-HZDU3J4U.js} +10 -5
- package/dist/chunk-HZDU3J4U.js.map +1 -0
- package/dist/{chunk-7L6ITC4T.js → chunk-IEGR4TP3.js} +22 -6
- package/dist/chunk-IEGR4TP3.js.map +1 -0
- package/dist/{chunk-SCVBYSF7.js → chunk-IRUYJD23.js} +28 -28
- package/dist/{chunk-6RYPUR6O.js → chunk-IW46JIQM.js} +34 -44
- package/dist/chunk-IW46JIQM.js.map +1 -0
- package/dist/{chunk-WFXWJITA.js → chunk-JLMD3IGK.js} +2 -2
- package/dist/{chunk-TZQBASI7.js → chunk-KD4BE5MW.js} +13 -49
- package/dist/chunk-KD4BE5MW.js.map +1 -0
- package/dist/{chunk-FAHIMJRU.js → chunk-L7ESV37V.js} +2 -2
- package/dist/{chunk-JNM3TPLX.js → chunk-LETB5HCK.js} +2 -2
- package/dist/{chunk-G5CQHVQG.js → chunk-M3H7PQWT.js} +13 -16
- package/dist/chunk-M3H7PQWT.js.map +1 -0
- package/dist/{chunk-JWNT5KW7.js → chunk-O7SDRBVB.js} +3 -1
- package/dist/chunk-O7SDRBVB.js.map +1 -0
- package/dist/{chunk-YQLQJZ7S.js → chunk-OLI5SD6I.js} +8 -4
- package/dist/chunk-OLI5SD6I.js.map +1 -0
- package/dist/{chunk-SDPIT4HW.js → chunk-Q5HR4ADS.js} +2 -2
- package/dist/{chunk-TRQKHP7D.js → chunk-QY42WGBX.js} +23 -2
- package/dist/chunk-QY42WGBX.js.map +1 -0
- package/dist/{chunk-I5MLE6S7.js → chunk-TO57WYBB.js} +15 -1
- package/dist/chunk-TO57WYBB.js.map +1 -0
- package/dist/chunk-UM5S7DKK.js +33 -0
- package/dist/chunk-UM5S7DKK.js.map +1 -0
- package/dist/{chunk-MDNX7HTN.js → chunk-YEJCNOET.js} +4 -4
- package/dist/{chunk-HAIMYUEY.js → chunk-YKVNI44Y.js} +51 -28
- package/dist/chunk-YKVNI44Y.js.map +1 -0
- package/dist/{chunk-DT6Y7XBI.js → chunk-YRBFSZ2G.js} +17 -39
- package/dist/chunk-YRBFSZ2G.js.map +1 -0
- package/dist/{chunk-SPWFUHHK.js → chunk-ZVTPSV5Z.js} +11 -10
- package/dist/{chunk-SPWFUHHK.js.map → chunk-ZVTPSV5Z.js.map} +1 -1
- package/dist/{chunk-N6HH7K6P.js → chunk-ZY25RG4Y.js} +2 -2
- package/dist/{claude-code-ROSIILTM.js → claude-code-JLPFUZ4V.js} +18 -41
- package/dist/claude-code-JLPFUZ4V.js.map +1 -0
- package/dist/{claude-code-W74MNTAC.js → claude-code-NMRKTAP7.js} +2 -2
- package/dist/cli/sdk.js +7 -7
- package/dist/cli.js +1 -1
- package/dist/{cline-JUTO4V27.js → cline-GBP3EVSX.js} +17 -49
- package/dist/cline-GBP3EVSX.js.map +1 -0
- package/dist/{codebuff-3PMZJJHP.js → codebuff-ETYT5UBL.js} +2 -2
- package/dist/{codebuff-2QUHL6HO.js → codebuff-WMKFUVER.js} +11 -42
- package/dist/codebuff-WMKFUVER.js.map +1 -0
- package/dist/{codex-YBBTIDD4.js → codex-5CZR5XKX.js} +28 -64
- package/dist/codex-5CZR5XKX.js.map +1 -0
- package/dist/{codex-Q3ATRRX3.js → codex-YAORPCOD.js} +2 -2
- package/dist/{continue-W4TU4JSB.js → continue-3AGXWM5W.js} +12 -35
- package/dist/continue-3AGXWM5W.js.map +1 -0
- package/dist/{copilot-cli-Y74SOP5G.js → copilot-cli-IH4P3GRZ.js} +17 -66
- package/dist/copilot-cli-IH4P3GRZ.js.map +1 -0
- package/dist/{copilot-cli-SJGIXVSD.js → copilot-cli-MZTAWMPW.js} +2 -2
- package/dist/{crush-GMEWLXK5.js → crush-FDBL3JEK.js} +17 -44
- package/dist/crush-FDBL3JEK.js.map +1 -0
- package/dist/{crush-LN5QFFR2.js → crush-QBXLNN7T.js} +2 -2
- package/dist/{cursor-QZ6MTO74.js → cursor-DIW43TRZ.js} +2 -2
- package/dist/{cursor-JBS4B3Q4.js → cursor-R5QB4YR4.js} +15 -53
- package/dist/cursor-R5QB4YR4.js.map +1 -0
- package/dist/{detect-2NIXILMZ.js → detect-L5OZPVFO.js} +4 -4
- package/dist/{doctor-QWJBWALA.js → doctor-WDIQ2YFE.js} +16 -15
- package/dist/{doctor-QWJBWALA.js.map → doctor-WDIQ2YFE.js.map} +1 -1
- package/dist/{droid-W5WF5XEX.js → droid-U3ZBLQFO.js} +2 -2
- package/dist/{droid-TJMTNCQE.js → droid-X2QHQL7D.js} +19 -42
- package/dist/droid-X2QHQL7D.js.map +1 -0
- package/dist/{gemini-cli-RAONGZRG.js → gemini-cli-HCN7XKSX.js} +17 -65
- package/dist/gemini-cli-HCN7XKSX.js.map +1 -0
- package/dist/{gemini-cli-CIRW37TU.js → gemini-cli-NMVVBO3K.js} +2 -2
- package/dist/{goose-A3M5GODH.js → goose-64V46Y2W.js} +2 -2
- package/dist/{goose-G4KKM6YU.js → goose-ILMM76JP.js} +21 -57
- package/dist/goose-ILMM76JP.js.map +1 -0
- package/dist/{hermes-ZYLMOFLZ.js → hermes-F3EGTK6N.js} +2 -2
- package/dist/{hermes-PJDTJE6F.js → hermes-YX2ODUZL.js} +17 -55
- package/dist/hermes-YX2ODUZL.js.map +1 -0
- package/dist/{hook-FDGIQPRC.js → hook-JRDQ56BN.js} +14 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{install-TLG3QVP3.js → install-HRCVPRAQ.js} +16 -15
- package/dist/{install-TLG3QVP3.js.map → install-HRCVPRAQ.js.map} +1 -1
- package/dist/{introspect-Cn3MSA4_.d.ts → introspect-B164-hj3.d.ts} +1 -1
- package/dist/{jetbrains-copilot-DNXSLQE5.js → jetbrains-copilot-QESFUN6W.js} +12 -39
- package/dist/jetbrains-copilot-QESFUN6W.js.map +1 -0
- package/dist/{kilo-FERJ3X46.js → kilo-POTCYIRG.js} +28 -92
- package/dist/kilo-POTCYIRG.js.map +1 -0
- package/dist/{kilo-NSWAVP6P.js → kilo-SW7BH6EG.js} +2 -2
- package/dist/{kilo-cli-TDCONH5G.js → kilo-cli-MO6FSMH2.js} +19 -50
- package/dist/kilo-cli-MO6FSMH2.js.map +1 -0
- package/dist/{kilo-cli-EVBKUODA.js → kilo-cli-ZF272ZHN.js} +2 -2
- package/dist/{kimi-EI3UM3ZV.js → kimi-5FHEXGZD.js} +72 -13
- package/dist/kimi-5FHEXGZD.js.map +1 -0
- package/dist/{kimi-RIRD4J5J.js → kimi-RXS36OJP.js} +36 -50
- package/dist/kimi-RXS36OJP.js.map +1 -0
- package/dist/{kiro-X7JVLAN7.js → kiro-VTLX7CMF.js} +12 -33
- package/dist/kiro-VTLX7CMF.js.map +1 -0
- package/dist/{kiro-UCLSLBW2.js → kiro-WGOI5CGT.js} +2 -2
- package/dist/{leaderboard-MXGENYXN.js → leaderboard-ASGR3E2U.js} +6 -6
- package/dist/{mimo-code-RFLQ727H.js → mimo-code-F4BWMESO.js} +57 -91
- package/dist/mimo-code-F4BWMESO.js.map +1 -0
- package/dist/{mux-D77HBSBU.js → mux-2YFCYFM7.js} +11 -41
- package/dist/mux-2YFCYFM7.js.map +1 -0
- package/dist/{mux-ZMLVAXOO.js → mux-XM6AP35L.js} +2 -2
- package/dist/{nemoclaw-LXUTMR56.js → nemoclaw-QDB6O22A.js} +7 -7
- package/dist/{omp-CO7IXURG.js → omp-P2JNAEPN.js} +18 -50
- package/dist/omp-P2JNAEPN.js.map +1 -0
- package/dist/openclaw-3UMBSSZM.js +18 -0
- package/dist/{openclaw-IYA7ASL6.js → openclaw-5EQFGOWL.js} +2 -2
- package/dist/{opencode-QKZ5EIYU.js → opencode-Q6H4YT66.js} +24 -90
- package/dist/opencode-Q6H4YT66.js.map +1 -0
- package/dist/{opencode-HWT3RSMN.js → opencode-QGP4ALQY.js} +2 -2
- package/dist/{package-IA56LSC3.js → package-IXBSG7HD.js} +11 -11
- package/dist/{pi-UFX2OYFV.js → pi-GGDQREVG.js} +2 -2
- package/dist/{pi-YGBXYWY5.js → pi-GW25LPC3.js} +6 -6
- package/dist/{qwen-code-W7NUCDEN.js → qwen-code-GQILBJ5B.js} +2 -2
- package/dist/{qwen-code-E2MSEZK6.js → qwen-code-R6XXEEHJ.js} +13 -34
- package/dist/qwen-code-R6XXEEHJ.js.map +1 -0
- package/dist/{roo-code-AGGJ473T.js → roo-code-EZPNFC5V.js} +17 -54
- package/dist/roo-code-EZPNFC5V.js.map +1 -0
- package/dist/{roo-code-ZLKH4A7N.js → roo-code-SUWI4BBN.js} +2 -2
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +12 -12
- package/dist/sdk/index.d.ts +3 -3
- package/dist/sdk/index.js +6 -6
- package/dist/sdk/test.d.ts +2 -2
- package/dist/sdk/test.js +9 -9
- package/dist/{serve-RNUCK5F7.js → serve-CFBQYKRB.js} +14 -14
- package/dist/{status-JOX6RLJU.js → status-ASBFEFQQ.js} +11 -10
- package/dist/{status-JOX6RLJU.js.map → status-ASBFEFQQ.js.map} +1 -1
- package/dist/{statusline-ZVFI5DRT.js → statusline-WKLWSM7I.js} +13 -13
- package/dist/{synthetic-UKR6SMGS.js → synthetic-6FRA7P5Q.js} +2 -2
- package/dist/{telemetry-3FN6HDVU.js → telemetry-64GCGNIW.js} +10 -10
- package/dist/{trae-6L226ZDU.js → trae-6VQW2UVP.js} +12 -41
- package/dist/trae-6VQW2UVP.js.map +1 -0
- package/dist/{trae-PA26DPBW.js → trae-CPPYASR2.js} +2 -2
- package/dist/{types-_6arHbX9.d.ts → types-COSBYpKs.d.ts} +7 -5
- package/dist/{uninstall-SCKZYB4J.js → uninstall-KDJYE5ET.js} +16 -15
- package/dist/{uninstall-SCKZYB4J.js.map → uninstall-KDJYE5ET.js.map} +1 -1
- package/dist/{upgrade-WU3KZWR4.js → upgrade-YQRVYR5U.js} +16 -15
- package/dist/{upgrade-WU3KZWR4.js.map → upgrade-YQRVYR5U.js.map} +1 -1
- package/dist/{usage-G72SF7JG.js → usage-OV2BDZKF.js} +3 -3
- package/dist/{usage-event-DHSEBO5T.js → usage-event-KPI3NEX3.js} +13 -13
- package/dist/{vscode-copilot-6EOZDLXC.js → vscode-copilot-UOEEV4P3.js} +17 -67
- package/dist/vscode-copilot-UOEEV4P3.js.map +1 -0
- package/dist/{warp-N5YYN5BF.js → warp-LUK2BPCU.js} +9 -21
- package/dist/warp-LUK2BPCU.js.map +1 -0
- package/dist/{warp-JNQA7PBU.js → warp-NRBBJQR2.js} +2 -2
- package/dist/{windsurf-4TGSJIVV.js → windsurf-NZ3G42NQ.js} +8 -20
- package/dist/windsurf-NZ3G42NQ.js.map +1 -0
- package/dist/{zed-6P2UFWXU.js → zed-LGWGZ4EO.js} +13 -23
- package/dist/zed-LGWGZ4EO.js.map +1 -0
- package/dist/{zed-NNNX5MNT.js → zed-XJFOOKCO.js} +2 -2
- package/package.json +7 -2
- package/dist/amazon-q-W574ZX35.js +0 -276
- package/dist/amazon-q-W574ZX35.js.map +0 -1
- package/dist/amp-JPBQJJVM.js.map +0 -1
- package/dist/antigravity-VK2Y3FGV.js +0 -18
- package/dist/chunk-6RYPUR6O.js.map +0 -1
- package/dist/chunk-7ECYDK5T.js.map +0 -1
- package/dist/chunk-7L6ITC4T.js.map +0 -1
- package/dist/chunk-DT6Y7XBI.js.map +0 -1
- package/dist/chunk-G5CQHVQG.js.map +0 -1
- package/dist/chunk-HAIMYUEY.js.map +0 -1
- package/dist/chunk-I5MLE6S7.js.map +0 -1
- package/dist/chunk-JWNT5KW7.js.map +0 -1
- package/dist/chunk-PFDUGDOA.js.map +0 -1
- package/dist/chunk-TRQKHP7D.js.map +0 -1
- package/dist/chunk-TZQBASI7.js.map +0 -1
- package/dist/chunk-WZA4CSGJ.js.map +0 -1
- package/dist/chunk-YQLQJZ7S.js.map +0 -1
- package/dist/claude-code-ROSIILTM.js.map +0 -1
- package/dist/cline-JUTO4V27.js.map +0 -1
- package/dist/codebuff-2QUHL6HO.js.map +0 -1
- package/dist/codex-YBBTIDD4.js.map +0 -1
- package/dist/continue-W4TU4JSB.js.map +0 -1
- package/dist/copilot-cli-Y74SOP5G.js.map +0 -1
- package/dist/crush-GMEWLXK5.js.map +0 -1
- package/dist/cursor-JBS4B3Q4.js.map +0 -1
- package/dist/droid-TJMTNCQE.js.map +0 -1
- package/dist/gemini-cli-RAONGZRG.js.map +0 -1
- package/dist/goose-G4KKM6YU.js.map +0 -1
- package/dist/hermes-PJDTJE6F.js.map +0 -1
- package/dist/jetbrains-copilot-DNXSLQE5.js.map +0 -1
- package/dist/kilo-FERJ3X46.js.map +0 -1
- package/dist/kilo-cli-TDCONH5G.js.map +0 -1
- package/dist/kimi-EI3UM3ZV.js.map +0 -1
- package/dist/kimi-RIRD4J5J.js.map +0 -1
- package/dist/kiro-X7JVLAN7.js.map +0 -1
- package/dist/mimo-code-RFLQ727H.js.map +0 -1
- package/dist/mux-D77HBSBU.js.map +0 -1
- package/dist/omp-CO7IXURG.js.map +0 -1
- package/dist/openclaw-XWJLEW35.js +0 -18
- package/dist/opencode-QKZ5EIYU.js.map +0 -1
- package/dist/qwen-code-E2MSEZK6.js.map +0 -1
- package/dist/roo-code-AGGJ473T.js.map +0 -1
- package/dist/trae-6L226ZDU.js.map +0 -1
- package/dist/vscode-copilot-6EOZDLXC.js.map +0 -1
- package/dist/warp-N5YYN5BF.js.map +0 -1
- package/dist/windsurf-4TGSJIVV.js.map +0 -1
- package/dist/zed-6P2UFWXU.js.map +0 -1
- /package/dist/{action-YCU53FNE.js.map → action-AZRZ3V7E.js.map} +0 -0
- /package/dist/{amp-TULUPXXL.js.map → amp-BQZOL5GR.js.map} +0 -0
- /package/dist/{antigravity-VK2Y3FGV.js.map → antigravity-3XHBPXZK.js.map} +0 -0
- /package/dist/{antigravity-STHZWMP3.js.map → antigravity-EA4L225L.js.map} +0 -0
- /package/dist/{antigravity-cli-P6HADOAO.js.map → antigravity-cli-B2HXZFYQ.js.map} +0 -0
- /package/dist/{chunk-CYOAH23F.js.map → chunk-25BNMXPP.js.map} +0 -0
- /package/dist/{chunk-NLFPH4S2.js.map → chunk-2RPHDVHW.js.map} +0 -0
- /package/dist/{chunk-F5RX62T7.js.map → chunk-7QRUZAUG.js.map} +0 -0
- /package/dist/{chunk-BCHWFTV4.js.map → chunk-F4DSMVZ7.js.map} +0 -0
- /package/dist/{chunk-2F5QH3D4.js.map → chunk-HU6DZZBG.js.map} +0 -0
- /package/dist/{chunk-SCVBYSF7.js.map → chunk-IRUYJD23.js.map} +0 -0
- /package/dist/{chunk-WFXWJITA.js.map → chunk-JLMD3IGK.js.map} +0 -0
- /package/dist/{chunk-FAHIMJRU.js.map → chunk-L7ESV37V.js.map} +0 -0
- /package/dist/{chunk-JNM3TPLX.js.map → chunk-LETB5HCK.js.map} +0 -0
- /package/dist/{chunk-SDPIT4HW.js.map → chunk-Q5HR4ADS.js.map} +0 -0
- /package/dist/{chunk-MDNX7HTN.js.map → chunk-YEJCNOET.js.map} +0 -0
- /package/dist/{chunk-N6HH7K6P.js.map → chunk-ZY25RG4Y.js.map} +0 -0
- /package/dist/{claude-code-W74MNTAC.js.map → claude-code-NMRKTAP7.js.map} +0 -0
- /package/dist/{codebuff-3PMZJJHP.js.map → codebuff-ETYT5UBL.js.map} +0 -0
- /package/dist/{codex-Q3ATRRX3.js.map → codex-YAORPCOD.js.map} +0 -0
- /package/dist/{copilot-cli-SJGIXVSD.js.map → copilot-cli-MZTAWMPW.js.map} +0 -0
- /package/dist/{crush-LN5QFFR2.js.map → crush-QBXLNN7T.js.map} +0 -0
- /package/dist/{cursor-QZ6MTO74.js.map → cursor-DIW43TRZ.js.map} +0 -0
- /package/dist/{detect-2NIXILMZ.js.map → detect-L5OZPVFO.js.map} +0 -0
- /package/dist/{droid-W5WF5XEX.js.map → droid-U3ZBLQFO.js.map} +0 -0
- /package/dist/{gemini-cli-CIRW37TU.js.map → gemini-cli-NMVVBO3K.js.map} +0 -0
- /package/dist/{goose-A3M5GODH.js.map → goose-64V46Y2W.js.map} +0 -0
- /package/dist/{hermes-ZYLMOFLZ.js.map → hermes-F3EGTK6N.js.map} +0 -0
- /package/dist/{hook-FDGIQPRC.js.map → hook-JRDQ56BN.js.map} +0 -0
- /package/dist/{kilo-NSWAVP6P.js.map → kilo-SW7BH6EG.js.map} +0 -0
- /package/dist/{kilo-cli-EVBKUODA.js.map → kilo-cli-ZF272ZHN.js.map} +0 -0
- /package/dist/{kiro-UCLSLBW2.js.map → kiro-WGOI5CGT.js.map} +0 -0
- /package/dist/{leaderboard-MXGENYXN.js.map → leaderboard-ASGR3E2U.js.map} +0 -0
- /package/dist/{mux-ZMLVAXOO.js.map → mux-XM6AP35L.js.map} +0 -0
- /package/dist/{nemoclaw-LXUTMR56.js.map → nemoclaw-QDB6O22A.js.map} +0 -0
- /package/dist/{openclaw-XWJLEW35.js.map → openclaw-3UMBSSZM.js.map} +0 -0
- /package/dist/{openclaw-IYA7ASL6.js.map → openclaw-5EQFGOWL.js.map} +0 -0
- /package/dist/{opencode-HWT3RSMN.js.map → opencode-QGP4ALQY.js.map} +0 -0
- /package/dist/{package-IA56LSC3.js.map → package-IXBSG7HD.js.map} +0 -0
- /package/dist/{pi-UFX2OYFV.js.map → pi-GGDQREVG.js.map} +0 -0
- /package/dist/{pi-YGBXYWY5.js.map → pi-GW25LPC3.js.map} +0 -0
- /package/dist/{qwen-code-W7NUCDEN.js.map → qwen-code-GQILBJ5B.js.map} +0 -0
- /package/dist/{roo-code-ZLKH4A7N.js.map → roo-code-SUWI4BBN.js.map} +0 -0
- /package/dist/{serve-RNUCK5F7.js.map → serve-CFBQYKRB.js.map} +0 -0
- /package/dist/{statusline-ZVFI5DRT.js.map → statusline-WKLWSM7I.js.map} +0 -0
- /package/dist/{synthetic-UKR6SMGS.js.map → synthetic-6FRA7P5Q.js.map} +0 -0
- /package/dist/{telemetry-3FN6HDVU.js.map → telemetry-64GCGNIW.js.map} +0 -0
- /package/dist/{trae-PA26DPBW.js.map → trae-CPPYASR2.js.map} +0 -0
- /package/dist/{usage-G72SF7JG.js.map → usage-OV2BDZKF.js.map} +0 -0
- /package/dist/{usage-event-DHSEBO5T.js.map → usage-event-KPI3NEX3.js.map} +0 -0
- /package/dist/{warp-JNQA7PBU.js.map → warp-NRBBJQR2.js.map} +0 -0
- /package/dist/{zed-NNNX5MNT.js.map → zed-XJFOOKCO.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/marketplace-state.ts"],"sourcesContent":["/**\n * core/marketplace-state — pure read/write helpers for MARKETPLACE-method\n * install state. A LEAF module (fs + paths only, no adapters, no spawning) so\n * the installer, the marketplace orchestration, doctor, and status can all\n * import it without cycles.\n *\n * Two kinds of state live here:\n *\n * 1. The per-connector state record `connectorDir(id)/marketplace-installs.json`\n * — one entry per platform a connector was marketplace-installed on\n * ({ format, bundleDir, marketplace, scope, version, contentHash,\n * installedAt }). It drives `uninstall --method auto`, upgrade staleness\n * warnings, doctor, and the double-install guard.\n *\n * 2. Read-only fs EVIDENCE probes of the hosts' own plugin state (e.g.\n * Claude Code's `$CLAUDE_CONFIG_DIR/plugins/installed_plugins.json`).\n * These catch installs the user performed manually from `package`'s\n * printed instructions, which the state record alone would miss. Probes\n * read undocumented host internals, so every reader degrades to null/false\n * on absence or parse failure — the guard convention is \"refuse only on\n * positive evidence\" (docs: design risk 4).\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n existsSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport TOML from \"@iarna/toml\";\n\nimport type { InstallScope, PlatformId } from \"./types.js\";\nimport type { PackageFormat } from \"./package.js\";\nimport { connectorDir, connectorsDir, dataRoot, ensureDir } from \"./paths.js\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// State record: connectorDir(id)/marketplace-installs.json\n// ─────────────────────────────────────────────────────────────────────────\n\n/** The catalog name every staged local marketplace is registered under. */\nexport const MARKETPLACE_NAME = \"agent-connector\";\n\n/** One marketplace install of one connector on one platform. */\nexport interface MarketplaceInstallRecord {\n /** Bundle format that was staged + installed (e.g. \"claude-plugin\"). */\n format: PackageFormat;\n /** Absolute path of the staged plugin dir the host references/copied. */\n bundleDir: string;\n /** Marketplace/catalog name the install is keyed under ({@link MARKETPLACE_NAME}). */\n marketplace: string;\n /** Install scope (v1: always \"user\"). */\n scope: InstallScope;\n /** connector.version at install time (staleness warnings key off this). */\n version: string;\n /** sha256 over the staged plugin dir at install time (drift detection). */\n contentHash: string;\n /** ISO timestamp of the recorded install. */\n installedAt: string;\n}\n\n/** platform → record map persisted per connector. */\nexport type MarketplaceInstalls = Partial<\n Record<PlatformId, MarketplaceInstallRecord>\n>;\n\n/** Path of the per-connector marketplace state record. */\nexport function marketplaceInstallsPath(connectorId: string): string {\n return join(connectorDir(connectorId), \"marketplace-installs.json\");\n}\n\n/** Read the state record ({} on absence/parse failure — never throws). */\nexport function readMarketplaceInstalls(connectorId: string): MarketplaceInstalls {\n try {\n const raw = readFileSync(marketplaceInstallsPath(connectorId), \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as MarketplaceInstalls;\n }\n } catch {\n /* absent / unreadable → empty */\n }\n return {};\n}\n\n/** Write the state record; an empty map DELETES the file (uninstall leaves no husk). */\nexport function writeMarketplaceInstalls(\n connectorId: string,\n installs: MarketplaceInstalls,\n): void {\n const path = marketplaceInstallsPath(connectorId);\n if (Object.keys(installs).length === 0) {\n rmSync(path, { force: true });\n return;\n }\n ensureDir(dirname(path));\n writeFileSync(path, `${JSON.stringify(installs, null, 2)}\\n`, \"utf8\");\n}\n\n/** True when THIS connector has any recorded marketplace install. */\nexport function connectorHasMarketplaceRecords(connectorId: string): boolean {\n return Object.keys(readMarketplaceInstalls(connectorId)).length > 0;\n}\n\n/**\n * True when ANY registered connector (optionally excluding one) still records a\n * marketplace install. Guards home-bin removal on purge: an installed plugin's\n * hooks exec the home-bin OUTSIDE any adapter-managed config, so removing the\n * launcher while a marketplace install survives would silently kill its hooks.\n */\nexport function anyMarketplaceRecordsRemain(excludeConnectorId?: string): boolean {\n const dir = connectorsDir();\n if (!existsSync(dir)) return false;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return false;\n }\n return entries.some(\n (id) => id !== excludeConnectorId && connectorHasMarketplaceRecords(id),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Staging roots — bundles staged by-reference must survive cwd changes and\n// `rm -rf dist-plugin`, so they live under the data-root, one shared root per\n// host family, holding every marketplace-installed connector's bundle plus ONE\n// regenerated catalog named \"agent-connector\" listing them all.\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Root for every marketplace staging tree: <dataRoot>/marketplace. */\nexport function marketplaceRoot(): string {\n return join(dataRoot(), \"marketplace\");\n}\n\n/** Shared Claude-family staging root: <dataRoot>/marketplace/claude. */\nexport function claudeStagingRoot(): string {\n return join(marketplaceRoot(), \"claude\");\n}\n\n/** Shared Codex-family staging root: <dataRoot>/marketplace/codex. */\nexport function codexStagingRoot(): string {\n return join(marketplaceRoot(), \"codex\");\n}\n\n/** Shared agy (Antigravity) staging root: <dataRoot>/marketplace/agy. */\nexport function agyStagingRoot(): string {\n return join(marketplaceRoot(), \"agy\");\n}\n\n/** Shared Gemini-CLI staging root: <dataRoot>/marketplace/gemini. */\nexport function geminiStagingRoot(): string {\n return join(marketplaceRoot(), \"gemini\");\n}\n\n/** Shared Qwen-Code staging root: <dataRoot>/marketplace/qwen. */\nexport function qwenStagingRoot(): string {\n return join(marketplaceRoot(), \"qwen\");\n}\n\n/** Shared droid (Factory) staging root: <dataRoot>/marketplace/droid. */\nexport function droidStagingRoot(): string {\n return join(marketplaceRoot(), \"droid\");\n}\n\n/**\n * Shared npm-local staging root: <dataRoot>/marketplace/npm. Every npm-plugin\n * host (opencode / kilo / kilo-cli) stages here; the host config's `plugin`\n * array references the bundle by `file://<this>/<id>`, so it MUST live under the\n * stable data-root (it survives cwd changes and `rm -rf dist-plugin`).\n */\nexport function npmStagingRoot(): string {\n return join(marketplaceRoot(), \"npm\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Claude Code host-state readers ($CLAUDE_CONFIG_DIR || ~/.claude)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Claude Code's config dir, honoring the CLAUDE_CONFIG_DIR override. */\nexport function claudeConfigDir(): string {\n const override = process.env.CLAUDE_CONFIG_DIR;\n if (override && override.trim() !== \"\") return resolve(override);\n return join(homedir(), \".claude\");\n}\n\n/** The installed_plugins.json key our marketplace install creates for `id`. */\nexport function claudePluginKey(connectorId: string): string {\n return `${connectorId}@${MARKETPLACE_NAME}`;\n}\n\nfunction readJsonFile(path: string): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n /* absent / unreadable → null */\n }\n return null;\n}\n\n/** The `plugins` map from Claude's installed_plugins.json (null when unreadable). */\nexport function readClaudeInstalledPlugins(): Record<string, unknown> | null {\n const file = join(claudeConfigDir(), \"plugins\", \"installed_plugins.json\");\n const parsed = readJsonFile(file);\n const plugins = parsed?.plugins;\n if (plugins && typeof plugins === \"object\" && !Array.isArray(plugins)) {\n return plugins as Record<string, unknown>;\n }\n return null;\n}\n\n/** True when Claude's own state lists `<id>@agent-connector` as installed. */\nexport function claudePluginInstalled(connectorId: string): boolean {\n const plugins = readClaudeInstalledPlugins();\n if (!plugins) return false;\n const entry = plugins[claudePluginKey(connectorId)];\n return Array.isArray(entry) ? entry.length > 0 : entry != null;\n}\n\n/** True when ANY `*@agent-connector` plugin remains in Claude's state. */\nexport function anyClaudeAgentConnectorPlugins(): boolean {\n const plugins = readClaudeInstalledPlugins();\n if (!plugins) return false;\n return Object.entries(plugins).some(\n ([key, entry]) =>\n key.endsWith(`@${MARKETPLACE_NAME}`) &&\n (Array.isArray(entry) ? entry.length > 0 : entry != null),\n );\n}\n\n/**\n * The directory Claude's known_marketplaces.json records for `name`, or null\n * when not registered / unreadable. Used both as a presence probe and as the\n * NAME-COLLISION check: a registration pointing somewhere other than our\n * staging root belongs to the user and must never be touched.\n */\nexport function claudeKnownMarketplacePath(name: string): string | null {\n const file = join(claudeConfigDir(), \"plugins\", \"known_marketplaces.json\");\n const parsed = readJsonFile(file);\n const entry = parsed?.[name];\n if (!entry || typeof entry !== \"object\") return null;\n const e = entry as { installLocation?: unknown; source?: { path?: unknown } };\n if (typeof e.installLocation === \"string\") return e.installLocation;\n const sourcePath = e.source?.path;\n return typeof sourcePath === \"string\" ? sourcePath : null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Codex host-state readers ($CODEX_HOME || ~/.codex → config.toml)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Codex's config home, honoring the CODEX_HOME override (defaults to ~/.codex). */\nexport function codexConfigHome(): string {\n const env = process.env.CODEX_HOME;\n if (env && env.trim() !== \"\") {\n if (env.startsWith(\"~\")) return join(homedir(), env.replace(/^~[/\\\\]?/, \"\"));\n return resolve(env);\n }\n return join(homedir(), \".codex\");\n}\n\n/** The config.toml key our marketplace install creates for `id` (== claude's). */\nexport function codexPluginKey(connectorId: string): string {\n return `${connectorId}@${MARKETPLACE_NAME}`;\n}\n\n/** Parse <CODEX_HOME>/config.toml ({} on absence/parse failure — never throws). */\nfunction readCodexConfig(): Record<string, unknown> {\n const file = join(codexConfigHome(), \"config.toml\");\n try {\n return TOML.parse(readFileSync(file, \"utf8\")) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/**\n * True when Codex's config.toml carries the `[plugins.\"<id>@agent-connector\"]`\n * table. The DEFINITIVE install probe — empty `plugins/cache/...` dirs linger\n * after uninstall, so the cache dir must NOT be used (docs: codex quirk).\n */\nexport function codexPluginInstalled(connectorId: string): boolean {\n const cfg = readCodexConfig();\n const plugins = cfg[\"plugins\"];\n if (!plugins || typeof plugins !== \"object\" || Array.isArray(plugins)) return false;\n return codexPluginKey(connectorId) in (plugins as Record<string, unknown>);\n}\n\n/** True when ANY `*@agent-connector` plugin remains in Codex's config.toml. */\nexport function anyCodexAgentConnectorPlugins(): boolean {\n const cfg = readCodexConfig();\n const plugins = cfg[\"plugins\"];\n if (!plugins || typeof plugins !== \"object\" || Array.isArray(plugins)) return false;\n return Object.keys(plugins as Record<string, unknown>).some((k) =>\n k.endsWith(`@${MARKETPLACE_NAME}`),\n );\n}\n\n/**\n * The directory config.toml records as `[marketplaces.agent-connector].source`,\n * or null when not registered / unreadable. Both a presence probe and the\n * NAME-COLLISION check: a registration pointing somewhere other than our\n * staging root belongs to the user and must never be touched.\n */\nexport function codexMarketplaceSource(name: string): string | null {\n const cfg = readCodexConfig();\n const marketplaces = cfg[\"marketplaces\"];\n if (!marketplaces || typeof marketplaces !== \"object\" || Array.isArray(marketplaces)) {\n return null;\n }\n const entry = (marketplaces as Record<string, unknown>)[name];\n if (!entry || typeof entry !== \"object\") return null;\n const source = (entry as { source?: unknown }).source;\n return typeof source === \"string\" ? source : null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// droid (Factory) host-state readers (~/.factory/settings.json, JSON, NO env)\n//\n// DOCS-ONLY: no droid binary is present on this box. The settings.json key\n// shapes below (`enabledPlugins[\"<id>@agent-connector\"] === true` +\n// `extraKnownMarketplaces[\"agent-connector\"].source`) follow the documented\n// factory plugin model — confirm the exact JSON shapes live when a droid binary\n// is available. Both readers read defensively (refuse only on positive evidence).\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Factory's settings file: ~/.factory/settings.json (NO dedicated env). */\nexport function factorySettingsPath(): string {\n return join(homedir(), \".factory\", \"settings.json\");\n}\n\n/** The settings.json key our marketplace install creates for `id` (== codex's). */\nexport function droidPluginKey(connectorId: string): string {\n return `${connectorId}@${MARKETPLACE_NAME}`;\n}\n\n/** The `enabledPlugins` map from droid's settings.json (null when unreadable). */\nfunction readDroidEnabledPlugins(): Record<string, unknown> | null {\n const parsed = readJsonFile(factorySettingsPath());\n const plugins = parsed?.enabledPlugins;\n if (plugins && typeof plugins === \"object\" && !Array.isArray(plugins)) {\n return plugins as Record<string, unknown>;\n }\n return null;\n}\n\n/**\n * True when droid's settings.json carries\n * `enabledPlugins[\"<id>@agent-connector\"] === true`. DOCS-only: the strict\n * `=== true` value shape is unverified live (a future droid may store an object).\n */\nexport function droidPluginInstalled(connectorId: string): boolean {\n const plugins = readDroidEnabledPlugins();\n if (!plugins) return false;\n return plugins[droidPluginKey(connectorId)] === true;\n}\n\n/** True when ANY `*@agent-connector` plugin remains enabled in droid's settings. */\nexport function anyDroidAgentConnectorPlugins(): boolean {\n const plugins = readDroidEnabledPlugins();\n if (!plugins) return false;\n return Object.entries(plugins).some(\n ([key, value]) => key.endsWith(`@${MARKETPLACE_NAME}`) && value === true,\n );\n}\n\n/**\n * The directory droid's settings.json records as\n * `extraKnownMarketplaces[\"<name>\"].source`, or null when not registered /\n * unreadable. Both a presence probe and the NAME-COLLISION check (a registration\n * pointing somewhere other than our staging root belongs to the user). DOCS-only:\n * the local-path `source` field shape is undocumented, so this reads defensively\n * — accepts either a bare string source or an object carrying a `path`/`source`\n * string, and NORMALIZES to a single string for the samePath compare.\n */\nexport function droidMarketplaceSource(name: string): string | null {\n const parsed = readJsonFile(factorySettingsPath());\n const known = parsed?.extraKnownMarketplaces;\n if (!known || typeof known !== \"object\" || Array.isArray(known)) return null;\n const entry = (known as Record<string, unknown>)[name];\n if (entry == null) return null;\n if (typeof entry === \"string\") return entry;\n if (typeof entry === \"object\" && !Array.isArray(entry)) {\n const e = entry as { source?: unknown; path?: unknown };\n if (typeof e.source === \"string\") return e.source;\n if (typeof e.path === \"string\") return e.path;\n }\n return null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// agy (Antigravity) host-state readers (~/.gemini/config/plugins, NO env)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** agy's config dir: ~/.gemini/config (NO dedicated env — HOME isolation). */\nexport function agyConfigDir(): string {\n return join(homedir(), \".gemini\", \"config\");\n}\n\n/** agy's plugins dir: ~/.gemini/config/plugins. */\nexport function agyPluginsDir(): string {\n return join(agyConfigDir(), \"plugins\");\n}\n\n/**\n * Candidate `import_manifest.json` locations. agy 1.0.7 records the manifest at\n * `<config>/plugins/import_manifest.json` on POSIX but at\n * `<config>/import_manifest.json` on Windows (live-confirmed on the my-window\n * box) — both are probed, then the copied plugin dir is the final fallback.\n */\nexport function agyImportManifestPaths(): string[] {\n return [\n join(agyConfigDir(), \"import_manifest.json\"), // win32 location\n join(agyPluginsDir(), \"import_manifest.json\"), // posix location\n ];\n}\n\n/** @deprecated single-location accessor — prefer {@link agyImportManifestPaths}. */\nexport function agyImportManifestPath(): string {\n return join(agyPluginsDir(), \"import_manifest.json\");\n}\n\n/**\n * True when `id` is installed per agy's import manifest (`imports[].name`),\n * checking BOTH the win32 and posix manifest locations, with a fallback probe of\n * the copied plugin dir's plugin.json. agy's uninstall removes the plugin dir\n * and clears the manifest entry (live-verified on Windows: install → dir +\n * manifest present, uninstall → both gone, re-install idempotent).\n */\nexport function agyPluginInstalled(connectorId: string): boolean {\n for (const manifest of agyImportManifestPaths()) {\n try {\n const parsed = JSON.parse(readFileSync(manifest, \"utf8\")) as {\n imports?: unknown;\n };\n const imports = parsed.imports;\n if (\n Array.isArray(imports) &&\n imports.some(\n (e) =>\n e != null &&\n typeof e === \"object\" &&\n (e as { name?: unknown }).name === connectorId,\n )\n ) {\n return true;\n }\n } catch {\n /* absent / unreadable manifest → try the next candidate */\n }\n }\n return existsSync(join(agyPluginsDir(), connectorId, \"plugin.json\"));\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Gemini CLI host-state readers (~/.gemini/extensions, NO env — HOME isolation)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Gemini CLI's config dir: ~/.gemini (NO dedicated config-dir env — isolation is\n * via HOME, live-confirmed on gemini 0.36.0). DISTINCT from agy's ~/.gemini/config\n * tree: gemini extensions land under ~/.gemini/extensions/<id>/.\n */\nexport function geminiConfigDir(): string {\n return join(homedir(), \".gemini\");\n}\n\n/**\n * True when `id` is installed per Gemini's own extension store: the marker file\n * ~/.gemini/extensions/<id>/gemini-extension.json exists. Gemini exits 0 even on\n * a logical failure, so this fs probe — not the exit code — is the source of\n * truth (the driver re-probes after every spawn).\n */\nexport function geminiExtensionInstalled(connectorId: string): boolean {\n return existsSync(\n join(geminiConfigDir(), \"extensions\", connectorId, \"gemini-extension.json\"),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Qwen Code host-state readers (~/.qwen/extensions, NO env — HOME isolation)\n//\n// DOCS-ONLY: no qwen binary is present on this box. qwen-code is a gemini-cli\n// fork, so the extension store SHAPE (~/.qwen/extensions/<id>/qwen-extension.json)\n// mirrors gemini's — confirm the marker filename live when a qwen binary lands.\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Qwen Code's config dir: ~/.qwen (NO dedicated config-dir env — isolation is\n * via HOME, like its gemini-cli parent). DOCS-only: confirm live when a qwen\n * binary is available. DISTINCT from ~/.gemini (the gemini-cli store).\n */\nexport function qwenConfigDir(): string {\n return join(homedir(), \".qwen\");\n}\n\n/**\n * True when `id` is installed per Qwen's own extension store: the marker file\n * ~/.qwen/extensions/<id>/qwen-extension.json exists. Mirrors gemini's fs probe\n * (qwen is a gemini fork); the driver re-probes after every spawn. DOCS-only:\n * the marker filename (`qwen-extension.json`) is unverified live.\n */\nexport function qwenExtensionInstalled(connectorId: string): boolean {\n return existsSync(\n join(qwenConfigDir(), \"extensions\", connectorId, \"qwen-extension.json\"),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// npm-local host-state readers (opencode / kilo / kilo-cli — XDG_CONFIG_HOME)\n//\n// These hosts have NO uninstall verb; installs are recorded as `file://<dir>`\n// entries in a `plugin` array inside an opencode-style config file, which the\n// driver EDITS to remove. This module is a spawn/shared-free LEAF, so a tiny\n// posix-resolve path comparison is inlined here rather than importing\n// shared.ts's samePath (and samePath does not strip the `file://` scheme).\n// ─────────────────────────────────────────────────────────────────────────\n\n/** $XDG_CONFIG_HOME when set & non-empty, else ~/.config (matches the adapters). */\nfunction xdgConfigHome(): string {\n const xdg = process.env.XDG_CONFIG_HOME;\n return xdg && xdg.trim() !== \"\" ? resolve(xdg) : join(homedir(), \".config\");\n}\n\n/** opencode's config dir: $XDG_CONFIG_HOME/opencode (or ~/.config/opencode). */\nexport function opencodeConfigDir(): string {\n return join(xdgConfigHome(), \"opencode\");\n}\n\n/** kilo / kilo-cli's config dir: $XDG_CONFIG_HOME/kilo (or ~/.config/kilo). */\nexport function kiloConfigDir(): string {\n return join(xdgConfigHome(), \"kilo\");\n}\n\n/**\n * Candidate config filenames per npm-local host, most-preferred first. opencode\n * accepts opencode.jsonc / opencode.json / config.json (JSONC — comments\n * tolerated); kilo / kilo-cli use opencode.json (plain JSON).\n */\nfunction npmConfigCandidates(platform: PlatformId): string[] {\n return platform === \"opencode\"\n ? [\"opencode.jsonc\", \"opencode.json\", \"config.json\"]\n : [\"opencode.json\"];\n}\n\n/** The config DIR for an npm-local host (opencode vs kilo/kilo-cli). */\nfunction npmConfigDir(platform: PlatformId): string {\n return platform === \"opencode\" ? opencodeConfigDir() : kiloConfigDir();\n}\n\n/**\n * The config FILE path for `platform`: the first existing candidate, else the\n * most-preferred candidate (the path a fresh install would write). Never throws.\n */\nexport function npmConfigFilePath(platform: PlatformId): string {\n const dir = npmConfigDir(platform);\n const candidates = npmConfigCandidates(platform);\n for (const name of candidates) {\n if (existsSync(join(dir, name))) return join(dir, name);\n }\n return join(dir, candidates[0]!);\n}\n\n/** Strip a single leading `file://` (and `file://localhost/`) scheme prefix. */\nexport function stripFileScheme(entry: string): string {\n let s = entry;\n if (s.startsWith(\"file://localhost/\")) s = s.slice(\"file://localhost\".length);\n else if (s.startsWith(\"file://\")) s = s.slice(\"file://\".length);\n return s;\n}\n\n/**\n * Strip `//` line and `/* */` block comments from JSONC, STRING-AWARE: a `//`\n * or `/*` inside a JSON string literal (e.g. the `file://` in a plugin entry) is\n * left intact. Scans char-by-char tracking string + escape state — a naive\n * regex would corrupt `\"file:///dir\"` by eating from the embedded `//`.\n */\nfunction stripJsoncComments(text: string): string {\n let out = \"\";\n let inString = false;\n let inLine = false;\n let inBlock = false;\n for (let i = 0; i < text.length; i++) {\n const c = text[i]!;\n const next = i + 1 < text.length ? text[i + 1]! : \"\";\n if (inLine) {\n if (c === \"\\n\") {\n inLine = false;\n out += c;\n }\n continue;\n }\n if (inBlock) {\n if (c === \"*\" && next === \"/\") {\n inBlock = false;\n i++;\n }\n continue;\n }\n if (inString) {\n out += c;\n if (c === \"\\\\\") {\n // Copy the escaped char verbatim (covers \\\" and \\\\).\n if (next !== \"\") {\n out += next;\n i++;\n }\n } else if (c === '\"') {\n inString = false;\n }\n continue;\n }\n if (c === '\"') {\n inString = true;\n out += c;\n continue;\n }\n if (c === \"/\" && next === \"/\") {\n inLine = true;\n i++;\n continue;\n }\n if (c === \"/\" && next === \"*\") {\n inBlock = true;\n i++;\n continue;\n }\n out += c;\n }\n return out;\n}\n\n/** Tolerant JSONC → object: strip // line and block comments, then JSON.parse. */\nfunction parseJsonc(text: string): Record<string, unknown> | null {\n // A parse failure degrades to null (the \"refuse only on positive evidence\"\n // convention) rather than throwing.\n try {\n const parsed = JSON.parse(stripJsoncComments(text)) as unknown;\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n /* unreadable / not JSONC → null */\n }\n return null;\n}\n\n/** Read + parse an npm-local host's config file ({}-shaped or null). Never throws. */\nexport function readNpmConfig(platform: PlatformId): Record<string, unknown> | null {\n const file = npmConfigFilePath(platform);\n try {\n return parseJsonc(readFileSync(file, \"utf8\"));\n } catch {\n return null;\n }\n}\n\n/**\n * Convert an npm `plugin`-array entry to a native absolute path. opencode/kilo\n * record a proper `file://` URL — on Windows that is `file:///C:/Users/…` (drive\n * letter + forward slashes), which a textual `file://`-strip leaves as the broken\n * `/C:/Users/…`. `fileURLToPath` does the correct cross-platform decode\n * (`file:///C:/x`→`C:\\x`, `file:///tmp/x`→`/tmp/x`). Bare-path entries (also\n * accepted by the hosts) and any malformed URL fall back to a textual strip.\n */\nfunction npmEntryToPath(entry: string): string {\n if (/^file:\\/\\//i.test(entry)) {\n try {\n return fileURLToPath(entry);\n } catch {\n /* malformed file URL (e.g. backslashes) → manual strip below */\n }\n }\n return stripFileScheme(entry);\n}\n\n/**\n * Path equality for the npm probe. Resolve symlinks with `realpathSync.native`\n * when the path exists (catches macOS `/var/folders`→`/private/var/folders` and\n * win32 8.3 short paths — the host records the realpath, we hold the symlink),\n * else lexical `resolve()`; case-fold on win32. Inlined here (leaf module)\n * rather than importing shared.ts's samePath, which pulls in the spawn layer.\n */\nfunction npmPathEquals(a: string, b: string): boolean {\n const norm = (p: string): string => {\n let s: string;\n try {\n s = realpathSync.native(p);\n } catch {\n s = resolve(p);\n }\n return process.platform === \"win32\" ? s.toLowerCase() : s;\n };\n return norm(a) === norm(b);\n}\n\n/**\n * The `plugin`-array ENTRY (the raw string, including any `file://`) whose value\n * — after stripping a leading `file://` — path-equals `id`'s staged bundle dir,\n * or null when no entry references it. Used both as the install probe and to\n * locate the exact entry the uninstall edit must drop.\n */\nexport function npmPluginArrayEntry(\n platform: PlatformId,\n connectorId: string,\n): string | null {\n const cfg = readNpmConfig(platform);\n if (!cfg) return null;\n const arr = cfg[\"plugin\"];\n if (!Array.isArray(arr)) return null;\n const target = join(npmStagingRoot(), connectorId);\n for (const raw of arr) {\n if (typeof raw !== \"string\") continue;\n if (npmPathEquals(npmEntryToPath(raw), target)) return raw;\n }\n return null;\n}\n\n/** True when `platform`'s config `plugin` array references `id`'s staged bundle. */\nexport function npmPluginInstalled(platform: PlatformId, connectorId: string): boolean {\n return npmPluginArrayEntry(platform, connectorId) != null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Cross-host marketplace-install EVIDENCE (the double-install guard's probe)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Read-only evidence that `connectorId` is marketplace/plugin-installed on\n * `platform`: the state record first, then a cheap fs probe of the host's own\n * plugin state (catches manual installs from `package`'s printed instructions).\n * Returns a human-readable description of the evidence, or null when none.\n */\nexport function marketplaceEvidence(\n connectorId: string,\n platform: PlatformId,\n): string | null {\n if (readMarketplaceInstalls(connectorId)[platform]) {\n return \"recorded in marketplace-installs.json\";\n }\n switch (platform) {\n case \"claude-code\":\n if (claudePluginInstalled(connectorId)) {\n return `${claudePluginKey(connectorId)} listed in Claude's installed_plugins.json`;\n }\n return null;\n case \"gemini-cli\":\n return geminiExtensionInstalled(connectorId)\n ? `extension ${connectorId} listed in Gemini's extensions store`\n : null;\n case \"qwen-code\":\n return qwenExtensionInstalled(connectorId)\n ? `extension ${connectorId} listed in Qwen's extensions store`\n : null;\n case \"droid\":\n return droidPluginInstalled(connectorId)\n ? `${droidPluginKey(connectorId)} enabled in droid's settings.json`\n : null;\n case \"antigravity\":\n case \"antigravity-cli\": {\n const dir = join(homedir(), \".gemini\", \"config\", \"plugins\", connectorId);\n return existsSync(dir) ? `plugin dir ${dir} exists` : null;\n }\n case \"opencode\":\n case \"kilo\":\n case \"kilo-cli\":\n return npmPluginInstalled(platform, connectorId)\n ? `${connectorId} listed in ${platform}'s config plugin array`\n : null;\n case \"codex\": {\n const codexHome =\n process.env.CODEX_HOME && process.env.CODEX_HOME.trim() !== \"\"\n ? resolve(process.env.CODEX_HOME)\n : join(homedir(), \".codex\");\n const dir = join(codexHome, \"plugins\", \"cache\", MARKETPLACE_NAME, connectorId);\n return existsSync(dir) ? `codex plugin cache ${dir} exists` : null;\n }\n default:\n return null;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Content hashing — staged-bundle drift detection\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Deterministic sha256 over every file under `dir` (sorted relative paths +\n * bytes). Empty string when the dir is missing/unreadable. Used to record the\n * staged bundle's content at install time and detect drift later (claude caches\n * a versioned COPY, so an edited connector goes silently stale without this).\n */\nexport function hashDirectory(dir: string): string {\n if (!existsSync(dir)) return \"\";\n const files: string[] = [];\n const walk = (d: string): void => {\n let entries: string[];\n try {\n entries = readdirSync(d);\n } catch {\n return;\n }\n for (const name of entries.sort()) {\n const full = join(d, name);\n let st;\n try {\n st = statSync(full);\n } catch {\n continue;\n }\n if (st.isDirectory()) walk(full);\n else if (st.isFile()) files.push(full);\n }\n };\n walk(dir);\n const hash = createHash(\"sha256\");\n for (const file of files.sort()) {\n hash.update(relative(dir, file).split(\"\\\\\").join(\"/\"));\n hash.update(\"\\0\");\n try {\n hash.update(readFileSync(file));\n } catch {\n /* unreadable file contributes only its path */\n }\n hash.update(\"\\0\");\n }\n return hash.digest(\"hex\");\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,SAAS,MAAM,UAAU,eAAe;AACjD,SAAS,qBAAqB;AAE9B,OAAO,UAAU;AAWV,IAAM,mBAAmB;AA0BzB,SAAS,wBAAwB,aAA6B;AACnE,SAAO,KAAK,aAAa,WAAW,GAAG,2BAA2B;AACpE;AAGO,SAAS,wBAAwB,aAA0C;AAChF,MAAI;AACF,UAAM,MAAM,aAAa,wBAAwB,WAAW,GAAG,MAAM;AACrE,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAClE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,CAAC;AACV;AAGO,SAAS,yBACd,aACA,UACM;AACN,QAAM,OAAO,wBAAwB,WAAW;AAChD,MAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,GAAG;AACtC,WAAO,MAAM,EAAE,OAAO,KAAK,CAAC;AAC5B;AAAA,EACF;AACA,YAAU,QAAQ,IAAI,CAAC;AACvB,gBAAc,MAAM,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AACtE;AAGO,SAAS,+BAA+B,aAA8B;AAC3E,SAAO,OAAO,KAAK,wBAAwB,WAAW,CAAC,EAAE,SAAS;AACpE;AAQO,SAAS,4BAA4B,oBAAsC;AAChF,QAAM,MAAM,cAAc;AAC1B,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAC7B,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,QAAQ;AAAA,IACb,CAAC,OAAO,OAAO,sBAAsB,+BAA+B,EAAE;AAAA,EACxE;AACF;AAUO,SAAS,kBAA0B;AACxC,SAAO,KAAK,SAAS,GAAG,aAAa;AACvC;AAGO,SAAS,oBAA4B;AAC1C,SAAO,KAAK,gBAAgB,GAAG,QAAQ;AACzC;AAGO,SAAS,mBAA2B;AACzC,SAAO,KAAK,gBAAgB,GAAG,OAAO;AACxC;AAGO,SAAS,iBAAyB;AACvC,SAAO,KAAK,gBAAgB,GAAG,KAAK;AACtC;AAGO,SAAS,oBAA4B;AAC1C,SAAO,KAAK,gBAAgB,GAAG,QAAQ;AACzC;AAGO,SAAS,kBAA0B;AACxC,SAAO,KAAK,gBAAgB,GAAG,MAAM;AACvC;AAGO,SAAS,mBAA2B;AACzC,SAAO,KAAK,gBAAgB,GAAG,OAAO;AACxC;AAQO,SAAS,iBAAyB;AACvC,SAAO,KAAK,gBAAgB,GAAG,KAAK;AACtC;AAOO,SAAS,kBAA0B;AACxC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAY,SAAS,KAAK,MAAM,GAAI,QAAO,QAAQ,QAAQ;AAC/D,SAAO,KAAK,QAAQ,GAAG,SAAS;AAClC;AAGO,SAAS,gBAAgB,aAA6B;AAC3D,SAAO,GAAG,WAAW,IAAI,gBAAgB;AAC3C;AAEA,SAAS,aAAa,MAA8C;AAClE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AACpD,QAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAClE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGO,SAAS,6BAA6D;AAC3E,QAAM,OAAO,KAAK,gBAAgB,GAAG,WAAW,wBAAwB;AACxE,QAAM,SAAS,aAAa,IAAI;AAChC,QAAM,UAAU,QAAQ;AACxB,MAAI,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG;AACrE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,sBAAsB,aAA8B;AAClE,QAAM,UAAU,2BAA2B;AAC3C,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,QAAQ,gBAAgB,WAAW,CAAC;AAClD,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,IAAI,SAAS;AAC5D;AAGO,SAAS,iCAA0C;AACxD,QAAM,UAAU,2BAA2B;AAC3C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,OAAO,QAAQ,OAAO,EAAE;AAAA,IAC7B,CAAC,CAAC,KAAK,KAAK,MACV,IAAI,SAAS,IAAI,gBAAgB,EAAE,MAClC,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,IAAI,SAAS;AAAA,EACxD;AACF;AAQO,SAAS,2BAA2B,MAA6B;AACtE,QAAM,OAAO,KAAK,gBAAgB,GAAG,WAAW,yBAAyB;AACzE,QAAM,SAAS,aAAa,IAAI;AAChC,QAAM,QAAQ,SAAS,IAAI;AAC3B,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,oBAAoB,SAAU,QAAO,EAAE;AACpD,QAAM,aAAa,EAAE,QAAQ;AAC7B,SAAO,OAAO,eAAe,WAAW,aAAa;AACvD;AAOO,SAAS,kBAA0B;AACxC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,OAAO,IAAI,KAAK,MAAM,IAAI;AAC5B,QAAI,IAAI,WAAW,GAAG,EAAG,QAAO,KAAK,QAAQ,GAAG,IAAI,QAAQ,YAAY,EAAE,CAAC;AAC3E,WAAO,QAAQ,GAAG;AAAA,EACpB;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACjC;AAGO,SAAS,eAAe,aAA6B;AAC1D,SAAO,GAAG,WAAW,IAAI,gBAAgB;AAC3C;AAGA,SAAS,kBAA2C;AAClD,QAAM,OAAO,KAAK,gBAAgB,GAAG,aAAa;AAClD,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAOO,SAAS,qBAAqB,aAA8B;AACjE,QAAM,MAAM,gBAAgB;AAC5B,QAAM,UAAU,IAAI,SAAS;AAC7B,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,OAAO,EAAG,QAAO;AAC9E,SAAO,eAAe,WAAW,KAAM;AACzC;AAGO,SAAS,gCAAyC;AACvD,QAAM,MAAM,gBAAgB;AAC5B,QAAM,UAAU,IAAI,SAAS;AAC7B,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,OAAO,EAAG,QAAO;AAC9E,SAAO,OAAO,KAAK,OAAkC,EAAE;AAAA,IAAK,CAAC,MAC3D,EAAE,SAAS,IAAI,gBAAgB,EAAE;AAAA,EACnC;AACF;AAQO,SAAS,uBAAuB,MAA6B;AAClE,QAAM,MAAM,gBAAgB;AAC5B,QAAM,eAAe,IAAI,cAAc;AACvC,MAAI,CAAC,gBAAgB,OAAO,iBAAiB,YAAY,MAAM,QAAQ,YAAY,GAAG;AACpF,WAAO;AAAA,EACT;AACA,QAAM,QAAS,aAAyC,IAAI;AAC5D,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,SAAU,MAA+B;AAC/C,SAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;AAaO,SAAS,sBAA8B;AAC5C,SAAO,KAAK,QAAQ,GAAG,YAAY,eAAe;AACpD;AAGO,SAAS,eAAe,aAA6B;AAC1D,SAAO,GAAG,WAAW,IAAI,gBAAgB;AAC3C;AAGA,SAAS,0BAA0D;AACjE,QAAM,SAAS,aAAa,oBAAoB,CAAC;AACjD,QAAM,UAAU,QAAQ;AACxB,MAAI,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG;AACrE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,qBAAqB,aAA8B;AACjE,QAAM,UAAU,wBAAwB;AACxC,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,eAAe,WAAW,CAAC,MAAM;AAClD;AAGO,SAAS,gCAAyC;AACvD,QAAM,UAAU,wBAAwB;AACxC,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,OAAO,QAAQ,OAAO,EAAE;AAAA,IAC7B,CAAC,CAAC,KAAK,KAAK,MAAM,IAAI,SAAS,IAAI,gBAAgB,EAAE,KAAK,UAAU;AAAA,EACtE;AACF;AAWO,SAAS,uBAAuB,MAA6B;AAClE,QAAM,SAAS,aAAa,oBAAoB,CAAC;AACjD,QAAM,QAAQ,QAAQ;AACtB,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AACxE,QAAM,QAAS,MAAkC,IAAI;AACrD,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACtD,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,WAAW,SAAU,QAAO,EAAE;AAC3C,QAAI,OAAO,EAAE,SAAS,SAAU,QAAO,EAAE;AAAA,EAC3C;AACA,SAAO;AACT;AAOO,SAAS,eAAuB;AACrC,SAAO,KAAK,QAAQ,GAAG,WAAW,QAAQ;AAC5C;AAGO,SAAS,gBAAwB;AACtC,SAAO,KAAK,aAAa,GAAG,SAAS;AACvC;AAQO,SAAS,yBAAmC;AACjD,SAAO;AAAA,IACL,KAAK,aAAa,GAAG,sBAAsB;AAAA;AAAA,IAC3C,KAAK,cAAc,GAAG,sBAAsB;AAAA;AAAA,EAC9C;AACF;AAcO,SAAS,mBAAmB,aAA8B;AAC/D,aAAW,YAAY,uBAAuB,GAAG;AAC/C,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAGxD,YAAM,UAAU,OAAO;AACvB,UACE,MAAM,QAAQ,OAAO,KACrB,QAAQ;AAAA,QACN,CAAC,MACC,KAAK,QACL,OAAO,MAAM,YACZ,EAAyB,SAAS;AAAA,MACvC,GACA;AACA,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,WAAW,KAAK,cAAc,GAAG,aAAa,aAAa,CAAC;AACrE;AAWO,SAAS,kBAA0B;AACxC,SAAO,KAAK,QAAQ,GAAG,SAAS;AAClC;AAQO,SAAS,yBAAyB,aAA8B;AACrE,SAAO;AAAA,IACL,KAAK,gBAAgB,GAAG,cAAc,aAAa,uBAAuB;AAAA,EAC5E;AACF;AAeO,SAAS,gBAAwB;AACtC,SAAO,KAAK,QAAQ,GAAG,OAAO;AAChC;AAQO,SAAS,uBAAuB,aAA8B;AACnE,SAAO;AAAA,IACL,KAAK,cAAc,GAAG,cAAc,aAAa,qBAAqB;AAAA,EACxE;AACF;AAaA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,QAAQ,IAAI;AACxB,SAAO,OAAO,IAAI,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,KAAK,QAAQ,GAAG,SAAS;AAC5E;AAGO,SAAS,oBAA4B;AAC1C,SAAO,KAAK,cAAc,GAAG,UAAU;AACzC;AAGO,SAAS,gBAAwB;AACtC,SAAO,KAAK,cAAc,GAAG,MAAM;AACrC;AAOA,SAAS,oBAAoB,UAAgC;AAC3D,SAAO,aAAa,aAChB,CAAC,kBAAkB,iBAAiB,aAAa,IACjD,CAAC,eAAe;AACtB;AAGA,SAAS,aAAa,UAA8B;AAClD,SAAO,aAAa,aAAa,kBAAkB,IAAI,cAAc;AACvE;AAMO,SAAS,kBAAkB,UAA8B;AAC9D,QAAM,MAAM,aAAa,QAAQ;AACjC,QAAM,aAAa,oBAAoB,QAAQ;AAC/C,aAAW,QAAQ,YAAY;AAC7B,QAAI,WAAW,KAAK,KAAK,IAAI,CAAC,EAAG,QAAO,KAAK,KAAK,IAAI;AAAA,EACxD;AACA,SAAO,KAAK,KAAK,WAAW,CAAC,CAAE;AACjC;AAGO,SAAS,gBAAgB,OAAuB;AACrD,MAAI,IAAI;AACR,MAAI,EAAE,WAAW,mBAAmB,EAAG,KAAI,EAAE,MAAM,mBAAmB,MAAM;AAAA,WACnE,EAAE,WAAW,SAAS,EAAG,KAAI,EAAE,MAAM,UAAU,MAAM;AAC9D,SAAO;AACT;AAQA,SAAS,mBAAmB,MAAsB;AAChD,MAAI,MAAM;AACV,MAAI,WAAW;AACf,MAAI,SAAS;AACb,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,UAAM,OAAO,IAAI,IAAI,KAAK,SAAS,KAAK,IAAI,CAAC,IAAK;AAClD,QAAI,QAAQ;AACV,UAAI,MAAM,MAAM;AACd,iBAAS;AACT,eAAO;AAAA,MACT;AACA;AAAA,IACF;AACA,QAAI,SAAS;AACX,UAAI,MAAM,OAAO,SAAS,KAAK;AAC7B,kBAAU;AACV;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,UAAU;AACZ,aAAO;AACP,UAAI,MAAM,MAAM;AAEd,YAAI,SAAS,IAAI;AACf,iBAAO;AACP;AAAA,QACF;AAAA,MACF,WAAW,MAAM,KAAK;AACpB,mBAAW;AAAA,MACb;AACA;AAAA,IACF;AACA,QAAI,MAAM,KAAK;AACb,iBAAW;AACX,aAAO;AACP;AAAA,IACF;AACA,QAAI,MAAM,OAAO,SAAS,KAAK;AAC7B,eAAS;AACT;AACA;AAAA,IACF;AACA,QAAI,MAAM,OAAO,SAAS,KAAK;AAC7B,gBAAU;AACV;AACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,WAAW,MAA8C;AAGhE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,mBAAmB,IAAI,CAAC;AAClD,QAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAClE,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGO,SAAS,cAAc,UAAsD;AAClF,QAAM,OAAO,kBAAkB,QAAQ;AACvC,MAAI;AACF,WAAO,WAAW,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,SAAS,eAAe,OAAuB;AAC7C,MAAI,cAAc,KAAK,KAAK,GAAG;AAC7B,QAAI;AACF,aAAO,cAAc,KAAK;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,gBAAgB,KAAK;AAC9B;AASA,SAAS,cAAc,GAAW,GAAoB;AACpD,QAAM,OAAO,CAAC,MAAsB;AAClC,QAAI;AACJ,QAAI;AACF,UAAI,aAAa,OAAO,CAAC;AAAA,IAC3B,QAAQ;AACN,UAAI,QAAQ,CAAC;AAAA,IACf;AACA,WAAO,QAAQ,aAAa,UAAU,EAAE,YAAY,IAAI;AAAA,EAC1D;AACA,SAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAC3B;AAQO,SAAS,oBACd,UACA,aACe;AACf,QAAM,MAAM,cAAc,QAAQ;AAClC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,QAAQ;AACxB,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,QAAM,SAAS,KAAK,eAAe,GAAG,WAAW;AACjD,aAAW,OAAO,KAAK;AACrB,QAAI,OAAO,QAAQ,SAAU;AAC7B,QAAI,cAAc,eAAe,GAAG,GAAG,MAAM,EAAG,QAAO;AAAA,EACzD;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,UAAsB,aAA8B;AACrF,SAAO,oBAAoB,UAAU,WAAW,KAAK;AACvD;AAYO,SAAS,oBACd,aACA,UACe;AACf,MAAI,wBAAwB,WAAW,EAAE,QAAQ,GAAG;AAClD,WAAO;AAAA,EACT;AACA,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,UAAI,sBAAsB,WAAW,GAAG;AACtC,eAAO,GAAG,gBAAgB,WAAW,CAAC;AAAA,MACxC;AACA,aAAO;AAAA,IACT,KAAK;AACH,aAAO,yBAAyB,WAAW,IACvC,aAAa,WAAW,yCACxB;AAAA,IACN,KAAK;AACH,aAAO,uBAAuB,WAAW,IACrC,aAAa,WAAW,uCACxB;AAAA,IACN,KAAK;AACH,aAAO,qBAAqB,WAAW,IACnC,GAAG,eAAe,WAAW,CAAC,sCAC9B;AAAA,IACN,KAAK;AAAA,IACL,KAAK,mBAAmB;AACtB,YAAM,MAAM,KAAK,QAAQ,GAAG,WAAW,UAAU,WAAW,WAAW;AACvE,aAAO,WAAW,GAAG,IAAI,cAAc,GAAG,YAAY;AAAA,IACxD;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,mBAAmB,UAAU,WAAW,IAC3C,GAAG,WAAW,cAAc,QAAQ,2BACpC;AAAA,IACN,KAAK,SAAS;AACZ,YAAM,YACJ,QAAQ,IAAI,cAAc,QAAQ,IAAI,WAAW,KAAK,MAAM,KACxD,QAAQ,QAAQ,IAAI,UAAU,IAC9B,KAAK,QAAQ,GAAG,QAAQ;AAC9B,YAAM,MAAM,KAAK,WAAW,WAAW,SAAS,kBAAkB,WAAW;AAC7E,aAAO,WAAW,GAAG,IAAI,sBAAsB,GAAG,YAAY;AAAA,IAChE;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAYO,SAAS,cAAc,KAAqB;AACjD,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAC7B,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,CAAC,MAAoB;AAChC,QAAI;AACJ,QAAI;AACF,gBAAU,YAAY,CAAC;AAAA,IACzB,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,QAAQ,KAAK,GAAG;AACjC,YAAM,OAAO,KAAK,GAAG,IAAI;AACzB,UAAI;AACJ,UAAI;AACF,aAAK,SAAS,IAAI;AAAA,MACpB,QAAQ;AACN;AAAA,MACF;AACA,UAAI,GAAG,YAAY,EAAG,MAAK,IAAI;AAAA,eACtB,GAAG,OAAO,EAAG,OAAM,KAAK,IAAI;AAAA,IACvC;AAAA,EACF;AACA,OAAK,GAAG;AACR,QAAM,OAAO,WAAW,QAAQ;AAChC,aAAW,QAAQ,MAAM,KAAK,GAAG;AAC/B,SAAK,OAAO,SAAS,KAAK,IAAI,EAAE,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC;AACrD,SAAK,OAAO,IAAI;AAChB,QAAI;AACF,WAAK,OAAO,aAAa,IAAI,CAAC;AAAA,IAChC,QAAQ;AAAA,IAER;AACA,SAAK,OAAO,IAAI;AAAA,EAClB;AACA,SAAO,KAAK,OAAO,KAAK;AAC1B;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/telemetry-accessor.ts","../src/telemetry/measure.ts","../src/runtime/hook-entrypoint.ts"],"sourcesContent":["/**\n * runtime/telemetry-accessor — build the handler-facing {@link TelemetryAccessor}.\n *\n * Every runtime entrypoint (hook / statusline / action) stamps `ctx.telemetry`\n * (or `evt.telemetry`) with the accessor built here so a connector handler can\n * read ITS OWN recorded usage on demand (`await ctx.telemetry?.()`), without the\n * handler reaching into the telemetry store directly.\n *\n * The accessor is LAZY (it opens the store only when called) and DEFENSIVE:\n * - AGENT_CONNECTOR_TELEMETRY=0 → resolves zeros without touching disk;\n * - any read error → resolves zeros (NEVER throws into a handler — the hook\n * runtime is fail-open, the statusline fail-safe, and even the action\n * surface must not have a usage read crash an otherwise-fine run).\n *\n * COST: each call reads the connector's telemetry file in full (the NDJSON\n * store has no since/limit bound) and linear-scans it. Cheap for normal stores,\n * but a handler on a HOT path (e.g. a status line rendered every refresh) should\n * call it sparingly and cache the result rather than invoking it per render.\n */\n\nimport type { TelemetryAccessor, TelemetryUsageSummary } from \"../core/types.js\";\nimport { openStore } from \"../telemetry/store.js\";\n\n/** The empty summary returned when telemetry is off or a read fails. */\nconst ZERO: TelemetryUsageSummary = {\n inputTokens: 0,\n outputTokens: 0,\n totalTokens: 0,\n calls: 0,\n};\n\n/**\n * Build a {@link TelemetryAccessor} bound to `connectorId`. The returned async\n * function sums every stored row's inputTokens/outputTokens for the connector\n * and counts the rows (`calls`). Honors the AGENT_CONNECTOR_TELEMETRY=0 kill\n * switch (zeros, no disk touch) and swallows any error (zeros) — it never throws.\n */\nexport function buildTelemetryAccessor(connectorId: string): TelemetryAccessor {\n return async (): Promise<TelemetryUsageSummary> => {\n if (process.env.AGENT_CONNECTOR_TELEMETRY === \"0\") return { ...ZERO };\n try {\n const store = openStore({});\n try {\n let inputTokens = 0;\n let outputTokens = 0;\n let calls = 0;\n // COERCE each addend to a finite number: query() tolerates malformed\n // LINES but does not validate field shapes, so a legacy/hand-edited row\n // with a missing (→ NaN) or string (→ concat) token field must not\n // poison the summary's numeric contract. A non-finite token reads as 0.\n for (const row of store.query({ connectorId })) {\n inputTokens += Number.isFinite(row.inputTokens) ? row.inputTokens : 0;\n outputTokens += Number.isFinite(row.outputTokens) ? row.outputTokens : 0;\n calls += 1;\n }\n return {\n inputTokens,\n outputTokens,\n totalTokens: inputTokens + outputTokens,\n calls,\n };\n } finally {\n store.close();\n }\n } catch {\n // Never throw into a handler — a usage read failure resolves to zeros.\n return { ...ZERO };\n }\n };\n}\n","/**\n * telemetry/measure — pure measurement helpers over a single MCP tool call.\n *\n * The framework tokenizes its OWN bytes: the tool-call arguments going in, the\n * tool result coming out, and the fixed tool-definition schemas. These helpers\n * turn those into {@link TokenCount}s using the default {@link Tokenizer}.\n *\n * Honesty rules:\n * • Binary modalities (image/audio/resource blobs) are NOT tokenized — base64\n * would massively over-count. Each gets a flat per-modality estimate, and\n * because that estimate is itself an approximation the combined confidence\n * is pulled down to at least \"tokenizer-approx\".\n * • When combining several counts, the reported confidence is the WORST\n * (least-confident) of the inputs — never report \"exact\" for a mixed bag.\n *\n * Everything here is pure: no IO, no mutation of inputs.\n */\n\nimport { getTokenizer } from \"./tokenizer.js\";\nimport { worstConfidence } from \"./types.js\";\nimport type {\n ConfidenceSource,\n ModelFamily,\n TokenCount,\n Tokenizer,\n} from \"./types.js\";\n\n/** Per-call measurement: tokens in, tokens out, and combined confidence. */\nexport interface ToolCallMeasurement {\n inputTokens: number;\n outputTokens: number;\n source: ConfidenceSource;\n}\n\n/**\n * Flat token estimates for non-text content blocks. These mirror common host\n * accounting (an image tile is roughly ~85 tokens at low detail); they are\n * deliberately coarse — we never tokenize the underlying base64 payload.\n */\nconst MODALITY_ESTIMATE: Record<string, number> = {\n image: 85,\n audio: 85,\n resource: 85,\n};\n/** Fallback estimate for an unknown non-text block type. */\nconst UNKNOWN_BLOCK_ESTIMATE = 85;\n\n// Confidence ordering + the worst-of comparison live in ./types (the single\n// source of truth, imported as `worstConfidence` above). Re-exported here so\n// callers that already import `worstConfidence` from this module keep working\n// as a new ConfidenceSource value (e.g. tokenizer-calibrated) slots into the\n// shared ordering.\nexport { worstConfidence };\n\n/** Reduce a list of sources to the single worst (least-confident) one. */\nfunction combineSources(sources: readonly ConfidenceSource[]): ConfidenceSource {\n // Default to the most-confident floor so an empty list combines to the only\n // other source it is paired with; in practice callers always pass ≥1 source.\n let worst: ConfidenceSource = \"host-native\";\n for (const s of sources) worst = worstConfidence(worst, s);\n return worst;\n}\n\n// ── Structural shapes we read from a tool result (kept local + narrow) ───────\n\n/** A text content block: `{ type: \"text\", text: \"...\" }`. */\ninterface TextBlock {\n type: \"text\";\n text?: unknown;\n}\n/** Any content block; only text blocks expose a `text` field we tokenize. */\ninterface ContentBlock {\n type?: unknown;\n text?: unknown;\n}\n/** The portion of an MCP tool result we measure. */\ninterface ToolResultLike {\n content?: unknown;\n structuredContent?: unknown;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction isTextBlock(block: ContentBlock): block is TextBlock {\n return block.type === \"text\";\n}\n\n/**\n * Measure one tool call.\n *\n * Input = canonical JSON of `args`.\n * Output = tokens over the TEXTUAL parts of `result`:\n * • If `result.content` is an array of blocks, text blocks are tokenized via\n * their `text` field and non-text blocks add a flat per-modality estimate\n * (no base64 tokenization).\n * • `result.structuredContent`, when present, is tokenized as canonical JSON.\n * • Anything else (string / arbitrary object) is tokenized as canonical JSON.\n *\n * The reported `source` is the worst of the input and output sources.\n */\nexport function measureToolCall(\n args: unknown,\n result: unknown,\n family: ModelFamily,\n tok: Tokenizer = getTokenizer(),\n): ToolCallMeasurement {\n const input = tok.countValue(args, family);\n const output = measureResult(result, family, tok);\n return {\n inputTokens: input.tokens,\n outputTokens: output.tokens,\n source: worstConfidence(input.source, output.source),\n };\n}\n\n/** Tokenize the textual + structured parts of a tool result. */\nfunction measureResult(\n result: unknown,\n family: ModelFamily,\n tok: Tokenizer,\n): TokenCount {\n if (!isObject(result)) {\n // Plain string or scalar result — count its canonical form directly.\n return tok.countValue(result, family);\n }\n\n const res = result as ToolResultLike;\n const sources: ConfidenceSource[] = [];\n let tokens = 0;\n\n if (Array.isArray(res.content)) {\n for (const raw of res.content) {\n if (!isObject(raw)) continue;\n const block = raw as ContentBlock;\n if (isTextBlock(block)) {\n const text = typeof block.text === \"string\" ? block.text : \"\";\n const c = tok.count(text, family);\n tokens += c.tokens;\n sources.push(c.source);\n } else {\n // image / audio / resource / unknown → flat estimate, no base64 BPE.\n const key = typeof block.type === \"string\" ? block.type : \"\";\n tokens += MODALITY_ESTIMATE[key] ?? UNKNOWN_BLOCK_ESTIMATE;\n // A flat estimate is an approximation, never \"exact\".\n sources.push(\"tokenizer-approx\");\n }\n }\n } else if (res.content !== undefined) {\n // content present but not an array (e.g. a bare string) → count it whole.\n const c = tok.countValue(res.content, family);\n tokens += c.tokens;\n sources.push(c.source);\n }\n\n if (res.structuredContent !== undefined) {\n const c = tok.countValue(res.structuredContent, family);\n tokens += c.tokens;\n sources.push(c.source);\n }\n\n // Nothing measurable found → count the whole object as a fallback so we never\n // silently report zero for a non-empty result.\n if (sources.length === 0) {\n return tok.countValue(result, family);\n }\n\n return { tokens, source: combineSources(sources) };\n}\n\n/**\n * Measure the fixed tool-definition overhead: tokenize the JSON of the entire\n * `tools/list` array once. `tools` is the raw array of tool descriptors as the\n * server advertises them (shape is host/SDK-specific, so it is treated as\n * opaque JSON here).\n */\nexport function measureToolDefs(\n tools: readonly unknown[],\n family: ModelFamily,\n tok: Tokenizer = getTokenizer(),\n): TokenCount {\n return tok.countValue(tools, family);\n}\n\n/**\n * Measure one RUNTIME hook dispatch (the new `hook` developer-axis surface).\n *\n * Input = canonical JSON of the inbound normalized event payload the handler\n * reads (the host-provided fields: toolInput / prompt / raw / etc.).\n * Output = canonical JSON of what the handler RETURNS that becomes context or a\n * decision the host consumes (additionalContext / updatedInput / reason\n * and the rest of the normalized {@link HookResponse}).\n *\n * Uses the SAME tokenizer + worst-of confidence combination as the proxy. Pure:\n * no IO, no mutation. The two values are tokenized whole (a hook payload has no\n * binary content blocks to special-case, unlike a tool result).\n */\nexport function measureHook(\n eventPayload: unknown,\n handlerReturn: unknown,\n family: ModelFamily,\n tok: Tokenizer = getTokenizer(),\n): ToolCallMeasurement {\n const input = tok.countValue(eventPayload, family);\n const output = tok.countValue(handlerReturn, family);\n return {\n inputTokens: input.tokens,\n outputTokens: output.tokens,\n source: worstConfidence(input.source, output.source),\n };\n}\n","/**\n * runtime/hook-entrypoint — the universal json-stdio hook dispatcher.\n *\n * Every json-stdio host points its hook config at the single stable home binary\n * (`<homeBin> hook <platformId> <event> --connector <id>`, built by\n * core/spawn.buildHomeBinHookCommand). The CLI reads stdin and calls\n * {@link runHook} with the parsed flags + the raw stdin string. This module:\n *\n * 1. Loads the registered connector (live handlers) and the host adapter.\n * 2. Parses the host's stdin payload into a normalized event via the adapter.\n * 3. Finds the connector's handler for the event, honoring the tool matcher.\n * 4. Runs the handler, normalizes its return, and formats the adapter's native\n * reply.\n *\n * Fail-open is the safety contract: ANY error degrades to exit 0 (\"allow\") so a\n * framework or handler bug can never wedge a host's tool call — EXCEPT when the\n * event is PreToolUse or PermissionRequest and the handler explicitly denied,\n * in which case the deny (a deliberate security decision) is preserved rather\n * than swallowed.\n */\n\nimport type {\n HookEventName,\n HookResponse,\n NativeHookDef,\n NativeHookEvent,\n PlatformId,\n ResolvedConnector,\n} from \"../core/types.js\";\nimport { log } from \"../core/logger.js\";\nimport {\n loadRegisteredConnector,\n readRegisteredMeta,\n} from \"../core/load-connector.js\";\nimport { loadAdapter, REGISTERED_PLATFORM_IDS } from \"../adapters/registry.js\";\nimport { buildTelemetryAccessor } from \"./telemetry-accessor.js\";\nimport type { NormalizedEvent } from \"../adapters/spi.js\";\nimport { projectIdentity } from \"../core/paths.js\";\nimport { measureHook } from \"../telemetry/measure.js\";\nimport { openStore, newRecordId } from \"../telemetry/store.js\";\nimport { getTokenizer, inferModelFamily } from \"../telemetry/tokenizer.js\";\n\n/** Flags + stdin the CLI hands to {@link runHook}. */\nexport interface RunHookOptions {\n /** Host platform id from the hook command (`hook <platformId> ...`). */\n platformId: string;\n /** Canonical lifecycle event name from the hook command. */\n event: HookEventName;\n /** Connector id from `--connector <id>`. */\n connectorId: string;\n /** Raw stdin payload (host-native JSON). Empty string is tolerated → `{}`. */\n stdin: string;\n}\n\n/** Process-level result the CLI translates into exit code + stdout/stderr. */\nexport interface RunHookResult {\n exitCode: number;\n stdout?: string;\n stderr?: string;\n}\n\n/** A pass-through \"allow\" reply with exit 0 and no payload. */\nconst ALLOW: RunHookResult = { exitCode: 0 };\n\n/**\n * The string a connector matcher filters on, when the event carries one:\n * tool events (PreToolUse / PostToolUse / PermissionRequest /\n * PostToolUseFailure) expose `toolName`; subagent events expose `agentType`.\n * Returns undefined when the event has neither (no filtering) — including a\n * SubagentStop arriving WITHOUT agent_type, which some hosts fail to populate;\n * filtering out such an event would silently drop a real stop, so we run the\n * handler instead (fail-open).\n */\nexport function eventMatcherSubject(evt: NormalizedEvent): string | undefined {\n const name = (evt as { toolName?: unknown }).toolName;\n if (typeof name === \"string\") return name;\n const agentType = (evt as { agentType?: unknown }).agentType;\n if (typeof agentType === \"string\") return agentType;\n return undefined;\n}\n\n/**\n * Tolerantly parse the host's stdin payload. Empty / whitespace-only input is a\n * legitimate \"no payload\" signal from some hosts and resolves to `{}`. Malformed\n * JSON also degrades to `{}` rather than throwing — fail-open is the contract.\n */\nfunction parseStdin(stdin: string): unknown {\n const trimmed = stdin.trim();\n if (trimmed === \"\") return {};\n try {\n return JSON.parse(trimmed);\n } catch {\n return {};\n }\n}\n\n/**\n * Compile the connector's matcher (a regex string) for a tool event. A missing\n * or empty matcher matches everything (returns null → no filtering). An invalid\n * regex degrades to \"match everything\" so a typo never blocks tool calls.\n */\nexport function compileMatcher(matcher: string | undefined): RegExp | null {\n if (!matcher || matcher === \"\") return null;\n try {\n return new RegExp(matcher);\n } catch {\n return null;\n }\n}\n\n/**\n * Normalize a handler return into a concrete HookResponse. A void/non-object\n * return becomes `{}` — NO decision — which every adapter formats as\n * pass-through allow (`response.decision ?? \"allow\"`). Deliberately NOT\n * `{decision:\"allow\"}`: on PermissionRequest an explicit \"allow\" is an ACTIVE\n * grant that suppresses the host's permission dialog, and a handler that\n * returns nothing must fall through to the dialog, never silently grant.\n */\nfunction normalizeResponse(value: HookResponse | void): HookResponse {\n if (value == null || typeof value !== \"object\") return {};\n return value;\n}\n\n/**\n * Resolve the host platform to stamp on a hook telemetry row. Mirrors the serve\n * `--host` plumbing: the install-target platform id is baked into the hook\n * command (`hook <platformId> <event> …`) so `opts.platformId` is authoritative,\n * but an explicit env override (AGENT_CONNECTOR_HOST) wins when it names a known\n * platform. Falls back to the event's adapter-stamped hostPlatform, then\n * \"unknown\" — never throws.\n */\nfunction resolveHookHostPlatform(\n platformId: string,\n evt: NormalizedEvent | NativeHookEvent,\n): PlatformId {\n const override = process.env.AGENT_CONNECTOR_HOST;\n if (\n override !== undefined &&\n REGISTERED_PLATFORM_IDS.has(override as PlatformId)\n ) {\n return override as PlatformId;\n }\n if (REGISTERED_PLATFORM_IDS.has(platformId as PlatformId)) {\n return platformId as PlatformId;\n }\n const fromEvent = (evt as { hostPlatform?: unknown }).hostPlatform;\n if (\n typeof fromEvent === \"string\" &&\n REGISTERED_PLATFORM_IDS.has(fromEvent as PlatformId)\n ) {\n return fromEvent as PlatformId;\n }\n return \"unknown\";\n}\n\n/**\n * Record one RUNTIME hook dispatch as a `hook` developer-axis telemetry row.\n *\n * input = the inbound event payload the handler reads (normalized event, or\n * the {@link NativeHookEvent} envelope for native passthrough hooks);\n * output = what the handler returned (a normalized {@link HookResponse}, or the\n * verbatim native reply object). Tokenized with the SAME tokenizer the\n * proxy uses. For a native hook `opts.event` is the host-native name.\n *\n * MUST be fail-open (the hook runtime is fail-open by contract): any error here\n * is swallowed so a measurement bug can NEVER break a host's hook. Honors the\n * AGENT_CONNECTOR_TELEMETRY=0 kill switch (skips entirely; the store's append is\n * already a no-op under it, but we also skip the tokenize work).\n */\nfunction recordHookTelemetry(\n opts: { platformId: string; event: string; connectorId: string },\n connector: ResolvedConnector,\n evt: NormalizedEvent | NativeHookEvent,\n response: unknown,\n): void {\n if (process.env.AGENT_CONNECTOR_TELEMETRY === \"0\") return;\n try {\n const family = inferModelFamily(\"\", connector.telemetry.modelFamilyHint);\n const measurement = measureHook(evt, response, family, getTokenizer());\n\n const sessionId =\n typeof (evt as { sessionId?: unknown }).sessionId === \"string\"\n ? ((evt as { sessionId: string }).sessionId)\n : \"\";\n const evtProjectDir = (evt as { projectDir?: unknown }).projectDir;\n const projectDir =\n typeof evtProjectDir === \"string\" && evtProjectDir !== \"\"\n ? evtProjectDir\n : process.cwd();\n const id = projectIdentity(projectDir);\n\n const store = openStore({});\n try {\n store.append({\n id: newRecordId(0),\n ts: Date.now(),\n connectorId: opts.connectorId,\n toolName: opts.event, // for a hook row the per-item name IS the event\n scope: \"hook\",\n surfaceKind: \"hook\",\n hostPlatform: resolveHookHostPlatform(opts.platformId, evt),\n sessionId,\n projectKey: id.key,\n projectDir: id.dir,\n inputTokens: measurement.inputTokens,\n outputTokens: measurement.outputTokens,\n confidenceSource: measurement.source,\n isError: false,\n });\n } finally {\n store.close();\n }\n } catch {\n // Fail-open: a telemetry error must NEVER break the hook runtime.\n }\n}\n\n/**\n * Dispatch one host hook invocation. Always resolves (never rejects): every\n * failure path returns a concrete {@link RunHookResult} so the CLI can exit\n * cleanly. See the module header for the fail-open contract.\n */\nexport async function runHook(opts: RunHookOptions): Promise<RunHookResult> {\n const { platformId, event, connectorId, stdin } = opts;\n\n try {\n const connector = await loadRegisteredConnector(connectorId);\n const adapter = await loadAdapter(platformId);\n\n // mcp-only hosts (and any adapter without runtime dispatch) cannot parse a\n // hook payload — nothing to do, allow.\n if (!adapter || !adapter.parseEvent || !adapter.formatReply) {\n return ALLOW;\n }\n\n const raw = parseStdin(stdin);\n const evt = adapter.parseEvent(event, raw);\n // The hook command carries the authoritative connector id; stamp it on the\n // event so handlers see the connector they were dispatched for.\n evt.connectorId = connectorId;\n // Unify the hook event with HostCtx: the adapter's parseEvent has no\n // capabilities/scope/telemetry at parse time (they are runtime-only), so the\n // runtime ALWAYS backfills them before the handler runs. capabilities comes\n // from the adapter; scope is recovered from the registered metadata (sync);\n // telemetry is the lazy per-connector usage accessor. The SAME three\n // assignments run on the error path (failOpenOrPreserveDeny) for symmetry.\n evt.capabilities = adapter.capabilities;\n evt.scope = readRegisteredMeta(connectorId)?.scope;\n evt.telemetry = buildTelemetryAccessor(connectorId);\n\n const definition = connector.hooks[event];\n if (!definition || typeof definition.handler !== \"function\") {\n // The connector declares no handler for this event — allow.\n return ALLOW;\n }\n\n // Per-host handler override: when dispatching for host X, `hosts[X].handler`\n // wins over the top-level handler; a host not listed (or a per-host entry\n // that is somehow not a function) falls back to the top-level handler.\n // Selection NEVER throws — fail-open is preserved.\n // NOTE: keyed on the RAW install-baked platformId (the host this entrypoint\n // was registered for), NOT resolveHookHostPlatform's AGENT_CONNECTOR_HOST\n // override — that override only re-labels the telemetry row. The error path\n // (failOpenOrPreserveDeny) resolves the IDENTICAL handler the same way, so a\n // per-host deny is preserved consistently.\n const perHost = definition.hosts?.[platformId as PlatformId]?.handler;\n const handler = typeof perHost === \"function\" ? perHost : definition.handler;\n\n // Matcher: if set and the event's subject (tool name, or agent type for\n // Subagent* events) does not match, allow (the hook is simply not\n // interested in this tool/agent). The matcher is the definition's — a\n // per-host handler shares the top-level matcher.\n const subject = eventMatcherSubject(evt);\n if (subject !== undefined) {\n const re = compileMatcher(definition.matcher);\n if (re && !re.test(subject)) return ALLOW;\n }\n\n // Run the resolved handler. Its return is normalized (void → allow) and\n // handed to the adapter to render the host-native reply.\n const handlerResult = await handler(\n // The adapter parsed `evt` as exactly this event's payload type; the\n // HooksConfig handler for the same event expects that payload.\n evt as never,\n );\n const response = normalizeResponse(handlerResult);\n\n // Developer-axis telemetry for the RUNTIME `hook` surface. Fail-open: never\n // lets a measurement error break the hook (handled inside the helper).\n recordHookTelemetry(opts, connector, evt, response);\n\n const reply = adapter.formatReply(event, response);\n return {\n exitCode: reply.exitCode,\n ...(reply.stdout !== undefined ? { stdout: reply.stdout } : {}),\n ...(reply.stderr !== undefined ? { stderr: reply.stderr } : {}),\n };\n } catch (err) {\n // Fail-open: a framework/handler error must not wedge the host's tool call.\n // The ONE exception is a PreToolUse / PermissionRequest deny — a deliberate\n // block we honor even if a later step threw. We re-derive that decision\n // defensively from the adapter so a throw AFTER the deny still surfaces it.\n const message = err instanceof Error ? err.message : String(err);\n log.error(`hook ${platformId}/${event} (${connectorId}) failed:`, message);\n return failOpenOrPreserveDeny(opts, message);\n }\n}\n\n/**\n * Events whose explicit deny survives the fail-open error path. Both are\n * permission gates — a deny there is a deliberate security decision that must\n * never be silently downgraded to allow. (Stop-class denies are persistence\n * conveniences, not security boundaries, so they stay fail-open.)\n */\nconst DENY_PRESERVE_EVENTS: ReadonlySet<HookEventName> = new Set([\n \"PreToolUse\",\n \"PermissionRequest\",\n]);\n\n/**\n * Error path. Returns fail-open allow, UNLESS this is a deny-preserving event\n * (PreToolUse / PermissionRequest) whose handler explicitly denied — in which\n * case the deny is reconstructed and preserved (a security decision is never\n * silently downgraded to allow).\n *\n * Best-effort: if anything in the deny reconstruction itself throws, we fall\n * back to allow (we never escalate an error into a spurious block).\n */\nasync function failOpenOrPreserveDeny(\n opts: RunHookOptions,\n errorMessage: string,\n): Promise<RunHookResult> {\n if (!DENY_PRESERVE_EVENTS.has(opts.event)) return ALLOW;\n try {\n const connector = await loadRegisteredConnector(opts.connectorId);\n const adapter = await loadAdapter(opts.platformId);\n if (!adapter?.parseEvent || !adapter.formatReply) return ALLOW;\n\n const definition = connector.hooks[opts.event];\n if (!definition || typeof definition.handler !== \"function\") return ALLOW;\n\n // Reconstruct the deny from the SAME handler the main path would have run —\n // the per-host override if one is declared for this host, else the top-level\n // handler. Otherwise a per-host deny could be silently downgraded on the\n // error path.\n const perHost = definition.hosts?.[opts.platformId as PlatformId]?.handler;\n const handler = typeof perHost === \"function\" ? perHost : definition.handler;\n\n const raw = parseStdin(opts.stdin);\n const evt = adapter.parseEvent(opts.event, raw);\n evt.connectorId = opts.connectorId;\n // Backfill the HostCtx fields here too (symmetry with the main path), so a\n // per-host deny reconstructed on the error path still carries capabilities/\n // scope/telemetry — a handler that branches on them denies identically.\n evt.capabilities = adapter.capabilities;\n evt.scope = readRegisteredMeta(opts.connectorId)?.scope;\n evt.telemetry = buildTelemetryAccessor(opts.connectorId);\n\n const subject = eventMatcherSubject(evt);\n if (subject !== undefined) {\n const re = compileMatcher(definition.matcher);\n if (re && !re.test(subject)) return ALLOW;\n }\n\n const response = normalizeResponse(await handler(evt as never));\n if (response.decision !== \"deny\") return ALLOW;\n\n const reply = adapter.formatReply(opts.event, response);\n return {\n exitCode: reply.exitCode,\n ...(reply.stdout !== undefined ? { stdout: reply.stdout } : {}),\n ...(reply.stderr !== undefined\n ? { stderr: reply.stderr }\n : { stderr: errorMessage }),\n };\n } catch {\n // Reconstruction failed — never escalate an internal error into a block.\n return ALLOW;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Native (passthrough) hooks — host events OUTSIDE the normalized union\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Flags + stdin for {@link runNativeHook}. `event` is HOST-NATIVE (non-union). */\nexport interface RunNativeHookOptions {\n /** Host platform id from the hook command (`hook <platformId> ...`). */\n platformId: string;\n /** Host-native event name, verbatim (e.g. \"TaskCreated\"). */\n event: string;\n /** Connector id from `--connector <id>`. */\n connectorId: string;\n /** Raw stdin payload (host-native JSON). Empty string is tolerated → `{}`. */\n stdin: string;\n}\n\n/** The connector's native hook definition for `platformId`/`event`, if any. */\nfunction nativeHookDef(\n connector: ResolvedConnector,\n platformId: string,\n event: string,\n): NativeHookDef | undefined {\n return connector.platforms[platformId as PlatformId]?.nativeHooks?.[event];\n}\n\n/**\n * True when the registered connector declares `event` under\n * `platforms[platformId].nativeHooks` with a live handler. Used by the hook CLI\n * to decide whether a non-union event name is accepted (declared → dispatch via\n * {@link runNativeHook}) or rejected with the strict unknown-event error.\n * Fail-safe: any load error reads as \"not declared\".\n */\nexport async function isNativeHookDeclared(\n platformId: string,\n event: string,\n connectorId: string,\n): Promise<boolean> {\n try {\n const connector = await loadRegisteredConnector(connectorId);\n return typeof nativeHookDef(connector, platformId, event)?.handler === \"function\";\n } catch {\n return false;\n }\n}\n\n/**\n * Dispatch one NATIVE (passthrough) hook invocation. No normalized parse, no\n * HookResponse mapping, no runtime matcher evaluation (the host's own matcher\n * already filtered — the def's matcher string was written verbatim into the\n * host config at install):\n *\n * stdin JSON → NativeHookEvent{raw} → handler → VERBATIM JSON stdout (exit 0).\n *\n * void/undefined return → exit 0 with no output. Fail-open: ANY throw degrades\n * to exit 0 with no output — exit-2 blocking semantics are not modeled in v1.\n * Telemetry: records the same scope:\"hook\" developer-axis row as normalized\n * hooks, with the NATIVE event name as the per-item name.\n */\nexport async function runNativeHook(\n opts: RunNativeHookOptions,\n): Promise<RunHookResult> {\n const { platformId, event, connectorId, stdin } = opts;\n try {\n const connector = await loadRegisteredConnector(connectorId);\n const def = nativeHookDef(connector, platformId, event);\n if (!def || typeof def.handler !== \"function\") {\n // Not declared for this platform — nothing to do, allow.\n return ALLOW;\n }\n\n const raw = parseStdin(stdin);\n // Best-effort session/project extraction from the json-stdio common shape\n // (Claude Code: session_id / cwd). Everything else stays in `raw` untouched.\n const sessionId =\n typeof (raw as { session_id?: unknown })?.session_id === \"string\"\n ? (raw as { session_id: string }).session_id\n : \"\";\n const cwd = (raw as { cwd?: unknown })?.cwd;\n const evt: NativeHookEvent = {\n event,\n hostPlatform: REGISTERED_PLATFORM_IDS.has(platformId as PlatformId)\n ? (platformId as PlatformId)\n : \"unknown\",\n sessionId,\n ...(typeof cwd === \"string\" && cwd !== \"\" ? { projectDir: cwd } : {}),\n raw,\n };\n\n const result = await def.handler(evt);\n\n // Same scope:\"hook\" telemetry row as normalized hooks (fail-open inside).\n recordHookTelemetry(opts, connector, evt, result === undefined ? {} : result);\n\n if (result === undefined) return ALLOW; // void → exit 0, no output\n const json = JSON.stringify(result);\n // Non-JSON-serializable returns (functions/symbols) degrade to silence.\n if (json === undefined) return ALLOW;\n return { exitCode: 0, stdout: json };\n } catch (err) {\n // Fail-open, unconditionally: native hooks have no deny-preserve carve-out\n // (there is no normalized decision to reconstruct).\n const message = err instanceof Error ? err.message : String(err);\n log.error(\n `native hook ${platformId}/${event} (${connectorId}) failed:`,\n message,\n );\n return ALLOW;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/spawn.ts"],"sourcesContent":["/**\n * core/spawn — cross-platform command construction.\n *\n * Borrowed from context-mode (issues #369/#372/#548/#738): bare `node` is\n * unreliable on Windows Git Bash (MSYS) and MSYS rewrites absolute paths on\n * non-C: drives. Always use the resolved runtime path, forward slashes, and\n * double-quoting. No-ops on macOS/Linux.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport type { InstallScope, PlatformId, ServerDef } from \"./types.js\";\nimport type {\n LaunchMethod,\n TelemetryInstallScope,\n} from \"../telemetry/types.js\";\n\n/**\n * Narrow the framework's 5-value {@link InstallScope} down to the two telemetry\n * slicing buckets: only a `project`-local install is `project`; everything else\n * (system/user/profile/managed) is global and reads as `user`.\n */\nexport function narrowInstallScope(scope: InstallScope): TelemetryInstallScope {\n return scope === \"project\" ? \"project\" : \"user\";\n}\n\n/** Build `\"<nodePath>\" \"<scriptPath>\"`. */\nexport function buildNodeCommand(\n scriptPath: string,\n opts?: { nodePath?: string },\n): string {\n const nodePath = (opts?.nodePath ?? process.execPath).replace(/\\\\/g, \"/\");\n const safe = scriptPath.replace(/\\\\/g, \"/\");\n return `\"${nodePath}\" \"${safe}\"`;\n}\n\n/**\n * Strict inverse of buildNodeCommand. Returns the two quoted tokens, or null if\n * `cmd` was not produced by buildNodeCommand (no fragile last-whitespace tail\n * grabbing — that was the #548 doubled-path bug when paths contained spaces).\n */\nexport function parseNodeCommand(\n cmd: string,\n): { nodePath: string; scriptPath: string } | null {\n if (typeof cmd !== \"string\" || cmd.length === 0) return null;\n const m = cmd.match(/^\"([^\"]+)\"\\s+\"([^\"]+)\"\\s*$/);\n if (!m || !m[1] || !m[2]) return null;\n return { nodePath: m[1], scriptPath: m[2] };\n}\n\n/** Quote a single argument for inclusion in a host hook command string. */\nexport function quoteArg(arg: string): string {\n return `\"${arg.replace(/\\\\/g, \"/\")}\"`;\n}\n\n/**\n * True when `hay` (slash-normalized) has the quoted home-bin path IMMEDIATELY\n * followed by `verb` — i.e. the verb sits in its command SLOT (right after the\n * home binary), not merely somewhere in the argument list. This distinguishes\n * `\"<bin>\" statusline …` from `\"<bin>\" action … statusline …`: the action verb\n * takes a free-form `actionId` positional that can legitimately equal another\n * verb's name (`statusline` / `usage-event` / `hook`), and only the SLOT\n * position identifies the real verb. `homeBinNorm` is the slash-normalized\n * home-bin path the caller already derived.\n */\nfunction hasVerbSlot(hay: string, homeBinNorm: string, verb: string): boolean {\n return hay.includes(`\"${homeBinNorm}\" ${verb} `);\n}\n\n/**\n * Build the universal hook command a host config points at:\n * \"<homeBin>\" hook <platformId> <event> --connector <id>\n * Pointing every host at the one stable home binary is how a single update\n * propagates everywhere (docs/ARCHITECTURE.md §3 R1).\n */\nexport function buildHomeBinHookCommand(\n homeBinPath: string,\n platformId: string,\n event: string,\n connectorId: string,\n): string {\n return `${quoteArg(homeBinPath)} hook ${platformId} ${event} --connector ${connectorId}`;\n}\n\n/**\n * True when `command` is a home-bin hook command for exactly `connectorId`.\n *\n * Critical: the connector id is the LAST token of the hook command\n * (`... --connector <id>`), so a naive `command.includes(\"--connector \" + id)`\n * collides on shared-prefix ids — `--connector acme-db` contains\n * `--connector acme`, which would let uninstalling `acme` strip `acme-db`'s\n * hooks. We anchor the id token: the character after it must be end-of-string,\n * whitespace, or a closing double-quote (the JSON-embedded case). Connector ids\n * are kebab-case (ID_RE) so they never contain whitespace or quotes themselves.\n */\nexport function isHomeBinHookCommand(\n command: string | undefined,\n homeBinPath: string,\n connectorId: string,\n): boolean {\n if (!command) return false;\n // The stored command was forward-slashed by quoteArg(); homeBinPath() yields\n // NATIVE (back)slashes on Windows (node:path join). Normalize BOTH sides so the\n // ownership substring test holds on win32 (without this, detection always\n // returns false there → duplicate hooks on re-install + orphans on uninstall).\n // No-op on POSIX.\n const hay = command.replace(/\\\\/g, \"/\");\n if (!hay.includes(homeBinPath.replace(/\\\\/g, \"/\"))) return false;\n const token = `--connector ${connectorId}`;\n let from = 0;\n for (;;) {\n const idx = hay.indexOf(token, from);\n if (idx < 0) return false;\n const after = hay.charAt(idx + token.length);\n if (after === \"\" || after === '\"' || /\\s/.test(after)) return true;\n from = idx + token.length;\n }\n}\n\n/**\n * Build the OPT-IN host-native usage-event command an AfterModel / PostInvocation\n * hook points at:\n * \"<homeBin>\" usage-event <platformId> --connector <id>\n * Mirrors {@link buildHomeBinHookCommand} (same anchoring rules apply) but routes\n * to the hidden `usage-event` entrypoint, which records a DISTINCT `model_turn`\n * row instead of dispatching a connector handler.\n */\nexport function buildUsageEventCommand(\n homeBinPath: string,\n platformId: string,\n connectorId: string,\n): string {\n return `${quoteArg(homeBinPath)} usage-event ${platformId} --connector ${connectorId}`;\n}\n\n/**\n * True when `command` is OUR host-native usage-event command for exactly\n * `connectorId`. Same end-of-token anchoring as {@link isHomeBinHookCommand} so a\n * shared-prefix id can't collide; additionally requires the ` usage-event ` verb\n * so it is never confused with a plain `hook` command during uninstall.\n */\nexport function isUsageEventCommand(\n command: string | undefined,\n homeBinPath: string,\n connectorId: string,\n): boolean {\n if (!command) return false;\n // Normalize separators like isHomeBinHookCommand (win32 ownership detection).\n const hay = command.replace(/\\\\/g, \"/\");\n const homeBinNorm = homeBinPath.replace(/\\\\/g, \"/\");\n if (!hay.includes(homeBinNorm)) return false;\n if (!hasVerbSlot(hay, homeBinNorm, \"usage-event\")) return false;\n return isHomeBinHookCommand(command, homeBinPath, connectorId);\n}\n\n/**\n * Build the home-bin statusline command a host's status line config points at:\n * \"<homeBin>\" statusline <platformId> --connector <id>\n * Mirrors {@link buildHomeBinHookCommand} / {@link buildUsageEventCommand} (the\n * same single-home-binary indirection) but routes to the `statusline`\n * entrypoint, which renders the connector's HUD line for the host.\n */\nexport function buildHomeBinStatuslineCommand(\n homeBinPath: string,\n platformId: string,\n connectorId: string,\n): string {\n return `${quoteArg(homeBinPath)} statusline ${platformId} --connector ${connectorId}`;\n}\n\n/**\n * True when `command` is OUR home-bin statusline command for exactly\n * `connectorId`. Same end-of-token anchoring as {@link isHomeBinHookCommand} so a\n * shared-prefix id can't collide; additionally requires the ` statusline ` verb\n * so it is never confused with a plain `hook` / `usage-event` command.\n */\nexport function isHomeBinStatuslineCommand(\n command: string | undefined,\n homeBinPath: string,\n connectorId: string,\n): boolean {\n if (!command) return false;\n // Normalize separators like isHomeBinHookCommand (win32 ownership detection).\n const hay = command.replace(/\\\\/g, \"/\");\n const homeBinNorm = homeBinPath.replace(/\\\\/g, \"/\");\n if (!hay.includes(homeBinNorm)) return false;\n if (!hasVerbSlot(hay, homeBinNorm, \"statusline\")) return false;\n return isHomeBinHookCommand(command, homeBinPath, connectorId);\n}\n\n/**\n * Build the home-bin action command a future host affordance (slash command /\n * keybinding) points at:\n * \"<homeBin>\" action <platformId> <actionId> --connector <id>\n * Mirrors {@link buildHomeBinStatuslineCommand} (the same single-home-binary\n * indirection) but routes to the `action` entrypoint, which re-imports the\n * connector and runs the named action's run(ctx). v1 ships only this command\n * builder + the runtime verb; no adapter EMITS the affordance yet.\n */\nexport function buildHomeBinActionCommand(\n homeBinPath: string,\n platformId: string,\n actionId: string,\n connectorId: string,\n): string {\n return `${quoteArg(homeBinPath)} action ${platformId} ${actionId} --connector ${connectorId}`;\n}\n\n/**\n * True when `command` is OUR home-bin action command for exactly `connectorId`.\n * Same end-of-token anchoring as {@link isHomeBinHookCommand} so a shared-prefix\n * id can't collide; additionally requires the ` action ` verb so it is never\n * confused with a plain `hook` / `usage-event` / `statusline` command.\n */\nexport function isHomeBinActionCommand(\n command: string | undefined,\n homeBinPath: string,\n connectorId: string,\n): boolean {\n if (!command) return false;\n // Normalize separators like isHomeBinHookCommand (win32 ownership detection).\n const hay = command.replace(/\\\\/g, \"/\");\n const homeBinNorm = homeBinPath.replace(/\\\\/g, \"/\");\n if (!hay.includes(homeBinNorm)) return false;\n if (!hasVerbSlot(hay, homeBinNorm, \"action\")) return false;\n return isHomeBinHookCommand(command, homeBinPath, connectorId);\n}\n\n/**\n * Single source of truth for \"is OPT-IN host-native turn-usage capture enabled\n * for this connector?\" — read by the Gemini / Antigravity adapters at install\n * time to decide whether to ALSO write the AfterModel / PostInvocation usage hook.\n * Enabled when the connector opts in via telemetry.hostNativeUsage OR when the\n * install-time env switch AGENT_CONNECTOR_HOST_NATIVE=1 forces it on. Off by\n * default (privacy: host-native capture is never installed silently).\n */\nexport function isHostNativeUsageEnabled(\n telemetry: { enabled: boolean; hostNativeUsage?: boolean },\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n if (telemetry.enabled !== true) return false;\n if (env.AGENT_CONNECTOR_HOST_NATIVE === \"1\") return true;\n return telemetry.hostNativeUsage === true;\n}\n\n/**\n * Single source of truth for \"should this server be wrapped by `serve` for\n * telemetry?\" — so every adapter behaves identically even for a server that\n * skipped normalizeServer (e.g. a per-platform override). On by default for an\n * stdio server with a command, unless explicitly disabled or telemetry is off.\n */\nexport function shouldWrapForTelemetry(\n server: ServerDef,\n telemetry: { enabled: boolean },\n): boolean {\n return (\n server.wrapForTelemetry !== false &&\n telemetry.enabled === true &&\n server.transport === \"stdio\" &&\n typeof server.command === \"string\" &&\n server.command !== \"\"\n );\n}\n\n/**\n * Build the telemetry-wrapping server command:\n * \"<homeBin>\" serve --connector <id> --scope <scope> --host <platformId> -- <realCommand> <realArgs...>\n * Used when a stdio server opts into transparent telemetry capture.\n *\n * `scope` records the install dimension (global user vs project-local) on every\n * telemetry row so usage can be sliced by it later. It is OPTIONAL for backward\n * compatibility: when omitted, the `--scope` flag is not emitted and the runtime\n * treats the scope as \"unknown\".\n *\n * `platformId` bakes the install TARGET platform into the wrapper so the proxy\n * stamps `hostPlatform` correctly even under a headless spawn where runtime env\n * markers are absent (detectRuntimeHost only knows claude-code/cursor/codex and\n * otherwise mis-attributes). Emitted as `--host <platformId>` in the FLAG\n * section (before `--`). It is OPTIONAL + backward-compatible: when omitted the\n * `--host` flag is not emitted and the runtime falls back to env detection.\n */\nexport function buildServeWrapperCommand(\n homeBinPath: string,\n connectorId: string,\n realCommand: string,\n realArgs: string[],\n scope?: InstallScope,\n platformId?: PlatformId,\n): { command: string; args: string[] } {\n const flags = [\"serve\", \"--connector\", connectorId];\n if (scope !== undefined) flags.push(\"--scope\", narrowInstallScope(scope));\n if (platformId !== undefined) flags.push(\"--host\", platformId);\n return {\n command: homeBinPath,\n args: [...flags, \"--\", realCommand, ...realArgs],\n };\n}\n\n/** Injectable deps for {@link resolveSpawnCommand} (so the win32 branch is unit-testable on POSIX). */\nexport interface SpawnResolveDeps {\n platform?: NodeJS.Platform;\n /** PATH value (`;`-separated on Windows). */\n pathEnv?: string;\n /** PATHEXT value (`;`-separated). */\n pathExt?: string;\n /** File-existence probe (defaults to fs.existsSync). */\n exists?: (p: string) => boolean;\n}\n\n/** How a child command should be handed to spawn() on this platform. */\nexport interface SpawnResolution {\n /** The command to pass to spawn() (an absolute path on win32 when resolved). */\n file: string;\n /**\n * Whether spawn() MUST use `shell: true`. On native Windows a `.cmd`/`.bat`\n * shim (npx.cmd, pnpm.cmd, the agent-connector.cmd launcher) cannot be run by\n * raw CreateProcess — Node throws EINVAL since the 2024 security fix unless a\n * shell is used. `.exe` resolves directly with no shell.\n */\n needsShell: boolean;\n}\n\n/**\n * Resolve a child command for spawn() across platforms. NO-OP on macOS/Linux\n * (returns the command unchanged, no shell). On native Windows it fixes the two\n * spawn failure modes for bare package runners:\n * 1. raw CreateProcess ignores PATHEXT, so spawn(\"npx\") → ENOENT even though\n * `npx.cmd` is on PATH. We search PATH × PATHEXT and return the resolved\n * absolute path.\n * 2. a resolved `.cmd`/`.bat` still cannot be launched without a shell, so we\n * flag `needsShell` for those (and `.exe`/`.com` run directly).\n * A bare name that PATH search misses falls back to `{ file: command,\n * needsShell: true }` so cmd.exe's own PATHEXT lookup still finds it.\n */\nexport function resolveSpawnCommand(\n command: string,\n deps: SpawnResolveDeps = {},\n): SpawnResolution {\n const platform = deps.platform ?? process.platform;\n if (platform !== \"win32\") return { file: command, needsShell: false };\n\n const isBatch = (s: string): boolean => /\\.(cmd|bat)$/i.test(s);\n\n // Already a path or already has an extension → don't search; only decide shell.\n if (/[\\\\/]/.test(command) || /\\.[a-z0-9]+$/i.test(command)) {\n return { file: command, needsShell: isBatch(command) };\n }\n\n const exists = deps.exists ?? existsSync;\n const pathEnv = deps.pathEnv ?? process.env.PATH ?? process.env.Path ?? \"\";\n const pathExt = deps.pathExt ?? process.env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\";\n const dirs = pathEnv.split(\";\").filter((d) => d !== \"\");\n const exts = pathExt.split(\";\").filter((e) => e !== \"\");\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, command + ext);\n if (exists(candidate)) return { file: candidate, needsShell: isBatch(ext) };\n }\n }\n // Not found on PATH — let cmd.exe resolve it (handles shim dirs we may miss).\n return { file: command, needsShell: true };\n}\n\n/** Basenames (case-insensitive, sans common extensions) of the ephemeral package runners. */\nconst PACKAGE_RUNNERS: Record<string, Extract<LaunchMethod, \"npx\" | \"bunx\" | \"uvx\">> = {\n npx: \"npx\",\n bunx: \"bunx\",\n uvx: \"uvx\",\n};\n\n/** Basenames of language interpreters that launch a local script. */\nconst INTERPRETERS: ReadonlySet<string> = new Set([\"node\", \"bun\", \"deno\"]);\n\n/**\n * Strip a directory prefix and a trailing executable extension to get the bare\n * program name. Tolerates both POSIX and Windows separators (the wrapped command\n * may be an absolute Windows path) and lowercases for case-insensitive matching.\n */\nfunction commandBasename(realCommand: string): string {\n const noDir = realCommand.replace(/\\\\/g, \"/\").split(\"/\").pop() ?? realCommand;\n return noDir.replace(/\\.(exe|cmd|bat|com)$/i, \"\").toLowerCase();\n}\n\n/**\n * Classify how the real MCP server is launched, for the \"launch-method\" slicing\n * dimension. Package runners (npx/bunx/uvx) and interpreters (node/bun/deno) are\n * detected by the command basename. A remote (http) server is not launched\n * locally, so the caller — which alone knows the transport — passes\n * `isRemote: true` to force \"http\". Anything else resolves to \"binary\".\n */\nexport function detectLaunchMethod(\n realCommand: string,\n realArgs: string[],\n opts?: { isRemote?: boolean },\n): LaunchMethod {\n void realArgs; // reserved: args may later refine node-vs-script heuristics\n if (opts?.isRemote === true) return \"http\";\n if (typeof realCommand !== \"string\" || realCommand.trim() === \"\") {\n return \"unknown\";\n }\n const base = commandBasename(realCommand);\n if (base === \"\") return \"unknown\";\n const runner = PACKAGE_RUNNERS[base];\n if (runner !== undefined) return runner;\n if (INTERPRETERS.has(base)) return \"node\";\n return \"binary\";\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/usage/paths.ts"],"sourcesContent":["/**\n * usage/paths — per-platform HOST storage-root resolution + small fs walkers.\n *\n * This is the read-only complement to core/paths.ts: where that module resolves\n * the FRAMEWORK data-root (~/.agent-connector), this one resolves each agent\n * CLI's OWN native storage roots (~/.qwen, ~/.claude, …) so the usage readers\n * can parse them. It NEVER writes; it only enumerates files.\n *\n * Ported from tokscale paths.rs (the tokscale cache dir) and from each reader's\n * storage-path spec (docs/research/usage-readers.json). Resolution order is\n * uniform across platforms:\n * 1. an explicit AGENT_CONNECTOR_<PLATFORM>_DIR env override (verbatim, when\n * non-empty — an empty string is treated as unset so we never resolve to \"\");\n * 2. the platform-specific default for the current OS.\n *\n * The \"tokscale cache dir\" (~/.config/tokscale, or AGENT_CONNECTOR_TOKSCALE_DIR)\n * is where synced readers (cursor/antigravity/antigravity-cli/trae/warp) look for\n * a local cache that a separate tokscale run may have produced — we read it if\n * present, but we never create or sync it. (Antigravity's own native store is\n * protobuf .pb with no public schema, so it too is synced-cache-or-skip.)\n */\n\nimport {\n existsSync,\n readdirSync,\n statSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// Home / env helpers\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Expand a leading \"~\" (and \"~/\") to the user's home directory. */\nexport function expandHome(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return join(homedir(), p.slice(2));\n }\n return p;\n}\n\n/**\n * Read an env override, treating empty/blank as unset (mirrors tokscale's\n * is_config_dir_overridden contract: an empty override must NOT resolve to \"\").\n * Relative paths are resolved against the process CWD; \"~\" is expanded.\n */\nfunction envOverride(name: string): string | undefined {\n const raw = process.env[name];\n if (raw == null || raw.trim() === \"\") return undefined;\n const expanded = expandHome(raw.trim());\n return isAbsolute(expanded) ? expanded : resolve(expanded);\n}\n\n/** $XDG_DATA_HOME (when set & non-empty) else ~/.local/share. */\nfunction xdgDataHome(): string {\n const xdg = process.env.XDG_DATA_HOME;\n if (xdg && xdg.trim() !== \"\") return resolve(expandHome(xdg.trim()));\n return join(homedir(), \".local\", \"share\");\n}\n\n/** $XDG_CONFIG_HOME (when set & non-empty) else ~/.config. */\nfunction xdgConfigHome(): string {\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg && xdg.trim() !== \"\") return resolve(expandHome(xdg.trim()));\n return join(homedir(), \".config\");\n}\n\n/** Windows %LOCALAPPDATA% else ~/AppData/Local. */\nfunction localAppData(): string {\n const v = process.env.LOCALAPPDATA;\n if (v && v.trim() !== \"\") return resolve(v.trim());\n return join(homedir(), \"AppData\", \"Local\");\n}\n\nconst isMac = process.platform === \"darwin\";\nconst isWin = process.platform === \"win32\";\n\n// ─────────────────────────────────────────────────────────────────────────\n// tokscale cache dir (for synced readers)\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve the tokscale config dir (where synced caches live). Ported from\n * tokscale paths.rs get_config_dir():\n * 1. AGENT_CONNECTOR_TOKSCALE_DIR override (verbatim, non-empty);\n * 2. macOS: $HOME/.config/tokscale (NOT ~/Library/Application Support);\n * 3. Linux: $XDG_CONFIG_HOME/tokscale or ~/.config/tokscale;\n * 4. Windows: %APPDATA%/tokscale (config_dir equivalent) else ~/.config/tokscale.\n *\n * We use AGENT_CONNECTOR_TOKSCALE_DIR (not TOKSCALE_CONFIG_DIR) so the framework\n * never accidentally couples to a tokscale install's hermetic test env.\n */\nexport function tokscaleConfigDir(): string {\n const override = envOverride(\"AGENT_CONNECTOR_TOKSCALE_DIR\");\n if (override) return override;\n if (isMac) return join(homedir(), \".config\", \"tokscale\");\n if (isWin) {\n const appData = process.env.APPDATA;\n if (appData && appData.trim() !== \"\") return join(resolve(appData.trim()), \"tokscale\");\n return join(homedir(), \".config\", \"tokscale\");\n }\n return join(xdgConfigHome(), \"tokscale\");\n}\n\n/** The directory a synced platform's local cache lives under, e.g. `<tokscale>/cursor-cache`. */\nexport function tokscaleCacheDir(name: string): string {\n return join(tokscaleConfigDir(), name);\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Antigravity native store roots (DETECTION ONLY — not parsed for usage)\n//\n// CONFIRMED-BY-INSTALL (2026-06-03, docs/research/antigravity-paths-confirmed.md):\n// the native Antigravity store is `~/.gemini/antigravity/conversations/<uuid>.pb`\n// — PROTOBUF with no public schema — and `brain/<uuid>/` holds only media +\n// `*.metadata.json`. There are NO `transcript*.jsonl` files. The `agy` CLI shares\n// this SAME `~/.gemini/antigravity/` dir (no separate config/storage dir). So the\n// native dir is used only for platform DETECTION (in the adapters); the usage\n// readers do NOT parse it (protobuf, unreadable) — they are SYNCED readers that\n// read the tokscale synced-cache instead (see hostRoots → antigravity-cache).\n//\n// These helpers are retained for any detection caller and honor the standard\n// AGENT_CONNECTOR_<PLATFORM>_DIR override (prepended, preferred when set).\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * The native Antigravity store root for DETECTION: `~/.gemini/antigravity/`\n * (CONFIRMED). Its `conversations/<uuid>.pb` payloads are protobuf with no public\n * schema, so this dir is NOT parsed for usage — only its presence is a signal.\n *\n * Honors AGENT_CONNECTOR_ANTIGRAVITY_DIR (verbatim, when set) as the override.\n */\nexport function antigravityNativeRoots(): string[] {\n const out: string[] = [];\n const override = envOverride(\"AGENT_CONNECTOR_ANTIGRAVITY_DIR\");\n if (override) out.push(override);\n out.push(join(homedir(), \".gemini\", \"antigravity\"));\n return out;\n}\n\n/**\n * The native store root for the Antigravity CLI (`agy`) for DETECTION. CONFIRMED:\n * `agy` has NO separate dir — it SHARES the IDE's `~/.gemini/antigravity/` (also\n * protobuf, not parsed for usage).\n *\n * Honors AGENT_CONNECTOR_ANTIGRAVITY_CLI_DIR (verbatim, when set) as the override.\n */\nexport function antigravityCliNativeRoots(): string[] {\n const out: string[] = [];\n const override = envOverride(\"AGENT_CONNECTOR_ANTIGRAVITY_CLI_DIR\");\n if (override) out.push(override);\n out.push(join(homedir(), \".gemini\", \"antigravity\"));\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Per-platform host roots\n// ─────────────────────────────────────────────────────────────────────────\n\n/**\n * Candidate host storage roots for a platform, most-preferred first. Readers\n * iterate the returned list and use the first that exists (each variant is the\n * OS-correct default; the env override, when present, is prepended).\n *\n * Only the platforms this subsystem currently ships a reader for are listed in\n * detail; the table is structured so a new platform is one new case. Returning\n * `[]` (no candidate root) makes a reader fail-open to zero records.\n */\nexport function hostRoots(platformId: string): string[] {\n const out: string[] = [];\n const override = envOverride(`AGENT_CONNECTOR_${platformId.toUpperCase().replace(/-/g, \"_\")}_DIR`);\n if (override) out.push(override);\n\n switch (platformId) {\n case \"qwen-code\":\n // ~/.qwen/projects/*/chats/*.jsonl\n out.push(join(homedir(), \".qwen\", \"projects\"));\n break;\n case \"claude-code\":\n out.push(join(homedir(), \".claude\", \"projects\"));\n break;\n case \"codex\":\n out.push(join(homedir(), \".codex\", \"sessions\"));\n break;\n case \"gemini-cli\":\n out.push(join(homedir(), \".gemini\", \"tmp\"));\n break;\n case \"pi\":\n out.push(join(homedir(), \".pi\", \"agent\", \"sessions\"));\n break;\n case \"kimi\":\n out.push(join(homedir(), \".kimi\", \"sessions\"));\n break;\n case \"copilot-cli\":\n out.push(join(xdgDataHome(), \"Copilot\", \"telemetry\"));\n out.push(join(xdgConfigHome(), \"copilot\", \"telemetry\"));\n break;\n case \"amp\":\n out.push(join(xdgDataHome(), \"amp\", \"threads\"));\n break;\n case \"droid\":\n out.push(join(homedir(), \".factory\", \"sessions\"));\n break;\n case \"mux\":\n out.push(join(homedir(), \".mux\", \"sessions\"));\n break;\n case \"kiro\":\n out.push(join(homedir(), \".kiro\", \"sessions\", \"cli\"));\n break;\n case \"opencode\":\n out.push(join(xdgDataHome(), \"opencode\", \"opencode.db\"));\n break;\n case \"goose\":\n if (isMac) out.push(join(homedir(), \"Library\", \"Application Support\", \"goose\", \"sessions\", \"sessions.db\"));\n out.push(join(xdgDataHome(), \"goose\", \"sessions\", \"sessions.db\"));\n out.push(join(xdgDataHome(), \"Block\", \"goose\", \"sessions\", \"sessions.db\"));\n break;\n case \"hermes\":\n out.push(join(homedir(), \".hermes\", \"state.db\"));\n break;\n case \"kilo-cli\":\n out.push(join(xdgDataHome(), \"kilo\", \"kilo.db\"));\n break;\n case \"crush\":\n out.push(join(homedir(), \".cache\", \"crush\", \"crush.db\"));\n break;\n case \"zed\":\n if (isMac) out.push(join(homedir(), \"Library\", \"Application Support\", \"Zed\", \"threads\", \"threads.db\"));\n else if (isWin) out.push(join(localAppData(), \"Zed\", \"threads\", \"threads.db\"));\n else out.push(join(xdgDataHome(), \"zed\", \"threads\", \"threads.db\"));\n break;\n // Synced platforms: local cache under the tokscale config dir.\n case \"cursor\":\n out.push(tokscaleCacheDir(\"cursor-cache\"));\n break;\n case \"antigravity\":\n // SYNCED: the native store is protobuf (.pb, no public schema) and NOT\n // parseable, so the reader reads only the tokscale synced-cache mirror if a\n // separate tokscale run produced one (else [] → \"requires sync\"). The\n // native `~/.gemini/antigravity/` dir is used only for detection.\n out.push(tokscaleCacheDir(\"antigravity-cache\"));\n break;\n case \"antigravity-cli\":\n // SYNCED: `agy` shares the IDE's protobuf (.pb) store (no schema), so the\n // reader reads only the tokscale synced-cache mirror if present (else [] →\n // \"requires sync\"). Shares the IDE's antigravity-cache name (same data).\n out.push(tokscaleCacheDir(\"antigravity-cache\"));\n break;\n case \"trae\":\n out.push(tokscaleCacheDir(\"trae-cache\"));\n break;\n case \"warp\":\n out.push(tokscaleCacheDir(\"warp-cache\"));\n break;\n default:\n break;\n }\n return out;\n}\n\n/** First existing host root for a platform, or undefined when none is present. */\nexport function firstExistingRoot(platformId: string): string | undefined {\n for (const root of hostRoots(platformId)) {\n if (existsSync(root)) return root;\n }\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// Directory / glob walkers (tolerant — never throw)\n// ─────────────────────────────────────────────────────────────────────────\n\n/** Is the path an existing directory? Tolerant (false on any stat error). */\nexport function isDir(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Is the path an existing regular file? Tolerant (false on any stat error). */\nexport function isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively list every file under `root` whose name matches `predicate`.\n * Tolerant: an unreadable directory is skipped, never thrown. Returns absolute\n * paths in directory-walk order. A non-existent root yields [].\n */\nexport function walkFiles(\n root: string,\n predicate: (name: string, absPath: string) => boolean,\n maxDepth = 12,\n): string[] {\n const out: string[] = [];\n if (!isDir(root)) return out;\n\n const stack: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }];\n while (stack.length > 0) {\n const { dir, depth } = stack.pop() as { dir: string; depth: number };\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n continue; // unreadable dir → skip\n }\n for (const entry of entries) {\n const abs = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (depth < maxDepth) stack.push({ dir: abs, depth: depth + 1 });\n } else if (entry.isFile() && predicate(entry.name, abs)) {\n out.push(abs);\n }\n }\n }\n return out;\n}\n\n/**\n * List immediate subdirectory names of `dir` (not recursive). Tolerant: returns\n * [] for a missing/unreadable directory.\n */\nexport function listSubdirs(dir: string): string[] {\n if (!isDir(dir)) return [];\n try {\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => join(dir, e.name));\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/jsonc.ts","../src/core/object-map.ts","../src/adapters/base.ts","../src/core/hook-array.ts"],"sourcesContent":["/**\n * core/jsonc — tolerant JSONC / JSON5-lite parsing.\n *\n * Many hosts document their config file as JSONC (or JSON5): // line comments,\n * /* *\\/ block comments, and trailing commas are all legal. A strict JSON.parse\n * false-fails such a perfectly valid file → null → and a careless `?? {}` then\n * OVERWRITES the user's entire config with just our entry (data loss). To avoid\n * that, every JSON/JSONC adapter parses through `parseJsonc`, which strips\n * comments + trailing commas in ONE string-aware pass before JSON.parse.\n *\n * The single pass is deliberately string-literal aware: a // or /* inside a\n * quoted value (e.g. \"http://x//y\") is NOT a comment, and a comma inside a\n * string (e.g. \"a,]\") is NOT a trailing comma — both must survive verbatim.\n */\n\n/**\n * Strip // line comments, /* *\\/ block comments, and trailing commas from JSONC\n * text in a SINGLE string-aware pass.\n *\n * Rules:\n * - While inside a string literal, every character (including //, /*, and ,)\n * is copied verbatim; backslash escapes the next character.\n * - Outside a string, // … runs to end-of-line and /* … *\\/ to its close.\n * - A comma is dropped ONLY when the next non-whitespace, non-comment token is\n * a closing `}` or `]` (a trailing comma). A comma followed by another value\n * or by end-of-input is preserved (the latter lets JSON.parse report the\n * real syntax error instead of us masking it).\n */\nexport function stripJsonc(text: string): string {\n const n = text.length;\n let out = \"\";\n let i = 0;\n let inString = false;\n let quote = \"\";\n\n while (i < n) {\n const ch = text[i] as string;\n\n if (inString) {\n out += ch;\n if (ch === \"\\\\\") {\n // Copy the escaped character verbatim (covers \\\" \\\\ \\/ etc.).\n if (i + 1 < n) {\n out += text[i + 1] as string;\n i += 2;\n continue;\n }\n } else if (ch === quote) {\n inString = false;\n }\n i += 1;\n continue;\n }\n\n const next = i + 1 < n ? (text[i + 1] as string) : \"\";\n\n // Enter a string literal.\n if (ch === '\"' || ch === \"'\") {\n inString = true;\n quote = ch;\n out += ch;\n i += 1;\n continue;\n }\n\n // Line comment — skip to end of line (keep the newline for line tracking).\n if (ch === \"/\" && next === \"/\") {\n i += 2;\n while (i < n && text[i] !== \"\\n\") i += 1;\n continue;\n }\n\n // Block comment — skip to the closing */.\n if (ch === \"/\" && next === \"*\") {\n i += 2;\n while (i < n && !(text[i] === \"*\" && text[i + 1] === \"/\")) i += 1;\n i += 2; // consume the closing */\n continue;\n }\n\n // Trailing comma — drop it only when the next significant token (skipping\n // whitespace AND comments) is a closing } or ]. This stays string-aware\n // because we only reach here outside a string.\n if (ch === \",\") {\n const closer = nextSignificant(text, i + 1);\n if (closer === \"}\" || closer === \"]\") {\n i += 1; // drop the trailing comma\n continue;\n }\n }\n\n out += ch;\n i += 1;\n }\n\n return out;\n}\n\n/**\n * Peek the next significant (non-whitespace, non-comment) character at/after\n * `from`, returning \"\" at end-of-input. Used only outside string literals.\n */\nfunction nextSignificant(text: string, from: number): string {\n const n = text.length;\n let i = from;\n while (i < n) {\n const ch = text[i] as string;\n if (ch === \" \" || ch === \"\\t\" || ch === \"\\r\" || ch === \"\\n\") {\n i += 1;\n continue;\n }\n const next = i + 1 < n ? (text[i + 1] as string) : \"\";\n if (ch === \"/\" && next === \"/\") {\n i += 2;\n while (i < n && text[i] !== \"\\n\") i += 1;\n continue;\n }\n if (ch === \"/\" && next === \"*\") {\n i += 2;\n while (i < n && !(text[i] === \"*\" && text[i + 1] === \"/\")) i += 1;\n i += 2;\n continue;\n }\n return ch;\n }\n return \"\";\n}\n\n/**\n * Parse JSONC text tolerantly: strip comments + trailing commas, then JSON.parse.\n * Throws (like JSON.parse) on genuinely malformed input — callers that want a\n * fail-soft null should wrap in try/catch (BaseAdapter.readJson does).\n */\nexport function parseJsonc<T = unknown>(text: string): T {\n return JSON.parse(stripJsonc(text)) as T;\n}\n","import type { ChangeRecord } from \"./types.js\";\n\n/**\n * Format-agnostic upsert/remove for a config that stores entries in an object\n * map at config[rootKey][entryId]. Extracted from BaseAdapter.upsertServerInJson\n * / removeServerFromJson so JSON today (and other formats via a Codec later)\n * share ONE create/skip/update + overwrite/malformed-guard orchestration. The\n * JSON binding in BaseAdapter is a byte-identical wrapper over this.\n */\n\n/** The only format-specific surface: parse a config file to a record and\n * serialize it back. parse() fail-softs to null (absent or unparseable). */\nexport interface ObjectMapCodec {\n parse(path: string): Record<string, unknown> | null;\n serialize(path: string, data: Record<string, unknown>, dryRun: boolean): void;\n /** Detection half of the overwrite guard: present, non-empty, still unparseable. */\n isPresentButUnparseable(path: string): boolean;\n}\n\n/** What to do when config[rootKey] is present but the wrong type (hand-edited to\n * an array / primitive). \"warn-skip\" leaves the user's value untouched (the JSON\n * server/hook policy); \"coerce\" replaces it with a fresh {} and proceeds (the\n * TOML/YAML host policy, used by a later follow-up). */\nexport type MalformedRootPolicy = \"warn-skip\" | \"coerce\";\n\n/** True when a PRESENT value is not a plain object map (array / primitive).\n * null/undefined is NOT malformed — that is the well-formed \"absent\" case the\n * engine creates fresh. */\nexport function isMalformedRootValue(value: unknown): boolean {\n return value != null && (typeof value !== \"object\" || Array.isArray(value));\n}\n\nexport interface ObjectMapUpsertOptions {\n codec: ObjectMapCodec;\n rootKey: string;\n platform: string;\n configPath: string;\n entryId: string;\n entry: unknown;\n dryRun?: boolean;\n policy?: MalformedRootPolicy;\n}\n\nexport interface ObjectMapRemoveOptions {\n codec: ObjectMapCodec;\n rootKey: string;\n platform: string;\n configPath: string;\n entryId: string;\n dryRun?: boolean;\n policy?: MalformedRootPolicy;\n}\n\nfunction unparseableWarn(platform: string, configPath: string): ChangeRecord {\n return {\n platform: platform as ChangeRecord[\"platform\"],\n action: \"warn\",\n path: configPath,\n detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`,\n };\n}\n\nfunction malformedWarn(platform: string, rootKey: string, configPath: string): ChangeRecord {\n return {\n platform: platform as ChangeRecord[\"platform\"],\n action: \"warn\",\n path: configPath,\n detail: `existing \"${rootKey}\" in ${configPath} is not an object map; left untouched (fix it, then re-run)`,\n };\n}\n\nexport function upsertInObjectMap(o: ObjectMapUpsertOptions): ChangeRecord {\n const policy = o.policy ?? \"warn-skip\";\n if (o.codec.isPresentButUnparseable(o.configPath)) {\n return unparseableWarn(o.platform, o.configPath);\n }\n const cfg = o.codec.parse(o.configPath) ?? {};\n if (isMalformedRootValue(cfg[o.rootKey])) {\n if (policy === \"warn-skip\") return malformedWarn(o.platform, o.rootKey, o.configPath);\n cfg[o.rootKey] = {}; // coerce\n }\n const bucket = (cfg[o.rootKey] ?? (cfg[o.rootKey] = {})) as Record<string, unknown>;\n const before = JSON.stringify(bucket[o.entryId]);\n const after = JSON.stringify(o.entry);\n let action: ChangeRecord[\"action\"];\n if (before === undefined) action = \"create\";\n else if (before === after) action = \"skip\";\n else action = \"update\";\n if (action !== \"skip\") {\n bucket[o.entryId] = o.entry;\n o.codec.serialize(o.configPath, cfg, o.dryRun ?? false);\n }\n return {\n platform: o.platform as ChangeRecord[\"platform\"],\n action,\n path: o.configPath,\n detail: `${o.rootKey}.${o.entryId}`,\n };\n}\n\nexport function removeFromObjectMap(o: ObjectMapRemoveOptions): ChangeRecord {\n const policy = o.policy ?? \"warn-skip\";\n if (o.codec.isPresentButUnparseable(o.configPath)) {\n return unparseableWarn(o.platform, o.configPath);\n }\n const cfg = o.codec.parse(o.configPath);\n if (cfg && isMalformedRootValue(cfg[o.rootKey])) {\n if (policy === \"warn-skip\") return malformedWarn(o.platform, o.rootKey, o.configPath);\n // coerce: a malformed root holds nothing of ours to remove → absent skip\n return {\n platform: o.platform as ChangeRecord[\"platform\"],\n action: \"skip\",\n path: o.configPath,\n detail: `${o.rootKey}.${o.entryId} absent`,\n };\n }\n const bucket = cfg?.[o.rootKey] as Record<string, unknown> | undefined;\n if (!cfg || !bucket || !(o.entryId in bucket)) {\n return {\n platform: o.platform as ChangeRecord[\"platform\"],\n action: \"skip\",\n path: o.configPath,\n detail: `${o.rootKey}.${o.entryId} absent`,\n };\n }\n delete bucket[o.entryId];\n o.codec.serialize(o.configPath, cfg, o.dryRun ?? false);\n return {\n platform: o.platform as ChangeRecord[\"platform\"],\n action: \"remove\",\n path: o.configPath,\n detail: `${o.rootKey}.${o.entryId}`,\n };\n}\n","/**\n * adapters/base — shared adapter foundation.\n *\n * Provides JSON config IO, settings backup, and a default doctor. Per-platform\n * specifics (paths, capabilities, native render format, runtime parse/format)\n * are abstract and implemented by each adapter. TOML/YAML adapters override the\n * JSON helpers entirely.\n */\n\nimport {\n copyFileSync,\n existsSync,\n readdirSync,\n readFileSync,\n rmSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport type { Dirent } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\n\nimport type {\n ChangeRecord,\n DetectedPlatform,\n DiagnosticResult,\n HealthCheck,\n HookParadigm,\n PlatformCapabilities,\n PlatformId,\n PlatformMemoryOverride,\n} from \"../core/types.js\";\nimport { backupsDir, ensureDir } from \"../core/paths.js\";\nimport { parseJsonc } from \"../core/jsonc.js\";\nimport {\n isMalformedRootValue as isMalformedRootValueFn,\n removeFromObjectMap,\n upsertInObjectMap,\n type ObjectMapCodec,\n} from \"../core/object-map.js\";\nimport { removeFromArray, upsertInArray } from \"../core/hook-array.js\";\nimport { buildHomeBinActionCommand, buildHomeBinHookCommand } from \"../core/spawn.js\";\nimport {\n MEMORY_CONTENT_SOFT_BUDGET_BYTES,\n listManagedBlocks,\n loadMemoryLedger,\n recordMemoryTarget,\n removeManagedBlocksFile,\n saveMemoryLedger,\n upsertManagedBlockFile,\n} from \"../core/managed-block.js\";\nimport { renderFrontmatterMd } from \"./claude-code/render.js\";\nimport type { Adapter, InstallContext, MemoryTarget } from \"./spi.js\";\n\n/** Content-surface kinds with BaseAdapter default install/uninstall handling. */\ntype ContentSurface =\n | \"commands\"\n | \"skills\"\n | \"subagents\"\n | \"memory\"\n | \"statusline\"\n | \"actions\";\n\n/**\n * Per-host policy + observables for the shared object-map hook-array merge\n * engine ({@link BaseAdapter.upsertHookEntries} / {@link BaseAdapter.removeHookEntries}).\n * `E` is the host's per-event array element type (e.g. claude-code's\n * `{ matcher, hooks: [...] }`). EVERY observable string + ownership predicate\n * is carried here so the engine body hardcodes none of them and per-host\n * wording never drifts; a future host needing divergent details overrides only\n * its descriptor, not the engine.\n */\nexport interface HookMergeDescriptor<E> {\n /**\n * How to handle a present-but-malformed `hooks` root (an array / primitive\n * instead of an object map) on install:\n * - \"warn-skip\" (DEFAULT): route through {@link BaseAdapter.malformedHookRootSkip}\n * — surface a `warn`, write nothing, leave the user's value untouched. This\n * is byte-identical to the pre-axis engine, so existing hosts that omit the\n * field are unchanged.\n * - \"coerce\": replace a malformed root (and any non-array event bucket) with a\n * fresh object map / array, then proceed — matching the container hosts\n * (codex, goose) whose server path already coerces a malformed root.\n */\n malformedPolicy?: \"warn-skip\" | \"coerce\";\n /**\n * Canonical → host event name. `undefined` = the host has no equivalent for\n * this event → the engine emits a warn (via unmappedWarnDetail) and skips it.\n * Omit the field entirely for identity hosts (claude-code) — no warn path.\n */\n mapEvent?(event: string): string | undefined;\n /** Warn detail for an event mapEvent returns undefined for. Required iff mapEvent can return undefined. */\n unmappedWarnDetail?(event: string): string;\n /** Render one host entry from the event name, declared matcher, and home-bin command. */\n renderEntry(event: string, matcher: string, command: string): E;\n /** Upsert find: does `entry` already carry THIS specific command (idempotency check)? */\n entryOwnsCommand(entry: E, command: string): boolean;\n /**\n * Uninstall whole-entry predicate (FLAT hosts): does `entry` belong to us?\n * NESTED hosts supplying {@link stripInner} may return a never-true predicate —\n * the engine then only ever invokes `stripInner`. Bound to `ctx` because\n * ownership is connector-generic (ANY of our commands), wider than the\n * install-time specific-command check. The `event` param lets EVENT-SPECIFIC\n * hosts (codex, whose ownership is per-event by construction) match the\n * canonical event being stripped; hosts whose ownership is event-independent\n * (claude-code/droid/cursor/qwen) ignore it — a `(ctx) => …` arrow is assignable\n * to this 2-param type (fewer params is fine in TS), so they need no change.\n */\n ownsEntryForRemove(ctx: InstallContext, event: string): (entry: E) => boolean;\n /**\n * Uninstall inner-strip (NESTED hosts): strip our owned inner commands from\n * `entry`, returning the rebuilt entry (`null` when fully emptied) and the\n * count of inner commands removed. Omit for FLAT hosts.\n */\n stripInner?(ctx: InstallContext): (entry: E) => { next: E | null; removed: number };\n /** Idempotent-reinstall skip detail, e.g. `hooks.<event> already registered`. */\n skipDetail(event: string): string;\n /** Uninstall remove detail, e.g. `hooks.<event> (<n>)`. */\n removeDetail(event: string, removed: number): string;\n /** Uninstall skip detail when no hooks section is present at all. */\n absentDetail: string;\n /** Uninstall skip detail when a hooks section exists but nothing matched. */\n noMatchDetail: string;\n /**\n * Uninstall: the exact event keys to scan, in order. Omit (default) to scan\n * every key actually present (`Object.keys(hooks)`). EVENT-SPECIFIC hosts whose\n * ownership predicate rebuilds a per-event command (codex) MUST pin their fixed\n * event set here so a home-bin command hand-placed under a foreign event key is\n * left untouched — byte-identical to a fixed-event-list uninstall loop.\n */\n removeEventKeys?: readonly string[];\n /** Optional post-mutation hook on the whole parsed file, before the single write. */\n onMutate?(file: Record<string, unknown>): void;\n /**\n * Native-pass ownership find (EXACT command match, vs the normalized\n * entryOwnsCommand). Set only when a `native` list is passed.\n */\n nativeOwnsCommand?(entry: E, command: string): boolean;\n /** Native-pass skip detail, e.g. `hooks.<event> (native) already registered`. */\n nativeSkipDetail?(event: string): string;\n /** Native-pass create/update detail, e.g. `hooks.<event> (native)`. */\n nativeMutateDetail?(event: string): string;\n}\n\n/**\n * One resolved action trigger for an EMITTER adapter: the host-native trigger\n * the adapter writes runs `command`, which re-enters the home binary at the\n * `action` verb. `label`/`description` fall back to the action id when the\n * connector declared none. Produced by {@link BaseAdapter.actionTriggers}.\n */\nexport interface ActionTrigger {\n /** The action id — also the per-action file/key name on most hosts. */\n id: string;\n /** Display label (action.description ?? action.id). */\n label: string;\n /** One-line description (action.description ?? action.id). */\n description: string;\n /** `<homeBin> action <host> <id> --connector <connectorId>` (the verb the host runs). */\n command: string;\n}\n\n/**\n * Documented USER-scope AGENTS.md locations, per host (the AGENTS.md-first\n * research matrix). Only hosts with a REAL user/global AGENTS.md file are\n * listed. Hosts whose documented user-scope memory lives in a DIFFERENT file\n * (qwen-code ~/.qwen/QWEN.md, goose .goosehints, kilo/roo/kiro rules dirs,\n * copilot-cli copilot-instructions.md) override memoryTargets in-adapter;\n * hosts whose user rules are app/UI/cloud-managed (warp Drive, trae,\n * jetbrains/cursor settings UI) deliberately have no row — user-scope memory\n * there reports the standard skip-warn (the JetBrains-MCP precedent), never a\n * write into a file the host ignores.\n */\nconst AGENTS_MD_USER_PATHS: Partial<Record<PlatformId, () => MemoryTarget>> = {\n // codex resolves its own user path in-adapter ($CODEX_HOME with ~ expansion\n // + the AGENTS.override.md shadow probe) — deliberately no row here.\n zed: () => ({\n path:\n process.platform === \"win32\" && process.env.APPDATA\n ? join(process.env.APPDATA, \"Zed\", \"AGENTS.md\")\n : join(homedir(), \".config\", \"zed\", \"AGENTS.md\"),\n reason: \"zed personal instructions file\",\n }),\n amp: () => ({\n path: join(homedir(), \".config\", \"amp\", \"AGENTS.md\"),\n reason: \"amp user-scope AGENTS.md\",\n }),\n mux: () => ({\n path: join(homedir(), \".mux\", \"AGENTS.md\"),\n reason: \"mux global AGENTS.md\",\n }),\n pi: () => ({\n path: join(homedir(), \".pi\", \"agent\", \"AGENTS.md\"),\n reason: \"pi global AGENTS.md\",\n }),\n droid: () => ({\n path: join(homedir(), \".factory\", \"AGENTS.md\"),\n reason: \"droid (Factory) personal AGENTS.md\",\n }),\n opencode: () => ({\n path: join(homedir(), \".config\", \"opencode\", \"AGENTS.md\"),\n reason: \"opencode global AGENTS.md\",\n }),\n antigravity: () => ({\n path: join(homedir(), \".gemini\", \"AGENTS.md\"),\n reason: \"antigravity global rules (~/.gemini/AGENTS.md, shared tree)\",\n }),\n \"antigravity-cli\": () => ({\n path: join(homedir(), \".gemini\", \"AGENTS.md\"),\n reason: \"agy shares the IDE's ~/.gemini tree (idempotent upsert dedupes)\",\n }),\n omp: () => ({\n path: join(homedir(), \".omp\", \"agent\", \"AGENTS.md\"),\n reason: \"omp global AGENTS.md (pi-parity; verify for your version)\",\n }),\n};\n\nexport abstract class BaseAdapter implements Adapter {\n abstract readonly id: PlatformId;\n abstract readonly name: string;\n abstract readonly paradigm: HookParadigm;\n abstract readonly capabilities: PlatformCapabilities;\n\n abstract detectInstalled(projectDir: string): DetectedPlatform;\n abstract getConfigDir(ctx: InstallContext): string;\n abstract getServerConfigPath(ctx: InstallContext): string;\n abstract getHookConfigPath(ctx: InstallContext): string;\n abstract installServer(ctx: InstallContext): ChangeRecord[];\n abstract uninstallServer(ctx: InstallContext): ChangeRecord[];\n abstract installHooks(ctx: InstallContext): ChangeRecord[];\n abstract uninstallHooks(ctx: InstallContext): ChangeRecord[];\n\n // ── Content surfaces (commands / skills / subagents) ─────────────────────\n // CONCRETE (overridable) defaults so every adapter inherits all six methods\n // and the installer can call them without optional-chaining. Each returns a\n // single skip when the connector declares no entries of that surface, else a\n // single \"warn\" — mirroring the mcp-only hook handling. Supporting adapters\n // override only the surfaces they honor.\n\n installCommands(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"commands\", ctx.connector.commands.length);\n }\n uninstallCommands(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"commands\", ctx.connector.commands.length);\n }\n installSkills(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"skills\", ctx.connector.skills.length);\n }\n uninstallSkills(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"skills\", ctx.connector.skills.length);\n }\n installSubagents(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"subagents\", ctx.connector.subagents.length);\n }\n uninstallSubagents(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"subagents\", ctx.connector.subagents.length);\n }\n\n // ── Memory surface (managed marker blocks; AGENTS.md-first) ───────────────\n // ONE generic implementation for every supporting host: the per-adapter\n // `memoryTargets()` hook resolves WHERE to write (AGENTS.md defaults below;\n // claude-code → CLAUDE.md and gemini-cli → GEMINI.md override it), and the\n // core/managed-block engine performs the surgical, hash-stamped block edit.\n // Unlike commands/skills/subagents these files are SHARED and user-authored —\n // bytes outside this connector's own marker pair are never touched.\n\n /**\n * Resolve the memory/rules file(s) this host actually reads at ctx.scope.\n * Base default (AGENTS.md-first, owner mandate):\n * - an explicit `platforms[<id>].memory.path` override wins;\n * - project scope → `<projectDir>/AGENTS.md` (the agents.md standard);\n * - user scope → the host's documented user-scope AGENTS.md, when one\n * exists ({@link AGENTS_MD_USER_PATHS}); otherwise [] → skip-warn.\n * Exception hosts (claude-code, gemini-cli) override this hook.\n */\n protected memoryTargets(ctx: InstallContext): MemoryTarget[] {\n const override = this.memoryOverride(ctx);\n if (override?.path) {\n const base = ctx.scope === \"project\" ? ctx.projectDir : homedir();\n return [\n {\n path: isAbsolute(override.path) ? override.path : join(base, override.path),\n reason: `platforms.${this.id}.memory.path override`,\n },\n ];\n }\n if (ctx.scope === \"project\") {\n return [\n { path: join(ctx.projectDir, \"AGENTS.md\"), reason: \"AGENTS.md standard (project root)\" },\n ];\n }\n if (ctx.scope === \"user\") {\n const user = AGENTS_MD_USER_PATHS[this.id]?.();\n return user ? [user] : [];\n }\n return [];\n }\n\n /** The per-platform memory override object, when one is declared. */\n protected memoryOverride(ctx: InstallContext): PlatformMemoryOverride | undefined {\n const o = ctx.connector.platforms[this.id]?.memory;\n return o && typeof o === \"object\" ? o : undefined;\n }\n\n installMemory(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n // `?? []` tolerates pre-resolved connectors from versions before the\n // memory surface existed (the installer applies the same guard).\n const entries = connector.memory ?? [];\n if (connector.platforms[this.id]?.memory === false) {\n return [{ platform: this.id, action: \"skip\", detail: `memory disabled for ${this.id}` }];\n }\n if (entries.length === 0) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no memory\" }];\n }\n if (!(this.capabilities.supportsMemory ?? false)) {\n return this.unsupportedSurface(ctx, \"memory\", entries.length);\n }\n\n const changes: ChangeRecord[] = [];\n const override = this.memoryOverride(ctx);\n if (override?.mode && this.id !== \"claude-code\") {\n changes.push({\n platform: this.id,\n action: \"warn\",\n detail: `memory.mode is claude-code-only; ignored on ${this.id}`,\n });\n }\n if (ctx.scope !== \"project\" && ctx.scope !== \"user\") {\n // system/profile/managed memory files are admin-owned — out of scope (v1).\n changes.push({\n platform: this.id,\n action: \"warn\",\n detail:\n `memory not supported at ${ctx.scope} scope on ${this.id} ` +\n `(managed/admin memory files are out of scope); ${entries.length} skipped`,\n });\n return changes;\n }\n const targets = this.memoryTargets(ctx);\n if (targets.length === 0) {\n changes.push({\n platform: this.id,\n action: \"warn\",\n detail:\n `no ${ctx.scope}-scope memory file on ${this.id} ` +\n `(user rules are app/UI-managed or undocumented); ${entries.length} skipped`,\n });\n return changes;\n }\n changes.push(...this.writeMemoryBlocks(ctx, targets));\n return changes;\n }\n\n /**\n * Upsert every declared memory entry's block into every resolved target and\n * record the ownership ledger rows (`connectorDir(id)/memory-state.json`).\n * Shared by the base implementation and the exception-host overrides.\n */\n protected writeMemoryBlocks(ctx: InstallContext, targets: MemoryTarget[]): ChangeRecord[] {\n const changes: ChangeRecord[] = [];\n const ledger = loadMemoryLedger(ctx.connector.id);\n let ledgerMutated = false;\n\n for (const target of targets) {\n for (const entry of ctx.connector.memory ?? []) {\n const blockId = `${ctx.connector.id}/${entry.name ?? \"memory\"}`;\n const res = upsertManagedBlockFile(target.path, {\n blockId,\n connectorId: ctx.connector.id,\n content: entry.content,\n ...(target.commentStyle ? { commentStyle: target.commentStyle } : {}),\n force: ctx.force ?? false,\n dryRun: ctx.dryRun,\n });\n changes.push({\n platform: this.id,\n action: res.action,\n path: target.path,\n detail: `memory: ${res.detail} — ${target.reason}`,\n });\n if (res.backupPath) {\n changes.push({\n platform: this.id,\n action: \"create\",\n path: res.backupPath,\n detail: \"backed up memory file before destructive change\",\n });\n }\n const bytes = Buffer.byteLength(entry.content, \"utf8\");\n if (bytes > MEMORY_CONTENT_SOFT_BUDGET_BYTES) {\n changes.push({\n platform: this.id,\n action: \"warn\",\n path: target.path,\n detail:\n `memory entry \"${entry.name ?? \"memory\"}\" is ${bytes} bytes ` +\n `(soft budget ${MEMORY_CONTENT_SOFT_BUDGET_BYTES}); this file is injected into ` +\n `every prompt — keep guidance terse`,\n });\n }\n if (res.action !== \"warn\") {\n recordMemoryTarget(ledger, {\n platform: this.id,\n scope: ctx.scope,\n path: target.path,\n blockId,\n createdFile: res.createdFile,\n hash: res.hash,\n });\n ledgerMutated = true;\n }\n }\n // Per-FILE budget (e.g. codex caps combined project docs at 32 KiB —\n // budgetBytes ≈ 28 KiB leaves headroom): warn when the whole target file\n // (user content + every block) outgrows what the host will actually load.\n if (target.budgetBytes !== undefined && !ctx.dryRun && existsSync(target.path)) {\n const fileBytes = statSync(target.path).size;\n if (fileBytes > target.budgetBytes) {\n changes.push({\n platform: this.id,\n action: \"warn\",\n path: target.path,\n detail:\n `memory file is ${fileBytes} bytes — over this host's ~${target.budgetBytes}-byte ` +\n `budget; the host may truncate or drop it`,\n });\n }\n }\n }\n\n if (ledgerMutated && !ctx.dryRun) saveMemoryLedger(ctx.connector.id, ledger);\n return changes;\n }\n\n uninstallMemory(ctx: InstallContext): ChangeRecord[] {\n const { connector } = ctx;\n if (!(this.capabilities.supportsMemory ?? false)) {\n // Nothing could ever have been written by a non-supporting adapter.\n return this.unsupportedSurface(ctx, \"memory\", (connector.memory ?? []).length);\n }\n\n // Candidate files = union of (a) memoryTargets recomputed NOW (probe re-run\n // — catches teammate machines with no ledger; the markers in the committed\n // file are the source of truth) and (b) ledger rows for this platform from\n // EVERY scope (catches a scope flag that differs from install time).\n const ledger = loadMemoryLedger(connector.id);\n const mine = ledger.targets.filter((t) => t.platform === this.id);\n const candidates = new Map<string, { createdFile: boolean }>();\n if (ctx.scope === \"project\" || ctx.scope === \"user\") {\n for (const t of this.memoryTargets(ctx)) {\n candidates.set(t.path, { createdFile: false });\n }\n }\n for (const row of mine) {\n const existing = candidates.get(row.path);\n candidates.set(row.path, { createdFile: (existing?.createdFile ?? false) || row.createdFile });\n }\n if (candidates.size === 0) {\n return [\n { platform: this.id, action: \"skip\", detail: `no memory targets to clean on ${this.id}` },\n ];\n }\n\n // Prefix scan (`<connectorId>/`), NOT the declared entry list: renamed or\n // removed entries and stale blocks from older versions are reclaimed too.\n const changes: ChangeRecord[] = [];\n for (const [path, { createdFile }] of candidates) {\n const results = removeManagedBlocksFile(\n path,\n { blockIdPrefix: `${connector.id}/` },\n { dryRun: ctx.dryRun, deleteFileIfCreated: createdFile },\n );\n for (const r of results) {\n changes.push({\n platform: this.id,\n action: r.action,\n path: r.path,\n detail: `memory: ${r.detail}`,\n });\n if (r.backupPath) {\n changes.push({\n platform: this.id,\n action: \"create\",\n path: r.backupPath,\n detail: \"backed up memory file before block removal\",\n });\n }\n }\n }\n\n // Prune this connector's ledger rows for this platform (the file itself is\n // deleted when no rows remain). Markers stay the uninstall source of truth.\n if (!ctx.dryRun && mine.length > 0) {\n ledger.targets = ledger.targets.filter((t) => t.platform !== this.id);\n saveMemoryLedger(connector.id, ledger);\n }\n return changes;\n }\n\n /**\n * Doctor: per-ledger-row memory state for this platform — file present,\n * block present, and recorded-vs-actual inner hash (drift → warn, the\n * user edited inside our block; sync reports the same drift and never\n * clobbers it without --force).\n */\n protected memoryDiagnostics(ctx: InstallContext): DiagnosticResult[] {\n const results: DiagnosticResult[] = [];\n const rows = loadMemoryLedger(ctx.connector.id).targets.filter(\n (t) => t.platform === this.id,\n );\n for (const row of rows) {\n const check = `${this.name}: memory block ${row.blockId}`;\n if (!existsSync(row.path)) {\n results.push({\n check,\n status: \"warn\",\n message: `memory file missing: ${row.path}`,\n fix: \"agent-connector install (sync) will re-create the block\",\n fixable: true,\n });\n continue;\n }\n let raw: string;\n try {\n raw = readFileSync(row.path, \"utf8\");\n } catch {\n results.push({ check, status: \"warn\", message: `cannot read ${row.path}` });\n continue;\n }\n const block = listManagedBlocks(raw).find((b) => b.blockId === row.blockId);\n if (!block) {\n results.push({\n check,\n status: \"warn\",\n message: `managed block not found in ${row.path}`,\n fix: \"agent-connector install (sync) will re-append it\",\n fixable: true,\n });\n } else if (block.drifted) {\n results.push({\n check,\n status: \"warn\",\n message:\n `user-edited: inner content hash ${block.actualHash} differs from recorded ` +\n `${block.recordedHash} in ${row.path}; sync leaves it intact (use --force to overwrite)`,\n });\n } else {\n results.push({ check, status: \"pass\", message: `intact in ${row.path}` });\n }\n // Shadow-flip probe: re-run memoryTargets NOW for the row's scope — if\n // the host's resolution moved (a WARP.md / AGENTS.override.md / .rules /\n // .hermes.md appeared, gemini context.fileName changed), the written\n // block sits in a file the host no longer reads. The #1 wrong-file\n // risk; doctor is the detection layer.\n // (`_shared/` bridge rows deliberately live OUTSIDE memoryTargets —\n // e.g. claude-code's @AGENTS.md import in CLAUDE.md — skip those.)\n if (\n !row.blockId.startsWith(\"_shared/\") &&\n row.scope === ctx.scope &&\n (ctx.scope === \"project\" || ctx.scope === \"user\")\n ) {\n const current = this.memoryTargets(ctx);\n if (current.length > 0 && !current.some((t) => t.path === row.path)) {\n results.push({\n check,\n status: \"warn\",\n message:\n `${this.id} now resolves its ${row.scope}-scope memory file to ` +\n `${current[0]!.path} (${current[0]!.reason}) — the block in ${row.path} ` +\n `may no longer be read`,\n fix: \"agent-connector install (sync) re-probes and writes the file the host reads now\",\n fixable: true,\n });\n }\n }\n }\n return results;\n }\n\n // ── Declarative host-config key patches (configPatch) ─────────────────────\n // CONCRETE (overridable) defaults, mirroring the content surfaces above: the\n // installer additionally guards these behind capabilities.supportsConfigPatch\n // (the nativeHooks precedent), so these defaults only fire if a capability-\n // flagged adapter forgot to override them — still skip-warn, never silent.\n\n installConfigPatches(ctx: InstallContext): ChangeRecord[] {\n const count = (ctx.connector.platforms[this.id]?.configPatch ?? []).length;\n if (count === 0) {\n return [\n { platform: this.id, action: \"skip\", detail: \"connector declares no configPatch entries\" },\n ];\n }\n return [\n {\n platform: this.id,\n action: \"warn\",\n detail: `configPatch not supported on ${this.id}; ${count} skipped`,\n },\n ];\n }\n\n uninstallConfigPatches(_ctx: InstallContext): ChangeRecord[] {\n // Nothing was ever applied by a non-supporting adapter → nothing to release.\n return [];\n }\n\n // ── Statusline surface (a HUD/status line) ────────────────────────────────\n // CONCRETE (overridable) defaults, mirroring the content surfaces: the\n // installer calls these unconditionally (BaseAdapter defines them), and they\n // self-skip when the connector declares no statusline, else skip-warn on a\n // non-supporting host. Only claude-code overrides them today.\n\n installStatusline(ctx: InstallContext): ChangeRecord[] {\n if (ctx.connector.statusline == null) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no statusline\" }];\n }\n if (ctx.connector.platforms[this.id]?.statusline === false) {\n return [{ platform: this.id, action: \"skip\", detail: `statusline disabled for ${this.id}` }];\n }\n if (!(this.capabilities.supportsStatusline ?? false)) {\n return this.unsupportedSurface(ctx, \"statusline\", 1);\n }\n // A supporting adapter must override this; the default is the same skip-warn\n // safety net the configPatch defaults provide (never silent).\n return this.unsupportedSurface(ctx, \"statusline\", 1);\n }\n\n uninstallStatusline(ctx: InstallContext): ChangeRecord[] {\n if (ctx.connector.statusline == null) {\n return [{ platform: this.id, action: \"skip\", detail: \"connector declares no statusline\" }];\n }\n if (!(this.capabilities.supportsStatusline ?? false)) {\n // Nothing could ever have been written by a non-supporting adapter.\n return this.unsupportedSurface(ctx, \"statusline\", 1);\n }\n return this.unsupportedSurface(ctx, \"statusline\", 1);\n }\n\n // ── Action surface (a user-invokable action) ──────────────────────────────\n // CONCRETE (overridable) defaults, mirroring the statusline pair. v1 ships\n // ONLY the dispatch backbone: there is no verifiable host affordance to emit,\n // so EVERY host honestly skip-warns and NO adapter overrides these. (`?? []`\n // tolerates pre-resolved connectors from versions before the action surface\n // existed, exactly like installMemory.)\n\n installActions(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"actions\", (ctx.connector.actions ?? []).length);\n }\n uninstallActions(ctx: InstallContext): ChangeRecord[] {\n return this.unsupportedSurface(ctx, \"actions\", (ctx.connector.actions ?? []).length);\n }\n\n /**\n * Shared, surface-agnostic helper for the action EMITTERS: yield one\n * {@link ActionTrigger} per declared action, each carrying the home-bin verb\n * command the host trigger must run\n * (`<homeBin> action <host> <actionId> --connector <id>`, via\n * {@link buildHomeBinActionCommand}) plus a `label`/`description` that fall\n * back to the action id. Each supporting adapter iterates this and writes its\n * host-native trigger (hermes quick_commands / warp workflow / droid exec\n * file) — the per-host file shape is NOT this helper's concern, only the\n * common (id, label, description, command) tuple. Callers own the standard\n * guards (platforms[id].actions === false / empty actions).\n */\n protected actionTriggers(ctx: InstallContext): ActionTrigger[] {\n return (ctx.connector.actions ?? []).map((action) => ({\n id: action.id,\n label: action.description ?? action.id,\n description: action.description ?? action.id,\n command: buildHomeBinActionCommand(\n ctx.homeBinPath,\n this.id,\n action.id,\n ctx.connector.id,\n ),\n }));\n }\n\n /**\n * Default response for a content surface this platform cannot honor: a single\n * skip when the connector declares none of that surface, else a single \"warn\"\n * ChangeRecord noting how many were skipped. Mirrors mcp-only hook handling.\n */\n protected unsupportedSurface(\n _ctx: InstallContext,\n surface: ContentSurface,\n count: number,\n ): ChangeRecord[] {\n if (count === 0) {\n return [{ platform: this.id, action: \"skip\", detail: `connector declares no ${surface}` }];\n }\n return [\n {\n platform: this.id,\n action: \"warn\",\n detail: `${surface} not supported on ${this.id}; ${count} skipped`,\n },\n ];\n }\n\n // ── Content-file helpers (used by surface-supporting adapters) ────────────\n\n /**\n * Write a content file idempotently: \"skip\" when the existing bytes are\n * already identical, else \"create\"/\"update\". Creates parent dirs (mkdir -p).\n * Honors dryRun (computes the action but writes nothing).\n */\n protected writeContentFile(path: string, contents: string, dryRun: boolean): ChangeRecord {\n let action: ChangeRecord[\"action\"];\n if (!existsSync(path)) {\n action = \"create\";\n } else {\n let current: string | null = null;\n try {\n current = readFileSync(path, \"utf8\");\n } catch {\n current = null;\n }\n action = current === contents ? \"skip\" : \"update\";\n }\n if (action !== \"skip\" && !dryRun) {\n ensureDir(dirname(path));\n writeFileSync(path, contents, \"utf8\");\n }\n return { platform: this.id, action, path, detail: basename(path) };\n }\n\n /**\n * Remove a content file we wrote; \"skip\" when already absent. Honors dryRun.\n * Uses rmSync with `force` to also tolerate a path that is a directory tree\n * (e.g. a skill folder removed by the supporting adapter).\n */\n protected removeContentFile(path: string, dryRun: boolean): ChangeRecord {\n if (!existsSync(path)) {\n return { platform: this.id, action: \"skip\", path, detail: `${basename(path)} absent` };\n }\n if (!dryRun) {\n rmSync(path, { recursive: true, force: true });\n }\n return { platform: this.id, action: \"remove\", path, detail: basename(path) };\n }\n\n /**\n * Defense-in-depth path containment: resolve `rel` against `baseDir` and\n * return the absolute path ONLY when it stays inside `baseDir`. Returns null\n * when `rel` escapes (absolute path, `..` traversal, etc.) so a caller can\n * skip-and-warn rather than write/delete outside the surface dir. This is the\n * runtime backstop behind the config-time validation in normalizeSkills.\n */\n protected resolveWithin(baseDir: string, rel: string): string | null {\n const base = resolve(baseDir);\n const target = resolve(base, rel);\n if (target === base) return null; // rel resolved to the dir itself (e.g. \"\" / \".\")\n const rind = relative(base, target);\n // Outside when the relative path climbs out (`..`) or is itself absolute.\n if (rind === \"\" || rind.startsWith(\"..\") || resolve(base, rind) !== target) {\n return null;\n }\n if (rind === \"..\" || rind.startsWith(`..${sep}`)) return null;\n return target;\n }\n\n /**\n * Recursively report whether a directory tree contains any regular FILE (vs\n * only empty/nested-empty subdirectories). Symlinks count as files (we never\n * recurse through them). Used by removeDirIfEmpty to decide if a tree is safe\n * to drop. An unreadable entry is treated as \"a file is present\" (conservative).\n */\n private dirTreeHasFile(dir: string): boolean {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return true; // unreadable → assume it holds data; do not remove\n }\n for (const e of entries) {\n if (e.isDirectory()) {\n if (this.dirTreeHasFile(join(dir, e.name))) return true;\n } else {\n return true; // a file / symlink / special → the tree is not \"empty\"\n }\n }\n return false;\n }\n\n /**\n * Remove a directory ONLY when its tree holds NO regular files — i.e. it is\n * empty or contains only (nested) empty directories, such as the intermediate\n * dirs our own nested skill resources created. Replaces the unconditional\n * `rm -rf <dir>` that uninstallSkills used to call, which destroyed user-added\n * files / sibling-tool files / shared skill dirs the connector never wrote.\n * When ANY file remains (at any depth) the whole tree is left in place and the\n * skip is noted. Honors dryRun (computes the action but removes nothing). A\n * missing dir is a no-op \"skip\".\n */\n protected removeDirIfEmpty(dir: string, dryRun: boolean): ChangeRecord {\n if (!existsSync(dir)) {\n return { platform: this.id, action: \"skip\", path: dir, detail: `${basename(dir)} absent` };\n }\n let stat: ReturnType<typeof statSync>;\n try {\n stat = statSync(dir);\n } catch {\n return {\n platform: this.id,\n action: \"skip\",\n path: dir,\n detail: `${basename(dir)} unreadable; left in place`,\n };\n }\n if (!stat.isDirectory()) {\n return {\n platform: this.id,\n action: \"skip\",\n path: dir,\n detail: `${basename(dir)} not a directory; left in place`,\n };\n }\n if (this.dirTreeHasFile(dir)) {\n return {\n platform: this.id,\n action: \"skip\",\n path: dir,\n detail: `${basename(dir)} still holds files we did not write; left in place`,\n };\n }\n if (!dryRun) {\n try {\n // The tree is files-free: only (possibly nested) empty dirs remain, which\n // are ours / inert. A recursive remove drops them without touching data.\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // Lost a race or perms — non-fatal; report as a skip.\n return {\n platform: this.id,\n action: \"skip\",\n path: dir,\n detail: `${basename(dir)} could not be removed; left in place`,\n };\n }\n }\n return { platform: this.id, action: \"remove\", path: dir, detail: basename(dir) };\n }\n\n /**\n * Render a YAML-frontmatter + markdown body document:\n * \"---\\n\" + <yaml> + \"---\\n\\n\" + <body> + \"\\n\".\n * Delegates to the shared renderer (adapters/claude-code/render) so every\n * content-file path — adapters AND the plugin packager — emits identical bytes.\n */\n protected renderFrontmatterMd(\n frontmatter: Record<string, unknown>,\n body: string,\n ): string {\n return renderFrontmatterMd(frontmatter, body);\n }\n\n // ── JSON config helpers (used by JSON-format adapters) ───────────────────\n\n /**\n * Read + parse a JSON/JSONC config file, returning null when it is absent OR\n * unparseable. Parses TOLERANTLY via parseJsonc (strips // and /* *\\/ comments\n * and trailing commas) so a perfectly valid JSONC file never false-fails to\n * null — that null is what `?? {}` would otherwise turn into a clobbering\n * overwrite of the user's whole config.\n */\n protected readJson<T = Record<string, unknown>>(path: string): T | null {\n if (!existsSync(path)) return null;\n try {\n return parseJsonc<T>(readFileSync(path, \"utf8\"));\n } catch {\n return null;\n }\n }\n\n /**\n * True when `path` exists and holds non-whitespace content but readJson cannot\n * parse it (even after JSONC stripping). The overwrite guard: writing a\n * `{}`-based config over a present-but-broken settings file would silently\n * destroy the user's data, so callers warn-and-skip instead.\n */\n protected isPresentButUnparseable(path: string): boolean {\n if (!existsSync(path)) return false;\n let raw: string;\n try {\n if (statSync(path).size === 0) return false;\n raw = readFileSync(path, \"utf8\");\n } catch {\n return false;\n }\n if (raw.trim() === \"\") return false;\n return this.readJson(path) === null;\n }\n\n protected writeJson(path: string, data: unknown, dryRun = false): void {\n if (dryRun) return;\n ensureDir(dirname(path));\n writeFileSync(path, `${JSON.stringify(data, null, 2)}\\n`, \"utf8\");\n }\n\n /**\n * True when a PRESENT rootKey value is not a plain object map (the user\n * hand-edited it to an array / string / number / boolean). Assigning a server\n * entry into such a value would corrupt the file silently (a named property on\n * an Array is dropped by JSON.stringify) or throw under strict mode (a property\n * write / `in` operator against a primitive), so callers warn-and-skip. A\n * `null`/`undefined` value is NOT malformed — it is the well-formed \"absent\"\n * case the helpers create fresh.\n */\n protected isMalformedRootValue(value: unknown): boolean {\n return isMalformedRootValueFn(value);\n }\n\n /**\n * Warn-skip ChangeRecord when an existing JSON `hooks` config is present-but-\n * malformed (root hand-edited to an array/primitive, or an existing event\n * bucket is a non-array) — installing a hook into it would silently drop the\n * entry (a named property on an Array is dropped by JSON.stringify) or throw\n * under strict mode (a `.push`/property write against a primitive or non-array\n * bucket). Returns null when the hooks config is well-formed, including the\n * absent case the installers create fresh (proceed normally). Mirrors the\n * server-root guard (isMalformedRootValue). All targeted hosts use the Claude-\n * style `{ Event: Matcher[] }` shape, so every present event bucket must be an\n * array; a non-array bucket is the same malformed class as a wrong-typed root.\n */\n protected malformedHookRootSkip(configPath: string, hooksRoot: unknown): ChangeRecord | null {\n if (hooksRoot == null) return null; // absent → installer creates fresh\n if (this.isMalformedRootValue(hooksRoot)) {\n // array / primitive root\n return {\n platform: this.id,\n action: \"warn\",\n path: configPath,\n detail: `existing \"hooks\" in ${configPath} is not an object map; left untouched (fix it, then re-run)`,\n };\n }\n for (const v of Object.values(hooksRoot as Record<string, unknown>)) {\n if (v !== undefined && !Array.isArray(v)) {\n // an existing event bucket is a non-array\n return {\n platform: this.id,\n action: \"warn\",\n path: configPath,\n detail: `existing \"hooks\" in ${configPath} has a non-array event bucket; left untouched (fix it, then re-run)`,\n };\n }\n }\n return null;\n }\n\n // ── Shared object-map hook-array merge engine ────────────────────────────\n // installHooks/uninstallHooks for every json-stdio host that stores hooks as\n // `{ <event>: HookEntry[] }` share ONE create/update/skip + strip/remove\n // orchestration. The host supplies a {@link HookMergeDescriptor} carrying\n // EVERY observable string and ownership predicate — the engine body hardcodes\n // none of them, so per-host wording (skip/remove/absent/no-match details) and\n // FLAT-vs-NESTED removal stay host-controlled and never drift to a lowest-\n // common-denominator phrasing the suites (which assert by action) would miss.\n // The write-gate is the `mutated` bool, matching the migrated host exactly.\n\n /**\n * Install: upsert each `{ event, matcher }` in `pending` into the object-map\n * `hooks` root at `configPath`. Reproduces the canonical host's loop: read\n * `?? {}`, warn-skip on a malformed `hooks` root, then per pending item render\n * the entry, find the owned slot, and create/update/skip. Writes once at the\n * end iff anything mutated.\n *\n * Hosts that fold their `nativeHooks` surface into `pending` (claude-code) pass\n * no `native` arg. Hosts that keep the native surface SEPARATE — verbatim\n * event keys, EXACT-command ownership, a distinct `(native)` detail — pass it\n * as the optional `native` list (cursor); that pass runs after the normalized\n * loop and before the single write. Omitting it is byte-identical to before.\n */\n /**\n * Resolve the per-event hook bucket. With \"coerce\" policy a present-but-non-\n * array bucket (hand-edited) is replaced by a fresh array; otherwise `??=` is\n * used (the warn-skip path already rejected any non-array bucket via\n * malformedHookRootSkip, so `??=` only ever fills the absent case).\n */\n private coerceHookBucket<E>(\n hooks: Record<string, E[]>,\n event: string,\n coerce: boolean,\n ): E[] {\n if (coerce) {\n return Array.isArray(hooks[event]) ? hooks[event] : (hooks[event] = []);\n }\n return (hooks[event] ??= []);\n }\n\n protected upsertHookEntries<E>(\n ctx: InstallContext,\n configPath: string,\n pending: ReadonlyArray<{ event: string; matcher: string }>,\n descriptor: HookMergeDescriptor<E>,\n native?: ReadonlyArray<{ event: string; matcher: string }>,\n ): ChangeRecord[] {\n const file = this.readJson<Record<string, unknown>>(configPath) ?? {};\n const coerce = descriptor.malformedPolicy === \"coerce\";\n let hooks: Record<string, E[]>;\n if (coerce) {\n // COERCE policy: a present-but-malformed `hooks` root (array / primitive,\n // hand-edited) is replaced by a fresh object map; a well-formed object map\n // is kept as-is. Never warn, never throw — matches the container hosts'\n // server-path policy.\n const existing = file.hooks;\n hooks =\n existing && typeof existing === \"object\" && !Array.isArray(existing)\n ? (existing as Record<string, E[]>)\n : ((file.hooks = {}) as Record<string, E[]>);\n } else {\n const skip = this.malformedHookRootSkip(configPath, file.hooks);\n if (skip) return [skip];\n hooks = (file.hooks ??= {}) as Record<string, E[]>;\n }\n\n const changes: ChangeRecord[] = [];\n let mutated = false;\n\n for (const { event, matcher } of pending) {\n const hostEvent = descriptor.mapEvent ? descriptor.mapEvent(event) : event;\n if (hostEvent === undefined) {\n changes.push({\n platform: this.id,\n action: \"warn\",\n path: configPath,\n detail: descriptor.unmappedWarnDetail!(event),\n });\n continue;\n }\n // Command is built from the CANONICAL `event` (NOT hostEvent) — the home\n // binary dispatches on the canonical name; only the host's bucket key,\n // detail, and skipDetail use the mapped `hostEvent`.\n const command = buildHomeBinHookCommand(\n ctx.homeBinPath,\n this.id,\n event,\n ctx.connector.id,\n );\n const entry = descriptor.renderEntry(hostEvent, matcher, command);\n const bucket = this.coerceHookBucket(hooks, hostEvent, coerce);\n const r = upsertInArray(bucket, entry, (e) =>\n descriptor.entryOwnsCommand(e, command),\n );\n\n if (r.action !== \"skip\") {\n hooks[hostEvent] = r.array;\n mutated = true;\n }\n changes.push({\n platform: this.id,\n action: r.action,\n path: configPath,\n detail:\n r.action === \"skip\"\n ? descriptor.skipDetail(hostEvent)\n : `hooks.${hostEvent}`,\n });\n }\n\n // Native pass (json-stdio hosts with a verbatim `nativeHooks` surface):\n // native event-name keys are NOT mapEvent-mapped, are found by EXACT command\n // (so a native key coinciding with a normalized event's mapped key adds a\n // SECOND distinct command rather than clobbering it), and carry the `(native)`\n // detail. Hosts passing no `native` list skip this loop entirely → the\n // normalized-only behavior is byte-identical.\n for (const { event, matcher } of native ?? []) {\n const command = buildHomeBinHookCommand(\n ctx.homeBinPath,\n this.id,\n event,\n ctx.connector.id,\n );\n const entry = descriptor.renderEntry(event, matcher, command);\n const bucket = this.coerceHookBucket(hooks, event, coerce);\n const r = upsertInArray(bucket, entry, (e) =>\n descriptor.nativeOwnsCommand!(e, command),\n );\n if (r.action !== \"skip\") {\n hooks[event] = r.array;\n mutated = true;\n }\n changes.push({\n platform: this.id,\n action: r.action,\n path: configPath,\n detail:\n r.action === \"skip\"\n ? descriptor.nativeSkipDetail!(event)\n : descriptor.nativeMutateDetail!(event),\n });\n }\n\n if (mutated) {\n descriptor.onMutate?.(file);\n this.writeJson(configPath, file, ctx.dryRun);\n }\n return changes;\n }\n\n /**\n * Uninstall: strip owned content from every event bucket in the object-map\n * `hooks` root at `configPath`. NESTED hosts pass `descriptor.stripInner`\n * (strip owned inner commands, drop emptied entries; `removed` = inner\n * commands); FLAT hosts omit it (drop whole owned entries). Reproduces the\n * canonical host: read (NO `?? {}`), `[skip]` (absentDetail) when no hooks\n * section, per-event remove → delete-or-replace bucket, write once iff\n * mutated, and a single `[skip]` (noMatchDetail) when nothing matched.\n */\n protected removeHookEntries<E>(\n ctx: InstallContext,\n configPath: string,\n descriptor: HookMergeDescriptor<E>,\n ): ChangeRecord[] {\n const file = this.readJson<Record<string, unknown>>(configPath);\n const hooks = file?.hooks as Record<string, E[]> | undefined;\n if (!file || !hooks) {\n return [\n {\n platform: this.id,\n action: \"skip\",\n path: configPath,\n detail: descriptor.absentDetail,\n },\n ];\n }\n\n const changes: ChangeRecord[] = [];\n let mutated = false;\n // stripInner is event-independent for current hosts → computed once. The\n // whole-entry ownership predicate is rebuilt PER EVENT so EVENT-SPECIFIC\n // hosts (codex) can match the canonical event being stripped; event-agnostic\n // hosts simply ignore the param.\n const stripInner = descriptor.stripInner?.(ctx);\n\n // Default: scan every present key. EVENT-SPECIFIC hosts pin a fixed event set\n // (descriptor.removeEventKeys) so a foreign-key bucket is never inspected —\n // matching their original fixed-event-list uninstall loop byte-for-byte.\n for (const event of descriptor.removeEventKeys ?? Object.keys(hooks)) {\n const bucket = hooks[event];\n if (!Array.isArray(bucket)) continue;\n\n const ownsEntry = descriptor.ownsEntryForRemove(ctx, event);\n const r = removeFromArray(bucket, ownsEntry, stripInner);\n if (r.removed > 0) {\n if (r.array.length > 0) hooks[event] = r.array;\n else delete hooks[event];\n changes.push({\n platform: this.id,\n action: \"remove\",\n path: configPath,\n detail: descriptor.removeDetail(event, r.removed),\n });\n mutated = true;\n }\n }\n\n if (mutated) this.writeJson(configPath, file, ctx.dryRun);\n if (changes.length === 0) {\n changes.push({\n platform: this.id,\n action: \"skip\",\n path: configPath,\n detail: descriptor.noMatchDetail,\n });\n }\n return changes;\n }\n\n /** Bind BaseAdapter's JSON leaf IO (readJson/writeJson + the unparseable\n * overwrite guard) as a Codec for the object-map engine. */\n private jsonObjectMapCodec(): ObjectMapCodec {\n return {\n parse: (path) => this.readJson<Record<string, unknown>>(path),\n serialize: (path, data, dryRun) => this.writeJson(path, data, dryRun),\n isPresentButUnparseable: (path) => this.isPresentButUnparseable(path),\n };\n }\n\n /**\n * Upsert `entry` at `config[rootKey][serverId]` in a JSON file, creating the\n * file/object as needed. Returns a ChangeRecord describing create/update/skip.\n * Idempotent: an identical entry yields \"skip\".\n */\n protected upsertServerInJson(\n configPath: string,\n rootKey: string,\n serverId: string,\n entry: unknown,\n dryRun = false,\n ): ChangeRecord {\n return upsertInObjectMap({\n codec: this.jsonObjectMapCodec(),\n rootKey,\n platform: this.id,\n configPath,\n entryId: serverId,\n entry,\n dryRun,\n });\n }\n\n /** Remove `config[rootKey][serverId]` from a JSON file. */\n protected removeServerFromJson(\n configPath: string,\n rootKey: string,\n serverId: string,\n dryRun = false,\n ): ChangeRecord {\n return removeFromObjectMap({\n codec: this.jsonObjectMapCodec(),\n rootKey,\n platform: this.id,\n configPath,\n entryId: serverId,\n dryRun,\n });\n }\n\n // ── Backup ───────────────────────────────────────────────────────────────\n\n backupSettings(ctx: InstallContext): string | null {\n const files = [...new Set([this.getServerConfigPath(ctx), this.getHookConfigPath(ctx)])].filter(\n (f) => existsSync(f),\n );\n if (files.length === 0) return null;\n if (ctx.dryRun) return null;\n ensureDir(backupsDir());\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n let last: string | null = null;\n for (const f of files) {\n const dest = join(backupsDir(), `${this.id}-${stamp}-${basename(f)}`);\n copyFileSync(f, dest);\n last = dest;\n }\n return last;\n }\n\n // ── Diagnostics ─────────────────────────────────────────────────────────\n\n getHealthChecks?(ctx: InstallContext): readonly HealthCheck[];\n\n doctor(ctx: InstallContext): DiagnosticResult[] {\n const results: DiagnosticResult[] = [];\n const cfg = this.getServerConfigPath(ctx);\n results.push(\n existsSync(cfg)\n ? { check: `${this.name}: config present`, status: \"pass\", message: cfg }\n : {\n check: `${this.name}: config present`,\n status: \"warn\",\n message: `not found: ${cfg}`,\n fix: `agent-connector install --targets ${this.id}`,\n // Only sync-recoverable when the connector actually declares a\n // server — a re-sync writes the config file only then. For a\n // serverless (memory/configPatch-only) connector the file is never\n // created, so marking it fixable would loop it into \"still failing\".\n fixable: !!ctx.connector.server,\n },\n );\n for (const hc of this.getHealthChecks?.(ctx) ?? []) {\n const r = hc.check();\n results.push({\n check: hc.name,\n status: r.status === \"OK\" ? \"pass\" : \"fail\",\n message: r.detail ?? r.status,\n });\n }\n // Memory-surface checks are ledger-driven: no installed memory → no rows →\n // no noise. Shared by every adapter (the overrides call super.doctor()).\n results.push(...this.memoryDiagnostics(ctx));\n return results;\n }\n}\n","/** Decision for one inner-array upsert. */\nexport type ArrayUpsertAction = \"create\" | \"update\" | \"skip\";\n\nexport interface ArrayUpsertResult<E> {\n array: E[];\n action: ArrayUpsertAction;\n}\n\n/**\n * Upsert one rendered `candidate` into a per-event hook array.\n * - find the FIRST entry the caller OWNS via `ownsEntry`;\n * - hit + JSON-equal → skip (array returned unchanged);\n * - hit + differs → update (replace that slot);\n * - miss → create (push).\n */\nexport function upsertInArray<E>(\n array: E[],\n candidate: E,\n ownsEntry: (entry: E) => boolean,\n): ArrayUpsertResult<E> {\n const idx = array.findIndex(ownsEntry);\n if (idx >= 0) {\n if (JSON.stringify(array[idx]) === JSON.stringify(candidate)) {\n return { array, action: \"skip\" };\n }\n const next = array.slice();\n next[idx] = candidate;\n return { array: next, action: \"update\" };\n }\n return { array: [...array, candidate], action: \"create\" };\n}\n\nexport interface ArrayRemoveResult<E> {\n array: E[];\n removed: number;\n}\n\n/**\n * Remove owned content from a per-event hook array.\n * - FLAT hosts: omit `stripInner` → drop whole entries matching `ownsEntry`;\n * `removed` = whole entries dropped.\n * - NESTED hosts: pass `stripInner` → strip owned inner commands and rebuild,\n * dropping entries left empty; `removed` = inner commands removed.\n */\nexport function removeFromArray<E>(\n array: E[],\n ownsEntry: (entry: E) => boolean,\n stripInner?: (entry: E) => { next: E | null; removed: number },\n): ArrayRemoveResult<E> {\n if (!stripInner) {\n const kept = array.filter((e) => !ownsEntry(e));\n return { array: kept, removed: array.length - kept.length };\n }\n const kept: E[] = [];\n let removed = 0;\n for (const e of array) {\n const r = stripInner(e);\n removed += r.removed;\n if (r.next !== null) kept.push(r.next);\n }\n return { array: kept, removed };\n}\n"],"mappings":"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","names":["kept"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/paths.ts"],"sourcesContent":["/**\n * core/paths — home data-root, stable home binary, and project identity.\n *\n * Operating model (docs/ARCHITECTURE.md §3):\n * • One home data-root holds the single binary + shared telemetry + state.\n * Override with AGENT_CONNECTOR_DATA_DIR (the cross-agent shared-DB key).\n * This relocates FRAMEWORK state only — never platform-native config files.\n * • The home binary lives at a STABLE path so every host's pointer config\n * keeps working across updates (no versioned cache dir → no cache-heal bug).\n * • Per-project data is keyed by a stable project identity (git remote ||\n * normalized absolute path), hashed — not by code location.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join, resolve } from \"node:path\";\n\n/** Framework data-root: AGENT_CONNECTOR_DATA_DIR || ~/.agent-connector. */\nexport function dataRoot(): string {\n const override = process.env.AGENT_CONNECTOR_DATA_DIR;\n if (override && override.trim() !== \"\") return resolve(override);\n return join(homedir(), \".agent-connector\");\n}\n\nexport function connectorsDir(): string {\n return join(dataRoot(), \"connectors\");\n}\nexport function connectorDir(id: string): string {\n return join(connectorsDir(), id);\n}\nexport function backupsDir(): string {\n return join(dataRoot(), \"backups\");\n}\nexport function logsDir(): string {\n return join(dataRoot(), \"logs\");\n}\n\n/** Telemetry store path for the selected backend. */\nexport function telemetryPath(store: \"ndjson\" | \"sqlite\" = \"ndjson\"): string {\n return join(dataRoot(), store === \"sqlite\" ? \"telemetry.db\" : \"telemetry.ndjson\");\n}\n\n/** Stable, OS-correct path to the single home binary pointer. */\nexport function homeBinPath(): string {\n const name = process.platform === \"win32\" ? \"agent-connector.cmd\" : \"agent-connector\";\n return join(dataRoot(), \"bin\", name);\n}\n\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\n/**\n * Write a stable launcher at homeBinPath() that execs the real CLI entry.\n * Avoids symlinks (Windows constraint) and versioned paths. Idempotent.\n *\n * @param cliEntry absolute path to the installed cli.js\n * @param nodePath node runtime to exec with (defaults to current process.execPath)\n * @returns the stable launcher path\n */\nexport function ensureHomeBin(cliEntry: string, nodePath: string = process.execPath): string {\n const binPath = homeBinPath();\n ensureDir(join(dataRoot(), \"bin\"));\n const node = nodePath.replace(/\\\\/g, \"/\");\n const cli = cliEntry.replace(/\\\\/g, \"/\");\n if (process.platform === \"win32\") {\n writeFileSync(binPath, `@echo off\\r\\n\"${node}\" \"${cli}\" %*\\r\\n`, \"utf8\");\n } else {\n writeFileSync(binPath, `#!/bin/sh\\nexec \"${node}\" \"${cli}\" \"$@\"\\n`, \"utf8\");\n chmodSync(binPath, 0o755);\n }\n return binPath;\n}\n\n/** Read remote.origin.url from a project's .git/config without spawning git. */\nfunction gitRemoteUrl(projectDir: string): string | null {\n const cfg = join(projectDir, \".git\", \"config\");\n if (!existsSync(cfg)) return null;\n try {\n const text = readFileSync(cfg, \"utf8\");\n // Find [remote \"origin\"] ... url = X\n const lines = text.split(/\\r?\\n/);\n let inOrigin = false;\n for (const line of lines) {\n const s = line.trim();\n if (s.startsWith(\"[\")) inOrigin = /^\\[remote\\s+\"origin\"\\]$/.test(s);\n else if (inOrigin) {\n const m = s.match(/^url\\s*=\\s*(.+)$/);\n if (m && m[1]) return m[1].trim();\n }\n }\n } catch {\n /* ignore */\n }\n return null;\n}\n\nexport interface ProjectIdentity {\n /** Hashed stable key used to partition telemetry/state. */\n key: string;\n /** Human-readable label (git remote or directory name). */\n label: string;\n /** Absolute project directory. */\n dir: string;\n}\n\n/**\n * Derive a stable project identity. Prefers the git origin URL (stable across\n * clones/locations); falls back to the normalized absolute path.\n */\nexport function projectIdentity(projectDir: string): ProjectIdentity {\n const dir = resolve(projectDir);\n const remote = gitRemoteUrl(dir);\n const basis = remote ?? dir;\n const key = createHash(\"sha256\").update(basis).digest(\"hex\").slice(0, 16);\n return { key, label: remote ?? basename(dir), dir };\n}\n"],"mappings":";;;AAaA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,UAAU,MAAM,eAAe;AAGjC,SAAS,WAAmB;AACjC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAY,SAAS,KAAK,MAAM,GAAI,QAAO,QAAQ,QAAQ;AAC/D,SAAO,KAAK,QAAQ,GAAG,kBAAkB;AAC3C;AAEO,SAAS,gBAAwB;AACtC,SAAO,KAAK,SAAS,GAAG,YAAY;AACtC;AACO,SAAS,aAAa,IAAoB;AAC/C,SAAO,KAAK,cAAc,GAAG,EAAE;AACjC;AACO,SAAS,aAAqB;AACnC,SAAO,KAAK,SAAS,GAAG,SAAS;AACnC;AAMO,SAAS,cAAc,QAA6B,UAAkB;AAC3E,SAAO,KAAK,SAAS,GAAG,UAAU,WAAW,iBAAiB,kBAAkB;AAClF;AAGO,SAAS,cAAsB;AACpC,QAAM,OAAO,QAAQ,aAAa,UAAU,wBAAwB;AACpE,SAAO,KAAK,SAAS,GAAG,OAAO,IAAI;AACrC;AAEO,SAAS,UAAU,KAAmB;AAC3C,MAAI,CAAC,WAAW,GAAG,EAAG,WAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC1D;AAUO,SAAS,cAAc,UAAkB,WAAmB,QAAQ,UAAkB;AAC3F,QAAM,UAAU,YAAY;AAC5B,YAAU,KAAK,SAAS,GAAG,KAAK,CAAC;AACjC,QAAM,OAAO,SAAS,QAAQ,OAAO,GAAG;AACxC,QAAM,MAAM,SAAS,QAAQ,OAAO,GAAG;AACvC,MAAI,QAAQ,aAAa,SAAS;AAChC,kBAAc,SAAS;AAAA,GAAiB,IAAI,MAAM,GAAG;AAAA,GAAY,MAAM;AAAA,EACzE,OAAO;AACL,kBAAc,SAAS;AAAA,QAAoB,IAAI,MAAM,GAAG;AAAA,GAAY,MAAM;AAC1E,cAAU,SAAS,GAAK;AAAA,EAC1B;AACA,SAAO;AACT;AAGA,SAAS,aAAa,YAAmC;AACvD,QAAM,MAAM,KAAK,YAAY,QAAQ,QAAQ;AAC7C,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAC7B,MAAI;AACF,UAAM,OAAO,aAAa,KAAK,MAAM;AAErC,UAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,QAAI,WAAW;AACf,eAAW,QAAQ,OAAO;AACxB,YAAM,IAAI,KAAK,KAAK;AACpB,UAAI,EAAE,WAAW,GAAG,EAAG,YAAW,0BAA0B,KAAK,CAAC;AAAA,eACzD,UAAU;AACjB,cAAM,IAAI,EAAE,MAAM,kBAAkB;AACpC,YAAI,KAAK,EAAE,CAAC,EAAG,QAAO,EAAE,CAAC,EAAE,KAAK;AAAA,MAClC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAeO,SAAS,gBAAgB,YAAqC;AACnE,QAAM,MAAM,QAAQ,UAAU;AAC9B,QAAM,SAAS,aAAa,GAAG;AAC/B,QAAM,QAAQ,UAAU;AACxB,QAAM,MAAM,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACxE,SAAO,EAAE,KAAK,OAAO,UAAU,SAAS,GAAG,GAAG,IAAI;AACpD;","names":[]}
|