chainlesschain 0.161.10 → 0.161.12

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 (175) hide show
  1. package/package.json +4 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-CmCGAwDv.js → AIOps-B1dwBvzW.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-eeaISbP3.js → ActionButton-DfR4oLvh.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CJEX10hA.js → Analytics-D3ZYGXjr.js} +1 -1
  6. package/src/assets/web-panel/assets/AppLayout-CsmOoh-7.js +3 -0
  7. package/src/assets/web-panel/assets/{AppLayout-BpbfrB7k.css → AppLayout-DBgtkmP7.css} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-B4BUSPse.js → Audit-B4gwDm63.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DIDcWoL9.js → Backup-T42uSArV.js} +1 -1
  10. package/src/assets/web-panel/assets/BaseInput-CFi52nMs.js +1 -0
  11. package/src/assets/web-panel/assets/{Chat-D-yEkssi.js → Chat-D7Vvok1V.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-Bww7tFNj.js → Checkbox-Dwaflpww.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-D9k-kRxr.js → Codegen-BIqx3G0b.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-CWhmQejT.js → Col-DzIUYUNu.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-Ck1ojsb1.js → Community-DKePtzhk.js} +1 -1
  16. package/src/assets/web-panel/assets/Compact-CirVV9Wq.js +1 -0
  17. package/src/assets/web-panel/assets/{Compliance-BSOLLyKA.js → Compliance-CrXOr0sy.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-BiOMErjG.js → Cowork-BEBP6Tht.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cron-DnVm70q-.js → Cron-k7nNUuqh.js} +1 -1
  20. package/src/assets/web-panel/assets/{Crosschain-CqIVrbR9.js → Crosschain-CBnX0Dhq.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-DS9MUwUx.js → DID-B48FszWS.js} +1 -1
  22. package/src/assets/web-panel/assets/{Dashboard-BlrXX2bG.js → Dashboard-Pb3qfFpp.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-CqBG_led.js → Dropdown-pUHy4CQ2.js} +1 -1
  24. package/src/assets/web-panel/assets/Federation-CT0Qs-kR.js +1 -0
  25. package/src/assets/web-panel/assets/{FormItemContext-DQUjKw5j.js → FormItemContext-CbJJp5BR.js} +1 -1
  26. package/src/assets/web-panel/assets/{Git-pitws4KW.js → Git-B3mGNLQe.js} +1 -1
  27. package/src/assets/web-panel/assets/{Governance-BNG31zqe.js → Governance-BKf4733q.js} +1 -1
  28. package/src/assets/web-panel/assets/{Inference-W32jdXsU.js → Inference-DZjU541G.js} +1 -1
  29. package/src/assets/web-panel/assets/{KnowledgeGraph-_qBNRolE.js → KnowledgeGraph-C8L-7Dd1.js} +1 -1
  30. package/src/assets/web-panel/assets/{Logs-BnnANM_j.js → Logs-CwDwOiKv.js} +1 -1
  31. package/src/assets/web-panel/assets/{Marketplace-C6OwNTw7.js → Marketplace-C2YWWU0M.js} +1 -1
  32. package/src/assets/web-panel/assets/{McpTools-D7cbYTwR.js → McpTools-dIbkOypF.js} +1 -1
  33. package/src/assets/web-panel/assets/{Memory-7kL8dh9_.js → Memory-7eF8WzcY.js} +1 -1
  34. package/src/assets/web-panel/assets/{MobileBridge-B5ZSxpt9.js → MobileBridge-C74GHLbX.js} +3 -3
  35. package/src/assets/web-panel/assets/{MobileBridge-DkkMCDx9.css → MobileBridge-DwxX55b_.css} +1 -1
  36. package/src/assets/web-panel/assets/{Mtc-DOm5jweE.js → Mtc-CSEDo5Fo.js} +1 -1
  37. package/src/assets/web-panel/assets/{MtcAudit-D9rDFqWR.js → MtcAudit-DiJXxOrB.js} +6 -6
  38. package/src/assets/web-panel/assets/{Multisig-CVqb7ayL.js → Multisig-FZTU5ri6.js} +1 -1
  39. package/src/assets/web-panel/assets/{NLProgramming-BETAJaqD.js → NLProgramming-DjF-gIUw.js} +1 -1
  40. package/src/assets/web-panel/assets/{Notes-CUJ1PDmG.js → Notes-BUE5CvMO.js} +1 -1
  41. package/src/assets/web-panel/assets/{NotificationSettings-BauSiotO.js → NotificationSettings-Dfbrobje.js} +1 -1
  42. package/src/assets/web-panel/assets/{Organization-B0T5PGg4.js → Organization-C6YvqjQB.js} +1 -1
  43. package/src/assets/web-panel/assets/{Overflow-CHLIn241.js → Overflow-BvHNhdMR.js} +1 -1
  44. package/src/assets/web-panel/assets/{OverrideContext-DYxbd09I.js → OverrideContext-x9ZzjLwk.js} +1 -1
  45. package/src/assets/web-panel/assets/{P2P-yqfDCE9U.js → P2P-BO0hQHFS.js} +1 -1
  46. package/src/assets/web-panel/assets/{Permissions-DWu7ec_n.js → Permissions-CCPlrJeP.js} +1 -1
  47. package/src/assets/web-panel/assets/{Pipeline-BPXGoHep.js → Pipeline-DTCL3FjJ.js} +1 -1
  48. package/src/assets/web-panel/assets/{Privacy-B2fRmhNs.js → Privacy-08DYgOe_.js} +1 -1
  49. package/src/assets/web-panel/assets/{ProjectInit-dPr0sgSZ.js → ProjectInit-B7j-Z8sa.js} +2 -2
  50. package/src/assets/web-panel/assets/{ProjectSettings-dKjwdfXX.js → ProjectSettings-CFqLhV1w.js} +1 -1
  51. package/src/assets/web-panel/assets/Projects-0OrfK90x.css +1 -0
  52. package/src/assets/web-panel/assets/Projects-BPlpx2UN.js +1 -0
  53. package/src/assets/web-panel/assets/{Providers-OpB_FLki.js → Providers-BCBPbVbF.js} +1 -1
  54. package/src/assets/web-panel/assets/{QuickAsk-B0gFMvI8.js → QuickAsk-C8Job6zl.js} +1 -1
  55. package/src/assets/web-panel/assets/{Recommend-C2NwxWiR.js → Recommend-DOV_Aje-.js} +1 -1
  56. package/src/assets/web-panel/assets/{Reputation-CTBtQ8Bi.js → Reputation-DOgK_dIK.js} +1 -1
  57. package/src/assets/web-panel/assets/Row-8jdU1xYg.js +1 -0
  58. package/src/assets/web-panel/assets/{RssFeed-ChOug33H.js → RssFeed-B289OgNZ.js} +1 -1
  59. package/src/assets/web-panel/assets/{Search-DrlVYajZ.js → Search-cr0AndFE.js} +1 -1
  60. package/src/assets/web-panel/assets/{Security-BxWm_HMW.js → Security-w4diykaE.js} +1 -1
  61. package/src/assets/web-panel/assets/{Services-Bq__zdi1.js → Services-dTxAI5cG.js} +2 -2
  62. package/src/assets/web-panel/assets/{Skeleton-D2BPihBx.js → Skeleton-B3FiUiRo.js} +1 -1
  63. package/src/assets/web-panel/assets/{Skills-Q1ZtavwR.js → Skills-DgtBTAFC.js} +1 -1
  64. package/src/assets/web-panel/assets/{Sla-CyIKjjSg.js → Sla-B4Y6bvbL.js} +1 -1
  65. package/src/assets/web-panel/assets/{SpeechSettings-DPddWDCV.js → SpeechSettings-C8FEvArc.js} +1 -1
  66. package/src/assets/web-panel/assets/{SyncSettings-CY-Fdii1.js → SyncSettings-CHvV5L0m.js} +1 -1
  67. package/src/assets/web-panel/assets/{Tasks-DAWxFePY.js → Tasks-B4Py5ORS.js} +1 -1
  68. package/src/assets/web-panel/assets/{Templates-CuIQmfXP.js → Templates-MeTyXM5u.js} +1 -1
  69. package/src/assets/web-panel/assets/{Tenant-CYsUC_mL.js → Tenant-Doiz7wyQ.js} +1 -1
  70. package/src/assets/web-panel/assets/Terminal-DSEAdwWN.js +3 -0
  71. package/src/assets/web-panel/assets/Terminal-G1DrFtKr.css +1 -0
  72. package/src/assets/web-panel/assets/{Tokens-DpwM8qKh.js → Tokens-BBrtaEKt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Trigger-CBHUHnc-.js → Trigger-nPvjho27.js} +1 -1
  74. package/src/assets/web-panel/assets/{Trust-DfQZgxcX.js → Trust-BFwPyS_6.js} +1 -1
  75. package/src/assets/web-panel/assets/{UkeySign-BcOnuHWe.js → UkeySign-DFsHoY1J.js} +1 -1
  76. package/src/assets/web-panel/assets/{VideoEditing-Ci-c6HHb.js → VideoEditing-FkSKqHE9.js} +1 -1
  77. package/src/assets/web-panel/assets/{Wallet-DBuVM9t_.js → Wallet-AXo-_4-w.js} +4 -4
  78. package/src/assets/web-panel/assets/{WebAuthn-Dc3XC6-l.js → WebAuthn-DoScnQ-4.js} +1 -1
  79. package/src/assets/web-panel/assets/{WorkflowEditor-BFBZCgWB.js → WorkflowEditor-IEMfIax-.js} +1 -1
  80. package/src/assets/web-panel/assets/addon-fit-CK6X9sAG.js +1 -0
  81. package/src/assets/web-panel/assets/{chat-tqUunmHQ.js → chat-pc1ciH6T.js} +1 -1
  82. package/src/assets/web-panel/assets/{collapseMotion-CZQhO80h.js → collapseMotion-CjFH_Jop.js} +1 -1
  83. package/src/assets/web-panel/assets/{colors-7dfqj18R.js → colors-CjkIkB0e.js} +1 -1
  84. package/src/assets/web-panel/assets/{compact-item-_8YefjuR.js → compact-item-CQZnkP5p.js} +1 -1
  85. package/src/assets/web-panel/assets/{createContext-CPZ59Zcw.js → createContext-B1McGnV-.js} +1 -1
  86. package/src/assets/web-panel/assets/{echarts-TwhxblyG.js → echarts-Bq-n0MtJ.js} +1 -1
  87. package/src/assets/web-panel/assets/{hasIn-BidaS7SK.js → hasIn-CbkA6peP.js} +1 -1
  88. package/src/assets/web-panel/assets/{icons-Q99MnhG8.js → icons-NT6gy8Ee.js} +2 -2
  89. package/src/assets/web-panel/assets/{index-Ds3hIeDc.js → index-5c4JzwY3.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-51Z_iRMU.js → index-B8r6OBuk.js} +2 -2
  91. package/src/assets/web-panel/assets/{index-Ca0EAS0W.js → index-BNLeseG1.js} +2 -2
  92. package/src/assets/web-panel/assets/index-BTyhXFDW.js +1 -0
  93. package/src/assets/web-panel/assets/index-BXfePRef.js +1 -0
  94. package/src/assets/web-panel/assets/index-Bs-romIz.js +1 -0
  95. package/src/assets/web-panel/assets/index-C052wi94.js +1 -0
  96. package/src/assets/web-panel/assets/{index-Dev3hWia.js → index-C0Lj7Yl0.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B3-Fdfqy.js → index-C5mpCgak.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B_pQvbuN.js → index-CEENN81t.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CLceS3rm.js → index-CFCQl1hk.js} +2 -2
  100. package/src/assets/web-panel/assets/{index-DTwrQg1G.js → index-CGke5qZp.js} +1 -1
  101. package/src/assets/web-panel/assets/index-CJbqE5Sw.js +1 -0
  102. package/src/assets/web-panel/assets/index-CJr12ypE.js +21 -0
  103. package/src/assets/web-panel/assets/{index-CG0swGFV.js → index-CKledOCh.js} +1 -1
  104. package/src/assets/web-panel/assets/index-CKwkP66s.js +13 -0
  105. package/src/assets/web-panel/assets/{index-DfHN8qWn.js → index-ChCCGHwz.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CW90j-dZ.js → index-CzZ3LxPK.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CzARuKje.js → index-D4rhVRSV.js} +1 -1
  108. package/src/assets/web-panel/assets/index-DGb36exe.js +3 -0
  109. package/src/assets/web-panel/assets/{index-DccwP9Op.js → index-DHpwwlmv.js} +1 -1
  110. package/src/assets/web-panel/assets/index-DHtiecyM.js +1 -0
  111. package/src/assets/web-panel/assets/{index-Cxwyyzpd.js → index-DJWYN-AT.js} +2 -2
  112. package/src/assets/web-panel/assets/{index-DJfKgyA-.js → index-DMjPzhsp.js} +2 -2
  113. package/src/assets/web-panel/assets/{index-BqsvxElz.js → index-DPzT5L14.js} +4 -4
  114. package/src/assets/web-panel/assets/{index-D8upWkW0.js → index-DWq64zmv.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DTmWBWkJ.js → index-DYAWIXFt.js} +1 -1
  116. package/src/assets/web-panel/assets/index-Dc4fj_Ys.js +1 -0
  117. package/src/assets/web-panel/assets/{index-Cw1VkvZr.js → index-DcSe5y5O.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Xg1JWxyg.js → index-Di5tuS0d.js} +2 -2
  119. package/src/assets/web-panel/assets/{index-D95E7B-a.js → index-DjM0jsm1.js} +2 -2
  120. package/src/assets/web-panel/assets/{index-CvkR7sYL.js → index-DjxVsyn_.js} +1 -1
  121. package/src/assets/web-panel/assets/index-Dn7SHV2e.js +55 -0
  122. package/src/assets/web-panel/assets/index-DsChgzu2.js +12 -0
  123. package/src/assets/web-panel/assets/{index-CAUH-6ta.js → index-Dzfj71tf.js} +2 -2
  124. package/src/assets/web-panel/assets/index-NRWBOo4F.js +1 -0
  125. package/src/assets/web-panel/assets/{index-Cxy6yDHf.js → index-fqI1KnP_.js} +8 -8
  126. package/src/assets/web-panel/assets/{index-DJa3dvQf.js → index-uWvLy_3T.js} +6 -6
  127. package/src/assets/web-panel/assets/{index-BUlPL2uR.js → index-wwQ_ZkWN.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-DBQ94EVq.js → initDefaultProps-BTloFqyf.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BAgV2nZt.js → motion-44iMBc1o.js} +2 -2
  130. package/src/assets/web-panel/assets/{move-CPhu8BHp.js → move-CyWQI5eW.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-BKpLUN0q.js → omit-CXfJtuFy.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-BicOrtU0.js → pickAttrs-ANFpSZes.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-D2-Ct0PH.js → placementArrow-CigX-url.js} +1 -1
  134. package/src/assets/web-panel/assets/responsiveObserve-Ch48RA0m.js +1 -0
  135. package/src/assets/web-panel/assets/{slide-C7zAHcKr.js → slide-BEz3LORF.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-ByfnDIgi.js → statusUtils-D9EniG8V.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-C-ldcMcB.js → styleChecker-C1IvuY3L.js} +1 -1
  138. package/src/assets/web-panel/assets/useFlexGapSupport-C92oHeXB.js +1 -0
  139. package/src/assets/web-panel/assets/{useFs-yX-MUKbZ.js → useFs-CJhvFpgB.js} +1 -1
  140. package/src/assets/web-panel/assets/{useMergedState-D3PX36GT.js → useMergedState-O7QXt4P5.js} +1 -1
  141. package/src/assets/web-panel/assets/{useRefs-opfcK7Pm.js → useRefs-0J6m8UWN.js} +1 -1
  142. package/src/assets/web-panel/assets/{useState-Cp5WlLTS.js → useState-CSzR8F8O.js} +1 -1
  143. package/src/assets/web-panel/assets/{vendor-BVLz_z7V.js → vendor-M5lGV-wr.js} +1 -1
  144. package/src/assets/web-panel/assets/{vnode-CW-Z-Xwr.js → vnode-BPkyunN_.js} +1 -1
  145. package/src/assets/web-panel/assets/xterm-BZcWGsqw.js +9 -0
  146. package/src/assets/web-panel/assets/xterm-DFuMZ0ql.css +1 -0
  147. package/src/assets/web-panel/assets/{zoom-DR60LSPO.js → zoom-B6Ipb64r.js} +1 -1
  148. package/src/assets/web-panel/index.html +3 -3
  149. package/src/gateways/terminal/PtyManager.js +248 -0
  150. package/src/gateways/terminal/RingBuffer.js +61 -0
  151. package/src/gateways/terminal/terminal-handlers.js +121 -0
  152. package/src/gateways/ws/topic-handler-attachment.js +211 -0
  153. package/src/runtime/agent-runtime.js +33 -0
  154. package/src/assets/web-panel/assets/AppLayout-CrYEU1PZ.js +0 -3
  155. package/src/assets/web-panel/assets/BaseInput-CuJjCIcU.js +0 -1
  156. package/src/assets/web-panel/assets/Compact-CuDFE-4P.js +0 -1
  157. package/src/assets/web-panel/assets/Federation-DCBYjpOX.js +0 -1
  158. package/src/assets/web-panel/assets/Projects-C34BWmmy.css +0 -1
  159. package/src/assets/web-panel/assets/Projects-fRCTrULX.js +0 -1
  160. package/src/assets/web-panel/assets/Row-CQ36tMlO.js +0 -1
  161. package/src/assets/web-panel/assets/index-BJmJfXGw.js +0 -1
  162. package/src/assets/web-panel/assets/index-BtBRkcWM.js +0 -1
  163. package/src/assets/web-panel/assets/index-C0MN4j0q.js +0 -12
  164. package/src/assets/web-panel/assets/index-Ca5Qc31r.js +0 -1
  165. package/src/assets/web-panel/assets/index-CmCovo5r.js +0 -55
  166. package/src/assets/web-panel/assets/index-D7yjk-E8.js +0 -3
  167. package/src/assets/web-panel/assets/index-DMRsx5W4.js +0 -1
  168. package/src/assets/web-panel/assets/index-DP7namG9.js +0 -21
  169. package/src/assets/web-panel/assets/index-DWSEcCO0.js +0 -1
  170. package/src/assets/web-panel/assets/index-D_vErh-N.js +0 -13
  171. package/src/assets/web-panel/assets/index-DsykGbFs.js +0 -1
  172. package/src/assets/web-panel/assets/index-OZ08PLpO.js +0 -1
  173. package/src/assets/web-panel/assets/index-QVPLcn-I.js +0 -1
  174. package/src/assets/web-panel/assets/responsiveObserve-DGFr293p.js +0 -1
  175. package/src/assets/web-panel/assets/useFlexGapSupport-CKCvqfWX.js +0 -1
