mulmoterminal 4.27.0 → 5.0.1

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 (141) hide show
  1. package/README.md +14 -0
  2. package/common/codeBlocks.ts +33 -4
  3. package/common/transcriptView.ts +78 -0
  4. package/dist/assets/{AllPackages-B1YCJRYB-D1H--dDi.js → AllPackages-B1YCJRYB-4WQ_EIrl.js} +1 -1
  5. package/dist/assets/{BaseConfiguration-B6s_tKOW-NM1em0z-.js → BaseConfiguration-B6s_tKOW-CuXGj6gD.js} +1 -1
  6. package/dist/assets/{Element-BMI7Nj5w-D60lwUcy.js → Element-BMI7Nj5w-Bik3AfgD.js} +1 -1
  7. package/dist/assets/{Entities-C8p_0jCn-CRrh9My2.js → Entities-C8p_0jCn-CjiJEF3r.js} +1 -1
  8. package/dist/assets/{FunctionList-YWk54TCy-DxlXvxS1.js → FunctionList-YWk54TCy-Dq1GQL-M.js} +1 -1
  9. package/dist/assets/{InputJax-C5dj-ntL-DMM7A5J_.js → InputJax-C5dj-ntL-DEjnMcSa.js} +1 -1
  10. package/dist/assets/{MathItem-YJvQL-XJ-DjZ8sLcx.js → MathItem-YJvQL-XJ-pMaMH7Pe.js} +1 -1
  11. package/dist/assets/{MmlFactory-DSoKkE7s-BVH8BvMO.js → MmlFactory-DSoKkE7s-BTFYdeyR.js} +1 -1
  12. package/dist/assets/{Options-oARQeeuT-CeIzK2vQ.js → Options-oARQeeuT-DN-QpMwi.js} +1 -1
  13. package/dist/assets/{OutputJax-DfUZNNCu-BipoI_Vx.js → OutputJax-DfUZNNCu-0eNVVspA.js} +1 -1
  14. package/dist/assets/{PrioritizedList-6lun1Xqd-CkxjdTLH.js → PrioritizedList-6lun1Xqd-B3xDwkAm.js} +1 -1
  15. package/dist/assets/{Styles-5NmwS-3M-CuS-cAHH.js → Styles-5NmwS-3M-CYH8SIw8.js} +1 -1
  16. package/dist/assets/{TeXAtom-B5ni9myz-jvVqbY5R.js → TeXAtom-B5ni9myz-B2Lg1OeP.js} +1 -1
  17. package/dist/assets/{abnfDiagram-VCTEODGH-BGhi-oVb-DdAoBuDS.js → abnfDiagram-VCTEODGH-BGhi-oVb-rfnIw5mF.js} +1 -1
  18. package/dist/assets/{arc-CK5ru50N-ByhquzQA.js → arc-CK5ru50N-CpwYDtFh.js} +1 -1
  19. package/dist/assets/architecture-7GRP2DOG-otSkCkT5-BXYe9Y4x.js +1 -0
  20. package/dist/assets/{architectureDiagram-5GKGNRK7-ELuPYMlf-BMar3__r.js → architectureDiagram-5GKGNRK7-ELuPYMlf-jRFBK2xY.js} +1 -1
  21. package/dist/assets/{assistive-mml-CdU3rUpx-CvEGUtQr.js → assistive-mml-CdU3rUpx-CfM4F5jW.js} +1 -1
  22. package/dist/assets/{blockDiagram-I7D4REHJ-BZ7-OGWQ-CzYqZh-X.js → blockDiagram-I7D4REHJ-BZ7-OGWQ-GPkhdnf1.js} +1 -1
  23. package/dist/assets/{c4Diagram-7LVT6UL2-DCovPZ_4-DVY6zyiz.js → c4Diagram-7LVT6UL2-DCovPZ_4-DlgnltG1.js} +1 -1
  24. package/dist/assets/channel-Bmu_OOc6-Byl1e1Rn.js +1 -0
  25. package/dist/assets/{chunk-4HAMMTFA-DqpzLmar-7_4tQ0bg.js → chunk-4HAMMTFA-DqpzLmar-BYwab83k.js} +1 -1
  26. package/dist/assets/{chunk-75Z2AOVW-CT5TYzp5-CXC4AdVC.js → chunk-75Z2AOVW-CT5TYzp5-BRr2sap2.js} +1 -1
  27. package/dist/assets/{chunk-DU6HZSFF-BVf-rtdf-B3kg-yCL.js → chunk-DU6HZSFF-BVf-rtdf-BiTD5bH2.js} +1 -1
  28. package/dist/assets/{chunk-F27PBJKO-rP8pUsk3-BfNwyFPK.js → chunk-F27PBJKO-rP8pUsk3-jZL8tnaO.js} +1 -1
  29. package/dist/assets/{chunk-IMKFNOWR-D_IT0r3G-C9_8s332.js → chunk-IMKFNOWR-D_IT0r3G-4Na8DCGZ.js} +1 -1
  30. package/dist/assets/{chunk-POPQ4Y6H-C5YURnHe-eTW1aY6b.js → chunk-POPQ4Y6H-C5YURnHe-BtvIAZQ9.js} +1 -1
  31. package/dist/assets/{chunk-SHT3W25Y-CfF0J8Qh-v6mzVzMd.js → chunk-SHT3W25Y-CfF0J8Qh-CSKY6pei.js} +1 -1
  32. package/dist/assets/{chunk-SVP7TREG-CTZbRIbX-RW-t42ub.js → chunk-SVP7TREG-CTZbRIbX-DXmyUlG6.js} +1 -1
  33. package/dist/assets/{chunk-TICWLB2K-DP1lYmZm-7dMJ2TPj.js → chunk-TICWLB2K-DP1lYmZm-2WQGl4am.js} +1 -1
  34. package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-DuFfjK1v.js → chunk-XXDRQBXY-vg0kfC73-BNbpCBp8.js} +1 -1
  35. package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-DGA1_qS0.js +1 -0
  36. package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-DGA1_qS0.js +1 -0
  37. package/dist/assets/{cose-bilkent-JH36ORCC-qIiD3RGD-DrmynCpE.js → cose-bilkent-JH36ORCC-qIiD3RGD-glIzrMsu.js} +1 -1
  38. package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-DCyyQmwN.js +1 -0
  39. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BZrUCS-v-CKIcLifI.js → cynefinDiagram-5FMLGOSQ-BZrUCS-v-BP9iYDiL.js} +1 -1
  40. package/dist/assets/{dagre-C2SZ8h4o-DcuFxdj9.js → dagre-C2SZ8h4o-Cvn7MMN8.js} +1 -1
  41. package/dist/assets/{dagre-GXQ25YYZ-CQcLM4na-DHXn50pv.js → dagre-GXQ25YYZ-CQcLM4na-CUPIues9.js} +1 -1
  42. package/dist/assets/{diagram-S7CK7UJ4-BdmcmUQh-CPCAAKMD.js → diagram-S7CK7UJ4-BdmcmUQh-BzmDaJl4.js} +1 -1
  43. package/dist/assets/{diagram-UQ7AKVKN-BtvmEiYl-aA6cOCqA.js → diagram-UQ7AKVKN-BtvmEiYl-C-SDkeQT.js} +1 -1
  44. package/dist/assets/{diagram-VSXAHHWV-qEG6N0bV-DCHB2hTq.js → diagram-VSXAHHWV-qEG6N0bV-BBEGoVq0.js} +1 -1
  45. package/dist/assets/{diagram-VX7I27RA-BA8is49N-DOwC18Z0.js → diagram-VX7I27RA-BA8is49N-C_RijuTO.js} +1 -1
  46. package/dist/assets/{diagram-Z3DM3KII-XPcU7IOQ-C9RPVYC4.js → diagram-Z3DM3KII-XPcU7IOQ-CxOwdwHR.js} +1 -1
  47. package/dist/assets/{dist-v-Rpnm_y.js → dist-BvG2w_9k.js} +1 -1
  48. package/dist/assets/{dist-wvHglY68.js → dist-CWF1S7vL.js} +1 -1
  49. package/dist/assets/{dist-fO6jnnrd.js → dist-DBRVDw9D.js} +1 -1
  50. package/dist/assets/{dist-DeiyD9Ki-CJ4cIe6S.js → dist-DeiyD9Ki-BDcmanZh.js} +1 -1
  51. package/dist/assets/{dist-CZghBWKw.js → dist-uQlE_JYp.js} +1 -1
  52. package/dist/assets/{ebnfDiagram-PWID7BFC-CTHIHdBS-DHkqIR3N.js → ebnfDiagram-PWID7BFC-CTHIHdBS-BGwyAx_X.js} +1 -1
  53. package/dist/assets/{erDiagram-RLTQ6QDP-DVpWTNZj-C3lwqnXe.js → erDiagram-RLTQ6QDP-DVpWTNZj-CpwnuTPx.js} +1 -1
  54. package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-DWVjLwmJ.js +1 -0
  55. package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-CjAJJOoL.js +1 -0
  56. package/dist/assets/{ganttDiagram-EL5Y4UJY-BfAgk15F-2NYwYOby.js → ganttDiagram-EL5Y4UJY-BfAgk15F-CuhMMDMU.js} +1 -1
  57. package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-CmU_NC2u.js +1 -0
  58. package/dist/assets/{gitGraphDiagram-WWUBYQGX-DEVVZ6wt-k0BkI3nD.js → gitGraphDiagram-WWUBYQGX-DEVVZ6wt-Bh_VaB4G.js} +1 -1
  59. package/dist/assets/{graphlib-BYLhHyYO-Bdiyyely.js → graphlib-BYLhHyYO-BCQclP1Y.js} +1 -1
  60. package/dist/assets/{html-Z7dGbczU-BTiRsVw0.js → html-Z7dGbczU-UIlR1xAp.js} +1 -1
  61. package/dist/assets/index-DgdW3UkV.css +1 -0
  62. package/dist/assets/{index-6GNSSCav.js → index-sxsMmFcI.js} +300 -298
  63. package/dist/assets/info-A6RAGUB7-fR2SUJmJ-Dc6y50d4.js +1 -0
  64. package/dist/assets/{infoDiagram-27XIBGKW-CvHUhAHd-CrTJpCqo.js → infoDiagram-27XIBGKW-CvHUhAHd-DigY259e.js} +1 -1
  65. package/dist/assets/{ishikawaDiagram-5VMMS53U-CeqyKdwk-eDLXPEBD.js → ishikawaDiagram-5VMMS53U-CeqyKdwk-ACmU-Jzv.js} +1 -1
  66. package/dist/assets/{journeyDiagram-3NMN7TZE-Jfi7MPDi-CvhaSk4y.js → journeyDiagram-3NMN7TZE-Jfi7MPDi-YIFwpJyP.js} +1 -1
  67. package/dist/assets/{kanban-definition-UXKFOSKX-yOrlojns-C2SZht6T.js → kanban-definition-UXKFOSKX-yOrlojns-0RXVl3u9.js} +1 -1
  68. package/dist/assets/{lengths-CvmPfBid-DqArahpv.js → lengths-CvmPfBid-BAfNjGrF.js} +1 -1
  69. package/dist/assets/{linear-jdmEy-qs-Cv8NGHBd.js → linear-jdmEy-qs-Djjlqhox.js} +1 -1
  70. package/dist/assets/{liteAdaptor-pH5MXTG0-B1LIDixs.js → liteAdaptor-pH5MXTG0-pAc8cmKr.js} +1 -1
  71. package/dist/assets/material-symbols-outlined-Dp0GkOHZ.woff2 +0 -0
  72. package/dist/assets/{mathjax-DVEo4UYj-CTQSsbqK.js → mathjax-DVEo4UYj-Cnj1zcS1.js} +1 -1
  73. package/dist/assets/{mermaid-parser.core-CzBW8Fof-BTOBTXVI.js → mermaid-parser.core-CzBW8Fof-s3bWPn0O.js} +2 -2
  74. package/dist/assets/{mermaid.core-Dv4uylek-CwMi65mG.js → mermaid.core-Dv4uylek-DqGXjOPu.js} +4 -4
  75. package/dist/assets/{mindmap-definition-YA3MSWOX-DP_2R9Ga-BJ9md154.js → mindmap-definition-YA3MSWOX-DP_2R9Ga-KrOUlEvG.js} +1 -1
  76. package/dist/assets/{mo-Xv_6CC7A-YT1Ep9ep.js → mo-Xv_6CC7A-5T19MjVl.js} +1 -1
  77. package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-DzcrK2qY.js +1 -0
  78. package/dist/assets/{pegDiagram-XKGWAZYB-BsBWryhi-DcmWmYCC.js → pegDiagram-XKGWAZYB-BsBWryhi-DYwgFu60.js} +1 -1
  79. package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-EXLeDSJc.js +1 -0
  80. package/dist/assets/{pieDiagram-E7YTZNPT-OugkS2pU-cVOrSsmv.js → pieDiagram-E7YTZNPT-OugkS2pU-CitvFGku.js} +1 -1
  81. package/dist/assets/{quadrantDiagram-AXDQQJYC-DXEAAaUE-CJ7E8xF3.js → quadrantDiagram-AXDQQJYC-DXEAAaUE-BJz-5fWN.js} +1 -1
  82. package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-CYsT6Q2i.js +1 -0
  83. package/dist/assets/railroad-74A4TZTK-6lEXVJo8-CGKkcPl-.js +1 -0
  84. package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-CX4pHVjI.js +1 -0
  85. package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-DuFCuwxK.js +1 -0
  86. package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-BZCRCTz5.js +1 -0
  87. package/dist/assets/{railroadDiagram-O6MQD6OU-BJys8O96-0MKi1nlY.js → railroadDiagram-O6MQD6OU-BJys8O96-8S8j18MZ.js} +1 -1
  88. package/dist/assets/{requirementDiagram-BXWQKSXE-BaDgCtM5-Dh9naf1P.js → requirementDiagram-BXWQKSXE-BaDgCtM5-D0sLbDHs.js} +1 -1
  89. package/dist/assets/{sankeyDiagram-P5KCCOFB-puZsr_vK-DnFp1pF-.js → sankeyDiagram-P5KCCOFB-puZsr_vK-C1OX3N7F.js} +1 -1
  90. package/dist/assets/{sequenceDiagram-WJ2MYXX4-D1ziEryC-QyuuTtv6.js → sequenceDiagram-WJ2MYXX4-D1ziEryC-CvoJhcsM.js} +1 -1
  91. package/dist/assets/{src-CLC0ddSd-DaM5Q5tg.js → src-CLC0ddSd-D-EBSy-7.js} +1 -1
  92. package/dist/assets/{stateDiagram-D77RDMKH-HSEurT6h-CqWtYwN6.js → stateDiagram-D77RDMKH-HSEurT6h-DWNnPlrT.js} +1 -1
  93. package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-jwvolGHy.js +1 -0
  94. package/dist/assets/{svg-D_RYJvQC-T-beFXEZ.js → svg-D_RYJvQC-xxS_UOMd.js} +1 -1
  95. package/dist/assets/{swimlanes-42K2YHIH-umBsuvsI-CBrrgPel.js → swimlanes-42K2YHIH-umBsuvsI-BE4MiInp.js} +1 -1
  96. package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-DNoFZykH.js +8 -0
  97. package/dist/assets/{tex-Dx2UJ3VU-tBwETaM2.js → tex-Dx2UJ3VU-Bvt244KZ.js} +1 -1
  98. package/dist/assets/{timeline-definition-24CTP7MA-D9plt22Y-XpXWZKjL.js → timeline-definition-24CTP7MA-D9plt22Y-DHwEP0-q.js} +1 -1
  99. package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-Cw7gALPI.js +1 -0
  100. package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-BuBwqHqi.js +1 -0
  101. package/dist/assets/{vennDiagram-4TSXK5OY-DZe7wY85-BX7eqUQe.js → vennDiagram-4TSXK5OY-DZe7wY85-BzqCxOQu.js} +1 -1
  102. package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-Ds6_n_M_.js +1 -0
  103. package/dist/assets/{wardleyDiagram-VM6X3IG4-DK9yytWx-Dwg2gU8u.js → wardleyDiagram-VM6X3IG4-DK9yytWx-FK9bW_eQ.js} +1 -1
  104. package/dist/assets/{xychartDiagram-S5SC5T6Z-CLSO4Pc9-D0s7XGBw.js → xychartDiagram-S5SC5T6Z-CLSO4Pc9-BGF6axe4.js} +1 -1
  105. package/dist/index.html +2 -2
  106. package/package.json +1 -1
  107. package/server/agents/copilot-sessions.ts +12 -4
  108. package/server/agents/sqlite-read.ts +1 -1
  109. package/server/backends/remoteHost/handlers/deps.ts +1 -1
  110. package/server/backends/remoteHost/handlers.spec.ts +1 -1
  111. package/server/infra/jsonl-file.ts +13 -3
  112. package/server/routes/app-routes.ts +1 -1
  113. package/server/routes/session-routes.ts +33 -0
  114. package/server/session/transcript-view-codex.ts +4 -3
  115. package/server/session/transcript-view-copilot.ts +11 -9
  116. package/server/session/transcript-view-cursor.ts +3 -2
  117. package/server/session/transcript-view-read.ts +298 -45
  118. package/server/session/transcript-view.ts +47 -43
  119. package/dist/assets/architecture-7GRP2DOG-otSkCkT5-Zckz-s0E.js +0 -1
  120. package/dist/assets/channel-Bmu_OOc6-Dl37yKXs.js +0 -1
  121. package/dist/assets/classDiagram-ZZMXUADV-BDQOAOBf-DSv4U5yn.js +0 -1
  122. package/dist/assets/classDiagram-v2-VYDZK3BY-CwPXtzoF-DSv4U5yn.js +0 -1
  123. package/dist/assets/cynefin-OW5HDTMX-Xr6IPdZG-BOI4Ydck.js +0 -1
  124. package/dist/assets/eventmodeling-NTZA5JFV-B7bRrpt_-aX72rk9t.js +0 -1
  125. package/dist/assets/flowDiagram-HODETNUW-r-3T5g30-BJR3huwZ.js +0 -1
  126. package/dist/assets/gitGraph-4MIJSDKK-BhzSx5wq-D7vu6KLb.js +0 -1
  127. package/dist/assets/index-C4KnFBMJ.css +0 -1
  128. package/dist/assets/info-A6RAGUB7-fR2SUJmJ-CeXI1IP4.js +0 -1
  129. package/dist/assets/material-symbols-outlined-DhPLOrnb.woff2 +0 -0
  130. package/dist/assets/packet-AYTQ26CC-B_oKR9KZ-BTHvzsvA.js +0 -1
  131. package/dist/assets/pie-WAS4IAKB-D1ZXrN1W-D2lL43qt.js +0 -1
  132. package/dist/assets/radar-RG4KPBEZ-BmUgDkiH-C-pMUxsI.js +0 -1
  133. package/dist/assets/railroad-74A4TZTK-6lEXVJo8-D0K7WIF_.js +0 -1
  134. package/dist/assets/railroad-abnf-HS5TGJTU-BG28Qiz1-BwwZFdLC.js +0 -1
  135. package/dist/assets/railroad-ebnf-LZEXJU2U-DzpHhtTH-DEXLsLfR.js +0 -1
  136. package/dist/assets/railroad-peg-WCYAUIDC-B2CHCTyG-CIIHCbpi.js +0 -1
  137. package/dist/assets/stateDiagram-v2-MP3YSRHH-DnOa-Otc-DqMPmDPL.js +0 -1
  138. package/dist/assets/swimlanesDiagram-VR7AAH4N-ZLUyNmnU-Bew5AEPR.js +0 -8
  139. package/dist/assets/treeView-Q6P3EWNA-BdvZ0_C5-BheZ-W3N.js +0 -1
  140. package/dist/assets/treemap-WGGIJYW6-CIJLPQlC-Cjamu2ad.js +0 -1
  141. package/dist/assets/wardley-WFR3VGLG-CKqtFD8V-BhmuAvdu.js +0 -1
