mulmoterminal 2.6.0 → 2.8.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.
Files changed (251) hide show
  1. package/README.md +175 -29
  2. package/bin/cli-args.d.ts +5 -0
  3. package/bin/cli-args.js +20 -0
  4. package/bin/instances.d.ts +10 -0
  5. package/bin/instances.js +115 -0
  6. package/bin/mulmoterminal.js +23 -1
  7. package/common/bundledSkills.ts +31 -0
  8. package/common/dirConfigSource.ts +6 -0
  9. package/common/dropUpload.ts +16 -0
  10. package/common/finiteNumber.ts +9 -0
  11. package/common/launchAgent.ts +1 -1
  12. package/common/modelPresets.ts +1 -1
  13. package/common/pastedImageTypes.ts +23 -0
  14. package/common/quickCommands.ts +3 -1
  15. package/common/rateLimits.ts +2 -3
  16. package/common/remoteHostHealth.ts +6 -0
  17. package/common/sessionAgent.ts +22 -1
  18. package/common/sessionMemo.ts +44 -0
  19. package/common/toolGroups.ts +14 -0
  20. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-2UuooAL1.js → abnfDiagram-VRR7QNED-RjiYivmv-rGHffQn8.js} +1 -1
  21. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-Dg-O9e_V.js +1 -0
  22. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-Cadi87eN.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-ClBqZt07.js} +1 -1
  23. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-DqOBzK4r.js → blockDiagram-677ZJIJ3-BPuAJQRW-CRpyIN7j.js} +1 -1
  24. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-Czc9LUxD.js → c4Diagram-LMCZKHZV-C0LAqQso-CP3Nkj32.js} +1 -1
  25. package/dist/assets/channel-7wUqSdoX-Cc3ae2uC.js +1 -0
  26. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-DPioxLNJ.js → chunk-32BRIVSS-BRrYpgtb-bBx9h5ar.js} +1 -1
  27. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-BGc0pGjo.js → chunk-52WLFC77-BJ-ss3Xr-CJsYFk5u.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-C4mkPiqB.js → chunk-C7G6YPKG-BZEucKEL-CySmS4KO.js} +1 -1
  29. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-C3Wn7cGn.js → chunk-EX3LRPZG-DLS6FBN1-Bd6UgUyI.js} +1 -1
  30. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-C8DnOSSj.js → chunk-FWX5IMBZ-DHLSFw1H-NTtO_c6Q.js} +2 -2
  31. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-BT_GFYXY.js → chunk-HOUHSVGY-Bhlt8hXJ-CZ6o_X29.js} +1 -1
  32. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-DY-sDZ2-.js → chunk-ICXQ74PX-DwgHBX_g-CLxX8cQ2.js} +1 -1
  33. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-DvOpObYg.js → chunk-MOJQB5TN-CMZRaeqt-LKJvD71K.js} +1 -1
  34. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BxAWKfDq.js → chunk-OGEWGWER-8Qy4a8b5-DaWsU33j.js} +1 -1
  35. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-HeYWroWP.js → chunk-PUDLZKDR-DcrWQRYh-zTDqq542.js} +1 -1
  36. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-Cqn2FjJe.js → chunk-Q4XR5HBZ-ZXVGkG8Z-Dtx2iGhH.js} +1 -1
  37. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-WySfFBLu.js → chunk-V7JOEXUC-DmGdheTX-BgwCMqp5.js} +1 -1
  38. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-xhVI0AT_.js → chunk-VAUOI2AC-CS9QJ4yz-FXdeVJH3.js} +1 -1
  39. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Dawtxx1M.js → chunk-VR4S4FIN-C6a91eNY-huBR399y.js} +1 -1
  40. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-cPQQuq_p.js → chunk-WYO6CB5R-BlzOfotS-B9k6Do_6.js} +1 -1
  41. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CwEy22EH.js → chunk-ZGVPDNZ5-BYwxNFTK-17x7kiDu.js} +1 -1
  42. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-gmGwQCKi.js +1 -0
  43. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-gmGwQCKi.js +1 -0
  44. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-C5PuFrCb.js +1 -0
  45. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--6gP3lGiQ.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BmU0gwz5.js} +1 -1
  46. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-Ogb4ZCJY.js → dagre-VKFMJZFB--oJKqXBZ-Cf0skqxL.js} +1 -1
  47. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-En7Wv4VU.js → diagram-FQU43EPY-CcJCB9bG-XUWMFcgP.js} +1 -1
  48. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-CvfDKE9B.js → diagram-G47NLZAW-C_o-WGG1-B6HfMZd3.js} +1 -1
  49. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--D_SuUUUL.js → diagram-NH7WQ7WH-CXJCYvY--BACwoprc.js} +1 -1
  50. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-COaQX8ss.js → diagram-OA4YK3LP-BMzeJ87A-zoxV9d9i.js} +1 -1
  51. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-B5qjqS3m.js → diagram-WEI45ONY-D_93NKqo-e-eIgWvy.js} +1 -1
  52. package/dist/assets/{dist-IEKl3ifz.js → dist-BHoD_w15.js} +1 -1
  53. package/dist/assets/{dist-CeY8Kgqb.js → dist-BWiUENTZ.js} +1 -1
  54. package/dist/assets/{dist-kXEjeiqx.js → dist-CX4c9UBB.js} +1 -1
  55. package/dist/assets/{dist-CBx3SN5Y.js → dist-Cm6rSKic.js} +1 -1
  56. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-Bbl_CbGD.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DujO8khX.js} +1 -1
  57. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-DtNjq60k.js → erDiagram-Q63AITRT-CiABnA0s-DaHESKPI.js} +1 -1
  58. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-D1MuSTYF.js +1 -0
  59. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-DT6n3-Ft.js +1 -0
  60. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-Dtybxb5k.js → ganttDiagram-NO4QXBWP-DQZvdFo1-De_ebWFp.js} +1 -1
  61. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CNsR02rf.js +1 -0
  62. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CibskzGI.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-C4RETxbL.js} +1 -1
  63. package/dist/assets/index-BivYtiDo.css +1 -0
  64. package/dist/assets/index-COmdTh0t.js +616 -0
  65. package/dist/assets/info-DKCQHKI2-Dplx5kMp-BeUJlQSg.js +1 -0
  66. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-WyZaP5rz.js → infoDiagram-FWYZ7A6U-7UnoB5AP-DNsURlj_.js} +1 -1
  67. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-C_Q9CmwS.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-QyeLiJnO.js} +1 -1
  68. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-D-Ru3_wb.js → journeyDiagram-5HDEW3XC-c5xIah9o-CFkMurTe.js} +1 -1
  69. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-eWJL56Xd.js → kanban-definition-HUTT4EX6-Cnt6loYD-DsoZE32R.js} +1 -1
  70. package/dist/assets/{lib-CTNrhK1N.js → lib-DC86gNgf.js} +1 -1
  71. package/dist/assets/{line-D7ziSjKi-CEMKL39T.js → line-D7ziSjKi-Dz37a1T7.js} +1 -1
  72. package/dist/assets/{marp-C4na0vHp.js → marp-B-h6KS_6.js} +1 -1
  73. package/dist/assets/{mermaid-parser.core-DxEa8E3F-3f1dQMCy.js → mermaid-parser.core-DxEa8E3F-DteVxl8X.js} +2 -2
  74. package/dist/assets/{mermaid.core-V0OYwIz3-Dg1HWQ_i.js → mermaid.core-V0OYwIz3-DAwdqlFZ.js} +3 -3
  75. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-CyxV57-U.js → mindmap-definition-LN4V7U3C-CexN3O6L-B4GX796F.js} +1 -1
  76. package/dist/assets/packet-7NZHBO7P-CQI3flND-C_pMfWi_.js +1 -0
  77. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-B40BLiy-.js → pegDiagram-2B236MQR-BiM4G0cW-BWBzRiaC.js} +1 -1
  78. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-bM2FdcNh.js +1 -0
  79. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-j0wsTlPA.js → pieDiagram-ENE6RG2P-B_fBBS-2-CsBnZfAY.js} +1 -1
  80. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BVxlsDNI.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-B1j3aQ5d.js} +1 -1
  81. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BB9wirGo.js +1 -0
  82. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CbOoELZg.js +1 -0
  83. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-uE3YbEg2.js +1 -0
  84. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-u4DQhs69.js +1 -0
  85. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-B2vytEWW.js +1 -0
  86. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-7RnYoafQ.js → railroadDiagram-RFXS5EU6--lNIlG64-BjmRh1tU.js} +1 -1
  87. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-xH07LQI_.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-UJKUKfTI.js} +1 -1
  88. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-BUmys5rr.js → sankeyDiagram-HTMAVEWB-DlutaXDv-Bsrgx8aw.js} +1 -1
  89. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-BYyf27T3.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-BpxKPxVk.js} +1 -1
  90. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-DGzqzTG5.js → stateDiagram-2N3HPSRC-jf_dXUEU-90vLBL-t.js} +1 -1
  91. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-B-UjCOGA.js +1 -0
  92. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-BD-WjB9j.js → swimlanes-5IMT3BWC-CYjtALQH-BQj65nKh.js} +1 -1
  93. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-BMxuxpet.js +8 -0
  94. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-BL8HXryX.js → timeline-definition-FHXFAJF6-DYJ4oUm8-Brr9I81k.js} +1 -1
  95. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DvTQoVxm.js +1 -0
  96. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DCrIc8uR.js +1 -0
  97. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-_pUekkpD.js → vennDiagram-L72KCM5P-CdKAHoek-BFYXaKjS.js} +1 -1
  98. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k--RdJ9IY0.js +1 -0
  99. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-BVNPNGsg.js → wardleyDiagram-EHGQE667-BF6c4_CW-BN4fz_6C.js} +1 -1
  100. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-C5EYDD6T.js → xychartDiagram-FW5EYKEG-CGiKngj7-CJAizw9P.js} +1 -1
  101. package/dist/index.html +2 -2
  102. package/package.json +17 -11
  103. package/server/agents/agent-resume.ts +27 -0
  104. package/server/agents/antigravity-args.ts +39 -0
  105. package/server/agents/antigravity-mcp.ts +122 -0
  106. package/server/agents/antigravity-session.ts +68 -0
  107. package/server/agents/antigravity.ts +7 -0
  108. package/server/agents/appended-prompt.ts +28 -0
  109. package/server/agents/claude-args.ts +11 -12
  110. package/server/agents/claude.ts +1 -0
  111. package/server/agents/codex-rate-limits.spec.ts +20 -0
  112. package/server/agents/codex-rate-limits.ts +16 -6
  113. package/server/agents/codex.ts +1 -0
  114. package/server/agents/rate-limit-persist.spec.ts +26 -1
  115. package/server/agents/rate-limit-persist.ts +3 -4
  116. package/server/agents/registry.ts +2 -0
  117. package/server/agents/statusline.spec.ts +4 -0
  118. package/server/agents/statusline.ts +2 -3
  119. package/server/agents/types.ts +4 -1
  120. package/server/backends/collections.ts +15 -3
  121. package/server/backends/feed-summary.ts +1 -1
  122. package/server/backends/fileChange.ts +19 -6
  123. package/server/backends/fileOps.ts +5 -1
  124. package/server/backends/google.ts +3 -1
  125. package/server/backends/html.ts +67 -9
  126. package/server/backends/imageResult.ts +2 -2
  127. package/server/backends/markdown.ts +20 -9
  128. package/server/backends/mulmoscript.spec.ts +3 -3
  129. package/server/backends/openPath.ts +67 -0
  130. package/server/backends/remoteHost/googleCalendar.ts +9 -4
  131. package/server/backends/remoteHost/handlers.ts +2 -1
  132. package/server/backends/remoteHost/index.ts +26 -22
  133. package/server/backends/remoteHost/launchTerminal.ts +2 -2
  134. package/server/backends/remoteHost/routes.spec.ts +13 -1
  135. package/server/backends/remoteHost/routes.ts +6 -3
  136. package/server/backends/remoteHost/sessionActivity.ts +27 -3
  137. package/server/backends/remoteHost/terminalInput.ts +1 -1
  138. package/server/backends/remoteHost/terminalScreen.ts +72 -4
  139. package/server/backends/thumbnailStore.ts +5 -2
  140. package/server/cli-google.ts +1 -1
  141. package/server/config/app-config.ts +16 -0
  142. package/server/config/config-routes.ts +7 -0
  143. package/server/config/config-schema.ts +8 -0
  144. package/server/config/dir-config.ts +10 -2
  145. package/server/config/header-config.ts +1 -1
  146. package/server/config/header-context.ts +1 -1
  147. package/server/files/dirRequest.ts +2 -1
  148. package/server/files/pathContainment.ts +5 -1
  149. package/server/files/pick-file.ts +2 -1
  150. package/server/git/worktree-pr.ts +3 -3
  151. package/server/git/worktree-routes.ts +6 -5
  152. package/server/index.ts +136 -26
  153. package/server/infra/collection-tool.ts +1 -1
  154. package/server/infra/gui-mcp-registration.ts +15 -2
  155. package/server/infra/has-binary.ts +162 -14
  156. package/server/infra/install-bundled-skills.ts +7 -8
  157. package/server/infra/plugins-registry.ts +10 -1
  158. package/server/infra/resolve-bin.ts +3 -3
  159. package/server/infra/spawn-cwd.ts +46 -0
  160. package/server/infra/tmux-routes.ts +3 -2
  161. package/server/infra/tmux.ts +106 -1
  162. package/server/infra/web-push.ts +1 -1
  163. package/server/mcp/bridge.mjs +66 -0
  164. package/server/mcp/broker.ts +43 -8
  165. package/server/mcp/gui-call-history.ts +96 -0
  166. package/server/routes/app-routes.ts +53 -9
  167. package/server/routes/drop-routes.ts +50 -0
  168. package/server/routes/gui-mcp-routes.ts +6 -0
  169. package/server/routes/hook-routes.ts +26 -9
  170. package/server/routes/mcp-routes.ts +53 -3
  171. package/server/routes/plugin-routes.ts +24 -8
  172. package/server/routes/routeParams.ts +4 -2
  173. package/server/routes/same-origin-guard.ts +17 -3
  174. package/server/routes/session-routes.ts +47 -2
  175. package/server/routes/terminal-ws-path.ts +2 -1
  176. package/server/routes/tool-routes.ts +14 -4
  177. package/server/routes/ws-routes.ts +155 -42
  178. package/server/session/activity-transition.ts +9 -4
  179. package/server/session/background-chat.ts +24 -10
  180. package/server/session/cleared-transcripts.ts +118 -0
  181. package/server/session/command-summary.ts +2 -1
  182. package/server/session/completion-hooks.ts +51 -0
  183. package/server/session/hook-settings.ts +1 -1
  184. package/server/session/launch-choice.ts +2 -2
  185. package/server/session/lifecycle.ts +21 -1
  186. package/server/session/mcp-config.ts +3 -3
  187. package/server/session/provider-env.ts +2 -2
  188. package/server/session/pty-connection.ts +36 -3
  189. package/server/session/pty-exit-log.ts +92 -0
  190. package/server/session/pty-relay.ts +35 -0
  191. package/server/session/pty-spawn.ts +101 -9
  192. package/server/session/registry.ts +160 -37
  193. package/server/session/session-detail-view.ts +28 -6
  194. package/server/session/session-drops.ts +174 -0
  195. package/server/session/{dev-terminal-sessions.ts → session-id-log.ts} +16 -15
  196. package/server/session/session-list.ts +15 -4
  197. package/server/session/session-memos.ts +72 -0
  198. package/server/session/session-reads.ts +6 -5
  199. package/server/session/session-settings.ts +27 -4
  200. package/server/session/session-title.ts +11 -3
  201. package/server/session/session-tool-groups.ts +2 -2
  202. package/server/session/sessionListTitle.ts +13 -4
  203. package/server/session/spawn-antigravity.ts +83 -0
  204. package/server/session/spawn-claude.ts +41 -16
  205. package/server/session/spawn-codex.ts +8 -20
  206. package/server/session/spawn-deps.ts +2 -0
  207. package/server/session/spawn-shell.ts +10 -5
  208. package/server/session/spawners.ts +2 -0
  209. package/server/session/task-push.ts +12 -6
  210. package/server/session/taskPushRules.ts +5 -4
  211. package/server/session/terminal-replay.ts +15 -0
  212. package/server/session/tmux-size-sync.ts +184 -0
  213. package/server/session/tool-hook.ts +3 -3
  214. package/server/session/tool-store.ts +28 -24
  215. package/server/session/types.ts +9 -3
  216. package/server/skills/mulmoterminal-bug-report/faq.md +6 -0
  217. package/server/skills/mulmoterminal-config/SKILL.md +113 -490
  218. package/server/skills/mulmoterminal-dirs/SKILL.md +227 -0
  219. package/server/skills/{mulmoterminal-config → mulmoterminal-dirs}/palettes.json +1 -1
  220. package/server/skills/mulmoterminal-header/SKILL.md +165 -0
  221. package/server/skills/mulmoterminal-keys/SKILL.md +153 -0
  222. package/server/skills/mulmoterminal-model/SKILL.md +102 -0
  223. package/server/skills/mulmoterminal-notify/SKILL.md +111 -0
  224. package/server/skills/mulmoterminal-theme/SKILL.md +128 -0
  225. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BgkdTJQE.js +0 -1
  226. package/dist/assets/channel-7wUqSdoX-C1cie6OA.js +0 -1
  227. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Do5m3IoG.js +0 -1
  228. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Do5m3IoG.js +0 -1
  229. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-DwN-y4W_.js +0 -1
  230. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-EIcAkJu9.js +0 -1
  231. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BLvmawCu.js +0 -1
  232. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CWvVulXn.js +0 -1
  233. package/dist/assets/index-B1LdU5cE.js +0 -610
  234. package/dist/assets/index-Dwz79YwY.css +0 -1
  235. package/dist/assets/info-DKCQHKI2-Dplx5kMp-CBgSXevA.js +0 -1
  236. package/dist/assets/packet-7NZHBO7P-CQI3flND-BlXZ8rUp.js +0 -1
  237. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-elRgXCv3.js +0 -1
  238. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BZqQUr3N.js +0 -1
  239. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Cb5GXZCb.js +0 -1
  240. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-BddD0JXg.js +0 -1
  241. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-7VkImbqm.js +0 -1
  242. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-DHko1-Zq.js +0 -1
  243. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-DDeTaew1.js +0 -1
  244. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-DZLi_c2t.js +0 -8
  245. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-C9kkOUqZ.js +0 -1
  246. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-zjv1syce.js +0 -1
  247. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DE_WhSD_.js +0 -1
  248. package/server/agents/codex-resume.ts +0 -26
  249. package/server/backends/remoteHost/firestoreSafeResult.ts +0 -72
  250. package/server/backends/remoteHost/presenceProbe.ts +0 -67
  251. package/server/backends/remoteHost/resilientRunner.ts +0 -231
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mulmoterminal
2
2
 
