@rubytech/create-maxy-code 0.1.306 → 0.1.308

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 (207) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/admin-access-password/__tests__/index.test.ts +114 -0
  3. package/payload/platform/lib/admin-access-password/dist/index.d.ts +40 -0
  4. package/payload/platform/lib/admin-access-password/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/admin-access-password/dist/index.js +172 -0
  6. package/payload/platform/lib/admin-access-password/dist/index.js.map +1 -0
  7. package/payload/platform/lib/admin-access-password/src/index.ts +152 -0
  8. package/payload/platform/lib/admin-access-password/tsconfig.json +8 -0
  9. package/payload/platform/lib/models/dist/index.d.ts +6 -1
  10. package/payload/platform/lib/models/dist/index.d.ts.map +1 -1
  11. package/payload/platform/lib/models/dist/index.js +11 -5
  12. package/payload/platform/lib/models/dist/index.js.map +1 -1
  13. package/payload/platform/lib/models/src/index.ts +10 -4
  14. package/payload/platform/package.json +2 -2
  15. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  16. package/payload/platform/plugins/admin/mcp/dist/index.js +60 -19
  17. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  18. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +8 -2
  19. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +35 -14
  20. package/payload/platform/plugins/docs/references/admin-ui.md +33 -12
  21. package/payload/platform/plugins/docs/references/internals.md +1 -1
  22. package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
  23. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  24. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  25. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  26. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +3 -0
  27. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  28. package/payload/platform/services/claude-session-manager/dist/http-server.js +196 -1
  29. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  30. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -0
  31. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  32. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +60 -1
  33. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  34. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +13 -0
  35. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +27 -3
  37. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts +8 -0
  39. package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +7 -0
  41. package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
  42. package/payload/platform/services/webchat-channel/dist/instructions.d.ts +8 -0
  43. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -0
  44. package/payload/platform/services/webchat-channel/dist/instructions.js +44 -0
  45. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -0
  46. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  47. package/payload/platform/services/webchat-channel/dist/server.js +11 -18
  48. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  49. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  50. package/payload/platform/services/whatsapp-channel/dist/server.js +34 -10
  51. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  52. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +9 -0
  53. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  54. package/payload/platform/services/whatsapp-channel/dist/targets.js +13 -0
  55. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  56. package/payload/platform/templates/account.json +1 -1
  57. package/payload/server/{chunk-TUY3RB5G.js → chunk-FBGIHWWM.js} +197 -82
  58. package/payload/server/maxy-edge.js +1 -1
  59. package/payload/server/public/assets/AdminLoginScreens-BZIA9Z6o.js +1 -0
  60. package/payload/server/public/assets/AdminShell-iazLHsk7.js +1 -0
  61. package/payload/server/public/assets/Checkbox-8elc7oXU.js +1 -0
  62. package/payload/server/public/assets/admin-BEx6o3fa.js +1 -0
  63. package/payload/server/public/assets/{arc-BrTRjBIM.js → arc-DxSm8tli.js} +1 -1
  64. package/payload/server/public/assets/architecture-YZFGNWBL-6g9jRVgc.js +1 -0
  65. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DJ9on7sZ.js → architectureDiagram-Q4EWVU46-7JSAh0vL.js} +1 -1
  66. package/payload/server/public/assets/{audio-attachment-mime-j2VvxEtx.js → audio-attachment-mime-DzbKNqbH.js} +4 -4
  67. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CIcyQn0q.js → blockDiagram-DXYQGD6D-DLoVmHKD.js} +1 -1
  68. package/payload/server/public/assets/{jsx-runtime-DZkld3Wk.css → brand-CmMZe4RK.css} +1 -1
  69. package/payload/server/public/assets/brand-Dh5UlVO2.js +9 -0
  70. package/payload/server/public/assets/browser-D16sPVkI.js +1 -0
  71. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dt5dh_GR.js → c4Diagram-AHTNJAMY-Nv1G0o9P.js} +1 -1
  72. package/payload/server/public/assets/channel-BgQLJanG.js +1 -0
  73. package/payload/server/public/assets/chat-x4PnXhkd.js +1 -0
  74. package/payload/server/public/assets/{chunk-2KRD3SAO-BUq3MqUy.js → chunk-2KRD3SAO-BpqPMhoA.js} +1 -1
  75. package/payload/server/public/assets/{chunk-336JU56O-CUzJH5Qt.js → chunk-336JU56O-DgXHlVU_.js} +2 -2
  76. package/payload/server/public/assets/chunk-426QAEUC-Dr_XVuUq.js +1 -0
  77. package/payload/server/public/assets/{chunk-4BX2VUAB-B4JUc3UA.js → chunk-4BX2VUAB-nwyZSZH8.js} +1 -1
  78. package/payload/server/public/assets/{chunk-4TB4RGXK-HRO9fIIs.js → chunk-4TB4RGXK-Tfxz9LHX.js} +1 -1
  79. package/payload/server/public/assets/{chunk-55IACEB6-MZdrLBuw.js → chunk-55IACEB6-6FMJQGXG.js} +1 -1
  80. package/payload/server/public/assets/{chunk-5FUZZQ4R-XcAN5zlM.js → chunk-5FUZZQ4R-g2FgZF3D.js} +1 -1
  81. package/payload/server/public/assets/{chunk-5PVQY5BW-PQ1GEsxo.js → chunk-5PVQY5BW-DtLwXsPH.js} +1 -1
  82. package/payload/server/public/assets/{chunk-67CJDMHE-BVvCaqGd.js → chunk-67CJDMHE-K91CP5ZU.js} +1 -1
  83. package/payload/server/public/assets/{chunk-7N4EOEYR-BlBbZdpM.js → chunk-7N4EOEYR-BGpCQhqK.js} +1 -1
  84. package/payload/server/public/assets/{chunk-AA7GKIK3-Ct-PgkGa.js → chunk-AA7GKIK3-jkDbInck.js} +1 -1
  85. package/payload/server/public/assets/{chunk-BSJP7CBP-DZRRfCSz.js → chunk-BSJP7CBP-mDosdzGs.js} +1 -1
  86. package/payload/server/public/assets/{chunk-CIAEETIT-BoGEJiXe.js → chunk-CIAEETIT-DhBxewpQ.js} +1 -1
  87. package/payload/server/public/assets/{chunk-EDXVE4YY-DiSmN31f.js → chunk-EDXVE4YY-B7c-9Emg.js} +1 -1
  88. package/payload/server/public/assets/{chunk-ENJZ2VHE-uhhokIvs.js → chunk-ENJZ2VHE-Cqhhncox.js} +1 -1
  89. package/payload/server/public/assets/{chunk-FMBD7UC4-CfCx5WI7.js → chunk-FMBD7UC4-BpDq6wj1.js} +1 -1
  90. package/payload/server/public/assets/{chunk-FOC6F5B3-Dj9E9gTu.js → chunk-FOC6F5B3-Ds02-ktu.js} +1 -1
  91. package/payload/server/public/assets/{chunk-ICPOFSXX-DcBs_FNy.js → chunk-ICPOFSXX-BTX5POFr.js} +2 -2
  92. package/payload/server/public/assets/{chunk-K5T4RW27-7-VefyJD.js → chunk-K5T4RW27-Du1PXXBU.js} +1 -1
  93. package/payload/server/public/assets/{chunk-KGLVRYIC-CqgTEVxm.js → chunk-KGLVRYIC-siasOAf8.js} +1 -1
  94. package/payload/server/public/assets/{chunk-LIHQZDEY-C0gzDXha.js → chunk-LIHQZDEY-xf1rbZtf.js} +1 -1
  95. package/payload/server/public/assets/{chunk-ORNJ4GCN-JXz23R1H.js → chunk-ORNJ4GCN-DeTLQ_LD.js} +1 -1
  96. package/payload/server/public/assets/{chunk-OYMX7WX6-C2snCNq9.js → chunk-OYMX7WX6-DQ7_oVJn.js} +1 -1
  97. package/payload/server/public/assets/chunk-QZHKN3VN-DF4o9HRJ.js +1 -0
  98. package/payload/server/public/assets/{chunk-U2HBQHQK-CXCU7M-M.js → chunk-U2HBQHQK-CXmFUKgn.js} +1 -1
  99. package/payload/server/public/assets/{chunk-X2U36JSP-CEdcSb28.js → chunk-X2U36JSP-Bj8-8Wkz.js} +1 -1
  100. package/payload/server/public/assets/{chunk-XPW4576I-Cckc75YS.js → chunk-XPW4576I-Con3TXqt.js} +1 -1
  101. package/payload/server/public/assets/{chunk-YZCP3GAM-4nN8AO7H.js → chunk-YZCP3GAM-Dx8BHGWf.js} +1 -1
  102. package/payload/server/public/assets/{chunk-ZZ45TVLE-DcoBc8-7.js → chunk-ZZ45TVLE-Dp4Q5Y-f.js} +1 -1
  103. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DAbPKU6u.js +1 -0
  104. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BKuFQLcv.js +1 -0
  105. package/payload/server/public/assets/clone-DnNHGhNe.js +1 -0
  106. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-BFFro_t9.js → cose-bilkent-S5V4N54A-pZiCv69E.js} +1 -1
  107. package/payload/server/public/assets/{dagre-CN5to_JM.js → dagre-CLqhEgbL.js} +1 -1
  108. package/payload/server/public/assets/{dagre-KV5264BT-WYrPwDg0.js → dagre-KV5264BT-BCxE8iyk.js} +1 -1
  109. package/payload/server/public/assets/data-DqG45sq0.js +1 -0
  110. package/payload/server/public/assets/{diagram-5BDNPKRD-Dz2CiboN.js → diagram-5BDNPKRD-slUEdZzz.js} +1 -1
  111. package/payload/server/public/assets/{diagram-G4DWMVQ6-r8h3Ip9O.js → diagram-G4DWMVQ6-DPcraMfu.js} +1 -1
  112. package/payload/server/public/assets/{diagram-MMDJMWI5-CECcpGtr.js → diagram-MMDJMWI5-DR6luhGK.js} +1 -1
  113. package/payload/server/public/assets/{diagram-TYMM5635-CHOcAyIP.js → diagram-TYMM5635-Jap1B4wA.js} +1 -1
  114. package/payload/server/public/assets/{erDiagram-SMLLAGMA-YIqKdPbS.js → erDiagram-SMLLAGMA-DJKUs2hO.js} +1 -1
  115. package/payload/server/public/assets/file-download-BvGS7Qmi.js +1 -0
  116. package/payload/server/public/assets/{flatten-DhYYI_lo.js → flatten-Cl1Bc3dR.js} +1 -1
  117. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-DaEvQWpj.js → flowDiagram-DWJPFMVM-C8W-ZZ9W.js} +1 -1
  118. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-x1hClEOB.js → ganttDiagram-T4ZO3ILL-DYCX4Xu2.js} +1 -1
  119. package/payload/server/public/assets/gitGraph-7Q5UKJZL-g7mvu6IY.js +1 -0
  120. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-C-sZsh6Z.js → gitGraphDiagram-UUTBAWPF-CZu_fpgS.js} +1 -1
  121. package/payload/server/public/assets/{graph-Uaa5heM5.js → graph-DKSjcpmR.js} +3 -3
  122. package/payload/server/public/assets/graph-labels-CVWPQgBV.js +1 -0
  123. package/payload/server/public/assets/{graphlib-CNkAqm2w.js → graphlib-Bdp7TA4y.js} +1 -1
  124. package/payload/server/public/assets/info-OMHHGYJF-DZqJuIkB.js +1 -0
  125. package/payload/server/public/assets/infoDiagram-42DDH7IO-cGfGccHz.js +2 -0
  126. package/payload/server/public/assets/{isEmpty-DGhq_DBP.js → isEmpty-D1pGOD1J.js} +1 -1
  127. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-wHE-RwzZ.js → ishikawaDiagram-UXIWVN3A-B7KfIX6z.js} +1 -1
  128. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DQB6iThz.js → journeyDiagram-VCZTEJTY-CSDwBfhF.js} +1 -1
  129. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Ck08vXSA.js → kanban-definition-6JOO6SKY-CWzcEKTr.js} +1 -1
  130. package/payload/server/public/assets/{line-Cdc2FBEU.js → line-BNQSlbYn.js} +1 -1
  131. package/payload/server/public/assets/{linear-DhtUm5H7.js → linear-BN6kGN93.js} +1 -1
  132. package/payload/server/public/assets/{mermaid-parser.core-CSYulPIL.js → mermaid-parser.core-CcFhkElq.js} +2 -2
  133. package/payload/server/public/assets/{mermaid.core-CP-hOmMh.js → mermaid.core-BsIeJ7Cc.js} +3 -3
  134. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-GxlkvV1Z.js → mindmap-definition-QFDTVHPH-CQVzj6D_.js} +1 -1
  135. package/payload/server/public/assets/operator-Cnzbhdh2.js +1 -0
  136. package/payload/server/public/assets/{ordinal-CibZU3PM.js → ordinal-CBZeH4Yp.js} +1 -1
  137. package/payload/server/public/assets/packet-4T2RLAQJ-Hn8rlHpy.js +1 -0
  138. package/payload/server/public/assets/page-CH8JQkQN.js +1 -0
  139. package/payload/server/public/assets/pie-ZZUOXDRM-BPGWVqBm.js +1 -0
  140. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Di6kWh9c.js → pieDiagram-DEJITSTG-DnrQ9fyn.js} +1 -1
  141. package/payload/server/public/assets/{public-DBXfM02B.js → public-CA90VhI9.js} +3 -3
  142. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-iNHC5bkO.js → quadrantDiagram-34T5L4WZ-BQWlCyWi.js} +1 -1
  143. package/payload/server/public/assets/radar-PYXPWWZC-DMdLrj3E.js +1 -0
  144. package/payload/server/public/assets/{reduce-Da0RBva0.js → reduce-PuPlFPRX.js} +1 -1
  145. package/payload/server/public/assets/{requirementDiagram-MS252O5E-BBlLREXo.js → requirementDiagram-MS252O5E-Du_vZhU1.js} +1 -1
  146. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-qyDkP-Ca.js → sankeyDiagram-XADWPNL6-Cf6Z8GPQ.js} +1 -1
  147. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-C0MJuS0m.js → sequenceDiagram-FGHM5R23-CS8GVol8.js} +1 -1
  148. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-aHTrlosa.js → stateDiagram-FHFEXIEX-Bv1NW2F1.js} +1 -1
  149. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BGUCxPmp.js +1 -0
  150. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BlRZIxEp.js → timeline-definition-GMOUNBTQ-ClGYAsr0.js} +1 -1
  151. package/payload/server/public/assets/treeView-SZITEDCU-CWxofUbI.js +1 -0
  152. package/payload/server/public/assets/treemap-W4RFUUIX-Cfgb_ZG0.js +1 -0
  153. package/payload/server/public/assets/useSelectionMode-FIodJKzS.js +5 -0
  154. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-s76adysx.js → vennDiagram-DHZGUBPP-C2wTcxQT.js} +1 -1
  155. package/payload/server/public/assets/wardley-RL74JXVD-B45olYjj.js +1 -0
  156. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-xC8t2QGW.js → wardleyDiagram-NUSXRM2D-D86-ivEx.js} +1 -1
  157. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DRcHg-Wj.js → xychartDiagram-5P7HB3ND-Ba5WAN8P.js} +1 -1
  158. package/payload/server/public/browser.html +6 -6
  159. package/payload/server/public/chat.html +9 -10
  160. package/payload/server/public/data.html +5 -5
  161. package/payload/server/public/graph.html +8 -8
  162. package/payload/server/public/index.html +9 -10
  163. package/payload/server/public/operator.html +11 -12
  164. package/payload/server/public/public.html +7 -7
  165. package/payload/server/server.js +4235 -3522
  166. package/payload/server/public/assets/AdminLoginScreens-Cw882rmm.js +0 -1
  167. package/payload/server/public/assets/AdminShell-DpWl1uoY.js +0 -1
  168. package/payload/server/public/assets/Checkbox-DAWZItdK.js +0 -1
  169. package/payload/server/public/assets/Transcript-DbK2YA3K.js +0 -1
  170. package/payload/server/public/assets/admin-nQRdn79w.js +0 -1
  171. package/payload/server/public/assets/architecture-YZFGNWBL-DqQB3T6j.js +0 -1
  172. package/payload/server/public/assets/browser-CHWZDCXt.js +0 -1
  173. package/payload/server/public/assets/channel-NZeJdLVK.js +0 -1
  174. package/payload/server/public/assets/chat-BBK6ulFy.js +0 -1
  175. package/payload/server/public/assets/chunk-426QAEUC-DFKX5l0L.js +0 -1
  176. package/payload/server/public/assets/chunk-QZHKN3VN-BWCCN0AM.js +0 -1
  177. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DPlCQCX2.js +0 -1
  178. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CqkQq74h.js +0 -1
  179. package/payload/server/public/assets/clone-CSykdD1A.js +0 -1
  180. package/payload/server/public/assets/data-33mdnHv8.js +0 -1
  181. package/payload/server/public/assets/file-download-P_qlMCg5.js +0 -1
  182. package/payload/server/public/assets/gitGraph-7Q5UKJZL-V7Tx7_eY.js +0 -1
  183. package/payload/server/public/assets/graph-labels-rG63rHvl.js +0 -1
  184. package/payload/server/public/assets/info-OMHHGYJF-D3DqVPGn.js +0 -1
  185. package/payload/server/public/assets/infoDiagram-42DDH7IO-B_O-R0vN.js +0 -2
  186. package/payload/server/public/assets/jsx-runtime-sFVKdwlP.js +0 -9
  187. package/payload/server/public/assets/operator-CiDwt5I9.js +0 -1
  188. package/payload/server/public/assets/packet-4T2RLAQJ-BY7mgCTQ.js +0 -1
  189. package/payload/server/public/assets/page-BvkaS_tu.js +0 -1
  190. package/payload/server/public/assets/pie-ZZUOXDRM-B9f6jN54.js +0 -1
  191. package/payload/server/public/assets/radar-PYXPWWZC-BKS8JOUe.js +0 -1
  192. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-V2vo4BjY.js +0 -1
  193. package/payload/server/public/assets/treeView-SZITEDCU-bIKzyIdw.js +0 -1
  194. package/payload/server/public/assets/treemap-W4RFUUIX-CQycQ4H9.js +0 -1
  195. package/payload/server/public/assets/useSelectionMode-Dky-Stnf.js +0 -5
  196. package/payload/server/public/assets/wardley-RL74JXVD-COr9o-nG.js +0 -1
  197. /package/payload/server/public/assets/{_baseFor-BFYpZGkN.js → _baseFor-brRjpUOX.js} +0 -0
  198. /package/payload/server/public/assets/{array-CYkMkqnU.js → array-BGFCBI0e.js} +0 -0
  199. /package/payload/server/public/assets/{cytoscape.esm-DqlHsaog.js → cytoscape.esm-TvRJ2tGX.js} +0 -0
  200. /package/payload/server/public/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CRZydyG6.js} +0 -0
  201. /package/payload/server/public/assets/{dist-t9p-NJH2.js → dist-Dbh7HrZX.js} +0 -0
  202. /package/payload/server/public/assets/{init-Rr1s_RiX.js → init-B8gtcn7T.js} +0 -0
  203. /package/payload/server/public/assets/{katex-pVJiI_rc.js → katex-RxgSu_dy.js} +0 -0
  204. /package/payload/server/public/assets/{path-BAQ3hXlG.js → path-DZF-JdEe.js} +0 -0
  205. /package/payload/server/public/assets/{preload-helper-B_l5jfgx.js → preload-helper-CQ36nxok.js} +0 -0
  206. /package/payload/server/public/assets/{rough.esm-nHaDi0Kw.js → rough.esm-6CnTHTkH.js} +0 -0
  207. /package/payload/server/public/assets/{src-DuPQVw-H.js → src-BoaldmnP.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:9d8f9a898ee1cb0726e604e78f5fd1bf2c9660b76604d523ba1c4e3598092a98
4
+ content-hash: sha256:3dc18de6b756569b69f9991344abc2af34919802f4e632a815f2250b8a11f786
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2288,7 +2288,7 @@ either is a regression.
2288
2288
 
2289
2289
  **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble with only the message text (no label, no per-turn time); all agent output renders as plain prose (the reply-document filename line stays, as prose); a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp reader (`/whatsapp`) omits the prop and keeps the default `renderTimeline`, so its rendering is unchanged; both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx` unmodified, `app/chat/__tests__/transcript-render.test.tsx` new).
2290
2290
 
2291
- **maxy title for public sessions.** A `role=public` webchat spawn never produces a useful Claude Code `ai-title` (an anonymous one-line visitor turn), so every public row would otherwise read identically. The webchat route (`chat.ts`) composes a deterministic title — `Web · <senderId[:8]>[ · <personId>] · <UTC YYYY-MM-DD HH:mm>` (personId present only for gated visitors) — and threads it through the bridge (`dispatchOnce` → `managerSpawn`) to the manager `POST /spawn` body as `name`. `/spawn` validates it with `validateUserTitle` and, for `role=public` only, writes it into `UserTitleStore` so it occupies the operator-rename tier and wins over `ai-title`. Admin (`/rc-spawn`) and WhatsApp titling are unchanged. Observability: every public spawn logs `[spawn] role=public … title="…"` (or `title=missing`); the manager's row builder emits `[public-title] sessionId=… unexpected titleSource=<ai|null>` on the next list read for any public row that did not resolve from the user tier.
2291
+ **maxy title for public sessions.** A `role=public` webchat spawn never produces a useful Claude Code `ai-title` (an anonymous one-line visitor turn), so every public row would otherwise read identically. The webchat route (`chat.ts`) composes a deterministic title — `Web · <senderId[:8]>[ · <personId>] · <UTC YYYY-MM-DD HH:mm>` (personId present only for gated visitors) — and threads it through the native webchat gateway's public spawn (`handleInbound` → `buildPublicWebchatSpawnRequest` → `managerSpawn`) to the manager `POST /public-spawn` body as `name`. `/spawn` validates it with `validateUserTitle` and, for `role=public` only, writes it into `UserTitleStore` so it occupies the operator-rename tier and wins over `ai-title`. Admin (`/rc-spawn`) and WhatsApp titling are unchanged. Observability: every public spawn logs `[spawn] role=public … title="…"` (or `title=missing`); the manager's row builder emits `[public-title] sessionId=… unexpected titleSource=<ai|null>` on the next list read for any public row that did not resolve from the user tier.
2292
2292
 
2293
2293
  ### Graph
2294
2294
 
@@ -2330,12 +2330,30 @@ a web/transient doc with no persisted file is `not-downloadable reason=no-persis
2330
2330
  **`/data` File panel — refresh and reload-survival.** The panel listing is a
2331
2331
  snapshot from its last fetch, so a file an agent writes (or an upload/delete
2332
2332
  elsewhere) leaves stale rows and timestamps until something re-fetches. A
2333
- **Refresh** button beside Upload re-fetches the current folder in place (fresh
2334
- `mtime`s, no browser reload). The current directory is mirrored into the URL
2335
- hash (`/data#path=<rel>`), so a browser reload restores that folder instead of
2336
- snapping back to the data root; the hash never reaches the server and `/data`
2337
- has no client router, so the channel is isolated. `path=` is cleared at the
2338
- root for a clean URL.
2333
+ **Refresh** icon inside the search bar (beside Upload) re-fetches the current
2334
+ folder in place (fresh `mtime`s, no browser reload). The current directory is
2335
+ mirrored into the URL hash (`/data#path=<rel>`), so a browser reload restores
2336
+ that folder instead of snapping back to the data root; the hash never reaches
2337
+ the server and `/data` has no client router, so the channel is isolated.
2338
+ `path=` is cleared at the root for a clean URL.
2339
+
2340
+ **`/data` operator surface.** `/data` mounts the operator
2341
+ `HeaderMenu` (logo-left / name-centre / burger-right; flyout = Chat / Data /
2342
+ Log out) above the toolbar — the burger's Chat item returns to `/`, replacing
2343
+ the former standalone Home button. The search bar spans the full toolbar width
2344
+ with Refresh and Upload as inline icons at every width (the prior `≤600px`
2345
+ ResizeObserver collapse is removed). File rows are two stacked full-width lines
2346
+ (name, then `size · modified`) with no file-type icon, no hover download icon,
2347
+ and no per-row delete button; a folder row keeps its glyph and navigates on tap,
2348
+ a file row downloads on tap. **Long-press** a file row to enter multi-select:
2349
+ every file row shows a selection indicator, the search bar becomes a Cancel
2350
+ control, and a fixed bottom bar offers **Share / Download / Delete** over the
2351
+ selection. Delete removes the non-protected files and keeps protected ones
2352
+ (`N protected file(s) kept`); the listing carries a per-entry `protected` flag
2353
+ from `isProtectedFromDeletion` so the UI never re-derives the rule. Share uses
2354
+ the Web Share API and is disabled where unavailable. Signals:
2355
+ `[data-ui] op=mount header=operator home=absent`, `op=select-enter`,
2356
+ `op=select-exit reason=<cancel|action>`, `op=share supported=<bool> count=<n>`.
2339
2357
 
2340
2358
  **`/data` search — locate-in-files link and clear button.** Every
2341
2359
  search hit is a `:FileArtifact` backed by a physical file, so each result card
@@ -2462,11 +2480,14 @@ authoritative. This section names the surfaces and what backs each.
2462
2480
 
2463
2481
  > **Admin variant only.** Every surface below belongs to the `admin` shell
2464
2482
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
2465
- > all: its dashboard collapsed to two surfaces the persistent admin webchat
2466
- > at `/` and the data browser at `/data` with a three-item header (Chat,
2467
- > Data, Log out) and a single-column `.platform.platform-operator` grid. The
2468
- > operator never sees the sessions list, the WhatsApp reader, or the nav rows.
2469
- > The operator surface is documented in full in the internal doc
2483
+ > all: its dashboard is the persistent admin webchat at `/` and the data
2484
+ > browser at `/data`, plus a read-only **Conversations** reader reached from a
2485
+ > dynamic burger item header order Chat Conversations → Data → Log out, in
2486
+ > a single-column `.platform.platform-operator` grid. The operator never sees
2487
+ > the sessions list or the nav rows; the Conversations reader lists the agent's
2488
+ > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
2489
+ > via the same admin-gated reader routes, never the operator's own session. The
2490
+ > operator surface is documented in full in the internal doc
2470
2491
  > `maxy-code/.docs/operator-dashboard.md`.
2471
2492
 
2472
2493
  | Surface | What it does | Backed by |
@@ -3252,7 +3273,7 @@ The `brand-excluded` branch closes the recurring crash-restart loop on brands th
3252
3273
 
3253
3274
  **Per-spawn signals (server.log).** Every spawn emits `pty-spawn-mcp-config servers=<N> tools=<M> bytes=<B> path=<…>` once, plus one `pty-spawn-agents-dir role=<admin|public> path=<…>` per added directory. Specialist spawns additionally emit `pty-spawn-allowlist specialist=<name> count=<N> stripped=<S> sourced-from=agent-frontmatter` where `stripped` is the count of brand-excluded tool names removed before argv emission. The diagnostic one-liner is `grep -E 'pty-spawn-mcp-config|pty-spawn-agents-dir|pty-spawn-allowlist|mcp-config-allowlist-coverage|specialist-tool-strip|boot-failed reason=' ~/.<brand>/logs/server.log | tail -50`.
3254
3275
 
3255
- **Channel follower cold-start retry.** Each channel PTY session (webchat, whatsapp, email) has one JSONL follower ([`platform/ui/app/lib/channel-pty-bridge/follower.ts`](../../../ui/app/lib/channel-pty-bridge/follower.ts)) reading `GET /<sessionId>/log?follow=1` and fanning each assistant `end_turn` out to the awaiting `dispatchOnce`. A freshly-spawned PTY has no JSONL on disk until claude flushes its first line; during that window the manager answers `202 {pending:true}`. The follower retries every `CHANNEL_PTY_FOLLOWER_RETRY_MS` (default 1000) until a 200 stream opens or `CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS` elapses. The follower is shared across channels, so that window defaults to the longest channel turn window (whatsapp's `WHATSAPP_PTY_TURN_TIMEOUT_MS`, 300000 — longer than webchat's 120000) so it never abandons a turn the caller is still awaiting. Because public sessions idle-reap, every webchat greeting is the first turn of a fresh spawn and crosses this window — before the retry, a 202 (which satisfies `res.ok`) was consumed as a single non-event line, the stream ended, and the follower died silently, timing out every public turn. The lifecycle is greppable as `follower-connect status=<code>` → `follower-retry attempt=N reason=pending` → `follower-open` → `outbound bytes=N`; `follower-give-up reason=pending-timeout` marks the JSONL never appearing. A `reject reason=turn-timeout` with no preceding `follower-open` (and no manager `log-follow-open`) for that sessionId is the signature. See `.docs/gated-public-agents.md` "Webchat turn lifecycle" for the full tag list.
3276
+ **Channel follower cold-start retry.** Each `dispatchOnce`-based channel PTY session has one JSONL follower ([`platform/ui/app/lib/channel-pty-bridge/follower.ts`](../../../ui/app/lib/channel-pty-bridge/follower.ts)) reading `GET /<sessionId>/log?follow=1` and fanning each assistant `end_turn` out to the awaiting `dispatchOnce`. (WhatsApp and public webchat have since migrated to the native event channel — an MCP notification in, a `reply`-tool call out — and no longer use this follower; the remaining `dispatchOnce` channels are email and telegram. The webchat detail below is the historical record of the bug the retry fixed.) A freshly-spawned PTY has no JSONL on disk until claude flushes its first line; during that window the manager answers `202 {pending:true}`. The follower retries every `CHANNEL_PTY_FOLLOWER_RETRY_MS` (default 1000) until a 200 stream opens or `CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS` elapses. The follower is shared across channels, so that window defaults to the longest channel turn window (whatsapp's `WHATSAPP_PTY_TURN_TIMEOUT_MS`, 300000 — longer than webchat's 120000) so it never abandons a turn the caller is still awaiting. Because public sessions idle-reap, every webchat greeting is the first turn of a fresh spawn and crosses this window — before the retry, a 202 (which satisfies `res.ok`) was consumed as a single non-event line, the stream ended, and the follower died silently, timing out every public turn. The lifecycle is greppable as `follower-connect status=<code>` → `follower-retry attempt=N reason=pending` → `follower-open` → `outbound bytes=N`; `follower-give-up reason=pending-timeout` marks the JSONL never appearing. A `reject reason=turn-timeout` with no preceding `follower-open` (and no manager `log-follow-open`) for that sessionId is the signature. See `.docs/gated-public-agents.md` "Webchat turn lifecycle" for the full tag list.
3256
3277
 
3257
3278
  **Brand-process start counter.** `platform/ui/server-init.cjs` increments a persistent counter at `/tmp/server-init-<accountId>-restart.count` on every fresh start and emits `[server-init] start count=<N> account=<accountId> counter-path=<…>` to `server.log`. /tmp clears on reboot, so a clean reboot starts the count fresh; any value `>1` between operator-observed reboots means the brand process (driven by its `Requires=<brand>-claude-session-manager.service` clause) is restarting. The diagnostic one-liner is `grep '\[server-init\] start' ~/.<brand>/logs/server.log | tail -5` — the trailing `count=` value is the loop depth without counting SIGTERMs.
3258
3279
 
@@ -57,7 +57,7 @@ either is a regression.
57
57
 
58
58
  **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble with only the message text (no label, no per-turn time); all agent output renders as plain prose (the reply-document filename line stays, as prose); a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp reader (`/whatsapp`) omits the prop and keeps the default `renderTimeline`, so its rendering is unchanged; both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx` unmodified, `app/chat/__tests__/transcript-render.test.tsx` new).
59
59
 
60
- **maxy title for public sessions.** A `role=public` webchat spawn never produces a useful Claude Code `ai-title` (an anonymous one-line visitor turn), so every public row would otherwise read identically. The webchat route (`chat.ts`) composes a deterministic title — `Web · <senderId[:8]>[ · <personId>] · <UTC YYYY-MM-DD HH:mm>` (personId present only for gated visitors) — and threads it through the bridge (`dispatchOnce` → `managerSpawn`) to the manager `POST /spawn` body as `name`. `/spawn` validates it with `validateUserTitle` and, for `role=public` only, writes it into `UserTitleStore` so it occupies the operator-rename tier and wins over `ai-title`. Admin (`/rc-spawn`) and WhatsApp titling are unchanged. Observability: every public spawn logs `[spawn] role=public … title="…"` (or `title=missing`); the manager's row builder emits `[public-title] sessionId=… unexpected titleSource=<ai|null>` on the next list read for any public row that did not resolve from the user tier.
60
+ **maxy title for public sessions.** A `role=public` webchat spawn never produces a useful Claude Code `ai-title` (an anonymous one-line visitor turn), so every public row would otherwise read identically. The webchat route (`chat.ts`) composes a deterministic title — `Web · <senderId[:8]>[ · <personId>] · <UTC YYYY-MM-DD HH:mm>` (personId present only for gated visitors) — and threads it through the native webchat gateway's public spawn (`handleInbound` → `buildPublicWebchatSpawnRequest` → `managerSpawn`) to the manager `POST /public-spawn` body as `name`. `/spawn` validates it with `validateUserTitle` and, for `role=public` only, writes it into `UserTitleStore` so it occupies the operator-rename tier and wins over `ai-title`. Admin (`/rc-spawn`) and WhatsApp titling are unchanged. Observability: every public spawn logs `[spawn] role=public … title="…"` (or `title=missing`); the manager's row builder emits `[public-title] sessionId=… unexpected titleSource=<ai|null>` on the next list read for any public row that did not resolve from the user tier.
61
61
 
62
62
  ### Graph
63
63
 
@@ -99,12 +99,30 @@ a web/transient doc with no persisted file is `not-downloadable reason=no-persis
99
99
  **`/data` File panel — refresh and reload-survival.** The panel listing is a
100
100
  snapshot from its last fetch, so a file an agent writes (or an upload/delete
101
101
  elsewhere) leaves stale rows and timestamps until something re-fetches. A
102
- **Refresh** button beside Upload re-fetches the current folder in place (fresh
103
- `mtime`s, no browser reload). The current directory is mirrored into the URL
104
- hash (`/data#path=<rel>`), so a browser reload restores that folder instead of
105
- snapping back to the data root; the hash never reaches the server and `/data`
106
- has no client router, so the channel is isolated. `path=` is cleared at the
107
- root for a clean URL.
102
+ **Refresh** icon inside the search bar (beside Upload) re-fetches the current
103
+ folder in place (fresh `mtime`s, no browser reload). The current directory is
104
+ mirrored into the URL hash (`/data#path=<rel>`), so a browser reload restores
105
+ that folder instead of snapping back to the data root; the hash never reaches
106
+ the server and `/data` has no client router, so the channel is isolated.
107
+ `path=` is cleared at the root for a clean URL.
108
+
109
+ **`/data` operator surface.** `/data` mounts the operator
110
+ `HeaderMenu` (logo-left / name-centre / burger-right; flyout = Chat / Data /
111
+ Log out) above the toolbar — the burger's Chat item returns to `/`, replacing
112
+ the former standalone Home button. The search bar spans the full toolbar width
113
+ with Refresh and Upload as inline icons at every width (the prior `≤600px`
114
+ ResizeObserver collapse is removed). File rows are two stacked full-width lines
115
+ (name, then `size · modified`) with no file-type icon, no hover download icon,
116
+ and no per-row delete button; a folder row keeps its glyph and navigates on tap,
117
+ a file row downloads on tap. **Long-press** a file row to enter multi-select:
118
+ every file row shows a selection indicator, the search bar becomes a Cancel
119
+ control, and a fixed bottom bar offers **Share / Download / Delete** over the
120
+ selection. Delete removes the non-protected files and keeps protected ones
121
+ (`N protected file(s) kept`); the listing carries a per-entry `protected` flag
122
+ from `isProtectedFromDeletion` so the UI never re-derives the rule. Share uses
123
+ the Web Share API and is disabled where unavailable. Signals:
124
+ `[data-ui] op=mount header=operator home=absent`, `op=select-enter`,
125
+ `op=select-exit reason=<cancel|action>`, `op=share supported=<bool> count=<n>`.
108
126
 
109
127
  **`/data` search — locate-in-files link and clear button.** Every
110
128
  search hit is a `:FileArtifact` backed by a physical file, so each result card
@@ -231,11 +249,14 @@ authoritative. This section names the surfaces and what backs each.
231
249
 
232
250
  > **Admin variant only.** Every surface below belongs to the `admin` shell
233
251
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
234
- > all: its dashboard collapsed to two surfaces the persistent admin webchat
235
- > at `/` and the data browser at `/data` with a three-item header (Chat,
236
- > Data, Log out) and a single-column `.platform.platform-operator` grid. The
237
- > operator never sees the sessions list, the WhatsApp reader, or the nav rows.
238
- > The operator surface is documented in full in the internal doc
252
+ > all: its dashboard is the persistent admin webchat at `/` and the data
253
+ > browser at `/data`, plus a read-only **Conversations** reader reached from a
254
+ > dynamic burger item header order Chat Conversations → Data → Log out, in
255
+ > a single-column `.platform.platform-operator` grid. The operator never sees
256
+ > the sessions list or the nav rows; the Conversations reader lists the agent's
257
+ > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
258
+ > via the same admin-gated reader routes, never the operator's own session. The
259
+ > operator surface is documented in full in the internal doc
239
260
  > `maxy-code/.docs/operator-dashboard.md`.
240
261
 
241
262
  | Surface | What it does | Backed by |
@@ -477,7 +477,7 @@ The `brand-excluded` branch closes the recurring crash-restart loop on brands th
477
477
 
478
478
  **Per-spawn signals (server.log).** Every spawn emits `pty-spawn-mcp-config servers=<N> tools=<M> bytes=<B> path=<…>` once, plus one `pty-spawn-agents-dir role=<admin|public> path=<…>` per added directory. Specialist spawns additionally emit `pty-spawn-allowlist specialist=<name> count=<N> stripped=<S> sourced-from=agent-frontmatter` where `stripped` is the count of brand-excluded tool names removed before argv emission. The diagnostic one-liner is `grep -E 'pty-spawn-mcp-config|pty-spawn-agents-dir|pty-spawn-allowlist|mcp-config-allowlist-coverage|specialist-tool-strip|boot-failed reason=' ~/.<brand>/logs/server.log | tail -50`.
479
479
 
480
- **Channel follower cold-start retry.** Each channel PTY session (webchat, whatsapp, email) has one JSONL follower ([`platform/ui/app/lib/channel-pty-bridge/follower.ts`](../../../ui/app/lib/channel-pty-bridge/follower.ts)) reading `GET /<sessionId>/log?follow=1` and fanning each assistant `end_turn` out to the awaiting `dispatchOnce`. A freshly-spawned PTY has no JSONL on disk until claude flushes its first line; during that window the manager answers `202 {pending:true}`. The follower retries every `CHANNEL_PTY_FOLLOWER_RETRY_MS` (default 1000) until a 200 stream opens or `CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS` elapses. The follower is shared across channels, so that window defaults to the longest channel turn window (whatsapp's `WHATSAPP_PTY_TURN_TIMEOUT_MS`, 300000 — longer than webchat's 120000) so it never abandons a turn the caller is still awaiting. Because public sessions idle-reap, every webchat greeting is the first turn of a fresh spawn and crosses this window — before the retry, a 202 (which satisfies `res.ok`) was consumed as a single non-event line, the stream ended, and the follower died silently, timing out every public turn. The lifecycle is greppable as `follower-connect status=<code>` → `follower-retry attempt=N reason=pending` → `follower-open` → `outbound bytes=N`; `follower-give-up reason=pending-timeout` marks the JSONL never appearing. A `reject reason=turn-timeout` with no preceding `follower-open` (and no manager `log-follow-open`) for that sessionId is the signature. See `.docs/gated-public-agents.md` "Webchat turn lifecycle" for the full tag list.
480
+ **Channel follower cold-start retry.** Each `dispatchOnce`-based channel PTY session has one JSONL follower ([`platform/ui/app/lib/channel-pty-bridge/follower.ts`](../../../ui/app/lib/channel-pty-bridge/follower.ts)) reading `GET /<sessionId>/log?follow=1` and fanning each assistant `end_turn` out to the awaiting `dispatchOnce`. (WhatsApp and public webchat have since migrated to the native event channel — an MCP notification in, a `reply`-tool call out — and no longer use this follower; the remaining `dispatchOnce` channels are email and telegram. The webchat detail below is the historical record of the bug the retry fixed.) A freshly-spawned PTY has no JSONL on disk until claude flushes its first line; during that window the manager answers `202 {pending:true}`. The follower retries every `CHANNEL_PTY_FOLLOWER_RETRY_MS` (default 1000) until a 200 stream opens or `CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS` elapses. The follower is shared across channels, so that window defaults to the longest channel turn window (whatsapp's `WHATSAPP_PTY_TURN_TIMEOUT_MS`, 300000 — longer than webchat's 120000) so it never abandons a turn the caller is still awaiting. Because public sessions idle-reap, every webchat greeting is the first turn of a fresh spawn and crosses this window — before the retry, a 202 (which satisfies `res.ok`) was consumed as a single non-event line, the stream ended, and the follower died silently, timing out every public turn. The lifecycle is greppable as `follower-connect status=<code>` → `follower-retry attempt=N reason=pending` → `follower-open` → `outbound bytes=N`; `follower-give-up reason=pending-timeout` marks the JSONL never appearing. A `reject reason=turn-timeout` with no preceding `follower-open` (and no manager `log-follow-open`) for that sessionId is the signature. See `.docs/gated-public-agents.md` "Webchat turn lifecycle" for the full tag list.
481
481
 
482
482
  **Brand-process start counter.** `platform/ui/server-init.cjs` increments a persistent counter at `/tmp/server-init-<accountId>-restart.count` on every fresh start and emits `[server-init] start count=<N> account=<accountId> counter-path=<…>` to `server.log`. /tmp clears on reboot, so a clean reboot starts the count fresh; any value `>1` between operator-observed reboots means the brand process (driven by its `Requires=<brand>-claude-session-manager.service` clause) is restarting. The diagnostic one-liner is `grep '\[server-init\] start' ~/.<brand>/logs/server.log | tail -5` — the trailing `count=` value is the loop depth without counting SIGTERMs.
483
483
 
@@ -55,6 +55,10 @@ const ALLOWLIST = new Set([
55
55
  // before the module's `../paths` mock fires, to redirect MAXY_DIR off the
56
56
  // operator's real home config.
57
57
  'platform/ui/app/lib/__tests__/operator-domains.test.ts',
58
+ // Same vi.hoisted pattern: the per-admin remote-auth test builds a tmpdir
59
+ // before the `../paths` mock fires, to point USERS_FILE/REMOTE_PASSWORD_FILE
60
+ // at a temp dir off the operator's real home config.
61
+ 'platform/ui/app/lib/__tests__/remote-auth-per-admin.test.ts',
58
62
  ])
59
63
 
60
64
  const REQUIRE_CALL_RE = /\brequire\s*\(/
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-tool-names.generated.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiB7C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAiLtD,CAAA"}
1
+ {"version":3,"file":"canonical-tool-names.generated.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiB7C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAkLtD,CAAA"}
@@ -45,6 +45,7 @@ export const CANONICAL_MAXY_TOOL_NAMES = [
45
45
  "mcp__plugin_admin_admin__public-hostname",
46
46
  "mcp__plugin_admin_admin__publish-site",
47
47
  "mcp__plugin_admin_admin__qr-generate",
48
+ "mcp__plugin_admin_admin__remote-auth-revoke-password",
48
49
  "mcp__plugin_admin_admin__remote-auth-set-password",
49
50
  "mcp__plugin_admin_admin__remote-auth-status",
50
51
  "mcp__plugin_admin_admin__session-reset",
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-tool-names.generated.js","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"canonical-tool-names.generated.js","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"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"}
@@ -35,6 +35,9 @@ export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'>
35
35
  * short value to exercise the bind-timeout response without a 60s wait. */
36
36
  rcSpawnUrlWaitMs?: number;
37
37
  }
38
+ export declare function forkArgvSegment(fromSessionId: string, newSessionId: string, model: string): string[];
39
+ export declare function formatReseatLine(fromModel: string | null, toModel: string, oldSessionId: string, newSessionId: string, observed: string | null): string;
40
+ export declare function firstPostArmAssistantModel(lines: string[], armTimeMs: number): string | null;
38
41
  /** Task 189 — Hono instance with the test-only fan-out trigger attached.
39
42
  * The vitest fixture for the `/events` url-capture re-emit drives this
40
43
  * directly (no real PTY spawn in the suite); production callers route
@@ -1 +1 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAY3B,OAAO,EAgBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAqBzB,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAA;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAgFhE,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAoND;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AAmBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CA6/DpD"}
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAY3B,OAAO,EAgBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAqBzB,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAA;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAgFhE,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAwGD,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpG;AAMD,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAER;AAkCD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe5F;AAwJD;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AAmBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CA+lEpD"}
@@ -182,6 +182,111 @@ function resolveRow(watcher, id) {
182
182
  // auto-archive` lines cover the lifecycle.
183
183
  const END_TURN_SIGNATURE = '"stop_reason":"end_turn"';
184
184
  const AUTO_ARCHIVE_SPECIALIST = 'database-operator';
185
+ // Task 876 — re-seat (operator-initiated model change for an existing admin
186
+ // webchat session). Fork the source onto a fresh session born on the chosen
187
+ // model: `--resume <old>` seeds history, `--fork-session` mints into the
188
+ // pre-supplied `--session-id <new>`, `--model` pins the fork's first turn.
189
+ // Pure so the argv shape is unit-tested without a spawn.
190
+ export function forkArgvSegment(fromSessionId, newSessionId, model) {
191
+ return ['--resume', fromSessionId, '--fork-session', '--session-id', newSessionId, '--model', model];
192
+ }
193
+ // Task 876 — the re-seat post-condition line. Emitted ONLY after a real
194
+ // assistant turn on the fork is read (intention is not logged, per the
195
+ // observability contract): a re-seat that silently lands on the wrong model
196
+ // is visible rather than reported as success. Correlation key: old→new id.
197
+ export function formatReseatLine(fromModel, toModel, oldSessionId, newSessionId, observed) {
198
+ return `[webchat:reseat] op=reseat from-model=${fromModel ?? 'unknown'} to-model=${toModel} old-session=${oldSessionId} new-session=${newSessionId} first-turn-model=${observed ?? 'unknown'}`;
199
+ }
200
+ // Task 876 — read the most-recent real (non-synthetic) assistant model from a
201
+ // session's JSONL. The source's last answered model is the re-seat's
202
+ // from-model; a stranded session whose only tail is a synthetic error yields
203
+ // null ('unknown'). Bottom-up scan: the latest answered turn wins.
204
+ function lastRealAssistantModel(jsonlPath) {
205
+ let lines;
206
+ try {
207
+ lines = readFileSync(jsonlPath, 'utf8').split('\n').filter((l) => l.length > 0);
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ for (let i = lines.length - 1; i >= 0; i--) {
213
+ try {
214
+ const rec = JSON.parse(lines[i]);
215
+ if (rec.type === 'assistant' && typeof rec.message?.model === 'string' && rec.message.model !== '<synthetic>') {
216
+ return rec.message.model;
217
+ }
218
+ }
219
+ catch {
220
+ /* a mid-write line — skip it */
221
+ }
222
+ }
223
+ return null;
224
+ }
225
+ // Task 876 — the FORK's first post-reseat assistant model. The fork JSONL
226
+ // opens with the copied history, and `--fork-session` PRESERVES each copied
227
+ // turn's original `timestamp` (verified: only `sessionId` is rewritten), so a
228
+ // post-arm timestamp cleanly discriminates the operator's first new turn from
229
+ // the copied history — independent of WHEN the copy flushes to disk (a
230
+ // line-count baseline would race the flush). Returns the first non-synthetic
231
+ // assistant model whose turn timestamp is strictly after `armTimeMs`, or null
232
+ // when no such turn has landed yet.
233
+ export function firstPostArmAssistantModel(lines, armTimeMs) {
234
+ for (const line of lines) {
235
+ if (line.length === 0)
236
+ continue;
237
+ try {
238
+ const rec = JSON.parse(line);
239
+ if (rec.type !== 'assistant')
240
+ continue;
241
+ const model = rec.message?.model;
242
+ if (typeof model !== 'string' || model === '<synthetic>')
243
+ continue;
244
+ const ts = typeof rec.timestamp === 'string' ? Date.parse(rec.timestamp) : NaN;
245
+ if (Number.isFinite(ts) && ts > armTimeMs)
246
+ return model;
247
+ }
248
+ catch {
249
+ /* a mid-write line — skip it */
250
+ }
251
+ }
252
+ return null;
253
+ }
254
+ // Task 876 — arm a one-shot reader on the FORK's first post-reseat assistant
255
+ // turn. `armTimeMs` is captured here (after bind); only assistant turns
256
+ // timestamped after it are read, so the copied history (old timestamps) is
257
+ // skipped however late it flushes. Emits the correlation line once a real
258
+ // model is read, then detaches; cleaned up on PTY exit.
259
+ function attachReseatObserve(deps, newSessionId, ctx) {
260
+ let done = false;
261
+ const jsonlPath = jsonlPathForSessionId(deps.spawnCwd, newSessionId);
262
+ const armTimeMs = Date.now();
263
+ function tryEmit() {
264
+ if (done)
265
+ return;
266
+ let lines;
267
+ try {
268
+ lines = readFileSync(jsonlPath, 'utf8').split('\n');
269
+ }
270
+ catch {
271
+ return;
272
+ }
273
+ const observed = firstPostArmAssistantModel(lines, armTimeMs);
274
+ if (observed === null)
275
+ return;
276
+ done = true;
277
+ deps.logger(formatReseatLine(ctx.fromModel, ctx.toModel, ctx.oldSessionId, newSessionId, observed));
278
+ }
279
+ const unsubscribe = deps.watcher.subscribe((event) => {
280
+ if (done)
281
+ return;
282
+ if (event.sessionId !== newSessionId)
283
+ return;
284
+ if (event.source !== 'jsonl')
285
+ return;
286
+ tryEmit();
287
+ });
288
+ onPtyExit(newSessionId, () => unsubscribe());
289
+ }
185
290
  // One-shot auto-close: once the session's JSONL contains an assistant