package/README.md CHANGED
@@ -1641,6 +1641,20 @@ narrow and tall for one record you are discussing — and each position keeps it
1641
1641
  prompt history, codex's rollout), so a prompt sent mid-turn is there and text a skill
1642
1642
  injected is not. A `/clear` draws a line: the pane shows what you have asked *since*, the
1643
1643
  same way the header, the title and the last reply all stop describing the ended conversation.
1644
+ - **Conversation pane** — the enlarged cell's conversation, read as TURNS rather than as the
1645
+ screen, from `GET /api/transcript/view`. A Claude cell runs on the alternate screen, so the
1646
+ terminal keeps no scrollback worth scrolling; this reads the agent's own transcript instead,
1647
+ and **scrolling up walks the session back to its first turn**, a page at a time.
1648
+ - It **takes the terminal's place when you open it** — the one pane that does, because reading
1649
+ is its whole subject. The expand button puts the terminal back beside it.
1650
+ - **A frame per speaker**: what you asked in one, what the agent answered in another, named
1651
+ ("You" / "Claude"). The reply is **rendered markdown** — headings, lists, tables, fenced
1652
+ code — not the characters it is written with.
1653
+ - **Tool traffic is folded away**, one line saying what ran (`Bash · Read`); click to open it.
1654
+ It is most of a turn's bulk and almost none of what you came back to read.
1655
+ - A **snapshot**, not a live tail — the terminal beside it is the live view — and the header's
1656
+ reload fetches the newest turns again. Claude, Codex, Cursor and Copilot; an agent whose
1657
+ conversation no reader here can read yet says so rather than showing an empty pane.
1644
1658
  - **Notifications** (🔔) — a toolbar bell with an unread badge and a dropdown of active
