mulmoterminal 4.7.0 → 4.7.2

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.
Files changed (146) hide show
  1. package/README.md +9 -6
  2. package/bin/update-check.d.ts +10 -0
  3. package/bin/update-check.js +30 -15
  4. package/common/updateStatus.ts +42 -0
  5. package/common/worktreePath.ts +62 -0
  6. package/dist/assets/{abnfDiagram-VRR7QNED-WSmThe6b-D7o5fw1r.js → abnfDiagram-VRR7QNED-WSmThe6b-DknQ8qLF.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-C07fcF9Q.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Tj3i7Oow-qFko9kCE.js → architectureDiagram-ZJ3FMSHR-Tj3i7Oow-CfVlsJeY.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-c-hNYBhI-CixyWpNJ.js → blockDiagram-677ZJIJ3-c-hNYBhI-8WciGLbY.js} +1 -1
  10. package/dist/assets/{c4Diagram-LMCZKHZV-WIGVajf5-DAX3Om5N.js → c4Diagram-LMCZKHZV-WIGVajf5-4zxd1D-H.js} +1 -1
  11. package/dist/assets/channel-OqG5cGJi-BGDppPvY.js +1 -0
  12. package/dist/assets/{chunk-32BRIVSS-CtSq3QOR-BffBZP9f.js → chunk-32BRIVSS-CtSq3QOR-DSiMp8Df.js} +1 -1
  13. package/dist/assets/{chunk-52WLFC77-2Up0T-6t-oG1jhM8x.js → chunk-52WLFC77-2Up0T-6t-wBuEc9dl.js} +1 -1
  14. package/dist/assets/{chunk-C7G6YPKG-ivengO-7-BIJz2IVz.js → chunk-C7G6YPKG-ivengO-7-CsEaBceS.js} +1 -1
  15. package/dist/assets/{chunk-EX3LRPZG-BHoWQ6W8-Xx1eDNWz.js → chunk-EX3LRPZG-BHoWQ6W8-DyrLqwia.js} +1 -1
  16. package/dist/assets/{chunk-FWX5IMBZ-PvRXPghr-CsrDNNhN.js → chunk-FWX5IMBZ-PvRXPghr-DzGVj1EZ.js} +2 -2
  17. package/dist/assets/{chunk-HOUHSVGY-A3_zwY4q-DmY2m0Ak.js → chunk-HOUHSVGY-A3_zwY4q-gbpEybOh.js} +1 -1
  18. package/dist/assets/{chunk-ICXQ74PX-vzZqgurQ--OQCYCJd.js → chunk-ICXQ74PX-vzZqgurQ-CX8v32jV.js} +1 -1
  19. package/dist/assets/{chunk-MOJQB5TN-CorQ_Pl1-Bo-W29fw.js → chunk-MOJQB5TN-CorQ_Pl1-BbhhBfNZ.js} +1 -1
  20. package/dist/assets/{chunk-OGEWGWER-BVhfs7WG-C6-xivCq.js → chunk-OGEWGWER-BVhfs7WG-g8-Yk46a.js} +1 -1
  21. package/dist/assets/{chunk-PUDLZKDR-CJo7KQJ3-B3NhAbN0.js → chunk-PUDLZKDR-CJo7KQJ3-D54XyASB.js} +1 -1
  22. package/dist/assets/{chunk-Q4XR5HBZ-DLkq8ZGO-pa6s8c6B.js → chunk-Q4XR5HBZ-DLkq8ZGO-Cp9BjlFR.js} +1 -1
  23. package/dist/assets/{chunk-V7JOEXUC-DqefuUOb-dAvwkopm.js → chunk-V7JOEXUC-DqefuUOb-hfRs7DgI.js} +1 -1
  24. package/dist/assets/{chunk-VAUOI2AC-BKx35BBi-DF4roMbC.js → chunk-VAUOI2AC-BKx35BBi-_cGUYYkB.js} +1 -1
  25. package/dist/assets/{chunk-VR4S4FIN-DpJgr4Ur-OJFNZPOB.js → chunk-VR4S4FIN-DpJgr4Ur-BLwcW-oS.js} +1 -1
  26. package/dist/assets/{chunk-WYO6CB5R-nS6zX9ct-CEyECF4F.js → chunk-WYO6CB5R-nS6zX9ct-BCV_YuAf.js} +1 -1
  27. package/dist/assets/{chunk-ZGVPDNZ5-DNtiHxpJ-Du2jEWIg.js → chunk-ZGVPDNZ5-DNtiHxpJ-BZyncxN5.js} +1 -1
  28. package/dist/assets/classDiagram-OUVF2IWQ-C6h0nbT3-BDmsSI5o.js +1 -0
  29. package/dist/assets/classDiagram-v2-EOCWNBFH-CzsXJ_wA-BDmsSI5o.js +1 -0
  30. package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-DUAQ7cEo.js +1 -0
  31. package/dist/assets/{cynefinDiagram-TSTJHNR4-969y1qZK-Czc3nk-B.js → cynefinDiagram-TSTJHNR4-969y1qZK-R4IF-GDG.js} +1 -1
  32. package/dist/assets/{dagre-VKFMJZFB-CZfgAZFk-BAU9MQjJ.js → dagre-VKFMJZFB-CZfgAZFk-B3BU2W9n.js} +1 -1
  33. package/dist/assets/{diagram-FQU43EPY-CSdnleGM-DYTiEZKM.js → diagram-FQU43EPY-CSdnleGM-DlES3bH_.js} +1 -1
  34. package/dist/assets/{diagram-G47NLZAW-DKYe-Mwx-DDc61ZrT.js → diagram-G47NLZAW-DKYe-Mwx-TA1uLGcO.js} +1 -1
  35. package/dist/assets/{diagram-NH7WQ7WH-DMasKpwa-urCd5zHO.js → diagram-NH7WQ7WH-DMasKpwa-BoNLwqJM.js} +1 -1
  36. package/dist/assets/{diagram-OA4YK3LP-CSyc0BDR-HYgoBJgE.js → diagram-OA4YK3LP-CSyc0BDR-B7qjD4oD.js} +1 -1
  37. package/dist/assets/{diagram-WEI45ONY-CcJW2fNK-DgDEbPeN.js → diagram-WEI45ONY-CcJW2fNK-CJr_YFwh.js} +1 -1
  38. package/dist/assets/{dist-DmeqT16V.js → dist-BVmZUxag.js} +1 -1
  39. package/dist/assets/{dist-BmKUp6B_.js → dist-Bs26L6Uo.js} +1 -1
  40. package/dist/assets/{dist-De7z8hYT.js → dist-Ck2bcp79.js} +1 -1
  41. package/dist/assets/{dist-C5Zo-KHU.js → dist-hk584lKq.js} +1 -1
  42. package/dist/assets/{ebnfDiagram-CCIWWBDH-CeBUH2N0-CVbiuH2Y.js → ebnfDiagram-CCIWWBDH-CeBUH2N0-CJvdvEoE.js} +1 -1
  43. package/dist/assets/{erDiagram-Q63AITRT-1OOp34oV-BaP_H4cL.js → erDiagram-Q63AITRT-1OOp34oV-D7zchmQZ.js} +1 -1
  44. package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-LbMUWt0K.js +1 -0
  45. package/dist/assets/flowDiagram-23GEKE2U-DK0InIny-CgFlW5J-.js +1 -0
  46. package/dist/assets/{ganttDiagram-NO4QXBWP-DQT88dUZ-DtByhyeF.js → ganttDiagram-NO4QXBWP-DQT88dUZ-rzJQMynv.js} +1 -1
  47. package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-BKASBsfj.js +1 -0
  48. package/dist/assets/{gitGraphDiagram-IHSO6WYX-CeZGS4qi-CVchYIt9.js → gitGraphDiagram-IHSO6WYX-CeZGS4qi-DJXHtX8p.js} +1 -1
  49. package/dist/assets/index-Beuki558.css +1 -0
  50. package/dist/assets/index-D8Ue8uUi.js +621 -0
  51. package/dist/assets/info-DKCQHKI2-DPUCBYWS-B_uAl6tM.js +1 -0
  52. package/dist/assets/{infoDiagram-FWYZ7A6U-BDt8lBTy-CissNDjy.js → infoDiagram-FWYZ7A6U-BDt8lBTy-F6uWaK6Z.js} +1 -1
  53. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DOHYs_AA-CFtjTBXT.js → ishikawaDiagram-FXEZZL3T-DOHYs_AA-BaQGVSMu.js} +1 -1
  54. package/dist/assets/{journeyDiagram-5HDEW3XC-B33IW9yy-BcIIur36.js → journeyDiagram-5HDEW3XC-B33IW9yy-BDOLFh7j.js} +1 -1
  55. package/dist/assets/{kanban-definition-HUTT4EX6-Bkdm46eX-De883A50.js → kanban-definition-HUTT4EX6-Bkdm46eX-JRi19r7s.js} +1 -1
  56. package/dist/assets/{lib-B-7XHKN-.js → lib-Cew7_Jjt.js} +1 -1
  57. package/dist/assets/{line-Dy_Qlcgo-YN10axId.js → line-Dy_Qlcgo-BGhFl6cn.js} +1 -1
  58. package/dist/assets/{marp-jVDysiSn.js → marp-CNcv4E0w.js} +1 -1
  59. package/dist/assets/{mermaid-parser.core-C1baW7NG-D6V0bamn.js → mermaid-parser.core-C1baW7NG-CX-WYPvl.js} +2 -2
  60. package/dist/assets/{mermaid.core-C09q4TmN-2yfF6I6y.js → mermaid.core-C09q4TmN-BWcj-SjJ.js} +3 -3
  61. package/dist/assets/{mindmap-definition-LN4V7U3C-bfYIhPQ0-CKAbVaUS.js → mindmap-definition-LN4V7U3C-bfYIhPQ0-Doeeu3c_.js} +1 -1
  62. package/dist/assets/packet-7NZHBO7P-CC7His0F-C-GhzDeh.js +1 -0
  63. package/dist/assets/{pegDiagram-2B236MQR-CZQOKUMC-CC0RIOMO.js → pegDiagram-2B236MQR-CZQOKUMC-BL664l6e.js} +1 -1
  64. package/dist/assets/pie-RZYD4A2V-CCrcq2sL-DH3JEn97.js +1 -0
  65. package/dist/assets/{pieDiagram-ENE6RG2P-DiVKPdHE-PJb2uZUl.js → pieDiagram-ENE6RG2P-DiVKPdHE-D2F0muw8.js} +1 -1
  66. package/dist/assets/{quadrantDiagram-ABIIQ3AL-CbPY3kXI-CpeIJ5cW.js → quadrantDiagram-ABIIQ3AL-CbPY3kXI-BuluyAhk.js} +1 -1
  67. package/dist/assets/radar-I7S5WNFK-C5TUrpHf-CKlKMnx4.js +1 -0
  68. package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-9xlZj7yu.js +1 -0
  69. package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-DwYHEnpr.js +1 -0
  70. package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-DDuohTo0.js +1 -0
  71. package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-CIg-L0qV.js +1 -0
  72. package/dist/assets/{railroadDiagram-RFXS5EU6-CFRaQzr0-_qHVckh1.js → railroadDiagram-RFXS5EU6-CFRaQzr0-Bz5m6f6w.js} +1 -1
  73. package/dist/assets/{requirementDiagram-TGXJPOKE-BiIDduje-DGWofb8o.js → requirementDiagram-TGXJPOKE-BiIDduje-DhmVPO1X.js} +1 -1
  74. package/dist/assets/{sankeyDiagram-HTMAVEWB-DcK87A5Y-Bi4ngM9j.js → sankeyDiagram-HTMAVEWB-DcK87A5Y-DrXbGSsa.js} +1 -1
  75. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DgEQYcvc-D1J0YDNs.js → sequenceDiagram-DBY2YBRQ-DgEQYcvc-NTL4MjQg.js} +1 -1
  76. package/dist/assets/{stateDiagram-2N3HPSRC-Bg7EXtW2-DAsjkoLJ.js → stateDiagram-2N3HPSRC-Bg7EXtW2-o5YFIh15.js} +1 -1
  77. package/dist/assets/stateDiagram-v2-6OUMAXLB-BjhArXP_-BBigPgnO.js +1 -0
  78. package/dist/assets/{swimlanes-5IMT3BWC-CKORDKr0-DXiMSf2W.js → swimlanes-5IMT3BWC-CKORDKr0-4I1aelc5.js} +1 -1
  79. package/dist/assets/swimlanesDiagram-G3AALYLV-CyvIfl1o-C-cNxszL.js +8 -0
  80. package/dist/assets/{timeline-definition-FHXFAJF6-BM3Iky2r-DOheaxIs.js → timeline-definition-FHXFAJF6-BM3Iky2r-ByvmDJln.js} +1 -1
  81. package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-DYp5xd4_.js +1 -0
  82. package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-DRarugfQ.js +1 -0
  83. package/dist/assets/{vennDiagram-L72KCM5P-VNmAhEFP-C2GWgfPO.js → vennDiagram-L72KCM5P-VNmAhEFP-B929kBwl.js} +1 -1
  84. package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-BFJCJrkY.js +1 -0
  85. package/dist/assets/{wardleyDiagram-EHGQE667-BkHQpyFm-C-h5twdG.js → wardleyDiagram-EHGQE667-BkHQpyFm-DwbsrtZu.js} +1 -1
  86. package/dist/assets/{xychartDiagram-FW5EYKEG-Yz-8aLqK-CUZcHWQQ.js → xychartDiagram-FW5EYKEG-Yz-8aLqK-rmfx08e3.js} +1 -1
  87. package/dist/index.html +2 -2
  88. package/package.json +1 -1
  89. package/server/agents/antigravity-mcp.ts +5 -0
  90. package/server/agents/antigravity-session.ts +5 -1
  91. package/server/agents/antigravity-skills.ts +82 -0
  92. package/server/agents/codex-session.ts +16 -2
  93. package/server/agents/git-exclude.ts +32 -10
  94. package/server/agents/grok-session.ts +18 -1
  95. package/server/agents/muse-session.ts +16 -6
  96. package/server/backends/collectionActionGuards.ts +67 -0
  97. package/server/backends/collections.ts +11 -42
  98. package/server/backends/customViewRoutes.ts +9 -28
  99. package/server/backends/workspaceSetup.ts +15 -0
  100. package/server/config/config-routes.ts +12 -1
  101. package/server/config/cwd-presets.ts +17 -4
  102. package/server/config/update-status.ts +21 -19
  103. package/server/files/pick-file.ts +48 -22
  104. package/server/git/worktree-routes.ts +5 -2
  105. package/server/infra/symlink-guard.ts +27 -0
  106. package/server/infra/tmux.ts +9 -1
  107. package/server/routes/session-routes.ts +21 -12
  108. package/server/routes/ws-routes.ts +229 -102
  109. package/server/session/codex-activity-track.ts +10 -3
  110. package/server/session/codex-activity-watch.ts +30 -0
  111. package/server/session/dir-session.ts +136 -12
  112. package/server/session/pty-spawn.ts +8 -0
  113. package/server/session/reap-idle-sessions.ts +7 -0
  114. package/server/session/registry.ts +40 -1
  115. package/server/session/session-settings.ts +59 -1
  116. package/server/session/spawn-antigravity.ts +31 -10
  117. package/server/session/spawn-codex.ts +28 -3
  118. package/server/session/spawn-grok.ts +26 -9
  119. package/server/session/spawn-muse.ts +13 -2
  120. package/server/session/surviving-sessions.ts +2 -0
  121. package/server/session/survivor-agent-guard.ts +106 -0
  122. package/server/session/worktree-session-limit.ts +2 -2
  123. package/server/skills/mulmoterminal-dirs/SKILL.md +3 -1
  124. package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-R3ZCNmlM.js +0 -1
  125. package/dist/assets/channel-OqG5cGJi-BwbBOK-P.js +0 -1
  126. package/dist/assets/classDiagram-OUVF2IWQ-C6h0nbT3-9x_ObMZR.js +0 -1
  127. package/dist/assets/classDiagram-v2-EOCWNBFH-CzsXJ_wA-9x_ObMZR.js +0 -1
  128. package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-CUSoofIc.js +0 -1
  129. package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-COMVRYlu.js +0 -1
  130. package/dist/assets/flowDiagram-23GEKE2U-DK0InIny-BGzwORBA.js +0 -1
  131. package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-p99B1sB1.js +0 -1
  132. package/dist/assets/index-BShBNIbp.js +0 -621
  133. package/dist/assets/index-C96r_nGv.css +0 -1
  134. package/dist/assets/info-DKCQHKI2-DPUCBYWS-B3Xogxjv.js +0 -1
  135. package/dist/assets/packet-7NZHBO7P-CC7His0F-DK0gG3W_.js +0 -1
  136. package/dist/assets/pie-RZYD4A2V-CCrcq2sL-tVbhk8PH.js +0 -1
  137. package/dist/assets/radar-I7S5WNFK-C5TUrpHf-C6nl9qN3.js +0 -1
  138. package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-B0tG64BI.js +0 -1
  139. package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-xn6rR9WF.js +0 -1
  140. package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-Cf1RsQF2.js +0 -1
  141. package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-DS1TVkAn.js +0 -1
  142. package/dist/assets/stateDiagram-v2-6OUMAXLB-BjhArXP_-ByBnkuoH.js +0 -1
  143. package/dist/assets/swimlanesDiagram-G3AALYLV-CyvIfl1o-rxdxX3BL.js +0 -8
  144. package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-RLAgEKh2.js +0 -1
  145. package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-YdjbZwx5.js +0 -1
  146. package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-DfKSfsi1.js +0 -1
