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.
- package/DEEPSEEK-LICENSE +21 -0
- package/LICENSE +21 -0
- package/NOTICE +17 -0
- package/PASEO-LICENSE +211 -0
- package/README.md +256 -2
- package/TOOL-CONFIG.md +33 -0
- package/VERIFICATION.md +297 -0
- package/assets/fonts/README.md +13 -0
- package/assets/fonts/manifest.json +437 -0
- package/assets/fonts/web/fangyuan-latin-238bc49130b1.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u30-0fe8d99641c3.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u32-202a573131a1.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u4e-cc2f8b20be49.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u4f-9189b5f2d321.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u50-e2560e003872.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u51-a9c5e7a8c7c4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u52-0cfd00b473c8.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u53-18122a07f60a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u54-8ca41aa9cff2.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u55-7bd55e14647e.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u56-26f093e8a810.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u57-36bde83a8378.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u58-d1df14217098.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u59-4e41ab22c51a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5a-746a367174e6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5b-3d1dd404d70c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5c-fe0a273f69e7.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5d-af1f0a1c8d2d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5e-598c65420e17.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5f-a92eaaa5265a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u60-48ee3d26462d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u61-7a57f35bc077.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u62-d2d0b7b36631.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u63-cade7c6635e6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u64-ed34e40d7f65.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u65-e13e83e69c38.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u66-1dbcfa5da147.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u67-6b0a26b82da6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u68-1b8217cfceed.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u69-673e63627790.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6a-3c94008a4640.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6b-44572c410fa0.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6c-1b323b366e88.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6d-cbc376d6fe3f.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6e-7f7e211e2fee.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6f-ff280ce052fa.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u70-cb727eaf73b6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u71-0bb1747439fe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u72-cccd39590d69.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u73-e67705133584.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u74-03a4e7ac9df5.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u75-a2d30b327e65.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u76-c08fe029c988.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u77-76f03be1f4cb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u78-5b721deaf582.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u79-db7713c2eda4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7a-3e40d3726a09.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7b-a8204d1c9dba.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7c-89bcdae046de.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7d-bcc45fa6351a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7e-49fab96d9269.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7f-dc110e1ecbf3.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u80-c5ce9be0335e.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u81-b026091b3779.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u82-a7c6960e875d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u83-4b7d1c2c50d4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u84-4119128100c8.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u85-ca43dd4c5411.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u86-54fc0e3158fb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u87-b64f5eb55720.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u88-00feb1251f08.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u89-db419435b316.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8a-06c11d45fb87.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8b-5c2f6fda436a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8c-2b46d2d1f4cc.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8d-1a67d064000d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8e-eb87baf0091c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8f-96c1f0632da6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u90-86752dba8cbe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u91-0573b8548552.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u92-f85ecf735974.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u93-e7e0451735db.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u94-f3957ff98bcd.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u95-b31129d29013.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u96-ca7f779c2c13.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u97-100a25198b2c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u98-59e4b784a94f.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u99-d35af6e30868.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9a-1eb6f7cdfcfe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9b-8f428d81dcbb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9c-e10f7f8f0274.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9e-2be65003b759.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9f-02c3d5224fdb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-ufb-3eaee54bc476.woff2 +0 -0
- package/assets/fonts/web/fangyuan-ufe-f64da8a3acce.woff2 +0 -0
- package/assets/fonts/web/fangyuan-uff-0a6dcfb689fd.woff2 +0 -0
- package/client/assets/iconsax/LICENSE +16 -0
- package/client/assets/iconsax/SOURCE.md +14 -0
- package/config/routergo-deepseek.example.yml +25 -0
- package/config.example.json +24 -0
- package/cordis.patch.yml +5 -0
- package/lib/LINK-THIRD-PARTY-LICENSES.txt +1197 -0
- package/lib/client.js +3577 -0
- package/lib/link-web/app.css +92 -0
- package/lib/link-web/app.js +599 -0
- package/lib/link-web/index.html +1 -0
- package/lib/link-web/workbench.js +118 -0
- package/lib/link.mjs +244 -0
- package/lib/relay/ip-certificate.mjs +559 -0
- package/lib/relay/relay.example.json +8 -0
- package/lib/relay/run-relay-server.cjs +4100 -0
- package/lib/server/bootstrap.mjs +147 -0
- package/lib/server/bootstrap.sh +39 -0
- package/lib/server/manifest.json +8 -0
- package/lib/server/server-seed.tar.gz +0 -0
- package/locale/en.json +6 -0
- package/locale/zh-CN.json +6 -0
- package/openapi.json +629 -0
- package/package.json +104 -4
- package/shared/plugin-install.mjs +198 -0
- package/shared/tool-quick-settings.mjs +70 -0
- package/shared/update-feed.mjs +191 -0
- package/shared/update-public-key.pem +3 -0
- package/src/cli.mjs +102 -0
- package/src/commands.mjs +17 -0
- package/src/delegation.mjs +47 -0
- package/src/fonts.mjs +17 -0
- package/src/harness-adapter.mjs +228 -0
- package/src/harness-commands.mjs +39 -0
- package/src/harness-selection.mjs +5 -0
- package/src/harness-usage.mjs +31 -0
- package/src/isolated-launch.mjs +22 -0
- package/src/line-process.mjs +114 -0
- package/src/link-plugin.mjs +21 -0
- package/src/mcp.mjs +21 -0
- package/src/model-providers.mjs +125 -0
- package/src/models.mjs +37 -0
- package/src/native-auth.mjs +32 -0
- package/src/picker-catalog.mjs +82 -0
- package/src/plugin.mjs +138 -0
- package/src/projects.mjs +96 -0
- package/src/provider-access.mjs +11 -0
- package/src/providers/acp.mjs +138 -0
- package/src/providers/claude.mjs +115 -0
- package/src/providers/codex.mjs +134 -0
- package/src/providers/opencode.mjs +138 -0
- package/src/providers/pi-permissions.ts +24 -0
- package/src/providers/pi.mjs +97 -0
- package/src/questions.mjs +34 -0
- package/src/recent-workspace.mjs +36 -0
- package/src/runtime.mjs +421 -0
- package/src/session-routes.mjs +89 -0
- package/src/store.mjs +98 -0
- package/src/tool-catalog.mjs +22 -0
- package/src/tool-config.mjs +94 -0
- package/src/tool-install.mjs +31 -0
- package/src/tool-profiles.mjs +114 -0
- package/src/tooling.mjs +90 -0
- package/src/updates.mjs +156 -0
- package/src/usage-import-parsers.mjs +37 -0
- package/src/usage-import.mjs +114 -0
- package/src/usage-timeline.mjs +36 -0
- package/src/usage.mjs +142 -0
- package/src/web.mjs +274 -0
- package/voice/README.md +49 -0
- package/voice/STREAMING.md +141 -0
- package/voice/VERIFICATION.md +36 -0
- package/voice/client/audio-worklet.js +35 -0
- package/voice/locale/en.json +1 -0
- package/voice/locale/zh-CN.json +1 -0
- package/voice/src/asr.mjs +44 -0
- package/voice/src/calls.mjs +37 -0
- package/voice/src/config.mjs +139 -0
- package/voice/src/media.mjs +70 -0
- package/voice/src/plugin.mjs +10 -0
- package/voice/src/realtime-protocols.mjs +30 -0
- package/voice/src/realtime-socket.mjs +86 -0
- package/voice/src/realtime.mjs +130 -0
- package/voice/src/tts.mjs +16 -0
- 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
|
+
})();
|