3
- **Run a whole team of coding agents from your browser — and actually keep up with them.**
3
+ **Run multiple Claude Code and Codex sessions in parallel — and see which one needs you.**
4
4
 
5
5
  A **browser terminal** for **parallel AI coding agents**: several **Claude Code** and **Codex**
6
6
  sessions side by side, each in its own cell, with the one that needs you marked in colour. Vibe
@@ -14,6 +14,9 @@ and a **phone push** reaches you when a turn finishes.
14
14
  view, everyday workflows, the full feature list, configuration, and mobile push notifications.
15
15
  - **ユーザーガイド:** [日本語](https://receptron.github.io/mulmoterminal/guide/ja/) —
16
16
  グリッドの使い方・日々のワークフロー・機能一覧・設定・スマホ通知の設定はこちら。
17
+ - **Updates / アップデート情報:** new releases and features are announced **in Japanese** on X —
18
+ 新バージョンや新機能のお知らせは X の
19
+ [Singularity Society (@SingularitySoci)](https://x.com/SingularitySoci) で。
17
20
 
18
21
  ![MulmoTerminal — a grid of live Claude Code sessions, each color-coded by state, updating in real time](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/hero.gif)
19
22
 
@@ -86,11 +89,31 @@ output you haven't seen) — driven by Claude/Codex activity hooks the server in
86
89
  *Besides the grid there's a **single view** for focusing on one agent: the conversation/terminal on the left, and a **GUI panel** ("Canvas") on the right where the agent's tool calls render as documents, forms, charts, images, and HTML — not just printed text. Switch between the two with the chat / grid icons in the toolbar. **The app opens on the grid** (`/`); the single view has its own URL, `/chat`, so you can bookmark either.*
87
90
 
88
91
  **Inserting a file path** — like a native terminal, you can put a file's absolute path into
89
- the prompt: **drag a file** onto the terminal (works where the browser exposes the path via
90
- `file://` Firefox/Safari), or click the **📎 file button** in the terminal header, which
91
- asks the local server to open the OS file dialog and inserts the chosen path (works in every
92
- browser, including Chrome). The path is inserted at the cursor — it is not submitted, so you
93
- can review it first.
92
+ the prompt: **drag a file** onto the terminal, or click the **file button** in the terminal
93
+ header, which asks the local server to open the OS file dialog and inserts the chosen path. The
94
+ path is inserted at the cursor it is not submitted, so you can review it first.
95
+
96
+ A drag inserts the file's **own** path where the browser exposes one via `file://`
97
+ (Firefox/Safari), so editing it afterwards edits the file you dropped. Where the browser
98
+ withholds it — **Chrome**, and every browser when MulmoTerminal is open **from another
99
+ machine**, where a local path would name nothing on the host — the file's bytes are sent
100
+ instead, saved to a private per-session directory under the OS temp dir, and *that* path is
101
+ inserted. The session is granted that directory at launch (Claude Code's `--add-dir`, bind-mounted
102
+ in the sandbox too), so the agent reads it without a permission prompt; the copies are removed
103
+ when the session ends, and any left by a crash are swept at the next start. Up to 110 MiB per
104
+ file — the same ceiling as a phone attachment. **A session already running when you upgrade
105
+ was launched without that grant**, so drops into it still prompt; new sessions don't.
106
+
107
+ **Pasting a screenshot** — take a screenshot and paste it straight into the terminal
108
+ (`Cmd`/`Ctrl`+`V`). The image is saved to the session's own drop directory — the same place a
109
+ dropped file goes, with the same grant, the same 110 MiB ceiling and the same cleanup when the
110
+ session ends — and its **absolute path** is inserted at the cursor, so the agent can read it.
111
+ Unlike a drop, this does not need the browser to expose a path — the bytes are on the
112
+ clipboard — so it also covers Chrome, where dropping a file cannot insert a path. It works
113
+ wherever the browser puts the image on the clipboard as `image/png`, `image/jpeg`,
114
+ `image/gif`, or `image/webp`. Anything else is left to the terminal's own paste handling,
115
+ exactly as before — including a paste that carries **plain text** next to the image, which
116
+ copying from a web page usually does, so that pasting text keeps working.
94
117
 
95
118
  <a id="clicking-a-file-path"></a>
96
119
 
@@ -129,6 +152,51 @@ dotfiles are server-only. The 45 extensions both sides agree on live in
129
152
 
130
153
  ---
131
154
 
155
+ ## What people say after switching
156
+
157
+ > These are experiences reported by users who moved over from an IDE or a split terminal —
158
+ > not benchmarks, and not claims we measured. Your setup may differ.
159
+
160
+ ### "It stopped eating my memory"
161
+
162
+ Keeping several agents apart by opening several IDE windows is expensive: each one brings its own
163
+ editor, language server, extensions and file watchers. One user reported a **64 GB machine
164
+ stuttering** under that load, and running smoothly after moving over — here the agents are PTYs on
165
+ a server and the UI is browser tabs.
166
+
167
+ ### "I stopped answering the wrong agent"
168
+
169
+ Six panes of scrolling text look identical. Users have described **typing a reply into another
170
+ agent's terminal**, and losing track of what they had asked in the first place. As one put it, the
171
+ windows all look the same, so switching between them costs time just to work out what you are
172
+ looking at.
173
+
174
+ The problem isn't attention — it's that N identical panes means holding N contexts in your head.
175
+ Colour-coded state, a name badge and a per-directory colour move that onto the screen instead.
176
+
177
+ ### "Watching many and reading one stopped being a trade-off"
178
+
179
+ Splitting a terminal six ways leaves every pane too small to read a long answer without constant
180
+ scrolling and resizing — one user described exactly that with a 4,000-character reply. So you
181
+ quietly accept worse reading every time you add an agent.
182
+
183
+ **Grid ↔ enlarge removes that.** Watch all of them, then blow one up and read it properly — the
184
+ cockpit roster keeps the rest in view as text while you do.
185
+
186
+ ### "My existing sessions came with me"
187
+
188
+ Sessions resume as-is — same `claude --resume`, same transcripts. Point it at a directory you
189
+ already work in and your history is there. Nothing to migrate, nothing to redo. One user said this
190
+ alone made the switch worth it, having previously lost context to killed sessions.
191
+
192
+ ---
193
+
194
+ **You don't need ten agents for this to pay off.** Users have reported the switch being worth it at
195
+ **one to three** parallel sessions. The wins above are about not losing track, not about running
196
+ more.
197
+
198
+ ---
199
+
132
200
  ## Install & run
133
201
 
134
202
  Needs **Node ≥ 22.9**, plus these CLIs on your `PATH`:
@@ -161,7 +229,10 @@ and every CLI in the table above), seeds the launcher's **directory
161
229
  presets** from the projects in your Claude Code history, and writes `~/.mulmoterminal/config.json`.
162
230
  It's **idempotent** — re-run it any time to refresh the presets; it overwrites the managed parts
163
231
  and keeps your other settings. When `claude` is installed it can hand off to the
164
- `/mulmoterminal-config` skill for interactive tweaks.
232
+ `/mulmoterminal-config` skill for interactive tweaks — it routes to the one that owns what you
233
+ want to change. Once the app is up you can also reach them from **Settings**: each section that a
234
+ skill can write ends in a button that starts that skill in a new session, which is how the settings
235
+ with no UI (a theme of your own, `keymap`) get written without hand-editing JSON.
165
236
 
166
237
  **Google account (optional).** Link a Google account to enable the chat's `google` tool and the
167
238
  phone's `google.calendar.*` commands: read/create events on any calendar (not just your primary),
@@ -298,12 +369,12 @@ SDK; we drive the real interactive CLI and relay its TTY over the WebSocket.
298
369
 
299
370
  ---
300
371
 
301
- ## Agents: Claude & Codex
372
+ ## Agents: Claude, Codex & Antigravity
302
373
 
303
374
  MulmoTerminal drives **interactive coding-agent CLIs**, not just Claude. An
304
375
  `AgentAdapter` seam abstracts the per-agent bits (which binary to spawn, how it resumes)
305
- so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Two adapters ship
306
- today — **Claude Code** (the default) and **Codex**.
376
+ so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Three adapters ship
377
+ today — **Claude Code** (the default), **Codex**, and **Antigravity** (`agy`).
307
378
 
308
379
  - **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
309
380
  `--session-id <uuid>`, so it knows the live session's id even before its transcript
@@ -317,10 +388,34 @@ today — **Claude Code** (the default) and **Codex**.
317
388
  `CODEX_HOME`) and maps the new rollout to the session — attributed only when it's