186
291
  // message with `stop_reason === "end_turn"`, stop the PTY so the session
187
292
  // closes after its single turn. Shared by the public-spawn database-operator
@@ -632,6 +737,17 @@ export function buildHttpApp(deps) {
632
737
  const personId = typeof body.personId === 'string' && body.personId.length > 0 && body.personId.length <= 128
633
738
  ? body.personId
634
739
  : undefined;
740
+ // Task 756 — the per-conversation webchat native channel for a public
741
+ // spawn. Shape-validated here (all three fields are non-empty strings, the
742
+ // gateway is loopback http) before it reaches the spawn, which composes it
743
+ // under the public ringfence (deny-all + reply-only allow + strict mcp).
744
+ const wc = body.webchatChannel;
745
+ const webchatChannel = wc && typeof wc === 'object'
746
+ && typeof wc.key === 'string' && wc.key.length > 0
747
+ && typeof wc.gatewayUrl === 'string' && wc.gatewayUrl.startsWith('http')
748
+ && typeof wc.serverPath === 'string' && wc.serverPath.startsWith('/')
749
+ ? { key: wc.key, gatewayUrl: wc.gatewayUrl, serverPath: wc.serverPath }
750
+ : undefined;
635
751
  // Task 624 — maxy-composed public-session title. Validated by the same
636
752
  // validateUserTitle the /:sessionId/rename route uses; persisted only for
637
753
  // role=public after the sessionId is minted. An invalid/absent name leaves
@@ -708,6 +824,7 @@ export function buildHttpApp(deps) {
708
824
  conversationNodeId,
709
825
  sliceToken,
710
826
  personId,
827
+ webchatChannel,
711
828
  });
