@deepseek-insight/dsi 0.6.12 → 0.6.14

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 (189) hide show
  1. package/.agents/skills/dsi-thin/SKILL.md +140 -0
  2. package/.agents/skills/dsi-thin/references/ADR-0019-snapshot.md +363 -0
  3. package/.agents/skills/dsi-thin/references/authority-adr.md +28 -0
  4. package/.agents/skills/dsi-thin/references/input-contract.md +42 -0
  5. package/.agents/skills/dsi-thin/references/proven-gates.md +61 -0
  6. package/.agents/skills/dsi-thin/references/two-lane-contract.md +53 -0
  7. package/build/client/_app/immutable/assets/0.B-dIufDc.css +2 -0
  8. package/build/client/_app/immutable/assets/2.B70dfiy3.css +1 -0
  9. package/build/client/_app/immutable/chunks/{B9fOkvHb.js → 0HT0ILkA.js} +1 -1
  10. package/build/client/_app/immutable/chunks/{Dwt_JXeR.js → 2KTqoQH-.js} +1 -1
  11. package/build/client/_app/immutable/chunks/{BIvFR1xA.js → 2M1KS72j.js} +1 -1
  12. package/build/client/_app/immutable/chunks/{a1Hqq5w-.js → 2XtVrxH4.js} +1 -1
  13. package/build/client/_app/immutable/chunks/38H6DLwz.js +1 -0
  14. package/build/client/_app/immutable/chunks/{Cdn_bzx2.js → 3E8jbhSl.js} +1 -1
  15. package/build/client/_app/immutable/chunks/{CmNEu9LM.js → A5Iiq81h.js} +1 -1
  16. package/build/client/_app/immutable/chunks/{C48u-2qp.js → ANnPTMPF.js} +1 -1
  17. package/build/client/_app/immutable/chunks/{BfChGCnE.js → B5S0Wke9.js} +1 -1
  18. package/build/client/_app/immutable/chunks/BBDOLPkB.js +1 -0
  19. package/build/client/_app/immutable/chunks/{DJ36HFmi.js → BImoJlJo.js} +1 -1
  20. package/build/client/_app/immutable/chunks/{DqRXQwgi.js → BJsp4NMH.js} +1 -1
  21. package/build/client/_app/immutable/chunks/{C7a8i7sD.js → BKU5-8Td.js} +1 -1
  22. package/build/client/_app/immutable/chunks/BMgyB-Sy.js +1 -0
  23. package/build/client/_app/immutable/chunks/BYHxHjHc.js +1 -0
  24. package/build/client/_app/immutable/chunks/{BC5nqwvj.js → BZ6uvzU8.js} +1 -1
  25. package/build/client/_app/immutable/chunks/{CvjdFV1E.js → B_Gknb5_.js} +1 -1
  26. package/build/client/_app/immutable/chunks/{CDTTZjYW.js → Bf1I5ROF.js} +1 -1
  27. package/build/client/_app/immutable/chunks/{CaAId7nh.js → Bh_Z0-Fh.js} +1 -1
  28. package/build/client/_app/immutable/chunks/{CLvIgAhR.js → BjafRUyA.js} +1 -1
  29. package/build/client/_app/immutable/chunks/{CG77uzLI.js → Bq4mG8qS.js} +1 -1
  30. package/build/client/_app/immutable/chunks/{CI1tkkzb.js → BqX9BB0G.js} +1 -1
  31. package/build/client/_app/immutable/chunks/BwNsQhd8.js +1 -0
  32. package/build/client/_app/immutable/chunks/{CvWnUnS1.js → Bx68-tOf.js} +1 -1
  33. package/build/client/_app/immutable/chunks/{RIIj2qfU.js → C1dC47iq.js} +1 -1
  34. package/build/client/_app/immutable/chunks/{BNx4_m7U.js → C6zqVyWx.js} +1 -1
  35. package/build/client/_app/immutable/chunks/{GLtnCmfd.js → C8seIiU_.js} +1 -1
  36. package/build/client/_app/immutable/chunks/{B_6pr4k2.js → CCWXLdgd.js} +1 -1
  37. package/build/client/_app/immutable/chunks/{D5ecapQY.js → CHYEvS-6.js} +1 -1
  38. package/build/client/_app/immutable/chunks/{OVDjSnH6.js → CIH5Juz4.js} +1 -1
  39. package/build/client/_app/immutable/chunks/CMByLjfJ.js +1 -0
  40. package/build/client/_app/immutable/chunks/{CnAA0dO8.js → CNJYmM8n.js} +1 -1
  41. package/build/client/_app/immutable/chunks/CP4wchdA.js +2 -0
  42. package/build/client/_app/immutable/chunks/{B2hm1w7K.js → CQTGR8fW.js} +1 -1
  43. package/build/client/_app/immutable/chunks/{Bp4vT1Ke.js → CW2uYEvb.js} +1 -1
  44. package/build/client/_app/immutable/chunks/{BPFR73BS.js → C_Gd0Wor.js} +1 -1
  45. package/build/client/_app/immutable/chunks/{Dn4fSUjR.js → C_MwBDNt.js} +1 -1
  46. package/build/client/_app/immutable/chunks/{Dea0pTfN.js → Ch7rc-L7.js} +4 -4
  47. package/build/client/_app/immutable/chunks/{J3KpJGd_.js → ChC4viXw.js} +1 -1
  48. package/build/client/_app/immutable/chunks/{4pCoNSa4.js → CiLhSVIj.js} +1 -1
  49. package/build/client/_app/immutable/chunks/{C7iYNw8X.js → Cx8YXmgi.js} +1 -1
  50. package/build/client/_app/immutable/chunks/{BRNBFqQ_.js → D4I0Dqwh.js} +1 -1
  51. package/build/client/_app/immutable/chunks/D6mTYwDH.js +6 -0
  52. package/build/client/_app/immutable/chunks/{l5GiMY16.js → D7-uGicP.js} +1 -1
  53. package/build/client/_app/immutable/chunks/{C8wGq5T2.js → D7XrPGMK.js} +1 -1
  54. package/build/client/_app/immutable/chunks/{xRO38jd8.js → D7eKc9ur.js} +1 -1
  55. package/build/client/_app/immutable/chunks/{CEj_BGCY.js → DCmONIPx.js} +1 -1
  56. package/build/client/_app/immutable/chunks/DJWLLhNJ.js +1 -0
  57. package/build/client/_app/immutable/chunks/{qkxAgKjo.js → DNf96C-O.js} +1 -1
  58. package/build/client/_app/immutable/chunks/{D4Ogpdhb.js → DSqRqQNp.js} +1 -1
  59. package/build/client/_app/immutable/chunks/{D4xFb7_b.js → DT1b6JaK.js} +1 -1
  60. package/build/client/_app/immutable/chunks/{D3bC95qg.js → DWYSL4om.js} +1 -1
  61. package/build/client/_app/immutable/chunks/{Bi5NvDds.js → DYfx-RRD.js} +1 -1
  62. package/build/client/_app/immutable/chunks/{Bi25XSWW.js → DfIqw6WM.js} +1 -1
  63. package/build/client/_app/immutable/chunks/{CGux-rdo.js → Dj4_qcIH.js} +4 -4
  64. package/build/client/_app/immutable/chunks/{Cx__ttXF.js → Dk9aRp2m.js} +1 -1
  65. package/build/client/_app/immutable/chunks/{C-58cZAk.js → DmLHHlla.js} +3 -3
  66. package/build/client/_app/immutable/chunks/{GNZHLVjs.js → Do0VAo0_.js} +1 -1
  67. package/build/client/_app/immutable/chunks/{BFsDYOFz.js → DrVddB75.js} +1 -1
  68. package/build/client/_app/immutable/chunks/{CdFbOrCf.js → Dso_gKeU.js} +1 -1
  69. package/build/client/_app/immutable/chunks/{Bl78Sc72.js → DykTZ90H.js} +1 -1
  70. package/build/client/_app/immutable/chunks/{DNlR6G0m.js → GdM4UPkS.js} +1 -1
  71. package/build/client/_app/immutable/chunks/{B52Khb6E.js → IBXFCASw.js} +1 -1
  72. package/build/client/_app/immutable/chunks/{BIUi504d.js → IoX9lmZK.js} +1 -1
  73. package/build/client/_app/immutable/chunks/{Bn64J6A-.js → bpkmZ_Dv.js} +1 -1
  74. package/build/client/_app/immutable/chunks/{iK_uzrhN.js → crRp5OG4.js} +1 -1
  75. package/build/client/_app/immutable/chunks/da2fibLv.js +8 -0
  76. package/build/client/_app/immutable/chunks/{BOKyldQm.js → dcghWWmq.js} +1 -1
  77. package/build/client/_app/immutable/chunks/{CItDRTlN.js → fJBuRi-A.js} +1 -1
  78. package/build/client/_app/immutable/chunks/{CRe9IZg0.js → gXyYVhfn.js} +1 -1
  79. package/build/client/_app/immutable/chunks/{C2vkjTiv.js → j2lJ25s0.js} +1 -1
  80. package/build/client/_app/immutable/chunks/{DypDva7F.js → l3etNtOh.js} +1 -1
  81. package/build/client/_app/immutable/chunks/{BGQXNvll.js → olUfRNXS.js} +1 -1
  82. package/build/client/_app/immutable/chunks/{D3W5wGWM.js → uEyn1Z7p.js} +1 -1
  83. package/build/client/_app/immutable/entry/app.CCuVGpQc.js +2 -0
  84. package/build/client/_app/immutable/entry/start.BtgtnpyB.js +1 -0
  85. package/build/client/_app/immutable/nodes/0.GHtnahSb.js +1 -0
  86. package/build/client/_app/immutable/nodes/1.CaQ_jAq8.js +1 -0
  87. package/build/client/_app/immutable/nodes/2.DxOLlTKc.js +337 -0
  88. package/build/client/_app/version.json +1 -1
  89. package/build/handler.js +7 -6
  90. package/build/index.js +7 -6
  91. package/build/server/chunks/chunks/{app-config.svelte.js-BW7TwbBy.js → app-config.svelte.js-D7wkLUkK.js} +2 -2
  92. package/build/server/chunks/chunks/{db.js-BylhmazO.js → db.js-DRhjT6xK.js} +2 -2
  93. package/build/server/chunks/chunks/{db2.js-Cm8FBINY.js → db2.js-DUta2B-n.js} +207 -222
  94. package/build/server/chunks/chunks/{dsh-connection.js-Lnv7PlHw.js → dsh-connection.js-DRtqPR_j.js} +959 -752
  95. package/build/server/chunks/chunks/{dsh-events.js-D9tSYE7f.js → dsh-events.js-B_E1BU9F.js} +141 -350
  96. package/build/server/chunks/chunks/{git-probe.js-D_H3YQbR.js → git-probe.js-Daf6r7Z9.js} +3 -3
  97. package/build/server/chunks/chunks/index-server.js-CV2lq-ha.js +147 -0
  98. package/build/server/chunks/chunks/{insight-config.js-XUD5BN0g.js → insight-config.js-CA-iT3lB.js} +5 -3
  99. package/build/server/chunks/chunks/{internal2.js-BcS47nI-.js → internal.js-BcS47nI-.js} +1 -1
  100. package/build/server/chunks/chunks/{internal.js-D_QJoxN-.js → internal2.js-DAPn3Vk8.js} +11 -153
  101. package/build/server/chunks/chunks/merge.js-yMwW440S.js +228 -0
  102. package/build/server/chunks/chunks/{server.js-11-pxAWV.js → server.js-DqHGsQU2.js} +2 -2
  103. package/build/server/chunks/chunks/{state.js-DbUk4Hrz.js → state.js-CsIZcr7q.js} +5 -4
  104. package/build/server/chunks/chunks/theme-prefs.svelte.js-8CaWz46g.js +109 -0
  105. package/build/server/chunks/chunks/{watcher.js-DU-MZGLU.js → watcher.js-CoR53lwR.js} +5 -5
  106. package/build/server/chunks/entries/endpoints/api/a2a/_id_/{_server.ts.js-DQenu1Rx.js → _server.ts.js-CpeC2QLb.js} +3 -3
  107. package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-BvEDeAhP.js → _server.ts.js-BKlF4XGH.js} +7 -6
  108. package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-DLJ4sQBq.js → _server.ts.js-BpEhlidV.js} +7 -6
  109. package/build/server/chunks/entries/endpoints/api/config/{_server.ts.js-zogZnK_B.js → _server.ts.js-BG78X__J.js} +3 -3
  110. package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-BAfRDeE_.js → _server.ts.js-Dmh7f-hU.js} +3 -3
  111. package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-N21TnqYh.js → _server.ts.js-BEci0ZAx.js} +3 -3
  112. package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-C5ckDTov.js → _server.ts.js-OXFzXBSp.js} +3 -3
  113. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-CEPz4kVZ.js → _server.ts.js-x44GKy4R.js} +3 -3
  114. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-CUB5TKnW.js → _server.ts.js-Da5ZqeJ7.js} +3 -3
  115. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-BfDAa0Vb.js → _server.ts.js-BACv9hW-.js} +3 -3
  116. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/changes/{_server.ts.js-QiLS5yAj.js → _server.ts.js-BtiUS3iD.js} +3 -3
  117. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-C5kJrRx-.js → _server.ts.js-BOOyd1LI.js} +3 -3
  118. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-vydjdEkJ.js → _server.ts.js-D0GwQbdF.js} +5 -4
  119. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-y5sqH_UC.js → _server.ts.js-Bd0JYMlf.js} +3 -3
  120. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-BoYIpj-f.js → _server.ts.js-DqAV7OOv.js} +3 -3
  121. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-SchAEll8.js → _server.ts.js-Cz4P-68E.js} +5 -4
  122. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-B8bmQCwT.js → _server.ts.js-C4fXXBIM.js} +3 -3
  123. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/path/{_server.ts.js-DxiKkvE-.js → _server.ts.js-_SIiC8sn.js} +2 -2
  124. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-BBexDstc.js → _server.ts.js-BiqNZiiN.js} +3 -3
  125. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-fd9StFXw.js → _server.ts.js-Bdx9V4oY.js} +3 -3
  126. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-98HaDusY.js → _server.ts.js-CuBqoV3c.js} +3 -3
  127. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-DN9APffn.js → _server.ts.js-CXPFI_Pn.js} +3 -3
  128. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-CeROEmUY.js → _server.ts.js-CRcaw4Yo.js} +3 -3
  129. package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-DKHmiqIs.js → _server.ts.js-dYrtVHDP.js} +3 -3
  130. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-S6XZDJmC.js → _server.ts.js-BtlfuMjf.js} +3 -3
  131. package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-CA4PUERZ.js → _server.ts.js-D12kAP9B.js} +3 -3
  132. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-BObM3Axz.js → _server.ts.js-C9JHp1WU.js} +3 -3
  133. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-CA34p4GV.js → _server.ts.js-bLF4Fbgn.js} +3 -3
  134. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-DPoRM_7u.js → _server.ts.js-BSw_zD-0.js} +3 -3
  135. package/build/server/chunks/entries/endpoints/api/prompts/_id_/{_server.ts.js-u-regI3R.js → _server.ts.js-bRLZ5TWI.js} +3 -3
  136. package/build/server/chunks/entries/endpoints/api/prompts/{_server.ts.js-YtkQGu7O.js → _server.ts.js-ChzWZoFk.js} +3 -3
  137. package/build/server/chunks/entries/endpoints/api/prompts/use/{_server.ts.js-tv0ivn2x.js → _server.ts.js-DdH5QmPS.js} +3 -3
  138. package/build/server/chunks/entries/endpoints/api/settings/{_server.ts.js-DmU2MBc5.js → _server.ts.js-DwGQj520.js} +2 -2
  139. package/build/server/chunks/entries/endpoints/api/terminal/{_server.ts.js-DHhod6dT.js → _server.ts.js-CwXC6Stk.js} +3 -2
  140. package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-BmEUPTSa.js → _server.ts.js-CqyKA9g2.js} +6 -5
  141. package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-_1uYvYEA.js → _server.ts.js-yu695YoE.js} +6 -5
  142. package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-DaXO3et9.js → _server.ts.js-BhDAvM6y.js} +6 -5
  143. package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-hp85iKSu.js → _server.ts.js-BRUgPrJ2.js} +6 -5
  144. package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-CPB-9252.js → _server.ts.js-C7VJ2G4_.js} +6 -5
  145. package/build/server/chunks/entries/endpoints/api/workspace/write-gate/{_server.ts.js-DKqzSQBE.js → _server.ts.js-BH5BRA_d.js} +6 -5
  146. package/build/server/chunks/entries/pages/{_error.svelte.js-AsgAVyDR.js → _error.svelte.js-B5TEtccE.js} +10 -9
  147. package/build/server/chunks/entries/pages/_layout.svelte.js-DRfiLzlz.js +20 -0
  148. package/build/server/chunks/entries/pages/{_page.server.ts.js-DTjoTdYK.js → _page.server.ts.js-CityLNBO.js} +3 -3
  149. package/build/server/chunks/entries/pages/{_page.svelte.js-CFdCjELr.js → _page.svelte.js-i6KsWK9O.js} +4654 -2413
  150. package/build/server/chunks/{handler-Bx2fbkEJ.js → handler-XuK3Ihhf.js} +3 -3
  151. package/build/server/chunks/{index.js-BldXQ38P.js → index.js-D2FEiD0I.js} +5 -4
  152. package/build/server/chunks/{manifest.js-Du-9DaEd.js → manifest.js-B_kahcFI.js} +45 -45
  153. package/build/server/chunks/nodes/0.js-C0B2o0le.js +12 -0
  154. package/build/server/chunks/nodes/1.js-C2e7NFqd.js +9 -0
  155. package/build/server/chunks/nodes/{2.js-DGLcInR6.js → 2.js-Bh3kLeYq.js} +9 -8
  156. package/package.json +2 -2
  157. package/skills/dsi-sloc/SKILL.md +117 -0
  158. package/skills/dsi-sloc/references/measurement.md +79 -0
  159. package/skills/dsi-sloc/references/report-skeleton.md +79 -0
  160. package/skills/dsi-thin/SKILL.md +214 -0
  161. package/skills/dsi-thin/references/ADR-0019-snapshot.md +363 -0
  162. package/skills/dsi-thin/references/authority-adr.md +28 -0
  163. package/skills/dsi-thin/references/input-contract.md +42 -0
  164. package/skills/dsi-thin/references/proven-gates.md +61 -0
  165. package/skills/dsi-thin/references/two-lane-contract.md +53 -0
  166. package/template/.dsi/prompts.sqlite +0 -0
  167. package/template/.dsi/prompts.sqlite-shm +0 -0
  168. package/template/.dsi/prompts.sqlite-wal +0 -0
  169. package/template/.dsi/resources/skills-reff.md +5 -4
  170. package/build/client/_app/immutable/assets/0.CRU0GiZX.css +0 -2
  171. package/build/client/_app/immutable/assets/2.Bzy6Q5kl.css +0 -1
  172. package/build/client/_app/immutable/chunks/-BV4MEqr.js +0 -1
  173. package/build/client/_app/immutable/chunks/66qbK5h1.js +0 -2
  174. package/build/client/_app/immutable/chunks/BLxS3bAX.js +0 -6
  175. package/build/client/_app/immutable/chunks/CPhcxli7.js +0 -1
  176. package/build/client/_app/immutable/chunks/CT1m72HM.js +0 -1
  177. package/build/client/_app/immutable/chunks/CxnI-Htc.js +0 -8
  178. package/build/client/_app/immutable/chunks/D0uZseUH.js +0 -1
  179. package/build/client/_app/immutable/chunks/D73RufXg.js +0 -1
  180. package/build/client/_app/immutable/chunks/HwuqC1sa.js +0 -1
  181. package/build/client/_app/immutable/chunks/NtHUheoE.js +0 -1
  182. package/build/client/_app/immutable/entry/app.D8WfdLSe.js +0 -2
  183. package/build/client/_app/immutable/entry/start.CVl0g0cv.js +0 -1
  184. package/build/client/_app/immutable/nodes/0.k1Ikzbp6.js +0 -1
  185. package/build/client/_app/immutable/nodes/1.X0If5xAN.js +0 -1
  186. package/build/client/_app/immutable/nodes/2.CELzu2i6.js +0 -337
  187. package/build/server/chunks/entries/pages/_layout.svelte.js-EFiihTdF.js +0 -18
  188. package/build/server/chunks/nodes/0.js-B0560RI0.js +0 -12
  189. package/build/server/chunks/nodes/1.js-D55AmdZs.js +0 -9