318
389
  unambiguous, never by "newest wins". Resume reattaches a live PTY, adopts a surviving
319
390
  tmux session, or cold-resumes the rollout id.
391
+ - **Antigravity** — spawned as `agy` (override with `ANTIGRAVITY_BIN`; `ANTIGRAVITY_MODEL` sets
392
+ `--model`). Antigravity runs on its own WebSocket (`/ws/antigravity`). Like Codex it mints its
393
+ own conversation id, so the server watches `~/.gemini/antigravity-cli/brain/` (home overridable
394
+ via `ANTIGRAVITY_HOME`) for the directory the new conversation creates — attributed only when
395
+ unambiguous — and cold-resumes it with `--conversation <id>`.
396
+
397
+ Its **GUI tools work differently**, because `agy` takes no MCP flag: it reads its servers from
398
+ `.agents/mcp_config.json` in the working directory. MulmoTerminal writes that file from the
399
+ directory's [Canvas switches](#wiki-collections--the-gui-panel) — the same switches Claude's cells read — so
400
+ one switch serves every agent, and rewrites it whenever a switch flips or an agy session starts.
401
+ Servers in it that MulmoTerminal did not write are left alone, the file is removed once no group
402
+ is on, and it is kept out of your `git status` through `.git/info/exclude` — a local switch on a
403
+ local machine, so it never reaches a diff or your team. The entry runs `server/mcp/bridge.mjs`, a stdio-to-HTTP shim onto the same in-process
404
+ GUI MCP server the other agents call. The **session id is never written into that file** — it is
405
+ per directory and shared by every session running there — and reaches the bridge through the agy
406
+ process's own environment instead.
407
+
408
+ The Docker sandbox does NOT cover agy: it stays claude-only until `buildDockerRunArgs` is
409
+ generalized (see `plans/feat-multi-agent-support.md`, PR#5). `agy` also ships as a standalone
410
+ binary rather than an npm package, so the sandbox image has nothing to install.
320
411
 
321
412
  **Choosing an agent.** The single view has a **New Codex session** button; each grid
322
- cell's launch form and the Collections browser carry a **Claude / Codex** toggle (your
323
- choice is remembered).
413
+ cell's launch form carries a **Claude / Codex / Antigravity / Shell** toggle, and the
414
+ Collections browser a **Claude / Codex / Antigravity** one (your choice is remembered).
415
+ **Shell** is not an agent: it runs your OS default shell (`$SHELL`, or `/bin/sh`) in the
416
+ chosen directory, with nothing to install and nothing to configure. It starts a launcher
417
+ cell, so it has no model, no MCP registration, and no worktree — those rows disappear
418
+ while it is picked.
324
419
 