712
829
  if (!result.ok) {
713
830
  if ('rejected' in result) {
@@ -1618,6 +1735,14 @@ export function buildHttpApp(deps) {
1618
1735
  }
1619
1736
  const sessionId = typeof body.sessionId === 'string' && body.sessionId.length > 0 ? body.sessionId : null;
1620
1737
  const name = typeof body.name === 'string' && body.name.length > 0 ? body.name : null;
1738
+ // Task 876 — re-seat fork. `forkFromSessionId` is the source whose history
1739
+ // is copied; `sessionId` is the caller-pre-minted fork target; `model` is
1740
+ // the chosen model. The UI allowlist (isSelectableModel) is the
1741
+ // authoritative gate; this shape-guard is defence-in-depth so a malformed
1742
+ // value cannot reach the `--model` argv (the manager has no rootDir-clean
1743
+ // path to import the models lib, so it guards on shape, not the exact list).
1744
+ const forkFromSessionId = typeof body.forkFromSessionId === 'string' && UUID_PATTERN.test(body.forkFromSessionId) ? body.forkFromSessionId : null;
1745
+ const reseatModel = typeof body.model === 'string' && /^claude-[a-z0-9.-]+(\[1m\])?$/i.test(body.model) ? body.model : null;
1621
1746
  // Task 626 — per-visitor slice ringfence for the public session-end review
1622
1747
  // rc session. Shape-gated identically to /public-spawn so a body-controlled
1623
1748
  // value cannot reach the child env. Absent on channel-admin / LinkedIn.
@@ -1625,6 +1750,14 @@ export function buildHttpApp(deps) {
1625
1750
  const personId = typeof body.personId === 'string' && body.personId.length > 0 && body.personId.length <= 128
1626
1751
  ? body.personId
1627
1752
  : undefined;
1753
+ // Task 865 — the authenticated admin's AdminUser.userId, threaded by the UI
1754
+ // from the validated PIN session (never message content). Shape-validated
1755
+ // the same way as personId; written into the classification sidecar so the
1756
+ // Sessions panel resolves the admin's display name. Absent on every
1757
+ // non-admin-webchat spawn.
1758
+ const adminUserId = typeof body.adminUserId === 'string' && body.adminUserId.length > 0 && body.adminUserId.length <= 128
1759
+ ? body.adminUserId
1760
+ : undefined;
1628
1761
  // Task 682 — per-session userId override. Server-resolved by the UI from the
1629
1762
  // authoritative users.json/account.json (never message content) and shape-
1630
1763
  // validated here the same way as personId. Absent for owner self-phone and
@@ -1679,6 +1812,27 @@ export function buildHttpApp(deps) {
1679
1812
  timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
1680
1813
  return c.json({ error: 'sessionId must be a v4 UUID' }, 400);
1681
1814
  }
1815
+ // Task 876 — re-seat fork validation. The fork needs a pre-minted target
1816
+ // id that does NOT yet exist, a shape-valid model, and a source whose
1817
+ // transcript exists (history to copy). Any failure → 400, no spawn.
1818
+ if (forkFromSessionId !== null) {
1819
+ if (sessionId === null) {
1820
+ timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
1821
+ return c.json({ error: 're-seat requires a pre-minted sessionId' }, 400);
1822
+ }
1823
+ if (reseatModel === null) {
1824
+ timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
1825
+ return c.json({ error: 're-seat requires a valid model' }, 400);
1826
+ }
1827
+ if (!findExistingJsonlForSessionId(deps.claudeConfigDir, forkFromSessionId)) {
1828
+ timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
1829
+ return c.json({ error: 're-seat source has no transcript' }, 400);
1830
+ }
1831
+ if (findExistingJsonlForSessionId(deps.claudeConfigDir, sessionId)) {
1832
+ timed(deps.logger, 'POST', '/rc-spawn', 400, Date.now() - start);
1833
+ return c.json({ error: 're-seat target id already exists' }, 400);
1834
+ }
1835
+ }
1682
1836
  // Task 702 — never run two processes on one sessionId. If we already own a
1683
1837
  // live PTY for this id, the spawn is a no-op (a bridge that lost its
1684
1838
  // in-memory index after a restart re-asks; the live PTY takes the next
@@ -1720,8 +1874,14 @@ export function buildHttpApp(deps) {
1720
1874
  const argv = ['--remote-control'];
1721
1875
  if (name)
1722
1876
  argv.push(name);
1723
- if (sessionId)
1877
+ // Task 876 — a re-seat forks the source onto the pre-minted target id; the
1878
+ // ordinary fresh/resume single-id push is replaced by the fork segment.
1879
+ if (forkFromSessionId !== null && reseatModel !== null && sessionId) {
1880
+ argv.push(...forkArgvSegment(forkFromSessionId, sessionId, reseatModel));
1881
+ }
1882
+ else if (sessionId) {
1724
1883
  argv.push(mode === 'resume' ? '--resume' : '--session-id', sessionId);
1884
+ }
1725
1885
  // Task 592 — per-spawn bypass-permissions waiver. The brand settings.json
1726
1886
  // carries `defaultMode: bypassPermissions` (Task 583); without this flag
1727
1887
  // the first-time "WARNING: … 1. No, exit 2. Yes, I accept" disclaimer
@@ -1996,6 +2156,12 @@ export function buildHttpApp(deps) {
1996
2156
  specialist: null,
1997
2157
  scopeUnitToken: unitToken,
1998
2158
  bridgeIds: [],
2159
+ // Task 863 — visitor identity from the rc-spawn body (public
2160
+ // path); null for channel-admin sessions, which carry none.
2161
+ personId: personId ?? null,
2162
+ // Task 865 — admin-webchat author identity from the rc-spawn body;
2163
+ // null for public and one-shot sessions, which carry none.
2164
+ adminUserId: adminUserId ?? null,
1999
2165
  }, deps.logger);
2000
2166
  deps.watcher.refreshSidecar?.(sessionId);
2001
2167
  }
@@ -2236,6 +2402,35 @@ export function buildHttpApp(deps) {
2236
2402
  // unit driven by node-pty; the operator-visible spawnMethod marker
2237
2403
  // is kept stable so server.log greps survive the Task 573 cleanup.
2238
2404
  deps.logger(`[sessions-rc-resume] mode=${mode} sessionId=${sessionId ?? 'none'} jsonlPath=${existingJsonlPath ?? 'none'} name=${name ?? 'none'} spawnedPid=${pty.pid} bridgeId=${bridgeSessionId ?? 'pending'} spawnMethod=systemd-scope env.CLAUDE_CONFIG_DIR=${deps.claudeConfigDir}`);
2405
+ // Task 876 — re-seat post-bind. Once the fork is bound: (1) arm the
2406
+ // one-shot observer so the post-condition line emits on the fork's first
2407
+ // real turn, and (2) best-effort stop the source session's live PTY — the
2408
+ // operator has explicitly moved off it (the removed-model case has no live
2409
+ // PTY, so the stop is a no-op there). Neither must fail the response.
2410
+ if (forkFromSessionId !== null && reseatModel !== null && sessionId && outcome === 'bound') {
2411
+ const sourceJsonl = findExistingJsonlForSessionId(deps.claudeConfigDir, forkFromSessionId);
2412
+ const fromModel = sourceJsonl ? lastRealAssistantModel(sourceJsonl) : null;
2413
+ attachReseatObserve(deps, sessionId, { fromModel, toModel: reseatModel, oldSessionId: forkFromSessionId });
2414
+ try {
2415
+ const source = deps.watcher.getBySessionId(forkFromSessionId);
2416
+ if (source && source.scopeUnitToken) {
2417
+ await stopSession({
2418
+ killGraceMs: deps.killGraceMs,
2419
+ logger: deps.logger,
2420
+ runSystemctl: deps.runSystemctl,
2421
+ rowScopeUnitToken: source.scopeUnitToken,
2422
+ rowPid: source.pid,
2423
+ }, forkFromSessionId);
2424
+ deps.logger(`[webchat:reseat] op=old-session-stop old-session=${forkFromSessionId} result=stopped`);
2425
+ }
2426
+ else {
2427
+ deps.logger(`[webchat:reseat] op=old-session-stop old-session=${forkFromSessionId} result=absent`);
2428
+ }
2429
+ }
2430
+ catch (err) {
2431
+ deps.logger(`[webchat:reseat] op=old-session-stop old-session=${forkFromSessionId} result=error err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
2432
+ }
2433
+ }
2239
2434
  timed(deps.logger, 'POST', '/rc-spawn', 200, Date.now() - start);
2240
2435
  // `slug` is the Task 648 canonical field the client navigates by;
2241
2436
  // `bridgeSessionId` is the same value, retained so pre-648 consumers and