mulmoterminal 4.26.0 → 5.0.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 +26 -0
- package/common/codeBlocks.ts +33 -4
- package/common/collectionRefresh.ts +34 -0
- package/common/keymap.ts +12 -1
- package/common/themeColors.ts +17 -0
- package/common/themeVars.ts +20 -2
- package/common/transcriptView.ts +78 -0
- package/dist/assets/{AllPackages-B1YCJRYB-kggoNw6z.js → AllPackages-B1YCJRYB-4WQ_EIrl.js} +1 -1
- package/dist/assets/{BaseConfiguration-B6s_tKOW-RQzsRUNx.js → BaseConfiguration-B6s_tKOW-CuXGj6gD.js} +1 -1
- package/dist/assets/{Element-BMI7Nj5w-BP5E_zl7.js → Element-BMI7Nj5w-Bik3AfgD.js} +1 -1
- package/dist/assets/{Entities-C8p_0jCn-C-_0T6vJ.js → Entities-C8p_0jCn-CjiJEF3r.js} +1 -1
- package/dist/assets/{FunctionList-YWk54TCy-DyxSzbCI.js → FunctionList-YWk54TCy-Dq1GQL-M.js} +1 -1
- package/dist/assets/{InputJax-C5dj-ntL-BtwdMizb.js → InputJax-C5dj-ntL-DEjnMcSa.js} +1 -1
- package/dist/assets/{MathItem-YJvQL-XJ-CeeATAwp.js → MathItem-YJvQL-XJ-pMaMH7Pe.js} +1 -1
- package/dist/assets/{MmlFactory-DSoKkE7s-CYu-aCTG.js → MmlFactory-DSoKkE7s-BTFYdeyR.js} +1 -1
- package/dist/assets/{Options-oARQeeuT-TJSj2iQR.js → Options-oARQeeuT-DN-QpMwi.js} +1 -1
- package/dist/assets/{OutputJax-DfUZNNCu-oLxtQ_g0.js → OutputJax-DfUZNNCu-0eNVVspA.js} +1 -1
- package/dist/assets/{PrioritizedList-6lun1Xqd-D4eZQDMF.js → PrioritizedList-6lun1Xqd-B3xDwkAm.js} +1 -1
- package/dist/assets/{Styles-5NmwS-3M-B9XVBSSq.js → Styles-5NmwS-3M-CYH8SIw8.js} +1 -1
- package/dist/assets/{TeXAtom-B5ni9myz-B3RMDIY5.js → TeXAtom-B5ni9myz-B2Lg1OeP.js} +1 -1
- package/dist/assets/{abnfDiagram-VCTEODGH-BGhi-oVb-DQZhwQSy.js → abnfDiagram-VCTEODGH-BGhi-oVb-rfnIw5mF.js} +1 -1
- package/dist/assets/{arc-CK5ru50N-CnsmPLzy.js → arc-CK5ru50N-CpwYDtFh.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-otSkCkT5-BXYe9Y4x.js +1 -0
- package/dist/assets/{architectureDiagram-5GKGNRK7-ELuPYMlf-Ci2fG9OM.js → architectureDiagram-5GKGNRK7-ELuPYMlf-jRFBK2xY.js} +1 -1
- package/dist/assets/{assistive-mml-CdU3rUpx-PFS2LrE8.js → assistive-mml-CdU3rUpx-CfM4F5jW.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-BZ7-OGWQ-iSiAD4OV.js → blockDiagram-I7D4REHJ-BZ7-OGWQ-GPkhdnf1.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-DCovPZ_4-DLrbYW62.js → c4Diagram-7LVT6UL2-DCovPZ_4-DlgnltG1.js} +1 -1
- package/dist/assets/channel-Bmu_OOc6-Byl1e1Rn.js +1 -0
- package/dist/assets/{chunk-4HAMMTFA-DqpzLmar-Begag6kK.js → chunk-4HAMMTFA-DqpzLmar-BYwab83k.js} +1 -1
- package/dist/assets/{chunk-75Z2AOVW-CT5TYzp5-Che06AJn.js → chunk-75Z2AOVW-CT5TYzp5-BRr2sap2.js} +1 -1
- package/dist/assets/{chunk-DU6HZSFF-BVf-rtdf-BY6ORnqO.js → chunk-DU6HZSFF-BVf-rtdf-BiTD5bH2.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-rP8pUsk3-DnQrEHyt.js → chunk-F27PBJKO-rP8pUsk3-jZL8tnaO.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-D_IT0r3G-CZbWs1tw.js → chunk-IMKFNOWR-D_IT0r3G-4Na8DCGZ.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-C5YURnHe-Pr-7cHNE.js → chunk-POPQ4Y6H-C5YURnHe-BtvIAZQ9.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-CfF0J8Qh-BjabP1RZ.js → chunk-SHT3W25Y-CfF0J8Qh-CSKY6pei.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-CTZbRIbX-B_MoCNpo.js → chunk-SVP7TREG-CTZbRIbX-DXmyUlG6.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-DP1lYmZm-DBUczLZh.js → chunk-TICWLB2K-DP1lYmZm-2WQGl4am.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-D9SGVKJn.js → chunk-XXDRQBXY-vg0kfC73-BNbpCBp8.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-DGA1_qS0.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-DGA1_qS0.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-qIiD3RGD-BUItDgWJ.js → cose-bilkent-JH36ORCC-qIiD3RGD-glIzrMsu.js} +1 -1
- package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-DCyyQmwN.js +1 -0
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BZrUCS-v-Vi8fZx7J.js → cynefinDiagram-5FMLGOSQ-BZrUCS-v-BP9iYDiL.js} +1 -1
- package/dist/assets/{dagre-C2SZ8h4o-BotF2A9M.js → dagre-C2SZ8h4o-Cvn7MMN8.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CQcLM4na-DqruNADY.js → dagre-GXQ25YYZ-CQcLM4na-CUPIues9.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-BdmcmUQh-BpurSv4a.js → diagram-S7CK7UJ4-BdmcmUQh-BzmDaJl4.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-BtvmEiYl-XHbHkO0x.js → diagram-UQ7AKVKN-BtvmEiYl-C-SDkeQT.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-qEG6N0bV-Cr5ut8_j.js → diagram-VSXAHHWV-qEG6N0bV-BBEGoVq0.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-BA8is49N-Bq5gY7-i.js → diagram-VX7I27RA-BA8is49N-C_RijuTO.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-XPcU7IOQ-C0yHj4hB.js → diagram-Z3DM3KII-XPcU7IOQ-CxOwdwHR.js} +1 -1
- package/dist/assets/{dist-BIvNvTDO.js → dist-Bi70Jy-b.js} +1 -1
- package/dist/assets/{dist-BNoLRzs-.js → dist-BvG2w_9k.js} +1 -1
- package/dist/assets/{dist-Fs7Cl-b9.js → dist-CWF1S7vL.js} +1 -1
- package/dist/assets/{dist-OM0ZTf3D.js → dist-CXGYSqlO.js} +1 -1
- package/dist/assets/{dist-BwEUG-6y.js → dist-Cb59cZDS.js} +1 -1
- package/dist/assets/{dist-wMJX53du.js → dist-D2QMLIZp.js} +1 -1
- package/dist/assets/{dist-BP4I4Ek8.js → dist-D3PmXl_m.js} +1 -1
- package/dist/assets/{dist-CoNrzCGM.js → dist-DBRVDw9D.js} +1 -1
- package/dist/assets/{dist-DeiyD9Ki-CG6FY-Fn.js → dist-DeiyD9Ki-BDcmanZh.js} +1 -1
- package/dist/assets/{dist-CBAuHlC8.js → dist-Gt7_-BLP.js} +1 -1
- package/dist/assets/{dist-DgKptpvT.js → dist-uQlE_JYp.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-CTHIHdBS-BJwIOuLO.js → ebnfDiagram-PWID7BFC-CTHIHdBS-BGwyAx_X.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-DVpWTNZj-D8m4MgiI.js → erDiagram-RLTQ6QDP-DVpWTNZj-CpwnuTPx.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-DWVjLwmJ.js +1 -0
- package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-CjAJJOoL.js +1 -0
- package/dist/assets/{ganttDiagram-EL5Y4UJY-BfAgk15F-0jMcI0Yj.js → ganttDiagram-EL5Y4UJY-BfAgk15F-CuhMMDMU.js} +1 -1
- package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-CmU_NC2u.js +1 -0
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-DEVVZ6wt-CawiFyGd.js → gitGraphDiagram-WWUBYQGX-DEVVZ6wt-Bh_VaB4G.js} +1 -1
- package/dist/assets/{graphlib-BYLhHyYO-CEk4IQ-f.js → graphlib-BYLhHyYO-BCQclP1Y.js} +1 -1
- package/dist/assets/{html-Z7dGbczU-BwxpBy6_.js → html-Z7dGbczU-UIlR1xAp.js} +1 -1
- package/dist/assets/index-DgdW3UkV.css +1 -0
- package/dist/assets/{index-D7Oh_lqc.js → index-sxsMmFcI.js} +327 -379
- package/dist/assets/info-A6RAGUB7-fR2SUJmJ-Dc6y50d4.js +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-CvHUhAHd-Y8J_Kc2X.js → infoDiagram-27XIBGKW-CvHUhAHd-DigY259e.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-CeqyKdwk-q6ZNkcwN.js → ishikawaDiagram-5VMMS53U-CeqyKdwk-ACmU-Jzv.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Jfi7MPDi-Bv6pv8wY.js → journeyDiagram-3NMN7TZE-Jfi7MPDi-YIFwpJyP.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-yOrlojns-B2y6NbLe.js → kanban-definition-UXKFOSKX-yOrlojns-0RXVl3u9.js} +1 -1
- package/dist/assets/{lengths-CvmPfBid-CBfhrPT6.js → lengths-CvmPfBid-BAfNjGrF.js} +1 -1
- package/dist/assets/{linear-jdmEy-qs-oCunWRsH.js → linear-jdmEy-qs-Djjlqhox.js} +1 -1
- package/dist/assets/{liteAdaptor-pH5MXTG0-BY38m-jj.js → liteAdaptor-pH5MXTG0-pAc8cmKr.js} +1 -1
- package/dist/assets/{mathjax-DVEo4UYj-C7BTzxwK.js → mathjax-DVEo4UYj-Cnj1zcS1.js} +1 -1
- package/dist/assets/{mermaid-parser.core-CzBW8Fof-CixE9zmB.js → mermaid-parser.core-CzBW8Fof-s3bWPn0O.js} +2 -2
- package/dist/assets/{mermaid.core-Dv4uylek-Djmx6tFE.js → mermaid.core-Dv4uylek-DqGXjOPu.js} +4 -4
- package/dist/assets/{mindmap-definition-YA3MSWOX-DP_2R9Ga-kTAulFVa.js → mindmap-definition-YA3MSWOX-DP_2R9Ga-KrOUlEvG.js} +1 -1
- package/dist/assets/{mo-Xv_6CC7A-CcvkyIE0.js → mo-Xv_6CC7A-5T19MjVl.js} +1 -1
- package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-DzcrK2qY.js +1 -0
- package/dist/assets/{pegDiagram-XKGWAZYB-BsBWryhi-BiG1mp1T.js → pegDiagram-XKGWAZYB-BsBWryhi-DYwgFu60.js} +1 -1
- package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-EXLeDSJc.js +1 -0
- package/dist/assets/{pieDiagram-E7YTZNPT-OugkS2pU-C5WoCQDp.js → pieDiagram-E7YTZNPT-OugkS2pU-CitvFGku.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-DXEAAaUE-U0asGLtC.js → quadrantDiagram-AXDQQJYC-DXEAAaUE-BJz-5fWN.js} +1 -1
- package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-CYsT6Q2i.js +1 -0
- package/dist/assets/railroad-74A4TZTK-6lEXVJo8-CGKkcPl-.js +1 -0
- package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-CX4pHVjI.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-DuFCuwxK.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-BZCRCTz5.js +1 -0
- package/dist/assets/{railroadDiagram-O6MQD6OU-BJys8O96-Dcm_CJA0.js → railroadDiagram-O6MQD6OU-BJys8O96-8S8j18MZ.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-BaDgCtM5-BFyzmV4B.js → requirementDiagram-BXWQKSXE-BaDgCtM5-D0sLbDHs.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-puZsr_vK-CTerarXU.js → sankeyDiagram-P5KCCOFB-puZsr_vK-C1OX3N7F.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-D1ziEryC-Dk_Vh7EW.js → sequenceDiagram-WJ2MYXX4-D1ziEryC-CvoJhcsM.js} +1 -1
- package/dist/assets/{src-CLC0ddSd-dG3DpXd2.js → src-CLC0ddSd-D-EBSy-7.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-HSEurT6h-CpOTqfOS.js → stateDiagram-D77RDMKH-HSEurT6h-DWNnPlrT.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-jwvolGHy.js +1 -0
- package/dist/assets/{svg-D_RYJvQC-C4pZWbZ-.js → svg-D_RYJvQC-xxS_UOMd.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-umBsuvsI-C-uZTY-8.js → swimlanes-42K2YHIH-umBsuvsI-BE4MiInp.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-DNoFZykH.js +8 -0
- package/dist/assets/{tex-Dx2UJ3VU-Ch7WPvt4.js → tex-Dx2UJ3VU-Bvt244KZ.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-D9plt22Y-BgXy-Pja.js → timeline-definition-24CTP7MA-D9plt22Y-DHwEP0-q.js} +1 -1
- package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-Cw7gALPI.js +1 -0
- package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-BuBwqHqi.js +1 -0
- package/dist/assets/{vennDiagram-4TSXK5OY-DZe7wY85-HHFNkULb.js → vennDiagram-4TSXK5OY-DZe7wY85-BzqCxOQu.js} +1 -1
- package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-Ds6_n_M_.js +1 -0
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DK9yytWx-qeOMmNlR.js → wardleyDiagram-VM6X3IG4-DK9yytWx-FK9bW_eQ.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-CLSO4Pc9-DLpk6ryk.js → xychartDiagram-S5SC5T6Z-CLSO4Pc9-BGF6axe4.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +9 -9
- package/server/agents/copilot-sessions.ts +12 -4
- package/server/agents/sqlite-read.ts +1 -1
- package/server/backends/calendarRefresh.ts +37 -0
- package/server/backends/calendarRefreshResult.ts +40 -0
- package/server/backends/feeds.ts +40 -9
- package/server/backends/remoteHost/handlers/deps.ts +1 -1
- package/server/backends/remoteHost/handlers.spec.ts +1 -1
- package/server/backends/sharedApp/headlessPreview.ts +46 -4
- package/server/config/config-schema.ts +6 -3
- package/server/files/files-browse.ts +14 -0
- package/server/files/project-files.ts +222 -0
- package/server/infra/jsonl-file.ts +13 -3
- package/server/routes/app-routes.ts +1 -1
- package/server/routes/session-routes.ts +33 -0
- package/server/session/transcript-view-codex.ts +4 -3
- package/server/session/transcript-view-copilot.ts +11 -9
- package/server/session/transcript-view-cursor.ts +3 -2
- package/server/session/transcript-view-read.ts +214 -42
- package/server/session/transcript-view.ts +47 -43
- package/server/skills/mulmoterminal-dirs/SKILL.md +3 -1
- package/server/skills/mulmoterminal-keys/SKILL.md +6 -0
- package/server/skills/mulmoterminal-theme/SKILL.md +49 -7
- package/dist/assets/architecture-7GRP2DOG-otSkCkT5-CFnWjMcA.js +0 -1
- package/dist/assets/channel-Bmu_OOc6-CLX7xRxl.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-CH44RAkG.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-CH44RAkG.js +0 -1
- package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-Ce8mYJyA.js +0 -1
- package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-DnpJhuuA.js +0 -1
- package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-BWizd0qT.js +0 -1
- package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-BmNB3hui.js +0 -1
- package/dist/assets/index-BJzCebxZ.css +0 -1
- package/dist/assets/info-A6RAGUB7-fR2SUJmJ-DJ6qtBt_.js +0 -1
- package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-Pi3e-tAz.js +0 -1
- package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-BOglNRvk.js +0 -1
- package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-zXnWiLPM.js +0 -1
- package/dist/assets/railroad-74A4TZTK-6lEXVJo8-CAtjJocL.js +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-BzFi41zD.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-CFTlT-st.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-uG4QNP6w.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-DgkDdTbh.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-CxUAVNvR.js +0 -8
- package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-VCyiiE1t.js +0 -1
- package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-jg24gWZX.js +0 -1
- package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-hF5yPxTk.js +0 -1
package/README.md
CHANGED
|
@@ -1232,6 +1232,18 @@ The toggle is not the only way in: while a cell is enlarged, **clicking a file p
|
|
|
1232
1232
|
printed** opens it here too, rather than in a new tab or full-screen — see
|
|
1233
1233
|
[Clicking a file path](#clicking-a-file-path).
|
|
1234
1234
|
|
|
1235
|
+
**Open a file by name instead of walking the tree.** The pane's header has a **search** button
|
|
1236
|
+
that opens a finder over it: type part of a file's name or path and it narrows a list of every
|
|
1237
|
+
file in the project — fuzzily, so `fpane` finds `src/components/FilesPane.vue`. Arrow keys and
|
|
1238
|
+
Enter pick one; it opens in the editor **with the tree expanded down to it**, so the files
|
|
1239
|
+
around it are one click away. In a git repository the candidates come from `git ls-files`, so
|
|
1240
|
+
everything your `.gitignore` excludes — `node_modules` and the rest — is never offered; outside
|
|
1241
|
+
one the tree is walked instead: no ignore file is read, and only a short list of directories
|
|
1242
|
+
nobody authors by hand (`node_modules`, virtualenvs, caches) is skipped. The panel says so rather
|
|
1243
|
+
than letting you believe an ignore file was applied. There is also a
|
|
1244
|
+
[`files-find` shortcut](https://receptron.github.io/mulmoterminal/guide/en/config.html#keymap)
|
|
1245
|
+
with no default binding, which opens the pane first if it is closed.
|
|
1246
|
+
|
|
1235
1247
|
All reads and writes go through `GET/PUT /api/files/browse/*?cwd=&path=`, and every
|
|
1236
1248
|
`path` is **contained within the project root** (server-side) — `..`/absolute escapes
|
|
1237
1249
|
are rejected for reads and writes alike, so editing can't reach outside the directory
|
|
@@ -1629,6 +1641,20 @@ narrow and tall for one record you are discussing — and each position keeps it
|
|
|
1629
1641
|
prompt history, codex's rollout), so a prompt sent mid-turn is there and text a skill
|
|
1630
1642
|
injected is not. A `/clear` draws a line: the pane shows what you have asked *since*, the
|
|
1631
1643
|
same way the header, the title and the last reply all stop describing the ended conversation.
|
|
1644
|
+
- **Conversation pane** — the enlarged cell's conversation, read as TURNS rather than as the
|
|
1645
|
+
screen, from `GET /api/transcript/view`. A Claude cell runs on the alternate screen, so the
|
|
1646
|
+
terminal keeps no scrollback worth scrolling; this reads the agent's own transcript instead,
|
|
1647
|
+
and **scrolling up walks the session back to its first turn**, a page at a time.
|
|
1648
|
+
- It **takes the terminal's place when you open it** — the one pane that does, because reading
|
|
1649
|
+
is its whole subject. The expand button puts the terminal back beside it.
|
|
1650
|
+
- **A frame per speaker**: what you asked in one, what the agent answered in another, named
|
|
1651
|
+
("You" / "Claude"). The reply is **rendered markdown** — headings, lists, tables, fenced
|
|
1652
|
+
code — not the characters it is written with.
|
|
1653
|
+
- **Tool traffic is folded away**, one line saying what ran (`Bash · Read`); click to open it.
|
|
1654
|
+
It is most of a turn's bulk and almost none of what you came back to read.
|
|
1655
|
+
- A **snapshot**, not a live tail — the terminal beside it is the live view — and the header's
|
|
1656
|
+
reload fetches the newest turns again. Claude, Codex, Cursor and Copilot; an agent whose
|
|
1657
|
+
conversation no reader here can read yet says so rather than showing an empty pane.
|
|
1632
1658
|
- **Notifications** (🔔) — a toolbar bell with an unread badge and a dropdown of active
|
|
1633
1659
|
notifications; click a row to jump to its session.
|
|
1634
1660
|
- **Star MulmoTerminal** — a star button in the grid toolbar that stars the project on GitHub
|
package/common/codeBlocks.ts
CHANGED
|
@@ -55,9 +55,31 @@ const closes = (line: string, opener: string): boolean => {
|
|
|
55
55
|
return !!fence && fence.run[0] === opener[0] && fence.run.length >= opener.length && fence.info.trim() === "";
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
/** A run of prose, or one fenced block — the whole text, in order and with nothing dropped.
|
|
59
|
+
*
|
|
60
|
+
* This is the ONE walk over the fences, and `fencedBlocks` below is it with the prose dropped. It
|
|
61
|
+
* is not a second scanner: the two used to be separate loops over the same rules, which is two
|
|
62
|
+
* places for a fence rule to drift.
|
|
63
|
+
*
|
|
64
|
+
* It is deliberately not tied to any one reader. The transcript pane rendered its segments for a
|
|
65
|
+
* while and now renders the reply as markdown instead (#2112); saying otherwise here sends the next
|
|
66
|
+
* reader looking for a consumer that no longer exists (Claude review, round 1). */
|
|
67
|
+
export type MarkdownSegment = { kind: "text"; text: string } | { kind: "code"; lang: string | null; body: string };
|
|
68
|
+
|
|
69
|
+
/** The one walk over the fences. `fencedBlocks` is this with the prose dropped.
|
|
70
|
+
*
|
|
71
|
+
* Prose runs are joined with the newlines they had, so a run's own blank lines survive; a segment
|
|
72
|
+
* is emitted only when it has a line in it, so an input that opens with a fence does not lead with
|
|
73
|
+
* an empty one. */
|
|
74
|
+
export function markdownSegments(markdown: string): MarkdownSegment[] {
|
|
59
75
|
const lines = markdown.split("\n");
|
|
60
|
-
const
|
|
76
|
+
const segments: MarkdownSegment[] = [];
|
|
77
|
+
const prose: string[] = [];
|
|
78
|
+
const flushProse = (): void => {
|
|
79
|
+
if (prose.length === 0) return;
|
|
80
|
+
segments.push({ kind: "text", text: prose.join("\n") });
|
|
81
|
+
prose.length = 0;
|
|
82
|
+
};
|
|
61
83
|
let i = 0;
|
|
62
84
|
// Each line is read into a const before use: with noUncheckedIndexedAccess, `lines[i]` is
|
|
63
85
|
// `string | undefined` even inside `i < lines.length`, and the loop condition is what actually
|
|
@@ -66,9 +88,11 @@ export function fencedBlocks(markdown: string): FencedBlock[] {
|
|
|
66
88
|
const line = lines[i];
|
|
67
89
|
const open = line === undefined ? null : fenceOf(line);
|
|
68
90
|
if (!open) {
|
|
91
|
+
if (line !== undefined) prose.push(line);
|
|
69
92
|
i++;
|
|
70
93
|
continue;
|
|
71
94
|
}
|
|
95
|
+
flushProse();
|
|
72
96
|
const fence = open.run;
|
|
73
97
|
const info = open.info.trim();
|
|
74
98
|
const body: string[] = [];
|
|
@@ -78,9 +102,14 @@ export function fencedBlocks(markdown: string): FencedBlock[] {
|
|
|
78
102
|
i++;
|
|
79
103
|
}
|
|
80
104
|
i++; // step over the closing fence (or past the end, for an unclosed block)
|
|
81
|
-
|
|
105
|
+
segments.push({ kind: "code", lang: info ? (info.split(/\s+/)[0]?.toLowerCase() ?? null) : null, body: body.join("\n") });
|
|
82
106
|
}
|
|
83
|
-
|
|
107
|
+
flushProse();
|
|
108
|
+
return segments;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function fencedBlocks(markdown: string): FencedBlock[] {
|
|
112
|
+
return markdownSegments(markdown).flatMap((segment) => (segment.kind === "code" ? [{ lang: segment.lang, body: segment.body }] : []));
|
|
84
113
|
}
|
|
85
114
|
|
|
86
115
|
/** The block a reader means by "the code you just gave me": the last one. Null when the reply
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The `POST /api/collections/:slug/refresh` response. One shape for BOTH arms of that
|
|
2
|
+
// route — a declarative/agent feed's `ingest` re-run and a `googleCalendar` sync — because
|
|
3
|
+
// the collection view reads one result whichever arm answered, and two host-side shapes for
|
|
4
|
+
// one button is how a field ends up set by one arm and never read from the other.
|
|
5
|
+
//
|
|
6
|
+
// It mirrors MulmoClaude's `CollectionRefreshBody` (server/api/routes/collectionCalendarRefresh.ts)
|
|
7
|
+
// so the two hosts over the shared workspace answer the same plugin identically. Re-stated
|
|
8
|
+
// rather than imported, for `collectionPush.ts`'s reason: the plugin ships its
|
|
9
|
+
// `CollectionRefreshResult` from `@mulmoclaude/collection-plugin/vue`, and the server has no
|
|
10
|
+
// business pulling a Vue package in to describe its own response.
|
|
11
|
+
//
|
|
12
|
+
// An absent field is an ABSENT KEY, never an explicit `undefined`. That is what the wire carries
|
|
13
|
+
// either way (JSON.stringify drops an undefined value), and under `exactOptionalPropertyTypes` it
|
|
14
|
+
// is also what makes this assignable to the plugin's own `CollectionRefreshResult` — so the
|
|
15
|
+
// binding in `collectionUi.ts` type-checks the server's shape against the view's expectation
|
|
16
|
+
// instead of both sides describing it separately.
|
|
17
|
+
|
|
18
|
+
export interface CollectionRefreshResult {
|
|
19
|
+
/** Always true — "the refresh ran", not "records changed". A refusal is told through
|
|
20
|
+
* `errors`, the way the push's `pushed` works. */
|
|
21
|
+
refreshed: true;
|
|
22
|
+
written: number;
|
|
23
|
+
/** Why the refresh did not do what was asked. Read beside the button, so an empty list is
|
|
24
|
+
* the only thing that means "it worked". */
|
|
25
|
+
errors: string[];
|
|
26
|
+
/** Records a `googleCalendar` sync deleted (the event was cancelled in Google). Sent by the
|
|
27
|
+
* calendar arm only: the feed engine counts its own `maxItems` evictions, but the feed arm
|
|
28
|
+
* has never reported them and MulmoClaude's does not either. */
|
|
29
|
+
removed?: number;
|
|
30
|
+
/** An agent-ingest feed dispatched a worker session instead of writing records itself. */
|
|
31
|
+
dispatched?: boolean;
|
|
32
|
+
/** The dispatched session, for the view to navigate to. */
|
|
33
|
+
chatId?: string;
|
|
34
|
+
}
|
package/common/keymap.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const KEYMAP_ACTIONS = [
|
|
|
22
22
|
"terminal-new-adjacent",
|
|
23
23
|
"terminal-close",
|
|
24
24
|
"terminal-restart",
|
|
25
|
+
"files-find",
|
|
25
26
|
"copy",
|
|
26
27
|
"paste",
|
|
27
28
|
] as const;
|
|
@@ -49,7 +50,17 @@ export const TERMINAL_SCOPED_ACTIONS: readonly KeymapAction[] = ["copy", "paste"
|
|
|
49
50
|
// Here rather than beside gridShortcutFor because BOTH sides decide from it: the grid dispatches on
|
|
50
51
|
// it, and validateKeymap has to know that these decline the key — the handler returns WITHOUT
|
|
51
52
|
// stopping the event, so a `send` on the same keystroke fires instead (codex on #1906).
|
|
52
|
-
|
|
53
|
+
// `files-find` is here because the pane it opens exists only in the ENLARGED row (see
|
|
54
|
+
// docs/grid-view-modes.md) — a tiled grid has nowhere to put it, so the key declines rather than
|
|
55
|
+
// guessing which of nine terminals was meant.
|
|
56
|
+
export const NEEDS_A_CURRENT_TERMINAL: readonly KeymapAction[] = [
|
|
57
|
+
"zoom-next",
|
|
58
|
+
"zoom-prev",
|
|
59
|
+
"terminal-new-adjacent",
|
|
60
|
+
"terminal-close",
|
|
61
|
+
"terminal-restart",
|
|
62
|
+
"files-find",
|
|
63
|
+
];
|
|
53
64
|
|
|
54
65
|
// A key that puts BYTES into the focused terminal instead of running an app action (#1005) —
|
|
55
66
|
// Cmd+Right as Ctrl+E for end-of-line, say, or Alt+B for word-back.
|
package/common/themeColors.ts
CHANGED
|
@@ -28,3 +28,20 @@ export const THEME_COLOR_KEYS = [
|
|
|
28
28
|
"brightCyan",
|
|
29
29
|
"brightWhite",
|
|
30
30
|
] as const;
|
|
31
|
+
|
|
32
|
+
export type ThemeColorKey = (typeof THEME_COLOR_KEYS)[number];
|
|
33
|
+
|
|
34
|
+
/** What a palette colour may look like: xterm accepts `#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa`.
|
|
35
|
+
* Here rather than in the server schema because BOTH sides check it — the server rejects a config
|
|
36
|
+
* on it, and the client re-checks the response before the value reaches a canvas. Two copies of
|
|
37
|
+
* this pattern is how one side ends up accepting what the other drops. */
|
|
38
|
+
export const PALETTE_COLOR_RE = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
39
|
+
|
|
40
|
+
/** STRICT — surrounding whitespace is a reject, not something to trim away. The server's
|
|
41
|
+
* `paletteColor` trims before it matches and hands back the trimmed value, so everything this
|
|
42
|
+
* predicate legitimately sees has already been normalised; accepting `" #fff "` here would mean
|
|
43
|
+
* forwarding that exact string to xterm, which does not trim and cannot parse it. Validate the
|
|
44
|
+
* value you are about to pass on, not a cleaned-up version of it. */
|
|
45
|
+
export function isPaletteColor(value: unknown): value is string {
|
|
46
|
+
return typeof value === "string" && PALETTE_COLOR_RE.test(value);
|
|
47
|
+
}
|
package/common/themeVars.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// it, so a key known to one side and not the other would be accepted and then silently ignored.
|
|
5
5
|
|
|
6
6
|
import { THEME_IDS, type ThemeId } from "./themeIds.js";
|
|
7
|
+
import type { ThemeColorKey } from "./themeColors.js";
|
|
7
8
|
|
|
8
9
|
// The CSS custom properties one theme sets, mirroring the `:root[data-theme=...]` blocks in
|
|
9
10
|
// src/style.css. A theme with no `extends` has to supply every one of them — a half-painted
|
|
@@ -65,6 +66,10 @@ export interface CustomThemeInput {
|
|
|
65
66
|
/** A built-in theme to start from. Omitted means `colors` must be complete. */
|
|
66
67
|
extends?: ThemeId;
|
|
67
68
|
colors: Partial<ThemeVars>;
|
|
69
|
+
/** xterm palette entries set outright, for the canvas colours `colors` cannot reach: the
|
|
70
|
+
* cursor pair, the selection foreground, the 16 ANSI colours. Wins over what `colors`
|
|
71
|
+
* implies (#2097). */
|
|
72
|
+
term?: Partial<Record<ThemeColorKey, string>>;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/** The full variable set for a theme: the base it extends, with its own colours on top.
|
|
@@ -132,11 +137,24 @@ export function isLightTheme(vars: ThemeVars): boolean {
|
|
|
132
137
|
/** The xterm palette a theme's variables imply. xterm draws on a canvas and cannot read CSS
|
|
133
138
|
* variables, so the same colours have to reach it as values — derived here rather than asked
|
|
134
139
|
* for twice, since two copies of one colour is two chances to change only one. The 16 ANSI
|
|
135
|
-
* colours are NOT derived: they come from the base theme, which is what `extends` is for.
|
|
136
|
-
|
|
140
|
+
* colours are NOT derived: they come from the base theme, which is what `extends` is for.
|
|
141
|
+
*
|
|
142
|
+
* The cursor pair is derived from the SAME two variables the cell it sits on uses, which makes
|
|
143
|
+
* the cursor an inverted cell — so the character under it stays as readable as the characters
|
|
144
|
+
* beside it. Inheriting the cursor from the base instead is what made a light theme extending a
|
|
145
|
+
* dark one draw a dark block with xterm's default black glyph on top (#2097). */
|
|
146
|
+
export function termThemeFromVars(vars: ThemeVars): {
|
|
147
|
+
background: string;
|
|
148
|
+
foreground: string;
|
|
149
|
+
selectionBackground: string;
|
|
150
|
+
cursor: string;
|
|
151
|
+
cursorAccent: string;
|
|
152
|
+
} {
|
|
137
153
|
return {
|
|
138
154
|
background: vars["--bg-base"],
|
|
139
155
|
foreground: vars["--term-fg"],
|
|
140
156
|
selectionBackground: vars["--term-selection"],
|
|
157
|
+
cursor: vars["--term-fg"],
|
|
158
|
+
cursorAccent: vars["--bg-base"],
|
|
141
159
|
};
|
|
142
160
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// What a hosted agent's conversation looks like ON THE WIRE.
|
|
2
|
+
//
|
|
3
|
+
// In `common/` because both ends decide from it: the host folds an agent's JSONL (or copilot's
|
|
4
|
+
// table) into these shapes in `server/session/transcript-view.ts`, and two clients render them —
|
|
5
|
+
// the phone's terminal detail page over the remoteHost socket, and the browser's transcript pane
|
|
6
|
+
// over `/api/transcript/view`. Mirroring the shape into `src/` would let one side gain a status the
|
|
7
|
+
// other silently renders as nothing.
|
|
8
|
+
//
|
|
9
|
+
// The RULES that produce these — the turn boundary, the line budget, the tool-result cap — stay on
|
|
10
|
+
// the server. This file is the vocabulary, not the folding.
|
|
11
|
+
|
|
12
|
+
export type TranscriptRowKind = "user" | "assistant" | "tool" | "unknown";
|
|
13
|
+
|
|
14
|
+
/** One content block, rendered.
|
|
15
|
+
*
|
|
16
|
+
* `text` may itself contain newlines — an assistant answer is passed through whole, because the
|
|
17
|
+
* wrapping belongs to the client's CSS and not to a host that cannot know its width.
|
|
18
|
+
*
|
|
19
|
+
* `clipped` says THIS row's text was cut, which is a different fact from `TranscriptView.truncated`
|
|
20
|
+
* (a whole turn was dropped). Naming them the same word would leave the client unable to decide
|
|
21
|
+
* which mark to draw. */
|
|
22
|
+
export interface TranscriptRow {
|
|
23
|
+
kind: TranscriptRowKind;
|
|
24
|
+
text: string;
|
|
25
|
+
clipped?: boolean;
|
|
26
|
+
/** This tool row is the CALL — the tool's name and the head of its arguments — rather than what
|
|
27
|
+
* the tool answered (#2112).
|
|
28
|
+
*
|
|
29
|
+
* Said by the host because only the host can say it: both halves arrive as `kind: "tool"`, and a
|
|
30
|
+
* client telling them apart by shape ("short and single-line means a name") is guessing about
|
|
31
|
+
* four agents' output at once. A pane that collapses tool traffic needs it to label the fold with
|
|
32
|
+
* what RAN — the part a reader is looking for when they open it again. */
|
|
33
|
+
call?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One exchange: a user prompt and everything that followed it.
|
|
37
|
+
*
|
|
38
|
+
* `at` is the BOUNDARY record's own timestamp — the moment the turn started — or null when it is
|
|
39
|
+
* not a string. Null rather than dropping the turn: a turn is worth more than its clock. */
|
|
40
|
+
export interface TranscriptTurn {
|
|
41
|
+
at: string | null;
|
|
42
|
+
rows: TranscriptRow[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What the host answers. A discriminated union rather than "readable: boolean": "no transcript
|
|
46
|
+
* yet", "the conversation was ended with /clear" and "too big to find a turn in" are three
|
|
47
|
+
* different things to tell a person, and one boolean collapses them into the same blank view. */
|
|
48
|
+
export type TranscriptView =
|
|
49
|
+
| { status: "ok"; turns: TranscriptTurn[]; truncated: boolean }
|
|
50
|
+
| { status: "none" }
|
|
51
|
+
| { status: "cleared" }
|
|
52
|
+
| { status: "too-large" }
|
|
53
|
+
/** This session's agent keeps a conversation somewhere, and no reader here can read it yet
|
|
54
|
+
* (#1822). A DIFFERENT fact from `none`, which means "this session has written nothing we can
|
|
55
|
+
* find" — the phone falls back to the screen for both, but only one of them is worth a sentence
|
|
56
|
+
* to a person, and only one of them is a thing to go and implement.
|
|
57
|
+
*
|
|
58
|
+
* Never answered for a shell or a launcher cell: those have no conversation and never will, so
|
|
59
|
+
* the screen IS their content rather than a fallback from something missing. */
|
|
60
|
+
| { status: "not-supported" };
|
|
61
|
+
|
|
62
|
+
/** One page of a conversation, read backwards from `older` (#2112).
|
|
63
|
+
*
|
|
64
|
+
* The cursor is OPAQUE to the client on purpose: it is a byte offset for the three agents that
|
|
65
|
+
* keep a file and a `turn_index` for the one that keeps a table, and a client that knew which
|
|
66
|
+
* would be deciding something only the host can know. It is handed back verbatim to ask for the
|
|
67
|
+
* page before this one.
|
|
68
|
+
*
|
|
69
|
+
* `older: null` means this page reaches the start of what can be read — either the head of the
|
|
70
|
+
* transcript, or the point past which the host refuses to read (`status: "too-large"`).
|
|
71
|
+
*
|
|
72
|
+
* The view is carried whole rather than flattened in beside the cursor: every non-`ok` status is
|
|
73
|
+
* a sentence the client already knows how to show, and re-deciding them per page is how the two
|
|
74
|
+
* ends drift. */
|
|
75
|
+
export interface TranscriptPage {
|
|
76
|
+
view: TranscriptView;
|
|
77
|
+
older: string | null;
|
|
78
|
+
}
|