325
420
  **Other models.**
326
421
  Claude Code can run against any **Anthropic-compatible** backend (OpenRouter, Moonshot, a
@@ -447,7 +542,14 @@ the `claude` / `codex` sessions themselves.
447
542
  | `CODEX_BIN` | `codex` | The Codex CLI binary to spawn. |
448
543
  | `CODEX_MODEL`| codex default | Model passed to Codex as `--model` (unset = Codex's own default). |
449
544
  | `CODEX_HOME` | `~/.codex` | Codex home — where its session rollouts and MulmoTerminal-mirrored skills live. |
545
+ | `ANTIGRAVITY_BIN` | `agy` | The Antigravity CLI binary to spawn. |
546
+ | `ANTIGRAVITY_MODEL` | agy default | Model passed to Antigravity as `--model` (unset = agy's own default). |
547
+ | `ANTIGRAVITY_HOME` | `~/.gemini/antigravity-cli` | Antigravity home directory containing session brain storage. |
450
548
  | `MULMOTERMINAL_HOME` | `~/.mulmoterminal` | Root for managed **git worktrees**. |
549
+ | `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. |
550
+ | `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`). Set it to the same value MulmoClaude uses. |
551
+ | `MULMOTERMINAL_NO_SKILL_INSTALL` | unset | Set to any value to skip installing the bundled skills (`mulmoterminal-config` and the `-dirs` / `-theme` / `-header` / `-keys` / `-model` / `-notify` / `-bug-report` / `-decisions` family) into `~/.claude/skills/` and the Codex skills root on startup. |
552
+ | `GEMINI_IMAGE_MODEL` | `gemini-3.1-flash-image-preview` | Model used for image generation (needs `GEMINI_API_KEY`). The default is a **preview** model Google schedules for retirement around mid-2026, so pin a stable one here (e.g. `gemini-2.5-flash-image`) rather than waiting for a code change. |
451
553
  | `WAIT_REAP_GRACE_MS` | `1800000` | How long a **waiting** background session is kept before it's auto-reaped (`0` or negative = never). |
452
554
 
453
555
  The Docker-sandbox variables (`MULMOTERMINAL_SANDBOX`, `MULMOTERMINAL_SANDBOX_IMAGE`,
@@ -477,7 +579,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
477
579
  | `soundKinds` | Which moments beep — see [Notification sounds](#notification-sounds). Defaults to `["finished","waiting"]`; the other kinds are opt-in. |
478
580
  | `sounds` | Per-kind sound: `{ "waiting": "preset:coin" }`. A `preset:<id>` reference or an absolute path; a kind with no entry falls back to `soundFile`. |
479
581
  | `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). |
480
- | `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides Claudea plain shell, `codex`, any interactive command. |
582
+ | `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides the agents any interactive command. A plain shell needs no entry: the launch form's **Shell** toggle opens `$SHELL` unconfigured. |
481
583
  | `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"` / `"codex"` / `"shell"`) scopes a chip to session kinds — omit it to offer the chip everywhere. Empty by default. |
482
584
  | `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the **single-view** Claude session's `--mcp-config` (a `localhost` URL is reached over `host.docker.internal` in the Docker sandbox). Takes effect on the next session. |
483
585
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
@@ -491,6 +593,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
491
593
  | `decisionDigest` | Keep a **Markdown digest of the decisions this project's sessions asked for**, refreshed at startup and every few hours, so an agent can read what has already been decided before asking something similar. Written to `~/.mulmoterminal/decisions/<project>.md` (never into your repository) and served to agents by the bundled `mulmoterminal-decisions` skill. **Off by default** — it is a vision-stage idea, and it writes a file that would otherwise not exist. The digest holds dated facts, never inferred rules. |
492
594
  | `issueWorkComments` | Let a cell **comment on the issue it is working on**: once when it starts, and again when its PR merges (closing the issue if GitHub has not already). The comment names the working **directory** it happened in — the folder name only, never the path — so a reader can tell which clone. **Off by default**; it writes to GitHub, often on somebody else's issue. Needs `gh` logged in. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#issue-work-comments). |
493
595
  | `prWorkdirFooter` | Ends a PR body with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. Applies to **both** paths that open PRs here: **⧉ Open PR** appends it to the PR it creates, and every Claude session is told to end the bodies it writes with the same line (the name is resolved by the server, so a session inside a managed worktree still names the main checkout). **On by default**; set `false` to opt out — read per PR and per session spawn, so no restart is needed (there is no Settings control for it). Appending is idempotent: an existing PR never gets a second copy. |
596
+ | `appendSystemPrompt` | Whether a spawned Claude session is asked to end a reply with a **closing summary** — what was asked, what was achieved, what was not (see [Closing summary](#closing-summary)). **On by default**; set `false` to opt out, and a directory's `.mulmoterminal.json` outranks this. Read per spawn, so no restart is needed (there is no Settings control for it), though a session already running keeps what it was launched with. `true` / `false` only. |
494
597
  | `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. No Settings UI: edit the file, then **restart** (this config is read once at startup). Unset uses the built-in stack (JetBrains Mono / Fira Code / Menlo / Consolas, then CJK faces for Japanese, Korean and Chinese). Unlike the per-browser font **size**, this is one value for the whole host — it names fonts, and which fonts exist is a property of the machine. A directory can override it. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#font-family). |
495
598
 
496
599
  Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
@@ -514,8 +617,8 @@ agent), or `"open"`. An `open` button targets one of `url` / `reveal` (OS file m
514
617
  opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
515
618
  there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
516
619
  `${dir}`, `${branch}`, `${repo}`, … substitute live context, and `when` (e.g. `"isGitRepo"`) gates
517
- visibility. The `/mulmoterminal-config` skill writes a valid config interactively; per-dir buttons
518
- merge over the global ones by `id`.
620
+ visibility. The `/mulmoterminal-header` skill writes a valid config interactively; per-dir buttons
621
+ merge over the global ones by `id`, while `chips` replace the global list wholesale.
519
622
 
520
623
  ### Notification sounds
521
624
 
@@ -611,9 +714,10 @@ malformed file is ignored.
611
714
  "colors": { "background": "#190a23", "cursor": "#ff2e63" }, // per-key palette overrides
612
715
  "fontSize": 16, // terminal font size in px (8–32); overrides Settings
613
716
  "fontFamily": "'Cica', monospace", // terminal font stack; overrides the global config
614
- "orderPriority": 10, // rank in the grid's "priority" ordering (lowest first)
717
+ "orderPriority": 10, // rank in the grid's "priority" order and the launcher chips (lowest first)
615
718
  "sound": "./.mulmoterminal/alert.mp3", // attention sound, RELATIVE to this directory
616
- "sounds": { "command-failed": "preset:gong" } // per-notification-kind override
719
+ "sounds": { "command-failed": "preset:gong" }, // per-notification-kind override
720
+ "appendSystemPrompt": false // no closing summary here; omit to follow the global setting
617
721
  }