@@ -1,5 +1,5 @@
1
1
  import { v as dshBaseUrl } from './config.js-B0w1-uCA.js';
2
- import { r as readDshPresetEnglishConfig, a as readDshAuthConfig, b as readServerConfig } from './insight-config.js-XUD5BN0g.js';
2
+ import { r as readDshAuthConfig, a as readServerConfig, b as readDshPresetEnglishConfig } from './insight-config.js-CA-iT3lB.js';
3
3
 
4
4
  //#region src/lib/server/dsh-rpc.ts
5
5
  /**
@@ -369,116 +369,436 @@ var DshAuth = class {
369
369
  }
370
370
  };
371
371
  //#endregion
372
- //#region src/lib/server/dsh-connection.ts
372
+ //#region src/lib/server/connection/normalize.ts
373
+ /** The follow-<sessionId>-<streamSeq> logical stream id (one per generation). */
374
+ function followStreamId(sessionId, streamSeq) {
375
+ return `follow-${sessionId}-${String(streamSeq)}`;
376
+ }
373
377
  /**
374
- * dsh-connection — the transport singleton (BC-5) owning every wire byte to
375
- * the DSH Host (BC-6: it speaks only through dsh-rpc helpers and the two
376
- * downlink WebSocket paths).
377
- *
378
- * State model (PRD §3.5):
379
- * mux frames (session/subscribed, session/event) → per-session ring buffer
380
- * host frames (host/session-status) → per-session running map
381
- * running map re-baselines from session.list at every generation open
382
- * (2026-08-26): the host pushes status only on TRANSITIONS and sends no
383
- * initial snapshot, so a downlink rebuilt mid-turn would otherwise never
384
- * learn the session was already running.
385
- * ring buffer = freshness only; the ledger (session.history) is the truth —
386
- * cold loads and every resync re-read the ledger (BC-4).
387
- *
388
- * Generation semantics (guide Part 1): the two downlinks form one generation;
389
- * if either dies, both are rebuilt after backoff.
378
+ * Expand ONE follow/page event payload into its ring-buffer form
379
+ * (single-element list — ledger v2 serves every record as a plain event).
390
380
  */