1645
1659
  notifications; click a row to jump to its session.
1646
1660
  - **Star MulmoTerminal** — a star button in the grid toolbar that stars the project on GitHub
@@ -55,9 +55,31 @@ const closes = (line: string, opener: string): boolean => {
55
55
  return !!fence && fence.run[0] === opener[0] && fence.run.length >= opener.length && fence.info.trim() === "";
56
56
  };
57
57
 
58
- export function fencedBlocks(markdown: string): FencedBlock[] {
58
+ /** A run of prose, or one fenced block — the whole text, in order and with nothing dropped.
59
+ *
60
+ * This is the ONE walk over the fences, and `fencedBlocks` below is it with the prose dropped. It
61
+ * is not a second scanner: the two used to be separate loops over the same rules, which is two
62
+ * places for a fence rule to drift.
63
+ *
64
+ * It is deliberately not tied to any one reader. The transcript pane rendered its segments for a
65
+ * while and now renders the reply as markdown instead (#2112); saying otherwise here sends the next
66
+ * reader looking for a consumer that no longer exists (Claude review, round 1). */
67
+ export type MarkdownSegment = { kind: "text"; text: string } | { kind: "code"; lang: string | null; body: string };
68
+
69
+ /** The one walk over the fences. `fencedBlocks` is this with the prose dropped.
70
+ *
71
+ * Prose runs are joined with the newlines they had, so a run's own blank lines survive; a segment
72
+ * is emitted only when it has a line in it, so an input that opens with a fence does not lead with
73
+ * an empty one. */
74
+ export function markdownSegments(markdown: string): MarkdownSegment[] {
59
75
  const lines = markdown.split("\n");
60
- const blocks: FencedBlock[] = [];
76
+ const segments: MarkdownSegment[] = [];
77
+ const prose: string[] = [];
78
+ const flushProse = (): void => {
79
+ if (prose.length === 0) return;
80
+ segments.push({ kind: "text", text: prose.join("\n") });
81
+ prose.length = 0;
82
+ };
61
83
  let i = 0;
62
84
  // Each line is read into a const before use: with noUncheckedIndexedAccess, `lines[i]` is
63
85
  // `string | undefined` even inside `i < lines.length`, and the loop condition is what actually
@@ -66,9 +88,11 @@ export function fencedBlocks(markdown: string): FencedBlock[] {
66
88
  const line = lines[i];
67
89
  const open = line === undefined ? null : fenceOf(line);
68
90
  if (!open) {
91
+ if (line !== undefined) prose.push(line);
69
92
  i++;
70
93
  continue;
71
94
  }
95
+ flushProse();
72
96
  const fence = open.run;
73
97
  const info = open.info.trim();
74
98
  const body: string[] = [];
@@ -78,9 +102,14 @@ export function fencedBlocks(markdown: string): FencedBlock[] {
78
102
  i++;
79
103
  }
80
104
  i++; // step over the closing fence (or past the end, for an unclosed block)
81
- blocks.push({ lang: info ? (info.split(/\s+/)[0]?.toLowerCase() ?? null) : null, body: body.join("\n") });
105
+ segments.push({ kind: "code", lang: info ? (info.split(/\s+/)[0]?.toLowerCase() ?? null) : null, body: body.join("\n") });
82
106
  }
83
- return blocks;
107
+ flushProse();
108
+ return segments;
109
+ }
110
+
111
+ export function fencedBlocks(markdown: string): FencedBlock[] {
112
+ return markdownSegments(markdown).flatMap((segment) => (segment.kind === "code" ? [{ lang: segment.lang, body: segment.body }] : []));
84
113
  }
85
114
 
86
115
  /** The block a reader means by "the code you just gave me": the last one. Null when the reply
@@ -0,0 +1,78 @@
1
+ // What a hosted agent's conversation looks like ON THE WIRE.
2
+ //
3
+ // In `common/` because both ends decide from it: the host folds an agent's JSONL (or copilot's
4
+ // table) into these shapes in `server/session/transcript-view.ts`, and two clients render them —
5
+ // the phone's terminal detail page over the remoteHost socket, and the browser's transcript pane
6
+ // over `/api/transcript/view`. Mirroring the shape into `src/` would let one side gain a status the
7
+ // other silently renders as nothing.
8
+ //
9
+ // The RULES that produce these — the turn boundary, the line budget, the tool-result cap — stay on
10
+ // the server. This file is the vocabulary, not the folding.
11
+
12
+ export type TranscriptRowKind = "user" | "assistant" | "tool" | "unknown";
13
+
14
+ /** One content block, rendered.
15
+ *
16
+ * `text` may itself contain newlines — an assistant answer is passed through whole, because the
17
+ * wrapping belongs to the client's CSS and not to a host that cannot know its width.
18
+ *
19
+ * `clipped` says THIS row's text was cut, which is a different fact from `TranscriptView.truncated`
20
+ * (a whole turn was dropped). Naming them the same word would leave the client unable to decide
21
+ * which mark to draw. */
22
+ export interface TranscriptRow {
23
+ kind: TranscriptRowKind;
24
+ text: string;
25
+ clipped?: boolean;
26
+ /** This tool row is the CALL — the tool's name and the head of its arguments — rather than what
27
+ * the tool answered (#2112).
28
+ *
29
+ * Said by the host because only the host can say it: both halves arrive as `kind: "tool"`, and a
30
+ * client telling them apart by shape ("short and single-line means a name") is guessing about
31
+ * four agents' output at once. A pane that collapses tool traffic needs it to label the fold with
32
+ * what RAN — the part a reader is looking for when they open it again. */
33
+ call?: boolean;
34
+ }
35
+
36
+ /** One exchange: a user prompt and everything that followed it.
37
+ *
38
+ * `at` is the BOUNDARY record's own timestamp — the moment the turn started — or null when it is
39
+ * not a string. Null rather than dropping the turn: a turn is worth more than its clock. */
40
+ export interface TranscriptTurn {
41
+ at: string | null;
42
+ rows: TranscriptRow[];
43
+ }
44
+
45
+ /** What the host answers. A discriminated union rather than "readable: boolean": "no transcript
46
+ * yet", "the conversation was ended with /clear" and "too big to find a turn in" are three
47
+ * different things to tell a person, and one boolean collapses them into the same blank view. */
48
+ export type TranscriptView =
49
+ | { status: "ok"; turns: TranscriptTurn[]; truncated: boolean }
50
+ | { status: "none" }
51
+ | { status: "cleared" }
52
+ | { status: "too-large" }
53
+ /** This session's agent keeps a conversation somewhere, and no reader here can read it yet
54
+ * (#1822). A DIFFERENT fact from `none`, which means "this session has written nothing we can
55
+ * find" — the phone falls back to the screen for both, but only one of them is worth a sentence
56
+ * to a person, and only one of them is a thing to go and implement.
57
+ *
58
+ * Never answered for a shell or a launcher cell: those have no conversation and never will, so
59
+ * the screen IS their content rather than a fallback from something missing. */
60
+ | { status: "not-supported" };
61
+
62
+ /** One page of a conversation, read backwards from `older` (#2112).
63
+ *
64
+ * The cursor is OPAQUE to the client on purpose: it is a byte offset for the three agents that
65
+ * keep a file and a `turn_index` for the one that keeps a table, and a client that knew which
66
+ * would be deciding something only the host can know. It is handed back verbatim to ask for the
67
+ * page before this one.
68
+ *
69
+ * `older: null` means this page reaches the start of what can be read — either the head of the
70
+ * transcript, or the point past which the host refuses to read (`status: "too-large"`).
71
+ *
72
+ * The view is carried whole rather than flattened in beside the cursor: every non-`ok` status is
73
+ * a sentence the client already knows how to show, and re-deciding them per page is how the two
74
+ * ends drift. */
75
+ export interface TranscriptPage {
76
+ view: TranscriptView;
77
+ older: string | null;
78
+ }