@@ -0,0 +1 @@
1
+ .xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
@@ -1,4 +1,4 @@
1
- import{K as o}from"./index-BqsvxElz.js";import{i as f}from"./motion-BAgV2nZt.js";const c=new o("antZoomIn",{"0%":{transform:"scale(0.2)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),y=new o("antZoomOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.2)",opacity:0}}),a=new o("antZoomBigIn",{"0%":{transform:"scale(0.8)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),s=new o("antZoomBigOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.8)",opacity:0}}),g=new o("antZoomUpIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 0%"}}),O=new o("antZoomUpOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 0%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0}}),l=new o("antZoomLeftIn",{"0%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"0% 50%"}}),u=new o("antZoomLeftOut",{"0%":{transform:"scale(1)",transformOrigin:"0% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0}}),p=new o("antZoomRightIn",{"0%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"100% 50%"}}),z=new o("antZoomRightOut",{"0%":{transform:"scale(1)",transformOrigin:"100% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0}}),K=new o("antZoomDownIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 100%"}}),w=new o("antZoomDownOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 100%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0}}),I={zoom:{inKeyframes:c,outKeyframes:y},"zoom-big":{inKeyframes:a,outKeyframes:s},"zoom-big-fast":{inKeyframes:a,outKeyframes:s},"zoom-left":{inKeyframes:l,outKeyframes:u},"zoom-right":{inKeyframes:p,outKeyframes:z},"zoom-up":{inKeyframes:g,outKeyframes:O},"zoom-down":{inKeyframes:K,outKeyframes:w}},h=(n,r)=>{const{antCls:m}=n,t=`${m}-${r}`,{inKeyframes:i,outKeyframes:e}=I[r];return[f(t,i,e,r==="zoom-big-fast"?n.motionDurationFast:n.motionDurationMid),{[`
1
+ import{K as o}from"./index-DPzT5L14.js";import{i as f}from"./motion-44iMBc1o.js";const c=new o("antZoomIn",{"0%":{transform:"scale(0.2)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),y=new o("antZoomOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.2)",opacity:0}}),a=new o("antZoomBigIn",{"0%":{transform:"scale(0.8)",opacity:0},"100%":{transform:"scale(1)",opacity:1}}),s=new o("antZoomBigOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0.8)",opacity:0}}),g=new o("antZoomUpIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 0%"}}),O=new o("antZoomUpOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 0%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 0%",opacity:0}}),l=new o("antZoomLeftIn",{"0%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"0% 50%"}}),u=new o("antZoomLeftOut",{"0%":{transform:"scale(1)",transformOrigin:"0% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"0% 50%",opacity:0}}),p=new o("antZoomRightIn",{"0%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"100% 50%"}}),z=new o("antZoomRightOut",{"0%":{transform:"scale(1)",transformOrigin:"100% 50%"},"100%":{transform:"scale(0.8)",transformOrigin:"100% 50%",opacity:0}}),K=new o("antZoomDownIn",{"0%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0},"100%":{transform:"scale(1)",transformOrigin:"50% 100%"}}),w=new o("antZoomDownOut",{"0%":{transform:"scale(1)",transformOrigin:"50% 100%"},"100%":{transform:"scale(0.8)",transformOrigin:"50% 100%",opacity:0}}),I={zoom:{inKeyframes:c,outKeyframes:y},"zoom-big":{inKeyframes:a,outKeyframes:s},"zoom-big-fast":{inKeyframes:a,outKeyframes:s},"zoom-left":{inKeyframes:l,outKeyframes:u},"zoom-right":{inKeyframes:p,outKeyframes:z},"zoom-up":{inKeyframes:g,outKeyframes:O},"zoom-down":{inKeyframes:K,outKeyframes:w}},h=(n,r)=>{const{antCls:m}=n,t=`${m}-${r}`,{inKeyframes:i,outKeyframes:e}=I[r];return[f(t,i,e,r==="zoom-big-fast"?n.motionDurationFast:n.motionDurationMid),{[`
2
2
  ${t}-enter,
3
3
  ${t}-appear
4
4
  `]:{transform:"scale(0)",opacity:0,animationTimingFunction:n.motionEaseOutCirc,"&-prepare":{transform:"none"}},[`${t}-leave`]:{animationTimingFunction:n.motionEaseInOutCirc}}]};export{h as i,c as z};
@@ -8,9 +8,9 @@
8
8
  // Injected by web-ui-server.js at serve time
9
9
  window.__CC_CONFIG__ = __CC_CONFIG_PLACEHOLDER__;
10
10
  </script>
11
- <script type="module" crossorigin src="./assets/index-BqsvxElz.js"></script>
12
- <link rel="modulepreload" crossorigin href="./assets/vendor-BVLz_z7V.js">
13
- <link rel="modulepreload" crossorigin href="./assets/icons-Q99MnhG8.js">
11
+ <script type="module" crossorigin src="./assets/index-DPzT5L14.js"></script>
12
+ <link rel="modulepreload" crossorigin href="./assets/vendor-M5lGV-wr.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/icons-NT6gy8Ee.js">
14
14
  <link rel="stylesheet" crossorigin href="./assets/index-CfX1DEtk.css">
15
15
  </head>
16
16
  <body>
@@ -0,0 +1,248 @@
1
+ /**
2
+ * PtyManager — ESM mirror of `desktop-app-vue/src/main/terminal/PtyManager.js`.
3
+ *
4
+ * Keep both copies in sync. The desktop copy targets Electron's CJS main
5
+ * process; this one targets `cc ui` (Node ESM) so `cc ui` users also get
6
+ * remote-terminal support via the same WS protocol.
7
+ *
8
+ * Design notes — see docs/design/Android_Remote_Terminal_Plan_A.md §4:
9
+ * - node-pty loaded lazily so the module loads without the native
10
+ * binding installed (tests, headless CI, machines without it).
11
+ * - Per-session RingBuffer (256 KiB default, memory-only — no disk
12
+ * persistence: terminal stdout often contains secrets).
13
+ * - EventEmitter fans out `stdout` / `exit` so handlers translate to
14
+ * WS envelopes without coupling.
15
+ * - Idle kill default 24h, resets on stdin OR stdout activity.
16
+ * - Shell whitelist enforced at create() — unknown shell → throws
17
+ * `shell_not_allowed`.
18
+ */
19
+
20
+ import EventEmitter from "events";
21
+ import { randomUUID } from "crypto";
22
+ import { createRequire } from "module";
23
+ import { RingBuffer } from "./RingBuffer.js";
24
+
25
+ const require = createRequire(import.meta.url);
26
+
27
+ export const DEFAULT_CONFIG = Object.freeze({
28
+ shellWhitelist: ["pwsh", "cmd", "bash", "wsl"],
29
+ defaultShell: process.platform === "win32" ? "pwsh" : "bash",
30
+ defaultCwd: null,
31
+ maxConcurrentSessions: 8,
32
+ ringBufferBytes: 256 * 1024,
33
+ idleKillMs: 24 * 60 * 60 * 1000,
34
+ });
35
+
36
+ function resolveShellCmd(shell) {
37
+ if (process.platform === "win32") {
38
+ if (shell === "cmd") return process.env.ComSpec || "cmd.exe";
39
+ if (shell === "pwsh") return "pwsh.exe";
40
+ if (shell === "wsl") return "wsl.exe";
41
+ if (shell === "bash") return "bash.exe";
42
+ }
43
+ return shell;
44
+ }
45
+
46
+ class PtySession {
47
+ constructor({ id, shell, cwd, cols, rows, proc, createdAt, ringBuffer }) {
48
+ this.id = id;
49
+ this.shell = shell;
50
+ this.cwd = cwd;
51
+ this.cols = cols;
52
+ this.rows = rows;
53
+ this.proc = proc;
54
+ this.createdAt = createdAt;
55
+ this.ringBuffer = ringBuffer;
56
+ this.alive = true;
57
+ this.exitCode = null;
58
+ this.signal = null;
59
+ this.lastActivityAt = createdAt;
60
+ }
61
+ }
62
+
63
+ export class PtyManager extends EventEmitter {
64
+ constructor(opts = {}) {
65
+ super();
66
+ this.config = { ...DEFAULT_CONFIG, ...(opts.config || {}) };
67
+ this._deps = {
68
+ loadNodePty: opts._deps?.loadNodePty || (() => require("node-pty")),
69
+ now: opts._deps?.now || (() => Date.now()),
70
+ };
71
+ this._sessions = new Map();
72
+ this._idleTimer = null;
73
+ this._stopped = false;
74
+ }
75
+
76
+ create(req = {}) {
77
+ if (this._stopped) throw new Error("pty_manager_stopped");
78
+ if (this._sessions.size >= this.config.maxConcurrentSessions) {
79
+ throw new Error("max_concurrent_sessions_exceeded");
80
+ }
81
+ const shell = req.shell || this.config.defaultShell;
82
+ if (!this.config.shellWhitelist.includes(shell)) {
83
+ throw new Error("shell_not_allowed");
84
+ }
85
+
86
+ let pty;
87
+ try {
88
+ pty = this._deps.loadNodePty();
89
+ } catch {
90
+ const err = new Error("pty_native_unavailable");
91
+ err.cause = "node-pty failed to load (native binding missing)";
92
+ throw err;
93
+ }
94
+
95
+ const cwd = req.cwd || this.config.defaultCwd || process.cwd();
96
+ const cols = Number.isFinite(req.cols) ? req.cols : 80;
97
+ const rows = Number.isFinite(req.rows) ? req.rows : 24;
98
+ const cmd = resolveShellCmd(shell);
99
+
100
+ const proc = pty.spawn(cmd, [], {
101
+ name: "xterm-256color",
102
+ cols,
103
+ rows,
104
+ cwd,
105
+ env: { ...process.env, ...(req.env || {}) },
106
+ });
107
+
108
+ const sessionId = randomUUID();
109
+ const session = new PtySession({
110
+ id: sessionId,
111
+ shell,
112
+ cwd,
113
+ cols,
114
+ rows,
115
+ proc,
116
+ createdAt: this._deps.now(),
117
+ ringBuffer: new RingBuffer({ maxBytes: this.config.ringBufferBytes }),
118
+ });
119
+ this._sessions.set(sessionId, session);
120
+
121
+ proc.onData((data) => {
122
+ const buf = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
123
+ const seq = session.ringBuffer.push(buf);
124
+ session.lastActivityAt = this._deps.now();
125
+ this.emit("stdout", { sessionId, data: buf, seq });
126
+ });
127
+
128
+ proc.onExit(({ exitCode, signal }) => {
129
+ session.alive = false;
130
+ session.exitCode = exitCode ?? null;
131
+ session.signal = signal ?? null;
132
+ this.emit("exit", {
133
+ sessionId,
134
+ exitCode: session.exitCode,
135
+ signal: session.signal,
136
+ });
137
+ setTimeout(() => {
138
+ if (this._sessions.get(sessionId) === session) {
139
+ this._sessions.delete(sessionId);
140
+ }
141
+ }, 60 * 1000).unref?.();
142
+ });
143
+
144
+ this._ensureIdleSweeper();
145
+
146
+ return {
147
+ sessionId,
148
+ pid: proc.pid,
149
+ shell,
150
+ createdAt: session.createdAt,
151
+ };
152
+ }
153
+
154
+ write(sessionId, data) {
155
+ const session = this._sessions.get(sessionId);
156
+ if (!session) throw new Error("session_not_found");
157
+ if (!session.alive) throw new Error("session_not_alive");
158
+ const str = Buffer.isBuffer(data) ? data.toString("utf-8") : data;
159
+ session.proc.write(str);
160
+ session.lastActivityAt = this._deps.now();
161
+ }
162
+
163
+ resize(sessionId, cols, rows) {
164
+ const session = this._sessions.get(sessionId);
165
+ if (!session) throw new Error("session_not_found");
166
+ if (!session.alive) throw new Error("session_not_alive");
167
+ if (!Number.isFinite(cols) || !Number.isFinite(rows)) {
168
+ throw new Error("invalid_dimensions");
169
+ }
170
+ session.proc.resize(cols, rows);
171
+ session.cols = cols;
172
+ session.rows = rows;
173
+ }
174
+
175
+ close(sessionId) {
176
+ const session = this._sessions.get(sessionId);
177
+ if (!session) throw new Error("session_not_found");
178
+ if (session.alive) {
179
+ try {
180
+ session.proc.kill();
181
+ } catch {
182
+ /* onExit will mark dead */
183
+ }
184
+ }
185
+ }
186
+
187
+ list() {
188
+ const out = [];
189
+ for (const s of this._sessions.values()) {
190
+ out.push({
191
+ id: s.id,
192
+ shell: s.shell,
193
+ cwd: s.cwd,
194
+ createdAt: s.createdAt,
195
+ alive: s.alive,
196
+ lastSeq: s.ringBuffer.lastSeq,
197
+ });
198
+ }
199
+ return out;
200
+ }
201
+
202
+ history(sessionId, fromSeq = 0) {
203
+ const session = this._sessions.get(sessionId);
204
+ if (!session) throw new Error("session_not_found");
205
+ return session.ringBuffer.since(fromSeq);
206
+ }
207
+
208
+ _ensureIdleSweeper() {
209
+ if (this._idleTimer) return;
210
+ this._idleTimer = setInterval(() => this._sweepIdle(), 60 * 60 * 1000);
211
+ this._idleTimer.unref?.();
212
+ }
213
+
214
+ _sweepIdle() {
215
+ const now = this._deps.now();
216
+ for (const session of this._sessions.values()) {
217
+ if (!session.alive) continue;
218
+ if (now - session.lastActivityAt > this.config.idleKillMs) {
219
+ try {
220
+ session.proc.kill();
221
+ } catch {
222
+ /* onExit will clean up */
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ shutdown() {
229
+ this._stopped = true;
230
+ if (this._idleTimer) {
231
+ clearInterval(this._idleTimer);
232
+ this._idleTimer = null;
233
+ }
234
+ for (const session of this._sessions.values()) {
235
+ if (session.alive) {
236
+ try {
237
+ session.proc.kill();
238
+ } catch {
239
+ /* best effort */
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ get _sessionCount() {
246
+ return this._sessions.size;
247
+ }
248
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RingBuffer — bounded byte-aware FIFO for PTY stdout chunks.
3
+ *
4
+ * ESM mirror of `desktop-app-vue/src/main/terminal/RingBuffer.js`. Both
5
+ * shells (Electron web-shell + `cc ui`) ship their own copy because the
6
+ * runtime module systems are incompatible (CJS vs ESM). Keep them in
7
+ * sync — future consolidation goes into a workspace package once the
8
+ * surface stabilises.
9
+ */
10
+
11
+ export class RingBuffer {
12
+ constructor(opts = {}) {
13
+ const maxBytes = opts.maxBytes ?? 256 * 1024;
14
+ if (typeof maxBytes !== "number" || maxBytes <= 0) {
15
+ throw new TypeError("RingBuffer: maxBytes must be a positive number");
16
+ }
17
+ this.maxBytes = maxBytes;
18
+ this._entries = [];
19
+ this._totalBytes = 0;
20
+ this._lastSeq = 0;
21
+ this._firstRetainedSeq = 1;
22
+ }
23
+
24
+ push(data) {
25
+ const buf = Buffer.isBuffer(data)
26
+ ? data
27
+ : typeof data === "string"
28
+ ? Buffer.from(data, "utf-8")
29
+ : Buffer.from(data);
30
+ this._lastSeq += 1;
31
+ const seq = this._lastSeq;
32
+ this._entries.push({ seq, data: buf });
33
+ this._totalBytes += buf.byteLength;
34
+ this._evictUntilUnderCap();
35
+ return seq;
36
+ }
37
+
38
+ _evictUntilUnderCap() {
39
+ while (this._totalBytes > this.maxBytes && this._entries.length > 0) {
40
+ const evicted = this._entries.shift();
41
+ this._totalBytes -= evicted.data.byteLength;
42
+ this._firstRetainedSeq = evicted.seq + 1;
43
+ }
44
+ }
45
+
46
+ since(fromSeq = 0) {
47
+ const truncated = fromSeq > 0 && fromSeq < this._firstRetainedSeq;
48
+ const chunks = this._entries.filter((e) => e.seq >= fromSeq);
49
+ return { chunks, truncated };
50
+ }
51
+
52
+ get lastSeq() {
53
+ return this._lastSeq;
54
+ }
55
+ get totalBytes() {
56
+ return this._totalBytes;
57
+ }
58
+ get size() {
59
+ return this._entries.length;
60
+ }
61
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * terminal-handlers — WS topic handlers for Plan A remote-terminal.
3
+ *
4
+ * ESM mirror of `desktop-app-vue/src/main/web-shell/handlers/terminal-handlers.js`.
5
+ * Keep both copies in sync (see the matching mirror docstring there).
6
+ */
7
+
8
+ export const DEFAULT_DANGEROUS_PATTERNS = [
9
+ /\brm\s+-rf\b/i,
10
+ /\bformat\s+[a-z]:/i,
11
+ /\bshutdown\b/i,
12
+ /\bdel\s+\/[sq]/i,
13
+ /\bdiskpart\b/i,
14
+ /:\(\)\s*{\s*:\s*\|\s*:\s*&\s*}\s*;\s*:/,
15
+ ];
16
+
17
+ export function bufferToBase64(buf) {
18
+ return Buffer.isBuffer(buf)
19
+ ? buf.toString("base64")
20
+ : Buffer.from(buf, "utf-8").toString("base64");
21
+ }
22
+
23
+ export function base64ToBuffer(s) {
24
+ if (typeof s !== "string") {
25
+ throw new Error("data_must_be_base64_string");
26
+ }
27
+ return Buffer.from(s, "base64");
28
+ }
29
+
30
+ export function createTerminalHandlers(options) {
31
+ const { ptyManager, broadcast } = options;
32
+ if (!ptyManager) throw new TypeError("ptyManager is required");
33
+ if (typeof broadcast !== "function") {
34
+ throw new TypeError("broadcast must be a function");
35
+ }
36
+ const dangerousPatterns =
37
+ options.dangerousPatterns || DEFAULT_DANGEROUS_PATTERNS;
38
+ const requireConfirmation =
39
+ options.requireConfirmation || (async () => false);
40
+ const verifyTrustedSource = options.verifyTrustedSource || (() => true);
41
+
42
+ function attachServerEvents() {
43
+ ptyManager.on("stdout", ({ sessionId, data, seq }) => {
44
+ broadcast({
45
+ type: "terminal.stdout",
46
+ payload: { sessionId, data: bufferToBase64(data), seq },
47
+ });
48
+ });
49
+ ptyManager.on("exit", ({ sessionId, exitCode, signal }) => {
50
+ broadcast({
51
+ type: "terminal.exit",
52
+ payload: { sessionId, exitCode, signal },
53
+ });
54
+ });
55
+ }
56
+
57
+ const handlers = {
58
+ "terminal.create": async (frame) => {
59
+ if (!verifyTrustedSource(frame)) return null;
60
+ const payload = frame?.payload || frame || {};
61
+ return ptyManager.create({
62
+ shell: payload.shell,
63
+ cwd: payload.cwd,
64
+ env: payload.env,
65
+ cols: payload.cols,
66
+ rows: payload.rows,
67
+ });
68
+ },
69
+
70
+ "terminal.list": async (frame) => {
71
+ if (!verifyTrustedSource(frame)) return null;
72
+ return { sessions: ptyManager.list() };
73
+ },
74
+
75
+ "terminal.stdin": async (frame) => {
76
+ if (!verifyTrustedSource(frame)) return null;
77
+ const { sessionId, data } = frame?.payload || frame || {};
78
+ if (!sessionId) throw new Error("session_id_required");
79
+ const buf = base64ToBuffer(data);
80
+ const text = buf.toString("utf-8");
81
+ if (dangerousPatterns.some((re) => re.test(text))) {
82
+ const ok = await requireConfirmation(text, sessionId);
83
+ if (!ok) throw new Error("dangerous_keyword_blocked");
84
+ }
85
+ ptyManager.write(sessionId, buf);
86
+ return { ok: true };
87
+ },
88
+
89
+ "terminal.resize": async (frame) => {
90
+ if (!verifyTrustedSource(frame)) return null;
91
+ const { sessionId, cols, rows } = frame?.payload || frame || {};
92
+ if (!sessionId) throw new Error("session_id_required");
93
+ ptyManager.resize(sessionId, cols, rows);
94
+ return { ok: true };
95
+ },
96
+
97
+ "terminal.close": async (frame) => {
98
+ if (!verifyTrustedSource(frame)) return null;
99
+ const { sessionId } = frame?.payload || frame || {};
100
+ if (!sessionId) throw new Error("session_id_required");
101
+ ptyManager.close(sessionId);
102
+ return { ok: true };
103
+ },
104
+
105
+ "terminal.history": async (frame) => {
106
+ if (!verifyTrustedSource(frame)) return null;
107
+ const { sessionId, fromSeq } = frame?.payload || frame || {};
108
+ if (!sessionId) throw new Error("session_id_required");
109
+ const { chunks, truncated } = ptyManager.history(sessionId, fromSeq || 0);
110
+ return {
111
+ chunks: chunks.map((c) => ({
112
+ seq: c.seq,
113
+ data: bufferToBase64(c.data),
114
+ })),
115
+ truncated,
116
+ };
117
+ },
118
+ };
119
+
120
+ return { handlers, attachServerEvents };
121
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * topic-handler-attachment — wrap a `ChainlessChainWSServer` instance with
3
+ * custom topic handlers, mirroring the existing dispatcher.
4
+ *
5
+ * Extracted from `desktop-app-vue/src/main/web-shell/ws-cli-loader.js` so
6
+ * both consumers (desktop web-shell AND `cc ui`) get the same protocol
7
+ * features:
8
+ *
9
+ * - id + auth gates (token-protected servers reject unauthenticated
10
+ * custom-topic calls; native CLI dispatch is untouched)
11
+ * - plain async handler → `{type:"<topic>.result", ok, result}`
12
+ * - async generator handler → `{type:"<topic>.chunk", ok, chunk}` *N +
13
+ * terminal `{type:"<topic>.result", ok, result}`
14
+ * - `<topic>.cancel` frame → `gen.return("client_cancel")` for in-flight
15
+ * streams (keyed by request id, not topic)
16
+ * - `ws.on("close")` → unwind every stream the client owned
17
+ *
18
+ * Single call site contract:
19
+ *
20
+ * const handle = attachTopicHandlers(server, {
21
+ * handlers: { 'terminal.create': async (frame, ctx) => {...}, ... },
22
+ * });
23
+ * handle.register('extra.topic', fn);
24
+ * handle.broadcast({type:'terminal.stdout', payload:{...}});
25
+ *
26
+ * Returns `{ register, broadcast, topicHandlers }`. The server's
27
+ * `_dispatcher` is mutated in place — calling this twice on the same
28
+ * server stacks wrappers (each falls through to the previous), which is
29
+ * fine but usually unnecessary.
30
+ */
31
+
32
+ /**
33
+ * @typedef {(frame: any, ctx: { topic: string, id: string, server: any, ws: any, clientId: string }) => (Promise<any> | any | AsyncIterable<any>)} TopicHandler
34
+ */
35
+
36
+ /**
37
+ * @param {any} server ChainlessChainWSServer instance (post-start())
38
+ * @param {object} [opts]
39
+ * @param {Record<string, TopicHandler>} [opts.handlers]
40
+ * @returns {{
41
+ * register: (topic: string, fn: TopicHandler) => void,
42
+ * broadcast: (frame: any) => void,
43
+ * topicHandlers: Map<string, TopicHandler>,
44
+ * }}
45
+ */
46
+ export function attachTopicHandlers(server, opts = {}) {
47
+ /** @type {Map<string, TopicHandler>} */
48
+ const topicHandlers = new Map();
49
+ if (opts.handlers) {
50
+ for (const [topic, fn] of Object.entries(opts.handlers)) {
51
+ if (typeof fn !== "function") {
52
+ throw new TypeError(
53
+ `attachTopicHandlers: handler for "${topic}" must be a function`,
54
+ );
55
+ }
56
+ topicHandlers.set(topic, fn);
57
+ }
58
+ }
59
+
60
+ /** @type {Map<string, { gen: AsyncIterator<any>, clientId: string }>} */
61
+ const inFlightStreams = new Map();
62
+
63
+ async function unwindStream(id, reason) {
64
+ const entry = inFlightStreams.get(id);
65
+ if (!entry) return;
66
+ inFlightStreams.delete(id);
67
+ try {
68
+ if (typeof entry.gen.return === "function") {
69
+ await entry.gen.return(reason);
70
+ }
71
+ } catch {
72
+ // Generator throw on unwind is non-fatal.
73
+ }
74
+ }
75
+
76
+ const wsCloseHooked = new WeakSet();
77
+ function ensureWsCloseHook(ws, clientId) {
78
+ if (!ws || typeof ws.on !== "function") return;
79
+ if (wsCloseHooked.has(ws)) return;
80
+ wsCloseHooked.add(ws);
81
+ ws.on("close", () => {
82
+ for (const [id, entry] of inFlightStreams) {
83
+ if (entry.clientId === clientId) {
84
+ unwindStream(id, "ws_close").catch(() => {});
85
+ }
86
+ }
87
+ });
88
+ }
89
+
90
+ const originalDispatcher = server._dispatcher;
91
+ server._dispatcher = {
92
+ async dispatch(clientId, ws, message) {
93
+ const { id, type } = message || {};
94
+ ensureWsCloseHook(ws, clientId);
95
+
96
+ if (id && typeof type === "string" && type.endsWith(".cancel")) {
97
+ await unwindStream(id, "client_cancel");
98
+ return;
99
+ }
100
+
101
+ if (id && type && topicHandlers.has(type)) {
102
+ const client = server.clients.get(clientId);
103
+ if (server.token && (!client || !client.authenticated)) {
104
+ server._send(ws, {
105
+ id,
106
+ type: "error",
107
+ code: "AUTH_REQUIRED",
108
+ message: "Authentication required. Send an auth message first.",
109
+ });
110
+ return;
111
+ }
112
+
113
+ const handler = topicHandlers.get(type);
114
+ try {
115
+ const ret = handler(message, {
116
+ topic: type,
117
+ id,
118
+ server,
119
+ ws,
120
+ clientId,
121
+ });
122
+
123
+ if (ret && typeof ret[Symbol.asyncIterator] === "function") {
124
+ inFlightStreams.set(id, { gen: ret, clientId });
125
+ let finalReturn = null;
126
+ try {
127
+ for (;;) {
128
+ const step = await ret.next();
129
+ if (step.done) {
130
+ finalReturn = step.value ?? null;
131
+ break;
132
+ }
133
+ if (ws.readyState !== 1) {
134
+ await unwindStream(id, "readyState_closed");
135
+ return;
136
+ }
137
+ server._send(ws, {
138
+ id,
139
+ type: `${type}.chunk`,
140
+ ok: true,
141
+ chunk: step.value ?? null,
142
+ });
143
+ }
144
+ server._send(ws, {
145
+ id,
146
+ type: `${type}.result`,
147
+ ok: true,
148
+ result: finalReturn,
149
+ });
150
+ } catch (streamErr) {
151
+ server._send(ws, {
152
+ id,
153
+ type: `${type}.result`,
154
+ ok: false,
155
+ error:
156
+ streamErr instanceof Error
157
+ ? streamErr.message
158
+ : String(streamErr),
159
+ });
160
+ } finally {
161
+ inFlightStreams.delete(id);
162
+ }
163
+ return;
164
+ }
165
+
166
+ const result = await ret;
167
+ server._send(ws, {
168
+ id,
169
+ type: `${type}.result`,
170
+ ok: true,
171
+ result: result ?? null,
172
+ });
173
+ } catch (err) {
174
+ server._send(ws, {
175
+ id,
176
+ type: `${type}.result`,
177
+ ok: false,
178
+ error: err instanceof Error ? err.message : String(err),
179
+ });
180
+ }
181
+ return;
182
+ }
183
+
184
+ return originalDispatcher.dispatch(clientId, ws, message);
185
+ },
186
+ };
187
+
188
+ function register(topic, fn) {
189
+ if (typeof topic !== "string" || !topic) {
190
+ throw new TypeError(
191
+ "attachTopicHandlers: topic must be a non-empty string",
192
+ );
193
+ }
194
+ if (typeof fn !== "function") {
195
+ throw new TypeError("attachTopicHandlers: handler must be a function");
196
+ }
197
+ topicHandlers.set(topic, fn);
198
+ }
199
+
200
+ function broadcast(frame) {
201
+ if (!frame || typeof frame !== "object") return;
202
+ try {
203
+ server._broadcast(frame);
204
+ } catch {
205
+ // _send is readyState-guarded inside the server; the only path here
206
+ // would be an internal CLI ws-server bug. Don't crash the caller.
207
+ }
208
+ }
209
+
210
+ return { register, broadcast, topicHandlers };
211
+ }