package/README.md CHANGED
@@ -567,12 +567,12 @@ CLAUDE_CWD=/Users/you/my-project
567
567
 
568
568
  ### UI settings (`~/.mulmoterminal/config.json`)
569
569
 
570
- The Settings modal () persists per-user UI choices to `~/.mulmoterminal/config.json`
570
+ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmoterminal/config.json`
571
571
  (read/written via `GET`/`POST /api/config`):
572
572
 
573
573
  ![The Settings modal — theme, notification sound, PR repos, launch commands, and MCP servers](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/settings.png)
574
574
 
575
- *Open it from the button in the toolbar. Pick a **theme**, set the **terminal font size**, **font** and **scroll speed**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, register your own **MCP servers**, and turn on the switches for what this app writes on your behalf — **issue work comments**, the **PR clone footer**, the **closing summary**, the **decision digest**, the **dev worklog** — no need to hand-edit the config file. Four settings stay with their skill because a form would be the wrong tool for them (`keymap`, `themes`, `providers`, `buttons`/`chips`); Settings shows what each is doing now and launches that skill. Note that **theme, font size and scroll speed are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
575
+ *Open it from the gear button in the toolbar. Under the title, a **Version** row shows what is running: the version from the shipped `package.json`, plus a `commit <sha>` chip on a git checkout — there the version is only whatever was last released, so the commit is what identifies the build. When something newer exists, the row is followed by the header badge's update notice, command included. Pick a **theme**, set the **terminal font size**, **font** and **scroll speed**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, register your own **MCP servers**, and turn on the switches for what this app writes on your behalf — **issue work comments**, the **PR clone footer**, the **closing summary**, the **decision digest**, the **dev worklog** — no need to hand-edit the config file. Four settings stay with their skill because a form would be the wrong tool for them (`keymap`, `themes`, `providers`, `buttons`/`chips`); Settings shows what each is doing now and launches that skill. Note that **theme, font size and scroll speed are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
576
576
 
577
577
  | Field | Meaning |
578
578
  | ------------ | ------- |
@@ -847,7 +847,9 @@ In dev, open the Vite URL; its proxy forwards `/ws`, `/ws/pubsub`, and `/api` to
847
847
 
848
848
  An empty grid cell's launcher sets the **Working directory** by typing, by a preset
849
849
  chip, or with the **📁 folder button** (a native OS folder dialog). The preset chips are
850
- the directories you have launched in; the **workspace** leads them always, labelled
850
+ the directories you have launched in **worktrees excluded**, since one is a single task's
851
+ branch that is deleted with the task, not a place to launch in again; the
852
+ **workspace** leads them always, labelled
851
853
  **WORKSPACE** and marked with an icon, whether or not you have ever launched there — it is
852
854
  the one directory where a claude or codex session reaches every GUI tool (agy and grok get
853
855
  what the directory registered, there as anywhere — see
@@ -879,7 +881,7 @@ alongside the Claude sessions. Scripts are **per-directory**: the cell reads the
879
881
  different projects' scripts.
880
882
 
881
883
  The same launcher also has an **or launch** row for your configured **launch commands**
882
- — any interactive command — set in Settings () → **Launch commands** as
884
+ — any interactive command — set in Settings (the gear button) → **Launch commands** as
883
885
  `{ label, command }` (e.g. `htop` → `htop`, `Codex` → `codex`). A plain shell needs no
884
886
  entry here: the Agent Picker's **Shell** option already opens `$SHELL`. Unlike
885
887
  a one-shot script, a launcher runs as a **persistent terminal in the cell's directory**:
@@ -1170,7 +1172,7 @@ with no schema on disk, so the fields are identified by measurement (see
1170
1172
  rather than a number it is unsure of, so if a future agy release moves those fields, an
1171
1173
  Antigravity cell falls back to showing its model alone — it will not show a wrong percentage.
1172
1174
 
1173
- The **Settings** modal () shows an **estimated $ cost** — Session / Today / Month — from
1175
+ The **Settings** modal (the gear button) shows an **estimated $ cost** — Session / Today / Month — from
1174
1176
  `GET /api/cost`, using a built-in public per-model price table (cache reads billed at
1175
1177
  0.1×, cache writes at 1.25× input). It's an estimate: real billing differs, **flat-plan
1176
1178
  (Max) usage isn't reflected**, and turns on unpriced models are flagged and excluded.
@@ -1611,6 +1613,7 @@ From a shell: `mulmoterminal room read <room>` · `room post <room> <text…> [-
1611
1613
  | `GET /api/sound?kind=` · `/api/dir-sound?cwd=&kind=` · `/api/sound-preset/:id` · `/api/dir-config?cwd=` | Custom / per-directory / preset attention sound + per-dir config. `kind` selects a config entry, never a path. |
1612
1614
  | `GET /api/dir-config-detail?cwd=` | The same per-dir config, **plus** the settings a running terminal doesn't need (`provider`, `model`, `skills`, `addDirs`, header button/chip **labels**), **plus** which keys the file set and how each fared (applied / dropped in validation / not a setting at all). Read-only; backs the Settings modal's **Directory settings** preview. Unlike the other `?cwd=` routes this one does **not** fall back to the default workspace — it reports on the directory it was asked about, so a path that no longer exists comes back as `exists:false`. Sound paths and button commands stay server-side. |
1613
1615
  | `GET /api/launch-options` | The Anthropic-compatible backends this server can reach, each with its models and — when it can't — the reason. Reports the **name** of the env var a key is read from, never the key. |
1616
+ | `GET /api/update-status` | What is running and whether anything newer exists: `install` (`npm` / `git`), `version`, `commit` (a checkout's short HEAD sha), `latest` (npm, only when newer) and the one-line `notice`. Backs the header's **Update** badge and the Settings version line. Computed once at startup and served from memory — `ready` is false until that check lands. |
1614
1617
  | `GET /api/notifications`(`/history`) · `POST /api/notifications/:id/clear` | Notification feed. |
1615
1618
  | `POST /api/transcribe`(`/model`…) | Voice-input transcription (Whisper, macOS). |
1616
1619
  | `POST /api/translation` | Runtime UI-string translation. |
@@ -1958,7 +1961,7 @@ src/
1958
1961
  PrsOverlay.vue cross-repo PRs & Issues
1959
1962
  Wiki*View.vue, Collections*.vue, AccountingOverlay.vue workspace views
1960
1963
  TimelineOverlay.vue, ToolsPane.vue, NotificationBell.vue, RemoteHostControl.vue
1961
- SettingsModal.vue settings — the dialog shell + section order
1964
+ SettingsModal.vue settings — the dialog shell + section order
1962
1965
  settings/ one file per settings section (theme, sounds,
1963
1966
  web push, google, PR repos, launchers, quick
1964
1967
  commands, MCP, cost, shortcuts, …), plus the
@@ -6,6 +6,16 @@ export interface UpdateNoticeDeps {
6
6
  fetchLatest?: () => Promise<string | null>;
7
7
  }
8
8
  export declare function computeUpdateNotice(pkgDir: string, currentVersion: string, deps?: UpdateNoticeDeps): Promise<string | null>;
9
+ export declare function readInstallInfo(
10
+ pkgDir: string,
11
+ currentVersion: string,
12
+ deps?: UpdateNoticeDeps,
13
+ ): Promise<Pick<import("../common/updateStatus.js").UpdateStatus, "install" | "version" | "commit">>;
14
+ export declare function computeUpdateInfo(
15
+ pkgDir: string,
16
+ currentVersion: string,
17
+ deps?: UpdateNoticeDeps,
18
+ ): Promise<Omit<import("../common/updateStatus.js").UpdateStatus, "ready">>;
9
19
  export declare function isUpdateCheckDisabled(env: Record<string, string | undefined>): boolean;
10
20
  export declare function hasNodeModulesSegment(pkgDir: string): boolean;
11
21
  export declare function classifyInstall(pkgDir: string, isGitWorkTree: boolean): "npm" | "git";
@@ -168,15 +168,12 @@ export function gitUpdateNotice({ localSha, localShort, remoteSha, defaultBranch
168
168
  const LS_REMOTE_TIMEOUT_MS = 6000;
169
169
 
170
170
  // The git branch of the check: local HEAD vs the remote's, read with ls-remote so no fetch is
171
- // forced. Silent on a dirty tree (can't fast-forward) or any unreadable probe.
172
- async function gitUpdateNotice_(git) {
171
+ // forced. Silent on a dirty tree (can't fast-forward) or any unreadable probe. `localShort` is
172
+ // passed in because the caller has already read it for the version display.
173
+ async function gitUpdateNotice_(git, localShort) {
173
174
  const status = await git(["status", "--porcelain"]);
174
175
  if (status === null || isTreeDirtyForUpdate(status)) return null;
175
- const [localSha, localShort, lsRemote] = await Promise.all([
176
- git(["rev-parse", "HEAD"]),
177
- git(["rev-parse", "--short", "HEAD"]),
178
- git(["ls-remote", "--symref", "origin", "HEAD"], LS_REMOTE_TIMEOUT_MS),
179
- ]);
176
+ const [localSha, lsRemote] = await Promise.all([git(["rev-parse", "HEAD"]), git(["ls-remote", "--symref", "origin", "HEAD"], LS_REMOTE_TIMEOUT_MS)]);
180
177
  return gitUpdateNotice({
181
178
  localSha,
182
179
  localShort,
@@ -186,14 +183,32 @@ async function gitUpdateNotice_(git) {
186
183
  });
187
184
  }
188
185
 
189
- // The whole check, front to back: which install this is, then its notice (or null when
190
- // current). `pkgDir` is where the tool livesa node_modules dir (→ npm) or a bare checkout
191
- // (→ git). `deps` lets tests drive it without spawning git or hitting the network; production
192
- // callers pass nothing and get the real git/registry probes bound to pkgDir.
193
- export async function computeUpdateNotice(pkgDir, currentVersion, deps = {}) {
186
+ // What is running, without asking the network: the install kind and for a checkout, where the
187
+ // package.json version is only the last releasethe commit that identifies the build. Local
188
+ // probes only, so this is also what the UI gets when the update check is opted out.
189
+ export async function readInstallInfo(pkgDir, currentVersion, deps = {}) {
194
190
  const git = deps.runGit ?? ((args, timeout_ms) => runGit(pkgDir, args, timeout_ms));
195
- const fetchLatest = deps.fetchLatest ?? fetchLatestVersion;
196
191
  const inWorkTree = hasNodeModulesSegment(pkgDir) ? false : (await git(["rev-parse", "--is-inside-work-tree"])) === "true";
197
- if (classifyInstall(pkgDir, inWorkTree) === "git") return gitUpdateNotice_(git);
198
- return npmUpdateNotice(currentVersion, await fetchLatest());
192
+ const install = classifyInstall(pkgDir, inWorkTree);
193
+ return { install, version: currentVersion, commit: install === "git" ? await git(["rev-parse", "--short", "HEAD"]) : null };
194
+ }
195
+
196
+ // The whole check, front to back: what is running, what is newer, and the one line that says so
197
+ // (null when current). `pkgDir` is where the tool lives — a node_modules dir (→ npm) or a bare
198
+ // checkout (→ git). `deps` lets tests drive it without spawning git or hitting the network;
199
+ // production callers pass nothing and get the real git/registry probes bound to pkgDir.
200
+ export async function computeUpdateInfo(pkgDir, currentVersion, deps = {}) {
201
+ const git = deps.runGit ?? ((args, timeout_ms) => runGit(pkgDir, args, timeout_ms));
202
+ const fetchLatest = deps.fetchLatest ?? fetchLatestVersion;
203
+ const info = await readInstallInfo(pkgDir, currentVersion, { runGit: git });
204
+ if (info.install === "git") return { ...info, latest: null, notice: await gitUpdateNotice_(git, info.commit) };
205
+ const latest = await fetchLatest();
206
+ // `latest` is what the UI OFFERS, so it carries only a version worth moving to — the registry
207
+ // answering with the version already installed is not news.
208
+ return { ...info, latest: latest && isNewerVersion(latest, currentVersion) ? latest : null, notice: npmUpdateNotice(currentVersion, latest) };
209
+ }
210
+
211
+ // The notice alone, for the launcher's console line.
212
+ export async function computeUpdateNotice(pkgDir, currentVersion, deps = {}) {
213
+ return (await computeUpdateInfo(pkgDir, currentVersion, deps)).notice;
199
214
  }
@@ -0,0 +1,42 @@
1
+ // What GET /api/update-status answers: which install is running, what it is, and what is newer.
2
+ // Shared because both sides decide from it — the server builds it from the startup probe, the
3
+ // header badge and the Settings version line read it.
4
+
5
+ import { isRecord } from "./isRecord.js";
6
+
7
+ /** How the tool was installed, which decides what "the running version" even means. */
8
+ export type InstallKind = "npm" | "git";
9
+
10
+ export interface UpdateStatus {
11
+ /** False until the startup check has landed; `commit` / `latest` / `notice` are not final yet. */
12
+ ready: boolean;
13
+ install: InstallKind;
14
+ /** The shipped package.json version. For a git checkout this is the last release, not the build. */
15
+ version: string;
16
+ /** git installs: short HEAD sha — the only thing that identifies which build is running. */
17
+ commit: string | null;
18
+ /** npm installs: the registry's version, present only when it is newer than `version`. */
19
+ latest: string | null;
20
+ /** The one-line "update available" rendering both the launcher and the header badge show. */
21
+ notice: string | null;
22
+ }
23
+
24
+ const isInstallKind = (value: unknown): value is InstallKind => value === "npm" || value === "git";
25
+
26
+ const stringOrNull = (value: unknown): string | null => (typeof value === "string" ? value : null);
27
+
28
+ /**
29
+ * The response as a status, or null when it is not one. Null rather than a filled-in default: a
30
+ * body we could not read must leave the version line hidden, not print a version nobody is on.
31
+ */
32
+ export function parseUpdateStatus(body: unknown): UpdateStatus | null {
33
+ if (!isRecord(body) || !isInstallKind(body.install) || typeof body.version !== "string") return null;
34
+ return {
35
+ ready: body.ready === true,
36
+ install: body.install,
37
+ version: body.version,
38
+ commit: stringOrNull(body.commit),
39
+ latest: stringOrNull(body.latest),
40
+ notice: stringOrNull(body.notice),
41
+ };
42
+ }
@@ -0,0 +1,62 @@
1
+ // What a managed worktree's path says about it, for the two sides that both have to ask.
2
+ //
3
+ // In `common/` because the launch form and `mulmoterminal init` each decide from it — the browser
4
+ // records the directory a cell launched in, the CLI seeds the same list from Claude's history — and
5
+ // a rule mirrored into `src/` and `server/` is a rule that drifts.
6
+
7
+ import { dirPathKey } from "./dirPathKey.js";
8
+
9
+ // A managed worktree lives at <root>/<repo>-<8hex>/<task> (see the server's `worktreesRoot`).
10
+ const MANAGED_DIR = /^(.+)-[0-9a-f]{8}$/;
11
+
12
+ /**
13
+ * The repo and task to CALL a worktree path, for a header that would otherwise show a long managed
14
+ * path. Shape alone, with no idea where the managed root is — which is why it is only ever used to
15
+ * name something. Getting it wrong shows a label; it decides nothing.
16
+ */
17
+ export function worktreeLabel(cwd: string | null): { repo: string; task: string } | null {
18
+ if (!cwd) return null;
19
+ const parts = cwd.split(/[/\\]/).filter(Boolean);
20
+ const i = parts.indexOf("worktrees");
21
+ const dir = parts[i + 1];
22
+ const task = parts[i + 2];
23
+ if (i < 0 || !dir || !task) return null;
24
+ const m = MANAGED_DIR.exec(dir);
25
+ return m?.[1] === undefined ? null : { repo: m[1], task };
26
+ }
27
+
28
+ /**
29
+ * Whether this directory is one WE created — asked before recording it as a working-directory
30
+ * preset, which a worktree must never become: it is one branch for one task, deleted when the task
31
+ * is done, and nothing prunes the chip when the directory goes (the close button is the only
32
+ * remover), so the row filled with paths that no longer exist.
33
+ *
34
+ * Anchored on the managed ROOT, not on the shape above. Shape alone matches any
35
+ * `…/worktrees/<name>-<8hex>/<task>` a person or another tool happens to have, and the cost here is
36
+ * not a wrong label — it is a real working directory silently missing from the launcher (Codex on
37
+ * #1543). The root is the same authority the server's own `worktreeTask` uses; the browser is told
38
+ * it by `/api/config`.
39
+ *
40
+ * **An unknown root answers `false`** — record the directory. Before `/api/config` resolves there is
41
+ * nothing to compare against, and of the two ways to be wrong, an extra chip the user can delete
42
+ * beats a directory that quietly never appears.
43
+ *
44
+ * Lexical containment (`dirPathKey`), because the browser has no filesystem: it folds both
45
+ * separators, `.`/`..` and a trailing slash, but not a symlink or Windows' case-insensitivity.
46
+ * Both values come from the same server, so they agree in practice — and a spelling it cannot fold
47
+ * falls back to "not ours", which is the safe direction above.
48
+ */
49
+ export function isManagedWorktreePath(cwd: string | null | undefined, worktreesRoot: string | null | undefined): boolean {
50
+ if (!cwd || !worktreesRoot) return false;
51
+ const rootKey = dirPathKey(worktreesRoot);
52
+ if (rootKey === "") return false;
53
+ const prefix = rootKey.endsWith("/") ? rootKey : `${rootKey}/`;
54
+ const key = dirPathKey(cwd);
55
+ if (!key.startsWith(prefix)) return false;
56
+ const below = key.slice(prefix.length).split("/").filter(Boolean);
57
+ // `<repo>-<hash>/<task>` at least, and the first segment has to be a name WE mint. The root
58
+ // holds worktrees but is not one, the per-repo directory above a task is not one either, and a
59
+ // directory someone put under the root by hand carries no hash — all three are places a person
60
+ // could reasonably be working, and dropping one loses it from the launcher (Codex on #1543).
61
+ return below.length >= 2 && MANAGED_DIR.test(below[0] ?? "");
62
+ }
@@ -1 +1 @@
1
- import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-D6V0bamn.js";import"./chunk-WYO6CB5R-nS6zX9ct-CEyECF4F.js";import"./chunk-VAUOI2AC-BKx35BBi-DF4roMbC.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CorQ_Pl1-Bo-W29fw.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
1
+ import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-CX-WYPvl.js";import"./chunk-WYO6CB5R-nS6zX9ct-BCV_YuAf.js";import"./chunk-VAUOI2AC-BKx35BBi-_cGUYYkB.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CorQ_Pl1-BbhhBfNZ.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
@@ -0,0 +1 @@
1
+ import{f as e}from"./mermaid-parser.core-C1baW7NG-CX-WYPvl.js";export{e as createArchitectureServices};