benchgo-plugin 0.0.0-stage → 0.0.2

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 (180) hide show
  1. package/DEEPSEEK-LICENSE +21 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +17 -0
  4. package/PASEO-LICENSE +211 -0
  5. package/README.md +256 -2
  6. package/TOOL-CONFIG.md +33 -0
  7. package/VERIFICATION.md +297 -0
  8. package/assets/fonts/README.md +13 -0
  9. package/assets/fonts/manifest.json +437 -0
  10. package/assets/fonts/web/fangyuan-latin-238bc49130b1.woff2 +0 -0
  11. package/assets/fonts/web/fangyuan-u30-0fe8d99641c3.woff2 +0 -0
  12. package/assets/fonts/web/fangyuan-u32-202a573131a1.woff2 +0 -0
  13. package/assets/fonts/web/fangyuan-u4e-cc2f8b20be49.woff2 +0 -0
  14. package/assets/fonts/web/fangyuan-u4f-9189b5f2d321.woff2 +0 -0
  15. package/assets/fonts/web/fangyuan-u50-e2560e003872.woff2 +0 -0
  16. package/assets/fonts/web/fangyuan-u51-a9c5e7a8c7c4.woff2 +0 -0
  17. package/assets/fonts/web/fangyuan-u52-0cfd00b473c8.woff2 +0 -0
  18. package/assets/fonts/web/fangyuan-u53-18122a07f60a.woff2 +0 -0
  19. package/assets/fonts/web/fangyuan-u54-8ca41aa9cff2.woff2 +0 -0
  20. package/assets/fonts/web/fangyuan-u55-7bd55e14647e.woff2 +0 -0
  21. package/assets/fonts/web/fangyuan-u56-26f093e8a810.woff2 +0 -0
  22. package/assets/fonts/web/fangyuan-u57-36bde83a8378.woff2 +0 -0
  23. package/assets/fonts/web/fangyuan-u58-d1df14217098.woff2 +0 -0
  24. package/assets/fonts/web/fangyuan-u59-4e41ab22c51a.woff2 +0 -0
  25. package/assets/fonts/web/fangyuan-u5a-746a367174e6.woff2 +0 -0
  26. package/assets/fonts/web/fangyuan-u5b-3d1dd404d70c.woff2 +0 -0
  27. package/assets/fonts/web/fangyuan-u5c-fe0a273f69e7.woff2 +0 -0
  28. package/assets/fonts/web/fangyuan-u5d-af1f0a1c8d2d.woff2 +0 -0
  29. package/assets/fonts/web/fangyuan-u5e-598c65420e17.woff2 +0 -0
  30. package/assets/fonts/web/fangyuan-u5f-a92eaaa5265a.woff2 +0 -0
  31. package/assets/fonts/web/fangyuan-u60-48ee3d26462d.woff2 +0 -0
  32. package/assets/fonts/web/fangyuan-u61-7a57f35bc077.woff2 +0 -0
  33. package/assets/fonts/web/fangyuan-u62-d2d0b7b36631.woff2 +0 -0
  34. package/assets/fonts/web/fangyuan-u63-cade7c6635e6.woff2 +0 -0
  35. package/assets/fonts/web/fangyuan-u64-ed34e40d7f65.woff2 +0 -0
  36. package/assets/fonts/web/fangyuan-u65-e13e83e69c38.woff2 +0 -0
  37. package/assets/fonts/web/fangyuan-u66-1dbcfa5da147.woff2 +0 -0
  38. package/assets/fonts/web/fangyuan-u67-6b0a26b82da6.woff2 +0 -0
  39. package/assets/fonts/web/fangyuan-u68-1b8217cfceed.woff2 +0 -0
  40. package/assets/fonts/web/fangyuan-u69-673e63627790.woff2 +0 -0
  41. package/assets/fonts/web/fangyuan-u6a-3c94008a4640.woff2 +0 -0
  42. package/assets/fonts/web/fangyuan-u6b-44572c410fa0.woff2 +0 -0
  43. package/assets/fonts/web/fangyuan-u6c-1b323b366e88.woff2 +0 -0
  44. package/assets/fonts/web/fangyuan-u6d-cbc376d6fe3f.woff2 +0 -0
  45. package/assets/fonts/web/fangyuan-u6e-7f7e211e2fee.woff2 +0 -0
  46. package/assets/fonts/web/fangyuan-u6f-ff280ce052fa.woff2 +0 -0
  47. package/assets/fonts/web/fangyuan-u70-cb727eaf73b6.woff2 +0 -0
  48. package/assets/fonts/web/fangyuan-u71-0bb1747439fe.woff2 +0 -0
  49. package/assets/fonts/web/fangyuan-u72-cccd39590d69.woff2 +0 -0
  50. package/assets/fonts/web/fangyuan-u73-e67705133584.woff2 +0 -0
  51. package/assets/fonts/web/fangyuan-u74-03a4e7ac9df5.woff2 +0 -0
  52. package/assets/fonts/web/fangyuan-u75-a2d30b327e65.woff2 +0 -0
  53. package/assets/fonts/web/fangyuan-u76-c08fe029c988.woff2 +0 -0
  54. package/assets/fonts/web/fangyuan-u77-76f03be1f4cb.woff2 +0 -0
  55. package/assets/fonts/web/fangyuan-u78-5b721deaf582.woff2 +0 -0
  56. package/assets/fonts/web/fangyuan-u79-db7713c2eda4.woff2 +0 -0
  57. package/assets/fonts/web/fangyuan-u7a-3e40d3726a09.woff2 +0 -0
  58. package/assets/fonts/web/fangyuan-u7b-a8204d1c9dba.woff2 +0 -0
  59. package/assets/fonts/web/fangyuan-u7c-89bcdae046de.woff2 +0 -0
  60. package/assets/fonts/web/fangyuan-u7d-bcc45fa6351a.woff2 +0 -0
  61. package/assets/fonts/web/fangyuan-u7e-49fab96d9269.woff2 +0 -0
  62. package/assets/fonts/web/fangyuan-u7f-dc110e1ecbf3.woff2 +0 -0
  63. package/assets/fonts/web/fangyuan-u80-c5ce9be0335e.woff2 +0 -0
  64. package/assets/fonts/web/fangyuan-u81-b026091b3779.woff2 +0 -0
  65. package/assets/fonts/web/fangyuan-u82-a7c6960e875d.woff2 +0 -0
  66. package/assets/fonts/web/fangyuan-u83-4b7d1c2c50d4.woff2 +0 -0
  67. package/assets/fonts/web/fangyuan-u84-4119128100c8.woff2 +0 -0
  68. package/assets/fonts/web/fangyuan-u85-ca43dd4c5411.woff2 +0 -0
  69. package/assets/fonts/web/fangyuan-u86-54fc0e3158fb.woff2 +0 -0
  70. package/assets/fonts/web/fangyuan-u87-b64f5eb55720.woff2 +0 -0
  71. package/assets/fonts/web/fangyuan-u88-00feb1251f08.woff2 +0 -0
  72. package/assets/fonts/web/fangyuan-u89-db419435b316.woff2 +0 -0
  73. package/assets/fonts/web/fangyuan-u8a-06c11d45fb87.woff2 +0 -0
  74. package/assets/fonts/web/fangyuan-u8b-5c2f6fda436a.woff2 +0 -0
  75. package/assets/fonts/web/fangyuan-u8c-2b46d2d1f4cc.woff2 +0 -0
  76. package/assets/fonts/web/fangyuan-u8d-1a67d064000d.woff2 +0 -0
  77. package/assets/fonts/web/fangyuan-u8e-eb87baf0091c.woff2 +0 -0
  78. package/assets/fonts/web/fangyuan-u8f-96c1f0632da6.woff2 +0 -0
  79. package/assets/fonts/web/fangyuan-u90-86752dba8cbe.woff2 +0 -0
  80. package/assets/fonts/web/fangyuan-u91-0573b8548552.woff2 +0 -0
  81. package/assets/fonts/web/fangyuan-u92-f85ecf735974.woff2 +0 -0
  82. package/assets/fonts/web/fangyuan-u93-e7e0451735db.woff2 +0 -0
  83. package/assets/fonts/web/fangyuan-u94-f3957ff98bcd.woff2 +0 -0
  84. package/assets/fonts/web/fangyuan-u95-b31129d29013.woff2 +0 -0
  85. package/assets/fonts/web/fangyuan-u96-ca7f779c2c13.woff2 +0 -0
  86. package/assets/fonts/web/fangyuan-u97-100a25198b2c.woff2 +0 -0
  87. package/assets/fonts/web/fangyuan-u98-59e4b784a94f.woff2 +0 -0
  88. package/assets/fonts/web/fangyuan-u99-d35af6e30868.woff2 +0 -0
  89. package/assets/fonts/web/fangyuan-u9a-1eb6f7cdfcfe.woff2 +0 -0
  90. package/assets/fonts/web/fangyuan-u9b-8f428d81dcbb.woff2 +0 -0
  91. package/assets/fonts/web/fangyuan-u9c-e10f7f8f0274.woff2 +0 -0
  92. package/assets/fonts/web/fangyuan-u9e-2be65003b759.woff2 +0 -0
  93. package/assets/fonts/web/fangyuan-u9f-02c3d5224fdb.woff2 +0 -0
  94. package/assets/fonts/web/fangyuan-ufb-3eaee54bc476.woff2 +0 -0
  95. package/assets/fonts/web/fangyuan-ufe-f64da8a3acce.woff2 +0 -0
  96. package/assets/fonts/web/fangyuan-uff-0a6dcfb689fd.woff2 +0 -0
  97. package/client/assets/iconsax/LICENSE +16 -0
  98. package/client/assets/iconsax/SOURCE.md +14 -0
  99. package/config/routergo-deepseek.example.yml +25 -0
  100. package/config.example.json +24 -0
  101. package/cordis.patch.yml +5 -0
  102. package/lib/LINK-THIRD-PARTY-LICENSES.txt +1197 -0
  103. package/lib/client.js +3577 -0
  104. package/lib/link-web/app.css +92 -0
  105. package/lib/link-web/app.js +599 -0
  106. package/lib/link-web/index.html +1 -0
  107. package/lib/link-web/workbench.js +118 -0
  108. package/lib/link.mjs +244 -0
  109. package/lib/relay/ip-certificate.mjs +559 -0
  110. package/lib/relay/relay.example.json +8 -0
  111. package/lib/relay/run-relay-server.cjs +4100 -0
  112. package/lib/server/bootstrap.mjs +147 -0
  113. package/lib/server/bootstrap.sh +39 -0
  114. package/lib/server/manifest.json +8 -0
  115. package/lib/server/server-seed.tar.gz +0 -0
  116. package/locale/en.json +6 -0
  117. package/locale/zh-CN.json +6 -0
  118. package/openapi.json +629 -0
  119. package/package.json +104 -4
  120. package/shared/plugin-install.mjs +198 -0
  121. package/shared/tool-quick-settings.mjs +70 -0
  122. package/shared/update-feed.mjs +191 -0
  123. package/shared/update-public-key.pem +3 -0
  124. package/src/cli.mjs +102 -0
  125. package/src/commands.mjs +17 -0
  126. package/src/delegation.mjs +47 -0
  127. package/src/fonts.mjs +17 -0
  128. package/src/harness-adapter.mjs +228 -0
  129. package/src/harness-commands.mjs +39 -0
  130. package/src/harness-selection.mjs +5 -0
  131. package/src/harness-usage.mjs +31 -0
  132. package/src/isolated-launch.mjs +22 -0
  133. package/src/line-process.mjs +114 -0
  134. package/src/link-plugin.mjs +21 -0
  135. package/src/mcp.mjs +21 -0
  136. package/src/model-providers.mjs +125 -0
  137. package/src/models.mjs +37 -0
  138. package/src/native-auth.mjs +32 -0
  139. package/src/picker-catalog.mjs +82 -0
  140. package/src/plugin.mjs +138 -0
  141. package/src/projects.mjs +96 -0
  142. package/src/provider-access.mjs +11 -0
  143. package/src/providers/acp.mjs +138 -0
  144. package/src/providers/claude.mjs +115 -0
  145. package/src/providers/codex.mjs +134 -0
  146. package/src/providers/opencode.mjs +138 -0
  147. package/src/providers/pi-permissions.ts +24 -0
  148. package/src/providers/pi.mjs +97 -0
  149. package/src/questions.mjs +34 -0
  150. package/src/recent-workspace.mjs +36 -0
  151. package/src/runtime.mjs +421 -0
  152. package/src/session-routes.mjs +89 -0
  153. package/src/store.mjs +98 -0
  154. package/src/tool-catalog.mjs +22 -0
  155. package/src/tool-config.mjs +94 -0
  156. package/src/tool-install.mjs +31 -0
  157. package/src/tool-profiles.mjs +114 -0
  158. package/src/tooling.mjs +90 -0
  159. package/src/updates.mjs +156 -0
  160. package/src/usage-import-parsers.mjs +37 -0
  161. package/src/usage-import.mjs +114 -0
  162. package/src/usage-timeline.mjs +36 -0
  163. package/src/usage.mjs +142 -0
  164. package/src/web.mjs +274 -0
  165. package/voice/README.md +49 -0
  166. package/voice/STREAMING.md +141 -0
  167. package/voice/VERIFICATION.md +36 -0
  168. package/voice/client/audio-worklet.js +35 -0
  169. package/voice/locale/en.json +1 -0
  170. package/voice/locale/zh-CN.json +1 -0
  171. package/voice/src/asr.mjs +44 -0
  172. package/voice/src/calls.mjs +37 -0
  173. package/voice/src/config.mjs +139 -0
  174. package/voice/src/media.mjs +70 -0
  175. package/voice/src/plugin.mjs +10 -0
  176. package/voice/src/realtime-protocols.mjs +30 -0
  177. package/voice/src/realtime-socket.mjs +86 -0
  178. package/voice/src/realtime.mjs +130 -0
  179. package/voice/src/tts.mjs +16 -0
  180. package/voice/src/web.mjs +171 -0