391
- /** Live capacity from ~/.dsi/settings.yaml (no cache — runtime edits
392
- * apply, same contract as every other config section). */
393
- function ringCapacity() {
394
- return readServerConfig().ringCapacity;
381
+ function expandEntry(e) {
382
+ return [{
383
+ type: e.type,
384
+ seq: typeof e.seq === "number" ? e.seq : -1,
385
+ time: typeof e.time === "number" ? e.time : 0,
386
+ data: e.data
387
+ }];
395
388
  }
396
- var singleton;
397
- /** The process-wide transport (BC-5). Creates lazily on first use. */
398
- function getDshConnection(opts = {}) {
399
- singleton ??= new DshConnection(opts);
400
- return singleton;
389
+ /** Expand one page/snapshot record ({type:'event', event}) into its member
390
+ * events; a record without an event payload contributes nothing. */
391
+ function expandRecord(record) {
392
+ if (record === void 0 || record.event === void 0) return [];
393
+ return expandEntry(record.event);
401
394
  }
402
- var DshConnection = class {
403
- baseUrl;
404
- fetchFn;
405
- wsFactory;
406
- auth;
407
- rpcIdFactory;
408
- backoff;
395
+ /** Expand a records array into a flat event list. */
396
+ function expandRecords(records) {
397
+ return records.flatMap((record) => expandRecord(record));
398
+ }
399
+ /** The last member time of one compact stream record, or undefined when
400
+ * the record carries no readable time (malformed members stay silent —
401
+ * the baseline is presentation seed data, never re-validated truth). */
402
+ function compactRecordTailTime(record) {
403
+ if (record.type === "chunk") return typeof record.time === "number" ? record.time : void 0;
404
+ if (record.type === "text-chunks" || record.type === "reasoning-chunks" || record.type === "tool-call-chunks") {
405
+ const time0 = typeof record.time0 === "number" ? record.time0 : void 0;
406
+ const dt = Array.isArray(record.dt) ? record.dt : [];
407
+ if (time0 === void 0 || dt.some((g) => typeof g !== "number")) return void 0;
408
+ return dt.reduce((t, g) => t + g, time0);
409
+ }
410
+ }
411
+ function normalizeModelSelection(v) {
412
+ if (v === null || typeof v !== "object") return null;
413
+ const sel = v;
414
+ if (typeof sel.provider !== "string" || typeof sel.model !== "string") return null;
415
+ return {
416
+ provider: sel.provider,
417
+ model: sel.model,
418
+ ...typeof sel.reasoningEffort === "string" ? { reasoningEffort: sel.reasoningEffort } : {}
419
+ };
420
+ }
421
+ /**
422
+ * Conservative normalize of a SessionModels value: structural reads only,
423
+ * never throws, never invents data (task 3.2-T: a garbage catalog → empty
424
+ * groups + empty failures, honest `routable`/`current` — the selector can
425
+ * still show the current selection and say nothing is listed). Reasoning
426
+ * efforts are flattened to opaque ids (the adapter owns their meaning).
427
+ */
428
+ function normalizeModelDirectory(raw) {
429
+ const empty = {
430
+ current: null,
431
+ routable: false,
432
+ groups: [],
433
+ failures: []
434
+ };
435
+ if (raw === null || typeof raw !== "object") return empty;
436
+ const dir = raw;
437
+ const groups = Array.isArray(dir.groups) ? modelGroupsOf(dir.groups) : [];
438
+ const failures = Array.isArray(dir.failures) ? modelFailuresOf(dir.failures) : [];
439
+ if (dir.current === void 0 && dir.default !== void 0) return {
440
+ current: normalizeModelSelection(dir.default),
441
+ routable: Array.isArray(dir.routableProviders) ? dir.routableProviders.length > 0 || groups.length > 0 : true,
442
+ groups,
443
+ failures
444
+ };
445
+ return {
446
+ current: normalizeModelSelection(dir.current),
447
+ routable: dir.routable === true,
448
+ groups,
449
+ failures
450
+ };
451
+ }
452
+ /** ModelCatalog provider groups → DsiModelGroup rows (conservative). */
453
+ function modelGroupsOf(groups) {
454
+ return groups.flatMap((g) => {
455
+ if (g === null || typeof g !== "object") return [];
456
+ const grp = g;
457
+ if (typeof grp.id !== "string" || grp.id.length === 0) return [];
458
+ const models = Array.isArray(grp.models) ? grp.models.flatMap((m) => {
459
+ if (m === null || typeof m !== "object") return [];
460
+ const mod = m;
461
+ if (typeof mod.id !== "string" || mod.id.length === 0) return [];
462
+ return [{
463
+ id: mod.id,
464
+ name: typeof mod.name === "string" ? mod.name : null,
465
+ ...Array.isArray(mod.reasoning?.efforts) ? { reasoningEfforts: mod.reasoning.efforts.flatMap((e) => typeof e?.id === "string" ? [e.id] : []) } : {}
466
+ }];
467
+ }) : [];
468
+ return [{
469
+ id: grp.id,
470
+ name: typeof grp.name === "string" ? grp.name : null,
471
+ models
472
+ }];
473
+ });
474
+ }
475
+ /** ModelCatalog provider failures → diagnostic rows (conservative). */
476
+ function modelFailuresOf(failures) {
477
+ return failures.flatMap((f) => {
478
+ if (f === null || typeof f !== "object") return [];
479
+ const fail = f;
480
+ if (typeof fail.id !== "string" || fail.id.length === 0) return [];
481
+ return [{
482
+ id: fail.id,
483
+ name: typeof fail.name === "string" ? fail.name : null,
484
+ message: typeof fail.message === "string" ? fail.message : null
485
+ }];
486
+ });
487
+ }
488
+ function normalizeSessionRow(row) {
489
+ return {
490
+ sessionId: row.sessionId,
491
+ title: row.origin === "subagent" ? readSubagentLabel(row) ?? readTitle(row) : readTitle(row),
492
+ agentPreset: readAgentPreset(row) ?? row.agentPreset ?? null,
493
+ running: Boolean(row.running),
494
+ blank: Boolean(row.blank),
495
+ updatedAt: row.updatedAt ?? 0,
496
+ workspace: typeof row.cwd === "string" && row.cwd.length > 0 ? row.cwd : null,
497
+ turns: readTurns(row),
498
+ parentSessionId: row.parentSessionId ?? null,
499
+ origin: row.origin ?? null
500
+ };
501
+ }
502
+ function readTitle(row) {
503
+ const title = row.projections?.values?.["title"];
504
+ return typeof title === "string" && title.length > 0 ? title : null;
505
+ }
506
+ /** projections.values.subagent.label (the child descriptor's creation label;
507
+ * one-shot children may omit it — structural read, null when unreadable). */
508
+ function readSubagentLabel(row) {
509
+ const identity = row.projections?.values?.["subagent"];
510
+ const label = identity !== null && typeof identity === "object" ? identity.label : void 0;
511
+ return typeof label === "string" && label.length > 0 ? label : null;
512
+ }
513
+ /** projections.values.agentPreset (0.1.2 home of the preset id on list rows). */
514
+ function readAgentPreset(row) {
515
+ const preset = row.projections?.values?.["agentPreset"];
516
+ return typeof preset === "string" && preset.length > 0 ? preset : null;
517
+ }
518
+ /** `sessionStats.turns` (0.1.2: projections.values.sessionStats) — integer-strict, null on absent/junk. */
519
+ function readTurns(row) {
520
+ const stats = row.projections?.values?.["sessionStats"];
521
+ const turns = stats !== null && typeof stats === "object" ? stats.turns : row.sessionStats?.turns;
522
+ if (typeof turns !== "number" || !Number.isInteger(turns) || turns < 0) return null;
523
+ return turns;
524
+ }
525
+ /**
526
+ * English display copy for the host's SHIPPED presets, keyed by the stable
527
+ * preset id. The wire's `name`/`description` are the UNLOCALIZED metadata a
528
+ * preset publishes (DSH `PresetDisplaySource`), and the four built-ins ship
529
+ * Simplified Chinese there — DSH localizes them only inside its own web UI
530
+ * (`packages/preset/agent-presets/src/display.ts` folds trust:'system' + id →
531
+ * locale dictionary keys). This app has no host locale seat, so the overlay
532
+ * mirrors that fold in English: trust:'system' + a known id → the English
533
+ * copy; every other row (user presets, unknown future ids) keeps the host's
534
+ * name verbatim — user metadata is never translated, and an unknown id falls
535
+ * back honestly. Operator-tunable off via ~/.dsi/settings.yaml
536
+ * `dsh.presetEnglish: false` (readDshPresetEnglishConfig — no cache, so the
537
+ * next listPresets fetch picks an edit up). Keep the strings in step with DSH
538
+ * `packages/client/ui-agent-preset/src/client/locales.ts` (the `en` bundle).
539
+ */
540
+ var SHIPPED_PRESET_ENGLISH = {
541
+ standard: {
542
+ name: "Standard mode",
543
+ description: "Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows."
544
+ },
545
+ ptc: {
546
+ name: "PTC mode",
547
+ description: "Full coding agent without the workflow tool; other tools are exposed through the PTC mode SDK so the model can combine multi-step operations in one TypeScript program."
548
+ },
549
+ minimal: {
550
+ name: "Minimal mode",
551
+ description: "Two-tool coding agent with persistent bash and str_replace_editor."
552
+ },
553
+ cordis: {
554
+ name: "Creator mode",
555
+ description: "Built for creating custom agent presets, with all Standard mode capabilities plus runtime inspection, plugin experiments, and preset-authoring guidance."
556
+ }
557
+ };
558
+ /**
559
+ * Conservative normalize: unknown shapes keep their id but never invent data.
560
+ * The one English overlay seat (SHIPPED_PRESET_ENGLISH) applies before the
561
+ * host fallbacks — a shipped preset's label is locale-resolved here, never
562
+ * the wire's unlocalized text. Operator-tunable via
563
+ * ~/.dsi/settings.yaml `dsh.presetEnglish` (read live per listPresets
564
+ * call, passed through here): false hands the wire name through untouched.
565
+ */
566
+ function normalizePreset(row, overlayEnglish = true) {
567
+ const id = typeof row.id === "string" ? row.id : String(row.id ?? "");
568
+ const shipped = overlayEnglish && row.trust === "system" ? SHIPPED_PRESET_ENGLISH[id] : void 0;
569
+ return {
570
+ id,
571
+ name: shipped?.name ?? (typeof row.name === "string" ? row.name : null),
572
+ description: shipped?.description ?? (typeof row.description === "string" ? row.description : null),
573
+ isDefault: row.isDefault === true
574
+ };
575
+ }
576
+ //#endregion
577
+ //#region src/lib/server/connection/session-state.ts
578
+ var SessionStateStore = class {
579
+ deps;
409
580
  log;
410
581
  buffers = /* @__PURE__ */ new Map();
411
582
  running = /* @__PURE__ */ new Map();
412
- /**
413
- * Live assistant-stream tails (0.1.3-alpha.1): the one in-flight model
414
- * attempt per session, folded from the follow's seq-less assistant-stream
415
- * frames. Presentation state only — never part of the durable cursor
416
- * algebra (lastSeq/gap/coverage), because the frames carry no seq.
417
- */
418
583
  liveTails = /* @__PURE__ */ new Map();
419
- /**
420
- * Session-ids that received a host/session-status push since the running
421
- * re-baseline RPC was ISSUED (2026-08-26). A push is a strictly newer
422
- * transition than an in-flight snapshot — the seed must never overwrite
423
- * one. Swapped for a fresh set every time a seed is issued; read at
424
- * apply time so pushes landing mid-flight are counted.
425
- */
426
584
  runningSeedRaces = /* @__PURE__ */ new Set();
427
- wsMux;
428
- generation = 0;
429
- /** Logical streams by streamId → owning socket (0.1.2 remote.mux). */
430
- streamSockets = /* @__PURE__ */ new Map();
431
- /** Generation-scoped counter keeping stream ids unique across rebuilds. */
432
- streamSeq = 0;
433
- /** $events generation clientId (RemoteEventReadyFrame; binds $events/result). */
434
- eventClientId = null;
435
- /** True once the carrier's OPEN event fired (sends are legal). */
436
- opened = false;
437
- /** Per-session projection baselines (follow snapshot + control frames; W2). */
438
- projections = /* @__PURE__ */ new Map();
439
- /** Workspace registry cache from workspace/follow (W2; authority = host). */
440
- workspaces = [];
441
- /** Waiters resolved by the first workspace/follow baseline (one-shot). */
442
- workspaceBaselineWaiters = /* @__PURE__ */ new Set();
443
- /** Subscribes parked while the carrier is CONNECTING (flushed on open). */
444
- pendingSubscribes = /* @__PURE__ */ new Set();
445
- /**
446
- * Parent-address hints for subagent-origin sessions (SubagentAddressHint),
447
- * folded from session.list rows by rawListSessions.
448
- */
449
- subagentHints = /* @__PURE__ */ new Map();
450
- /** Session-ids whose hint absence was already probed with a list pass
451
- * (one probe per session lifetime — ordinary sessions never re-probe). */
452
- hintProbed = /* @__PURE__ */ new Set();
585
+ constructor(deps) {
586
+ this.deps = deps;
587
+ this.log = deps.log;
588
+ }
589
+ setRunning(sessionId, running) {
590
+ this.running.set(sessionId, running);
591
+ }
592
+ bufferKeys() {
593
+ return this.buffers.keys();
594
+ }
595
+ dispose() {
596
+ this.buffers.clear();
597
+ this.liveTails.clear();
598
+ this.running.clear();
599
+ this.runningSeedRaces.clear();
600
+ }
601
+ onHostFrame(payload) {
602
+ const frame = payload;
603
+ if (frame?.type === "host/session-status" && typeof frame.sessionId === "string") {
604
+ this.runningSeedRaces.add(frame.sessionId);
605
+ this.running.set(frame.sessionId, Boolean(frame.running));
606
+ }
607
+ }
608
+ async seedRunning() {
609
+ const generation = this.deps.generation();
610
+ this.runningSeedRaces = /* @__PURE__ */ new Set();
611
+ try {
612
+ const { items } = await this.deps.listSessions();
613
+ if (this.deps.disposed() || generation !== this.deps.generation()) return;
614
+ const races = this.runningSeedRaces;
615
+ const next = /* @__PURE__ */ new Map();
616
+ for (const [id, running] of this.running) if (races.has(id)) next.set(id, running);
617
+ for (const row of items) if (!races.has(row.sessionId)) next.set(row.sessionId, row.running);
618
+ this.running = next;
619
+ this.log(`dsh-connection: running map re-baselined (${items.length} sessions)`);
620
+ } catch (err) {
621
+ this.log("dsh-connection: running re-baseline failed — pushes stay authoritative", err);
622
+ }
623
+ }
624
+ bufferFor(sessionId) {
625
+ let buf = this.buffers.get(sessionId);
626
+ if (!buf) {
627
+ buf = {
628
+ entries: [],
629
+ lastSeq: -1,
630
+ coverageFloor: -1
631
+ };
632
+ this.buffers.set(sessionId, buf);
633
+ }
634
+ return buf;
635
+ }
636
+ seedBaseline(sessionId, lastSeq) {
637
+ const buf = this.bufferFor(sessionId);
638
+ if (lastSeq > buf.lastSeq && buf.entries.length === 0) {
639
+ buf.lastSeq = lastSeq;
640
+ buf.coverageFloor = lastSeq;
641
+ return;
642
+ }
643
+ if (buf.lastSeq >= 0 && lastSeq < buf.lastSeq) {
644
+ buf.entries.length = 0;
645
+ buf.lastSeq = lastSeq;
646
+ buf.coverageFloor = lastSeq;
647
+ }
648
+ }
649
+ seedSnapshotCursor(sessionId, cursor) {
650
+ const buf = this.bufferFor(sessionId);
651
+ if (cursor > buf.lastSeq) {
652
+ buf.lastSeq = cursor;
653
+ buf.coverageFloor = cursor;
654
+ return;
655
+ }
656
+ if (buf.lastSeq >= 0 && cursor < buf.lastSeq) {
657
+ buf.entries.length = 0;
658
+ buf.lastSeq = cursor;
659
+ buf.coverageFloor = cursor;
660
+ }
661
+ }
662
+ appendEvent(sessionId, event) {
663
+ const buf = this.bufferFor(sessionId);
664
+ if (event.seq > buf.lastSeq) {
665
+ if (buf.lastSeq >= 0 && event.seq > buf.lastSeq + 1) {
666
+ buf.coverageFloor = Math.max(buf.coverageFloor, buf.lastSeq);
667
+ this.log(`dsh-connection: seq gap for ${sessionId} (${buf.lastSeq} → ${event.seq}) — resync required below ${event.seq}`);
668
+ }
669
+ buf.lastSeq = event.seq;
670
+ buf.entries.push(event);
671
+ const cap = this.deps.ringCapacity();
672
+ if (buf.entries.length > cap) buf.entries.splice(0, buf.entries.length - cap);
673
+ }
674
+ }
675
+ onAssistantStreamFrame(sessionId, frame) {
676
+ if (frame.type === "start") {
677
+ this.liveTails.set(sessionId, {
678
+ attemptId: frame.attemptId,
679
+ turn: frame.turn,
680
+ step: frame.step,
681
+ startedAfterSeq: frame.startedAfterSeq,
682
+ startedAt: Date.now(),
683
+ lastTime: Date.now(),
684
+ text: "",
685
+ reasoning: "",
686
+ nextIndex: 0
687
+ });
688
+ return;
689
+ }
690
+ const tail = this.liveTails.get(sessionId);
691
+ if (tail === void 0) return;
692
+ if (frame.type === "chunk") {
693
+ if (frame.attemptId !== tail.attemptId || frame.index !== tail.nextIndex) {
694
+ this.liveTails.delete(sessionId);
695
+ return;
696
+ }
697
+ const kind = frame.chunk?.type;
698
+ if (kind === "text-delta" && typeof frame.chunk?.text === "string") tail.text += frame.chunk.text;
699
+ else if (kind === "reasoning-delta" && typeof frame.chunk?.text === "string") tail.reasoning += frame.chunk.text;
700
+ tail.nextIndex += 1;
701
+ if (typeof frame.time === "number") tail.lastTime = frame.time;
702
+ return;
703
+ }
704
+ this.liveTails.delete(sessionId);
705
+ }
706
+ seedLiveTailFromBaseline(sessionId, baseline) {
707
+ const active = baseline?.activeAttempt;
708
+ if (active === void 0) {
709
+ this.liveTails.delete(sessionId);
710
+ return;
711
+ }
712
+ const tail = {
713
+ attemptId: active.attemptId,
714
+ turn: active.turn,
715
+ step: active.step,
716
+ startedAfterSeq: active.startedAfterSeq,
717
+ startedAt: Date.now(),
718
+ lastTime: Date.now(),
719
+ text: "",
720
+ reasoning: "",
721
+ nextIndex: active.nextIndex
722
+ };
723
+ for (const record of active.stream ?? []) {
724
+ if (record.type === "text-chunks" || record.type === "reasoning-chunks") {
725
+ const texts = record.texts;
726
+ const joined = Array.isArray(texts) ? texts.filter((t) => typeof t === "string").join("") : "";
727
+ if (record.type === "text-chunks") tail.text += joined;
728
+ else tail.reasoning += joined;
729
+ }
730
+ const times = compactRecordTailTime(record);
731
+ if (times !== void 0) tail.lastTime = times;
732
+ }
733
+ this.liveTails.set(sessionId, tail);
734
+ }
735
+ liveAssistantStream(sessionId) {
736
+ const tail = this.liveTails.get(sessionId);
737
+ if (tail === void 0) return null;
738
+ return {
739
+ id: `a:${String(tail.turn)}:${String(tail.step)}`,
740
+ turn: tail.turn,
741
+ step: tail.step,
742
+ seq: tail.startedAfterSeq,
743
+ time: tail.lastTime,
744
+ text: tail.text,
745
+ reasoning: tail.reasoning
746
+ };
747
+ }
748
+ eventsSince(sessionId, n) {
749
+ const buf = this.bufferFor(sessionId);
750
+ const events = buf.entries.filter((e) => e.seq > n);
751
+ const firstDelta = events[0]?.seq;
752
+ const gap = firstDelta !== void 0 && firstDelta > n + 1;
753
+ return {
754
+ events,
755
+ lastSeq: buf.lastSeq,
756
+ running: this.running.get(sessionId) ?? false,
757
+ gap
758
+ };
759
+ }
760
+ hasBaseline(sessionId) {
761
+ return this.buffers.has(sessionId) && this.bufferFor(sessionId).lastSeq >= 0;
762
+ }
763
+ isRunning(sessionId) {
764
+ return this.running.get(sessionId) ?? false;
765
+ }
766
+ };
767
+ //#endregion
768
+ //#region src/lib/server/connection/downlinks.ts
769
+ /**
770
+ * connection/downlinks — the downlink generation lifecycle + frame demux
771
+ * (ADR-0022 W3, ADR-0019 move-whole): the ONE cookie-authenticated remote.mux
772
+ * carrier, its logical streams (session/follow, $events, workspace/follow,
773
+ * session/control), generation rebuild + backoff, the 0.1.2 frame demux,
774
+ * and the per-session projection / workspace-registry caches.
775
+ */
776
+ var DownlinkManager = class {
777
+ conn;
778
+ constructor(conn) {
779
+ this.conn = conn;
780
+ }
781
+ wsMux;
782
+ generation = 0;
783
+ /** Logical streams by streamId → owning socket (0.1.2 remote.mux). */
784
+ streamSockets = /* @__PURE__ */ new Map();
785
+ /** Generation-scoped counter keeping stream ids unique across rebuilds. */
786
+ streamSeq = 0;
787
+ /** $events generation clientId (RemoteEventReadyFrame; binds $events/result). */
788
+ eventClientId = null;
789
+ /** True once the carrier's OPEN event fired (sends are legal). */
790
+ opened = false;
791
+ /** Per-session projection baselines (follow snapshot + control frames; W2). */
792
+ projections = /* @__PURE__ */ new Map();
793
+ /** Workspace registry cache from workspace/follow (W2; authority = host). */
794
+ workspaces = [];
795
+ /** Waiters resolved by the first workspace/follow baseline (one-shot). */
796
+ workspaceBaselineWaiters = /* @__PURE__ */ new Set();
797
+ /** Subscribes parked while the carrier is CONNECTING (flushed on open). */
798
+ pendingSubscribes = /* @__PURE__ */ new Set();
453
799
  reconnectTimer;
454
- disposed = false;
455
800
  /** Wave 4.2 — consecutive failed generations; drives the backoff ladder. */
456
801
  reconnectAttempt = 0;
457
- /** Reap grace (see DshConnectionOptions.answerReapGraceMs). */
458
- answerReapGraceMs;
459
- answerReapTimer;
460
- /** Stale-generation retry deadline (see DshConnectionOptions.staleGenerationRetryMs). */
461
- staleGenerationRetryMs;
462
- constructor(opts = {}) {
463
- this.baseUrl = opts.baseUrl ?? dshBaseUrl();
464
- this.fetchFn = opts.fetchFn ?? fetch;
465
- this.wsFactory = opts.wsFactory ?? ((url, headers) => new WebSocket(url, headers === void 0 ? void 0 : { headers }));
466
- this.auth = opts.auth ?? new DshAuth({
467
- baseUrl: this.baseUrl,
468
- log: opts.log
469
- });
470
- this.rpcIdFactory = opts.rpcIdFactory ?? (() => crypto.randomUUID());
471
- this.backoff = opts.backoffScheduleMs ?? [
472
- 1e3,
473
- 2e3,
474
- 5e3,
475
- 1e4,
476
- 3e4
477
- ];
478
- this.answerReapGraceMs = opts.answerReapGraceMs ?? 3e3;
479
- this.staleGenerationRetryMs = opts.staleGenerationRetryMs ?? 5e3;
480
- this.log = opts.log ?? (() => {});
481
- }
482
802
  /**
483
803
  * Open the stream generation (idempotent): ONE cookie-authenticated
484
804
  * remote.mux socket (0.1.2 — events.mux/events.host are gone), with every
@@ -486,22 +806,22 @@ var DshConnection = class {
486
806
  * the $events forwarded-event stream (approvals/questions).
487
807
  */
488
808
  ensureDownlinks() {
489
- if (this.disposed || this.wsMux) return;
809
+ if (this.conn.disposed || this.wsMux) return;
490
810
  ++this.generation;
491
- this.log(`dsh-connection: opening remote.mux generation ${this.generation}`);
811
+ this.conn.log(`dsh-connection: opening remote.mux generation ${this.generation}`);
492
812
  this.wsMux = this.openDownlink((socket) => this.onMuxOpen(socket));
493
- this.seedRunning();
813
+ this.conn.sessions.seedRunning();
494
814
  }
495
815
  openDownlink(attach) {
496
- const url = this.baseUrl.replace(/^http/, "ws") + DSH_REMOTE_MUX_WS_PATH;
497
- const headers = this.auth.cookieHeader();
498
- const socket = this.wsFactory(url, headers === void 0 ? void 0 : { cookie: headers });
499
- this.auth.ensureCookie().catch(() => null);
816
+ const url = this.conn.baseUrl.replace(/^http/, "ws") + DSH_REMOTE_MUX_WS_PATH;
817
+ const headers = this.conn.auth.cookieHeader();
818
+ const socket = this.conn.wsFactory(url, headers === void 0 ? void 0 : { cookie: headers });
819
+ this.conn.auth.ensureCookie().catch(() => null);
500
820
  socket.addEventListener("message", (e) => {
501
821
  try {
502
822
  this.onStreamFrame(JSON.parse(String(e.data)));
503
823
  } catch (err) {
504
- this.log("dsh-connection: dropping malformed frame", err);
824
+ this.conn.log("dsh-connection: dropping malformed frame", err);
505
825
  }
506
826
  });
507
827
  socket.addEventListener("open", () => {
@@ -516,7 +836,7 @@ var DshConnection = class {
516
836
  onMuxOpen(socket) {
517
837
  this.streamSeq += 1;
518
838
  this.pendingSubscribes.clear();
519
- for (const sessionId of this.buffers.keys()) this.openFollowWithProbe(socket, sessionId);
839
+ for (const sessionId of this.conn.sessions.bufferKeys()) this.openFollowWithProbe(socket, sessionId);
520
840
  this.openEventStream(socket);
521
841
  this.openStateStream(socket, `workspaces-${String(this.streamSeq)}`, "workspace/follow");
522
842
  this.openStateStream(socket, `control-${String(this.streamSeq)}`, "session/control");
@@ -527,7 +847,7 @@ var DshConnection = class {
527
847
  }
528
848
  openFollowStream(socket, sessionId) {
529
849
  this.sendStreamOpen(socket, followStreamId(sessionId, this.streamSeq), "session/follow", { args: { request: {
530
- address: this.addressFor(sessionId),
850
+ address: this.conn.relay.addressFor(sessionId),
531
851
  maxMessages: 200,
532
852
  assistantStream: true
533
853
  } } });
@@ -547,20 +867,20 @@ var DshConnection = class {
547
867
  }
548
868
  /** Either socket dying rebuilds the whole generation (guide Part 1). */
549
869
  onDownlinkClosed() {
550
- if (this.disposed) return;
870
+ if (this.conn.disposed) return;
551
871
  if (!this.wsMux) return;
552
- this.log("dsh-connection: remote.mux died — rebuilding generation");
553
- this.fireCatalogInvalidated("all");
872
+ this.conn.log("dsh-connection: remote.mux died — rebuilding generation");
873
+ this.conn.relay.fireCatalogInvalidated("all");
554
874
  this.teardownSockets();
555
875
  this.scheduleReconnect();
556
876
  }
557
877
  scheduleReconnect() {
558
- if (this.disposed) return;
559
- const rung = Math.min(this.reconnectAttempt, this.backoff.length - 1);
560
- const delay = this.backoff[rung];
878
+ if (this.conn.disposed) return;
879
+ const rung = Math.min(this.reconnectAttempt, this.conn.backoff.length - 1);
880
+ const delay = this.conn.backoff[rung];
561
881
  if (this.reconnectTimer !== void 0) clearTimeout(this.reconnectTimer);
562
882
  this.reconnectTimer = setTimeout(() => {
563
- if (this.disposed) return;
883
+ if (this.conn.disposed) return;
564
884
  this.reconnectAttempt += 1;
565
885
  this.ensureDownlinks();
566
886
  }, delay);
@@ -598,7 +918,7 @@ var DshConnection = class {
598
918
  else if (frame.streamId.startsWith("control-")) this.onControlItem(value);
599
919
  return;
600
920
  }
601
- if (frame.type === "error") this.log(`dsh-connection: stream ${frame.streamId} failed`, frame.error?.code, frame.error?.message);
921
+ if (frame.type === "error") this.conn.log(`dsh-connection: stream ${frame.streamId} failed`, frame.error?.code, frame.error?.message);
602
922
  this.streamSockets.delete(frame.streamId);
603
923
  }
604
924
  /** session/follow item: snapshot seeds the baseline; events append; the
@@ -610,30 +930,30 @@ var DshConnection = class {
610
930
  if (sessionId === null) return;
611
931
  const snapshot = value;
612
932
  const events = expandRecords(snapshot.records ?? []);
613
- for (const event of events) this.appendEvent(sessionId, event);
614
- this.seedSnapshotCursor(sessionId, snapshot.cursor);
933
+ for (const event of events) this.conn.sessions.appendEvent(sessionId, event);
934
+ this.conn.sessions.seedSnapshotCursor(sessionId, snapshot.cursor);
615
935
  if (snapshot.projections !== null && typeof snapshot.projections === "object") this.projections.set(sessionId, snapshot.projections);
616
- this.seedLiveTailFromBaseline(sessionId, snapshot.assistantStream);
936
+ this.conn.sessions.seedLiveTailFromBaseline(sessionId, snapshot.assistantStream);
617
937
  return;
618
938
  }
619
939
  if (value.type === "assistant-stream") {
620
940
  const sessionId = this.sessionIdOfStream(streamId);
621
941
  const frame = value.frame;
622
- if (sessionId !== null && frame !== void 0) this.onAssistantStreamFrame(sessionId, frame);
942
+ if (sessionId !== null && frame !== void 0) this.conn.sessions.onAssistantStreamFrame(sessionId, frame);
623
943
  return;
624
944
  }
625
945
  if (value.type === "event") {
626
946
  const sessionId = this.sessionIdOfStream(streamId);
627
947
  const entry = value;
628
- if (sessionId !== null && entry.event !== void 0) for (const event of expandEntry(entry.event)) this.appendEvent(sessionId, event);
948
+ if (sessionId !== null && entry.event !== void 0) for (const event of expandEntry(entry.event)) this.conn.sessions.appendEvent(sessionId, event);
629
949
  }
630
950
  }
631
951
  /** $events item: forwarded Cordis event frames (waterfall = answerable). */
632
952
  onEventItem(value) {
633
953
  if (value.type === "ready") {
634
954
  if (typeof value.clientId === "string") this.eventClientId = value.clientId;
635
- this.log("dsh-connection: $events stream ready");
636
- this.armAnswerReap();
955
+ this.conn.log("dsh-connection: $events stream ready");
956
+ this.conn.relay.armAnswerReap();
637
957
  return;
638
958
  }
639
959
  if (value.type === "waterfall") {
@@ -643,7 +963,7 @@ var DshConnection = class {
643
963
  const request = value.request !== null && typeof value.request === "object" ? value.request : {};
644
964
  if (eventId === void 0) return;
645
965
  if (event === "approval/request") {
646
- this.registerPending({
966
+ this.conn.relay.registerPending({
647
967
  type: "approval/requested",
648
968
  sessionId: sessionId ?? "",
649
969
  ...request
@@ -651,43 +971,43 @@ var DshConnection = class {
651
971
  return;
652
972
  }
653
973
  if (event === "user-questions/request") {
654
- this.registerPending({
974
+ this.conn.relay.registerPending({
655
975
  type: "question/requested",
656
976
  sessionId: sessionId ?? "",
657
977
  ...request
658
978
  }, eventId);
659
979
  return;
660
980
  }
661
- this.log("dsh-connection: forwarded waterfall ignored", event);
981
+ this.conn.log("dsh-connection: forwarded waterfall ignored", event);
662
982
  return;
663
983
  }
664
984
  if (value.type === "cancel") {
665
985
  const eventId = typeof value.eventId === "string" ? value.eventId : void 0;
666
- if (eventId !== void 0) this.withdrawPending(eventId);
667
- this.log("dsh-connection: forwarded waterfall cancelled");
986
+ if (eventId !== void 0) this.conn.relay.withdrawPending(eventId);
987
+ this.conn.log("dsh-connection: forwarded waterfall cancelled");
668
988
  return;
669
989
  }
670
990
  if (value.type === "settle") {
671
991
  const eventId = typeof value.eventId === "string" ? value.eventId : void 0;
672
992
  const outcome = typeof value.outcome === "string" ? value.outcome : "unknown";
673
- if (eventId !== void 0) this.withdrawPending(eventId, outcome);
993
+ if (eventId !== void 0) this.conn.relay.withdrawPending(eventId, outcome);
674
994
  return;
675
995
  }
676
996
  if (value.type === "emit") {
677
997
  if (value.event === "api-session/status" && Array.isArray(value.args)) {
678
998
  const [sessionId, running] = value.args;
679
- if (typeof sessionId === "string") this.running.set(sessionId, running === true);
999
+ if (typeof sessionId === "string") this.conn.sessions.setRunning(sessionId, running === true);
680
1000
  return;
681
1001
  }
682
1002
  if (value.event === "commands/change") {
683
- this.fireCatalogInvalidated("all");
1003
+ this.conn.relay.fireCatalogInvalidated("all");
684
1004
  return;
685
1005
  }
686
1006
  if (value.event === "agent-preset/selected" && typeof value.agentId === "string") {
687
- this.fireCatalogInvalidated(value.agentId);
1007
+ this.conn.relay.fireCatalogInvalidated(value.agentId);
688
1008
  return;
689
1009
  }
690
- this.log("dsh-connection: forwarded emit ignored", value.event);
1010
+ this.conn.log("dsh-connection: forwarded emit ignored", value.event);
691
1011
  return;
692
1012
  }
693
1013
  }
@@ -712,7 +1032,7 @@ var DshConnection = class {
712
1032
  });
713
1033
  for (const waiter of this.workspaceBaselineWaiters) waiter.resolve();
714
1034
  this.workspaceBaselineWaiters.clear();
715
- this.log(`dsh-connection: workspace baseline (${this.workspaces.length} rows)`);
1035
+ this.conn.log(`dsh-connection: workspace baseline (${this.workspaces.length} rows)`);
716
1036
  return;
717
1037
  }
718
1038
  if (value.type === "upsert") {
@@ -743,7 +1063,7 @@ var DshConnection = class {
743
1063
  if (value.type === "baseline") {
744
1064
  const baseline = value.value ?? {};
745
1065
  for (const [sessionId, block] of Object.entries(baseline.projections ?? {})) if (block !== null && typeof block === "object") this.projections.set(sessionId, block);
746
- this.log(`dsh-connection: control baseline (${this.projections.size} sessions)`);
1066
+ this.conn.log(`dsh-connection: control baseline (${this.projections.size} sessions)`);
747
1067
  return;
748
1068
  }
749
1069
  if (value.type === "projection" && typeof value.sessionId === "string" && typeof value.key === "string") {
@@ -766,264 +1086,26 @@ var DshConnection = class {
766
1086
  const cut = rest.lastIndexOf("-");
767
1087
  return cut > 0 ? rest.slice(0, cut) : null;
768
1088
  }
769
- pendingAnswers = /* @__PURE__ */ new Map();
770
- /** Settlement ring: last outcome per rpcId, delivered-then-pruned. */
771
- settledAnswers = /* @__PURE__ */ new Map();
772
- /** Catalog-invalidation registry (Slash Menu W1) — listeners see every arm. */
773
- catalogInvalidated = /* @__PURE__ */ new Set();
774
- /** Subscribe to catalog invalidations ($events arms + socket reset). */
775
- onCatalogInvalidated(listener) {
776
- this.catalogInvalidated.add(listener);
777
- return () => this.catalogInvalidated.delete(listener);
778
- }
779
- fireCatalogInvalidated(scope) {
780
- for (const listener of this.catalogInvalidated) listener(scope);
781
- }
782
- onMuxFrame(payload, rpcId) {
783
- this.reconnectAttempt = 0;
784
- const frame = payload;
785
- switch (frame?.type) {
786
- case "session/subscribed":
787
- if (typeof frame.sessionId === "string") this.seedBaseline(frame.sessionId, frame.lastSeq ?? -1);
788
- return;
789
- case "session/event":
790
- if (frame.sessionId && frame.event) this.appendEvent(frame.sessionId, frame.event);
791
- return;
792
- case "approval/requested":
793
- case "question/requested":
794
- this.registerPending(frame, rpcId);
795
- return;
796
- case "approval/resolved":
797
- case "question/resolved":
798
- this.registerSettlement(frame);
799
- return;
800
- default: this.log("dsh-connection: mux frame ignored", frame?.type);
801
- }
802
- }
803
- onHostFrame(payload) {
804
- const frame = payload;
805
- if (frame?.type === "host/session-status" && typeof frame.sessionId === "string") {
806
- this.runningSeedRaces.add(frame.sessionId);
807
- this.running.set(frame.sessionId, Boolean(frame.running));
808
- }
809
- }
810
- /**
811
- * 2026-08-26 running re-baseline: DSH pushes host/session-status only on
812
- * transitions and the host stream sends no initial snapshot on (re)open —
813
- * a downlink rebuilt mid-turn never sees the running:true flip that
814
- * happened before it existed (the live bug: events kept flowing on the
815
- * mux while the streaming indicator said idle). session.list is the host's
816
- * documented reconnect baseline, so every generation opens with ONE list
817
- * RPC seeding the whole map — which also prunes entries for sessions that
818
- * died while disconnected. Pushes that land while the RPC is in flight win
819
- * over the snapshot (they are strictly newer transitions); a seed from a
820
- * superseded generation is dropped entirely.
821
- */
822
- async seedRunning() {
823
- const generation = this.generation;
824
- this.runningSeedRaces = /* @__PURE__ */ new Set();
825
- try {
826
- const { items } = await this.listSessions();
827
- if (this.disposed || generation !== this.generation) return;
828
- const races = this.runningSeedRaces;
829
- const next = /* @__PURE__ */ new Map();
830
- for (const [id, running] of this.running) if (races.has(id)) next.set(id, running);
831
- for (const row of items) if (!races.has(row.sessionId)) next.set(row.sessionId, row.running);
832
- this.running = next;
833
- this.log(`dsh-connection: running map re-baselined (${items.length} sessions)`);
834
- } catch (err) {
835
- this.log("dsh-connection: running re-baseline failed — pushes stay authoritative", err);
836
- }
837
- }
838
- bufferFor(sessionId) {
839
- let buf = this.buffers.get(sessionId);
840
- if (!buf) {
841
- buf = {
842
- entries: [],
843
- lastSeq: -1,
844
- coverageFloor: -1
845
- };
846
- this.buffers.set(sessionId, buf);
847
- }
848
- return buf;
849
- }
850
- seedBaseline(sessionId, lastSeq) {
851
- const buf = this.bufferFor(sessionId);
852
- if (lastSeq > buf.lastSeq && buf.entries.length === 0) {
853
- buf.lastSeq = lastSeq;
854
- buf.coverageFloor = lastSeq;
855
- return;
856
- }
857
- if (buf.lastSeq >= 0 && lastSeq < buf.lastSeq) {
858
- buf.entries.length = 0;
859
- buf.lastSeq = lastSeq;
860
- buf.coverageFloor = lastSeq;
861
- }
862
- }
863
- /**
864
- * Snapshot cursor seed (0.1.2). A snapshot BELOW the buffered high-water
865
- * mark means the upstream was rebuilt (stream restart): stale entries
866
- * belong to the dead instance — reset the buffer to the snapshot truth
867
- * (Wave 2 parity with the 0.1.1 rewound-baseline semantics).
868
- */
869
- seedSnapshotCursor(sessionId, cursor) {
870
- const buf = this.bufferFor(sessionId);
871
- if (cursor > buf.lastSeq) {
872
- buf.lastSeq = cursor;
873
- buf.coverageFloor = cursor;
874
- return;
875
- }
876
- if (buf.lastSeq >= 0 && cursor < buf.lastSeq) {
877
- buf.entries.length = 0;
878
- buf.lastSeq = cursor;
879
- buf.coverageFloor = cursor;
880
- }
881
- }
882
- appendEvent(sessionId, event) {
883
- const buf = this.bufferFor(sessionId);
884
- if (event.seq > buf.lastSeq) {
885
- if (buf.lastSeq >= 0 && event.seq > buf.lastSeq + 1) {
886
- buf.coverageFloor = Math.max(buf.coverageFloor, buf.lastSeq);
887
- this.log(`dsh-connection: seq gap for ${sessionId} (${buf.lastSeq} → ${event.seq}) — resync required below ${event.seq}`);
888
- }
889
- buf.lastSeq = event.seq;
890
- buf.entries.push(event);
891
- const cap = ringCapacity();
892
- if (buf.entries.length > cap) buf.entries.splice(0, buf.entries.length - cap);
893
- }
894
- }
895
- /**
896
- * Fold one assistant-stream frame into the session's live tail. Seq-less
897
- * presentation state: never touches lastSeq/coverage — the durable
898
- * cursor algebra stays event-entry-only. The host keeps ONE active
899
- * attempt per session and drops its own accumulator on any continuity
900
- * break (foreign attemptId, dense-index gap), so mirrors do the same.
901
- */
902
- onAssistantStreamFrame(sessionId, frame) {
903
- if (frame.type === "start") {
904
- this.liveTails.set(sessionId, {
905
- attemptId: frame.attemptId,
906
- turn: frame.turn,
907
- step: frame.step,
908
- startedAfterSeq: frame.startedAfterSeq,
909
- startedAt: Date.now(),
910
- lastTime: Date.now(),
911
- text: "",
912
- reasoning: "",
913
- nextIndex: 0
914
- });
915
- return;
916
- }
917
- const tail = this.liveTails.get(sessionId);
918
- if (tail === void 0) return;
919
- if (frame.type === "chunk") {
920
- if (frame.attemptId !== tail.attemptId || frame.index !== tail.nextIndex) {
921
- this.liveTails.delete(sessionId);
922
- return;
923
- }
924
- const kind = frame.chunk?.type;
925
- if (kind === "text-delta" && typeof frame.chunk?.text === "string") tail.text += frame.chunk.text;
926
- else if (kind === "reasoning-delta" && typeof frame.chunk?.text === "string") tail.reasoning += frame.chunk.text;
927
- tail.nextIndex += 1;
928
- if (typeof frame.time === "number") tail.lastTime = frame.time;
929
- return;
930
- }
931
- this.liveTails.delete(sessionId);
932
- }
933
- /**
934
- * Seed the live tail from a follow-opening snapshot's assistantStream
935
- * baseline (mux-drop reopen mid-attempt): expand the compact stream
936
- * records' text/reasoning runs into the tail. No activeAttempt → clear
937
- * any remembered tail — the fresh opening is authoritative for what is
938
- * still in flight.
939
- */
940
- seedLiveTailFromBaseline(sessionId, baseline) {
941
- const active = baseline?.activeAttempt;
942
- if (active === void 0) {
943
- this.liveTails.delete(sessionId);
944
- return;
945
- }
946
- const tail = {
947
- attemptId: active.attemptId,
948
- turn: active.turn,
949
- step: active.step,
950
- startedAfterSeq: active.startedAfterSeq,
951
- startedAt: Date.now(),
952
- lastTime: Date.now(),
953
- text: "",
954
- reasoning: "",
955
- nextIndex: active.nextIndex
956
- };
957
- for (const record of active.stream ?? []) {
958
- if (record.type === "text-chunks" || record.type === "reasoning-chunks") {
959
- const texts = record.texts;
960
- const joined = Array.isArray(texts) ? texts.filter((t) => typeof t === "string").join("") : "";
961
- if (record.type === "text-chunks") tail.text += joined;
962
- else tail.reasoning += joined;
963
- }
964
- const times = compactRecordTailTime(record);
965
- if (times !== void 0) tail.lastTime = times;
966
- }
967
- this.liveTails.set(sessionId, tail);
968
- }
969
- /**
970
- * The session's live tail for the events poll, or null when no attempt
971
- * is in flight. Served with the FULL accumulated text each poll — the
972
- * client replaces its streaming bubble wholesale (no cross-poll fragment
973
- * bookkeeping, so reconnects/retries converge from the served truth).
974
- */
975
- liveAssistantStream(sessionId) {
976
- const tail = this.liveTails.get(sessionId);
977
- if (tail === void 0) return null;
978
- return {
979
- id: `a:${String(tail.turn)}:${String(tail.step)}`,
980
- turn: tail.turn,
981
- step: tail.step,
982
- seq: tail.startedAfterSeq,
983
- time: tail.lastTime,
984
- text: tail.text,
985
- reasoning: tail.reasoning
986
- };
987
- }
988
- /**
989
- * Delta slice: events with seq > n, plus current lastSeq/running.
990
- * Wave 4.2: `gap` is true when the caller's `since` mark fell into a hole
991
- * the buffer no longer covers (mux drop). The response then carries the
992
- * resync flag so the client re-reads from the ledger instead of trusting
993
- * a partial delta (BC-4).
994
- */
995
- eventsSince(sessionId, n) {
996
- const buf = this.bufferFor(sessionId);
997
- const events = buf.entries.filter((e) => e.seq > n);
998
- const firstDelta = events[0]?.seq;
999
- const gap = firstDelta !== void 0 && firstDelta > n + 1;
1000
- return {
1001
- events,
1002
- lastSeq: buf.lastSeq,
1003
- running: this.running.get(sessionId) ?? false,
1004
- gap
1005
- };
1006
- }
1007
- /**
1008
- * Subscribe one session (0.1.2): create its buffer and open a
1009
- * session/follow logical stream on the live carrier (or mark it for the
1010
- * next generation). Idempotent; the snapshot frame seeds the baseline.
1011
- * A repeat call while THIS generation already holds the session's follow
1012
- * stream is a no-op — the api-dsh events route calls subscribe on every
1013
- * poll, and reopening would replace the stream (same streamId → fresh
1014
- * snapshot) on every tick. A stream 'end'/'error' frame drops the
1015
- * tracking entry so the next poll reopens and self-heals.
1016
- */
1017
- subscribe(sessionId) {
1018
- this.bufferFor(sessionId);
1019
- const socket = this.wsMux;
1020
- if (socket === void 0) return;
1021
- if (this.opened === false) {
1022
- this.pendingSubscribes.add(sessionId);
1023
- return;
1024
- }
1025
- if (this.streamSockets.has(followStreamId(sessionId, this.streamSeq))) return;
1026
- this.openFollowWithProbe(socket, sessionId);
1089
+ /**
1090
+ * Subscribe one session (0.1.2): create its buffer and open a
1091
+ * session/follow logical stream on the live carrier (or mark it for the
1092
+ * next generation). Idempotent; the snapshot frame seeds the baseline.
1093
+ * A repeat call while THIS generation already holds the session's follow
1094
+ * stream is a no-op — the api-dsh events route calls subscribe on every
1095
+ * poll, and reopening would replace the stream (same streamId → fresh
1096
+ * snapshot) on every tick. A stream 'end'/'error' frame drops the
1097
+ * tracking entry so the next poll reopens and self-heals.
1098
+ */
1099
+ subscribe(sessionId) {
1100
+ this.conn.sessions.bufferFor(sessionId);
1101
+ const socket = this.wsMux;
1102
+ if (socket === void 0) return;
1103
+ if (this.opened === false) {
1104
+ this.pendingSubscribes.add(sessionId);
1105
+ return;
1106
+ }
1107
+ if (this.streamSockets.has(followStreamId(sessionId, this.streamSeq))) return;
1108
+ this.openFollowWithProbe(socket, sessionId);
1027
1109
  }
1028
1110
  /**
1029
1111
  * Open the follow stream with the best-known address NOW (sync, the
@@ -1033,8 +1115,8 @@ var DshConnection = class {
1033
1115
  */
1034
1116
  openFollowWithProbe(socket, sessionId) {
1035
1117
  this.openFollowStream(socket, sessionId);
1036
- if (!this.subagentHints.has(sessionId) && !this.hintProbed.has(sessionId)) {
1037
- this.hintProbed.add(sessionId);
1118
+ if (!this.conn.relay.subagentHints.has(sessionId) && !this.conn.relay.hintProbed.has(sessionId)) {
1119
+ this.conn.relay.hintProbed.add(sessionId);
1038
1120
  this.probeHintThenSubscribe(sessionId, socket);
1039
1121
  }
1040
1122
  }
@@ -1046,10 +1128,10 @@ var DshConnection = class {
1046
1128
  * reopens every buffer with the then-current addresses. */
1047
1129
  async probeHintThenSubscribe(sessionId, socket) {
1048
1130
  try {
1049
- if (!this.subagentHints.has(sessionId)) await this.rawListSessions();
1131
+ if (!this.conn.relay.subagentHints.has(sessionId)) await this.conn.relay.rawListSessions();
1050
1132
  } catch {}
1051
- if (this.disposed || this.wsMux !== socket || this.opened === false) return;
1052
- if (this.subagentHints.has(sessionId)) this.openFollowStream(socket, sessionId);
1133
+ if (this.conn.disposed || this.wsMux !== socket || this.opened === false) return;
1134
+ if (this.conn.relay.subagentHints.has(sessionId)) this.openFollowStream(socket, sessionId);
1053
1135
  }
1054
1136
  /**
1055
1137
  * Per-session projection value (W2): follow-snapshot + control-baseline
@@ -1096,55 +1178,131 @@ var DshConnection = class {
1096
1178
  this.workspaceBaselineWaiters.delete(waiter);
1097
1179
  return this.workspaces.length > 0;
1098
1180
  }
1099
- /** True when the session has a live baseline (subscribed at least once). */
1100
- hasBaseline(sessionId) {
1101
- return this.buffers.has(sessionId) && this.bufferFor(sessionId).lastSeq >= 0;
1102
- }
1103
- isRunning(sessionId) {
1104
- return this.running.get(sessionId) ?? false;
1181
+ /** The workspace registry (2026-08-23 registry parity) — the AUTHORITY
1182
+ * for DSI's workspace pills/chips: a deleted workspace disappears, and
1183
+ * sessions outside every registry entry render cue-less (DSH's
1184
+ * Ungrouped). Membership is sessionIds, not cwd matching. */
1185
+ async listWorkspaces() {
1186
+ if (!await this.awaitWorkspaceBaseline()) this.conn.log("dsh-connection: workspace baseline did not land in time — honest empty");
1187
+ return { items: [...this.workspaces] };
1105
1188
  }
1106
- /** approval/requested | question/requested → registry (idempotent by rpcId). */
1107
- registerPending(frame, rpcId) {
1108
- const sessionId = typeof frame.sessionId === "string" ? frame.sessionId : "";
1109
- if (!sessionId) {
1110
- this.log("dsh-connection: answerable frame without sessionId — ignored", frame?.type);
1111
- return;
1112
- }
1113
- if (typeof rpcId !== "string" || rpcId.length === 0) {
1114
- this.log("dsh-connection: answerable frame without rpcId — ignored", frame?.type);
1115
- return;
1189
+ /** Cancel a scheduled reconnect (stale-generation answer retry path, W4). */
1190
+ cancelReconnect() {
1191
+ if (this.reconnectTimer !== void 0) {
1192
+ clearTimeout(this.reconnectTimer);
1193
+ this.reconnectTimer = void 0;
1116
1194
  }
1117
- const kind = frame.type === "approval/requested" ? "approval" : "question";
1118
- this.pendingAnswers.set(rpcId, {
1119
- rpcId,
1120
- sessionId,
1121
- kind,
1122
- body: frame,
1123
- receivedAt: Date.now(),
1124
- deliveredGen: this.generation
1125
- });
1126
- this.log(`dsh-connection: pending ${kind} registered (${rpcId}) for ${sessionId}`);
1127
1195
  }
1128
- /**
1129
- * Withdraw one pending answer (0.1.2 $events settle/cancel): remove the
1130
- * registry entry and record the outcome in the settlement ring. Unknown
1131
- * ids settle nothing (answered before we subscribed — honest no-op).
1132
- */
1133
- withdrawPending(eventId, outcome) {
1134
- const pending = this.pendingAnswers.get(eventId);
1135
- if (pending === void 0) {
1136
- this.log(`dsh-connection: settlement with no matching pending (${eventId}) — ignored`);
1137
- return;
1138
- }
1139
- this.pendingAnswers.delete(eventId);
1140
- this.settledAnswers.set(eventId, {
1141
- rpcId: eventId,
1142
- sessionId: pending.sessionId,
1143
- kind: pending.kind,
1144
- outcome: outcome ?? "cancelled",
1145
- settledAt: Date.now()
1146
- });
1147
- this.log(`dsh-connection: ${pending.kind} settled (${eventId}) outcome=${outcome ?? "cancelled"}`);
1196
+ };
1197
+ //#endregion
1198
+ //#region src/lib/server/connection/rpc-relay.ts
1199
+ /**
1200
+ * connection/rpc-relay — the RPC relay + answer registry (ADR-0022 W4,
1201
+ * ADR-0019 move-whole): the POC-3 pending-answer/settlement registries and
1202
+ * their replay reap, the catalog-invalidation listener registry, the
1203
+ * subagent parent-address hints, and every unary RPC forward (all bytes via
1204
+ * dsh-rpc helpers; the transport POST itself stays in the shell's rpc()).
1205
+ */
1206
+ var RpcRelay = class {
1207
+ conn;
1208
+ ringCapacity;
1209
+ answerReapTimer;
1210
+ answerReapGraceMs;
1211
+ staleGenerationRetryMs;
1212
+ constructor(conn, ringCapacity, opts) {
1213
+ this.conn = conn;
1214
+ this.ringCapacity = ringCapacity;
1215
+ this.answerReapGraceMs = opts.answerReapGraceMs;
1216
+ this.staleGenerationRetryMs = opts.staleGenerationRetryMs;
1217
+ }
1218
+ /**
1219
+ * POC-3 W1 — answerable frames arrive as ServerRequest pushes with a
1220
+ * stable rpcId. Mux-open REPLAYS still-pending requested frames with the
1221
+ * same rpcId (api-proxy.ts replay loop), so the registry must be
1222
+ * idempotent by rpcId (BC-C): a replay overwrites, never duplicates.
1223
+ */
1224
+ pendingAnswers = /* @__PURE__ */ new Map();
1225
+ /** Settlement ring: last outcome per rpcId, delivered-then-pruned. */
1226
+ settledAnswers = /* @__PURE__ */ new Map();
1227
+ /** Catalog-invalidation registry (Slash Menu W1) — listeners see every arm. */
1228
+ catalogInvalidated = /* @__PURE__ */ new Set();
1229
+ /**
1230
+ * Parent-address hints for subagent-origin sessions (SubagentAddressHint),
1231
+ * folded from session.list rows by rawListSessions.
1232
+ */
1233
+ subagentHints = /* @__PURE__ */ new Map();
1234
+ /** Session-ids whose hint absence was already probed with a list pass
1235
+ * (one probe per session lifetime — ordinary sessions never re-probe). */
1236
+ hintProbed = /* @__PURE__ */ new Set();
1237
+ /**
1238
+ * Fork-child display titles healed off the wire (2026-09-14, host gap
1239
+ * DSI-side): the host's session.list serves NO projections column for a
1240
+ * cold seeded (fork child) row — session-controller list.ts
1241
+ * projectionsFor short-circuits isSeeded headers because the projection
1242
+ * cache is keyed by the inherited cut a listed header does not carry.
1243
+ * The real title (what the panel shows once the session attaches) lives
1244
+ * in the child's ledger: a child that ran its own turns carries a NEW
1245
+ * session/title event near the tail, so ONE bounded tail-page read per
1246
+ * untitled fork row recovers it. The result caches for the process
1247
+ * lifetime (a retitle lands through the normal rename/live paths, which
1248
+ * serve the title directly). The parent row's title is the FALLBACK for
1249
+ * children the tail page shows never retitled — the inherited title IS
1250
+ * the parent's at fork time; stale only when the parent was renamed
1251
+ * after the fork. Absent hints and absent parents leave the row
1252
+ * honestly untitled.
1253
+ */
1254
+ forkTitleHints = /* @__PURE__ */ new Map();
1255
+ forkTitleInFlight = /* @__PURE__ */ new Set();
1256
+ /** Subscribe to catalog invalidations ($events arms + socket reset). */
1257
+ onCatalogInvalidated(listener) {
1258
+ this.catalogInvalidated.add(listener);
1259
+ return () => this.catalogInvalidated.delete(listener);
1260
+ }
1261
+ fireCatalogInvalidated(scope) {
1262
+ for (const listener of this.catalogInvalidated) listener(scope);
1263
+ }
1264
+ /** approval/requested | question/requested → registry (idempotent by rpcId). */
1265
+ registerPending(frame, rpcId) {
1266
+ const sessionId = typeof frame.sessionId === "string" ? frame.sessionId : "";
1267
+ if (!sessionId) {
1268
+ this.conn.log("dsh-connection: answerable frame without sessionId — ignored", frame?.type);
1269
+ return;
1270
+ }
1271
+ if (typeof rpcId !== "string" || rpcId.length === 0) {
1272
+ this.conn.log("dsh-connection: answerable frame without rpcId — ignored", frame?.type);
1273
+ return;
1274
+ }
1275
+ const kind = frame.type === "approval/requested" ? "approval" : "question";
1276
+ this.pendingAnswers.set(rpcId, {
1277
+ rpcId,
1278
+ sessionId,
1279
+ kind,
1280
+ body: frame,
1281
+ receivedAt: Date.now(),
1282
+ deliveredGen: this.conn.downlinks.generation
1283
+ });
1284
+ this.conn.log(`dsh-connection: pending ${kind} registered (${rpcId}) for ${sessionId}`);
1285
+ }
1286
+ /**
1287
+ * Withdraw one pending answer (0.1.2 $events settle/cancel): remove the
1288
+ * registry entry and record the outcome in the settlement ring. Unknown
1289
+ * ids settle nothing (answered before we subscribed — honest no-op).
1290
+ */
1291
+ withdrawPending(eventId, outcome) {
1292
+ const pending = this.pendingAnswers.get(eventId);
1293
+ if (pending === void 0) {
1294
+ this.conn.log(`dsh-connection: settlement with no matching pending (${eventId}) — ignored`);
1295
+ return;
1296
+ }
1297
+ this.pendingAnswers.delete(eventId);
1298
+ this.settledAnswers.set(eventId, {
1299
+ rpcId: eventId,
1300
+ sessionId: pending.sessionId,
1301
+ kind: pending.kind,
1302
+ outcome: outcome ?? "cancelled",
1303
+ settledAt: Date.now()
1304
+ });
1305
+ this.conn.log(`dsh-connection: ${pending.kind} settled (${eventId}) outcome=${outcome ?? "cancelled"}`);
1148
1306
  }
1149
1307
  /**
1150
1308
  * Schedule the still-pending replay reap for this generation (ready
@@ -1163,8 +1321,8 @@ var DshConnection = class {
1163
1321
  /** Withdraw every pending answer not re-delivered by the current generation. */
1164
1322
  reapUnconfirmedPending() {
1165
1323
  for (const [eventId, pending] of [...this.pendingAnswers]) {
1166
- if (pending.deliveredGen >= this.generation) continue;
1167
- this.log(`dsh-connection: reap unconfirmed pending (${eventId}) from generation ${pending.deliveredGen} — not in generation ${this.generation} replay`);
1324
+ if (pending.deliveredGen >= this.conn.downlinks.generation) continue;
1325
+ this.conn.log(`dsh-connection: reap unconfirmed pending (${eventId}) from generation ${pending.deliveredGen} — not in generation ${this.conn.downlinks.generation} replay`);
1168
1326
  this.withdrawPending(eventId);
1169
1327
  }
1170
1328
  }
@@ -1182,7 +1340,7 @@ var DshConnection = class {
1182
1340
  registerSettlement(frame) {
1183
1341
  const sessionId = typeof frame.sessionId === "string" ? frame.sessionId : "";
1184
1342
  if (!sessionId) {
1185
- this.log("dsh-connection: settlement frame without sessionId — ignored", frame?.type);
1343
+ this.conn.log("dsh-connection: settlement frame without sessionId — ignored", frame?.type);
1186
1344
  return;
1187
1345
  }
1188
1346
  const kind = frame.type === "approval/resolved" ? "approval" : "question";
@@ -1199,8 +1357,8 @@ var DshConnection = class {
1199
1357
  outcome,
1200
1358
  settledAt: Date.now()
1201
1359
  });
1202
- this.log(`dsh-connection: ${kind} settled (${rpcId}) outcome=${outcome}`);
1203
- } else this.log(`dsh-connection: settlement with no matching pending (${frame?.type}) — ignored`);
1360
+ this.conn.log(`dsh-connection: ${kind} settled (${rpcId}) outcome=${outcome}`);
1361
+ } else this.conn.log(`dsh-connection: settlement with no matching pending (${frame?.type}) — ignored`);
1204
1362
  }
1205
1363
  /** Pending answers for a session (for the PollResponse extension). */
1206
1364
  pendingFor(sessionId) {
@@ -1217,25 +1375,6 @@ var DshConnection = class {
1217
1375
  if (s && s.sessionId === sessionId) this.settledAnswers.delete(id);
1218
1376
  }
1219
1377
  }
1220
- /**
1221
- * Fork-child display titles healed off the wire (2026-09-14, host gap
1222
- * DSI-side): the host's session.list serves NO projections column for a
1223
- * cold seeded (fork child) row — session-controller list.ts
1224
- * projectionsFor short-circuits isSeeded headers because the projection
1225
- * cache is keyed by the inherited cut a listed header does not carry.
1226
- * The real title (what the panel shows once the session attaches) lives
1227
- * in the child's ledger: a child that ran its own turns carries a NEW
1228
- * session/title event near the tail, so ONE bounded tail-page read per
1229
- * untitled fork row recovers it. The result caches for the process
1230
- * lifetime (a retitle lands through the normal rename/live paths, which
1231
- * serve the title directly). The parent row's title is the FALLBACK for
1232
- * children the tail page shows never retitled — the inherited title IS
1233
- * the parent's at fork time; stale only when the parent was renamed
1234
- * after the fork. Absent hints and absent parents leave the row
1235
- * honestly untitled.
1236
- */
1237
- forkTitleHints = /* @__PURE__ */ new Map();
1238
- forkTitleInFlight = /* @__PURE__ */ new Set();
1239
1378
  async listSessions() {
1240
1379
  const items = (await this.rawListSessions()).map(normalizeSessionRow);
1241
1380
  const titleById = new Map(items.map((s) => [s.sessionId, s.title]));
@@ -1272,10 +1411,10 @@ var DshConnection = class {
1272
1411
  */
1273
1412
  async healForkTitle(sessionId) {
1274
1413
  try {
1275
- const title = (await this.waitForProjections(sessionId))?.values?.["title"];
1414
+ const title = (await this.conn.downlinks.waitForProjections(sessionId))?.values?.["title"];
1276
1415
  if (typeof title === "string" && title.length > 0) this.forkTitleHints.set(sessionId, title);
1277
1416
  } catch (error) {
1278
- this.log("dsh-connection: fork-title follow-baseline read failed for " + sessionId, error);
1417
+ this.conn.log("dsh-connection: fork-title follow-baseline read failed for " + sessionId, error);
1279
1418
  } finally {
1280
1419
  this.forkTitleInFlight.delete(sessionId);
1281
1420
  }
@@ -1303,7 +1442,7 @@ var DshConnection = class {
1303
1442
  }
1304
1443
  /** Raw session/list rows (0.1.2) — projections.asOfSeq drives page calls. */
1305
1444
  async rawListSessions() {
1306
- const raw = await this.rpc(DSH_METHODS.list, DSH_ARGS.list());
1445
+ const raw = await this.conn.rpc(DSH_METHODS.list, DSH_ARGS.list());
1307
1446
  const rows = Array.isArray(raw?.items) ? raw.items : [];
1308
1447
  for (const row of rows) this.foldSubagentHint(row);
1309
1448
  return rows;
@@ -1371,7 +1510,7 @@ var DshConnection = class {
1371
1510
  * "no served cursor", not "below the ledger head".
1372
1511
  */
1373
1512
  async pageCursorFor(sessionId) {
1374
- const live = this.bufferFor(sessionId).lastSeq;
1513
+ const live = this.conn.sessions.bufferFor(sessionId).lastSeq;
1375
1514
  if (live >= 0) return live;
1376
1515
  const throughSeq = (await this.rawListSessions()).find((r) => r.sessionId === sessionId)?.projections?.asOfSeq;
1377
1516
  if (typeof throughSeq === "number" && throughSeq >= 0) return throughSeq;
@@ -1385,7 +1524,7 @@ var DshConnection = class {
1385
1524
  */
1386
1525
  async history(sessionId) {
1387
1526
  const throughSeq = await this.pageCursorFor(sessionId);
1388
- return this.pageAsHistory(await this.rpc(DSH_METHODS.page, DSH_ARGS.page({
1527
+ return this.pageAsHistory(await this.conn.rpc(DSH_METHODS.page, DSH_ARGS.page({
1389
1528
  address: this.addressFor(sessionId),
1390
1529
  throughSeq
1391
1530
  })));
@@ -1394,37 +1533,21 @@ var DshConnection = class {
1394
1533
  * workspace button's host call. Raw view passthrough — DSI renders
1395
1534
  * only title/path; ordering and session membership stay host-owned. */
1396
1535
  async createWorkspace(path) {
1397
- return await this.rpc(DSH_METHODS.workspaceCreate, DSH_ARGS.workspaceCreate({ path }));
1536
+ return await this.conn.rpc(DSH_METHODS.workspaceCreate, DSH_ARGS.workspaceCreate({ path }));
1398
1537
  }
1399
1538
  /** Workspace rename (Chip Menu ADR D3): {request:{workspaceId,title}}
1400
1539
  * → the updated Workspace view. Refusals throw DshRpcError
1401
1540
  * (workspace/not-found, workspace/name-conflict). No cache mutation —
1402
1541
  * the workspace/follow upsert frame owns the registry (ADR D6). */
1403
1542
  async renameWorkspace(workspaceId, title) {
1404
- return await this.rpc(DSH_METHODS.workspaceRename, DSH_ARGS.workspaceRename(workspaceId, title));
1543
+ return await this.conn.rpc(DSH_METHODS.workspaceRename, DSH_ARGS.workspaceRename(workspaceId, title));
1405
1544
  }
1406
1545
  /** Workspace delete (Chip Menu ADR D3): {request:{workspaceId}} → the
1407
1546
  * removed id. The host removes ONLY the registry row (sessions survive,
1408
1547
  * ungrouped); the workspace/follow remove frame owns the registry
1409
1548
  * (ADR D6) — no local mutation here. */
1410
1549
  async deleteWorkspace(workspaceId) {
1411
- return await this.rpc(DSH_METHODS.workspaceDelete, DSH_ARGS.workspaceDelete(workspaceId));
1412
- }
1413
- /** Goal Bar (2026-09-08, ADR amendment): one goal mutation through the
1414
- * host's Goal Remotes — goals/pause | goals/resume | goals/clear |
1415
- * goals/edit with the CAS ref {id, revision} (a stale revision loses
1416
- * host-side). goals/edit additionally carries the changed-fields
1417
- * request {objective?, maxGoalRounds?} (Goal Editor, 2026-09-09). The
1418
- * raw RemoteResult passes through; the route maps refusals. */
1419
- async goalVerb(verb, sessionId, ref, request) {
1420
- return this.rpc(`goals/${verb}`, request === void 0 ? {
1421
- agentId: sessionId,
1422
- ref
1423
- } : {
1424
- agentId: sessionId,
1425
- ref,
1426
- request
1427
- });
1550
+ return await this.conn.rpc(DSH_METHODS.workspaceDelete, DSH_ARGS.workspaceDelete(workspaceId));
1428
1551
  }
1429
1552
  /**
1430
1553
  * workspaceFiles/read — one page of a UTF-8 text file inside the
@@ -1440,22 +1563,22 @@ var DshConnection = class {
1440
1563
  * @param range - line window; omitted fields take the host's page defaults.
1441
1564
  */
1442
1565
  async readWorkspaceFile(sessionId, path, range) {
1443
- return await this.rpc(DSH_METHODS.workspaceFileRead, DSH_ARGS.workspaceFileRead(sessionId, path, range));
1566
+ return await this.conn.rpc(DSH_METHODS.workspaceFileRead, DSH_ARGS.workspaceFileRead(sessionId, path, range));
1444
1567
  }
1445
1568
  /**
1446
1569
  * workspaceFiles/readBytes — one byte window of a workspace file, raw
1447
1570
  * bytes (base64 receipt), no text decoding and no binary refusal
1448
- * (2026-09-10: binary previews such as images read through this; `read`
1571
+ * (2026-09-10: binary previews such as images read through this; read
1449
1572
  * refuses them workspace-file/not-text).
1450
1573
  *
1451
1574
  * @param sessionId - owning session; the host confines the read to its
1452
- workspace root by containment.
1575
+ * workspace root by containment.
1453
1576
  * @param path - workspace path, absolute or root-relative.
1454
1577
  * @param range - byte window {offset, length}; omitted fields take the
1455
- host's window defaults.
1578
+ * host's window defaults.
1456
1579
  */
1457
1580
  async readWorkspaceFileBytes(sessionId, path, range) {
1458
- return await this.rpc(DSH_METHODS.workspaceFileReadBytes, DSH_ARGS.workspaceFileReadBytes(sessionId, path, range));
1581
+ return await this.conn.rpc(DSH_METHODS.workspaceFileReadBytes, DSH_ARGS.workspaceFileReadBytes(sessionId, path, range));
1459
1582
  }
1460
1583
  /**
1461
1584
  * workspaceFiles/list — one directory level inside the session's
@@ -1465,15 +1588,7 @@ var DshConnection = class {
1465
1588
  * {workspaceFileScopeId, path}; path is workspace-relative, '' = the root.
1466
1589
  */
1467
1590
  async listWorkspaceDirectory(sessionId, path) {
1468
- return await this.rpc(DSH_METHODS.workspaceFileList, DSH_ARGS.workspaceFileList(sessionId, path));
1469
- }
1470
- /** The workspace registry (2026-08-23 registry parity) — the AUTHORITY
1471
- * for DSI's workspace pills/chips: a deleted workspace disappears, and
1472
- * sessions outside every registry entry render cue-less (DSH's
1473
- * Ungrouped). Membership is sessionIds, not cwd matching. */
1474
- async listWorkspaces() {
1475
- if (!await this.awaitWorkspaceBaseline()) this.log("dsh-connection: workspace baseline did not land in time — honest empty");
1476
- return { items: [...this.workspaces] };
1591
+ return await this.conn.rpc(DSH_METHODS.workspaceFileList, DSH_ARGS.workspaceFileList(sessionId, path));
1477
1592
  }
1478
1593
  /**
1479
1594
  * host.listDirectory — one browsed directory level for the Add-workspace
@@ -1482,7 +1597,7 @@ var DshConnection = class {
1482
1597
  * garbage rows drop out, never throw, never invent data.
1483
1598
  */
1484
1599
  async listDirectory(path) {
1485
- const raw = await this.rpc(DSH_METHODS.listDirectory, DSH_ARGS.listDirectory(path));
1600
+ const raw = await this.conn.rpc(DSH_METHODS.listDirectory, DSH_ARGS.listDirectory(path));
1486
1601
  const entry = (v) => {
1487
1602
  if (v === null || typeof v !== "object") return null;
1488
1603
  const e = v;
@@ -1521,13 +1636,13 @@ var DshConnection = class {
1521
1636
  * @param attachmentId - durable content-addressed id from an ImageBlock.
1522
1637
  */
1523
1638
  async readAttachment(sessionId, attachmentId) {
1524
- return await this.rpc(DSH_METHODS.attachment, DSH_ARGS.attachment({
1639
+ return await this.conn.rpc(DSH_METHODS.attachment, DSH_ARGS.attachment({
1525
1640
  sessionId,
1526
1641
  attachmentId
1527
1642
  }));
1528
1643
  }
1529
1644
  async pickDirectory(signal) {
1530
- const raw = await this.rpc(DSH_METHODS.pickDirectory, DSH_ARGS.pickDirectory(), signal);
1645
+ const raw = await this.conn.rpc(DSH_METHODS.pickDirectory, DSH_ARGS.pickDirectory(), signal);
1531
1646
  return { path: typeof raw === "string" && raw.length > 0 ? raw : null };
1532
1647
  }
1533
1648
  /**
@@ -1538,7 +1653,7 @@ var DshConnection = class {
1538
1653
  */
1539
1654
  async historyPage(sessionId, beforeSeq) {
1540
1655
  const throughSeq = await this.pageCursorFor(sessionId);
1541
- return this.pageAsHistory(await this.rpc(DSH_METHODS.page, DSH_ARGS.page({
1656
+ return this.pageAsHistory(await this.conn.rpc(DSH_METHODS.page, DSH_ARGS.page({
1542
1657
  address: this.addressFor(sessionId),
1543
1658
  throughSeq,
1544
1659
  beforeSeq
@@ -1558,7 +1673,7 @@ var DshConnection = class {
1558
1673
  * Live-probed round-trip 2026-08-21 (probe7 → real session id).
1559
1674
  */
1560
1675
  async createSession(cwd, agentPreset) {
1561
- return await this.rpc(DSH_METHODS.create, DSH_ARGS.create({
1676
+ return await this.conn.rpc(DSH_METHODS.create, DSH_ARGS.create({
1562
1677
  ...cwd === void 0 ? {} : { cwd },
1563
1678
  ...agentPreset === void 0 ? {} : { agentPreset }
1564
1679
  }));
@@ -1571,7 +1686,7 @@ var DshConnection = class {
1571
1686
  * contract and refusals).
1572
1687
  */
1573
1688
  async forkSession(sessionId, atSeq) {
1574
- return await this.rpc(DSH_METHODS.fork, DSH_ARGS.fork({
1689
+ return await this.conn.rpc(DSH_METHODS.fork, DSH_ARGS.fork({
1575
1690
  sessionId,
1576
1691
  ...atSeq === void 0 ? {} : { atSeq }
1577
1692
  }));
@@ -1585,7 +1700,7 @@ var DshConnection = class {
1585
1700
  */
1586
1701
  async listPresets() {
1587
1702
  const overlayEnglish = readDshPresetEnglishConfig().presetEnglish;
1588
- const raw = await this.rpc(DSH_METHODS.presets, DSH_ARGS.presets());
1703
+ const raw = await this.conn.rpc(DSH_METHODS.presets, DSH_ARGS.presets());
1589
1704
  return { presets: (Array.isArray(raw?.presets) ? raw.presets : []).map((row) => normalizePreset(row, overlayEnglish)) };
1590
1705
  }
1591
1706
  /**
@@ -1600,8 +1715,8 @@ var DshConnection = class {
1600
1715
  type: "text",
1601
1716
  text: payload
1602
1717
  }] : payload;
1603
- return await this.rpc(DSH_METHODS.prompt, DSH_ARGS.prompt({
1604
- requestId: this.rpcIdFactory(),
1718
+ return await this.conn.rpc(DSH_METHODS.prompt, DSH_ARGS.prompt({
1719
+ requestId: this.conn.rpcIdFactory(),
1605
1720
  sessionId,
1606
1721
  mode,
1607
1722
  content,
@@ -1619,7 +1734,7 @@ var DshConnection = class {
1619
1734
  * receipt callers must distinguish from a success.
1620
1735
  */
1621
1736
  async executeCommand(sessionId, line) {
1622
- return await this.rpc(DSH_METHODS.commandExecute, DSH_ARGS.commandExecute(sessionId, line, [])) ?? null;
1737
+ return await this.conn.rpc(DSH_METHODS.commandExecute, DSH_ARGS.commandExecute(sessionId, line, [])) ?? null;
1623
1738
  }
1624
1739
  /**
1625
1740
  * commands/list (Slash Menu W1, task 1.2) — the host's command catalog,
@@ -1629,7 +1744,7 @@ var DshConnection = class {
1629
1744
  * open, never on panel mount (ADR §3.3).
1630
1745
  */
1631
1746
  async listCommands(sessionId) {
1632
- return await this.rpc(DSH_METHODS.commandsList, DSH_ARGS.commandsList(sessionId));
1747
+ return await this.conn.rpc(DSH_METHODS.commandsList, DSH_ARGS.commandsList(sessionId));
1633
1748
  }
1634
1749
  /**
1635
1750
  * skills/list (Slash Menu W1, task 1.2) — the session's user-invocable
@@ -1638,11 +1753,11 @@ var DshConnection = class {
1638
1753
  * read it freely on first open.
1639
1754
  */
1640
1755
  async listSkills(sessionId) {
1641
- return await this.rpc(DSH_METHODS.skillsList, DSH_ARGS.skillsList(sessionId));
1756
+ return await this.conn.rpc(DSH_METHODS.skillsList, DSH_ARGS.skillsList(sessionId));
1642
1757
  }
1643
1758
  /** session.cancel */
1644
1759
  async cancel(sessionId) {
1645
- return await this.rpc(DSH_METHODS.cancel, DSH_ARGS.cancel({ sessionId }));
1760
+ return await this.conn.rpc(DSH_METHODS.cancel, DSH_ARGS.cancel({ sessionId }));
1646
1761
  }
1647
1762
  /**
1648
1763
  * POC-3 W3 (3.1) — session.rename {sessionId, title}.
@@ -1653,7 +1768,7 @@ var DshConnection = class {
1653
1768
  * RpcError → 502 upstream). Session-backed subagents reject `agent-busy`.
1654
1769
  */
1655
1770
  async renameSession(sessionId, title) {
1656
- return await this.rpc(DSH_METHODS.rename, DSH_ARGS.rename({
1771
+ return await this.conn.rpc(DSH_METHODS.rename, DSH_ARGS.rename({
1657
1772
  sessionId,
1658
1773
  title
1659
1774
  }));
@@ -1667,7 +1782,7 @@ var DshConnection = class {
1667
1782
  * below never throws (task 3.2-T: garbage catalog → empty, not an error).
1668
1783
  */
1669
1784
  async listModels(_sessionId) {
1670
- return normalizeModelDirectory(await this.rpc(DSH_METHODS.modelCatalog, DSH_ARGS.modelCatalog()));
1785
+ return normalizeModelDirectory(await this.conn.rpc(DSH_METHODS.modelCatalog, DSH_ARGS.modelCatalog()));
1671
1786
  }
1672
1787
  /**
1673
1788
  * POC-3 W3 (3.2) — session.selectModel {sessionId, provider, model,
@@ -1676,7 +1791,7 @@ var DshConnection = class {
1676
1791
  * effort. Subagents reject `session/agent-busy` (RpcError → 502 upstream).
1677
1792
  */
1678
1793
  async selectModel(sessionId, provider, model, reasoningEffort) {
1679
- return normalizeModelSelection((await this.rpc(DSH_METHODS.selectModel, DSH_ARGS.selectModel({
1794
+ return normalizeModelSelection((await this.conn.rpc(DSH_METHODS.selectModel, DSH_ARGS.selectModel({
1680
1795
  sessionId,
1681
1796
  provider,
1682
1797
  model,
@@ -1705,7 +1820,7 @@ var DshConnection = class {
1705
1820
  * the wire carries the host's contract, not the panel's convenience.
1706
1821
  */
1707
1822
  async respond(rpcId, payload) {
1708
- if (this.eventClientId === null) throw new Error("dsh-connection: no $events generation to answer through yet");
1823
+ if (this.conn.downlinks.eventClientId === null) throw new Error("dsh-connection: no $events generation to answer through yet");
1709
1824
  const { sessionId, ...rest } = payload;
1710
1825
  const value = rest.outcome !== void 0 ? rest.outcome : rest.answer !== void 0 ? rest.answer : rest.answers !== void 0 ? { answers: rest.answers } : rest;
1711
1826
  try {
@@ -1717,7 +1832,7 @@ var DshConnection = class {
1717
1832
  reason: "not-pending"
1718
1833
  };
1719
1834
  if (err instanceof DshRpcError && err.code === "gateway/internal" && err.message.includes("no active event stream")) {
1720
- const previous = this.eventClientId;
1835
+ const previous = this.conn.downlinks.eventClientId;
1721
1836
  if (previous !== null && await this.refreshEventGeneration(previous)) {
1722
1837
  await this.sendEventResult(rpcId, value);
1723
1838
  this.forgetAnswered(rpcId, value);
@@ -1736,8 +1851,8 @@ var DshConnection = class {
1736
1851
  */
1737
1852
  async fetchHostPath(path, signal) {
1738
1853
  const attempt = async () => {
1739
- const cookie = await this.auth.ensureCookie();
1740
- return this.fetchFn(`${this.baseUrl}${path}`, {
1854
+ const cookie = await this.conn.auth.ensureCookie();
1855
+ return this.conn.fetchFn(`${this.conn.baseUrl}${path}`, {
1741
1856
  method: "GET",
1742
1857
  headers: cookie === null ? {} : { cookie },
1743
1858
  ...signal !== void 0 ? { signal } : {}
@@ -1745,15 +1860,15 @@ var DshConnection = class {
1745
1860
  };
1746
1861
  let response = await attempt();
1747
1862
  if (response.status === 401) {
1748
- this.auth.invalidate();
1863
+ this.conn.auth.invalidate();
1749
1864
  response = await attempt();
1750
1865
  }
1751
1866
  return response;
1752
1867
  }
1753
1868
  /** One $events/result answer under the CURRENT generation's clientId. */
1754
1869
  sendEventResult(rpcId, value) {
1755
- return this.rpc(REMOTE_EVENT_RESULT_ENDPOINT, {
1756
- clientId: this.eventClientId,
1870
+ return this.conn.rpc(REMOTE_EVENT_RESULT_ENDPOINT, {
1871
+ clientId: this.conn.downlinks.eventClientId,
1757
1872
  eventId: rpcId,
1758
1873
  outcome: {
1759
1874
  kind: "result",
@@ -1768,16 +1883,13 @@ var DshConnection = class {
1768
1883
  * caller may retry an answer against it.
1769
1884
  */
1770
1885
  async refreshEventGeneration(previousClientId) {
1771
- this.log("dsh-connection: $events generation stale — rebuilding for the answer retry");
1772
- if (this.reconnectTimer !== void 0) {
1773
- clearTimeout(this.reconnectTimer);
1774
- this.reconnectTimer = void 0;
1775
- }
1776
- this.teardownSockets();
1777
- this.ensureDownlinks();
1886
+ this.conn.log("dsh-connection: $events generation stale — rebuilding for the answer retry");
1887
+ this.conn.downlinks.cancelReconnect();
1888
+ this.conn.downlinks.teardownSockets();
1889
+ this.conn.downlinks.ensureDownlinks();
1778
1890
  const deadline = Date.now() + this.staleGenerationRetryMs;
1779
- while (!this.disposed && this.eventClientId === previousClientId && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 25));
1780
- return this.eventClientId !== previousClientId && this.eventClientId !== null;
1891
+ while (!this.conn.disposed && this.conn.downlinks.eventClientId === previousClientId && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 25));
1892
+ return this.conn.downlinks.eventClientId !== previousClientId && this.conn.downlinks.eventClientId !== null;
1781
1893
  }
1782
1894
  /**
1783
1895
  * Resync: replace the ring buffer with the ledger's tail page (BC-4).
@@ -1785,12 +1897,166 @@ var DshConnection = class {
1785
1897
  */
1786
1898
  async resyncFromLedger(sessionId) {
1787
1899
  const events = ((await this.history(sessionId)).events ?? []).map((h) => h.event);
1788
- const buf = this.bufferFor(sessionId);
1789
- buf.entries = [...events].slice(-ringCapacity());
1900
+ const buf = this.conn.sessions.bufferFor(sessionId);
1901
+ buf.entries = [...events].slice(-this.ringCapacity());
1790
1902
  buf.lastSeq = events.length > 0 ? events[events.length - 1].seq : -1;
1791
1903
  buf.coverageFloor = buf.entries[0]?.seq ?? buf.lastSeq;
1792
1904
  return buf.entries;
1793
1905
  }
1906
+ /** Teardown hook: transient registries never leak entries. */
1907
+ dispose() {
1908
+ clearTimeout(this.answerReapTimer);
1909
+ this.pendingAnswers.clear();
1910
+ this.settledAnswers.clear();
1911
+ }
1912
+ };
1913
+ //#endregion
1914
+ //#region src/lib/server/dsh-connection.ts
1915
+ /**
1916
+ * dsh-connection — the transport singleton (BC-5) owning every wire byte to
1917
+ * the DSH Host (BC-6: it speaks only through dsh-rpc helpers and the two
1918
+ * downlink WebSocket paths).
1919
+ *
1920
+ * State model (PRD §3.5):
1921
+ * mux frames (session/subscribed, session/event) → per-session ring buffer
1922
+ * host frames (host/session-status) → per-session running map
1923
+ * running map re-baselines from session.list at every generation open
1924
+ * (2026-08-26): the host pushes status only on TRANSITIONS and sends no
1925
+ * initial snapshot, so a downlink rebuilt mid-turn would otherwise never
1926
+ * learn the session was already running.
1927
+ * ring buffer = freshness only; the ledger (session.history) is the truth —
1928
+ * cold loads and every resync re-read the ledger (BC-4).
1929
+ *
1930
+ * Generation semantics (guide Part 1): the two downlinks form one generation;
1931
+ * if either dies, both are rebuilt after backoff.
1932
+ */
1933
+ /** Live capacity from ~/.dsi/settings.yaml (no cache — runtime edits
1934
+ * apply, same contract as every other config section). */
1935
+ function ringCapacity() {
1936
+ return readServerConfig().ringCapacity;
1937
+ }
1938
+ var singleton;
1939
+ /** The process-wide transport (BC-5). Creates lazily on first use. */
1940
+ function getDshConnection(opts = {}) {
1941
+ singleton ??= new DshConnection(opts);
1942
+ return singleton;
1943
+ }
1944
+ var DshConnection = class {
1945
+ baseUrl;
1946
+ fetchFn;
1947
+ wsFactory;
1948
+ auth;
1949
+ rpcIdFactory;
1950
+ backoff;
1951
+ log;
1952
+ /**
1953
+ * ADR-0022 W2 (move-whole): the session state machines — ring buffers,
1954
+ * running map + seed races, live assistant-stream tails — live in
1955
+ * connection/session-state.ts; the shell delegates.
1956
+ */
1957
+ sessions;
1958
+ /** ADR-0022 W3 (move-whole): the downlink generation lifecycle + frame
1959
+ * demux live in connection/downlinks.ts; the shell delegates. */
1960
+ downlinks;
1961
+ /** ADR-0022 W4 (move-whole): the RPC relay + answer registry live in
1962
+ * connection/rpc-relay.ts; the shell delegates. */
1963
+ relay;
1964
+ disposed = false;
1965
+ constructor(opts = {}) {
1966
+ this.baseUrl = opts.baseUrl ?? dshBaseUrl();
1967
+ this.fetchFn = opts.fetchFn ?? fetch;
1968
+ this.wsFactory = opts.wsFactory ?? ((url, headers) => new WebSocket(url, headers === void 0 ? void 0 : { headers }));
1969
+ this.auth = opts.auth ?? new DshAuth({
1970
+ baseUrl: this.baseUrl,
1971
+ log: opts.log
1972
+ });
1973
+ this.rpcIdFactory = opts.rpcIdFactory ?? (() => crypto.randomUUID());
1974
+ this.backoff = opts.backoffScheduleMs ?? [
1975
+ 1e3,
1976
+ 2e3,
1977
+ 5e3,
1978
+ 1e4,
1979
+ 3e4
1980
+ ];
1981
+ const answerReapGraceMs = opts.answerReapGraceMs ?? 3e3;
1982
+ const staleGenerationRetryMs = opts.staleGenerationRetryMs ?? 5e3;
1983
+ this.log = opts.log ?? (() => {});
1984
+ this.sessions = new SessionStateStore({
1985
+ log: this.log,
1986
+ ringCapacity,
1987
+ listSessions: () => this.listSessions(),
1988
+ generation: () => this.downlinks.generation,
1989
+ disposed: () => this.disposed
1990
+ });
1991
+ this.downlinks = new DownlinkManager(this);
1992
+ this.relay = new RpcRelay(this, ringCapacity, {
1993
+ answerReapGraceMs,
1994
+ staleGenerationRetryMs
1995
+ });
1996
+ }
1997
+ /** Open the stream generation (idempotent) — see DownlinkManager. */
1998
+ ensureDownlinks() {
1999
+ this.downlinks.ensureDownlinks();
2000
+ }
2001
+ /** Subscribe one session (0.1.2) — see DownlinkManager. */
2002
+ subscribe(sessionId) {
2003
+ this.downlinks.subscribe(sessionId);
2004
+ }
2005
+ /** Per-session projection value (W2) — see DownlinkManager. */
2006
+ projectionValue(sessionId, key) {
2007
+ return this.downlinks.projectionValue(sessionId, key);
2008
+ }
2009
+ /** The whole stored block for one session (null when absent). */
2010
+ projectionBlock(sessionId) {
2011
+ return this.downlinks.projectionBlock(sessionId);
2012
+ }
2013
+ /** Bounded wait for the session's FIRST projection baseline. */
2014
+ async waitForProjections(sessionId, timeoutMs = 2500) {
2015
+ return this.downlinks.waitForProjections(sessionId, timeoutMs);
2016
+ }
2017
+ /** The workspace registry — the AUTHORITY for DSI's workspace pills/chips. */
2018
+ async listWorkspaces() {
2019
+ return this.downlinks.listWorkspaces();
2020
+ }
2021
+ onMuxFrame(payload, rpcId) {
2022
+ this.downlinks.reconnectAttempt = 0;
2023
+ const frame = payload;
2024
+ switch (frame?.type) {
2025
+ case "session/subscribed":
2026
+ if (typeof frame.sessionId === "string") this.sessions.seedBaseline(frame.sessionId, frame.lastSeq ?? -1);
2027
+ return;
2028
+ case "session/event":
2029
+ if (frame.sessionId && frame.event) this.sessions.appendEvent(frame.sessionId, frame.event);
2030
+ return;
2031
+ case "approval/requested":
2032
+ case "question/requested":
2033
+ this.relay.registerPending(frame, rpcId);
2034
+ return;
2035
+ case "approval/resolved":
2036
+ case "question/resolved":
2037
+ this.relay.registerSettlement(frame);
2038
+ return;
2039
+ default: this.log("dsh-connection: mux frame ignored", frame?.type);
2040
+ }
2041
+ }
2042
+ /** The session's live tail for the events poll, or null when no attempt
2043
+ * is in flight (0.1.3-alpha.1 live assistant-stream tail). */
2044
+ liveAssistantStream(sessionId) {
2045
+ return this.sessions.liveAssistantStream(sessionId);
2046
+ }
2047
+ /** Delta slice: events with seq > n, plus current lastSeq/running. Wave 4.2:
2048
+ * the gap flag is true when the caller's since mark fell into a hole the
2049
+ * buffer no longer covers (mux drop) — the response carries the resync flag. */
2050
+ eventsSince(sessionId, n) {
2051
+ return this.sessions.eventsSince(sessionId, n);
2052
+ }
2053
+ /** True when the session has a live baseline (subscribed at least once). */
2054
+ hasBaseline(sessionId) {
2055
+ return this.sessions.hasBaseline(sessionId);
2056
+ }
2057
+ isRunning(sessionId) {
2058
+ return this.sessions.isRunning(sessionId);
2059
+ }
1794
2060
  async rpc(method, payload, signal) {
1795
2061
  const body = JSON.stringify({
1796
2062
  type: "client-request",
@@ -1820,220 +2086,161 @@ var DshConnection = class {
1820
2086
  const rpcId = JSON.parse(body).rpcId;
1821
2087
  return parseResponse(await response.text(), rpcId);
1822
2088
  }
1823
- dispose() {
1824
- this.disposed = true;
1825
- clearTimeout(this.answerReapTimer);
1826
- this.teardownSockets();
1827
- this.pendingAnswers.clear();
1828
- this.settledAnswers.clear();
1829
- this.buffers.clear();
1830
- this.liveTails.clear();
1831
- this.running.clear();
2089
+ /** Subscribe to catalog invalidations ($events arms + socket reset). */
2090
+ onCatalogInvalidated(listener) {
2091
+ return this.relay.onCatalogInvalidated(listener);
1832
2092
  }
1833
- };
1834
- /** The follow-<sessionId>-<streamSeq> logical stream id (one per generation). */
1835
- function followStreamId(sessionId, streamSeq) {
1836
- return `follow-${sessionId}-${String(streamSeq)}`;
1837
- }
1838
- /**
1839
- * Expand ONE follow/page event payload into its ring-buffer form
1840
- * (single-element list — ledger v2 serves every record as a plain event).
1841
- */
1842
- function expandEntry(e) {
1843
- return [{
1844
- type: e.type,
1845
- seq: typeof e.seq === "number" ? e.seq : -1,
1846
- time: typeof e.time === "number" ? e.time : 0,
1847
- data: e.data
1848
- }];
1849
- }
1850
- /** Expand one page/snapshot record ({type:'event', event}) into its member
1851
- * events; a record without an event payload contributes nothing. */
1852
- function expandRecord(record) {
1853
- if (record === void 0 || record.event === void 0) return [];
1854
- return expandEntry(record.event);
1855
- }
1856
- /** Expand a records array into a flat event list. */
1857
- function expandRecords(records) {
1858
- return records.flatMap((record) => expandRecord(record));
1859
- }
1860
- /** The last member time of one compact stream record, or undefined when
1861
- * the record carries no readable time (malformed members stay silent —
1862
- * the baseline is presentation seed data, never re-validated truth). */
1863
- function compactRecordTailTime(record) {
1864
- if (record.type === "chunk") return typeof record.time === "number" ? record.time : void 0;
1865
- if (record.type === "text-chunks" || record.type === "reasoning-chunks" || record.type === "tool-call-chunks") {
1866
- const time0 = typeof record.time0 === "number" ? record.time0 : void 0;
1867
- const dt = Array.isArray(record.dt) ? record.dt : [];
1868
- if (time0 === void 0 || dt.some((g) => typeof g !== "number")) return void 0;
1869
- return dt.reduce((t, g) => t + g, time0);
2093
+ /** Pending answers for a session (PollResponse extension). */
2094
+ pendingFor(sessionId) {
2095
+ return this.relay.pendingFor(sessionId);
1870
2096
  }
1871
- }
1872
- function normalizeModelSelection(v) {
1873
- if (v === null || typeof v !== "object") return null;
1874
- const sel = v;
1875
- if (typeof sel.provider !== "string" || typeof sel.model !== "string") return null;
1876
- return {
1877
- provider: sel.provider,
1878
- model: sel.model,
1879
- ...typeof sel.reasoningEffort === "string" ? { reasoningEffort: sel.reasoningEffort } : {}
1880
- };
1881
- }
1882
- /**
1883
- * Conservative normalize of a SessionModels value: structural reads only,
1884
- * never throws, never invents data (task 3.2-T: a garbage catalog → empty
1885
- * groups + empty failures, honest `routable`/`current` — the selector can
1886
- * still show the current selection and say nothing is listed). Reasoning
1887
- * efforts are flattened to opaque ids (the adapter owns their meaning).
1888
- */
1889
- function normalizeModelDirectory(raw) {
1890
- const empty = {
1891
- current: null,
1892
- routable: false,
1893
- groups: [],
1894
- failures: []
1895
- };
1896
- if (raw === null || typeof raw !== "object") return empty;
1897
- const dir = raw;
1898
- const groups = Array.isArray(dir.groups) ? modelGroupsOf(dir.groups) : [];
1899
- const failures = Array.isArray(dir.failures) ? modelFailuresOf(dir.failures) : [];
1900
- if (dir.current === void 0 && dir.default !== void 0) return {
1901
- current: normalizeModelSelection(dir.default),
1902
- routable: Array.isArray(dir.routableProviders) ? dir.routableProviders.length > 0 || groups.length > 0 : true,
1903
- groups,
1904
- failures
1905
- };
1906
- return {
1907
- current: normalizeModelSelection(dir.current),
1908
- routable: dir.routable === true,
1909
- groups,
1910
- failures
1911
- };
1912
- }
1913
- /** ModelCatalog provider groups → DsiModelGroup rows (conservative). */
1914
- function modelGroupsOf(groups) {
1915
- return groups.flatMap((g) => {
1916
- if (g === null || typeof g !== "object") return [];
1917
- const grp = g;
1918
- if (typeof grp.id !== "string" || grp.id.length === 0) return [];
1919
- const models = Array.isArray(grp.models) ? grp.models.flatMap((m) => {
1920
- if (m === null || typeof m !== "object") return [];
1921
- const mod = m;
1922
- if (typeof mod.id !== "string" || mod.id.length === 0) return [];
1923
- return [{
1924
- id: mod.id,
1925
- name: typeof mod.name === "string" ? mod.name : null,
1926
- ...Array.isArray(mod.reasoning?.efforts) ? { reasoningEfforts: mod.reasoning.efforts.flatMap((e) => typeof e?.id === "string" ? [e.id] : []) } : {}
1927
- }];
1928
- }) : [];
1929
- return [{
1930
- id: grp.id,
1931
- name: typeof grp.name === "string" ? grp.name : null,
1932
- models
1933
- }];
1934
- });
1935
- }
1936
- /** ModelCatalog provider failures → diagnostic rows (conservative). */
1937
- function modelFailuresOf(failures) {
1938
- return failures.flatMap((f) => {
1939
- if (f === null || typeof f !== "object") return [];
1940
- const fail = f;
1941
- if (typeof fail.id !== "string" || fail.id.length === 0) return [];
1942
- return [{
1943
- id: fail.id,
1944
- name: typeof fail.name === "string" ? fail.name : null,
1945
- message: typeof fail.message === "string" ? fail.message : null
1946
- }];
1947
- });
1948
- }
1949
- function normalizeSessionRow(row) {
1950
- return {
1951
- sessionId: row.sessionId,
1952
- title: row.origin === "subagent" ? readSubagentLabel(row) ?? readTitle(row) : readTitle(row),
1953
- agentPreset: readAgentPreset(row) ?? row.agentPreset ?? null,
1954
- running: Boolean(row.running),
1955
- blank: Boolean(row.blank),
1956
- updatedAt: row.updatedAt ?? 0,
1957
- workspace: typeof row.cwd === "string" && row.cwd.length > 0 ? row.cwd : null,
1958
- turns: readTurns(row),
1959
- parentSessionId: row.parentSessionId ?? null,
1960
- origin: row.origin ?? null
1961
- };
1962
- }
1963
- function readTitle(row) {
1964
- const title = row.projections?.values?.["title"];
1965
- return typeof title === "string" && title.length > 0 ? title : null;
1966
- }
1967
- /** projections.values.subagent.label (the child descriptor's creation label;
1968
- * one-shot children may omit it — structural read, null when unreadable). */
1969
- function readSubagentLabel(row) {
1970
- const identity = row.projections?.values?.["subagent"];
1971
- const label = identity !== null && typeof identity === "object" ? identity.label : void 0;
1972
- return typeof label === "string" && label.length > 0 ? label : null;
1973
- }
1974
- /** projections.values.agentPreset (0.1.2 home of the preset id on list rows). */
1975
- function readAgentPreset(row) {
1976
- const preset = row.projections?.values?.["agentPreset"];
1977
- return typeof preset === "string" && preset.length > 0 ? preset : null;
1978
- }
1979
- /** `sessionStats.turns` (0.1.2: projections.values.sessionStats) — integer-strict, null on absent/junk. */
1980
- function readTurns(row) {
1981
- const stats = row.projections?.values?.["sessionStats"];
1982
- const turns = stats !== null && typeof stats === "object" ? stats.turns : row.sessionStats?.turns;
1983
- if (typeof turns !== "number" || !Number.isInteger(turns) || turns < 0) return null;
1984
- return turns;
1985
- }
1986
- /**
1987
- * English display copy for the host's SHIPPED presets, keyed by the stable
1988
- * preset id. The wire's `name`/`description` are the UNLOCALIZED metadata a
1989
- * preset publishes (DSH `PresetDisplaySource`), and the four built-ins ship
1990
- * Simplified Chinese there — DSH localizes them only inside its own web UI
1991
- * (`packages/preset/agent-presets/src/display.ts` folds trust:'system' + id →
1992
- * locale dictionary keys). This app has no host locale seat, so the overlay
1993
- * mirrors that fold in English: trust:'system' + a known id → the English
1994
- * copy; every other row (user presets, unknown future ids) keeps the host's
1995
- * name verbatim — user metadata is never translated, and an unknown id falls
1996
- * back honestly. Operator-tunable off via ~/.dsi/settings.yaml
1997
- * `dsh.presetEnglish: false` (readDshPresetEnglishConfig — no cache, so the
1998
- * next listPresets fetch picks an edit up). Keep the strings in step with DSH
1999
- * `packages/client/ui-agent-preset/src/client/locales.ts` (the `en` bundle).
2000
- */
2001
- var SHIPPED_PRESET_ENGLISH = {
2002
- standard: {
2003
- name: "Standard mode",
2004
- description: "Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows."
2005
- },
2006
- ptc: {
2007
- name: "PTC mode",
2008
- description: "Full coding agent without the workflow tool; other tools are exposed through the PTC mode SDK so the model can combine multi-step operations in one TypeScript program."
2009
- },
2010
- minimal: {
2011
- name: "Minimal mode",
2012
- description: "Two-tool coding agent with persistent bash and str_replace_editor."
2013
- },
2014
- cordis: {
2015
- name: "Creator mode",
2016
- description: "Built for creating custom agent presets, with all Standard mode capabilities plus runtime inspection, plugin experiments, and preset-authoring guidance."
2097
+ /** Recent settlements for a session (ring: delivered-then-pruned). */
2098
+ settlementsFor(sessionId) {
2099
+ return this.relay.settlementsFor(sessionId);
2100
+ }
2101
+ /** Delivery hook: drop settlements the client has already seen. */
2102
+ pruneSettlements(sessionId, seenRpcIds) {
2103
+ this.relay.pruneSettlements(sessionId, seenRpcIds);
2104
+ }
2105
+ /** Normalized session/list with fork-title healing. */
2106
+ async listSessions() {
2107
+ return this.relay.listSessions();
2108
+ }
2109
+ /** The workspace-RPC OWNER for a session (parent-chain walk). */
2110
+ async workspaceOwnerSessionId(sessionId) {
2111
+ return this.relay.workspaceOwnerSessionId(sessionId);
2112
+ }
2113
+ /** The session/page + session/follow address for one session id. */
2114
+ addressFor(sessionId) {
2115
+ return this.relay.addressFor(sessionId);
2116
+ }
2117
+ /** session.history successor (0.1.2 session/page tail). */
2118
+ async history(sessionId) {
2119
+ return this.relay.history(sessionId);
2120
+ }
2121
+ /** Workspace registry adoption (Add-workspace button's host call). */
2122
+ async createWorkspace(path) {
2123
+ return this.relay.createWorkspace(path);
2124
+ }
2125
+ /** Workspace rename (Chip Menu ADR D3). */
2126
+ async renameWorkspace(workspaceId, title) {
2127
+ return this.relay.renameWorkspace(workspaceId, title);
2128
+ }
2129
+ /** Workspace delete (Chip Menu ADR D3). */
2130
+ async deleteWorkspace(workspaceId) {
2131
+ return this.relay.deleteWorkspace(workspaceId);
2132
+ }
2133
+ /**
2134
+ * Goal Bar (2026-09-08, ADR amendment): one goal mutation through the
2135
+ * host's Goal Remotes — goals/pause | goals/resume | goals/clear |
2136
+ * goals/edit with the CAS ref {id, revision} (a stale revision loses
2137
+ * host-side). goals/edit additionally carries the changed-fields
2138
+ * request {objective?, maxGoalRounds?} (Goal Editor, 2026-09-09). The
2139
+ * raw RemoteResult passes through; the route maps refusals.
2140
+ * Kept self-contained in the shell: tests pin it via
2141
+ * DshConnection.prototype.goalVerb.call(fakeRpcThis, …).
2142
+ */
2143
+ async goalVerb(verb, sessionId, ref, request) {
2144
+ return this.rpc(`goals/${verb}`, request === void 0 ? {
2145
+ agentId: sessionId,
2146
+ ref
2147
+ } : {
2148
+ agentId: sessionId,
2149
+ ref,
2150
+ request
2151
+ });
2152
+ }
2153
+ /** workspaceFiles/read — one page of a UTF-8 text file. */
2154
+ async readWorkspaceFile(sessionId, path, range) {
2155
+ return this.relay.readWorkspaceFile(sessionId, path, range);
2156
+ }
2157
+ /** workspaceFiles/readBytes — one byte window, raw bytes. */
2158
+ async readWorkspaceFileBytes(sessionId, path, range) {
2159
+ return this.relay.readWorkspaceFileBytes(sessionId, path, range);
2160
+ }
2161
+ /** workspaceFiles/list — one directory level. */
2162
+ async listWorkspaceDirectory(sessionId, path) {
2163
+ return this.relay.listWorkspaceDirectory(sessionId, path);
2164
+ }
2165
+ /** host.listDirectory — the folder picker browse. */
2166
+ async listDirectory(path) {
2167
+ return this.relay.listDirectory(path);
2168
+ }
2169
+ /** session.attachment — session-authorized durable image read. */
2170
+ async readAttachment(sessionId, attachmentId) {
2171
+ return this.relay.readAttachment(sessionId, attachmentId);
2172
+ }
2173
+ /** host.pickDirectory — the NATIVE directory chooser. */
2174
+ async pickDirectory(signal) {
2175
+ return this.relay.pickDirectory(signal);
2176
+ }
2177
+ /** session.history PAGE ending at beforeSeq−1 (ledger truth, BC-4). */
2178
+ async historyPage(sessionId, beforeSeq) {
2179
+ return this.relay.historyPage(sessionId, beforeSeq);
2180
+ }
2181
+ /** session.create {cwd?, agentPreset?}. */
2182
+ async createSession(cwd, agentPreset) {
2183
+ return this.relay.createSession(cwd, agentPreset);
2184
+ }
2185
+ /** session/fork — the fork child. */
2186
+ async forkSession(sessionId, atSeq) {
2187
+ return this.relay.forkSession(sessionId, atSeq);
2188
+ }
2189
+ /** agentPreset.list — the picker's only source of truth. */
2190
+ async listPresets() {
2191
+ return this.relay.listPresets();
2192
+ }
2193
+ /** session.prompt — returns after the receipt only (BC-3). */
2194
+ async prompt(sessionId, payload, mode = "queue") {
2195
+ return this.relay.prompt(sessionId, payload, mode);
2196
+ }
2197
+ /** commands/execute — the NATIVE slash-command surface. */
2198
+ async executeCommand(sessionId, line) {
2199
+ return this.relay.executeCommand(sessionId, line);
2200
+ }
2201
+ /** commands/list — the host's command catalog. */
2202
+ async listCommands(sessionId) {
2203
+ return this.relay.listCommands(sessionId);
2204
+ }
2205
+ /** skills/list — the session's user-invocable skill catalog. */
2206
+ async listSkills(sessionId) {
2207
+ return this.relay.listSkills(sessionId);
2208
+ }
2209
+ /** session.cancel */
2210
+ async cancel(sessionId) {
2211
+ return this.relay.cancel(sessionId);
2212
+ }
2213
+ /** session.rename {sessionId, title}. */
2214
+ async renameSession(sessionId, title) {
2215
+ return this.relay.renameSession(sessionId, title);
2216
+ }
2217
+ /** session.models → the model directory. */
2218
+ async listModels(_sessionId) {
2219
+ return this.relay.listModels(_sessionId);
2220
+ }
2221
+ /** session.selectModel → {selected: ModelSelection}. */
2222
+ async selectModel(sessionId, provider, model, reasoningEffort) {
2223
+ return this.relay.selectModel(sessionId, provider, model, reasoningEffort);
2224
+ }
2225
+ /** Answer a pending approval/question via $events/result. */
2226
+ async respond(rpcId, payload) {
2227
+ return this.relay.respond(rpcId, payload);
2228
+ }
2229
+ /** Authenticated GET of one Host path (cookie ladder). */
2230
+ async fetchHostPath(path, signal) {
2231
+ return this.relay.fetchHostPath(path, signal);
2232
+ }
2233
+ /** Resync: replace the ring buffer with the ledger's tail page (BC-4). */
2234
+ async resyncFromLedger(sessionId) {
2235
+ return this.relay.resyncFromLedger(sessionId);
2236
+ }
2237
+ dispose() {
2238
+ this.disposed = true;
2239
+ this.downlinks.teardownSockets();
2240
+ this.relay.dispose();
2241
+ this.sessions.dispose();
2017
2242
  }
2018
2243
  };
2019
- /**
2020
- * Conservative normalize: unknown shapes keep their id but never invent data.
2021
- * The one English overlay seat (SHIPPED_PRESET_ENGLISH) applies before the
2022
- * host fallbacks — a shipped preset's label is locale-resolved here, never
2023
- * the wire's unlocalized text. Operator-tunable via
2024
- * ~/.dsi/settings.yaml `dsh.presetEnglish` (read live per listPresets
2025
- * call, passed through here): false hands the wire name through untouched.
2026
- */
2027
- function normalizePreset(row, overlayEnglish = true) {
2028
- const id = typeof row.id === "string" ? row.id : String(row.id ?? "");
2029
- const shipped = overlayEnglish && row.trust === "system" ? SHIPPED_PRESET_ENGLISH[id] : void 0;
2030
- return {
2031
- id,
2032
- name: shipped?.name ?? (typeof row.name === "string" ? row.name : null),
2033
- description: shipped?.description ?? (typeof row.description === "string" ? row.description : null),
2034
- isDefault: row.isDefault === true
2035
- };
2036
- }
2037
2244
 
2038
2245
  export { DshRpcError as D, isPromptImageMediaType as a, buildPromptContent as b, getDshConnection as g, isSubagentRejection as i, mapRpcFailure as m, statusFor as s };
2039
- //# sourceMappingURL=dsh-connection.js-Lnv7PlHw.js.map
2246
+ //# sourceMappingURL=dsh-connection.js-DRtqPR_j.js.map