mulmoterminal 4.20.0 → 4.22.0
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 +146 -44
- package/common/agentSessionList.ts +6 -3
- package/common/customAgents.ts +2 -2
- package/common/guiMcpAgents.ts +34 -3
- package/common/launchAgent.ts +1 -1
- package/common/rawContentType.ts +85 -0
- package/common/sessionAgent.ts +4 -2
- package/common/toolGroups.ts +17 -1
- package/dist/assets/{AllPackages-CF2GkZ5g-DDT3DNT0.js → AllPackages-CF2GkZ5g-BcuVXmcX.js} +1 -1
- package/dist/assets/{BaseConfiguration-CAwZOAki-DMFLm7MS.js → BaseConfiguration-CAwZOAki-D9nXz-eP.js} +1 -1
- package/dist/assets/{Element-BMI7Nj5w-Df3aPtGa.js → Element-BMI7Nj5w-9E1MzusH.js} +1 -1
- package/dist/assets/{Entities-C8p_0jCn-D0Lwdbj9.js → Entities-C8p_0jCn-aaXtUOdq.js} +1 -1
- package/dist/assets/{FunctionList-YWk54TCy-DRo98GKI.js → FunctionList-YWk54TCy-XBYAozMP.js} +1 -1
- package/dist/assets/{InputJax-C5dj-ntL-ClsWSCFP.js → InputJax-C5dj-ntL-DkjYd-Hz.js} +1 -1
- package/dist/assets/{MathItem-YJvQL-XJ-DGGkricO.js → MathItem-YJvQL-XJ-RKdhqGjF.js} +1 -1
- package/dist/assets/{MmlFactory-DuXYIgx8-DbxqJHx2.js → MmlFactory-DuXYIgx8-Cs82jkFU.js} +1 -1
- package/dist/assets/{Options-oARQeeuT-Uhz5kUU-.js → Options-oARQeeuT-DT1Tx-Lf.js} +1 -1
- package/dist/assets/{OutputJax-DfUZNNCu-DIPvULXk.js → OutputJax-DfUZNNCu-C6-qqEPU.js} +1 -1
- package/dist/assets/{PrioritizedList-6lun1Xqd-SIPrl1H8.js → PrioritizedList-6lun1Xqd-CbjBO_Oc.js} +1 -1
- package/dist/assets/{Styles-BPXeoqzi-BexhgSym.js → Styles-BPXeoqzi-CrUDQm_T.js} +1 -1
- package/dist/assets/{TeXAtom-LHT3KFWH-CWaXrz_w.js → TeXAtom-LHT3KFWH-CAgE6z_w.js} +1 -1
- package/dist/assets/{abnfDiagram-VCTEODGH-DoYNjdBB-Ch3lUdPZ.js → abnfDiagram-VCTEODGH-DoYNjdBB-CYzeMhfw.js} +1 -1
- package/dist/assets/{arc-CqByPKkF-Bo4oy6xz.js → arc-CqByPKkF-CiGsCTiw.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-CRS69XmP-D0hwkUSe.js +1 -0
- package/dist/assets/{architectureDiagram-5GKGNRK7-BhcT6qS5-C8XZ2FRi.js → architectureDiagram-5GKGNRK7-BhcT6qS5-CxYCYh4L.js} +1 -1
- package/dist/assets/{assistive-mml-DXJXho4o-DMHyAOA7.js → assistive-mml-DXJXho4o-Rb2wPxtZ.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-1Cng0Q4K-eUD-wN3t.js → blockDiagram-I7D4REHJ-1Cng0Q4K-Cs0cTz0Q.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-CAb9c8nc-CwEZBjjS.js → c4Diagram-7LVT6UL2-CAb9c8nc-BUsnyqmK.js} +1 -1
- package/dist/assets/channel-BjESmYnj-CSH8it0a.js +1 -0
- package/dist/assets/{chunk-4HAMMTFA-ChwfIAnF-DGygijlF.js → chunk-4HAMMTFA-ChwfIAnF-BHjtPDde.js} +1 -1
- package/dist/assets/{chunk-75Z2AOVW-CbPA0Zt_-CwP-MrpQ.js → chunk-75Z2AOVW-CbPA0Zt_-CdgguGGU.js} +1 -1
- package/dist/assets/chunk-DU6HZSFF-DuOx2LFX--8Aqhl9J.js +125 -0
- package/dist/assets/{chunk-F27PBJKO-CgBMw9yN-BV0GTVRl.js → chunk-F27PBJKO-CgBMw9yN-DAXe7Kw9.js} +1 -1
- package/dist/assets/{chunk-GMAD6QVW-JTqlimUX-B_3f5Ra9.js → chunk-GMAD6QVW-JTqlimUX-B9fBYVVw.js} +3 -3
- package/dist/assets/{chunk-GVQU2GXP-D6ccB4DO-DyCem6PM.js → chunk-GVQU2GXP-D6ccB4DO-C9ciWebQ.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-ij1DSzMb-DR4fp7Tm.js → chunk-IMKFNOWR-ij1DSzMb-siJmTc1J.js} +1 -1
- package/dist/assets/{chunk-L3NEJ4N5-CUoAQVG2-krsx9cj3.js → chunk-L3NEJ4N5-CUoAQVG2-D2PIgA5u.js} +1 -1
- package/dist/assets/{chunk-OSK3NFVY-_VNbXQSu-CKJ7An_P.js → chunk-OSK3NFVY-_VNbXQSu-D6uNmbP7.js} +1 -1
- package/dist/assets/{chunk-P2QGCYS3-B8lzOn3Y-BZUzQSq8.js → chunk-P2QGCYS3-B8lzOn3Y-B0rhisRg.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-Bh_uOTmp-buGxtrwT.js → chunk-POPQ4Y6H-Bh_uOTmp-F2MZHALw.js} +1 -1
- package/dist/assets/{chunk-PWAF6VOD-DzjnfYSw-DDFZNlLO.js → chunk-PWAF6VOD-DzjnfYSw-BAEg9ICR.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-B8dQLS5w-WHHRV9Ur.js → chunk-SHT3W25Y-B8dQLS5w-JocijxSR.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-B6A-T8Th-C5XUikq2.js → chunk-SVP7TREG-B6A-T8Th-DGod9RQf.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-C1kwD29l-BMyD0-aG.js → chunk-TICWLB2K-C1kwD29l-D-fcAtmN.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-DBOxGghV.js → chunk-XXDRQBXY-vg0kfC73-BHJD87yL.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-DfPjXaJw.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-DfPjXaJw.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-NuHCtE3P-C-ULex5_.js → cose-bilkent-JH36ORCC-NuHCtE3P-BEFDutcR.js} +1 -1
- package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-DO_vgD-g.js +1 -0
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-CA7sro34-CNZhM7M-.js → cynefinDiagram-5FMLGOSQ-CA7sro34-hf-uq6uW.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-DOztjUR_-DBqYzqRp.js → dagre-GXQ25YYZ-DOztjUR_-DYJC88Cb.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-D_nsLfRT-COttNbwi.js → diagram-S7CK7UJ4-D_nsLfRT-DaDGeJUx.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-DNN-YX9a-BNAbLMSI.js → diagram-UQ7AKVKN-DNN-YX9a-D8JdyLBv.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV--HENzo-l-5NX8oJbz.js → diagram-VSXAHHWV--HENzo-l-CqoOnNdh.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-m2m9dROj-B9XHnhOq.js → diagram-VX7I27RA-m2m9dROj-CsT-aVOB.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-D4qpv4-B-keoTvYsy.js → diagram-Z3DM3KII-D4qpv4-B-DCgIzms7.js} +1 -1
- package/dist/assets/{dist-Dq9N76oy.js → dist-BIvNvTDO.js} +1 -1
- package/dist/assets/{dist-uJrW5fOA.js → dist-BP4I4Ek8.js} +7 -7
- package/dist/assets/{dist-Cz-H6Ed9.js → dist-BwEUG-6y.js} +1 -1
- package/dist/assets/{dist-w0uFuFoe.js → dist-C1y0E8Di.js} +2 -2
- package/dist/assets/{dist-J9wojbOY.js → dist-CBAuHlC8.js} +1 -1
- package/dist/assets/{dist-Bfh13BGU.js → dist-CrvZQbzA.js} +2 -2
- package/dist/assets/dist-D0m3HNDL.js +9 -0
- package/dist/assets/{dist-DeiyD9Ki-yabC7BmP.js → dist-DeiyD9Ki-HmcgckNS.js} +1 -1
- package/dist/assets/{dist-DgODKTA_.js → dist-DjAUC6Df.js} +1 -1
- package/dist/assets/{dist-BXSyA_TU.js → dist-OM0ZTf3D.js} +1 -1
- package/dist/assets/{dist-DQ0-6hCE.js → dist-wMJX53du.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-CAbv3sZg-CFLbyx6x.js → ebnfDiagram-PWID7BFC-CAbv3sZg-D_wgwK21.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-a10Ryh0--BWDkLVe9.js → erDiagram-RLTQ6QDP-a10Ryh0--Bujk3q7n.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-CO3LnOLR.js +1 -0
- package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-BAZr48OD.js +1 -0
- package/dist/assets/{ganttDiagram-EL5Y4UJY-Cb7leeOC-BPX1Xgmv.js → ganttDiagram-EL5Y4UJY-Cb7leeOC-BM1XnrTx.js} +1 -1
- package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-9qkMMsLJ.js +1 -0
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-BCSbUpXG-DU5RS79P.js → gitGraphDiagram-WWUBYQGX-BCSbUpXG-wz9LJhjS.js} +1 -1
- package/dist/assets/{html-DdQiKDqM-BXCkjklT.js → html-DdQiKDqM-_EHD0DY0.js} +1 -1
- package/dist/assets/index-BJzCebxZ.css +1 -0
- package/dist/assets/index-IfilW1QZ.js +5735 -0
- package/dist/assets/info-A6RAGUB7-4sGJh1is-B5MTNXpw.js +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-BJRaZItN-CACSQIFb.js → infoDiagram-27XIBGKW-BJRaZItN-DUn6Imd4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-DHlFe__S-BnGlDvTQ.js → ishikawaDiagram-5VMMS53U-DHlFe__S-QEXazEAW.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-BOIo3JM--Bgob4PnX.js → journeyDiagram-3NMN7TZE-BOIo3JM--BuXFu263.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-yrQdG683-DcyAGP1e.js → kanban-definition-UXKFOSKX-yrQdG683-DOReg6eb.js} +1 -1
- package/dist/assets/{lengths-CvmPfBid-BpVvPtRC.js → lengths-CvmPfBid-BCQFXUKI.js} +1 -1
- package/dist/assets/{line-DUb1TjJS-izZAT5LF.js → line-DUb1TjJS-BuJA9mg4.js} +1 -1
- package/dist/assets/{linear-xsuGWu5q-BuZMervL.js → linear-xsuGWu5q-n7S60vDq.js} +1 -1
- package/dist/assets/{liteAdaptor-C0FQiOEx-CxupHcvg.js → liteAdaptor-C0FQiOEx-Cq4--uCe.js} +1 -1
- package/dist/assets/{marp-DENGSRW0.js → marp-Bi6DZrXK.js} +8 -8
- package/dist/assets/material-symbols-outlined-Dp0GkOHZ.woff2 +0 -0
- package/dist/assets/{mathjax-DVEo4UYj-C3x97xdu.js → mathjax-DVEo4UYj-CQVW99gS.js} +1 -1
- package/dist/assets/{mermaid-parser.core-fkjeiYO7-M3XJIX-q.js → mermaid-parser.core-fkjeiYO7-BB0BoGpH.js} +2 -2
- package/dist/assets/{mermaid.core-CZDx2Cyn-RxeIjBzj.js → mermaid.core-CZDx2Cyn-BgLyvZEv.js} +4 -4
- package/dist/assets/{mindmap-definition-YA3MSWOX-BpKAVyqs-Dc3nmChK.js → mindmap-definition-YA3MSWOX-BpKAVyqs-C-qd-rAQ.js} +1 -1
- package/dist/assets/{mo-CrlNnGXX-FKXB2qNn.js → mo-CrlNnGXX-ZonVrraQ.js} +1 -1
- package/dist/assets/packet-AYTQ26CC-MQQyx8fI-DNzukwFF.js +1 -0
- package/dist/assets/{pegDiagram-XKGWAZYB-C7eG_Ohl-BWPGioqW.js → pegDiagram-XKGWAZYB-C7eG_Ohl-qsj3QzrM.js} +1 -1
- package/dist/assets/pie-WAS4IAKB-CyaL5i_d-BvgAS1Ii.js +1 -0
- package/dist/assets/{pieDiagram-E7YTZNPT-5f9wSfDR-D5yVRASZ.js → pieDiagram-E7YTZNPT-5f9wSfDR-BtYdsTu0.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-mWeaOj5S-BJX45LIU.js → quadrantDiagram-AXDQQJYC-mWeaOj5S-K6fu4j7h.js} +1 -1
- package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-BKmGFUW2.js +1 -0
- package/dist/assets/railroad-74A4TZTK-C2gqsOGX-CtqPBulg.js +1 -0
- package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-Uce82Efu.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-b31ZUSUp.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--Debzjp3V.js +1 -0
- package/dist/assets/{railroadDiagram-O6MQD6OU-DJskhE7u-4ptEO_5_.js → railroadDiagram-O6MQD6OU-DJskhE7u-Db2jf6Vu.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-B5E0bU4h-DwbLfp_q.js → requirementDiagram-BXWQKSXE-B5E0bU4h-Bu5DE1z8.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DS3AOTai-DpnR520V.js → sankeyDiagram-P5KCCOFB-DS3AOTai-CnB0Boj3.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bn2L9osv-UamgRzWE.js → sequenceDiagram-WJ2MYXX4-Bn2L9osv-CnvxSlmR.js} +1 -1
- package/dist/assets/{src-CLC0ddSd-CWeMRcOW.js → src-CLC0ddSd-ShceZCNY.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-DbtoW7HL-DeZl0bzn.js → stateDiagram-D77RDMKH-DbtoW7HL-CpFvDXBl.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-B6UpeGWr.js +1 -0
- package/dist/assets/{svg-CaHU_sxA-KCgKFb2G.js → svg-CaHU_sxA-BO3d_6Ky.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-90h8w_3B-D5ZhbAKb.js → swimlanes-42K2YHIH-90h8w_3B-Cb8CFT1b.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-DlAxKIKI.js +8 -0
- package/dist/assets/{tex-B1cONMvb-CfJfEPgR.js → tex-B1cONMvb-BfsG0Rte.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-Bx3KcICO-Bqjvu2sn.js → timeline-definition-24CTP7MA-Bx3KcICO-BwXqR_3e.js} +1 -1
- package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-BwdxVCvS.js +1 -0
- package/dist/assets/treemap-WGGIJYW6-DGh_szUA-BLuORwku.js +1 -0
- package/dist/assets/{vennDiagram-4TSXK5OY-DSexNe_H-6dTOx1vO.js → vennDiagram-4TSXK5OY-DSexNe_H-gzw6hGhi.js} +1 -1
- package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-BlIA2tmL.js +1 -0
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DesYYyZW-BdM1G8AK.js → wardleyDiagram-VM6X3IG4-DesYYyZW--gQrXHHZ.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-dIt6pHWV-CspedMkK.js → xychartDiagram-S5SC5T6Z-dIt6pHWV-DoBDFiMo.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +10 -9
- package/server/agents/copilot-args.ts +43 -0
- package/server/agents/copilot-hook.ts +80 -0
- package/server/agents/copilot-hooks-file.ts +389 -0
- package/server/agents/copilot-mcp.ts +18 -0
- package/server/agents/copilot-sessions.ts +99 -0
- package/server/agents/copilot.ts +9 -0
- package/server/agents/cursor-args.ts +42 -0
- package/server/agents/cursor-hook.ts +90 -0
- package/server/agents/cursor-hooks-file.ts +449 -0
- package/server/agents/cursor-sessions.ts +204 -0
- package/server/agents/cursor.ts +9 -0
- package/server/agents/machine-global-hooks.ts +28 -0
- package/server/agents/registry.ts +4 -0
- package/server/agents/types.ts +1 -1
- package/server/backends/files.ts +11 -0
- package/server/backends/presentPathRoot.ts +2 -0
- package/server/backends/rawServingPlan.ts +5 -62
- package/server/backends/remoteHost/session.ts +4 -0
- package/server/backends/remoteHost/terminalScreen.ts +9 -1
- package/server/backends/servedFileName.ts +30 -0
- package/server/config/agent-bins.ts +38 -0
- package/server/config/env.ts +16 -2
- package/server/config/port-from-argv.ts +13 -4
- package/server/files/dirRequest.ts +55 -17
- package/server/files/editableText.ts +21 -0
- package/server/files/files-browse.ts +27 -2
- package/server/files/open-file.ts +37 -0
- package/server/files/reveal-argv.ts +31 -0
- package/server/files/reveal.ts +44 -0
- package/server/files/spawnOpener.ts +50 -0
- package/server/files/wsl.ts +9 -2
- package/server/index.ts +29 -16
- package/server/infra/host-tools.ts +4 -0
- package/server/infra/shapescript-publish-tool.ts +133 -0
- package/server/routes/app-routes.ts +15 -1
- package/server/routes/hook-routes.ts +59 -8
- package/server/routes/plugin-routes.ts +63 -10
- package/server/routes/routeParams.ts +13 -10
- package/server/routes/session-routes.ts +42 -2
- package/server/routes/terminal-ws-path.ts +3 -1
- package/server/routes/ws-endpoint-guard.ts +78 -0
- package/server/routes/ws-routes.ts +125 -67
- package/server/session/background-chat.ts +3 -1
- package/server/session/registry.ts +8 -5
- package/server/session/session-reads.ts +12 -7
- package/server/session/spawn-copilot.ts +85 -0
- package/server/session/spawn-cursor.ts +69 -0
- package/server/session/spawn-deps.ts +4 -0
- package/server/session/spawners.ts +4 -0
- package/server/session/survivor-agent-guard.ts +10 -0
- package/dist/assets/architecture-7GRP2DOG-CRS69XmP-C2ssfvCR.js +0 -1
- package/dist/assets/channel-BjESmYnj-2RUWsayg.js +0 -1
- package/dist/assets/chunk-DU6HZSFF-DuOx2LFX-CTuzwS95.js +0 -125
- package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-pzxP_Wrc.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-pzxP_Wrc.js +0 -1
- package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-DCcf_TXw.js +0 -1
- package/dist/assets/dist-C9nehyYS.js +0 -9
- package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-DlQLOvg2.js +0 -1
- package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-C7f-ZHXY.js +0 -1
- package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-Ds160QXE.js +0 -1
- package/dist/assets/index-DL4am_tt.css +0 -1
- package/dist/assets/index-DxJ_cEl8.js +0 -5555
- package/dist/assets/info-A6RAGUB7-4sGJh1is-B2lF7LLg.js +0 -1
- package/dist/assets/material-symbols-outlined-CQbRTMbb.woff2 +0 -0
- package/dist/assets/packet-AYTQ26CC-MQQyx8fI-CtfAuBow.js +0 -1
- package/dist/assets/pie-WAS4IAKB-CyaL5i_d-DKk1YcPy.js +0 -1
- package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-D9bt98CI.js +0 -1
- package/dist/assets/railroad-74A4TZTK-C2gqsOGX-Cp7km1N9.js +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-DRrFPYSw.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-DLxY8vq-.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--CX-TRiPt.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-E71h53k-.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-BosBO5kA.js +0 -8
- package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-C7nhuiOY.js +0 -1
- package/dist/assets/treemap-WGGIJYW6-DGh_szUA-BHtZckP9.js +0 -1
- package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-CygXnbg_.js +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# MulmoTerminal
|
|
2
2
|
|
|
3
|
-
**Run multiple
|
|
3
|
+
**Run multiple coding-agent sessions in parallel — and see which one needs you.**
|
|
4
4
|
|
|
5
|
-
A **browser terminal** for **parallel AI coding agents**: several
|
|
6
|
-
|
|
5
|
+
A **browser terminal** for **parallel AI coding agents**: several sessions side by side, each in
|
|
6
|
+
its own cell, with the one that needs you marked in colour. **Claude Code** is the default and six
|
|
7
|
+
more CLIs are first-class — Codex, Antigravity, Grok, Muse, GitHub Copilot CLI and Cursor CLI. Vibe
|
|
7
8
|
coding with a single agent needs nothing but a shell — this is for when you run several and lose
|
|
8
9
|
track of which is waiting. Sessions survive a reload (tmux), work isolates in **git worktrees**,
|
|
9
10
|
and a **phone push** reaches you when a turn finishes.
|
|
@@ -47,7 +48,7 @@ That is the whole install.
|
|
|
47
48
|
|
|
48
49
|
</details>
|
|
49
50
|
|
|
50
|
-
MulmoTerminal turns [Claude Code](https://claude.com/claude-code)
|
|
51
|
+
MulmoTerminal turns [Claude Code](https://claude.com/claude-code) — and six other agent CLIs —
|
|
51
52
|
into a parallel, observable workspace: many agent sessions at once in a grid, each one
|
|
52
53
|
color-coded so you see at a glance which are **working**, which **need you**, and which are
|
|
53
54
|
**done** — plus rich GUI output, git worktrees with one-click PRs, cost readouts, and a
|
|
@@ -63,7 +64,7 @@ Requires **Node ≥ 22.12** and the [`claude`](https://claude.com/claude-code) C
|
|
|
63
64
|
### Why not tmux + iTerm panes?
|
|
64
65
|
|
|
65
66
|
Running agents in parallel was never the hard part — tmux does that fine. What gets lost
|
|
66
|
-
is **which** of
|
|
67
|
+
is **which** of them is waiting for you. A pane is opaque: working, finished and
|
|
67
68
|
blocked-on-a-permission all look the same until you read it. Here every cell reports its
|
|
68
69
|
state back to one grid — working (blue), done (green), **needs you** (amber) — with a chime
|
|
69
70
|
when one goes amber off-screen, and a [cockpit roster](#why-youll-want-it) of one line per
|
|
@@ -123,7 +124,7 @@ streamed to an [xterm.js](https://xtermjs.org/) terminal in the browser over a W
|
|
|
123
124
|
**cockpit roster** lists every session and reflects, in real time, which are **working**
|
|
124
125
|
(the agent is thinking, a spinner), which are **waiting on you** (a permission prompt or a
|
|
125
126
|
question — an amber dot; nothing proceeds until you answer) and which are **finished with output
|
|
126
|
-
you haven't seen** (a green dot) — driven by
|
|
127
|
+
you haven't seen** (a green dot) — driven by the activity each agent reports; claude's hooks are injected per
|
|
127
128
|
spawn. The horizontal tab bar carries the same two dots.
|
|
128
129
|
|
|
129
130
|

|
|
@@ -168,7 +169,8 @@ than as bytes (files within the session's working directory only):
|
|
|
168
169
|
| `.json` | **indented** in a new tab (Chrome and Safari otherwise show one long line) |
|
|
169
170
|
| `.csv` `.tsv` | a **table** in a new tab, with a sticky header that scrolls inside its own box |
|
|
170
171
|
| source, config, logs, and `.txt` — 46 extensions | the app's own **Files** view (`/files?path=`), where CodeMirror highlights it, the tree is right there, and it can be edited |
|
|
171
|
-
|
|
|
172
|
+
| images, PDF, SVG, HTML, video | raw bytes in a new tab, which the browser renders better than an editor would |
|
|
173
|
+
| everything else — `.xlsx`, `.docx`, `.zip`, a `Makefile` | the app's own **Files** view. A tab cannot display these, so opening one there is not a view — it is a download starting with no warning. The pane names the file and offers **Open in OS**, which hands it to the application that owns it (Excel for an `.xlsx`) |
|
|
172
174
|
|
|
173
175
|
**While a grid cell is enlarged, the [Files pane](#files-view-browse--edit) takes the click first** — every
|
|
174
176
|
row above except the last one, since the pane is the same editor plus a Markdown preview. The
|
|
@@ -267,7 +269,7 @@ Needs **Node ≥ 22.12**, plus these CLIs on your `PATH`:
|
|
|
267
269
|
| **Required** | `gh` | the cross-repo **PRs & Issues** view and one-click PR creation — it uses your `gh` login, so no token is stored | [cli.github.com](https://cli.github.com), then `gh auth login` |
|
|
268
270
|
| Optional | `glab` | the same for **GitLab** projects (#981) — gitlab.com, and a self-hosted instance you declare in `gitlabHosts` (#1332). Same arrangement: the CLI holds the credentials, this app stores no token | `brew install glab`, then `glab auth login` (self-hosted: `glab auth login --hostname gitlab.example.com`) |
|
|
269
271
|
| Recommended | `tmux` | [session persistence](#session-persistence-tmux) — terminals survive a server restart | `brew install tmux` · `sudo apt install tmux` · `sudo dnf install tmux` · no native Windows build (falls back to plain PTYs) |
|
|
270
|
-
| Optional | `
|
|
272
|
+
| Optional | any other agent CLI | a cell can run **Codex**, **Antigravity** (`agy`), **Grok**, **Muse**, **GitHub Copilot CLI** or **Cursor CLI** instead of Claude — install only the ones you use, and a missing one simply fails to start that cell. What each can do is [the capability matrix](docs/agent-capability-matrix.md); how to install and pick one is the [agents guide](https://receptron.github.io/mulmoterminal/guide/en/agents.html) | e.g. `npm i -g @openai/codex` |
|
|
271
273
|
| Optional | `ffmpeg` | video rendering from the [mulmo-script panel](#wiki-collections--the-gui-panel) (its plugin ships enabled) | `brew install ffmpeg` · `sudo apt install ffmpeg` · `sudo dnf install ffmpeg` |
|
|
272
274
|
| Optional | `ollama` | [`claude-ollama`](https://receptron.github.io/mulmoterminal/guide/en/claude-ollama.html) — Claude Code against a fully local model | [ollama.com/download](https://ollama.com/download) |
|
|
273
275
|
| Linux only | a file dialog | the **Choose a folder / Insert a file path** buttons, which open an OS dialog on the machine the server runs on. macOS and Windows have one built in; **WSL** uses the Windows one over interop and needs nothing installed. A Linux desktop needs one of these — without any, the buttons say so and you type the path instead (#1447) | `sudo apt install zenity` · `sudo dnf install zenity` · `kdialog`, `qarma` and `yad` also work |
|
|
@@ -364,7 +366,7 @@ The launcher detects it and prints the exact, OS-appropriate removal command; ru
|
|
|
364
366
|
|
|
365
367
|
- [Architecture](#architecture)
|
|
366
368
|
- [Why a PTY?](#why-a-pty)
|
|
367
|
-
- [Agents
|
|
369
|
+
- [Agents](#agents-claude-codex-antigravity-grok-muse-copilot--cursor)
|
|
368
370
|
- [Session persistence (tmux)](#session-persistence-tmux)
|
|
369
371
|
- [Tech stack](#tech-stack)
|
|
370
372
|
- [Configuration](#configuration)
|
|
@@ -417,11 +419,15 @@ The launcher detects it and prints the exact, OS-appropriate removal command; ru
|
|
|
417
419
|
- **Session list** is fetched over HTTP (`/api/sessions`) — by `App.vue` for the tab favicon,
|
|
418
420
|
and by an empty cell's launch form (`?cwd=`) for its resume rows.
|
|
419
421
|
- **Live activity** is pushed over a Socket.IO pub/sub channel (`/ws/pubsub`);
|
|
420
|
-
the server learns of activity from **
|
|
421
|
-
- **Other terminals** run on their own raw WebSockets
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
the server learns of activity from **hooks** that POST to `/api/hook`.
|
|
423
|
+
- **Other terminals** run on their own raw WebSockets — one per agent (`/ws/codex`,
|
|
424
|
+
`/ws/antigravity`, `/ws/grok`, `/ws/muse`, `/ws/copilot`, `/ws/cursor`), persistent
|
|
425
|
+
**launch commands** on `/ws/launch`, and one-off **script commands** (`yarn dev`, tests, …)
|
|
426
|
+
on `/ws/run`. **Three agents reach `/api/hook`** — Claude directly, Copilot and Cursor through
|
|
427
|
+
a translation of their own vocabularies; Codex is read from its rollout instead, and agy, grok
|
|
428
|
+
and muse report no activity at all. Which agent does what is the matrix in
|
|
429
|
+
[`docs/agent-capability-matrix.md`](docs/agent-capability-matrix.md); see also
|
|
430
|
+
[Scripts (Run menu)](#scripts-run-menu).
|
|
425
431
|
- In dev (`yarn dev`) the Vite dev server runs on its own port (`CLIENT_PORT`,
|
|
426
432
|
default `6856`) and proxies `/ws` (a prefix covering `/ws/codex`, `/ws/launch`, and
|
|
427
433
|
`/ws/run`), `/ws/pubsub`, `/api`, `/artifacts`, and `/htmlfile` to the backend
|
|
@@ -448,12 +454,15 @@ SDK; we drive the real interactive CLI and relay its TTY over the WebSocket.
|
|
|
448
454
|
|
|
449
455
|
---
|
|
450
456
|
|
|
451
|
-
## Agents: Claude, Codex, Antigravity &
|
|
457
|
+
## Agents: Claude, Codex, Antigravity, Grok, Muse, Copilot & Cursor
|
|
452
458
|
|
|
453
459
|
MulmoTerminal drives **interactive coding-agent CLIs**, not just Claude. An
|
|
454
460
|
`AgentAdapter` seam abstracts the per-agent bits (which binary to spawn, how it resumes)
|
|
455
|
-
so the PTY, grid, persistence, and GUI-panel plumbing stay shared.
|
|
456
|
-
today — **Claude Code** (the default), **Codex**, **Antigravity** (`agy`),
|
|
461
|
+
so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Seven adapters ship
|
|
462
|
+
today — **Claude Code** (the default), **Codex**, **Antigravity** (`agy`), **Grok**, **Muse**,
|
|
463
|
+
**GitHub Copilot CLI**, and **Cursor CLI**.
|
|
464
|
+
Which capabilities each one actually has — status dots, notifications, resume, cost, GUI tools —
|
|
465
|
+
is the matrix in [`docs/agent-capability-matrix.md`](docs/agent-capability-matrix.md).
|
|
457
466
|
|
|
458
467
|
- **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
|
|
459
468
|
`--session-id <uuid>`, so it knows the live session's id even before its transcript
|
|
@@ -511,9 +520,74 @@ today — **Claude Code** (the default), **Codex**, **Antigravity** (`agy`), and
|
|
|
511
520
|
`.git/info/exclude` only when MulmoTerminal created it. As with agy, the **session id is never
|
|
512
521
|
written into that file** — it reaches the bridge through the grok process's own environment.
|
|
513
522
|
|
|
523
|
+
- **Muse** — spawned as `muse` (override with `MUSE_BIN`; `MUSE_MODEL` sets `--model`), on its own
|
|
524
|
+
WebSocket (`/ws/muse`). It is codex-shaped on the id axis — it mints its own session id as a row
|
|
525
|
+
in `session-index.db`, so a fresh spawn is watched for the new row and a reconnect resumes it with
|
|
526
|
+
`muse resume <id>`. That resume also carries `--workspace <dir>`: the flag is what registers the
|
|
527
|
+
policy-gated workspace tools, so a resumed session without it comes back with the conversation and
|
|
528
|
+
without the tools. Sessions run under `--yolo`, because an unattended grid cell needs an
|
|
529
|
+
approval-free mode — it has no way to answer a modal prompt in a TUI nobody is watching. (Codex
|
|
530
|
+
is the exception that proves it: it is passed no such mode, so it can still block on its own
|
|
531
|
+
approval prompt.) Its GUI tools reach
|
|
532
|
+
it a third way, through an installed **plugin** rather than a flag or a directory file; see
|
|
533
|
+
[MCP server ids](#mcp-server-ids-why-a-workspace-cell-and-a-project-cell-disagree).
|
|
534
|
+
|
|
535
|
+
- **GitHub Copilot CLI** — spawned as `copilot` (override with `COPILOT_BIN`; `COPILOT_MODEL` sets
|
|
536
|
+
`--model`), on its own WebSocket (`/ws/copilot`). It is the **simplest** adapter here, because one
|
|
537
|
+
flag does what the others split in two: `--session-id <uuid>` sets the UUID for a NEW session *and*
|
|
538
|
+
resumes that same session later, so MulmoTerminal mints the id and there is no watcher, no
|
|
539
|
+
attribution guess and no mapping log. Sessions land in `~/.copilot/session-state/<id>/` with a
|
|
540
|
+
machine-wide index at `~/.copilot/session-store.db` (home overridable via `COPILOT_HOME`), which is
|
|
541
|
+
what `/api/copilot/sessions` queries by working directory.
|
|
542
|
+
|
|
543
|
+
**Its status dots, attention sound and tool history come from copilot's own hooks**, the way
|
|
544
|
+
Claude's do — but registered **once per machine**, not per spawn: MulmoTerminal writes
|
|
545
|
+
`<COPILOT_HOME>/hooks/mulmoterminal.json`, and every hook payload carries the `sessionId` that is
|
|
546
|
+
already ours. Copilot has no `--settings` equivalent, and its documented per-directory hook files
|
|
547
|
+
do not load. Three things follow. A copilot session **you** start in a plain terminal also posts
|
|
548
|
+
to MulmoTerminal (its id is unknown, so the server ignores it — and the hook is written to fail
|
|
549
|
+
silently and quickly). **Two MulmoTerminal instances on different ports share that one file**, so
|
|
550
|
+
the last one to start wins and the other's copilot cells run without status until their next
|
|
551
|
+
spawn — an accepted limitation, logged when it happens, not something that can be fixed without
|
|
552
|
+
either refusing a second instance or adding a machine-wide daemon. And the file is **removed when
|
|
553
|
+
the server exits**, with a stale one left by a crash reaped at the next startup: it names a bare
|
|
554
|
+
`127.0.0.1:<port>`, so a leftover would point copilot's prompts at whatever took that port next.
|
|
555
|
+
|
|
556
|
+
`--allow-all-tools` is passed for the reason every agent here needs one: a grid cell cannot answer
|
|
557
|
+
a modal prompt. The **whole GUI MCP** reaches a workspace cell through `--additional-mcp-config`,
|
|
558
|
+
the same per-spawn shape Claude's `--mcp-config` has; a project cell gets the groups its directory
|
|
559
|
+
registered. What it does **not** do yet: report being blocked on input (`permissionRequest` fires
|
|
560
|
+
on every tool call, not only when someone is asked), token/context badges, or `$` cost — see
|
|
561
|
+
[`docs/agent-capability-matrix.md`](docs/agent-capability-matrix.md) for what each would take.
|
|
562
|
+
|
|
563
|
+
- **Cursor CLI** — spawned as `cursor-agent` (override with `CURSOR_BIN`; `CURSOR_MODEL` sets
|
|
564
|
+
`--model`), on its own WebSocket (`/ws/cursor`). Identity works exactly as copilot's does: one
|
|
565
|
+
flag, `--resume <uuid>`, both starts a chat under an id MulmoTerminal invented and returns to it
|
|
566
|
+
later, so there is no watcher, no attribution guess and no mapping log. `--trust` is passed as well
|
|
567
|
+
as `--force`, because a directory Cursor has not seen before otherwise blocks on a Workspace Trust
|
|
568
|
+
prompt nobody is watching. Chats land under `~/.cursor/projects/<slug>/agent-transcripts/<id>/`,
|
|
569
|
+
where `<slug>` is a truncated-and-hashed form of the directory that cannot be reconstructed — so
|
|
570
|
+
`/api/cursor/sessions` reads each project directory's own `.workspace-trusted` to learn which
|
|
571
|
+
directory it stands for, and omits any that does not say.
|
|
572
|
+
|
|
573
|
+
**Its status dots and tool history come from cursor's own hooks**, registered **once per machine**
|
|
574
|
+
in `~/.cursor/hooks.json` — cursor has no `--settings` equivalent, and `--plugin-dir`, which would
|
|
575
|
+
have been one, does not deliver hooks to the interactive TUI. That file is a FIXED path, so a user
|
|
576
|
+
who already keeps their own hooks there is left alone (with a line in the log) rather than merged
|
|
577
|
+
into. It is removed when the server exits, and a stale one left by a crash is rewritten at the next
|
|
578
|
+
startup. Two MulmoTerminal instances share it with the same accepted limitation copilot's has.
|
|
579
|
+
|
|
580
|
+
Cursor is the only agent besides Claude that drives **both** status halves — `beforeSubmitPrompt`
|
|
581
|
+
starts the turn and `stop` ends it. What it does **not** do: report being blocked on input (every
|
|
582
|
+
event that fires before an approval prompt also fires when nothing is asked), the GUI MCP (cursor
|
|
583
|
+
reads MCP from a file and no writer ships yet), token/context badges (the `stop` payload carries
|
|
584
|
+
the counts, unread), or `$` cost — see
|
|
585
|
+
[`docs/agent-capability-matrix.md`](docs/agent-capability-matrix.md) for what each would take.
|
|
586
|
+
|
|
514
587
|
**Choosing an agent.** Each grid cell's launch form carries the **Agent Picker** — a
|
|
515
|
-
**Claude / Codex / Antigravity / Grok / Shell** toggle — and the Collections
|
|
516
|
-
Codex / Antigravity / Grok** one (your choice is
|
|
588
|
+
**Claude / Codex / Antigravity / Grok / Muse / Copilot / Cursor / Shell** toggle — and the Collections
|
|
589
|
+
browser a **Claude / Codex / Antigravity / Grok / Muse / Copilot / Cursor** one (your choice is
|
|
590
|
+
remembered).
|
|
517
591
|
**Shell** is not an agent: it runs your OS default shell (`$SHELL`, or `/bin/sh`) in the
|
|
518
592
|
chosen directory, with nothing to install and nothing to configure. It starts a launcher
|
|
519
593
|
cell, so it has no model, no MCP registration, and no worktree — those rows disappear
|
|
@@ -611,6 +685,14 @@ the `claude` / `codex` sessions themselves.
|
|
|
611
685
|
| `GROK_BIN` | `grok` | The Grok CLI binary to spawn. |
|
|
612
686
|
| `GROK_MODEL` | grok default | Model passed to Grok as `--model` (unset = grok's own default). |
|
|
613
687
|
| `GROK_HOME` | `~/.grok` | Grok home directory containing its per-directory session store. |
|
|
688
|
+
| `MUSE_BIN` | `muse` | The Muse CLI binary to spawn. |
|
|
689
|
+
| `MUSE_MODEL` | muse default | Model passed to Muse as `--model` (unset = muse's own default). |
|
|
690
|
+
| `MUSE_HOME` | `~/.local/share/muse` | Muse home directory containing its session index and logs. |
|
|
691
|
+
| `COPILOT_BIN` | `copilot` | The GitHub Copilot CLI binary to spawn. |
|
|
692
|
+
| `COPILOT_MODEL` | copilot default | Model passed to Copilot as `--model` (unset = copilot's own default). |
|
|
693
|
+
| `COPILOT_HOME` | `~/.copilot` | Copilot's config directory. MulmoTerminal registers its status hooks in `<COPILOT_HOME>/hooks/mulmoterminal.json` and reads the session list from `<COPILOT_HOME>/session-store.db`. |
|
|
694
|
+
| `CURSOR_BIN` | `cursor-agent` | The Cursor CLI binary to spawn. |
|
|
695
|
+
| `CURSOR_MODEL` | cursor default | Model passed to Cursor as `--model` (unset = cursor's own default). Cursor's model names are **account-specific** and a wrong one is a hard exit — check `cursor-agent --list-models` first. |
|
|
614
696
|
| `MULMOTERMINAL_HOME` | `~/.mulmoterminal` | Root for managed **git worktrees**. |
|
|
615
697
|
| `CLAUDE_CONFIG_DIR` | `~` | Claude Code's own config directory. `.claude.json` lives **inside** it, so relocating your Claude Code config moves that file too — MulmoTerminal reads it to tell whether the per-project GUI MCP server is registered (`server/infra/gui-mcp-registration.ts`). Leave it unset and `~/.claude.json` is used. |
|
|
616
698
|
| `MULMOCLAUDE_WORKSPACE_PATH` | `~/mulmoclaude` | Where the managed MulmoClaude workspace lives. MulmoTerminal seeds presets/helps **only** into this directory, so launching in an arbitrary project never writes them there (`server/backends/workspaceSetup.ts`), and it is what decides where MulmoTerminal's own runtime state goes — see the note under the table. Set it to the same value MulmoClaude uses. |
|
|
@@ -666,7 +748,7 @@ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmote
|
|
|
666
748
|
| `repoDirs` | `{ "owner/repo": "/abs/path" }` — which local clone work on a repo starts in, when you keep several side by side. Only the *choice* is stored; which clones exist is re-derived from `cwdPresets` on every read, and an entry that no longer names a clone of that repo is ignored. |
|
|
667
749
|
| `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides the agents — any interactive command. A plain shell needs no entry: the Agent Picker's **Shell** option opens `$SHELL` unconfigured. |
|
|
668
750
|
| `customAgents` | `{ id, label, agent, command }` entries offered in the **Agent Picker** — your own way of starting Claude Code (`ollama launch claude --model … --`, a wrapper script). Unlike a launcher, Claude Code's own argv is **appended** to `command`, so the cell is a real session: resume, cost, context, GUI tools. `agent` says which agent's arguments to append and is required (`"claude"` is the only value today); `command` must stop taking arguments where Claude Code's begin — hence the trailing `--` above. Up to 8. |
|
|
669
|
-
| `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` (`"claude"`
|
|
751
|
+
| `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` scopes a chip to session kinds — any of `SESSION_AGENTS` (`"claude"`, `"codex"`, `"antigravity"`, `"grok"`, `"muse"`, `"copilot"`, `"cursor"`, `"shell"`); omit it to offer the chip everywhere. Empty by default. |
|
|
670
752
|
| `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the `--mcp-config` of the **Claude** sessions that carry the full GUI MCP (codex is handed the GUI server alone, `codexGuiMcpServers`) — a cell whose working directory is the **workspace**, and a session the server starts itself (the phone, a scheduled task) unless it asks for a grid cell's shape, as an issue's seed session does (`issueSpawnOptions`). A cell in a project directory does not get this merge; the MCP config the user wrote is read either way. Takes effect on the next session. |
|
|
671
753
|
| `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
|
|
672
754
|
| `chips` | Header info chips (`dir` / `git` / `work` / `diff` / `ctx` / `usage` / `status` / `tools` / `env`, or custom text). `env` shows what this working tree was reserved by [`worktreeEnv`](#per-directory-settings-projectmulmoterminaljson) (`:3010`, clickable) and draws nothing where none is declared. Omit to keep the default set; `[]` hides all built-ins. `work` shows which PR / issue the cell is on (`#977 → #966`) and clears itself when the PR merges — see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#work-chip). |
|
|
@@ -889,7 +971,7 @@ Settings → Directory settings names both files and lists which keys the local
|
|
|
889
971
|
| `theme` | xterm palette for terminals in this directory (one of the built-in theme ids). |
|
|
890
972
|
| `colors` | Per-key xterm palette overrides applied on top of `theme` (or the app theme when `theme` is unset). Keys are xterm `ITheme` names (`background`, `foreground`, `cursor`, `selectionBackground`, the 16 ANSI colors, …); values are hex (`#rgb` / `#rrggbb` / `#rrggbbaa`). Unknown keys / bad values are dropped. |
|
|
891
973
|
| `fontSize` | Terminal font size in px for this directory (8–32), overriding the Settings value. A size outside the range is clamped; a non-number is ignored. Changing it re-fits the terminal, so the PTY learns the new width — unlike browser zoom, which leaves the two disagreeing. |
|
|
892
|
-
| `orderPriority` | This directory's rank in the grid's **priority** ordering — the third mode on the toolbar's ordering button, next to auto (attention-first) and manual (the move buttons). Any integer, **lowest first**; negatives are allowed. Directories that set nothing sort last, keeping their existing order, so adding the key to one project doesn't shuffle the rest. The grid reads it in **priority** mode only; the launcher's directory chips sort by it, so a project sits in the same place on both. The one exception is the **workspace** chip, which always leads the launcher's row regardless of any rank — it is not one of the directories being ranked against each other, and it is the one place a claude or codex session reaches every GUI tool without registering anything (agy and
|
|
974
|
+
| `orderPriority` | This directory's rank in the grid's **priority** ordering — the third mode on the toolbar's ordering button, next to auto (attention-first) and manual (the move buttons). Any integer, **lowest first**; negatives are allowed. Directories that set nothing sort last, keeping their existing order, so adding the key to one project doesn't shuffle the rest. The grid reads it in **priority** mode only; the launcher's directory chips sort by it, so a project sits in the same place on both. The one exception is the **workspace** chip, which always leads the launcher's row regardless of any rank — it is not one of the directories being ranked against each other, and it is the one place a claude or codex session reaches every GUI tool without registering anything (agy, grok and muse get what the directory registered wherever they run — see [MCP server ids](#mcp-server-ids-why-a-workspace-cell-and-a-project-cell-disagree)). |
|
|
893
975
|
| `fontFamily` | CSS font-family stack for this directory's terminals, overriding the global `fontFamily`. Use the names as your OS lists them (`"'Cica', 'MS Gothic', monospace"`). An unusable stack is ignored whole rather than half-applied; `monospace` is appended if you name no generic family. Prefer fonts whose fullwidth glyphs are exactly twice the Latin width, or box-drawing frames tear. |
|
|
894
976
|
| `sound` | Attention sound for this directory's sessions, a path **relative to the directory** (served at `GET /api/dir-sound`). The fallback for every kind. |
|
|
895
977
|
| `sounds` | Per-kind override of `sound`: `{ "command-failed": "preset:gong" }`. Each value is a `preset:<id>` or a directory-relative path, under the same confinement. |
|
|
@@ -957,8 +1039,8 @@ the directories you have launched in — **worktrees excluded**, since one is a
|
|
|
957
1039
|
branch that is deleted with the task, not a place to launch in again; the
|
|
958
1040
|
**workspace** leads them always, labelled
|
|
959
1041
|
**WORKSPACE** and marked with an icon, whether or not you have ever launched there — it is
|
|
960
|
-
the one directory where a claude or codex session reaches every GUI tool (agy and
|
|
961
|
-
what the directory registered, there as anywhere — see
|
|
1042
|
+
the one directory where a claude or codex session reaches every GUI tool (agy, grok and muse
|
|
1043
|
+
get what the directory registered, there as anywhere — see
|
|
962
1044
|
[MCP server ids](#mcp-server-ids-why-a-workspace-cell-and-a-project-cell-disagree)), so it is never a click you can
|
|
963
1045
|
lose. It has no remove button for the same reason. It is named for its role rather than its
|
|
964
1046
|
folder, because the folder name (`~/mulmoclaude` by default, or wherever `CLAUDE_CWD`
|
|
@@ -967,10 +1049,11 @@ points) says the least interesting true thing about it; the real path is on its
|
|
|
967
1049
|
**The launcher is shorter in the workspace**, because two of its choices do not apply
|
|
968
1050
|
there. The per-directory **Canvas switches** are replaced by a line saying every GUI tool
|
|
969
1051
|
is already available — a session there is handed the whole GUI MCP at spawn, so a switch
|
|
970
|
-
would register a group URL that then has nothing left to serve. **With Antigravity or
|
|
971
|
-
picked they stay**, in the workspace as everywhere else:
|
|
972
|
-
and
|
|
973
|
-
to a GUI tool and hiding them would
|
|
1052
|
+
would register a group URL that then has nothing left to serve. **With Antigravity, Grok or Muse
|
|
1053
|
+
picked they stay**, in the workspace as everywhere else: none of the three is handed anything at
|
|
1054
|
+
spawn — agy and grok read their servers from a file in the directory, muse from a plugin installed
|
|
1055
|
+
for the whole machine — so the switches are their only route to a GUI tool and hiding them would
|
|
1056
|
+
leave the session with none (see
|
|
974
1057
|
[MCP server ids](#mcp-server-ids-why-a-workspace-cell-and-a-project-cell-disagree)).
|
|
975
1058
|
And the **worktree**
|
|
976
1059
|
section is hidden: a worktree isolates work on one codebase onto a branch, while the
|
|
@@ -992,7 +1075,7 @@ The same launcher also has an **or launch** row for your configured **launch com
|
|
|
992
1075
|
entry here: the Agent Picker's **Shell** option already opens `$SHELL`. Unlike
|
|
993
1076
|
a one-shot script, a launcher runs as a **persistent terminal in the cell's directory**:
|
|
994
1077
|
it survives grid page switches and reconnects, and its dot shows running vs. exited (it
|
|
995
|
-
|
|
1078
|
+
reports no activity of its own, so no blocked/done states).
|
|
996
1079
|
|
|
997
1080
|
Every running terminal's header also has a **▶ Run ▾** dropdown (next to the
|
|
998
1081
|
connection status) — but **only when the
|
|
@@ -1198,7 +1281,7 @@ the same worktree reached by pasting its path into **WORKING DIRECTORY**, or by
|
|
|
1198
1281
|
chip, will not launch either — and the **server** refuses the spawn whichever client asks,
|
|
1199
1282
|
so a path spelled another way (a trailing slash, a symlink) does not slip past.
|
|
1200
1283
|
|
|
1201
|
-
What the limit covers is an **agent
|
|
1284
|
+
What the limit covers is an **agent** — every entry in `TERMINAL_AGENTS` (Claude, Codex, Antigravity, Grok, Muse, Copilot, Cursor) — including an **OR
|
|
1202
1285
|
LAUNCH** command that runs one of them. A **Shell**, and a launcher that runs anything else
|
|
1203
1286
|
(`yarn dev`, `lazygit`, `htop`), stays free — a worktree an agent is working in is exactly
|
|
1204
1287
|
where you want those. A project that declares `worktreeEnv` also gets **its own value per
|
|
@@ -1234,7 +1317,7 @@ Typing a task name yourself keeps the local base it has always used, with no fet
|
|
|
1234
1317
|
|
|
1235
1318
|

|
|
1236
1319
|
|
|
1237
|
-
*Every empty grid cell shows this launch form: pick an agent in the **Agent Picker** (**Claude / Codex / Antigravity / Grok / Shell**), type a **working directory** (frequent ones autocomplete from your presets), or — in a git repo — name a task under **OR ISOLATE IN A WORKTREE** and hit **+ New worktree** to start the agent on its own isolated branch. **Shell** runs your OS default shell there instead of an agent; **OR LAUNCH** runs one of your configured launch commands.*
|
|
1320
|
+
*Every empty grid cell shows this launch form: pick an agent in the **Agent Picker** (**Claude / Codex / Antigravity / Grok / Muse / Copilot / Cursor / Shell**), type a **working directory** (frequent ones autocomplete from your presets), or — in a git repo — name a task under **OR ISOLATE IN A WORKTREE** and hit **+ New worktree** to start the agent on its own isolated branch. **Shell** runs your OS default shell there instead of an agent; **OR LAUNCH** runs one of your configured launch commands.*
|
|
1238
1321
|
|
|
1239
1322
|
A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); click it for a
|
|
1240
1323
|
**Changes vs `<base>`** panel (file list + patch) with actions:
|
|
@@ -1286,7 +1369,7 @@ Each grid cell's header shows two badges for its session, refreshed when a turn
|
|
|
1286
1369
|
- **Context badge** — e.g. `Opus · ctx 35%`: the model family plus how full its context
|
|
1287
1370
|
window is (the *last* turn's input + cache tokens ÷ the model's window — **1M** for
|
|
1288
1371
|
current-gen Opus / Sonnet / Fable / Mythos, **200k** otherwise). A session running on a
|
|
1289
|
-
[provider model](#agents-claude--
|
|
1372
|
+
[provider model](#agents-claude-codex-antigravity-grok-muse-copilot--cursor) shows that model's name and its published window
|
|
1290
1373
|
(`Kimi K2.7 Code · ctx 12%`); a model in neither list keeps the label and hides the %,
|
|
1291
1374
|
since the window is never guessed. A reading **past 100%** shows `ctx ?` instead of the
|
|
1292
1375
|
number: the window is a hard cap, so an impossible percentage means the built-in window
|
|
@@ -1303,6 +1386,7 @@ what it writes down (`?agent=` on the route picks the reader):
|
|
|
1303
1386
|
| **Codex** | `gpt-5.5 · ctx 21%` — window from codex's own `model_context_window`, so no table to be out of date | full |
|
|
1304
1387
|
| **Grok** | `grok-4.5 · ctx 33%` — window from grok's own `contextWindowTokens`, so no table either | full |
|
|
1305
1388
|
| **Antigravity** | `Gemini 3.6 Flash · ctx 78%` — the model from the first step of the conversation's transcript, the reading from agy's own per-generation accounting (a real 256k window, not a table) | full |
|
|
1389
|
+
| **Muse** | `Muse · ctx 27%` — both from the `model_completed` events in the session.jsonl muse's sqlite index points at. Muse states no window, so this is the one non-Claude badge whose percentage **does** come from the table above | full |
|
|
1306
1390
|
|
|
1307
1391
|
The token badge hides itself when nothing has been counted, and the context badge shows
|
|
1308
1392
|
the model alone unless it has **both** a *current-context* token count from the agent and a
|
|
@@ -1356,16 +1440,25 @@ it came from — `mcp__<id>__presentChart` in Claude Code, `mcp-<id>-presentChar
|
|
|
1356
1440
|
also rewrites `-` in the id to `_`). So the id you register under is repeated on every tool, in
|
|
1357
1441
|
every listing, for the life of the session.
|
|
1358
1442
|
|
|
1359
|
-
MulmoTerminal delivers the GUI MCP by **three different routes**, and they do not share an id
|
|
1443
|
+
MulmoTerminal delivers the GUI MCP by **three different routes**, and they do not share an id.
|
|
1444
|
+
Which route an agent takes is `FULL_GUI_MCP_AGENTS` in `common/guiMcpAgents.ts`, and the test is one
|
|
1445
|
+
question: can this CLI be handed a session-scoped payload on a per-spawn flag? **Claude, Codex and
|
|
1446
|
+
Copilot** can. **Antigravity, Grok and Cursor** cannot — they read a file in the directory — and
|
|
1447
|
+
**Muse** reads neither, which is the third route below.
|
|
1448
|
+
|
|
1449
|
+
**Cursor is the one entry with a gap rather than a mechanism**: it reads `.cursor/mcp.json`, and
|
|
1450
|
+
nothing writes that file for you yet ([#2066](https://github.com/receptron/mulmoterminal/issues/2066)),
|
|
1451
|
+
so a cursor cell today reaches whatever MCP you configured yourself.
|
|
1452
|
+
|
|
1360
1453
|
|
|
1361
1454
|
| | Workspace cell / single view | Project-directory grid cell |
|
|
1362
1455
|
|---|---|---|
|
|
1363
|
-
| How it arrives | generated per spawn into `--mcp-config` (Claude)
|
|
1456
|
+
| How it arrives | generated per spawn into `--mcp-config` (Claude), `-c mcp_servers.<id>.url=` (Codex) or `--additional-mcp-config` (Copilot) | the user's OWN per-folder config — `.mcp.json`, `claude mcp add -s local` |
|
|
1364
1457
|
| Server id | **`mt`** | **`mulmoterminal-render`**, `-data`, `-media`, `-external` — one per tool group |
|
|
1365
1458
|
| Tools carried | all of them, on one URL | only the groups that directory registered |
|
|
1366
1459
|
| Tool name looks like | `mcp__mt__presentChart` | `mcp__mulmoterminal-render__presentChart` |
|
|
1367
1460
|
|
|
1368
|
-
The third is **Muse**, which reads neither a flag nor a file in the directory: its MCP servers are
|
|
1461
|
+
The third route is **Muse**, which reads neither a flag nor a file in the directory: its MCP servers are
|
|
1369
1462
|
declared by an installed **plugin**, and `muse plugins install` records one per MACHINE. So
|
|
1370
1463
|
MulmoTerminal registers a single `mulmoterminal` plugin holding all four group servers, and each
|
|
1371
1464
|
session is narrowed back to what its own directory switched on — the bridge asks the server which
|
|
@@ -1595,9 +1688,9 @@ quietly answering about the **default workspace** (#1151):
|
|
|
1595
1688
|
|
|
1596
1689
|
| Where | What happens |
|
|
1597
1690
|
| --- | --- |
|
|
1598
|
-
| `/ws`, `/ws/codex`, `/ws/antigravity`, `/ws/grok`, `/ws/launch`, `/ws/run` | The socket is closed with `{ type: "error", message }`, which the terminal shows as a red banner and does not retry. |
|
|
1691
|
+
| `/ws`, `/ws/codex`, `/ws/antigravity`, `/ws/grok`, `/ws/muse`, `/ws/copilot`, `/ws/cursor`, `/ws/launch`, `/ws/run` | The socket is closed with `{ type: "error", message }`, which the terminal shows as a red banner and does not retry. |
|
|
1599
1692
|
| A session that is still running (`?session=` names a live PTY or a surviving tmux session) | **Attaches anyway**, with a warning in the server log. Moving or renaming a directory must not shut you out of an agent that is still working in it — and the cwd reported back comes from the running PTY, not from the request. |
|
|
1600
|
-
| `GET /api/scripts`, `/api/skills`, `/api/dir-config`, `/api/dir-sound`, `/api/git-status`, `/api/pr-phase`, `/api/header`, `/api/sessions`, `/api/codex/sessions`, `/api/antigravity/sessions`, `/api/grok/sessions`, `/api/session/:id`, `/api/transcript/*`, `/api/cost` | `404 { error, cwd }` — a directory that is not there. |
|
|
1693
|
+
| `GET /api/scripts`, `/api/skills`, `/api/dir-config`, `/api/dir-sound`, `/api/git-status`, `/api/pr-phase`, `/api/header`, `/api/sessions`, `/api/codex/sessions`, `/api/copilot/sessions`, `/api/cursor/sessions`, `/api/antigravity/sessions`, `/api/grok/sessions`, `/api/muse/sessions`, `/api/session/:id`, `/api/transcript/*`, `/api/cost` | `404 { error, cwd }` — a directory that is not there. |
|
|
1601
1694
|
| A `?cwd=` that cannot name a directory at all (relative, or repeated as `?cwd=a&cwd=b`) | `400 { error, cwd }`. |
|
|
1602
1695
|
|
|
1603
1696
|
A request that names **no** directory is unaffected: `CLAUDE_CWD` is then the answer it
|
|
@@ -1689,11 +1782,15 @@ Empty output returns a `{ summary }` note rather than calling the CLI. Errors:
|
|
|
1689
1782
|
|
|
1690
1783
|
### HTTP: `POST /api/hook`
|
|
1691
1784
|
|
|
1692
|
-
**Internal endpoint.** Claude
|
|
1693
|
-
[Claude hook injection](#claude-hook-injection))
|
|
1785
|
+
**Internal endpoint.** Hooks POST their event payload here. Claude's are injected per session
|
|
1786
|
+
(see [Claude hook injection](#claude-hook-injection)); **Copilot's and Cursor's arrive from a
|
|
1787
|
+
machine-global hook file** and name their agent in an `x-mt-agent: copilot|cursor` header, with the
|
|
1788
|
+
event in `x-mt-hook` — the body is translated into the Claude shape below before anything reads it
|
|
1789
|
+
(`server/agents/{copilot,cursor}-hook.ts`), so everything downstream is written against one
|
|
1790
|
+
vocabulary. A request with no `x-mt-agent` is a Claude payload and is untouched.
|
|
1694
1791
|
You normally don't call this yourself.
|
|
1695
1792
|
|
|
1696
|
-
**Request `application/json`** — the Claude hook payload; only these fields are used:
|
|
1793
|
+
**Request `application/json`** — the Claude-shaped hook payload; only these fields are used:
|
|
1697
1794
|
|
|
1698
1795
|
```jsonc
|
|
1699
1796
|
{
|
|
@@ -1728,6 +1825,9 @@ same-origin-guarded.
|
|
|
1728
1825
|
| `GET /api/codex/sessions?cwd=` | Codex sessions for the project (from `~/.codex` rollouts), newest first. |
|
|
1729
1826
|
| `GET /api/antigravity/sessions?cwd=` | Antigravity conversations for the project, newest first. agy does record a workspace, but never as a complete conversation-to-workspace map (`cache/last_conversations.json` keeps one conversation per directory and is written at exit; `history.jsonl` carries no conversation id), so the project comes from MulmoTerminal's own `~/.mulmoterminal/antigravity-conversations.jsonl`; agy's transcript supplies the title. |
|
|
1730
1827
|
| `GET /api/grok/sessions?cwd=` | Grok conversations for the project, newest first. `~/.grok/sessions` is partitioned by working directory (percent-encoded), so this is a directory listing; each conversation's `summary.json` supplies the title and the last-active time, falling back to the directory's `prompt_history.jsonl`. |
|
|
1828
|
+
| `GET /api/muse/sessions?cwd=` | Muse sessions for the project, newest first. Muse keeps one sqlite index for the machine (`~/.local/share/muse/session-index.db`, home overridable via `MUSE_HOME`) with the workspace recorded per row, so this is a query rather than a directory listing. |
|
|
1829
|
+
| `GET /api/copilot/sessions?cwd=` | Copilot sessions for the project, newest first. Copilot keeps one sqlite index for the machine (`~/.copilot/session-store.db`) with the working directory on each row, so this is a query rather than a directory walk. |
|
|
1830
|
+
| `GET /api/cursor/sessions?cwd=` | Cursor chats for the project, newest first. Cursor's project directory name is a truncated-and-hashed slug of the path, so this reads each directory's own `.workspace-trusted` to learn which one it stands for and omits any that does not say. |
|
|
1731
1831
|
| `GET /api/cost?cwd=&session=` | Estimated $ cost — session / today / month. |
|
|
1732
1832
|
| `GET /api/transcript/timeline?session=&cwd=` | Per-session activity timeline (tools run). |
|
|
1733
1833
|
| `GET /api/transcript/last-turn?session=&cwd=&agent=` | A session's last completed exchange (`prompt`, `reply`) plus the `text` to paste into another terminal. `agent=codex` reads the codex rollout instead of the Claude transcript. |
|
|
@@ -1845,17 +1945,19 @@ A non-JSON frame is written to the PTY verbatim (fallback).
|
|
|
1845
1945
|
|
|
1846
1946
|
### More WebSocket endpoints
|
|
1847
1947
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1948
|
+
The other raw WebSockets share the `/ws` frame format (`output` / `input` / `resize` / `exit`).
|
|
1949
|
+
**Every non-Claude agent has one** — `/ws/codex`, `/ws/antigravity`, `/ws/grok`, `/ws/muse`,
|
|
1950
|
+
`/ws/copilot`, `/ws/cursor` — and they take the same query and behave the same way; codex's is
|
|
1951
|
+
documented here as the representative one, and the per-agent differences are the matrix in
|
|
1952
|
+
[`docs/agent-capability-matrix.md`](docs/agent-capability-matrix.md).
|
|
1850
1953
|
|
|
1851
|
-
- **`/ws/codex?session=<id>&cwd=<dir>&gui=<0|1>`** — a **Codex** agent PTY
|
|
1852
|
-
[Agents: Claude & Codex](#agents-claude--codex)). Like `/ws` it sends a `session` frame
|
|
1954
|
+
- **`/ws/codex?session=<id>&cwd=<dir>&gui=<0|1>`** — a **Codex** agent PTY. Like `/ws` it sends a `session` frame
|
|
1853
1955
|
with the id and reattaches to a live or tmux-backed session on resume. `gui=0` (grid
|
|
1854
1956
|
cells) omits the GUI MCP and marks the session a grid terminal.
|
|
1855
1957
|
- **`/ws/launch?session=<id>&cwd=<dir>&launcher=<index>`** — a **launch command** PTY (a
|
|
1856
1958
|
plain shell, `codex`, or any command configured in Settings → Launch commands). Unlike a
|
|
1857
1959
|
Run-menu script it's **persistent and reattachable** (survives page switches /
|
|
1858
|
-
reconnects), but it
|
|
1960
|
+
reconnects), but it reports no activity of its own, so its dot only shows running vs. exited.
|
|
1859
1961
|
|
|
1860
1962
|
### WebSocket: `/ws/run` (command terminal)
|
|
1861
1963
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
// The launcher's "or resume here" list shows the conversations of whatever the Agent Picker has
|
|
4
4
|
// selected, and each agent keeps its history in its own place: Claude in `~/.claude/projects`,
|
|
5
5
|
// codex in `~/.codex/sessions`, agy in its brain directory (found through our own log), grok in a
|
|
6
|
-
// per-cwd directory under `~/.grok/sessions
|
|
7
|
-
//
|
|
6
|
+
// per-cwd directory under `~/.grok/sessions`, muse and copilot each in a sqlite index of their own,
|
|
7
|
+
// cursor under `~/.cursor/projects/<slug>/agent-transcripts`. One store, one lister and one route
|
|
8
|
+
// per agent — so the route is chosen, not built.
|
|
8
9
|
//
|
|
9
10
|
// In `common/` because both sides decide from it: the server mounts these paths
|
|
10
11
|
// (server/routes/session-routes.ts) and the UI fetches them (src/composables/useDirLists.ts). A
|
|
11
|
-
// `Record<TerminalAgent, …>` rather than a lookup with a fallback, so a
|
|
12
|
+
// `Record<TerminalAgent, …>` rather than a lookup with a fallback, so a NEW agent is a TYPE
|
|
12
13
|
// ERROR here instead of a picker option that silently lists Claude's history under another name —
|
|
13
14
|
// which is the bug this whole map exists to fix (#1417).
|
|
14
15
|
import type { TerminalAgent } from "./sessionAgent.js";
|
|
@@ -19,6 +20,8 @@ export const AGENT_SESSION_LIST_PATHS: Record<TerminalAgent, string> = {
|
|
|
19
20
|
antigravity: "/api/antigravity/sessions",
|
|
20
21
|
grok: "/api/grok/sessions",
|
|
21
22
|
muse: "/api/muse/sessions",
|
|
23
|
+
copilot: "/api/copilot/sessions",
|
|
24
|
+
cursor: "/api/cursor/sessions",
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
/** The listing URL for one agent's conversations in one directory. */
|
package/common/customAgents.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A CUSTOM AGENT: the user's own way of starting Claude Code.
|
|
2
2
|
//
|
|
3
|
-
// It is an entry in the AGENT PICKER, beside
|
|
3
|
+
// It is an entry in the AGENT PICKER, beside the built-in agents and Shell — NOT a
|
|
4
4
|
// launcher chip. The difference is the whole point of the two controls (see CLAUDE.md):
|
|
5
5
|
//
|
|
6
6
|
// - A launcher chip runs the user's command line VERBATIM. Nothing is inserted, no MCP is
|
|
@@ -64,7 +64,7 @@ export const CUSTOM_AGENT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
|
64
64
|
* which looks like the config was ignored. */
|
|
65
65
|
export const isCustomAgentId = (value: unknown): value is string => typeof value === "string" && CUSTOM_AGENT_ID_RE.test(value) && !isLaunchAgent(value);
|
|
66
66
|
|
|
67
|
-
// What the Agent Picker holds: one of the
|
|
67
|
+
// What the Agent Picker holds: one of the built-ins (`LAUNCH_AGENTS`), or `custom:<id>`.
|
|
68
68
|
//
|
|
69
69
|
// The prefix is what keeps the union honest. Bare ids would make the type `string`, and every
|
|
70
70
|
// `pick === "shell"` in a template would then be a comparison the compiler cannot check — while
|
package/common/guiMcpAgents.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Which agents are handed the WHOLE GUI MCP — every tool on one generated `mt` URL — when their
|
|
2
|
-
// session runs in the workspace,
|
|
3
|
-
// registered
|
|
2
|
+
// session runs in the workspace, which reach GUI tools only through what their directory
|
|
3
|
+
// registered, and — a third answer since cursor landed — which reach NEITHER because the file
|
|
4
|
+
// they read is one nothing here writes yet.
|
|
4
5
|
//
|
|
5
6
|
// In `common/` because BOTH sides decide from it and neither can derive it: the server decides it
|
|
6
7
|
// by which spawn path consults `carriesFullGuiMcp`, and the launcher form decides from it whether
|
|
@@ -33,7 +34,12 @@ import { type SessionAgent } from "./sessionAgent.js";
|
|
|
33
34
|
import { isLaunchAgent } from "./launchAgent.js";
|
|
34
35
|
import { customAgentFor, type AgentPick, type CustomAgent } from "./customAgents.js";
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// Copilot is IN, and it is the first agent added to this list since it was written. It earns the
|
|
38
|
+
// place the same way claude and codex do and agy/grok/muse cannot: `--additional-mcp-config` takes
|
|
39
|
+
// the payload per SPAWN — the same `{"mcpServers":{…}}` object claude's `--mcp-config` takes, as a
|
|
40
|
+
// JSON string — so a session-scoped URL can be handed to one session and not another. That is the
|
|
41
|
+
// whole test this list applies; everything else about the agent is irrelevant to it.
|
|
42
|
+
export const FULL_GUI_MCP_AGENTS = ["claude", "codex", "copilot"] as const;
|
|
37
43
|
|
|
38
44
|
export type FullGuiMcpAgent = (typeof FULL_GUI_MCP_AGENTS)[number];
|
|
39
45
|
|
|
@@ -52,3 +58,28 @@ export const pickCarriesFullGuiMcp = (pick: AgentPick, customAgents: readonly Cu
|
|
|
52
58
|
if (custom) return agentCarriesFullGuiMcp(custom.agent);
|
|
53
59
|
return isLaunchAgent(pick) && agentCarriesFullGuiMcp(pick);
|
|
54
60
|
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Agents that reach NEITHER route — not the per-spawn flag above, and not the directory's
|
|
64
|
+
* registration either.
|
|
65
|
+
*
|
|
66
|
+
* Cursor alone, and that is a GAP rather than a mechanism: it reads `.cursor/mcp.json` (or
|
|
67
|
+
* `~/.cursor/mcp.json`) and nothing in this repo writes either file yet (#2066). Until one does,
|
|
68
|
+
* the launcher's four per-group switches cannot affect a cursor session — they register servers
|
|
69
|
+
* that Antigravity and Grok read — so showing them under a cursor cell's "GUI tools" is a control
|
|
70
|
+
* that does nothing, which is #1423's lesson from the other side: that issue was about HIDING the
|
|
71
|
+
* only control that could have helped, and this is about SHOWING one that cannot.
|
|
72
|
+
*
|
|
73
|
+
* Delete this the day #2066 lands. It is a list of one because the condition is a missing writer,
|
|
74
|
+
* not a property of the CLI.
|
|
75
|
+
*/
|
|
76
|
+
export const DIRECTORY_MCP_BLIND_AGENTS = ["cursor"] as const;
|
|
77
|
+
|
|
78
|
+
export const agentReachesNoGuiMcp = (agent: SessionAgent): boolean => DIRECTORY_MCP_BLIND_AGENTS.some((candidate) => candidate === agent);
|
|
79
|
+
|
|
80
|
+
/** The same question asked of an Agent Picker choice. A custom agent runs Claude Code, which is on
|
|
81
|
+
* the per-spawn route, so it is never blind. */
|
|
82
|
+
export const pickReachesNoGuiMcp = (pick: AgentPick, customAgents: readonly CustomAgent[]): boolean => {
|
|
83
|
+
if (customAgentFor(pick, customAgents)) return false;
|
|
84
|
+
return isLaunchAgent(pick) && agentReachesNoGuiMcp(pick);
|
|
85
|
+
};
|
package/common/launchAgent.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Deliberately NOT the configured `launchers` list: those are arbitrary user commands, and
|
|
5
5
|
// the phone picks from a fixed set the host can name. "shell" is the OS default shell, the
|
|
6
6
|
// same thing the header's new-terminal button opens.
|
|
7
|
-
export const LAUNCH_AGENTS = ["shell", "claude", "codex", "antigravity", "grok", "muse"] as const;
|
|
7
|
+
export const LAUNCH_AGENTS = ["shell", "claude", "codex", "antigravity", "grok", "muse", "copilot", "cursor"] as const;
|
|
8
8
|
|
|
9
9
|
export type LaunchAgent = (typeof LAUNCH_AGENTS)[number];
|
|
10
10
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// What a browser is handed for a file, decided from its name alone.
|
|
2
|
+
//
|
|
3
|
+
// BOTH sides read this. The server sets it as the raw route's `Content-Type`
|
|
4
|
+
// (server/backends/rawServingPlan.ts); the terminal's file links ask the same question to decide
|
|
5
|
+
// where a click goes, because a type the browser cannot display is a type that turns a new tab
|
|
6
|
+
// into a silent download (#2038). Two copies of this table would send a click to a tab the server
|
|
7
|
+
// then made a download — which is the bug, arrived at from the other side.
|
|
8
|
+
|
|
9
|
+
import { SOURCE_CODE_EXTENSIONS } from "./sourceExtensions.js";
|
|
10
|
+
|
|
11
|
+
/** What a name with no extension is keyed by — the whole basename, lower-cased. A dotfile like
|
|
12
|
+
* `.gitignore` has no extension at all to `path.extname`, and keying it by "" would download it. */
|
|
13
|
+
const contentTypeKey = (name: string): { ext: string; textKey: string } => {
|
|
14
|
+
const base = name.replace(/^.*[/\\]/, "").toLowerCase();
|
|
15
|
+
const dot = base.lastIndexOf(".");
|
|
16
|
+
const ext = dot <= 0 ? "" : base.slice(dot);
|
|
17
|
+
return { ext, textKey: ext || base };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Source / docs / config files: serve as text so they VIEW in the browser instead of
|
|
21
|
+
// downloading (the file-path links in terminal output point at these — a code workspace's
|
|
22
|
+
// `.md`/`.ts`/… should open, not save). text/plain is never executed, so it stays safe under
|
|
23
|
+
// the sandbox CSP; a genuinely unknown extension still falls through to octet-stream (download).
|
|
24
|
+
const TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
25
|
+
|
|
26
|
+
const MIME_BY_EXT: Record<string, string> = {
|
|
27
|
+
".png": "image/png",
|
|
28
|
+
".jpg": "image/jpeg",
|
|
29
|
+
".jpeg": "image/jpeg",
|
|
30
|
+
".gif": "image/gif",
|
|
31
|
+
".webp": "image/webp",
|
|
32
|
+
".avif": "image/avif",
|
|
33
|
+
".svg": "image/svg+xml",
|
|
34
|
+
".bmp": "image/bmp",
|
|
35
|
+
".ico": "image/x-icon",
|
|
36
|
+
".pdf": "application/pdf",
|
|
37
|
+
".mp3": "audio/mpeg",
|
|
38
|
+
".m4a": "audio/mp4",
|
|
39
|
+
".wav": "audio/wav",
|
|
40
|
+
".ogg": "audio/ogg",
|
|
41
|
+
".mp4": "video/mp4",
|
|
42
|
+
".webm": "video/webm",
|
|
43
|
+
".mov": "video/quicktime",
|
|
44
|
+
".txt": "text/plain; charset=utf-8",
|
|
45
|
+
".json": "application/json; charset=utf-8",
|
|
46
|
+
".csv": "text/csv; charset=utf-8",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// On top of the shared source set: prose and markup the browser would otherwise render or
|
|
50
|
+
// download, the languages the client's in-app viewer doesn't claim, and the extensionless
|
|
51
|
+
// dotfiles (matched by basename — see `textKey` below). `.txt`/`.json`/`.csv` are absent
|
|
52
|
+
// because MIME_BY_EXT already types them.
|
|
53
|
+
const TEXT_ONLY_EXTS = [
|
|
54
|
+
".md",
|
|
55
|
+
".markdown",
|
|
56
|
+
".rst",
|
|
57
|
+
".adoc",
|
|
58
|
+
".mdx",
|
|
59
|
+
".pl",
|
|
60
|
+
".r",
|
|
61
|
+
".dart",
|
|
62
|
+
".ex",
|
|
63
|
+
".exs",
|
|
64
|
+
".env",
|
|
65
|
+
".properties",
|
|
66
|
+
".html",
|
|
67
|
+
".htm",
|
|
68
|
+
".gitignore",
|
|
69
|
+
".dockerignore",
|
|
70
|
+
".editorconfig",
|
|
71
|
+
".lock",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const TEXT_EXTS = new Set<string>([...SOURCE_CODE_EXTENSIONS, ...TEXT_ONLY_EXTS]);
|
|
75
|
+
|
|
76
|
+
/** The type the raw route serves `name` as. `application/octet-stream` is the "nothing here can
|
|
77
|
+
* render it" answer, and in a browser tab that means a download rather than a view. */
|
|
78
|
+
export function rawContentType(name: string): string {
|
|
79
|
+
const { ext, textKey } = contentTypeKey(name);
|
|
80
|
+
return MIME_BY_EXT[ext] ?? (TEXT_EXTS.has(textKey) ? TEXT_PLAIN : "application/octet-stream");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Whether a browser tab would DISPLAY this rather than download it. The question a file link has
|
|
84
|
+
* to answer before sending a click to a new tab (#2038). */
|
|
85
|
+
export const browserDisplays = (name: string): boolean => rawContentType(name) !== "application/octet-stream";
|