@iasiv5/dsh-skip-browser-auth 0.3.4 → 0.3.6

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/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @iasiv5/dsh-skip-browser-auth
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@iasiv5/dsh-skip-browser-auth?label=npm&color=cb3837)](https://www.npmjs.com/package/@iasiv5/dsh-skip-browser-auth)
4
- [![DSH Web](https://img.shields.io/badge/DSH_Web-0.1.2--rc.1_%7C_0.1.5--rc.1_%7C_0.1.5--rc.2_%7C_0.1.7--rc.1_%7C_0.1.7--rc.2_%7C_0.2.0--rc.1_verified-blue)](#兼容版本)
4
+ [![DSH Web](https://img.shields.io/badge/DSH_Web-0.1.2--rc.1_%7C_0.1.5--rc.1_%7C_0.1.5--rc.2_%7C_0.1.7--rc.1_%7C_0.1.7--rc.2_%7C_0.2.0--rc.1_%7C_0.2.0--rc.2_verified-blue)](#兼容版本)
5
5
  [![License](https://img.shields.io/github/license/iasiv5/dsh-skip-browser-auth?color=green)](./LICENSE)
6
6
 
7
7
  个人自用的 DSH Web 插件:在已验证的 DSH compatibility profile 上自动跳过 BrowserAuth,打开 Web 地址即可直接使用,**不必每次把启动 URL 里那串随机 token 抄进浏览器**。
8
8
 
9
- - **精确 profile 门控**:当前支持 `0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.7-rc.1`、`0.1.7-rc.2` 与 `0.2.0-rc.1`,按 host/client 兼容代际分组;不是把所有版本塞进同一个实现。
9
+ - **精确 profile 门控**:当前支持 `0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2`,按 host/client 兼容代际分组;不是把所有版本塞进同一个实现。
10
10
  - **严格成对匹配**:runtime `@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-client-connection` 必须组成已知版本对。混合版本、未知版本、profile 未实现或解析失败都会自动休眠。
11
- - **分代适配**:`0.1.2-rc.1` 使用 `legacy-web-v1`;`0.1.5-rc.1` 与 `0.1.5-rc.2` 使用 `carrier-neutral-v2`,包含 request body mode、RPC carrier 和对应浏览器 client variant 适配;`0.1.7-rc.1`、`0.1.7-rc.2` 与 `0.2.0-rc.1` 使用 `carrier-neutral-v3`,覆盖 0.1.7 引入、0.2.0 沿用的 `admit()`/OperatorPeer 准入、`connection/request` waterfall 与 mount-preserving BrowserAuth。
11
+ - **分代适配**:`0.1.2-rc.1` 使用 `legacy-web-v1`;`0.1.5-rc.1` 与 `0.1.5-rc.2` 使用 `carrier-neutral-v2`,包含 request body mode、RPC carrier 和对应浏览器 client variant 适配;`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2` 使用 `carrier-neutral-v3`,覆盖 0.1.7 引入、0.2.0 沿用的 `admit()`/OperatorPeer 准入、`connection/request` waterfall 与 mount-preserving BrowserAuth。
12
12
  - **不修改 DSH 本体与任何机制**:仅通过 DSH 官方插件 patch 机制替换 Connection 行;卸载或禁用插件并重启,即完全恢复官方行为。
13
13
  - **安全提醒**:跳过认证后,Web 对本机/可信网络内的访问不再有身份层,请勿把 DSH 端口暴露给不可信网络。
14
14
 
@@ -36,15 +36,16 @@ sudo systemctl restart deepseek-harness.service # 按你的部署方式重启
36
36
  它支持的精确 compatibility profile:
37
37
  - DSH 0.1.2-rc.1:legacy-web-v1
38
38
  - DSH 0.1.5-rc.1 / 0.1.5-rc.2:carrier-neutral-v2
39
- - DSH 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1:carrier-neutral-v3
39
+ - DSH 0.1.7-rc.1 / 0.1.7-rc.2 / 0.2.0-rc.1 / 0.2.0-rc.2:carrier-neutral-v3
40
40
  其它版本或 runtime/connection 混合版本会自动休眠,不要强行处理。
41
+ desktop profile(存在 dsh-desktop-host 的环境)会自动休眠,这是设计行为,不要强行处理或改代码绕过。
41
42
 
42
43
  1. 先执行 dsh --version 告诉我当前 DSH 版本,然后照常安装:
43
44
  dsh plugin --profile web add @iasiv5/dsh-skip-browser-auth
44
45
  2. 重启 DSH Web 并轮询 http://127.0.0.1:3080 直到恢复 200
45
46
  (systemd 系统级部署:sudo systemctl restart deepseek-harness.service;
46
47
  user 级部署:systemctl --user restart dsh-web.service;端口以实际部署为准)。
47
- 3. 若版本是 0.1.2-rc.1、0.1.5-rc.1、0.1.5-rc.2、0.1.7-rc.1、0.1.7-rc.2 或 0.2.0-rc.1:
48
+ 3. 若版本是 0.1.2-rc.1、0.1.5-rc.1、0.1.5-rc.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1 或 0.2.0-rc.2:
48
49
  确认 journal/日志中出现固定警告
49
50
  "BrowserAuth has been skipped",并验证浏览器直接打开 Web 地址不再要求 token。
50
51
  4. 若版本不是精确白名单版本,或 profile/依赖契约不匹配:插件应自动休眠,
@@ -69,7 +70,9 @@ sudo systemctl restart deepseek-harness.service
69
70
  | `0.1.5-rc.2` + `0.1.5-rc.2` | `carrier-neutral-v2` | 同上(rc.2 为 rc.1 的重发布:代码逐字节一致) | ✅ active(npm diff 审计 + 真实 rc.2 npm fixture 组合测试已验证;真实部署仍需按清单确认) |
70
71
  | `0.1.7-rc.1` + `0.1.7-rc.1` | `carrier-neutral-v3` | carrier-neutral-017:admit()/OperatorPeer + `connection/request` waterfall + mount-preserving BrowserAuth + 0.1.7 client | ✅ active(npm diff 审计:插件契约面与 rc.2 逐字节一致 + 真实 rc.1 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.5;真实部署仍需按清单确认) |
71
72
  | `0.1.7-rc.2` + `0.1.7-rc.2` | `carrier-neutral-v3` | 同上 | ✅ active(真实 npm fixture 组合测试 + 2026-09-26 实机 checklist 与回滚演练,见 docs/DESIGN.md) |
72
- | `0.2.0-rc.1` + `0.2.0-rc.1` | `carrier-neutral-v3` | 同上(0.2.0-rc.1 为依赖对齐重发布:插件契约面代码逐字节一致) | ✅ active(npm diff 审计 + 真实 0.2.0-rc.1 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.6;真实部署待宿主升级后按清单确认) |
73
+ | `0.2.0-rc.1` + `0.2.0-rc.1` | `carrier-neutral-v3` | 同上(0.2.0-rc.1 为依赖对齐重发布:插件契约面代码逐字节一致) | ✅ active(npm diff 审计 + 真实 0.2.0-rc.1 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.6;2026-09-29 实机 checklist 已确认) |
74
+ | `0.2.0-rc.2` + `0.2.0-rc.2` | `carrier-neutral-v3` | 同上(0.2.0-rc.2 为依赖对齐重发布 + 无关 Desktop CLI 演进:插件契约面代码逐字节一致) | ✅ active(npm diff 审计 + 真实 0.2.0-rc.2 npm fixture 组合测试,见 docs/COMPATIBILITY.md §5.7;实机 checklist 见当日升级记录) |
75
+ | desktop profile(存在 dsh-desktop-host 的环境,任意版本对) | — | 😴 dormant-by-design:desktop 一律休眠 | dormant |
73
76
  | 其它版本或混合版本对 | — | 😴 自动休眠:官方行为分毫不变 | dormant |
74
77
 
75
78
  白名单匹配的是**完整版本对**,不是两个独立的 `includes()`:
@@ -86,6 +89,9 @@ runtime=0.1.7-rc.2 + connection=0.1.7-rc.2 → carrier-neutral-v3
86
89
  runtime=0.1.7-rc.2 + connection=0.2.0-rc.1 → dormant(同代际跨 minor 混合同样是事故形态)
87
90
  runtime=0.2.0-rc.1 + connection=0.1.7-rc.2 → dormant
88
91
  runtime=0.2.0-rc.1 + connection=0.2.0-rc.1 → carrier-neutral-v3
92
+ runtime=0.2.0-rc.1 + connection=0.2.0-rc.2 → dormant(同代际跨 patch 混合同样是事故形态)
93
+ runtime=0.2.0-rc.2 + connection=0.2.0-rc.1 → dormant
94
+ runtime=0.2.0-rc.2 + connection=0.2.0-rc.2 → carrier-neutral-v3
89
95
  runtime=0.1.7-rc.2 + connection=0.1.5-rc.2 → dormant(跨代际混合)
90
96
  ```
91
97
 
@@ -95,10 +101,11 @@ runtime=0.1.7-rc.2 + connection=0.1.5-rc.2 → dormant(跨代际混合)
95
101
 
96
102
  - 组合期版本探针使用 runtime 本体与官方 connection 包双锚点,并返回 profile id;路径段和 npm flat manifest 两种布局均支持。任何异常都 fail-closed 到 dormant。
97
103
  - `apply()` backstop 再读取两个 manifest,验证 active profile、patch 传入的 profile id、官方 connection sibling 行的 id/name/disabled/fiber 状态;不满足时在创建服务、注册 route、输出 warning 前 fail loud。
104
+ - 宿主 profile 门控:探针先解析 @deepseek-ai/dsh-desktop-host——命中即 desktop 环境,整体安静休眠(设计行为,desktop 上 Electron 自带 token,插件无收益);解析异常按 web 继续走版本对判定,任何异常兜底休眠。
98
105
  - `legacy-web-v1` 保留 0.1.2 的 `/api` prefix 与 buffered bridge。
99
106
  - `carrier-neutral-v2` 使用 runtime loader 取得当前 0.1.5 connection module,支持 `requestBodyMode`、exact Fetch route 和 streaming body,并将 generic `connection.rpc.handle()` 绑定到拥有 Web carrier 的 replacement context,避免 0.1.5 的 405 回归。
100
107
  - `carrier-neutral-v3` 在 v2 契约之上复刻 0.1.7 的三处新语义:`/api` 准入改走 `admit()`(OperatorPeer 作为 RPC handler 第 4 参传入)、共享通道请求经 `connection/request` waterfall(监听插件继续可见)、BrowserAuth stub 使用 mount-preserving URL 语义(token 清理 303 `./`、启动 URL 保留 mount)。
101
- - client bundle 是显式 profile dispatcher:host 通过 `webserver/index-inject` 注入 profile id,0.1.2/0.1.5/0.1.7 代际使用各自官方 factory(`0.2.0-rc.1` 与 `0.1.7` 契约逐字节一致,共用 0.1.7 factory);profile 缺失或未知时不猜测、不加载错误 variant。
108
+ - client bundle 是显式 profile dispatcher:host 通过 `webserver/index-inject` 注入 profile id,0.1.2/0.1.5/0.1.7 代际使用各自官方 factory(`0.2.0-rc.1`/`0.2.0-rc.2` 与 `0.1.7` 契约逐字节一致,共用 0.1.7 factory);profile 缺失或未知时不猜测、不加载错误 variant。
102
109
  - 激活时进程逐字输出固定警告:
103
110
 
104
111
  ```text
@@ -131,8 +138,10 @@ git diff --exit-code cordis.patch.yml
131
138
  - 真实 0.1.5 dedicated `connection.rpc.handle()`、exact Fetch route 和 405 回归
132
139
  - 真实 0.1.7 `admit()`/OperatorPeer、`connection/request` waterfall、mount-preserving token 清理、同代际跨 patch 混合 dormant(rc.1 ↔ rc.2)与跨代际混合 dormant
133
140
  - 真实 0.2.0-rc.1 `admit()`/OperatorPeer、waterfall、mount-preserving token 清理、跨 minor 混合 dormant(0.1.7 ↔ 0.2.0 双向)与 npm flat 布局
141
+ - 真实 0.2.0-rc.2 `admit()`/OperatorPeer、waterfall、mount-preserving token 清理、同代际跨 patch 混合 dormant(0.2.0-rc.1 ↔ 0.2.0-rc.2 双向)与 npm flat 布局
134
142
  - 三个官方 client factory 的 materialization、profile dispatcher 和未知 profile fail-closed
135
143
  - rc.2 dormant 基线、dispose/unload、Host/Origin/Fetch Metadata trust fence
144
+ - desktop 宿主 fixture:@deepseek-ai/dsh-desktop-host 桩包在场 + 白名单版本对仍整体休眠(官方行活跃、无插件路由、无横幅)
136
145
 
137
146
  完整的 GitHub API 审计、profile 数据模型、adapter seam、client 变体和未来新增代际 checklist 见 [docs/COMPATIBILITY.md](./docs/COMPATIBILITY.md);机制详版见 [docs/DESIGN.md](./docs/DESIGN.md)。
138
147
 
package/cordis.patch.yml CHANGED
@@ -7,12 +7,16 @@
7
7
  # 任何门控条件不成立(锚点解析失败、版本漂移、行不存在、名字漂移、
8
8
  # 官方行未禁用),Replacement 一律保持 dormant(fail-closed)。探针为假时,
9
9
  # 即使后续 patch 层把官方行强制 disabled,行绑定内嵌探针仍保持 dormant。
10
+ # 宿主 profile 门控(web-only):表达式先解析 @deepseek-ai/dsh-desktop-host/package.json,
11
+ # 解析出合法 URL 即 desktop ⇒ 探针为 null(desktop 一律休眠,设计行为);解析异常
12
+ # 按包不存在处理(web 继续),兜底仍是完整版本对白名单(fail-closed)。
10
13
  - id: connection
11
14
  name: '@deepseek-ai/dsh-client-connection'
12
15
  disabled: !!js >-
13
- (() => { try { const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof
14
- manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
15
- [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"}]}]; for (const profile of profiles) { if (profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null && ["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3"].includes(profile); } catch (gateError) { return false; } })()
16
+ (() => { try { const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; hostProfile = typeof desktopUrl === 'string' ? 'desktop' : 'web'; } catch (hostError) { hostProfile = 'web'; } const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef =
17
+ globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier,
18
+ attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles = [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0]
19
+ && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null && ["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3"].includes(profile); } catch (gateError) { return false; } })()
16
20
  - insert:
17
21
  - id: trusted-connection
18
22
  name: '@iasiv5/dsh-skip-browser-auth'
@@ -21,11 +25,13 @@
21
25
  config:
22
26
  trustedHosts: !!js ctx.webRuntime.trustedHosts
23
27
  compatibilityProfile: !!js >-
24
- (() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof
25
- manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
26
- [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"}]}]; for (const profile of profiles) { if (profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()
28
+ (() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; hostProfile = typeof desktopUrl === 'string' ? 'desktop' : 'web'; } catch (hostError) { hostProfile = 'web'; } const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef
29
+ === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url :
30
+ internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles = [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] &&
31
+ pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()
27
32
  disabled: !!js >-
28
- !((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const probeAllowed = ((() => { try { const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function')
29
- return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let
30
- anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles = [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"}]}]; for (const profile of profiles) { if (profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null && ["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3"].includes(profile); } catch (gateError) { return false; } })()); if
31
- (probeAllowed !== true) return false; return official.disabled === true; } catch (gateError) { return false; } })())
33
+ !((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const probeAllowed = ((() => { try { const profile = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = "@deepseek-ai/dsh-desktop-host" + '/package.json'; let hostProfile; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; hostProfile =
34
+ typeof desktopUrl === 'string' ? 'desktop' : 'web'; } catch (hostError) { hostProfile = 'web'; } const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ?
35
+ manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles =
36
+ [{"id":"legacy-web-v1","pairs":[{"runtime":"0.1.2-rc.1","connection":"0.1.2-rc.1"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v2","pairs":[{"runtime":"0.1.5-rc.1","connection":"0.1.5-rc.1"},{"runtime":"0.1.5-rc.2","connection":"0.1.5-rc.2"}],"hostProfiles":["web"]},{"id":"carrier-neutral-v3","pairs":[{"runtime":"0.1.7-rc.1","connection":"0.1.7-rc.1"},{"runtime":"0.1.7-rc.2","connection":"0.1.7-rc.2"},{"runtime":"0.2.0-rc.1","connection":"0.2.0-rc.1"},{"runtime":"0.2.0-rc.2","connection":"0.2.0-rc.2"}],"hostProfiles":["web"]}]; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()); return profile !== null && ["legacy-web-v1","carrier-neutral-v2","carrier-neutral-v3"].includes(profile); } catch (gateError) { return false; } })()); if (probeAllowed !== true) return false;
37
+ return official.disabled === true; } catch (gateError) { return false; } })())
@@ -15,6 +15,7 @@ export const COMPATIBILITY_PROFILES = [
15
15
  clientVariant: 'rc12',
16
16
  requiresRecoveryGlobal: false,
17
17
  browserAuthSemantics: 'root',
18
+ hostProfiles: ['web'],
18
19
  status: 'active',
19
20
  },
20
21
  {
@@ -31,6 +32,7 @@ export const COMPATIBILITY_PROFILES = [
31
32
  clientVariant: 'rc15',
32
33
  requiresRecoveryGlobal: true,
33
34
  browserAuthSemantics: 'root',
35
+ hostProfiles: ['web'],
34
36
  // Promoted after the carrier-neutral host/client adapter and fixture path
35
37
  // are implemented; real deployment validation remains documented separately.
36
38
  status: 'active',
@@ -56,16 +58,27 @@ export const COMPATIBILITY_PROFILES = [
56
58
  // (dsh-app-boot OPTIONAL_BUNDLES, web-app patch telemetry/schedule rows)
57
59
  // never intersect the connection contract. No second 0.1.x-line build input
58
60
  // is introduced: the rc17 client variant keeps anchoring at 0.1.7-rc.2.
61
+ //
62
+ // 0.2.0-rc.2 joined for the same reason (§5.7): another dependency-alignment
63
+ // republish on the contract face — lib/+client/ byte-identical in all anchor
64
+ // packages (web-app's whole diff is package.json, so cordis.patch.yml and its
65
+ // connection row are byte-identical too) — plus unrelated Desktop-only
66
+ // plugin-management CLI changes in the runtime (bin.js, lib/types/*.d.ts) and
67
+ // a ModuleLoader chunk-hash rename whose 34 changed lines all sit in that CLI
68
+ // area with resolveSync usage line-identical. rc17 keeps anchoring at
69
+ // 0.1.7-rc.2; no second 0.2.x-line build input is introduced.
59
70
  id: 'carrier-neutral-v3',
60
71
  pairs: [
61
72
  { runtime: '0.1.7-rc.1', connection: '0.1.7-rc.1' },
62
73
  { runtime: '0.1.7-rc.2', connection: '0.1.7-rc.2' },
63
74
  { runtime: '0.2.0-rc.1', connection: '0.2.0-rc.1' },
75
+ { runtime: '0.2.0-rc.2', connection: '0.2.0-rc.2' },
64
76
  ],
65
77
  hostAdapter: 'carrier-neutral-017',
66
78
  clientVariant: 'rc17',
67
79
  requiresRecoveryGlobal: true,
68
80
  browserAuthSemantics: 'mount-preserving',
81
+ hostProfiles: ['web'],
69
82
  // Promoted together with the adapter/client implementation; the fixture
70
83
  // composition suite and the real 0.1.7-rc.2 deployment checklist are the
71
84
  // promotion evidence (docs/COMPATIBILITY.md §5.4, docs/DESIGN.md).
@@ -86,13 +99,16 @@ export function resolveCompatibilityProfile(runtimeVersion, connectionVersion) {
86
99
  return undefined;
87
100
  return COMPATIBILITY_PROFILES.find((profile) => profile.pairs.some((pair) => pair.runtime === runtimeVersion && pair.connection === connectionVersion));
88
101
  }
89
- /** Resolve only a profile whose implementation is promoted to active. */
90
- export function resolveActiveCompatibilityProfile(runtimeVersion, connectionVersion) {
102
+ /** Resolve only a profile whose implementation is promoted to active AND whose
103
+ * hostProfiles admit the given host profile(未知宿主形态一律无匹配,fail-closed)。 */
104
+ export function resolveActiveCompatibilityProfile(runtimeVersion, connectionVersion, hostProfile) {
91
105
  const profile = resolveCompatibilityProfile(runtimeVersion, connectionVersion);
92
- return profile?.status === 'active' ? profile : undefined;
106
+ return profile?.status === 'active' && profile.hostProfiles.includes(hostProfile)
107
+ ? profile
108
+ : undefined;
93
109
  }
94
110
  export function isActiveCompatibilityProfileId(value) {
95
111
  return typeof value === 'string' && ACTIVE_COMPATIBILITY_PROFILE_IDS.includes(value);
96
112
  }
97
113
  /** Small JSON-safe table embedded into the Loader `!!js` expression. */
98
- export const GATE_PROFILE_TABLE = COMPATIBILITY_PROFILES.map(({ id, pairs }) => ({ id, pairs }));
114
+ export const GATE_PROFILE_TABLE = COMPATIBILITY_PROFILES.map(({ id, pairs, hostProfiles }) => ({ id, pairs, hostProfiles }));
package/lib/gate.js CHANGED
@@ -32,6 +32,15 @@
32
32
  * 环境无 require/模块导入,这是唯一受支持的同步内建模块获取方式)。
33
33
  * 两分支都拿不到精确版本 → dormant。任何异常一律 dormant(组合期绝不
34
34
  * fail loud;最后防线在 apply 内 backstop)。
35
+ *
36
+ * 宿主 profile 门控(2026-10-07,web-only / desktop 休眠):表达式在版本对判定
37
+ * 之前先解析 desktop 信号包(DESKTOP_HOST_PACKAGE)——解析出合法 {url: string}
38
+ * ⇒ desktop,由白名单表条目的 hostProfiles: ['web'] 条件统一拒绝(休眠,
39
+ * 策略单一来源 src/compatibility.ts);抛错/返回空值/url 非字符串 ⇒ 视为包
40
+ * 不存在(web 继续,组合测试 fake 与真实 v1 loader 契约即如此);resolver
41
+ * 缺失仍走原有 null 分支。检测异常的 fail-closed 兜底是版本对判定本身
42
+ * (真异常时双锚点同样解析失败 ⇒ null)。Node 侧镜像见 detectHostProfile;
43
+ * 语义与解冻条件记录于 docs/COMPATIBILITY.md §7。
35
44
  */
36
45
  import { ACTIVE_COMPATIBILITY_PROFILE_IDS, GATE_PROFILE_TABLE, } from './compatibility.js';
37
46
  export { ACTIVE_COMPATIBILITY_PROFILE_IDS, COMPATIBILITY_PROFILES, GATE_VERSION, GATE_VERSIONS, isActiveCompatibilityProfileId, resolveActiveCompatibilityProfile, resolveCompatibilityProfile, } from './compatibility.js';
@@ -40,6 +49,8 @@ export const CONNECTION_PACKAGE = '@deepseek-ai/dsh-client-connection';
40
49
  export const GATE_RUNTIME_PACKAGE = '@deepseek-ai/dsh';
41
50
  /** 探针锚点(顺序即求值顺序):宿主 runtime 本体在前,被替换对象在后。 */
42
51
  export const GATE_ANCHOR_PACKAGES = [GATE_RUNTIME_PACKAGE, CONNECTION_PACKAGE];
52
+ /** desktop 宿主信号包:仅 desktop runtime 携带;解析出合法 URL 即判定 desktop(web-only 门控)。 */
53
+ export const DESKTOP_HOST_PACKAGE = '@deepseek-ai/dsh-desktop-host';
43
54
  /**
44
55
  * 从已解析 URL 提取 pnpm store 版本段的正则源(`+name@VER_` / `+name@VER/`)。
45
56
  * 探针表达式内嵌的正则由此单一实现生成;仅匹配 pnpm 布局,非 pnpm 布局
@@ -55,7 +66,7 @@ const GATE_ANCHOR_TABLE = GATE_ANCHOR_PACKAGES.map((pkg) => [pkg, versionSegment
55
66
  // 逐锚点解析 `锚点包/package.json`,返回完整 runtime/connection pair 对应的
56
67
  // profile id;candidate profile 也可被识别,是否允许激活由下方 boolean probe
57
68
  // 根据 profile status 决定。解析失败、未知 pair 或混合 pair 一律返回 null。
58
- export const GATE_PROFILE_EXPRESSION = `(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = ${JSON.stringify(GATE_ANCHOR_TABLE)}; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles = ${JSON.stringify(GATE_PROFILE_TABLE)}; for (const profile of profiles) { if (profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()`;
69
+ export const GATE_PROFILE_EXPRESSION = `(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return null; const desktopSpecifier = ${JSON.stringify(DESKTOP_HOST_PACKAGE)} + '/package.json'; let hostProfile; try { const desktopResolved = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: desktopSpecifier, attributes: {} }) : internal.resolveSync(desktopSpecifier, ctx.baseUrl, {}); const desktopUrl = desktopResolved === null || typeof desktopResolved !== 'object' ? undefined : desktopResolved.url; hostProfile = typeof desktopUrl === 'string' ? 'desktop' : 'web'; } catch (hostError) { hostProfile = 'web'; } const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = ${JSON.stringify(GATE_ANCHOR_TABLE)}; const versions = []; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return null; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return null; } if (anchorVersion === null) return null; versions.push(anchorVersion); } const profiles = ${JSON.stringify(GATE_PROFILE_TABLE)}; for (const profile of profiles) { if (profile.hostProfiles.includes(hostProfile) && profile.pairs.some((pair) => pair.runtime === versions[0] && pair.connection === versions[1])) return profile.id; } return null; } catch (gateError) { return null; } })()`;
59
70
  // Boolean activation probe: only active profiles disable the official row.
60
71
  // A known candidate profile remains dormant until its host/client adapter is
61
72
  // promoted, while the profile expression still exposes its exact pair id.
@@ -73,3 +84,22 @@ export function evaluateGate(ctx) {
73
84
  // 与 vendor/loader/src/config/utils.ts 的 evaluate 同语义:with(ctx) + eval。
74
85
  return new Function('ctx', 'expr', 'with (ctx) { return eval(expr) }')(ctx, GATE_PROBE_EXPRESSION);
75
86
  }
87
+ /**
88
+ * Node 侧宿主形态检测(apply backstop 镜像):与 GATE_PROFILE_EXPRESSION 内联检测
89
+ * 同语义——解析 desktop 信号包得到合法 {url: string} ⇒ 'desktop';抛错/空值/url
90
+ * 非字符串 ⇒ 视为包不存在 ⇒ 'web'(fail-closed 兜底是后续版本对判定)。
91
+ * 调用方需已确认 resolver 存在(assertGateBackstop 的 presence 检查先于此执行)。
92
+ */
93
+ export function detectHostProfile(internal, baseUrl) {
94
+ const specifier = `${DESKTOP_HOST_PACKAGE}/package.json`;
95
+ try {
96
+ const resolved = internal.version === 'v2'
97
+ ? internal.resolveSync(baseUrl, { specifier, attributes: {} })
98
+ : internal.resolveSync(specifier, baseUrl, {});
99
+ const url = resolved?.url;
100
+ return typeof url === 'string' ? 'desktop' : 'web';
101
+ }
102
+ catch {
103
+ return 'web';
104
+ }
105
+ }
package/lib/host.js CHANGED
@@ -9,7 +9,7 @@
9
9
  import { readFileSync } from 'node:fs';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import z from '@deepseek-ai/schemastery';
12
- import { CONNECTION_PACKAGE, GATE_ANCHOR_PACKAGES, GATE_VERSIONS, isActiveCompatibilityProfileId, resolveActiveCompatibilityProfile, } from './gate.js';
12
+ import { CONNECTION_PACKAGE, DESKTOP_HOST_PACKAGE, detectHostProfile, GATE_ANCHOR_PACKAGES, GATE_VERSIONS, isActiveCompatibilityProfileId, resolveActiveCompatibilityProfile, } from './gate.js';
13
13
  import { COMPATIBILITY_PROFILES } from './compatibility.js';
14
14
  import { loadRuntimeConnectionModule } from './connection-runtime.js';
15
15
  import { createLegacyWebRuntime } from './adapters/legacy-web.js';
@@ -44,6 +44,11 @@ function profileById(value) {
44
44
  return undefined;
45
45
  return COMPATIBILITY_PROFILES.find((profile) => profile.id === value && profile.status === 'active');
46
46
  }
47
+ /**
48
+ * direct(无 loader/fiber entry)上下文的 profile 兜底:仅纯单测/dev 树可达。
49
+ * 宿主形态按 'web' 处理(dev 树即 web)——desktop 判定需要 resolver,真 desktop
50
+ * 上必然先经过 assertGateBackstop 的镜像检测 fail loud,不会走到这里。
51
+ */
47
52
  function directProfile(requestedProfile) {
48
53
  if (requestedProfile !== undefined) {
49
54
  const requested = profileById(requestedProfile);
@@ -82,6 +87,13 @@ function assertGateBackstop(ctx, requestedProfile) {
82
87
  }
83
88
  const baseUrl = ctx.baseUrl ?? '';
84
89
  const internal = loader.internal;
90
+ // 宿主 profile 镜像检测(2026-10-07 web-only 门控,纵深防御层):desktop 信号在
91
+ // 场 ⇒ fail loud。正常流程第①道探针已休眠、apply 不会被调;此层只防「patch 激活
92
+ // 但不应激活」。语义与 detectHostProfile/表达式层一致(docs/COMPATIBILITY.md §7)。
93
+ const hostProfile = detectHostProfile(internal, baseUrl);
94
+ if (hostProfile === 'desktop') {
95
+ throw new Error(`@iasiv5/dsh-skip-browser-auth: desktop profile not supported; disable or uninstall the plugin (${DESKTOP_HOST_PACKAGE} present)`);
96
+ }
85
97
  const versions = [];
86
98
  for (const anchorPackage of GATE_ANCHOR_PACKAGES) {
87
99
  const specifier = `${anchorPackage}/package.json`;
@@ -98,7 +110,7 @@ function assertGateBackstop(ctx, requestedProfile) {
98
110
  }
99
111
  versions.push(manifest.version);
100
112
  }
101
- const profile = resolveActiveCompatibilityProfile(versions[0], versions[1]);
113
+ const profile = resolveActiveCompatibilityProfile(versions[0], versions[1], hostProfile);
102
114
  if (profile === undefined) {
103
115
  throw new Error(`@iasiv5/dsh-skip-browser-auth: gate anchors ${GATE_ANCHOR_PACKAGES[0]}=${String(versions[0])}, ${GATE_ANCHOR_PACKAGES[1]}=${String(versions[1])} do not resolve to one active compatibility profile; whitelist is ${WHITELIST_TEXT}; disable or uninstall the plugin`);
104
116
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iasiv5/dsh-skip-browser-auth",
3
- "version": "0.3.4",
4
- "description": "替换官方 Connection 提供方跳过 BrowserAuth,免输 Token;已适配 0.1.2-rc.1、0.1.5-rc.1/rc.2、0.1.7-rc.1/rc.2 与 0.2.0-rc.1,详见仓库。",
3
+ "version": "0.3.6",
4
+ "description": "替换官方 Connection 提供方跳过 BrowserAuth,免输 Token;已适配 0.1.2-rc.1、0.1.5-rc.1/rc.2、0.1.7-rc.1/rc.2 与 0.2.0-rc.1/rc.2,详见仓库。仅支持 web profile;desktop profile(存在 dsh-desktop-host 的环境)自动休眠,属设计行为。",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "deepseek-harness",
@@ -43,6 +43,7 @@
43
43
  "test:rc15-fixture": "node scripts/install-rc15-fixture.mjs",
44
44
  "test:rc17-fixture": "node scripts/install-rc17-fixture.mjs",
45
45
  "test:all": "npm run test:rc2-fixture && npm run test:rc15-fixture && npm run test:rc17-fixture && npm run build && npm run typecheck && npm test",
46
+ "audit-candidate": "node scripts/audit-candidate.mjs",
46
47
  "prepare": "npm run build"
47
48
  },
48
49
  "dsh": {