@jingyi0605/codingns4dsh 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/README.md +1114 -0
  2. package/assets/provider-icons/claude-code.png +0 -0
  3. package/assets/provider-icons/codex.png +0 -0
  4. package/assets/provider-icons/command-code.svg +5 -0
  5. package/assets/provider-icons/deepseek-harness.svg +9 -0
  6. package/assets/provider-icons/gemini.png +0 -0
  7. package/assets/provider-icons/grok.png +0 -0
  8. package/assets/provider-icons/kimi.png +0 -0
  9. package/assets/provider-icons/opencode.png +0 -0
  10. package/assets/provider-icons/pi.svg +7 -0
  11. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +97 -0
  12. package/data/build/dist/bootstrap/dsh-connection-adapter.js +169 -0
  13. package/data/build/dist/bootstrap/index.d.ts +38 -0
  14. package/data/build/dist/bootstrap/index.js +44 -0
  15. package/data/build/dist/client/account-bar.d.ts +9 -0
  16. package/data/build/dist/client/account-bar.js +398 -0
  17. package/data/build/dist/client/bundle.js +26167 -0
  18. package/data/build/dist/client/cli-catalog.d.ts +24 -0
  19. package/data/build/dist/client/cli-catalog.js +108 -0
  20. package/data/build/dist/client/cli-slots.d.ts +38 -0
  21. package/data/build/dist/client/cli-slots.js +297 -0
  22. package/data/build/dist/client/debug/ui.d.ts +12 -0
  23. package/data/build/dist/client/debug/ui.js +451 -0
  24. package/data/build/dist/client/dsh-h5-bootstrap.d.ts +64 -0
  25. package/data/build/dist/client/dsh-h5-bootstrap.js +293 -0
  26. package/data/build/dist/client/dsh-runtime-version.d.ts +3 -0
  27. package/data/build/dist/client/dsh-runtime-version.js +11 -0
  28. package/data/build/dist/client/external-tool-stream.d.ts +24 -0
  29. package/data/build/dist/client/external-tool-stream.js +114 -0
  30. package/data/build/dist/client/features/cli-adapters.d.ts +7 -0
  31. package/data/build/dist/client/features/cli-adapters.js +215 -0
  32. package/data/build/dist/client/features/debug.d.ts +4 -0
  33. package/data/build/dist/client/features/debug.js +24 -0
  34. package/data/build/dist/client/features/index.d.ts +33 -0
  35. package/data/build/dist/client/features/index.js +44 -0
  36. package/data/build/dist/client/features/lan-access-panel.d.ts +5 -0
  37. package/data/build/dist/client/features/lan-access-panel.js +146 -0
  38. package/data/build/dist/client/features/lan-access.d.ts +9 -0
  39. package/data/build/dist/client/features/lan-access.js +30 -0
  40. package/data/build/dist/client/features/login-protection-panel.d.ts +5 -0
  41. package/data/build/dist/client/features/login-protection-panel.js +91 -0
  42. package/data/build/dist/client/features/login-protection-session.d.ts +6 -0
  43. package/data/build/dist/client/features/login-protection-session.js +25 -0
  44. package/data/build/dist/client/features/login-protection.d.ts +4 -0
  45. package/data/build/dist/client/features/login-protection.js +23 -0
  46. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +9 -0
  47. package/data/build/dist/client/features/reverse-proxy-panel.js +159 -0
  48. package/data/build/dist/client/features/reverse-proxy.d.ts +12 -0
  49. package/data/build/dist/client/features/reverse-proxy.js +112 -0
  50. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +5 -0
  51. package/data/build/dist/client/features/terminal-enhancement-panel.js +160 -0
  52. package/data/build/dist/client/features/terminal-enhancement.d.ts +4 -0
  53. package/data/build/dist/client/features/terminal-enhancement.js +24 -0
  54. package/data/build/dist/client/features/types.d.ts +58 -0
  55. package/data/build/dist/client/features/types.js +2 -0
  56. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +5 -0
  57. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +66 -0
  58. package/data/build/dist/client/features/workspace-session-enhancement.d.ts +4 -0
  59. package/data/build/dist/client/features/workspace-session-enhancement.js +101 -0
  60. package/data/build/dist/client/h5-bootstrap-entry.d.ts +2 -0
  61. package/data/build/dist/client/h5-bootstrap-entry.js +17 -0
  62. package/data/build/dist/client/index.d.ts +31 -0
  63. package/data/build/dist/client/index.js +98 -0
  64. package/data/build/dist/client/lan-access.d.ts +28 -0
  65. package/data/build/dist/client/lan-access.js +68 -0
  66. package/data/build/dist/client/locale.d.ts +20 -0
  67. package/data/build/dist/client/locale.js +393 -0
  68. package/data/build/dist/client/provider-icon-assets.d.ts +2 -0
  69. package/data/build/dist/client/provider-icon-assets.js +23 -0
  70. package/data/build/dist/client/provider-icons.d.ts +19 -0
  71. package/data/build/dist/client/provider-icons.js +39 -0
  72. package/data/build/dist/client/remote-web-context.d.ts +57 -0
  73. package/data/build/dist/client/remote-web-context.js +939 -0
  74. package/data/build/dist/client/session-adapter-cache.d.ts +18 -0
  75. package/data/build/dist/client/session-adapter-cache.js +84 -0
  76. package/data/build/dist/client/settings-bridge.d.ts +32 -0
  77. package/data/build/dist/client/settings-bridge.js +126 -0
  78. package/data/build/dist/client/settings-section.d.ts +30 -0
  79. package/data/build/dist/client/settings-section.js +93 -0
  80. package/data/build/dist/client/subscription-slot.d.ts +18 -0
  81. package/data/build/dist/client/subscription-slot.js +223 -0
  82. package/data/build/dist/client/terminal/index.d.ts +4 -0
  83. package/data/build/dist/client/terminal/index.js +3 -0
  84. package/data/build/dist/client/terminal/model.d.ts +116 -0
  85. package/data/build/dist/client/terminal/model.js +435 -0
  86. package/data/build/dist/client/terminal/styles.d.ts +21 -0
  87. package/data/build/dist/client/terminal/styles.js +51 -0
  88. package/data/build/dist/client/terminal/ui.d.ts +19 -0
  89. package/data/build/dist/client/terminal/ui.js +257 -0
  90. package/data/build/dist/client/terminal/xterm-view.d.ts +13 -0
  91. package/data/build/dist/client/terminal/xterm-view.js +213 -0
  92. package/data/build/dist/client/theme.d.ts +56 -0
  93. package/data/build/dist/client/theme.js +186 -0
  94. package/data/build/dist/client/workspace-session-archive-dom.d.ts +29 -0
  95. package/data/build/dist/client/workspace-session-archive-dom.js +525 -0
  96. package/data/build/dist/client/workspace-session-fiber.d.ts +3 -0
  97. package/data/build/dist/client/workspace-session-fiber.js +33 -0
  98. package/data/build/dist/client/workspace-session-logo-dom.d.ts +26 -0
  99. package/data/build/dist/client/workspace-session-logo-dom.js +128 -0
  100. package/data/build/dist/features/index.d.ts +3 -0
  101. package/data/build/dist/features/index.js +2 -0
  102. package/data/build/dist/features/registry.d.ts +74 -0
  103. package/data/build/dist/features/registry.js +382 -0
  104. package/data/build/dist/features/resource-scope/index.d.ts +39 -0
  105. package/data/build/dist/features/resource-scope/index.js +161 -0
  106. package/data/build/dist/host/auth-session.d.ts +42 -0
  107. package/data/build/dist/host/auth-session.js +186 -0
  108. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +14 -0
  109. package/data/build/dist/host/cli-adapters/claude-driver.js +103 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +45 -0
  111. package/data/build/dist/host/cli-adapters/codex-driver.js +534 -0
  112. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +38 -0
  113. package/data/build/dist/host/cli-adapters/command-code-driver.js +394 -0
  114. package/data/build/dist/host/cli-adapters/command-code-subscription.d.ts +19 -0
  115. package/data/build/dist/host/cli-adapters/command-code-subscription.js +126 -0
  116. package/data/build/dist/host/cli-adapters/driver.d.ts +41 -0
  117. package/data/build/dist/host/cli-adapters/driver.js +2 -0
  118. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +37 -0
  119. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +180 -0
  120. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +48 -0
  121. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +373 -0
  122. package/data/build/dist/host/cli-adapters/feature.d.ts +7 -0
  123. package/data/build/dist/host/cli-adapters/feature.js +335 -0
  124. package/data/build/dist/host/cli-adapters/gemini-driver.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/gemini-driver.js +427 -0
  126. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +38 -0
  127. package/data/build/dist/host/cli-adapters/grok-driver.js +372 -0
  128. package/data/build/dist/host/cli-adapters/http-sse-client.d.ts +22 -0
  129. package/data/build/dist/host/cli-adapters/http-sse-client.js +99 -0
  130. package/data/build/dist/host/cli-adapters/interaction-events.d.ts +11 -0
  131. package/data/build/dist/host/cli-adapters/interaction-events.js +80 -0
  132. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +65 -0
  133. package/data/build/dist/host/cli-adapters/json-rpc-process.js +256 -0
  134. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts +21 -0
  135. package/data/build/dist/host/cli-adapters/kimi-driver.js +382 -0
  136. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +22 -0
  137. package/data/build/dist/host/cli-adapters/model-catalog.js +89 -0
  138. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +55 -0
  139. package/data/build/dist/host/cli-adapters/opencode-driver.js +728 -0
  140. package/data/build/dist/host/cli-adapters/pi-driver.d.ts +43 -0
  141. package/data/build/dist/host/cli-adapters/pi-driver.js +312 -0
  142. package/data/build/dist/host/cli-adapters/process-utils.d.ts +16 -0
  143. package/data/build/dist/host/cli-adapters/process-utils.js +27 -0
  144. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +82 -0
  145. package/data/build/dist/host/cli-adapters/provider-subscription.js +610 -0
  146. package/data/build/dist/host/cli-adapters/registry.d.ts +95 -0
  147. package/data/build/dist/host/cli-adapters/registry.js +659 -0
  148. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +24 -0
  149. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +119 -0
  150. package/data/build/dist/host/cli-adapters/session-probe.d.ts +20 -0
  151. package/data/build/dist/host/cli-adapters/session-probe.js +139 -0
  152. package/data/build/dist/host/cli-adapters/session-store.d.ts +68 -0
  153. package/data/build/dist/host/cli-adapters/session-store.js +180 -0
  154. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +45 -0
  155. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +205 -0
  156. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +25 -0
  157. package/data/build/dist/host/cli-adapters/stream-normalizer.js +83 -0
  158. package/data/build/dist/host/cli-adapters/tool-observation.d.ts +9 -0
  159. package/data/build/dist/host/cli-adapters/tool-observation.js +39 -0
  160. package/data/build/dist/host/control-api-client.d.ts +84 -0
  161. package/data/build/dist/host/control-api-client.js +130 -0
  162. package/data/build/dist/host/credential-store.d.ts +54 -0
  163. package/data/build/dist/host/credential-store.js +114 -0
  164. package/data/build/dist/host/debug.d.ts +79 -0
  165. package/data/build/dist/host/debug.js +295 -0
  166. package/data/build/dist/host/dsh-device-runtime.d.ts +39 -0
  167. package/data/build/dist/host/dsh-device-runtime.js +118 -0
  168. package/data/build/dist/host/dsh-gateway-feature.d.ts +5 -0
  169. package/data/build/dist/host/dsh-gateway-feature.js +55 -0
  170. package/data/build/dist/host/dsh-runtime-version.d.ts +12 -0
  171. package/data/build/dist/host/dsh-runtime-version.js +69 -0
  172. package/data/build/dist/host/features/auth.d.ts +11 -0
  173. package/data/build/dist/host/features/auth.js +273 -0
  174. package/data/build/dist/host/features/debug.d.ts +5 -0
  175. package/data/build/dist/host/features/debug.js +90 -0
  176. package/data/build/dist/host/features/host-status.d.ts +12 -0
  177. package/data/build/dist/host/features/host-status.js +54 -0
  178. package/data/build/dist/host/features/index.d.ts +19 -0
  179. package/data/build/dist/host/features/index.js +29 -0
  180. package/data/build/dist/host/features/lan-access-dsh.d.ts +8 -0
  181. package/data/build/dist/host/features/lan-access-dsh.js +45 -0
  182. package/data/build/dist/host/features/terminal-process.d.ts +5 -0
  183. package/data/build/dist/host/features/terminal-process.js +110 -0
  184. package/data/build/dist/host/features/terminal-status.d.ts +18 -0
  185. package/data/build/dist/host/features/terminal-status.js +56 -0
  186. package/data/build/dist/host/features/types.d.ts +41 -0
  187. package/data/build/dist/host/features/types.js +2 -0
  188. package/data/build/dist/host/index.d.ts +43 -0
  189. package/data/build/dist/host/index.js +135 -0
  190. package/data/build/dist/host/lan-access-dsh.d.ts +83 -0
  191. package/data/build/dist/host/lan-access-dsh.js +831 -0
  192. package/data/build/dist/host/native-session-bridge.d.ts +130 -0
  193. package/data/build/dist/host/native-session-bridge.js +317 -0
  194. package/data/build/dist/host/relay-tunnel-runtime.d.ts +97 -0
  195. package/data/build/dist/host/relay-tunnel-runtime.js +361 -0
  196. package/data/build/dist/host/remote-web-runtime.d.ts +78 -0
  197. package/data/build/dist/host/remote-web-runtime.js +559 -0
  198. package/data/build/dist/host/rpc-table.d.ts +32 -0
  199. package/data/build/dist/host/rpc-table.js +47 -0
  200. package/data/build/dist/host/rpc.d.ts +18 -0
  201. package/data/build/dist/host/rpc.js +248 -0
  202. package/data/build/dist/host/settings.d.ts +19 -0
  203. package/data/build/dist/host/settings.js +80 -0
  204. package/data/build/dist/host/terminal/attachment-registry.d.ts +17 -0
  205. package/data/build/dist/host/terminal/attachment-registry.js +49 -0
  206. package/data/build/dist/host/terminal/backends/conpty-backend.d.ts +39 -0
  207. package/data/build/dist/host/terminal/backends/conpty-backend.js +160 -0
  208. package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts +36 -0
  209. package/data/build/dist/host/terminal/backends/local-pty-backend.js +178 -0
  210. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -0
  211. package/data/build/dist/host/terminal/backends/tmux-backend.js +191 -0
  212. package/data/build/dist/host/terminal/broker/conpty-broker-client.d.ts +27 -0
  213. package/data/build/dist/host/terminal/broker/conpty-broker-client.js +154 -0
  214. package/data/build/dist/host/terminal/broker/conpty-broker-process.d.ts +10 -0
  215. package/data/build/dist/host/terminal/broker/conpty-broker-process.js +147 -0
  216. package/data/build/dist/host/terminal/broker/conpty-broker-protocol.d.ts +92 -0
  217. package/data/build/dist/host/terminal/broker/conpty-broker-protocol.js +134 -0
  218. package/data/build/dist/host/terminal/controller-factory.d.ts +37 -0
  219. package/data/build/dist/host/terminal/controller-factory.js +82 -0
  220. package/data/build/dist/host/terminal/index.d.ts +17 -0
  221. package/data/build/dist/host/terminal/index.js +17 -0
  222. package/data/build/dist/host/terminal/runtime-adapter.d.ts +77 -0
  223. package/data/build/dist/host/terminal/runtime-adapter.js +18 -0
  224. package/data/build/dist/host/terminal/runtime-manager.d.ts +41 -0
  225. package/data/build/dist/host/terminal/runtime-manager.js +179 -0
  226. package/data/build/dist/host/terminal/shell-detection.d.ts +23 -0
  227. package/data/build/dist/host/terminal/shell-detection.js +105 -0
  228. package/data/build/dist/host/terminal/startup.d.ts +33 -0
  229. package/data/build/dist/host/terminal/startup.js +100 -0
  230. package/data/build/dist/host/terminal/terminal-controller.d.ts +78 -0
  231. package/data/build/dist/host/terminal/terminal-controller.js +324 -0
  232. package/data/build/dist/host/terminal/terminal-process-service.d.ts +30 -0
  233. package/data/build/dist/host/terminal/terminal-process-service.js +194 -0
  234. package/data/build/dist/host/terminal/terminal-process-store.d.ts +45 -0
  235. package/data/build/dist/host/terminal/terminal-process-store.js +183 -0
  236. package/data/build/dist/host/terminal/terminal-service.d.ts +87 -0
  237. package/data/build/dist/host/terminal/terminal-service.js +528 -0
  238. package/data/build/dist/host/terminal/terminal-store.d.ts +49 -0
  239. package/data/build/dist/host/terminal/terminal-store.js +242 -0
  240. package/data/build/dist/index.d.ts +37 -0
  241. package/data/build/dist/index.js +26 -0
  242. package/data/build/dist/shared/contracts/auth.d.ts +122 -0
  243. package/data/build/dist/shared/contracts/auth.js +8 -0
  244. package/data/build/dist/shared/contracts/cli-adapter.d.ts +192 -0
  245. package/data/build/dist/shared/contracts/cli-adapter.js +2 -0
  246. package/data/build/dist/shared/contracts/command-code.d.ts +5 -0
  247. package/data/build/dist/shared/contracts/command-code.js +2 -0
  248. package/data/build/dist/shared/contracts/config.d.ts +113 -0
  249. package/data/build/dist/shared/contracts/config.js +86 -0
  250. package/data/build/dist/shared/contracts/debug-config.d.ts +28 -0
  251. package/data/build/dist/shared/contracts/debug-config.js +78 -0
  252. package/data/build/dist/shared/contracts/debug.d.ts +75 -0
  253. package/data/build/dist/shared/contracts/debug.js +121 -0
  254. package/data/build/dist/shared/contracts/dsh-device.d.ts +72 -0
  255. package/data/build/dist/shared/contracts/dsh-device.js +2 -0
  256. package/data/build/dist/shared/contracts/errors.d.ts +33 -0
  257. package/data/build/dist/shared/contracts/errors.js +41 -0
  258. package/data/build/dist/shared/contracts/feature.d.ts +71 -0
  259. package/data/build/dist/shared/contracts/feature.js +2 -0
  260. package/data/build/dist/shared/contracts/host-status.d.ts +14 -0
  261. package/data/build/dist/shared/contracts/host-status.js +2 -0
  262. package/data/build/dist/shared/contracts/lan-access-dsh.d.ts +30 -0
  263. package/data/build/dist/shared/contracts/lan-access-dsh.js +2 -0
  264. package/data/build/dist/shared/contracts/peer-host.d.ts +37 -0
  265. package/data/build/dist/shared/contracts/peer-host.js +9 -0
  266. package/data/build/dist/shared/contracts/signaling.d.ts +67 -0
  267. package/data/build/dist/shared/contracts/signaling.js +2 -0
  268. package/data/build/dist/shared/contracts/subscription.d.ts +69 -0
  269. package/data/build/dist/shared/contracts/subscription.js +2 -0
  270. package/data/build/dist/shared/contracts/terminal-process.d.ts +65 -0
  271. package/data/build/dist/shared/contracts/terminal-process.js +2 -0
  272. package/data/build/dist/shared/contracts/terminal.d.ts +127 -0
  273. package/data/build/dist/shared/contracts/terminal.js +2 -0
  274. package/data/build/dist/shared/contracts/transport.d.ts +45 -0
  275. package/data/build/dist/shared/contracts/transport.js +3 -0
  276. package/data/build/dist/shared/contracts/version.d.ts +19 -0
  277. package/data/build/dist/shared/contracts/version.js +74 -0
  278. package/data/build/dist/shared/index.d.ts +22 -0
  279. package/data/build/dist/shared/index.js +9 -0
  280. package/data/build/dist/transport/carrier.d.ts +38 -0
  281. package/data/build/dist/transport/carrier.js +268 -0
  282. package/data/build/dist/transport/debug.d.ts +22 -0
  283. package/data/build/dist/transport/debug.js +57 -0
  284. package/data/build/dist/transport/dsh-envelope.d.ts +41 -0
  285. package/data/build/dist/transport/dsh-envelope.js +107 -0
  286. package/data/build/dist/transport/dsh-gateway.d.ts +64 -0
  287. package/data/build/dist/transport/dsh-gateway.js +254 -0
  288. package/data/build/dist/transport/dsh-session.d.ts +55 -0
  289. package/data/build/dist/transport/dsh-session.js +256 -0
  290. package/data/build/dist/transport/dsh-transport.d.ts +57 -0
  291. package/data/build/dist/transport/dsh-transport.js +125 -0
  292. package/data/build/dist/transport/frame.d.ts +132 -0
  293. package/data/build/dist/transport/frame.js +168 -0
  294. package/data/build/dist/transport/index.d.ts +11 -0
  295. package/data/build/dist/transport/index.js +11 -0
  296. package/data/build/dist/transport/multiplexer.d.ts +63 -0
  297. package/data/build/dist/transport/multiplexer.js +323 -0
  298. package/data/build/dist/transport/webrtc-client.d.ts +65 -0
  299. package/data/build/dist/transport/webrtc-client.js +255 -0
  300. package/data/build/dist/transport/webrtc-host.d.ts +58 -0
  301. package/data/build/dist/transport/webrtc-host.js +252 -0
  302. package/data/build/dist/typert.host.d.ts +25 -0
  303. package/data/build/dist/typert.host.js +150 -0
  304. package/dsh.bundle.patch +12 -0
  305. package/package.json +135 -0
  306. package/profile/README.md +27 -0
  307. package/profile/cordis.patch.yml +2 -0
  308. package/profile/package.json +20 -0
  309. package/profile/scripts/check-dsh-install.mjs +71 -0
  310. package/profile/version.json +5 -0
  311. package/scripts/check-dsh-install.mjs +88 -0
  312. package/version.json +6 -0