618
722
  ```
619
723
 
@@ -634,17 +738,18 @@ malformed file is ignored.
634
738
  | `theme` | xterm palette for terminals in this directory (one of the built-in theme ids). |
635
739
  | `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. |
636
740
  | `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. |
637
- | `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. Only the priority mode reads it. |
741
+ | `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 always sort by it, so a project sits in the same place on both. |
638
742
  | `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. |
639
743
  | `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. |
640
744
  | `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. |
745
+ | `appendSystemPrompt` | Whether this directory's Claude sessions are asked to end a reply with a **closing summary** (see [Closing summary](#closing-summary)). Omit to follow the global `appendSystemPrompt`, which is on; `true` / `false` here outranks it. Read per spawn, so a new session in this directory picks up an edit without a restart. |
641
746
  | `addDirs` | Extra directories this project's Claude sessions may read and edit — the terminal-side equivalent of opening several folders in one VS Code workspace, via Claude Code's `--add-dir`. Relative entries resolve against **this file's directory** (`"../shared-lib"`), a path that doesn't exist is dropped, max 16. In the Docker sandbox each one is bind-mounted too, so the grant is real inside the container — which widens the sandbox on purpose. Claude only: codex has no equivalent flag and ignores the key. |
642
747
 
643
748
  **Security.** `sound` and every `sounds` entry are directory-relative paths only — absolute