@@ -0,0 +1 @@
1
+ <!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="theme-color" content="#fffdf8"><meta name="color-scheme" content="light dark"><title>BenchGo</title><link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20600%20380%22%20role%3D%22img%22%20aria-label%3D%22BenchGo%22%3E%0A%20%20%3C!--%20Corner%20rule%20(master%20construction%2C%20geometry.json)%3A%20the%20cut%20is%20a%2045-degree%0A%20%20%20%20%20%20%20diagonal%20from%20(410%2C380)%20to%20(600%2C190)%3B%20the%20corner%20piece%20keeps%20the%20body's%0A%20%20%20%20%20%20%20R95%20rounded%20bottom-right%20corner%20-%20never%20a%20sharp%20point.%20Glyph%20must%20stay%0A%20%20%20%20%20%20%20fully%20inside%20the%20piece%3A%20clear%20of%20the%20diagonal%20(line%20x%2By%3D790)%20and%20of%20the%0A%20%20%20%20%20%20%20R95%20arc%20(center%20505%2C285).%20Current%20glyph%3A%20mini%20window%20at%20(543%2C324)%2C%0A%20%20%20%20%20%20%20scale%200.9%2C%20max%20radius%20~32.%20If%20the%20corner%20changes%2C%20recompute%20from%20the%0A%20%20%20%20%20%20%20master%20geometry%2C%20not%20from%20absolute%20pixels.%20--%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3CclipPath%20id%3D%22bodyClip%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M95%2C0%20H505%20A95%2C95%200%200%201%20600%2C95%20V190%20L410%2C380%20H95%20A95%2C95%200%200%201%200%2C285%20V95%20A95%2C95%200%200%201%2095%2C0%20Z%22%2F%3E%0A%20%20%20%20%3C%2FclipPath%3E%0A%20%20%20%20%3Cpattern%20id%3D%22winprint%22%20width%3D%22150%22%20height%3D%22118%22%20patternUnits%3D%22userSpaceOnUse%22%3E%0A%20%20%20%20%20%20%3Cg%20fill%3D%22none%22%20stroke%3D%22%23D9CCAF%22%20stroke-width%3D%222.5%22%20opacity%3D%220.6%22%3E%0A%20%20%20%20%20%20%20%20%3Crect%20x%3D%2212%22%20y%3D%2214%22%20width%3D%2232%22%20height%3D%2222%22%20rx%3D%225%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Crect%20x%3D%2284%22%20y%3D%2266%22%20width%3D%2232%22%20height%3D%2222%22%20rx%3D%225%22%2F%3E%0A%20%20%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%20%20%3Cg%20fill%3D%22%23D9CCAF%22%20opacity%3D%220.6%22%3E%0A%20%20%20%20%20%20%20%20%3Ccircle%20cx%3D%2221%22%20cy%3D%2222%22%20r%3D%222.8%22%2F%3E%3Ccircle%20cx%3D%2231%22%20cy%3D%2222%22%20r%3D%222.8%22%2F%3E%0A%20%20%20%20%20%20%20%20%3Ccircle%20cx%3D%2293%22%20cy%3D%2274%22%20r%3D%222.8%22%2F%3E%3Ccircle%20cx%3D%22103%22%20cy%3D%2274%22%20r%3D%222.8%22%2F%3E%0A%20%20%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3C%2Fpattern%3E%0A%20%20%3C%2Fdefs%3E%0A%0A%20%20%3C!--%20body%20--%3E%0A%20%20%3Cpath%20d%3D%22M95%2C0%20H505%20A95%2C95%200%200%201%20600%2C95%20V190%20L410%2C380%20H95%20A95%2C95%200%200%201%200%2C285%20V95%20A95%2C95%200%200%201%2095%2C0%20Z%22%20fill%3D%22%23F1E8DB%22%2F%3E%0A%20%20%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%22600%22%20height%3D%22380%22%20fill%3D%22url(%23winprint)%22%20clip-path%3D%22url(%23bodyClip)%22%2F%3E%0A%0A%20%20%3C!--%20terminal%20motif%20%3E_%20--%3E%0A%20%20%3Cg%20stroke%3D%22%23EE5A33%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%3E%0A%20%20%20%20%3Cpolyline%20points%3D%22472%2C50%20512%2C79%20472%2C108%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%20%20%3Crect%20x%3D%22528%22%20y%3D%2293%22%20width%3D%2242%22%20height%3D%2217%22%20rx%3D%228.5%22%20fill%3D%22%23EE5A33%22%2F%3E%0A%0A%20%20%3C!--%20eyes%3A%20bean%20%2B%20%3C%20(right%20eye%20at%2088%25%20scale%2C%20user-approved%202026-10-26%2C%20baked%20coords)%20--%3E%0A%20%20%3Crect%20x%3D%22123%22%20y%3D%22125%22%20width%3D%2290%22%20height%3D%22130%22%20rx%3D%2245%22%20fill%3D%22%23222321%22%2F%3E%0A%20%20%3Cpolyline%20points%3D%22342.6%2C150.1%20293.4%2C195%20342.6%2C239.9%22%20stroke%3D%22%23222321%22%20stroke-width%3D%2230%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20fill%3D%22none%22%2F%3E%0A%0A%20%20%3C!--%20orange%20cut%20corner%20--%3E%0A%20%20%3Cpath%20d%3D%22M410%2C380%20H505%20A95%2C95%200%200%200%20600%2C285%20V190%20Z%22%20fill%3D%22%23EE5A33%22%2F%3E%0A%20%20%3C!--%20mini%20window%20glyph%3A%20centered%20on%20corner%20incenter%2C%20max%20radius%20~36%20%3C%20inradius%2051%20--%3E%0A%20%20%3Cg%20transform%3D%22translate(543%20324)%20scale(0.9)%22%3E%0A%20%20%20%20%3Crect%20x%3D%22-26%22%20y%3D%22-18%22%20width%3D%2252%22%20height%3D%2236%22%20rx%3D%229%22%20fill%3D%22none%22%20stroke%3D%22%23F7F1E5%22%20stroke-width%3D%228%22%2F%3E%0A%20%20%20%20%3Ccircle%20cx%3D%22-9%22%20cy%3D%220%22%20r%3D%224%22%20fill%3D%22%23F7F1E5%22%2F%3E%0A%20%20%20%20%3Ccircle%20cx%3D%229%22%20cy%3D%220%22%20r%3D%224%22%20fill%3D%22%23F7F1E5%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"><link id="dsh-link-style" rel="stylesheet" href="./app.css"></head><body style="margin:0"><div id="root"></div><script src="./app.js"></script></body></html>
@@ -0,0 +1,118 @@
1
+ /* Installed before the shared Harness shell. The paired cookie is HttpOnly. */
2
+ (() => {
3
+ const base = new URL(document.baseURI);
4
+ const originalFetch = globalThis.fetch.bind(globalThis);
5
+ const route = path => new URL(path, base);
6
+ const apiFetch = (input, init = {}) => {
7
+ const url = new URL(input, base);
8
+ // Existing UI code can use either document-relative or root-relative API paths.
9
+ if (url.origin === base.origin && /^\/(api|plugins)\//.test(url.pathname)) url.pathname = base.pathname + url.pathname.slice(1);
10
+ if (url.origin !== base.origin || !url.pathname.startsWith(base.pathname)) throw new Error('工作台请求地址无效');
11
+ return originalFetch(url, { ...init, credentials: 'same-origin' }).then(response => {
12
+ if (response.status === 401) globalThis.dispatchEvent(new CustomEvent('dsh-link-authorization-expired'));
13
+ return response;
14
+ });
15
+ };
16
+ // Loopback HTTP cannot negotiate h2; multiplex subscriptions over one socket.
17
+ let localSocket;
18
+ const localStreams = new Map();
19
+ const localConnection = () => {
20
+ if (localSocket && localSocket.readyState <= 1) return localSocket;
21
+ const url = new URL('../../socket', base); url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
22
+ const socket = localSocket = new WebSocket(url);
23
+ socket.addEventListener('message', event => {
24
+ try { const frame = JSON.parse(event.data), stream = localStreams.get(frame.id); if (!stream) return; if (stream.items.length >= 128) { stream.error = new Error('工作台流消费过慢'); socket.close(); } else { stream.items.push(frame); stream.wake?.(); } } catch { socket.close(); }
25
+ });
26
+ socket.addEventListener('close', () => { for (const stream of localStreams.values()) { stream.error = new Error('主机连接已断开,请重试'); stream.wake?.(); } });
27
+ socket.addEventListener('error', () => {});
28
+ return socket;
29
+ };
30
+ const waitSocket = (socket, signal) => new Promise((resolve, reject) => {
31
+ const clean = () => { socket.removeEventListener('open', opened); socket.removeEventListener('close', failed); signal.removeEventListener('abort', failed); };
32
+ const opened = () => { clean(); resolve(); }, failed = () => { clean(); reject(signal.reason || new Error('主机连接不可用')); };
33
+ if (signal.aborted || socket.readyState > 1) return failed();
34
+ if (socket.readyState === 1) return opened();
35
+ socket.addEventListener('open', opened, { once: true }); socket.addEventListener('close', failed, { once: true }); signal.addEventListener('abort', failed, { once: true });
36
+ });
37
+ async function* localLines(payload, signal) {
38
+ const socket = localConnection(), stream = { items: [], wake: undefined, error: null }, id = payload.id;
39
+ await waitSocket(socket, signal); localStreams.set(id, stream);
40
+ const abort = () => { stream.error = signal.reason || new Error('已取消'); stream.wake?.(); };
41
+ signal.addEventListener('abort', abort, { once: true });
42
+ if (signal.aborted) abort();
43
+ socket.send(JSON.stringify({ type: 'open', id, body: payload }));
44
+ try {
45
+ while (true) {
46
+ if (stream.error) throw stream.error;
47
+ if (!stream.items.length) { await new Promise(resolve => { stream.wake = resolve; }); stream.wake = undefined; continue; }
48
+ const frame = stream.items.shift();
49
+ if (frame.type === 'headers' && frame.status >= 400) { if (frame.status === 401) globalThis.dispatchEvent(new CustomEvent('dsh-link-authorization-expired')); throw new Error('工作台连接失败:' + frame.status); }
50
+ if (frame.type === 'error') throw new Error(frame.error);
51
+ if (frame.type === 'end') return;
52
+ if (frame.type === 'data') yield frame.value;
53
+ }
54
+ } finally { signal.removeEventListener('abort', abort); localStreams.delete(id); if (socket.readyState === 1) socket.send(JSON.stringify({ type: 'cancel', id })); }
55
+ }
56
+ async function* lines(response) {
57
+ const reader = response.body?.getReader();
58
+ if (!reader) throw new Error('工作台流不可用');
59
+ const decoder = new TextDecoder(); let buffer = '';
60
+ try {
61
+ while (true) {
62
+ const { value, done } = await reader.read();
63
+ buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
64
+ let index;
65
+ while ((index = buffer.indexOf('\n')) >= 0) { const line = buffer.slice(0, index); buffer = buffer.slice(index + 1); if (line.trim()) yield JSON.parse(line); }
66
+ if (done) { if (buffer.trim()) yield JSON.parse(buffer); return; }
67
+ }
68
+ } finally { await reader.cancel().catch(() => {}); reader.releaseLock(); }
69
+ }
70
+ async function* openStream(endpoint, payload, signal, uplink) {
71
+ signal.throwIfAborted();
72
+ const id = crypto.randomUUID();
73
+ const lifetime = new AbortController();
74
+ const combined = AbortSignal.any([signal, lifetime.signal]);
75
+ const post = async frame => {
76
+ const response = await apiFetch(route('remote/uplink'), { method: 'POST', signal: combined, headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id, ...frame }) });
77
+ if (!response.ok) throw new Error('工作台上行连接失败:' + response.status);
78
+ };
79
+ let upload, iterator, failure;
80
+ try {
81
+ if (globalThis.__DSH_LINK_LOCAL_PROXY__) iterator = localLines({ id, endpoint, payload }, combined)[Symbol.asyncIterator]();
82
+ else {
83
+ const response = await apiFetch(route('remote/open'), { method: 'POST', signal: combined, headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id, endpoint, payload }) });
84
+ if (!response.ok) throw new Error('工作台连接失败:' + response.status);
85
+ iterator = lines(response)[Symbol.asyncIterator]();
86
+ }
87
+ const ready = await iterator.next();
88
+ if (ready.done || ready.value.type !== 'ready') throw new Error('工作台流初始化失败');
89
+ upload = (async () => {
90
+ if (uplink) for await (const value of uplink) { combined.throwIfAborted(); await post({ type: 'item', value }); }
91
+ await post({ type: 'end' });
92
+ })().catch(error => { if (!combined.aborted) { failure = error; lifetime.abort(error); } });
93
+ while (true) {
94
+ const next = await iterator.next();
95
+ if (next.done) throw new Error('工作台流意外中断');
96
+ const frame = next.value;
97
+ if (frame.type === 'end') return;
98
+ if (frame.type === 'error') throw Object.assign(new Error(frame.error?.message || '工作台流失败'), { code: frame.error?.code, details: frame.error?.details });
99
+ if (frame.type === 'item') yield frame.value;
100
+ }
101
+ } catch (error) { throw failure ?? error; }
102
+ finally { lifetime.abort(); await iterator?.return?.().catch(() => {}); void upload; }
103
+ }
104
+ globalThis.__DSH_LINK_WORKBENCH__ = true;
105
+ globalThis.__DSH_TRANSPORT__ = { fetch: apiFetch, openStream };
106
+ // Direct feature fetches (voice, preset editor, uploads) already resolve
107
+ // document.baseURI and carry the same cookie. Keep that path rooted here.
108
+ globalThis.addEventListener('dsh-link-authorization-expired', () => {
109
+ if (!document.getElementById('dsh-link-expired')) {
110
+ const banner = document.createElement('div');
111
+ banner.id = 'dsh-link-expired'; banner.setAttribute('role', 'alert');
112
+ banner.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:var(--background,#fff);color:var(--foreground,#222);display:grid;place-content:center;gap:16px;padding:28px;text-align:center';
113
+ const text = document.createElement('p'); text.textContent = '设备授权已失效,请重新配对';
114
+ const button = document.createElement('button'); button.textContent = '重新连接'; button.style.cssText = 'min-height:44px;padding:10px 20px;border-radius:12px;cursor:pointer';
115
+ button.onclick = () => location.assign('/link/'); banner.append(text, button); document.body.append(banner);
116
+ }
117
+ });
118
+ })();