@@ -0,0 +1,559 @@
1
+ import { createDshTransportDebugLogger } from '../transport/debug.js';
2
+ import WebSocket from 'ws';
3
+ /**
4
+ * 把 Host 自己的 DSH Web 暴露为 DSH Gateway 的 web 频道。
5
+ *
6
+ * 这个模块不接受任意 URL,也不把 Host 凭据传给 Client;具体本地 DSH Web
7
+ * 访问由 provider 完成,便于真实运行时和 Fake 测试分别注入。
8
+ */
9
+ export function createRemoteWebRuntimeFeature(options) {
10
+ const maxAssetBytes = options.maxAssetBytes ?? 16 * 1024 * 1024;
11
+ const debug = options.debug ?? createDshTransportDebugLogger({ side: 'host', component: 'remote-web' });
12
+ const sessions = new Map();
13
+ // 显式关闭的 session 不能被 WebSocket 惰性恢复;generation 重建造成的
14
+ // 内存丢失则仍允许按原 ID恢复,避免旧 iframe 必须重新加载整个页面。
15
+ const closedSessions = new Set();
16
+ const sockets = new Map();
17
+ const socketCleanups = new Map();
18
+ const closeStream = (streamId) => {
19
+ socketCleanups.get(streamId)?.();
20
+ socketCleanups.delete(streamId);
21
+ const socket = sockets.get(streamId);
22
+ sockets.delete(streamId);
23
+ try {
24
+ socket?.close(1000, 'DSH Web stream closed');
25
+ }
26
+ catch { /* 关闭路径尽力而为 */ }
27
+ };
28
+ const feature = {
29
+ channel: 'web',
30
+ canHandle: (envelope) => typeof envelope.meta.operation === 'string' && envelope.meta.operation.startsWith('web.'),
31
+ handleStream: async (context) => {
32
+ const operation = readOperation(context);
33
+ debug.log('web.request.start', { operation, streamId: context.envelope.streamId, bodyBytes: context.envelope.body?.byteLength ?? 0 });
34
+ if (operation === 'web.ws.open') {
35
+ try {
36
+ await openWebSocketStream(context);
37
+ }
38
+ catch (error) {
39
+ debug.log('web.websocket.open.outer.error', {
40
+ operation,
41
+ streamId: context.envelope.streamId,
42
+ error: error instanceof Error ? error.message : String(error),
43
+ });
44
+ context.send('stream.error', { errorCode: error instanceof RemoteWebRuntimeError ? error.code : 'WEB_SOCKET_OPEN_FAILED', detail: error instanceof Error ? error.message : 'DSH WebSocket 打开失败', retryable: true });
45
+ context.close();
46
+ }
47
+ return;
48
+ }
49
+ const request = readJson(context);
50
+ try {
51
+ switch (operation) {
52
+ case 'web.session.open': {
53
+ const session = await options.provider.openSession(readOptionalRecord(request));
54
+ sessions.set(session.sessionId, session);
55
+ closedSessions.delete(session.sessionId);
56
+ debug.log('web.session.opened', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId });
57
+ context.send('web.session.response', { encoding: 'json' }, encodeJson(session));
58
+ break;
59
+ }
60
+ case 'web.session.close': {
61
+ const session = requireSession(sessions, request);
62
+ debug.log('web.session.close', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId });
63
+ sessions.delete(session.sessionId);
64
+ closedSessions.add(session.sessionId);
65
+ context.send('web.session.close.response', { encoding: 'json' }, encodeJson({ closed: true }));
66
+ break;
67
+ }
68
+ case 'web.boot.get': {
69
+ const session = requireSession(sessions, request);
70
+ debug.log('web.boot.start', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId });
71
+ const boot = await options.provider.getBoot(session);
72
+ debug.log('web.boot.done', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId, htmlBytes: boot.html.length });
73
+ context.send('web.boot.response', { encoding: 'json' }, encodeJson(boot));
74
+ break;
75
+ }
76
+ case 'web.asset.get': {
77
+ const session = requireSession(sessions, request);
78
+ const path = readPath(request);
79
+ debug.log('web.asset.start', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId, path });
80
+ const asset = await options.provider.getAsset(session, path);
81
+ assertAssetSize(asset, maxAssetBytes);
82
+ debug.log('web.asset.done', { operation, streamId: context.envelope.streamId, sessionId: session.sessionId, path, bytes: asset.body.byteLength });
83
+ context.send('web.asset.response', { contentType: asset.contentType, ...(asset.etag ? { etag: asset.etag } : {}) }, asset.body);
84
+ break;
85
+ }
86
+ case 'web.plugin.manifest': {
87
+ const session = requireSession(sessions, request);
88
+ context.send('web.plugin.manifest.response', { encoding: 'json' }, encodeJson(await options.provider.getPluginManifest(session)));
89
+ break;
90
+ }
91
+ case 'web.plugin.bundle': {
92
+ const session = requireSession(sessions, request);
93
+ const record = readRecord(request);
94
+ const pluginId = readRequiredString(record.pluginId, 'pluginId');
95
+ const path = readRequiredString(record.path, 'path');
96
+ const asset = await options.provider.getPluginBundle(session, pluginId, path);
97
+ assertAssetSize(asset, maxAssetBytes);
98
+ context.send('web.plugin.bundle.response', { contentType: asset.contentType, ...(asset.etag ? { etag: asset.etag } : {}) }, asset.body);
99
+ break;
100
+ }
101
+ case 'web.request': {
102
+ const session = requireSession(sessions, request);
103
+ if (!options.provider.request)
104
+ throw new RemoteWebRuntimeError('WEB_REQUEST_UNSUPPORTED', 'Host 未提供 DSH Web HTTP 请求能力');
105
+ const record = readRecord(request);
106
+ const path = readPath(record);
107
+ const result = await options.provider.request(session, {
108
+ path,
109
+ ...(typeof record.method === 'string' ? { method: record.method } : {}),
110
+ ...(Array.isArray(record.headers) ? { headers: readHeaders(record.headers) } : {}),
111
+ ...(typeof record.body === 'string' ? { body: record.body } : {}),
112
+ });
113
+ context.send('web.request.response', { encoding: 'json' }, encodeJson(result));
114
+ break;
115
+ }
116
+ case 'web.debug': {
117
+ const record = readRecord(request);
118
+ debug.log('web.client.debug', {
119
+ streamId: context.envelope.streamId,
120
+ event: typeof record.event === 'string' ? record.event : 'unknown',
121
+ fields: isRecord(record.fields) ? record.fields : {},
122
+ });
123
+ context.send('web.debug.response', { encoding: 'json' }, encodeJson({ ok: true }));
124
+ break;
125
+ }
126
+ default:
127
+ throw new RemoteWebRuntimeError('WEB_OPERATION_UNSUPPORTED', `不支持的 DSH Web operation: ${operation}`);
128
+ }
129
+ }
130
+ catch (error) {
131
+ debug.log('web.request.error', { operation, streamId: context.envelope.streamId, error: error instanceof Error ? error.message : String(error) });
132
+ context.send('stream.error', { errorCode: error instanceof RemoteWebRuntimeError ? error.code : 'WEB_RUNTIME_FAILED', detail: error instanceof Error ? error.message : 'DSH Web Runtime 失败', retryable: false });
133
+ }
134
+ finally {
135
+ if (operation !== 'web.ws.open') {
136
+ context.close();
137
+ }
138
+ }
139
+ },
140
+ handleMessage: async (context, envelope) => {
141
+ if (envelope.type === 'web.ws.data') {
142
+ const socket = sockets.get(envelope.streamId);
143
+ if (!socket)
144
+ throw new RemoteWebRuntimeError('WEB_SOCKET_NOT_FOUND', 'DSH WebSocket 流不存在');
145
+ const body = envelope.body ?? new Uint8Array();
146
+ // Remote mux 是 JSON 文本协议。DataChannel/DSH Envelope 的 body
147
+ // 始终是二进制,但必须依据 encoding 恢复为本地 WebSocket 文本帧,
148
+ // 否则 DSH Web 会以 1003 (text messages required) 立即关闭连接。
149
+ const payload = envelope.meta.encoding === 'text'
150
+ ? new TextDecoder('utf-8', { fatal: true }).decode(body)
151
+ : body;
152
+ socket.send(payload);
153
+ debug.log('web.ws.data', {
154
+ streamId: envelope.streamId,
155
+ bytes: body.byteLength,
156
+ encoding: envelope.meta.encoding === 'text' ? 'text' : 'binary',
157
+ ...(typeof payload === 'string' ? { preview: payload.slice(0, 240) } : {}),
158
+ });
159
+ return;
160
+ }
161
+ if (envelope.type === 'stream.cancel' || envelope.type === 'stream.close' || envelope.type === 'web.ws.close') {
162
+ closeStream(envelope.streamId);
163
+ }
164
+ },
165
+ };
166
+ return feature;
167
+ async function openWebSocketStream(context) {
168
+ const request = readRecord(readJson(context));
169
+ // Host 重启或 generation 恢复后,旧 iframe 可能仍持有上一个 sessionId。
170
+ // 该 session 只代表本地 DSH Web 的短期句柄,不是认证凭据;在当前已认证
171
+ // Tunnel 内按原 ID 惰性恢复,避免浏览器必须先销毁整个 iframe 才能重连。
172
+ const session = await ensureWebSession(sessions, closedSessions, options.provider, request);
173
+ const path = readPath(request);
174
+ debug.log('web.websocket.open.start', {
175
+ streamId: context.envelope.streamId,
176
+ sessionId: session.sessionId,
177
+ path,
178
+ });
179
+ let socket;
180
+ try {
181
+ socket = await options.provider.openWebSocket(session, path);
182
+ debug.log('web.websocket.factory.done', {
183
+ streamId: context.envelope.streamId,
184
+ sessionId: session.sessionId,
185
+ path,
186
+ readyState: socket.readyState,
187
+ });
188
+ await waitForWebSocketOpen(socket);
189
+ }
190
+ catch (error) {
191
+ debug.log('web.websocket.open.error', {
192
+ streamId: context.envelope.streamId,
193
+ sessionId: session.sessionId,
194
+ path,
195
+ error: error instanceof Error ? error.message : String(error),
196
+ errorName: error instanceof Error ? error.name : typeof error,
197
+ });
198
+ context.send('stream.error', { errorCode: 'WEB_SOCKET_OPEN_FAILED', detail: error instanceof Error ? error.message : 'DSH WebSocket 打开失败', retryable: true });
199
+ context.close();
200
+ return;
201
+ }
202
+ sockets.set(context.envelope.streamId, socket);
203
+ const cleanup = () => {
204
+ socket.removeEventListener('message', onMessage);
205
+ socket.removeEventListener('close', onClose);
206
+ socket.removeEventListener('error', onError);
207
+ };
208
+ const onMessage = (event) => {
209
+ const value = event.data;
210
+ // Node ws 在收到文本帧时,部分版本的 EventTarget 适配会暴露 Buffer。
211
+ // /api/remote.mux 是 JSON 文本协议,必须在 Host 侧恢复 encoding=text,
212
+ // 否则 H5 Bridge 会把 JSON 当 ArrayBuffer,$events 首帧无法解析并立即关闭。
213
+ if (path === '/api/remote.mux') {
214
+ const text = decodeTextWebSocketValue(value);
215
+ if (text !== undefined) {
216
+ debug.log('web.websocket.message', {
217
+ streamId: context.envelope.streamId,
218
+ bytes: new TextEncoder().encode(text).byteLength,
219
+ encoding: 'text',
220
+ preview: text.slice(0, 240),
221
+ });
222
+ context.send('web.ws.data', { encoding: 'text' }, new TextEncoder().encode(text));
223
+ return;
224
+ }
225
+ }
226
+ const body = toBytes(value);
227
+ if (body) {
228
+ debug.log('web.websocket.message', { streamId: context.envelope.streamId, bytes: body.byteLength, encoding: 'binary' });
229
+ context.send('web.ws.data', { binary: true }, body);
230
+ }
231
+ else if (typeof value === 'string') {
232
+ debug.log('web.websocket.message', { streamId: context.envelope.streamId, bytes: new TextEncoder().encode(value).byteLength, encoding: 'text', preview: value.slice(0, 240) });
233
+ context.send('web.ws.data', { encoding: 'text' }, new TextEncoder().encode(value));
234
+ }
235
+ };
236
+ const onClose = (event) => {
237
+ const close = event;
238
+ debug.log('web.websocket.close', { streamId: context.envelope.streamId, code: close.code, reason: close.reason });
239
+ closeStream(context.envelope.streamId);
240
+ context.close();
241
+ };
242
+ const onError = (event) => {
243
+ const error = event;
244
+ debug.log('web.websocket.error', {
245
+ streamId: context.envelope.streamId,
246
+ sessionId: session.sessionId,
247
+ path,
248
+ detail: typeof error.message === 'string' ? error.message : error.error instanceof Error ? error.error.message : 'DSH WebSocket 连接失败',
249
+ });
250
+ context.send('stream.error', { errorCode: 'WEB_SOCKET_FAILED', detail: 'DSH WebSocket 连接失败', retryable: true });
251
+ closeStream(context.envelope.streamId);
252
+ context.close();
253
+ };
254
+ socket.addEventListener('message', onMessage);
255
+ socket.addEventListener('close', onClose);
256
+ socket.addEventListener('error', onError);
257
+ socketCleanups.set(context.envelope.streamId, cleanup);
258
+ context.send('web.ws.open.response', { encoding: 'json' }, encodeJson({ opened: true }));
259
+ await new Promise((resolve) => {
260
+ const finish = () => { cleanup(); resolve(); };
261
+ socket.addEventListener('close', finish);
262
+ socket.addEventListener('error', finish);
263
+ });
264
+ }
265
+ }
266
+ /** 访问本机 DSH Web 的默认 provider;只允许 loopback 和明确的资源路径。 */
267
+ export function createLocalDshWebRuntimeProvider(options) {
268
+ if (!Number.isInteger(options.port) || options.port <= 0 || options.port > 65535)
269
+ throw new TypeError('DSH Web 端口无效');
270
+ const fetcher = options.fetcher ?? globalThis.fetch.bind(globalThis);
271
+ const debug = createDshTransportDebugLogger({ side: 'host', component: 'web-provider' });
272
+ const baseUrl = `http://127.0.0.1:${options.port}`;
273
+ // `/codingns` 是插件自己的 DSH Connection RPC 通道;它与 `/api`
274
+ // 一样只在本机 Host 内部转发,不能因为 Web Provider 的资源白名单而被
275
+ // 当成未知路径拒绝。实际权限仍由 Connection 的浏览器认证和 RPC handler
276
+ // 共同校验,远端页面不能借此访问任意本机地址。
277
+ const allowed = options.allowedPathPrefixes ?? ['/', '/assets/', '/plugins/', '/api/', '/codingns/'];
278
+ const sessions = new Map();
279
+ let sessionCookie;
280
+ const fetchLocal = async (path, init = {}) => {
281
+ const headers = new Headers(init.headers);
282
+ // Cookie 只允许由 Host 侧认证交换产生,不能让远端页面伪造或覆盖。
283
+ if (sessionCookie !== undefined)
284
+ headers.set('cookie', sessionCookie);
285
+ else
286
+ headers.delete('cookie');
287
+ return fetcher(`${baseUrl}${path}`, { ...init, headers });
288
+ };
289
+ const ensureAuthenticated = async () => {
290
+ if (sessionCookie !== undefined || options.authenticatedUrl === undefined)
291
+ return;
292
+ debug.log('web.auth.start', { authenticatedUrlConfigured: true });
293
+ const response = await fetcher(options.authenticatedUrl, { redirect: 'manual' });
294
+ const setCookie = getSetCookie(response.headers);
295
+ if (setCookie !== undefined)
296
+ sessionCookie = setCookie;
297
+ if (response.status !== 303 || sessionCookie === undefined) {
298
+ debug.log('web.auth.error', { status: response.status, hasCookie: sessionCookie !== undefined });
299
+ throw new Error(`DSH Web 认证交换失败 (${response.status})`);
300
+ }
301
+ debug.log('web.auth.done', { status: response.status, hasCookie: true });
302
+ };
303
+ return {
304
+ async openSession(input) {
305
+ const session = { sessionId: input.sessionId?.trim() || `web_${cryptoRandomId()}`, dshVersion: options.dshVersion, ...(input.workspaceId?.trim() ? { workspaceId: input.workspaceId.trim() } : {}) };
306
+ sessions.set(session.sessionId, session);
307
+ return session;
308
+ },
309
+ async getBoot(session) {
310
+ ensureSession(sessions, session);
311
+ debug.log('web.provider.boot', { sessionId: session.sessionId, authenticated: sessionCookie !== undefined, path: normalizePath(options.bootPath ?? '/') });
312
+ await ensureAuthenticated();
313
+ const response = await fetchLocal(normalizePath(options.bootPath ?? '/'));
314
+ debug.log('web.provider.boot.response', { sessionId: session.sessionId, status: response.status });
315
+ if (!response.ok)
316
+ throw new Error(`读取 DSH Web boot 失败 (${response.status})`);
317
+ return { dshVersion: options.dshVersion, contentType: response.headers.get('content-type') ?? 'text/html; charset=utf-8', html: await response.text(), capabilities: ['boot', 'asset', 'websocket', 'plugin'] };
318
+ },
319
+ async getAsset(session, path) {
320
+ ensureSession(sessions, session);
321
+ const normalized = normalizePath(path);
322
+ if (!allowed.some((prefix) => normalized === prefix || normalized.startsWith(prefix)))
323
+ throw new Error('DSH Web 资源路径不在白名单内');
324
+ await ensureAuthenticated();
325
+ const response = await fetchLocal(normalized);
326
+ debug.log('web.provider.asset.response', { sessionId: session.sessionId, path: normalized, status: response.status });
327
+ const asset = await readAsset(response);
328
+ // 只对插件聚合脚本做轻量标记检查,确认远程 H5 拿到的是当前工作区
329
+ // 构建,而不是 DSH Web 进程缓存的旧 bundle。
330
+ if (normalized.startsWith('/plugins/??')) {
331
+ const source = new TextDecoder().decode(asset.body);
332
+ debug.log('web.provider.asset.marker', {
333
+ sessionId: session.sessionId,
334
+ path: normalized,
335
+ bytes: asset.body.byteLength,
336
+ hasRemoteTransportBinding: source.includes('remote.connection.binding.detected') || source.includes('__DSH_TRANSPORT__'),
337
+ });
338
+ }
339
+ return asset;
340
+ },
341
+ async getPluginManifest(session) {
342
+ ensureSession(sessions, session);
343
+ await ensureAuthenticated();
344
+ const response = await fetchLocal(normalizePath(options.pluginManifestPath ?? '/api/plugins/manifest'));
345
+ if (response.status === 404)
346
+ return [];
347
+ if (!response.ok)
348
+ throw new Error(`读取 DSH 插件清单失败 (${response.status})`);
349
+ return await response.json();
350
+ },
351
+ async getPluginBundle(session, _pluginId, path) { return this.getAsset(session, path); },
352
+ async request(session, input) {
353
+ ensureSession(sessions, session);
354
+ const path = normalizePath(input.path);
355
+ if (!allowed.some((prefix) => path === prefix || path.startsWith(prefix)))
356
+ throw new Error('DSH Web 请求路径不在白名单内');
357
+ const init = { method: input.method ?? 'GET' };
358
+ if (input.headers !== undefined)
359
+ init.headers = Object.fromEntries(input.headers);
360
+ if (input.body !== undefined)
361
+ init.body = input.body;
362
+ await ensureAuthenticated();
363
+ const response = await fetchLocal(path, init);
364
+ debug.log('web.provider.request.response', { sessionId: session.sessionId, path, method: input.method ?? 'GET', status: response.status });
365
+ return { status: response.status, headers: [...response.headers.entries()], body: await response.text() };
366
+ },
367
+ async openWebSocket(session, path) {
368
+ ensureSession(sessions, session);
369
+ const normalized = normalizePath(path);
370
+ if (!allowed.some((prefix) => normalized === prefix || normalized.startsWith(prefix)))
371
+ throw new Error('DSH WebSocket 路径不在白名单内');
372
+ await ensureAuthenticated();
373
+ debug.log('web.provider.websocket.start', {
374
+ sessionId: session.sessionId,
375
+ path: normalized,
376
+ url: `${baseUrl.replace(/^http:/u, 'ws:')}${normalized}`,
377
+ hasCookie: sessionCookie !== undefined,
378
+ });
379
+ // Node 的 WHATWG WebSocket 不支持自定义 Cookie 头;DSH Web 的本地连接
380
+ // 必须复用 Host 刚交换得到的认证 Cookie,因此使用 ws 提供的 Node 客户端。
381
+ const factory = options.websocketFactory ?? ((url, init) => {
382
+ return new WebSocket(url, init === undefined ? undefined : { headers: init.headers });
383
+ });
384
+ try {
385
+ const socket = factory(`${baseUrl.replace(/^http:/u, 'ws:')}${normalized}`, {
386
+ headers: {
387
+ ...(sessionCookie === undefined ? {} : { cookie: sessionCookie }),
388
+ // DSH Remote mux 按客户端约定校验 Origin;Node ws 不会自动带浏览器 Origin。
389
+ origin: baseUrl,
390
+ },
391
+ });
392
+ debug.log('web.provider.websocket.created', { sessionId: session.sessionId, path: normalized, readyState: socket.readyState });
393
+ return socket;
394
+ }
395
+ catch (error) {
396
+ debug.log('web.provider.websocket.error', { sessionId: session.sessionId, path: normalized, error: error instanceof Error ? error.message : String(error) });
397
+ throw error;
398
+ }
399
+ },
400
+ };
401
+ }
402
+ function getSetCookie(headers) {
403
+ const extended = headers;
404
+ const values = extended.getSetCookie?.() ?? (headers.get('set-cookie') === null ? [] : [headers.get('set-cookie')]);
405
+ for (const value of values) {
406
+ const match = /^([^=;\s]+=[^;]*)/u.exec(value);
407
+ if (match?.[1] !== undefined)
408
+ return match[1];
409
+ }
410
+ return undefined;
411
+ }
412
+ class RemoteWebRuntimeError extends Error {
413
+ code;
414
+ constructor(code, message) {
415
+ super(message);
416
+ this.code = code;
417
+ this.name = code;
418
+ }
419
+ }
420
+ function readOperation(context) {
421
+ const operation = context.envelope.meta.operation;
422
+ if (typeof operation !== 'string' || !operation.startsWith('web.'))
423
+ throw new RemoteWebRuntimeError('WEB_OPERATION_INVALID', 'DSH Web operation 无效');
424
+ return operation;
425
+ }
426
+ function readJson(context) {
427
+ if (!context.envelope.body || context.envelope.meta.encoding !== 'json')
428
+ return {};
429
+ try {
430
+ return JSON.parse(new TextDecoder().decode(context.envelope.body));
431
+ }
432
+ catch {
433
+ throw new RemoteWebRuntimeError('MESSAGE_INVALID', 'DSH Web 请求不是有效 JSON');
434
+ }
435
+ }
436
+ function readRecord(value) {
437
+ if (!value || typeof value !== 'object' || Array.isArray(value))
438
+ throw new RemoteWebRuntimeError('MESSAGE_INVALID', 'DSH Web 请求必须是对象');
439
+ return value;
440
+ }
441
+ function isRecord(value) {
442
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
443
+ }
444
+ function readOptionalRecord(value) {
445
+ const record = readRecord(value);
446
+ const result = {};
447
+ if (typeof record.workspaceId === 'string' && record.workspaceId.trim())
448
+ result.workspaceId = record.workspaceId.trim();
449
+ if (typeof record.sessionId === 'string' && record.sessionId.trim())
450
+ result.sessionId = record.sessionId.trim();
451
+ return result;
452
+ }
453
+ function requireSession(sessions, value) {
454
+ const record = readRecord(value);
455
+ const sessionId = readRequiredString(record.sessionId, 'sessionId');
456
+ const session = sessions.get(sessionId);
457
+ if (!session)
458
+ throw new RemoteWebRuntimeError('WEB_SESSION_NOT_FOUND', 'DSH Web Session 不存在');
459
+ return session;
460
+ }
461
+ async function ensureWebSession(sessions, closedSessions, provider, value) {
462
+ const record = readRecord(value);
463
+ const sessionId = readRequiredString(record.sessionId, 'sessionId');
464
+ if (closedSessions.has(sessionId))
465
+ throw new RemoteWebRuntimeError('WEB_SESSION_NOT_FOUND', 'DSH Web Session 不存在');
466
+ const current = sessions.get(sessionId);
467
+ if (current !== undefined)
468
+ return current;
469
+ const workspaceId = typeof record.workspaceId === 'string' && record.workspaceId.trim()
470
+ ? record.workspaceId.trim()
471
+ : undefined;
472
+ const session = await provider.openSession({ sessionId, ...(workspaceId === undefined ? {} : { workspaceId }) });
473
+ if (session.sessionId !== sessionId)
474
+ throw new RemoteWebRuntimeError('WEB_SESSION_INVALID', 'DSH Web Session ID 不一致');
475
+ sessions.set(session.sessionId, session);
476
+ return session;
477
+ }
478
+ function readPath(value) {
479
+ return normalizePath(readRequiredString(readRecord(value).path, 'path'));
480
+ }
481
+ function readRequiredString(value, name) {
482
+ if (typeof value !== 'string' || value.trim() === '')
483
+ throw new RemoteWebRuntimeError('MESSAGE_INVALID', `${name} 不能为空`);
484
+ return value.trim();
485
+ }
486
+ function readHeaders(value) {
487
+ return value.map((item) => {
488
+ if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== 'string' || typeof item[1] !== 'string')
489
+ throw new RemoteWebRuntimeError('MESSAGE_INVALID', 'DSH Web headers 无效');
490
+ return [item[0], item[1]];
491
+ });
492
+ }
493
+ function encodeJson(value) { return new TextEncoder().encode(JSON.stringify(value ?? null)); }
494
+ function assertAssetSize(asset, maxBytes) {
495
+ if (!(asset.body instanceof Uint8Array) || asset.body.byteLength > maxBytes)
496
+ throw new RemoteWebRuntimeError('WEB_ASSET_TOO_LARGE', 'DSH Web 资源超过大小上限');
497
+ }
498
+ async function readAsset(response) {
499
+ if (!response.ok)
500
+ throw new Error(`读取 DSH Web 资源失败 (${response.status})`);
501
+ return { contentType: response.headers.get('content-type') ?? 'application/octet-stream', body: new Uint8Array(await response.arrayBuffer()), ...(response.headers.get('etag') ? { etag: response.headers.get('etag') } : {}) };
502
+ }
503
+ function ensureSession(sessions, session) {
504
+ if (sessions.get(session.sessionId) !== session)
505
+ throw new Error('DSH Web Session 已失效');
506
+ }
507
+ function normalizePath(value) {
508
+ if (!value.startsWith('/') || value.includes('\\') || value.split('/').some((part) => part === '..'))
509
+ throw new Error('DSH Web 路径无效');
510
+ return value;
511
+ }
512
+ function toBytes(value) {
513
+ if (value instanceof Uint8Array)
514
+ return new Uint8Array(value);
515
+ if (value instanceof ArrayBuffer)
516
+ return new Uint8Array(value);
517
+ if (ArrayBuffer.isView(value))
518
+ return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
519
+ return null;
520
+ }
521
+ function decodeTextWebSocketValue(value) {
522
+ if (typeof value === 'string')
523
+ return value;
524
+ const bytes = toBytes(value);
525
+ if (!bytes)
526
+ return undefined;
527
+ try {
528
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
529
+ JSON.parse(text);
530
+ return text;
531
+ }
532
+ catch {
533
+ return undefined;
534
+ }
535
+ }
536
+ function cryptoRandomId() {
537
+ const random = globalThis.crypto?.randomUUID?.();
538
+ return random ?? Math.random().toString(36).slice(2);
539
+ }
540
+ function waitForWebSocketOpen(socket, timeoutMs = 15_000) {
541
+ if (socket.readyState === undefined || socket.readyState === 1)
542
+ return Promise.resolve();
543
+ return new Promise((resolve, reject) => {
544
+ const timer = setTimeout(() => { cleanup(); reject(new Error('等待 DSH WebSocket open 超时')); }, timeoutMs);
545
+ const onOpen = () => { cleanup(); resolve(); };
546
+ const onError = () => { cleanup(); reject(new Error('DSH WebSocket 打开失败')); };
547
+ const onClose = () => { cleanup(); reject(new Error('DSH WebSocket 在 open 前关闭')); };
548
+ const cleanup = () => {
549
+ clearTimeout(timer);
550
+ socket.removeEventListener('open', onOpen);
551
+ socket.removeEventListener('error', onError);
552
+ socket.removeEventListener('close', onClose);
553
+ };
554
+ socket.addEventListener('open', onOpen);
555
+ socket.addEventListener('error', onError);
556
+ socket.addEventListener('close', onClose);
557
+ });
558
+ }
559
+ //# sourceMappingURL=remote-web-runtime.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Codingns4DSH Host RPC 的命名空间分发表。
3
+ *
4
+ * 每个功能模块在启动时登记自己的命名空间,主 handler 只做一次 `namespace/action`
5
+ * 前缀解析。新增模块只登记新命名空间,不需要修改中心分发代码。
6
+ */
7
+ export type CodingNsRpcHandler = (action: string, payload: unknown) => unknown | Promise<unknown>;
8
+ export interface CodingNsRpcTarget {
9
+ readonly handler: CodingNsRpcHandler;
10
+ readonly action: string;
11
+ }
12
+ /** 携带稳定错误码的 RPC 业务错误。 */
13
+ export declare class CodingNsRpcError extends Error {
14
+ readonly code: string;
15
+ constructor(code: string, message: string);
16
+ }
17
+ export declare class CodingNsRpcTable {
18
+ private readonly handlers;
19
+ /**
20
+ * 登记一个命名空间的处理器。
21
+ *
22
+ * @param namespace - 不含斜杠的命名空间,例如 `auth`。
23
+ * @param handler - 接收动作名与载荷的处理器;抛错由主 handler 统一转成失败响应。
24
+ * @returns 注销函数,供模块在资源作用域中登记。
25
+ */
26
+ register(namespace: string, handler: CodingNsRpcHandler): () => void;
27
+ /** 把 `namespace/action` 解析为处理器与动作名;命名空间未登记或格式非法时返回 null。 */
28
+ resolve(endpoint: string): CodingNsRpcTarget | null;
29
+ /** 当前已登记的命名空间,供诊断与测试使用。 */
30
+ namespaces(): readonly string[];
31
+ }
32
+ //# sourceMappingURL=rpc-table.d.ts.map
@@ -0,0 +1,47 @@
1
+ /** 携带稳定错误码的 RPC 业务错误。 */
2
+ export class CodingNsRpcError extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(message);
6
+ this.code = code;
7
+ this.name = code;
8
+ }
9
+ }
10
+ export class CodingNsRpcTable {
11
+ handlers = new Map();
12
+ /**
13
+ * 登记一个命名空间的处理器。
14
+ *
15
+ * @param namespace - 不含斜杠的命名空间,例如 `auth`。
16
+ * @param handler - 接收动作名与载荷的处理器;抛错由主 handler 统一转成失败响应。
17
+ * @returns 注销函数,供模块在资源作用域中登记。
18
+ */
19
+ register(namespace, handler) {
20
+ if (namespace.trim() === '' || namespace.includes('/')) {
21
+ throw new TypeError(`RPC 命名空间非法: ${namespace}`);
22
+ }
23
+ if (this.handlers.has(namespace)) {
24
+ throw new CodingNsRpcError('CODINGNS_RPC_NAMESPACE_TAKEN', `RPC 命名空间已登记: ${namespace}`);
25
+ }
26
+ this.handlers.set(namespace, handler);
27
+ return () => {
28
+ if (this.handlers.get(namespace) === handler)
29
+ this.handlers.delete(namespace);
30
+ };
31
+ }
32
+ /** 把 `namespace/action` 解析为处理器与动作名;命名空间未登记或格式非法时返回 null。 */
33
+ resolve(endpoint) {
34
+ const separator = endpoint.indexOf('/');
35
+ if (separator <= 0 || separator === endpoint.length - 1)
36
+ return null;
37
+ const handler = this.handlers.get(endpoint.slice(0, separator));
38
+ if (handler === undefined)
39
+ return null;
40
+ return { handler, action: endpoint.slice(separator + 1) };
41
+ }
42
+ /** 当前已登记的命名空间,供诊断与测试使用。 */
43
+ namespaces() {
44
+ return [...this.handlers.keys()];
45
+ }
46
+ }
47
+ //# sourceMappingURL=rpc-table.js.map
@@ -0,0 +1,18 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection';
3
+ import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
4
+ import { type CodingNsRpcHandler, type CodingNsRpcTable } from './rpc-table.js';
5
+ /**
6
+ * 创建 Codingns4DSH Host RPC 主处理器。
7
+ *
8
+ * 它只做一次 `namespace/action` 前缀解析,具体动作由各功能模块在启动时登记的
9
+ * 命名空间处理器实现;新增模块不需要修改这个文件。这是浏览器表单与 Host 能力
10
+ * 之间的唯一边界:密码只在一次 RPC 请求中经过 Host,refresh token 只进入 Host
11
+ * 凭据存储。
12
+ */
13
+ export declare function createCodingNsRpcHandler(table: CodingNsRpcTable): ConnectionRpcHandler;
14
+ /** 在当前 Connection 上挂载 Codingns4DSH RPC 主处理器;注销由调用方的 effect 负责。 */
15
+ export declare function registerCodingNsRpc(ctx: Context, table: CodingNsRpcTable, settingsProvider?: SettingsProvider): void;
16
+ /** 创建远程设置处理器;只允许 Codingns4DSH 自己的 namespace 和路径编辑。 */
17
+ export declare function createCodingNsSettingsRpcHandler(provider: SettingsProvider): CodingNsRpcHandler;
18
+ //# sourceMappingURL=rpc.d.ts.map