644
749
  paths and any `../` that escapes the directory are rejected, and the path is never taken from the
645
750
  HTTP request, so an opened project can't point the player at arbitrary files.
646
751
  **When changes take effect.** A write made *through Claude's tools* — which includes the
647
- `mulmoterminal-config` skill — applies **live**: the tool hook that reports the write doubles
752
+ `mulmoterminal-dirs` skill — applies **live**: the tool hook that reports the write doubles
648
753
  as the reload signal, so colors, palette, font size and grid order update without reopening
649
754
  anything. There is no filesystem watcher, so an edit made **outside** a session (your own
650
755
  editor) is picked up when the terminal is next opened.
@@ -702,8 +807,9 @@ alongside the Claude sessions. Scripts are **per-directory**: the cell reads the
702
807
  different projects' scripts.
703
808
 
704
809
  The same launcher also has an **or launch** row for your configured **launch commands**
705
- a plain interactive shell, `codex`, any command — set in Settings (⚙) → **Launch
706
- commands** as `{ label, command }` (e.g. `Shell` → `$SHELL`, `Codex` → `codex`). Unlike
810
+ any interactive command — set in Settings (⚙) → **Launch commands** as
811
+ `{ label, command }` (e.g. `htop` → `htop`, `Codex` → `codex`). A plain shell needs no
812
+ entry here: the launch form's **Shell** toggle already opens `$SHELL`. Unlike
707
813
  a one-shot script, a launcher runs as a **persistent terminal in the cell's directory**:
708
814
  it survives grid page switches and reconnects, and its dot shows running vs. exited (it
709
815
  has no Claude hooks, so no blocked/done states).
@@ -848,7 +954,7 @@ repo without colliding. Worktrees live under `~/.mulmoterminal/worktrees/` (over
848
954
 
849
955
  ![An empty cell's launch form — choose the agent, working directory, or a worktree](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-launch-form.png)
850
956
 
851
- *Every empty grid cell shows this launch form: toggle **Claude / Codex**, 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. **OR LAUNCH** starts a plain shell or any launch command instead.*
957
+ *Every empty grid cell shows this launch form: toggle **Claude / Codex / Antigravity / 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.*
852
958
 
853
959
  A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); click it for a
854
960
  **Changes vs `<base>`** panel (file list + patch) with actions:
@@ -1191,6 +1297,7 @@ same-origin-guarded.
1191
1297
  | `POST /api/translation` | Runtime UI-string translation. |
1192
1298
  | `GET /api/remote-host/status` · `POST /api/remote-host/{connect,disconnect}` | Companion phone-client link. Each response carries the command channel's `health` (`online` / `reconnecting` / `offline`, plus the last listener error), so the toolbar shows a dropped channel instead of the last state it happened to fetch. |
1193
1299
  | `POST /api/open-dir` · `POST /api/pick-file` | Reveal a dir in Finder/Explorer; OS file-picker → path (`{ directory: true }` opens the folder picker — used by the launcher's Working-directory 📁 button). |
1300
+ | `POST /api/session/:id/drop` | A dropped file whose path the browser withheld. **Raw bytes**, not JSON, under the file's own content type (base64 in JSON would cap real files near 18 MB, and a dropped `.json` would be parsed as a document); the original name rides percent-encoded in `x-drop-filename` and is used for its **suffix only**. Answers `{ path }` — absolute, inside the private per-session directory the session was granted at launch. 110 MiB cap; 404 for a session this server isn't running. |
1194
1301
 
1195
1302
  The phone itself uses **none** of these routes — it reaches the host over Firestore command
1196
1303
  docs, not HTTP. Every command it can send, and the shapes it gets back, are in
@@ -1359,6 +1466,16 @@ Key rules:
1359
1466
  - Brand-new sessions appear in the sidebar **immediately** (before their `.jsonl`
1360
1467
  exists) via the in-memory `knownSessions` registry + a `created` push; an
1361
1468
  unused one disappears when its PTY is reaped.
1469
+ - **Background workers get their own filter.** A session nobody started by hand —
1470
+ a collection's scheduled refresh, or a plugin's `spawnBackgroundChat`
1471
+ `hidden: true` — is listed under the **Background** chip instead of among the
1472
+ chats, so a refresh schedule doesn't fill the history. It stays openable (a
1473
+ MulmoTerminal session is a live terminal, so a row you can't reach is a process
1474
+ you can't stop), and it is put on the same count+age retention as the
1475
+ scheduler's own sessions. The chip appears only when there is one to show. A
1476
+ **manual** collection Refresh is a normal visible session — unchanged. The
1477
+ marking is persisted (`~/.mulmoterminal/background-sessions.json`), so a worker
1478
+ stays out of the chat list after it finishes and after a restart.
1362
1479
 
1363
1480
  ---
1364
1481
 
@@ -1400,7 +1517,17 @@ placed last with nothing after it.
1400
1517
 
1401
1518
  It is deliberately **not** written on every turn: mid-work replies and short factual answers
1402
1519
  carry no standing request, and a summary that always appears stops being read. The wording
1403
- lives in `server/agents/session-summary-prompt.ts`; there is no setting to turn it off.
1520
+ lives in `server/agents/session-summary-prompt.ts`.
1521
+
1522
+ **On by default, and switchable off** with `appendSystemPrompt: false` — in
1523
+ `~/.mulmoterminal/config.json`, or in a directory's `.mulmoterminal.json`, which outranks the
1524
+ global value. Read per spawn, so no restart is needed; a session already running keeps what it
1525
+ was launched with. Nothing in the app parses what the summary says, so turning it off costs no
1526
+ feature — the roster and push notifications simply show the raw tail of the reply.
1527
+
1528
+ Which sections `--append-system-prompt` ends up carrying is decided in
1529
+ `server/agents/appended-prompt.ts`: this one and the `prWorkdirFooter` clone line are separate
1530
+ settings on the same flag, and with both off the flag is not passed at all.
1404
1531
 
1405
1532
  Passed inline rather than as `--append-system-prompt-file` for the same reason `--settings`
1406
1533
  is: the sandbox spawn runs in a container that cannot read a host path.
@@ -1419,12 +1546,13 @@ has its `/` and `.` characters replaced with `-` (e.g.
1419
1546
  A session's display **title** is derived by scanning its JSONL for, in order of
1420
1547
  preference:
1421
1548
 
1422
- 1. a live **AI title** the server generated for the session this run (see below),
1423
- 2. else the latest `ai-title` record's `aiTitle` (e.g. written by MulmoClaude),
1424
- 3. else the latest `last-prompt` record's `lastPrompt`,
1425
- 4. else the first real user message (slash/local-command wrappers like
1549
+ 1. the **session note** the user wrote (see below),
1550
+ 2. else a live **AI title** the server generated for the session this run (see below),
1551
+ 3. else the latest `ai-title` record's `aiTitle` (e.g. written by MulmoClaude),
1552
+ 4. else the latest `last-prompt` record's `lastPrompt`,
1553
+ 5. else the first real user message (slash/local-command wrappers like
1426
1554
  `<local-command-…>` are skipped),
1427
- 5. else `"(untitled session)"`.
1555
+ 6. else `"(untitled session)"`.
1428
1556
 
1429
1557
  In-memory sessions not yet persisted show as `"New session"` until their file
1430
1558
  appears, at which point the on-disk title takes over.
@@ -1444,6 +1572,23 @@ ack (so the raw last prompt would be stale), or every few turns to keep a long s
1444
1572
  title current. The title lives in memory (never written into Claude's own transcript); a
1445
1573
  resumed session falls back to any on-disk `ai-title`.
1446
1574
 
1575
+ ### Session note
1576
+
1577
+ Every tier above says what the **agent** said, which stops answering "which cell is this?"
1578
+ once several sessions are open. So a cell header also takes a **note you write yourself**: the
1579
+ pencil button beside the header text opens a one-line box (Enter saves, Esc cancels, clicking
1580
+ away saves). While a note is set it *replaces* the header line — the title it displaced stays in
1581
+ the tooltip — and it becomes the session's title in the sidebar list and on the phone's roster
1582
+ too, so one session goes by one name everywhere.
1583
+
1584
+ Notes are capped at 200 characters and folded to a single line. They are stored per **session
1585
+ id** in `~/.mulmoterminal/session-memos.jsonl` and survive both the session being reaped and a
1586
+ server restart: resume the session and the note comes back. Saving one publishes it on the
1587
+ `sessions` channel, so every other open tab and the phone update without asking.
1588
+
1589
+ `POST /api/session/:id/memo` with `{ "text": "…" }` writes one; an empty `text` erases it. The
1590
+ route answers with the **stored** text, which is what a reload will show.
1591
+
1447
1592
  ---
1448
1593
 
1449
1594
  ## Project structure
@@ -1471,7 +1616,8 @@ server/
1471
1616
  mcp/ per-session MCP broker
1472
1617
  backends/ wiki, collections, feeds, accounting, notifier,
1473
1618
  translation, whisper, remote-host, html, files
1474
- skills/ bundled skills (mulmoterminal-config, mulmoterminal-bug-report, mulmoterminal-decisions)
1619
+ skills/ bundled skills: mulmoterminal-config (entry point + audit), -dirs, -theme,
1620
+ -header, -keys, -model, -notify, -bug-report, -decisions
1475
1621
  fix-pty-perms.js postinstall: fixes node-pty binary permissions
1476
1622
  src/
1477
1623
  App.vue Layout; owns the active session + single/grid view
package/bin/cli-args.d.ts CHANGED
@@ -11,3 +11,8 @@ export declare const MIN_NODE_LABEL: string;
11
11
  export declare function nodeMeetsMinimum(version: string): boolean;
12
12
  export declare function serverNodeArgs(serverEntry: string, launchDir: string): string[];
13
13
  export declare function serverSpawnEnv(env: Record<string, string | undefined>, port: number, cwd: string): Record<string, string | undefined>;
14
+ export interface RunningInstance {
15
+ pid: number;
16
+ port: number | null;
17
+ }
18
+ export declare function runningInstancesPrompt(instances: readonly RunningInstance[]): string;
package/bin/cli-args.js CHANGED
@@ -108,6 +108,26 @@ export function saysYes(answer) {
108
108
  * updates never cross — so the instance that did not start a session shows a frozen history
109
109
  * for it (#611).
110
110
  */
111
+ /**
112
+ * The question asked when another server is ALREADY RUNNING, whatever port this one was told to
113
+ * use (#1061).
114
+ *
115
+ * `secondInstancePrompt` below only fires when the wanted port is taken, so `--port <free>`
116
+ * started a second instance in silence — which is how eight live sessions had their settings
117
+ * deleted by a peer's boot. The clash is a symptom; the shared `~/.mulmoterminal` is the thing
118
+ * that is not supported, and that is true at any port.
119
+ */
120
+ export function runningInstancesPrompt(instances) {
121
+ const where = instances.map((i) => (i.port === null ? `pid ${i.pid}` : `http://localhost:${i.port}`)).join(", ");
122
+ const subject = instances.length === 1 ? "MulmoTerminal is already running" : `${instances.length} MulmoTerminal servers are already running`;
123
+ return [
124
+ `${subject} (${where}).`,
125
+ " Running more than one is NOT a supported setup: they share ~/.mulmoterminal,",
126
+ " so they can overwrite each other's session state.",
127
+ "Start another one anyway? [y/N] ",
128
+ ].join("\n");
129
+ }
130
+
111
131
  export const SECOND_INSTANCE_NOTE = [
112
132
  " Note: both share ~/.mulmoterminal. A session's tool history does not live-update",
113
133
  " in the instance that did not start it.",
@@ -0,0 +1,10 @@
1
+ export interface InstanceEntry {
2
+ pid: number;
3
+ port: number | null;
4
+ startedAt: number | null;
5
+ }
6
+ export declare function instancesDir(): string;
7
+ export declare function isProcessAlive(pid: number): boolean;
8
+ export declare function registerInstance(port: number, pid?: number, startedAt?: number): () => void;
9
+ export declare function liveInstances(excludePid?: number): InstanceEntry[];
10
+ export declare function earliestStartedAt(instances: readonly InstanceEntry[]): number | null;
@@ -0,0 +1,115 @@
1
+ // Which MulmoTerminal servers are running right now (#1061).
2
+ //
3
+ // Two things need this answer and neither could get it before. The launcher asks so it can say
4
+ // "one is already running" no matter which port the new one was told to use — the port probe only
5
+ // notices a clash on the SAME port, so `--port <free>` started a second instance in silence. And
6
+ // the server asks before deleting orphaned session settings: without tmux, "nothing survived the
7
+ // last lifetime" is only true for the process that OWNED that lifetime, so a second instance was
8
+ // reading a peer's live files as leftovers and removing them.
9
+ //
10
+ // Shared as plain JS rather than through common/ because the launcher runs on bare node before
11
+ // any TypeScript exists — the same reason bin/update-check.js is shaped this way, and the server
12
+ // imports it the same way.
13
+ import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import path from "node:path";
16
+
17
+ export const instancesDir = () => path.join(homedir(), ".mulmoterminal", "instances");
18
+
19
+ const entryFile = (pid) => path.join(instancesDir(), `${pid}.json`);
20
+
21
+ /** Whether a pid belongs to a process that still exists. Signal 0 performs the permission and
22
+ * existence checks without delivering anything; EPERM means it IS there, just not ours. */
23
+ export function isProcessAlive(pid) {
24
+ if (!Number.isInteger(pid) || pid <= 0) return false;
25
+ try {
26
+ process.kill(pid, 0);
27
+ return true;
28
+ } catch (err) {
29
+ return err?.code === "EPERM";
30
+ }
31
+ }
32
+
33
+ /** Announce this process, and hand back the removal. Best-effort throughout: not being able to
34
+ * say hello is not a reason to fail a boot, and a crash simply leaves an entry whose pid no
35
+ * longer resolves — which every reader already has to handle. */
36
+ export function registerInstance(port, pid = process.pid, startedAt = Date.now()) {
37
+ const file = entryFile(pid);
38
+ try {
39
+ mkdirSync(instancesDir(), { recursive: true, mode: 0o700 });
40
+ // Written elsewhere and renamed into place: a reader that catches us mid-write would see a
41
+ // truncated entry, and "I could not parse it" must never be how a LIVE peer disappears from
42
+ // the registry (Codex review). rename is atomic for a reader on the same directory.
43
+ const tmp = `${file}.${startedAt}.tmp`;
44
+ writeFileSync(tmp, JSON.stringify({ pid, port, startedAt }), { encoding: "utf8", mode: 0o600 });
45
+ renameSync(tmp, file);
46
+ } catch {
47
+ return () => {};
48
+ }
49
+ return () => {
50
+ try {
51
+ rmSync(file, { force: true });
52
+ } catch {
53
+ // Left behind; the next reader drops it as stale.
54
+ }
55
+ };
56
+ }
57
+
58
+ const parseEntry = (raw) => {
59
+ try {
60
+ const entry = JSON.parse(raw);
61
+ if (!entry || typeof entry !== "object") return null;
62
+ const { pid, port, startedAt } = entry;
63
+ if (!Number.isInteger(pid) || pid <= 0) return null;
64
+ return { pid, port: Number.isInteger(port) ? port : null, startedAt: Number.isFinite(startedAt) ? startedAt : null };
65
+ } catch {
66
+ return null;
67
+ }
68
+ };
69
+
70
+ /** Every OTHER running server, newest entry first. Entries whose process is gone are deleted as
71
+ * they are found: a crash cannot clean up after itself, and leaving them would make a lone
72
+ * instance believe it has company forever. */
73
+ export function liveInstances(excludePid = process.pid) {
74
+ let names;
75
+ try {
76
+ names = readdirSync(instancesDir());
77
+ } catch {
78
+ return []; // never registered on this machine
79
+ }
80
+ const live = [];
81
+ for (const name of names) {
82
+ if (!name.endsWith(".json")) continue;
83
+ const file = path.join(instancesDir(), name);
84
+ let entry;
85
+ try {
86
+ entry = parseEntry(readFileSync(file, "utf8"));
87
+ } catch {
88
+ continue; // being written right now, or unreadable — say nothing about it
89
+ }
90
+ // Only a positively-identified dead owner earns removal. An entry we could not parse is one
91
+ // we know nothing about — deleting it on that basis is how a live peer gets erased, which is
92
+ // the very failure this registry exists to prevent (Codex review).
93
+ if (entry === null) continue;
94
+ if (!isProcessAlive(entry.pid)) {
95
+ try {
96
+ rmSync(file, { force: true });
97
+ } catch {
98
+ // not ours to remove; harmless
99
+ }
100
+ continue;
101
+ }
102
+ if (entry.pid !== excludePid) live.push(entry);
103
+ }
104
+ return live.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
105
+ }
106
+
107
+ /** When the EARLIEST of the given instances started, or null when there are none.
108
+ *
109
+ * This is the cutoff the settings prune needs: a file written before every live peer began
110
+ * cannot belong to one of them, so it is safe to treat as a leftover. A file written after that
111
+ * moment might be a peer's, and a maybe is not good enough to delete somebody's live state. */
112
+ export function earliestStartedAt(instances) {
113
+ const times = instances.map((i) => i.startedAt).filter((t) => typeof t === "number");
114
+ return times.length ? Math.min(...times) : null;
115
+ }
@@ -22,12 +22,14 @@ import {
22
22
  portInUseMessage,
23
23
  saysYes,
24
24
  secondInstancePrompt,
25
+ runningInstancesPrompt,
25
26
  SECOND_INSTANCE_NOTE,
26
27
  nodeMeetsMinimum,
27
28
  MIN_NODE_LABEL,
28
29
  serverNodeArgs,
29
30
  serverSpawnEnv,
30
31
  } from "./cli-args.js";
32
+ import { liveInstances } from "./instances.js";
31
33
 
32
34
  const __dirname = dirname(fileURLToPath(import.meta.url));
33
35
  const PKG_DIR = join(__dirname, "..");
@@ -243,6 +245,20 @@ function findEphemeralPort() {
243
245
  });
244
246
  }
245
247
 
248
+ // Ask about an ALREADY-RUNNING server, whatever port this one will use. Declining exits 0: the
249
+ // user answered the question that was asked, which is not a failure.
250
+ async function confirmNoRunningInstance() {
251
+ const running = liveInstances();
252
+ if (running.length === 0) return;
253
+ if (!process.stdin.isTTY) {
254
+ log(runningInstancesPrompt(running).replace(/\nStart another one anyway\? \[y\/N\] $/, ""));
255
+ log(SECOND_INSTANCE_NOTE);
256
+ return;
257
+ }
258
+ if (!(await promptYesNo(runningInstancesPrompt(running)))) process.exit(0);
259
+ log(SECOND_INSTANCE_NOTE);
260
+ }
261
+
246
262
  async function choosePort(requested, explicit) {
247
263
  if (await isPortFree(requested)) return requested;
248
264
  // No SILENT fallback: starting a second server on another port without saying so is how
@@ -322,7 +338,7 @@ function runServer(port, noOpen, cwd, onChild) {
322
338
 
323
339
  function printHelp() {
324
340
  console.log(`
325
- Usage: npx mulmoterminal [command] [options]
341
+ Usage: npx mulmoterminal@latest [command] [options]
326
342
 
327
343
  Commands:
328
344
  (none) Start the server (default)
@@ -396,6 +412,12 @@ async function main() {
396
412
  // The probe above can still lose to something binding the port in the same instant, in
397
413
  // which case the server exits 75 and runServer returns. Same answer as the probe: say who
398
414
  // has it rather than moving to a port nobody asked for.
415
+ // Before anything about PORTS: is one already running at all? A clash on the same port is only
416
+ // the visible half — `--port <free>` used to start a second instance in silence, and the two
417
+ // then share ~/.mulmoterminal (#1061). Non-TTY says it and carries on rather than hanging on a
418
+ // prompt nobody can answer: a script that asked for a server should still get one.
419
+ await confirmNoRunningInstance();
420
+
399
421
  const port = await choosePort(requestedPort, portExplicit);
400
422
  await runServer(port, noOpen, cwd, (c) => {
401
423
  child = c;
@@ -0,0 +1,31 @@
1
+ // The skills MulmoTerminal ships, named once for both sides.
2
+ //
3
+ // Shared rather than mirrored because BOTH sides decide from these: the server COPIES these
4
+ // directories into the user's skills roots at boot, and the client LAUNCHES one by name from the
5
+ // Settings section that owns it. A name that lives in two places is a name that can drift, and it
6
+ // did — the grid view and the single view each spelled the appearance skill out, a rename updated
7
+ // one of them, and the same button then launched a different skill depending on which view you
8
+ // pressed it from. Each half was self-consistent, so nothing failed (Codex review on #1103).
9
+ // `BundledSkillName` is what keeps the Settings buttons honest now: a slug naming no shipped skill
10
+ // is a type error rather than an agent replying that it can't find one.
11
+
12
+ /** Every skill directory under `server/skills/`, in the order the installer walks them. A spec
13
+ * pins this list against what is actually on disk: adding a directory is not what ships it, and
14
+ * one nobody lists is copied nowhere with no error at any point. */
15
+ export const BUNDLED_SKILL_NAMES = [
16
+ "mulmoterminal-config",
17
+ "mulmoterminal-dirs",
18
+ "mulmoterminal-theme",
19
+ "mulmoterminal-header",
20
+ "mulmoterminal-keys",
21
+ "mulmoterminal-model",
22
+ "mulmoterminal-notify",
23
+ "mulmoterminal-bug-report",
24
+ "mulmoterminal-decisions",
25
+ ] as const;
26
+
27
+ export type BundledSkillName = (typeof BUNDLED_SKILL_NAMES)[number];
28
+
29
+ /** The skill that owns `<project>/.mulmoterminal.json` — the one the generated JSON Schema ships
30
+ * beside, so the schema sits with the skill that actually writes that file. */
31
+ export const DIR_CONFIG_SKILL = "mulmoterminal-dirs";