@xinxing1708/dsh-codex-style 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.1.3 · 2026-10-24
4
+
5
+ - 整合本地最新的项目分组/单项目开合动效、对话右键菜单、长标题滚动和置顶/归档快捷提示。
6
+ - 增加后台自动检查:加载约 8 秒后查询,此后每 24 小时复查;失败时安静等待约 1 小时重试,并响应恢复联网。
7
+ - 发现新版时显示右下角提醒,可打开本插件详情或忽略本次版本;缓存检查时间与忽略版本,重新打开不会反复提醒。
8
+ - 自动检查和手动按钮复用同一查询,保留国内镜像与官方源;停用插件时取消请求、计时和事件监听。
9
+ - npm、GitHub 源码及 Windows 安装包统一为 0.1.3,保留 README 中的效果演示。
10
+
3
11
  ## 0.1.2 · 2026-10-24
4
12
 
5
13
  - npm 身份改为 `@xinxing1708/dsh-codex-style`,避免与第三方同名包冲突;显示名与仓库名可保留大小写。
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 xinxing1708 and DSH-Codex-Style contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xinxing1708 and DSH-Codex-Style contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,105 +1,117 @@
1
- # DSH Codex Style
2
-
3
- 为 **DeepSeek Harness(DSH)** 提供 Codex 桌面端风格的深色界面。当前版本 **0.1.2**,npm 包名 **`@xinxing1708/dsh-codex-style`**,适配 **Windows DSH 0.2.1-rc.2**。
4
-
5
- 本项目为社区独立插件,与 OpenAI 或 DeepSeek 无关联,未获其赞助或背书。
6
-
7
- ## 下载与安装
8
-
9
- 需要已安装 DSH `0.2.1-rc.2` 和 **Node.js 20 或更高版本**。可在 DSH 的「自定义 → 插件 → 添加插件」中输入完整 npm 包名 `@xinxing1708/dsh-codex-style`,沿用宿主的安装流程。若此前通过本项目脚本安装,请继续使用以下脚本方式升级,避免同时保留新旧名称。
10
-
11
- 在空目录的 PowerShell 中下载并解压 npm 包:
12
-
13
- ```powershell
14
- npm pack @xinxing1708/dsh-codex-style@0.1.2 --registry=https://registry.npmmirror.com
15
- tar -xf xinxing1708-dsh-codex-style-0.1.2.tgz
16
- cd package
17
- ```
18
-
19
- 国内镜像同步需要时间;若提示包尚不存在,可将 registry 换成 `https://registry.npmjs.org` 重试。
20
-
21
- 1. 退出 DSH。
22
- 2. 双击 `install.cmd`,或在解压目录运行:
23
-
24
- ```powershell
25
- node scripts/install.mjs
26
- ```
27
-
28
- 3. 安装成功后启动 DSH。
29
-
30
- 安装器自动核对 DSH 版本,备份 desktop profile 的三份配置,安装插件并输出回退脚本位置。无需管理员权限、无需构建或安装 npm 依赖。已有全局主题会按安装规则停用,安装文件保留;升级本插件时保留上一版本。
31
-
32
- DSH 安装在自定义位置时,在同一个 PowerShell 窗口设置现有 DSH 数据根目录,再运行安装:
33
-
34
- ```powershell
35
- $env:DSH_HOME = "你的 DSH 数据根目录"
36
- node scripts/install.mjs
37
- ```
38
-
39
- 默认数据根目录为当前用户的 `.dsh`。这里应指向含 `profiles/desktop` 和 `dsh-runtimes` 的目录。
40
-
41
- ## 功能
42
-
43
- - 52px 常驻导航:首页、定时任务、自定义;社区快捷入口沿用宿主原按钮与图标。
44
- - 默认 288px 的可拖动、可收起二级侧栏,收起后悬停显示浮卡。
45
- - 真实项目和会话树,包含标题/项目过滤、置顶、重命名、分叉、归档与恢复。
46
- - 项目分组和单项目平滑展开/收起;聊天右键及 Shift+F10 复用原菜单。
47
- - 长标题悬停后滚动;聊天行尾提供带渐变文字提示的置顶/归档图标,已置顶显示实心图钉。
48
- - 新聊天、项目悬停详情和项目编辑。
49
- - 齿轮账号/设置菜单,设置提示渐显渐隐。
50
- - 单行搜索框保持 32px;发送按钮在空输入时为灰色,有内容时为灰白色。
51
- - 真实权限预设、模型目录、思考强度滑条及 Max 动效。
52
- - 保留 DSH 原生输入编辑器、附件、“+”、消息、工具调用与审批流程。
53
- - 本插件详情页在「卸载」左侧提供真实「检查更新」按钮,可查看当前版本、最新版本和查询结果。
54
-
55
- 项目移除、会话归档和权限切换等控件使用宿主真实动作。模型切换沿用 DSH 的默认值保存行为,实际可用档位由模型目录决定。
56
-
57
- ## 检查更新
58
-
59
- 打开「自定义 → 插件 → 本插件详情」,点击「检查更新」。检查只读取 npm 版本信息,不安装或卸载插件。只有本插件的已安装包详情显示该入口,组件子页与其他插件不显示。
60
-
61
- 查询并行使用 npmmirror、腾讯云 npm 镜像和 npm 官方源,按有效响应中的最高版本提示;不依赖 GitHub,也不需要登录 npm。请求不携带凭据,最长等待约 6.5 秒;所有源失败、包未发布和最新版分别提示,不将错误当成已更新。镜像可能同步延迟,结果中会注明来源。
62
-
63
- 国内镜像为大陆直连用户提供更新查询通道;可用性仍取决于用户网络和镜像服务,无法保证所有运营商、所有时刻均可达。当前版本提供手动检查,没有后台定时检查或自动安装。
64
-
65
- ## 回退或卸载
66
-
67
- 退出 DSH,运行安装器输出的回退脚本:
68
-
69
- ```powershell
70
- node "<备份目录>/rollback.mjs"
71
- ```
72
-
73
- 初装时恢复安装前配置并解除插件链接;升级时恢复上一版本。若安装后的配置有其他修改,脚本会停止并提示合并。备份和插件版本目录保留,回退后重新启动 DSH。
74
-
75
- ## 从源码构建
76
-
77
- 下载仓库源码或克隆本仓库,在根目录运行:
78
-
79
- ```powershell
80
- npm run build
81
- ```
82
-
83
- 构建仅使用 Node.js 标准库,不需要 `npm install` 或联网。所需源码、样式、自绘图标和品牌图均已包含,产物写入 `lib/`。随后可运行 `node scripts/install.mjs`。
84
-
85
- | 路径 | 用途 |
86
- |---|---|
87
- | `src/` | 客户端组件、宿主入口及局部样式 |
88
- | `assets/` | 独立构建所需令牌、导航样式、图标及品牌图 |
89
- | `lib/` | 可安装的构建产物 |
90
- | `scripts/` | 构建、安装及回退 |
91
- | `cordis.patch.yml` / `skin.json` | DSH 插件接入及皮肤元数据 |
92
-
93
- ## 兼容范围
94
-
95
- 0.1.2 的四项侧栏交互已通过实际组件的隔离浏览器检查;更新入口以 DSH 随包的真实插件详情页与按钮验证位置、状态、取消及无修改行为,并完成本机无代理查询。真实日常宿主交互仍待用户验收。
96
-
97
- 仅适配 Windows DSH `0.2.1-rc.2`,当前只有深色主题。消息结构、插件/任务/设置内页及原生窗口控制沿用宿主;搜索范围为项目和会话标题。DOM 适配和布局接线依赖此 DSH 版本,升级宿主后需要重新核对。
98
-
99
- 发行前完成独立源码构建、包内容与隐私检查、临时 profile 中的安装/升级/回退及旧包名迁移验证;0.1.1 的搜索高度、发送配色和已有悬停修复继续保留。隔离检查不等同于日常 DSH 的完整交互验收。
100
-
101
- ## 许可与致谢
102
-
103
- 自研代码使用 [MIT License](LICENSE)。感谢项目贡献者及 `yuehuawu416-cmyk` 的修复。
104
-
105
- 品牌图来源、MIT 原文及上游品牌规范见 [第三方说明](THIRD_PARTY_NOTICES.md)。本包不分发个人配置、聊天数据、开发记录或参考截图。
1
+ # DSH Codex Style
2
+
3
+ 为 **DeepSeek Harness(DSH)** 提供 Codex 桌面端风格的深色界面。当前版本 **0.1.3**,npm 包名 **`@xinxing1708/dsh-codex-style`**,适配 **Windows DSH 0.2.1-rc.2**。
4
+
5
+ 本项目为社区独立插件,与 OpenAI 或 DeepSeek 无关联,未获其赞助或背书。
6
+
7
+ ## 下载与安装
8
+
9
+ 需要已安装 DSH `0.2.1-rc.2` 和 **Node.js 20 或更高版本**。可在 DSH 的「自定义 → 插件 → 添加插件」中输入完整 npm 包名 `@xinxing1708/dsh-codex-style`,沿用宿主的安装流程。若此前通过本项目脚本安装,请继续使用以下脚本方式升级,避免同时保留新旧名称。
10
+
11
+ 在空目录的 PowerShell 中下载并解压 npm 包:
12
+
13
+ ```powershell
14
+ npm pack @xinxing1708/dsh-codex-style@0.1.3 --registry=https://registry.npmmirror.com
15
+ tar -xf xinxing1708-dsh-codex-style-0.1.3.tgz
16
+ cd package
17
+ ```
18
+
19
+ 国内镜像同步需要时间;若提示包尚不存在,可将 registry 换成 `https://registry.npmjs.org` 重试。
20
+
21
+ 1. 退出 DSH。
22
+ 2. 双击 `install.cmd`,或在解压目录运行:
23
+
24
+ ```powershell
25
+ node scripts/install.mjs
26
+ ```
27
+
28
+ 3. 安装成功后启动 DSH。
29
+
30
+ 安装器自动核对 DSH 版本,备份 desktop profile 的三份配置,安装插件并输出回退脚本位置。无需管理员权限、无需构建或安装 npm 依赖。已有全局主题会按安装规则停用,安装文件保留;升级本插件时保留上一版本。
31
+
32
+ DSH 安装在自定义位置时,在同一个 PowerShell 窗口设置现有 DSH 数据根目录,再运行安装:
33
+
34
+ ```powershell
35
+ $env:DSH_HOME = "你的 DSH 数据根目录"
36
+ node scripts/install.mjs
37
+ ```
38
+
39
+ 默认数据根目录为当前用户的 `.dsh`。这里应指向含 `profiles/desktop` 和 `dsh-runtimes` 的目录。
40
+
41
+ ## 功能
42
+
43
+ - 52px 常驻导航:首页、定时任务、自定义;社区快捷入口沿用宿主原按钮与图标。
44
+ - 默认 288px 的可拖动、可收起二级侧栏,收起后悬停显示浮卡。
45
+ - 真实项目和会话树,包含标题/项目过滤、置顶、重命名、分叉、归档与恢复。
46
+ - 项目分组和单项目平滑展开/收起;聊天右键及 Shift+F10 复用原菜单。
47
+ - 长标题悬停后滚动;聊天行尾提供带渐变文字提示的置顶/归档图标,已置顶显示实心图钉。
48
+ - 新聊天、项目悬停详情和项目编辑。
49
+ - 齿轮账号/设置菜单,设置提示渐显渐隐。
50
+ - 单行搜索框保持 32px;发送按钮在空输入时为灰色,有内容时为灰白色。
51
+ - 真实权限预设、模型目录、思考强度滑条及 Max 动效。
52
+ - 保留 DSH 原生输入编辑器、附件、“+”、消息、工具调用与审批流程。
53
+ - 本插件详情页在「卸载」左侧提供真实「检查更新」按钮,可查看当前版本、最新版本和查询结果。
54
+
55
+ 项目移除、会话归档和权限切换等控件使用宿主真实动作。模型切换沿用 DSH 的默认值保存行为,实际可用档位由模型目录决定。
56
+
57
+ ## 检查更新
58
+
59
+ 打开「自定义 → 插件 → 本插件详情」,点击「检查更新」。检查只读取 npm 版本信息,不安装或卸载插件。只有本插件的已安装包详情显示该入口,组件子页与其他插件不显示。
60
+
61
+ 插件加载约 8 秒后自动查询,此后每 24 小时复查;查询结果保存在本地,重新打开不会重复查询。发现新版时在右下角提醒,点击「查看更新」进入本插件详情。关闭提醒会记住本次版本,下一个新版仍会提醒。查询失败时安静等待约 1 小时再试,恢复联网后可提前重试;手动按钮始终可用。
62
+
63
+ 查询并行使用 npmmirror、腾讯云 npm 镜像和 npm 官方源,按有效响应中的最高版本提示;不依赖 GitHub,也不需要登录 npm。请求不携带凭据,最长等待约 6.5 秒;所有源失败、包未发布和最新版分别提示,不将错误当成已更新。镜像可能同步延迟,结果中会注明来源。停用插件会取消检查与定时器。
64
+
65
+ 国内镜像为大陆直连用户提供更新查询通道;可用性仍取决于用户网络和镜像服务,无法保证所有运营商、所有时刻均可达。自动检查负责提醒,安装仍由用户按本页说明执行。
66
+
67
+ ## 回退或卸载
68
+
69
+ 退出 DSH,运行安装器输出的回退脚本:
70
+
71
+ ```powershell
72
+ node "<备份目录>/rollback.mjs"
73
+ ```
74
+
75
+ 初装时恢复安装前配置并解除插件链接;升级时恢复上一版本。若安装后的配置有其他修改,脚本会停止并提示合并。备份和插件版本目录保留,回退后重新启动 DSH。
76
+
77
+ ## 从源码构建
78
+
79
+ 下载仓库源码或克隆本仓库,在根目录运行:
80
+
81
+ ```powershell
82
+ npm run build
83
+ ```
84
+
85
+ 构建仅使用 Node.js 标准库,不需要 `npm install` 或联网。所需源码、样式、自绘图标和品牌图均已包含,产物写入 `lib/`。随后可运行 `node scripts/install.mjs`。
86
+
87
+ ## 部分效果演示
88
+ <img width="962" height="617" alt="image" src="https://github.com/user-attachments/assets/73a8455d-f4c0-476e-a8de-731c3bec69c4" />
89
+ <img width="962" height="617" alt="image" src="https://github.com/user-attachments/assets/17d9ed75-c0ff-4a6d-b759-c208c58b1099" />
90
+ <img width="962" height="617" alt="image" src="https://github.com/user-attachments/assets/a7dd4a26-9100-4633-865e-d3dc2dc82269" />
91
+ <img width="962" height="617" alt="image" src="https://github.com/user-attachments/assets/ee67555e-3fd0-4995-8ed2-db1c430b2855" />
92
+ <img width="962" height="617" alt="image" src="https://github.com/user-attachments/assets/8e74053d-a8a2-4c55-96c1-db504554c361" />
93
+
94
+
95
+
96
+
97
+ | 路径 | 用途 |
98
+ |---|---|
99
+ | `src/` | 客户端组件、宿主入口及局部样式 |
100
+ | `assets/` | 独立构建所需令牌、导航样式、图标及品牌图 |
101
+ | `lib/` | 可安装的构建产物 |
102
+ | `scripts/` | 构建、安装及回退 |
103
+ | `cordis.patch.yml` / `skin.json` | DSH 插件接入及皮肤元数据 |
104
+
105
+ ## 兼容范围
106
+
107
+ 四项侧栏交互已通过实际组件的隔离浏览器检查;更新入口以 DSH 随包的真实插件详情页与按钮验证位置、状态、取消及无修改行为,并完成本机无代理查询。0.1.3 另验证了自动检查周期、缓存、断网恢复、版本提醒与忽略操作。真实日常宿主交互仍待用户验收。
108
+
109
+ 仅适配 Windows DSH `0.2.1-rc.2`,当前只有深色主题。消息结构、插件/任务/设置内页及原生窗口控制沿用宿主;搜索范围为项目和会话标题。DOM 适配和布局接线依赖此 DSH 版本,升级宿主后需要重新核对。
110
+
111
+ 发行前完成独立源码构建、包内容与隐私检查、临时 profile 中的安装/升级/回退及旧包名迁移验证;0.1.1 的搜索高度、发送配色和已有悬停修复继续保留。隔离检查不等同于日常 DSH 的完整交互验收。
112
+
113
+ ## 许可与致谢
114
+
115
+ 自研代码使用 [MIT License](LICENSE)。感谢项目贡献者及 `yuehuawu416-cmyk` 的修复。
116
+
117
+ 品牌图来源、MIT 原文及上游品牌规范见 [第三方说明](THIRD_PARTY_NOTICES.md)。本包不分发个人配置、聊天数据、开发记录或参考截图。
package/install.cmd CHANGED
@@ -1,17 +1,17 @@
1
- @echo off
2
- setlocal
3
- cd /d "%~dp0"
4
- where node >nul 2>nul
5
- if errorlevel 1 (
6
- echo Node.js 20 or newer is required. Install Node.js, then run this file again.
7
- pause
8
- exit /b 1
9
- )
10
- node scripts\install.mjs
11
- if errorlevel 1 (
12
- echo Installation failed. Read the message above before trying again.
13
- pause
14
- exit /b 1
15
- )
16
- echo Installation complete. Restart DSH when convenient.
17
- pause
1
+ @echo off
2
+ setlocal
3
+ cd /d "%~dp0"
4
+ where node >nul 2>nul
5
+ if errorlevel 1 (
6
+ echo Node.js 20 or newer is required. Install Node.js, then run this file again.
7
+ pause
8
+ exit /b 1
9
+ )
10
+ node scripts\install.mjs
11
+ if errorlevel 1 (
12
+ echo Installation failed. Read the message above before trying again.
13
+ pause
14
+ exit /b 1
15
+ )
16
+ echo Installation complete. Restart DSH when convenient.
17
+ pause
package/lib/client.js CHANGED
@@ -2,10 +2,10 @@ window.__ModuleLoader__.load({
2
2
  id: "@xinxing1708/dsh-codex-style",
3
3
  factory: function(require) {
4
4
  'use strict';
5
- const CSS_TEXT = "/* 自研界面令牌;尺寸与色值参考界面测量。来源说明见 THIRD_PARTY_NOTICES.md。 */\r\n\r\nbody[data-dsh-codex-style] {\r\n /* ---- 间距 ---- */\r\n --cx-spacing: 4px;\r\n --cx-space-1: 4px;\r\n --cx-space-2: 8px;\r\n --cx-space-3: 12px;\r\n --cx-space-4: 16px;\r\n --cx-space-5: 20px;\r\n --cx-space-6: 24px;\r\n --cx-space-8: 32px;\r\n\r\n /* ---- 圆角阶梯 ---- */\r\n --cx-radius-2xs: 2px;\r\n --cx-radius-xs: 4px;\r\n --cx-radius-sm: 6px;\r\n --cx-radius-md: 8px;\r\n --cx-radius-lg: 10px;\r\n --cx-radius-xl: 12px;\r\n --cx-radius-2xl: 16px;\r\n --cx-radius-3xl: 20px;\r\n --cx-radius-4xl: 24px;\r\n --cx-radius-composer: 25px;\r\n --cx-radius-full: 9999px;\r\n\r\n /* ---- 暗色面(深色界面:导航/顶栏 #202020、侧栏 #1b1b1b、对话区 #181818) ---- */\r\n --cx-window: #202020;\r\n --cx-sidebar: #1b1b1b;\r\n --cx-surface: #181818;\r\n --cx-thread: #171717;\r\n --cx-nested: #212121;\r\n --cx-elevated: #282828;\r\n --cx-summary: #2d2d2d;\r\n --cx-hover: rgba(255, 255, 255, 0.045);\r\n --cx-active: rgba(255, 255, 255, 0.08);\r\n\r\n /* ---- 边框 ---- */\r\n --cx-border-l1: rgba(255, 255, 255, 0.08);\r\n --cx-border-l2: rgba(255, 255, 255, 0.04);\r\n --cx-border-l3: rgba(255, 255, 255, 0.16);\r\n --cx-border-strong: rgba(255, 255, 255, 0.16);\r\n\r\n /* ---- 文本 ---- */\r\n --cx-text: #ffffff;\r\n --cx-text-2: rgba(255, 255, 255, 0.7);\r\n --cx-text-3: rgba(255, 255, 255, 0.5);\r\n --cx-text-4: rgba(255, 255, 255, 0.32);\r\n --cx-text-inverse: #0d0d0d;\r\n\r\n /* ---- 强调 / 状态 ---- */\r\n --cx-accent: #0285ff;\r\n --cx-accent-soft: #339cff;\r\n --cx-focus: #339cff;\r\n --cx-success: #40c977;\r\n --cx-danger: #ff6764;\r\n --cx-warning: #ff8549;\r\n --cx-attention: #3a83f7;\r\n\r\n /* ---- diff ---- */\r\n --cx-diff-add-bg: rgba(64, 201, 119, 0.23);\r\n --cx-diff-del-bg: rgba(255, 103, 100, 0.23);\r\n --cx-diff-add-mark: #40c977;\r\n --cx-diff-del-mark: #ff6764;\r\n\r\n /* ---- 字体 ---- */\r\n --cx-font-sans: -apple-system, system-ui, \"Segoe UI\", sans-serif;\r\n --cx-font-mono: ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n\r\n /* ---- 字号 / 行高 ---- */\r\n --cx-text-xs: 12px;\r\n --cx-text-sm: 14px;\r\n --cx-text-md: 16px;\r\n --cx-text-lg: 18px;\r\n --cx-leading-xs: 16px;\r\n --cx-leading-sm: 22px;\r\n --cx-leading-md: 24px;\r\n --cx-leading-lg: 28px;\r\n\r\n /* ---- 字重 ---- */\r\n --cx-weight-regular: 400;\r\n --cx-weight-medium: 500;\r\n --cx-weight-semibold: 600;\r\n --cx-weight-bold: 700;\r\n --cx-weight-shell: 445; /* 外壳专用非标准字重 */\r\n\r\n /* ---- 动效 ---- */\r\n --cx-dur-fast: 120ms;\r\n --cx-dur: 150ms;\r\n --cx-dur-relaxed: 300ms;\r\n --cx-ease: ease;\r\n --cx-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);\r\n --cx-ease-scroll: cubic-bezier(0.22, 1, 0.36, 1);\r\n\r\n /* ---- 布局几何 ----\r\n 以真实 Codex 窗口截图(1988×1292 @1.5x)实测回填:\r\n 侧栏 288、标题栏 44、输入栈 710,详见 02-开发日志 第 10 轮。 */\r\n --cx-titlebar-h: 44px;\r\n --cx-rail-w: 52px;\r\n --cx-sidebar-w: 288px;\r\n --cx-thread-max: 768px;\r\n --cx-right-w: 667px;\r\n --cx-header-h: 48px;\r\n --cx-row-h: 30px;\r\n --cx-control-h: 28px;\r\n --cx-send-h: 32px;\r\n --cx-composer-stack-w: 742px; /* 含左右 16px 内边距 → 内容 710 */\r\n --cx-composer-h: 97px;\r\n --cx-picker-h: 37px;\r\n\r\n /* ---- 输入区实测色(真实截图取色) ---- */\r\n --cx-picker-fill: #282828;\r\n --cx-composer-fill: rgba(55, 55, 55, 0.94);\r\n\r\n /* ---- 应用视口 ----\r\n 真实 DSH 窗口截图 1988×1292(Windows 150% 缩放)换算为 1325×861 CSS。\r\n 原型按此尺寸渲染,各栏占比才与真实界面一致(旧 1600 视口下主区偏宽)。 */\r\n --cx-app-w: 1325px;\r\n --cx-app-h: 861px;\r\n\r\n /* ---- 推理强度选择器 ----\r\n 尺寸/颜色按用户真实 Codex 录屏帧实测(1992×1296 @1.503x)。 */\r\n --cx-reasoning-w: 255px;\r\n --cx-reasoning-snap-dur: 140ms; /* 第 20 轮节点切换过渡,待用户验收 */\r\n --cx-reasoning-fx-dur: 180ms;\r\n --cx-reasoning-text-dur: 220ms;\r\n /* 第 23 轮按用户新设计口径增强流速,两个亮带错速运行。 */\r\n --cx-reasoning-flow-dur: 2.8s;\r\n --cx-reasoning-bloom-dur: 4.2s;\r\n --cx-reasoning-blue: #4c9bfe;\r\n --cx-reasoning-track: #3e3e3e;\r\n /* 第 22 轮:新录屏 100/660/900/1120 帧取色,深底与两种柔亮色分层。 */\r\n --cx-reasoning-violet: #5134ad;\r\n --cx-reasoning-purple: #844fcc;\r\n --cx-reasoning-max-blue: #3a4ebd;\r\n --cx-reasoning-max-glow: #bd8dff;\r\n --cx-reasoning-max-ice: #adaaff;\r\n --cx-reasoning-max-text: #b98cff;\r\n --cx-reasoning-title: #4c9bfe;\r\n /* 浮层/菜单底色:真实录屏帧取色 rgb(45,45,45) */\r\n --cx-popover-fill: #2d2d2d;\r\n\r\n /* ---- 阴影 ---- */\r\n --cx-shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 0.08);\r\n --cx-shadow-md: 0 2px 4px -1px rgb(0 0 0 / 0.08);\r\n --cx-shadow-lg: 0 4px 8px -2px rgb(0 0 0 / 0.1);\r\n --cx-shadow-xl: 0 8px 16px -4px rgb(0 0 0 / 0.12);\r\n --cx-shadow-2xl: 0 16px 32px -8px rgb(0 0 0 / 0.19);\r\n --cx-composer-shadow:\r\n 0 0 0 0.5px var(--cx-border-strong),\r\n 0 3px 7.5px rgb(0 0 0 / 0.04),\r\n 0 0 20px rgb(0 0 0 / 0.05);\r\n\r\n /* ---- z 轴 ---- */\r\n --cx-z-ground: 0;\r\n --cx-z-panel: 10;\r\n --cx-z-floating: 20;\r\n --cx-z-drawer: 40;\r\n --cx-z-popover: 50;\r\n --cx-z-overlay: 70;\r\n}\r\n\n/* ============ 导航栏 ============ */\r\n.dsc-rail {\r\n grid-area: rail;\r\n position: relative;\r\n z-index: 30;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 8px 0;\r\n background: var(--cx-window);\r\n border-right: 1px solid var(--cx-border-l2);\r\n}\r\n.dsc-rail__spacer { flex: 1; }\r\n.dsc-rail__item {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: var(--cx-radius-md);\r\n color: var(--cx-text-3);\r\n transition: background-color var(--cx-dur-fast) var(--cx-ease), color var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-rail__item:hover { background: var(--cx-hover); color: var(--cx-text); }\r\n.dsc-rail__item--active { background: var(--cx-active); color: var(--cx-text); }\r\n/* 插头先以不透明白色统一描边,再整体降低透明度,避免相交线段叠色发亮。 */\r\n.dsc-rail__plugin-icon { color: #fff; opacity: .5; stroke-width: 1.5; transition: opacity var(--cx-dur-fast) var(--cx-ease); }\r\n.dsc-rail__item:hover .dsc-rail__plugin-icon,\r\n.dsc-rail__item--active .dsc-rail__plugin-icon { opacity: 1; }\r\n.dsc-rail__item::after {\r\n content: attr(data-tip);\r\n position: absolute;\r\n left: 44px;\r\n top: 50%;\r\n z-index: var(--cx-z-popover);\r\n padding: 4px 8px;\r\n border-radius: var(--cx-radius-sm);\r\n border: 1px solid var(--cx-border-l1);\r\n background: #2b2b2b;\r\n color: var(--cx-text);\r\n font-size: 12px;\r\n line-height: 16px;\r\n white-space: nowrap;\r\n box-shadow: var(--cx-shadow-lg);\r\n opacity: 0;\r\n transform: translateY(-50%) translateX(-2px);\r\n pointer-events: none;\r\n transition: opacity var(--cx-dur-fast) var(--cx-ease), transform var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-rail__item:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }\r\n\r\n/* ============ 侧栏 ============ */\r\n.dsc-sidebar {\r\n grid-area: sidebar;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n padding: 8px;\r\n background: var(--cx-sidebar);\r\n border-right: 1px solid var(--cx-border-l2);\r\n overflow: hidden;\r\n}\r\n.dsc-sidebar__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n height: 44px;\r\n margin: -8px -8px 6px;\r\n padding: 0 6px 0 10px;\r\n}\r\n.dsc-brand { display: flex; align-items: center; gap: 7px; }\r\n.dsc-brand__mark { display: block; height: 19px; width: auto; }\r\n.dsc-brand__wordmark {\r\n font-size: 17px;\r\n font-weight: 700;\r\n letter-spacing: -0.02em;\r\n color: var(--cx-text);\r\n line-height: 1;\r\n}\r\n.dsc-brand__badge {\r\n font-size: 9px;\r\n font-weight: 700;\r\n letter-spacing: 0.14em;\r\n line-height: 1;\r\n padding: 3px 5px 2px;\r\n border-radius: 3px;\r\n background: var(--cx-text);\r\n color: var(--cx-window);\r\n}\r\n.dsc-sidebar__header-actions { display: flex; align-items: center; gap: 2px; }\r\n.dsc-new-chat {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n height: 32px;\r\n padding: 0 8px;\r\n border-radius: var(--cx-radius-md);\r\n font-size: 13px;\r\n font-weight: var(--cx-weight-medium);\r\n color: var(--cx-text);\r\n background: var(--cx-active);\r\n transition: background-color var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-new-chat:hover { background: rgba(255, 255, 255, 0.12); }\r\n.dsc-new-chat kbd { margin-left: auto; font-size: 11px; color: var(--cx-text-4); }\r\n\n/* 自有组件与公开 slot/region 锚点;外壳几何适配通过自有 data-dsc-* 标记集中处理。 */\nbody[data-dsh-codex-style] { background: var(--cx-window); color-scheme: dark; --shiki-token-constant: #4dabf7; --shiki-token-string: #69db7c; --shiki-token-comment: #adb5bd; --shiki-token-keyword: #faa2c1; --shiki-token-function: #b197fc; }\n/* 整个三列外壳实际缩窄 52px,让宿主 ResizeObserver、右栏与拖拽使用同一可用宽度。\n 不能用 padding-left 代替:宿主测量的是 getBoundingClientRect().width。 */\nbody[data-dsh-codex-chrome] [data-dsc-frame] { margin-left: var(--cx-rail-w); width: calc(100% - var(--cx-rail-w)); }\n/* 全屏右栏原本按 100vw 展开;扣掉独立导航后,继续尊重宿主二级侧栏的真实宽度。 */\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame] [data-sidebar-right-panel=\"fullscreen\"] { max-width: max(0px, calc(100vw - var(--cx-rail-w) - var(--dsh-windows-sidebar-width, 0px))); }\n/* data-slot 是 renderer 声明的可寻址面;这里只传递变量,不选择/搬移宿主内部节点。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] { --dsw-specific-input-major: var(--cx-composer-fill); --dsw-radius-panel: var(--cx-radius-composer); --dsw-alias-border-l2: var(--cx-border-strong); --dsw-elevation-soft: var(--cx-composer-shadow); --dsh-composer-card-max-width: 710px; }\n/* DSH 0.2.1-rc.2 中英文发送标签;只覆盖发送色,保留停止按钮与宿主提交状态。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]) { --dsw-alias-button-info-fill: #dfdfdf; --dsw-alias-button-info-hover: #dfdfdf; color: #363636; opacity: 1; }\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]):disabled { --dsw-alias-button-info-fill: #8b8b8b; }\n/* 正文字体颜色:按 Codex 截图采样 #dfdfdf(原 #ffffff 偏亮)。仅覆盖对话内容区,不影响按钮、菜单等其它主文本。 */\nbody[data-dsh-codex-style] [data-conversation-content] { --dsw-alias-label-primary: #dfdfdf; }\n/* 原 sidebar/settings 父级继续挂载以保留原生页面与子槽位所有权;只隐藏其旧外观。 */\nbody[data-dsh-codex-chrome] [data-slot=\"sidebar\"] { display: block !important; position: relative; width: 100%; height: 100%; visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-slot=\"conversation.composer.bar\"] { --dsh-composer-side-clearance: 0px; display: flex !important; flex-direction: column; align-items: stretch; align-self: center; width: min(calc(100% - 32px),710px); max-width: 710px; position: relative; }\n/* 空态保留官方编辑器、草稿、附件、“+”与 chain;没有重建输入状态或移动节点。 */\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] { visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-region=\"composer\"] { position: absolute; inset: auto 0 0; z-index: 7; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.composer.bar\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.input.dock\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-composer-overlay] { visibility: visible; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"][data-dsc-hero-layout] [data-slot=\"conversation.hero.agentPreset\"] { visibility: visible; display: inline-flex !important; position: fixed; top: calc(var(--dsc-hero-card-top) - 33px); right: calc(100vw - var(--dsc-hero-card-right) + 18px); z-index: 8; height: 28px; max-width: 180px; }\n.dsc-ui { font: 14px/22px var(--cx-font-sans); color: var(--cx-text); box-sizing: border-box; }\n.dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { box-sizing: border-box; }\n.dsc-ui button, .dsc-ui input { font: inherit; color: inherit; }\n.dsc-ui button { border: 0; cursor: pointer; background: none; }\n.dsc-ui button:disabled { cursor: default; opacity: .45; }\n.dsc-ui button:focus-visible, .dsc-ui [role=slider]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: 2px; }\n.dsc-ui svg { flex: none; }\n.dsc-update-control { display: inline-flex; align-items: center; -webkit-app-region: no-drag; }\n.dsc-ui.dsc-update-panel { padding: 14px; }\n.dsc-update-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.dsc-ui .dsc-update-close { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border-radius: 6px; color: var(--cx-text-2); }\n.dsc-update-close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-update-message { margin: 14px 0 10px; font-weight: 500; }\n.dsc-update-message[data-state=update] { color: #72b4ff; }\n.dsc-update-message[data-state=error] { color: #ff8585; }\n.dsc-update-versions { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 0; font-size: 13px; }\n.dsc-update-versions dt { color: var(--cx-text-2); }\n.dsc-update-versions dd { margin: 0; text-align: right; overflow-wrap: anywhere; }\n.dsc-update-note { margin: 10px 0 0; color: var(--cx-text-2); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }\n.dsc-update-note > span { display: block; color: var(--cx-text-3); }\n.dsc-ui .dsc-update-retry { margin-top: 14px; padding: 5px 10px; border: 1px solid var(--cx-border-l3); border-radius: 7px; }\n.dsc-update-retry:hover { background: var(--cx-hover); }\n.dsc-update-spin { animation: dsc-update-spin .9s linear infinite; }\n@keyframes dsc-update-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) { .dsc-update-spin { animation: none; } }\n.dsc-proto-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; line-height: 1; }\n.dsc-proto-icon svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }\n/* 同一内容 portal 到 body:常驻时跟随宿主列,退场与浮卡不受 0px 列裁剪。只动画透明度和位移。 */\n.dsc-sidebar-shell { visibility: hidden; position: fixed; z-index: 20; min-width: 0; min-height: 0; display: flex; opacity: 0; transform: translateX(-24px); pointer-events: none; transition: opacity 180ms ease, transform 240ms var(--cx-ease-out), visibility 0s 240ms; -webkit-app-region: no-drag; }\n.dsc-sidebar-shell[data-open=true] { visibility: visible; opacity: 1; transform: translateX(0); pointer-events: auto; transition: opacity 180ms ease, transform 260ms var(--cx-ease-out), visibility 0s; }\n.dsc-sidebar-shell[data-mode=docked] > .dsc-sidebar { border-top-left-radius: 16px; }\n.dsc-sidebar-shell[data-mode=floating] { z-index: 40; transform: translateX(-12px); transition: opacity 150ms ease, transform 180ms var(--cx-ease-out), visibility 0s 180ms; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] { transform: translateX(0); transition-delay: 0s; }\n.dsc-sidebar-shell[data-mode=floating]::before { content: ''; position: absolute; top: 0; bottom: 0; left: -12px; width: 12px; }\n.dsc-sidebar-shell[data-mode=floating] > .dsc-sidebar { --cx-sidebar: #2c2c2c; border: 1px solid var(--cx-border-l1); border-radius: 12px; box-shadow: 0 0 0 3px #18181880, 0 8px 28px #0005; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] > .dsc-sidebar { animation: dsc-sidebar-content-in 150ms var(--cx-ease-out) both; }\n@keyframes dsc-sidebar-content-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: translateX(0); } }\n.dsc-independent-rail { visibility: visible; position: fixed; top: var(--dsh-frame-chrome-top, 0px); bottom: 0; left: 0; width: var(--cx-rail-w); z-index: 25; min-height: 0; padding-bottom: 56px; -webkit-app-region: no-drag; }\n.dsc-extension-navigation-anchor { flex: 1; align-self: stretch; min-height: 0; }\n/* 原生 panellist 继续负责图标注入、选中态、点击和卸载;只重排其可见区域。\n 位置来自自有轨道的剩余空间,超高时独立滚动,不覆盖底部齿轮。\n Windows 原样式在 collapsed 时 display:none,并在开合时淡出,故明确覆盖这两项。 */\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] { visibility: visible; display: flex !important; position: fixed; left: var(--dsc-extension-left); top: var(--dsc-extension-top); width: var(--dsc-extension-width); max-height: var(--dsc-extension-height); min-height: 0; z-index: 26; flex-direction: column; align-items: center; gap: 4px !important; padding: 0; margin: 0 !important; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ffffff26 transparent; opacity: 1 !important; transform: none !important; animation: none !important; transition: none !important; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-hidden] { display: none !important; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row] { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; min-height: 36px; padding: 0; margin: 0; border: 0; border-radius: var(--cx-radius-md); background: transparent; color: var(--cx-text-3); cursor: pointer; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:hover { color: var(--cx-text); background: var(--cx-hover); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row][aria-current=page] { color: var(--cx-text); background: var(--cx-active); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-label] { display: none; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] svg { display: block; max-width: 20px; max-height: 20px; }\n.dsc-navigation-error { position: fixed; left: calc(var(--cx-rail-w) + 8px); top: var(--dsh-frame-overlay-top, 20px); z-index: 50; max-width: 320px; }\n.dsc-rail .dsc-rail__item { color: var(--cx-text-3); background: transparent; flex: none; padding: 0; }\n.dsc-rail .dsc-rail__item:hover { color: var(--cx-text); background: var(--cx-hover); }\n.dsc-rail .dsc-rail__item--active { color: var(--cx-text); background: var(--cx-active); }\n.dsc-rail__item[data-preview-enabled=true]::after { display: none; }\n.dsc-sidebar { flex: 1; min-width: 0; min-height: 0; overflow: hidden; }\n.dsc-sidebar .dsc-sidebar__header { flex: none; }\n.dsc-sidebar .dsc-brand { min-width: 0; gap: 6px; }\n.dsc-sidebar .dsc-brand__mark { height: 19px; max-width: 25px; }\n.dsc-sidebar .dsc-brand__badge { font-size: 8px; padding: 3px 4px 2px; }\n.dsc-sidebar .dsc-sidebar__header-actions { margin-left: auto; }\n/* Codex:新聊天默认无底色、不显示快捷键,仅悬停时出现灰色背景。 */\n.dsc-sidebar .dsc-new-chat { flex: none; justify-content: flex-start; width: 100%; font-size: 13px; font-weight: var(--cx-weight-medium); background: transparent; color: #c2c2c2; margin-bottom: 8px; }\n.dsc-sidebar .dsc-new-chat:hover { background: rgba(255,255,255,.08); }\n.dsc-sidebar .dsc-workspaces { flex: 1; min-height: 0; height: auto; }\n/* 同时限定两个自有类,避免后面的通用 .dsc-search 把它重新变成伸展项。 */\n.dsc-search.dsc-sidebar-search { flex: none; height: 32px; margin: 2px 0 8px; min-height: 32px; }\n.dsc-sidebar-search button { padding: 0 3px; }\n.dsc-sidebar .dsc-tree-tools { justify-content: space-between; padding: 12px 0 4px 8px; }\n.dsc-tree-tools-label { font-size: 12px; color: var(--cx-text-3); }\n.dsc-projects-toggle { display: flex; align-items: center; gap: 4px; height: 28px; padding: 0; }\n.dsc-projects-toggle svg { color: var(--cx-text-3); transform: rotate(-90deg); transition: transform 180ms ease; }\n.dsc-projects-toggle[aria-expanded=true] svg { transform: rotate(0); }\n.dsc-sidebar .dsc-tree-scroll { padding: 0; scrollbar-color: #ffffff26 transparent; scrollbar-width: thin; }\n.dsc-section-page-title { font-size: 17px; font-weight: 600; }\n.dsc-secondary-nav { display: flex; flex-direction: column; gap: 4px; min-height: 0; overflow: auto; padding-top: 6px; }\n.dsc-secondary-nav .dsc-secondary-item { display: flex; align-items: center; gap: 8px; min-height: 32px; width: 100%; padding: 0 8px; border-radius: var(--cx-radius-md); text-align: left; color: var(--cx-text-2); font-size: 13px; }\n.dsc-secondary-nav .dsc-secondary-item.is-active, .dsc-secondary-nav .dsc-secondary-item:hover { background: var(--cx-active); color: var(--cx-text); }\n.dsc-secondary-item .dsc-proto-icon svg { width: 16px; height: 16px; }\n.dsc-window-sidebar-toggle { visibility: visible; position: fixed; top: 6px; left: 12px; z-index: 35; -webkit-app-region: no-drag; }\n.dsc-collapsed-new-chat { display: none; align-items: center; margin-right: 8px; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] .dsc-collapsed-new-chat { display: flex; }\n.dsc-collapsed-new-chat .dsc-icon-button { width: 28px; height: 28px; }\n.dsc-gear-launcher { visibility: visible; position: fixed; left: 8px; bottom: 12px; z-index: 35; }\n.dsc-gear-launcher .dsc-gear-button { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border-radius: var(--cx-radius-md); color: var(--cx-text-3); }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: var(--cx-hover); color: var(--cx-text); }\n/* 自绘悬停提示,避免系统 title 突然出现;菜单打开时不叠加提示。 */\n.dsc-gear-launcher .dsc-gear-button { position: relative; isolation: isolate; transition: color 150ms ease; }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: none; }\n.dsc-gear-button::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--cx-hover); opacity: 0; transition: opacity 150ms ease; pointer-events: none; }\n.dsc-gear-button:hover::before, .dsc-gear-button[aria-expanded=true]::before, .dsc-gear-button:focus-visible::before { opacity: 1; }\n.dsc-gear-button::after { content: attr(aria-label); position: absolute; left: calc(100% + 8px); top: 50%; padding: 5px 9px; border: 1px solid var(--cx-border-l1); border-radius: 7px; background: var(--cx-popover-fill); color: var(--cx-text); font-size: 12px; line-height: 18px; white-space: nowrap; box-shadow: 0 4px 12px #0004; pointer-events: none; opacity: 0; transform: translate(-3px, -50%); transition: opacity 150ms ease, transform 150ms ease; }\n.dsc-gear-button[aria-expanded=false]:hover::after, .dsc-gear-button[aria-expanded=false]:focus-visible::after { opacity: 1; transform: translate(0, -50%); }\n.dsc-app-menu { padding: 5px; }\n.dsc-menu-separator { height: 1px; margin: 4px 6px; background: var(--cx-border-l1); }\n.dsc-app-menu .dsc-account-row { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px; text-align: left; border-radius: var(--cx-radius-md); }\n.dsc-account-row:hover { background: var(--cx-active); }\n.dsc-account-row > img, .dsc-account-avatar { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--cx-active); object-fit: cover; }\n.dsc-account-row > span:last-child { display: flex; flex-direction: column; min-width: 0; }\n.dsc-account-row strong { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-account-row small { font-size: 11px; color: var(--cx-text-3); }\n.dsc-empty-hero { visibility: visible; position: fixed; z-index: 2; display: flex; flex-direction: column; align-items: center; gap: 24px; transform: translate(-50%,-50%); pointer-events: none; }\n.dsc-empty-hero svg { color: #525252; }\n.dsc-empty-hero h1 { font-size: 26px; line-height: 34px; font-weight: 400; color: var(--cx-text-2); margin: 0; white-space: nowrap; }\n.dsc-hero-projectbar { visibility: visible; position: fixed; z-index: 6; height: calc(var(--cx-picker-h) + var(--cx-radius-composer)); padding: 4px 4px var(--cx-radius-composer); background: var(--cx-picker-fill); border-radius: 20px; }\n.dsc-hero-projectbar .dsc-project-picker { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; max-width: calc(100% - 180px); color: var(--cx-text-2); font-size: 13px; border-radius: var(--cx-radius-full); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-hero-projectbar .dsc-project-picker:hover, .dsc-hero-projectbar .dsc-project-picker[aria-expanded=true] { background: var(--cx-active); }\n.dsc-ui button:active:not(:disabled) { transform: translateY(1px); }\n.dsc-brand-mark { width: 25px; height: 20px; object-fit: contain; }\n.dsc-brand { display: inline-flex; align-items: center; gap: 7px; }\n.dsc-brand strong { font-size: 17px; letter-spacing: -.02em; }\n.dsc-brand small { font: 700 8px/16px var(--cx-font-sans); letter-spacing: .12em; padding: 0 4px; color: #131313; background: #eee; border-radius: 3px; }\n.dsc-settings-trigger { display: inline-flex; gap: 8px; align-items: center; }\n.dsc-workspaces { height: 100%; min-height: 0; width: 100%; display: flex; flex-direction: column; background: var(--cx-sidebar); }\n.dsc-tree-tools { display: flex; gap: 4px; align-items: center; padding: 6px 8px 12px; }\n.dsc-search { flex: 1; display: flex; align-items: center; gap: 6px; min-width: 0; border-radius: 8px; background: #ffffff08; padding: 3px 8px; color: var(--cx-text-3); }\n.dsc-search input { min-width: 0; width: 100%; border: 0; outline: none; background: none; font-size: 12px; }\n.dsc-search:focus-within { box-shadow: 0 0 0 1px var(--cx-border-l3); }\n.dsc-search input::placeholder { color: var(--cx-text-3); }\n.dsc-icon-button { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 7px; color: var(--cx-text-2); }\n.dsc-icon-button:hover { background: var(--cx-active); }\n.dsc-tree-scroll { flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; padding: 0 8px; }\n.dsc-section-title { padding: 12px 8px 6px; font-size: 12px; font-weight: 500; color: var(--cx-text-3); }\n.dsc-thread, .dsc-project { display: flex; align-items: center; height: 30px; border-radius: 7px; position: relative; }\n.dsc-thread:hover, .dsc-project:hover, .dsc-thread.is-active { background: #ffffff0d; }\n.dsc-ui .dsc-thread-main, .dsc-ui .dsc-project-main { display: flex; flex: 1; align-items: center; gap: 7px; min-width: 0; height: 100%; padding: 0 8px; text-align: left; color: #c2c2c2; }\n.dsc-thread-title, .dsc-project-main > span { min-width: 0; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-ui .dsc-thread-main { padding-right: 62px; }\n.dsc-thread-title-text { display: inline-block; width: max-content; max-width: none; vertical-align: top; }\n.dsc-thread-title[data-overflow=true] { text-overflow: clip; mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent); }\n.dsc-thread:hover .dsc-thread-title[data-overflow=true] .dsc-thread-title-text,\n.dsc-thread-main:focus-visible .dsc-thread-title[data-overflow=true] .dsc-thread-title-text { animation: dsc-title-scroll var(--dsc-title-duration) linear 250ms infinite alternate; }\n@keyframes dsc-title-scroll { from { transform: translateX(0); } to { transform: translateX(var(--dsc-title-shift)); } }\n.dsc-thread-time { position: absolute; right: 9px; font-size: 10px; color: var(--cx-text-4); transition: opacity 120ms ease; }\n.dsc-thread:hover .dsc-thread-time, .dsc-thread:has(:focus-visible) .dsc-thread-time { display: none; }\n.dsc-thread .dsc-state[data-state=idle] { display: none; }\n.dsc-thread-actions { position: absolute; top: 0; right: 4px; height: 100%; display: flex; align-items: center; gap: 1px; opacity: 0; pointer-events: none; transition: opacity 150ms ease; }\n.dsc-thread:hover .dsc-thread-actions, .dsc-thread:has(:focus-visible) .dsc-thread-actions { opacity: 1; pointer-events: auto; }\n.dsc-ui .dsc-thread-action { display: grid; place-items: center; flex: none; width: 25px; height: 26px; padding: 0; border-radius: 5px; color: #969696; transition: color 120ms ease; }\n.dsc-ui .dsc-thread-action:hover, .dsc-ui .dsc-thread-action:focus-visible { color: #d2d2d2; }\n.dsc-thread-action svg { width: 14px; height: 14px; }\n.dsc-row-tooltip { position: fixed; z-index: 10001; pointer-events: none; padding: 5px 11px; border: 1px solid #ffffff08; border-radius: 13px; color: #dfdfdf; background: #2d2d2d; font-size: 13px; line-height: 18px; white-space: nowrap; box-shadow: 0 2px 5px #0002; opacity: 0; visibility: hidden; transform: translateY(3px); transition: opacity 80ms ease, transform 80ms ease, visibility 0s 80ms; }\n.dsc-row-tooltip[data-open=true] { opacity: 1; visibility: visible; transform: translateY(0); transition: opacity 120ms ease 350ms, transform 120ms ease 350ms, visibility 0s 350ms; }\n.dsc-state { color: var(--cx-text-3); flex: none; display: flex; align-items: center; }\n.dsc-state[data-state=pending] { color: var(--cx-warning); }\n.dsc-state[data-state=running] { color: var(--cx-accent-soft); animation: dsc-status-pulse 1.2s ease-in-out infinite; }\n.dsc-state[data-state=done] { color: var(--cx-success); }\n.dsc-row-action { display: flex; opacity: 0; pointer-events: none; transition: opacity 150ms ease; width: 25px; height: 25px; align-items: center; justify-content: center; border-radius: 5px; padding: 0; margin-right: 3px; color: var(--cx-text-3); }\n.dsc-thread:hover .dsc-row-action, .dsc-project:hover .dsc-row-action, .dsc-thread:has(:focus-visible) .dsc-row-action, .dsc-project:has(:focus-visible) .dsc-row-action { opacity: 1; pointer-events: auto; }\n.dsc-row-action:hover { color: #fff; background: var(--cx-active); }\n/* 本轮按用户录像为项目内容局部开放布局动画;外壳与其他面板的尺寸规则保持原约定。 */\n.dsc-project-fold { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 180ms cubic-bezier(.2,.8,.2,1), opacity 150ms ease; }\n.dsc-project-fold[data-open=true] { grid-template-rows: 1fr; opacity: 1; }\n.dsc-project-clip { min-height: 0; overflow: hidden; }\n.dsc-project-children { padding-left: 14px; }\n.dsc-project-children, .dsc-project-list { transform: translateY(-4px); transition: transform 180ms cubic-bezier(.2,.8,.2,1); }\n.dsc-project-fold[data-open=true] > .dsc-project-clip > :is(.dsc-project-children, .dsc-project-list) { transform: translateY(0); }\n.dsc-project-children .dsc-state { display: none; }\n.dsc-empty { display: block; color: var(--cx-text-3); padding: 10px 12px; font-size: 12px; }\n.dsc-archive-toggle { padding: 8px 12px; display: flex; gap: 6px; align-items: center; font-size: 11px; color: var(--cx-text-3); }\n.dsc-archive-toggle input { accent-color: var(--cx-accent); }\n.dsc-thread.is-archived { opacity: .55; }\n.dsc-error { padding: 4px 8px; color: var(--cx-danger); font: 12px/17px var(--cx-font-sans); white-space: normal; max-width: 310px; }\n.dsc-menu { z-index: 10000; padding: 6px; background: var(--cx-popover-fill); border: 1px solid var(--cx-border-l1); border-radius: 14px; box-shadow: 0 12px 36px #0007; overflow: auto; animation: dsc-pop-in 130ms ease both; }\n.dsc-menu-heading { font-size: 12px; color: var(--cx-text-3); padding: 5px 8px 8px; }\n.dsc-option { width: 100%; display: flex; gap: 9px; align-items: center; text-align: left; padding: 7px 8px; border-radius: 7px; min-height: 32px; }\n.dsc-option:hover, .dsc-option:focus-visible { background: #ffffff12; }\n.dsc-option-copy { display: flex; flex: 1; min-width: 0; flex-direction: column; line-height: 20px; }\n.dsc-option-copy small { font-size: 12px; color: var(--cx-text-3); line-height: 17px; }\n/* 行悬停详情卡:几何按原型实测(项目卡 321×130、右侧 5px;会话卡 470 宽、右侧 12px、上移 6px)。 */\n.dsc-row-pop { position: fixed; z-index: 10000; max-width: calc(100vw - 32px); padding: 6px; border-radius: var(--cx-radius-xl); border: 1px solid var(--cx-border-l1); background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); animation: dsc-row-pop-in 120ms var(--cx-ease-out) both; }\n.dsc-row-pop--project { width: 321px; padding: 8px; }\n.dsc-row-pop--thread { width: 470px; }\n.dsc-row-pop__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 8px 8px 10px; border-bottom: 1px solid var(--cx-border-l2); }\n.dsc-row-pop--project .dsc-row-pop__head { align-items: center; height: 28px; padding: 0 8px; border-bottom: 0; }\n.dsc-row-pop__name { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 14px; font-weight: var(--cx-weight-regular); color: var(--cx-text); }\n.dsc-row-pop__name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop__pin { display: inline-flex; align-items: center; flex: none; color: var(--cx-text-3); }\n.dsc-row-pop__excerpt { margin: 0; font-size: 13px; line-height: 20px; color: var(--cx-text); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n.dsc-row-pop__meta { display: flex; align-items: center; gap: 6px; flex: none; font-size: 12px; color: var(--cx-text-3); white-space: nowrap; }\n.dsc-row-pop__line { display: flex; align-items: center; gap: 8px; width: 100%; height: 32px; padding: 0 8px; border-radius: var(--cx-radius-md); font-size: 13px; color: var(--cx-text-2); text-align: left; }\n.dsc-row-pop__line svg { flex: none; color: var(--cx-text-3); }\n.dsc-row-pop--project .dsc-row-pop__line { height: 28px; font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--project .dsc-row-pop__line svg { color: var(--cx-text-2); }\n.dsc-row-pop__line--path, .dsc-row-pop__line--action { cursor: pointer; }\n.dsc-row-pop__line--path:hover, .dsc-row-pop__line--action:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-row-pop__line--path:focus-visible, .dsc-row-pop__line--action:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\n.dsc-row-pop__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop--project .dsc-row-pop__path { font-family: var(--cx-font-sans); font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--thread .dsc-row-pop__path { font-family: var(--cx-font-mono); font-size: 12px; }\n.dsc-row-pop__arrow { margin-left: auto; opacity: 0; transition: opacity 120ms var(--cx-ease); }\n.dsc-row-pop__line--path:hover .dsc-row-pop__arrow, .dsc-row-pop__line--path:focus-visible .dsc-row-pop__arrow { opacity: 1; }\n.dsc-row-pop__sep { height: 1px; background: var(--cx-border-l1); }\n@keyframes dsc-row-pop-in { from { opacity: 0; } to { opacity: 1; } }\n/* 「编辑项目」对话框:按参考图(面板 #2d2d2d、16 圆角、保存白底、移除暗红底);源文件夹只读展示。 */\n.dsc-modal { position: fixed; inset: 0; z-index: 11000; display: flex; align-items: center; justify-content: center; padding: 24px; }\n.dsc-modal__mask { position: absolute; inset: 0; background: rgba(0,0,0,.12); }\n.dsc-modal__panel { position: relative; display: flex; flex-direction: column; width: min(520px, calc(100vw - 32px)); padding: 24px; border-radius: 16px; background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); color: var(--cx-text); }\n.dsc-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }\n.dsc-modal__head h2 { margin: 0; font-size: 20px; line-height: 28px; font-weight: 600; }\n.dsc-modal__close { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border-radius: var(--cx-radius-sm); color: var(--cx-text-2); }\n.dsc-modal__close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__field { display: flex; align-items: center; gap: 10px; height: 44px; margin-top: 20px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); }\n.dsc-modal__field:focus-within { border-color: var(--cx-focus); }\n.dsc-modal__input { flex: 1; min-width: 0; height: 100%; border: 0; outline: none; background: none; font: 15px/22px var(--cx-font-sans); color: var(--cx-text); }\n.dsc-modal__input::placeholder { color: var(--cx-text-3); }\n.dsc-modal__label { margin: 20px 0 8px; font-size: 13px; line-height: 18px; color: var(--cx-text-3); }\n.dsc-modal__source { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-modal .dsc-error { margin-top: 12px; }\n.dsc-modal__actions { display: flex; align-items: center; gap: 8px; margin-top: 24px; }\n.dsc-modal__spacer { flex: 1; }\n.dsc-modal__remove { height: 36px; padding: 0 16px; border-radius: 999px; background: rgba(250,102,101,.14); color: #fa6665; font-size: 14px; }\n.dsc-modal__remove:hover:not(:disabled) { background: rgba(250,102,101,.22); }\n.dsc-modal__cancel { height: 36px; padding: 0 14px; border-radius: 999px; color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__cancel:hover:not(:disabled) { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__save { height: 36px; padding: 0 20px; border-radius: 999px; background: #dfdfdf; color: #131313; font-size: 14px; font-weight: 500; }\n.dsc-modal__save:hover:not(:disabled) { background: #fff; }\n.dsc-modal__save:disabled, .dsc-modal__remove:disabled, .dsc-modal__cancel:disabled { opacity: .6; cursor: default; }\n.dsc-permission, .dsc-model { position: relative; display: inline-flex; align-items: center; min-width: 0; }\n.dsc-model { gap: 2px; }\n.dsc-control-status { display: inline-flex; align-items: center; gap: 5px; padding: 0 7px; color: var(--cx-text-3); font-size: 11px; }\n.dsc-control-status button { padding: 2px 4px; text-decoration: underline; }\n.dsc-control-status button:hover { color: var(--cx-text); }\n.dsc-pill { height: 28px; display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; border-radius: 999px; white-space: nowrap; color: var(--cx-text-2); font-size: 14px !important; }\n.dsc-pill:hover, .dsc-pill[aria-expanded=true] { background: #ffffff12; color: white; }\n.dsc-pill[data-warning=true] { color: var(--cx-warning); }\n.dsc-model-trigger { max-width: 260px; gap: 5px; }\n/* 右下角触发器:模型名偏白、思维强度灰色(参考图 4)。 */\n.dsc-model-trigger .dsc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: #dfdfdf; }\n.dsc-model-trigger .dsc-effort-name { flex: none; color: #989898; }\n.dsc-model-trigger svg { flex: none; color: #989898; }\n/* 「修改模型」面板:标题与列表样式明确区别于宿主原生菜单。 */\n.dsc-menu.dsc-model-panel { padding: 8px; }\n.dsc-menu.dsc-model-panel .dsc-menu-heading { font-size: 14px; line-height: 20px; color: var(--cx-text); font-weight: 500; padding: 6px 8px 10px; }\n.dsc-model-group { color: var(--cx-text-3); font-size: 11px; padding: 7px 8px 2px; }\n.dsc-model-search { border: 0; border-bottom: 1px solid var(--cx-border-l1); background: none; outline: none; padding: 6px 8px; width: 100%; margin-bottom: 3px; }\n.dsc-back { display: flex; align-items: center; gap: 4px; color: var(--cx-text-3); font-size: 12px !important; border-radius: 5px; }\n.dsc-back:hover { color: #fff; }\n/* 滑条弹层裁掉横向溢出:Max/Low/High 档的撒点动画会向右超出边界,触发多余的水平滚动条。 */\n.dsc-reasoning-pop { padding: 12px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); overflow: hidden; animation: dsc-reasoning-in 130ms var(--cx-ease) both; }\n.dsc-reasoning { position: relative; }\n.dsc-reasoning-heading { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0 20px 10px; text-align: center; }\n.dsc-reasoning-heading strong { font-size: 14px; font-weight: var(--cx-weight-semibold); line-height: 18px; color: var(--cx-reasoning-title); transition: color 150ms ease; }\n.dsc-reasoning-model { display: inline-flex; align-items: center; justify-content: center; gap: 2px; max-width: 100%; padding: 4px 10px; border-radius: var(--cx-radius-md); color: #ffffff9e !important; font-size: 12px !important; line-height: 16px !important; white-space: nowrap; transition: background-color 120ms var(--cx-ease), color 120ms var(--cx-ease); }\n.dsc-reasoning-model:hover { background: #3c3b3b; color: var(--cx-text) !important; }\n.dsc-reasoning-reset { position: absolute; top: -4px; right: -4px; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: var(--cx-radius-sm); color: var(--cx-text-3) !important; }\n.dsc-reasoning-reset:hover { background: var(--cx-hover); color: var(--cx-text) !important; }\n.dsc-reasoning[data-level=max] .dsc-reasoning-heading strong { color: var(--cx-reasoning-max-text); }\n.dsc-slider { height: 32px; position: relative; margin: 0 2px; border-radius: 999px; cursor: pointer; touch-action: none; outline: none; }\n/* 提交期间保持原有视觉:只禁用拖拽,不切换光标/透明度,避免切换档位时闪变。 */\n.dsc-slider[aria-disabled=true] { cursor: default; opacity: 1; }\n.dsc-slider-track { position: absolute; inset: 4px 0; height: 24px; border-radius: 999px; background: var(--cx-reasoning-track); overflow: hidden; }\n.dsc-slider-fill { position: absolute; inset: 0; border-radius: inherit; background: var(--cx-reasoning-blue); transform-origin: left center; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-slider-max { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg,var(--cx-reasoning-max-blue),var(--cx-reasoning-purple) 52%,var(--cx-reasoning-violet)); opacity: 0; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-slider-max { opacity: 1; }\n.dsc-slider-max::before, .dsc-slider-max::after { content: ''; position: absolute; animation-play-state: paused; }\n.dsc-slider-max::before { inset: -50% -25%; background: radial-gradient(ellipse,var(--cx-reasoning-max-ice),#a095ffd9 22%,transparent 58%); animation: dsc-max-flow 2.8s cubic-bezier(.4,0,.6,1) -.45s infinite alternate; animation-play-state: paused; }\n.dsc-slider-max::after { inset: -40% -30%; background: radial-gradient(ellipse,#d6a2ff,var(--cx-reasoning-max-glow) 20%,transparent 55%); animation: dsc-max-bloom 4.2s cubic-bezier(.4,0,.6,1) -1.65s infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-slider-max::before, .dsc-slider[data-level=max] .dsc-slider-max::after { animation-play-state: running; }\n.dsc-stars { position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-stars { opacity: 1; }\n.dsc-stars i { position: absolute; border-radius: 50%; background: #fff; box-shadow: 0 0 3px #e0d6ff8c; animation: dsc-star-drift var(--pdur) cubic-bezier(.4,0,.6,1) var(--pdelay) infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-stars i { animation-play-state: running; }\n.dsc-tick { position: absolute; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: #ffffff38; transform: translate(-50%,-50%); transition: opacity 150ms ease,transform 120ms ease; }\n.dsc-tick.is-on { background: #ffffff4d; }\n.dsc-slider[data-level=max] .dsc-tick { opacity: 0; }\n.dsc-thumb-position { position: absolute; inset: 0; pointer-events: none; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-thumb { position: absolute; top: 50%; left: 14px; width: 28px; height: 28px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px #0007,0 0 0 1px #0000000f; transform: translate(-50%,-50%); pointer-events: auto; transition: transform 140ms var(--cx-ease-out),box-shadow 140ms var(--cx-ease); }\n/* 固定白色滑块为正圆,避免宿主全局圆角规则覆盖插件外形。 */\n.dsc-ui .dsc-slider .dsc-thumb { border-radius: 50% !important; }\n.dsc-thumb:hover { transform: translate(-50%,-50%) scale(1.1); box-shadow: 0 1px 6px #0008,0 0 0 1px #0000000f; }\n.dsc-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.dsc-permission-menu { padding: 4px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); }\n.dsc-permission-heading { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px 8px; color: var(--cx-text-3); font-size: 13px; line-height: 18px; }\n.dsc-permission-heading button { padding: 0; white-space: nowrap; text-decoration: underline; text-underline-offset: 2px; }\n.dsc-permission-help { margin: 4px 8px 8px; color: var(--cx-text-2); font-size: 12px; line-height: 18px; }\n.dsc-permission-menu .dsc-option { align-items: flex-start; gap: 8px; padding: 6px 8px; border-radius: var(--cx-radius-xl); color: var(--cx-text-2); font-size: 14px; line-height: 19px; }\n.dsc-permission-menu .dsc-option > svg { margin-top: 3px; }\n.dsc-permission-menu .dsc-option-copy { line-height: 19px; }\n.dsc-permission-menu .dsc-option[data-warning=true], .dsc-permission-menu .dsc-option[data-warning=true] small { color: var(--cx-warning); }\n.dsc-burst { position: absolute; left: 14px; top: 50%; }\n.dsc-burst i { position: absolute; width: 3px; height: 3px; border-radius: 50%; background: #b98cff; animation: dsc-burst 280ms cubic-bezier(.22,1,.36,1) both; }\n.dsc-slider-labels { display: flex; justify-content: space-between; padding-top: 3px; }\n.dsc-slider-labels button { padding: 0 3px; font-size: 10px; color: var(--cx-text-3); }\n.dsc-slider-labels button:hover { color: white; }\n.dsc-reasoning-description { display: block; text-align: center; color: var(--cx-text-3); font-size: 11px; line-height: 16px; margin-top: 7px; }\n@keyframes dsc-pop-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }\n/* 退场淡出:关闭弹层时先播放 160ms,再卸载 DOM。 */\n@keyframes dsc-pop-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(3px); } }\n.dsc-menu.dsc-menu-closing { animation: dsc-pop-out 160ms ease both; pointer-events: none; }\n@keyframes dsc-reasoning-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dsc-status-pulse { 50% { opacity: .4; } }\n@keyframes dsc-max-flow { from { transform: translateX(-32%) scale(.82,1); opacity: .95; } to { transform: translateX(32%) scale(1.02,1); opacity: .8; } }\n@keyframes dsc-max-bloom { from { transform: translateX(28%) scale(.7,1); opacity: .9; } to { transform: translateX(-28%) scale(1,1); opacity: 1; } }\n@keyframes dsc-star-drift { from { transform: translate(-50%,-50%) translate(calc(var(--dx)*-.5),calc(var(--dy)*-.5)); opacity: .45; } to { transform: translate(-50%,-50%) translate(calc(var(--dx)*.5),calc(var(--dy)*.5)); opacity: .8; } }\n@keyframes dsc-burst { 0% { transform: translate(-50%,-50%) scale(.45); opacity: 0; } 22% { opacity: .65; } 100% { transform: translate(-50%,-50%) translate(var(--bx),var(--by)) scale(.45); opacity: 0; } }\n@media (prefers-reduced-motion: reduce) {\n .dsc-sidebar-shell, .dsc-row-tooltip, .dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { animation: none !important; transition: none !important; }\n .dsc-burst { display: none; }\n}\n\n/* Codex 几何(第 48 轮):\n 1) 侧边栏列左上角圆角、对话列左上角直角(与 Codex 蓝框/红框一致);\n 2) 对话正文列与输入框同宽:由插件测量输入框卡片的实际视觉宽度写入 --dsc-composer-visual-width,\n 正文列优先取该值,回退到宿主 --dsh-composer-card-max-width(正文 + 32px)。 */\nbody[data-dsh-codex-style] [data-conversation-scroll] [class*=\"_column\"] { max-width: var(--dsc-composer-visual-width, var(--dsh-composer-card-max-width)); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-sidebar-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-center-column] { border-radius: 0; }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] [data-dsc-center-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\n";
5
+ const CSS_TEXT = "/* 自研界面令牌;尺寸与色值参考界面测量。来源说明见 THIRD_PARTY_NOTICES.md。 */\n\nbody[data-dsh-codex-style] {\n /* ---- 间距 ---- */\n --cx-spacing: 4px;\n --cx-space-1: 4px;\n --cx-space-2: 8px;\n --cx-space-3: 12px;\n --cx-space-4: 16px;\n --cx-space-5: 20px;\n --cx-space-6: 24px;\n --cx-space-8: 32px;\n\n /* ---- 圆角阶梯 ---- */\n --cx-radius-2xs: 2px;\n --cx-radius-xs: 4px;\n --cx-radius-sm: 6px;\n --cx-radius-md: 8px;\n --cx-radius-lg: 10px;\n --cx-radius-xl: 12px;\n --cx-radius-2xl: 16px;\n --cx-radius-3xl: 20px;\n --cx-radius-4xl: 24px;\n --cx-radius-composer: 25px;\n --cx-radius-full: 9999px;\n\n /* ---- 暗色面(深色界面:导航/顶栏 #202020、侧栏 #1b1b1b、对话区 #181818) ---- */\n --cx-window: #202020;\n --cx-sidebar: #1b1b1b;\n --cx-surface: #181818;\n --cx-thread: #171717;\n --cx-nested: #212121;\n --cx-elevated: #282828;\n --cx-summary: #2d2d2d;\n --cx-hover: rgba(255, 255, 255, 0.045);\n --cx-active: rgba(255, 255, 255, 0.08);\n\n /* ---- 边框 ---- */\n --cx-border-l1: rgba(255, 255, 255, 0.08);\n --cx-border-l2: rgba(255, 255, 255, 0.04);\n --cx-border-l3: rgba(255, 255, 255, 0.16);\n --cx-border-strong: rgba(255, 255, 255, 0.16);\n\n /* ---- 文本 ---- */\n --cx-text: #ffffff;\n --cx-text-2: rgba(255, 255, 255, 0.7);\n --cx-text-3: rgba(255, 255, 255, 0.5);\n --cx-text-4: rgba(255, 255, 255, 0.32);\n --cx-text-inverse: #0d0d0d;\n\n /* ---- 强调 / 状态 ---- */\n --cx-accent: #0285ff;\n --cx-accent-soft: #339cff;\n --cx-focus: #339cff;\n --cx-success: #40c977;\n --cx-danger: #ff6764;\n --cx-warning: #ff8549;\n --cx-attention: #3a83f7;\n\n /* ---- diff ---- */\n --cx-diff-add-bg: rgba(64, 201, 119, 0.23);\n --cx-diff-del-bg: rgba(255, 103, 100, 0.23);\n --cx-diff-add-mark: #40c977;\n --cx-diff-del-mark: #ff6764;\n\n /* ---- 字体 ---- */\n --cx-font-sans: -apple-system, system-ui, \"Segoe UI\", sans-serif;\n --cx-font-mono: ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n\n /* ---- 字号 / 行高 ---- */\n --cx-text-xs: 12px;\n --cx-text-sm: 14px;\n --cx-text-md: 16px;\n --cx-text-lg: 18px;\n --cx-leading-xs: 16px;\n --cx-leading-sm: 22px;\n --cx-leading-md: 24px;\n --cx-leading-lg: 28px;\n\n /* ---- 字重 ---- */\n --cx-weight-regular: 400;\n --cx-weight-medium: 500;\n --cx-weight-semibold: 600;\n --cx-weight-bold: 700;\n --cx-weight-shell: 445; /* 外壳专用非标准字重 */\n\n /* ---- 动效 ---- */\n --cx-dur-fast: 120ms;\n --cx-dur: 150ms;\n --cx-dur-relaxed: 300ms;\n --cx-ease: ease;\n --cx-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);\n --cx-ease-scroll: cubic-bezier(0.22, 1, 0.36, 1);\n\n /* ---- 布局几何 ----\n 以真实 Codex 窗口截图(1988×1292 @1.5x)实测回填:\n 侧栏 288、标题栏 44、输入栈 710,详见 02-开发日志 第 10 轮。 */\n --cx-titlebar-h: 44px;\n --cx-rail-w: 52px;\n --cx-sidebar-w: 288px;\n --cx-thread-max: 768px;\n --cx-right-w: 667px;\n --cx-header-h: 48px;\n --cx-row-h: 30px;\n --cx-control-h: 28px;\n --cx-send-h: 32px;\n --cx-composer-stack-w: 742px; /* 含左右 16px 内边距 → 内容 710 */\n --cx-composer-h: 97px;\n --cx-picker-h: 37px;\n\n /* ---- 输入区实测色(真实截图取色) ---- */\n --cx-picker-fill: #282828;\n --cx-composer-fill: rgba(55, 55, 55, 0.94);\n\n /* ---- 应用视口 ----\n 真实 DSH 窗口截图 1988×1292(Windows 150% 缩放)换算为 1325×861 CSS。\n 原型按此尺寸渲染,各栏占比才与真实界面一致(旧 1600 视口下主区偏宽)。 */\n --cx-app-w: 1325px;\n --cx-app-h: 861px;\n\n /* ---- 推理强度选择器 ----\n 尺寸/颜色按用户真实 Codex 录屏帧实测(1992×1296 @1.503x)。 */\n --cx-reasoning-w: 255px;\n --cx-reasoning-snap-dur: 140ms; /* 第 20 轮节点切换过渡,待用户验收 */\n --cx-reasoning-fx-dur: 180ms;\n --cx-reasoning-text-dur: 220ms;\n /* 第 23 轮按用户新设计口径增强流速,两个亮带错速运行。 */\n --cx-reasoning-flow-dur: 2.8s;\n --cx-reasoning-bloom-dur: 4.2s;\n --cx-reasoning-blue: #4c9bfe;\n --cx-reasoning-track: #3e3e3e;\n /* 第 22 轮:新录屏 100/660/900/1120 帧取色,深底与两种柔亮色分层。 */\n --cx-reasoning-violet: #5134ad;\n --cx-reasoning-purple: #844fcc;\n --cx-reasoning-max-blue: #3a4ebd;\n --cx-reasoning-max-glow: #bd8dff;\n --cx-reasoning-max-ice: #adaaff;\n --cx-reasoning-max-text: #b98cff;\n --cx-reasoning-title: #4c9bfe;\n /* 浮层/菜单底色:真实录屏帧取色 rgb(45,45,45) */\n --cx-popover-fill: #2d2d2d;\n\n /* ---- 阴影 ---- */\n --cx-shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 0.08);\n --cx-shadow-md: 0 2px 4px -1px rgb(0 0 0 / 0.08);\n --cx-shadow-lg: 0 4px 8px -2px rgb(0 0 0 / 0.1);\n --cx-shadow-xl: 0 8px 16px -4px rgb(0 0 0 / 0.12);\n --cx-shadow-2xl: 0 16px 32px -8px rgb(0 0 0 / 0.19);\n --cx-composer-shadow:\n 0 0 0 0.5px var(--cx-border-strong),\n 0 3px 7.5px rgb(0 0 0 / 0.04),\n 0 0 20px rgb(0 0 0 / 0.05);\n\n /* ---- z 轴 ---- */\n --cx-z-ground: 0;\n --cx-z-panel: 10;\n --cx-z-floating: 20;\n --cx-z-drawer: 40;\n --cx-z-popover: 50;\n --cx-z-overlay: 70;\n}\n\n/* ============ 导航栏 ============ */\n.dsc-rail {\n grid-area: rail;\n position: relative;\n z-index: 30;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: 8px 0;\n background: var(--cx-window);\n border-right: 1px solid var(--cx-border-l2);\n}\n.dsc-rail__spacer { flex: 1; }\n.dsc-rail__item {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: var(--cx-radius-md);\n color: var(--cx-text-3);\n transition: background-color var(--cx-dur-fast) var(--cx-ease), color var(--cx-dur-fast) var(--cx-ease);\n}\n.dsc-rail__item:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-rail__item--active { background: var(--cx-active); color: var(--cx-text); }\n/* 插头先以不透明白色统一描边,再整体降低透明度,避免相交线段叠色发亮。 */\n.dsc-rail__plugin-icon { color: #fff; opacity: .5; stroke-width: 1.5; transition: opacity var(--cx-dur-fast) var(--cx-ease); }\n.dsc-rail__item:hover .dsc-rail__plugin-icon,\n.dsc-rail__item--active .dsc-rail__plugin-icon { opacity: 1; }\n.dsc-rail__item::after {\n content: attr(data-tip);\n position: absolute;\n left: 44px;\n top: 50%;\n z-index: var(--cx-z-popover);\n padding: 4px 8px;\n border-radius: var(--cx-radius-sm);\n border: 1px solid var(--cx-border-l1);\n background: #2b2b2b;\n color: var(--cx-text);\n font-size: 12px;\n line-height: 16px;\n white-space: nowrap;\n box-shadow: var(--cx-shadow-lg);\n opacity: 0;\n transform: translateY(-50%) translateX(-2px);\n pointer-events: none;\n transition: opacity var(--cx-dur-fast) var(--cx-ease), transform var(--cx-dur-fast) var(--cx-ease);\n}\n.dsc-rail__item:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }\n\n/* ============ 侧栏 ============ */\n.dsc-sidebar {\n grid-area: sidebar;\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px;\n background: var(--cx-sidebar);\n border-right: 1px solid var(--cx-border-l2);\n overflow: hidden;\n}\n.dsc-sidebar__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 44px;\n margin: -8px -8px 6px;\n padding: 0 6px 0 10px;\n}\n.dsc-brand { display: flex; align-items: center; gap: 7px; }\n.dsc-brand__mark { display: block; height: 19px; width: auto; }\n.dsc-brand__wordmark {\n font-size: 17px;\n font-weight: 700;\n letter-spacing: -0.02em;\n color: var(--cx-text);\n line-height: 1;\n}\n.dsc-brand__badge {\n font-size: 9px;\n font-weight: 700;\n letter-spacing: 0.14em;\n line-height: 1;\n padding: 3px 5px 2px;\n border-radius: 3px;\n background: var(--cx-text);\n color: var(--cx-window);\n}\n.dsc-sidebar__header-actions { display: flex; align-items: center; gap: 2px; }\n.dsc-new-chat {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 32px;\n padding: 0 8px;\n border-radius: var(--cx-radius-md);\n font-size: 13px;\n font-weight: var(--cx-weight-medium);\n color: var(--cx-text);\n background: var(--cx-active);\n transition: background-color var(--cx-dur-fast) var(--cx-ease);\n}\n.dsc-new-chat:hover { background: rgba(255, 255, 255, 0.12); }\n.dsc-new-chat kbd { margin-left: auto; font-size: 11px; color: var(--cx-text-4); }\n\n/* 自有组件与公开 slot/region 锚点;外壳几何适配通过自有 data-dsc-* 标记集中处理。 */\nbody[data-dsh-codex-style] { background: var(--cx-window); color-scheme: dark; --shiki-token-constant: #4dabf7; --shiki-token-string: #69db7c; --shiki-token-comment: #adb5bd; --shiki-token-keyword: #faa2c1; --shiki-token-function: #b197fc; }\n/* 整个三列外壳实际缩窄 52px,让宿主 ResizeObserver、右栏与拖拽使用同一可用宽度。\n 不能用 padding-left 代替:宿主测量的是 getBoundingClientRect().width。 */\nbody[data-dsh-codex-chrome] [data-dsc-frame] { margin-left: var(--cx-rail-w); width: calc(100% - var(--cx-rail-w)); }\n/* 全屏右栏原本按 100vw 展开;扣掉独立导航后,继续尊重宿主二级侧栏的真实宽度。 */\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame] [data-sidebar-right-panel=\"fullscreen\"] { max-width: max(0px, calc(100vw - var(--cx-rail-w) - var(--dsh-windows-sidebar-width, 0px))); }\n/* data-slot 是 renderer 声明的可寻址面;这里只传递变量,不选择/搬移宿主内部节点。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] { --dsw-specific-input-major: var(--cx-composer-fill); --dsw-radius-panel: var(--cx-radius-composer); --dsw-alias-border-l2: var(--cx-border-strong); --dsw-elevation-soft: var(--cx-composer-shadow); --dsh-composer-card-max-width: 710px; }\n/* DSH 0.2.1-rc.2 中英文发送标签;只覆盖发送色,保留停止按钮与宿主提交状态。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]) { --dsw-alias-button-info-fill: #dfdfdf; --dsw-alias-button-info-hover: #dfdfdf; color: #363636; opacity: 1; }\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]):disabled { --dsw-alias-button-info-fill: #8b8b8b; }\n/* 正文字体颜色:按 Codex 截图采样 #dfdfdf(原 #ffffff 偏亮)。仅覆盖对话内容区,不影响按钮、菜单等其它主文本。 */\nbody[data-dsh-codex-style] [data-conversation-content] { --dsw-alias-label-primary: #dfdfdf; }\n/* 原 sidebar/settings 父级继续挂载以保留原生页面与子槽位所有权;只隐藏其旧外观。 */\nbody[data-dsh-codex-chrome] [data-slot=\"sidebar\"] { display: block !important; position: relative; width: 100%; height: 100%; visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-slot=\"conversation.composer.bar\"] { --dsh-composer-side-clearance: 0px; display: flex !important; flex-direction: column; align-items: stretch; align-self: center; width: min(calc(100% - 32px),710px); max-width: 710px; position: relative; }\n/* 空态保留官方编辑器、草稿、附件、“+”与 chain;没有重建输入状态或移动节点。 */\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] { visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-region=\"composer\"] { position: absolute; inset: auto 0 0; z-index: 7; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.composer.bar\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.input.dock\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-composer-overlay] { visibility: visible; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"][data-dsc-hero-layout] [data-slot=\"conversation.hero.agentPreset\"] { visibility: visible; display: inline-flex !important; position: fixed; top: calc(var(--dsc-hero-card-top) - 33px); right: calc(100vw - var(--dsc-hero-card-right) + 18px); z-index: 8; height: 28px; max-width: 180px; }\n.dsc-ui { font: 14px/22px var(--cx-font-sans); color: var(--cx-text); box-sizing: border-box; }\n.dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { box-sizing: border-box; }\n.dsc-ui button, .dsc-ui input { font: inherit; color: inherit; }\n.dsc-ui button { border: 0; cursor: pointer; background: none; }\n.dsc-ui button:disabled { cursor: default; opacity: .45; }\n.dsc-ui button:focus-visible, .dsc-ui [role=slider]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: 2px; }\n.dsc-ui svg { flex: none; }\n.dsc-update-control { display: inline-flex; align-items: center; -webkit-app-region: no-drag; }\n.dsc-update-notice { position: fixed; right: 24px; bottom: 24px; width: 320px; max-width: calc(100vw - 32px); padding: 14px; z-index: 10000; background: var(--cx-popover-fill); border: 1px solid var(--cx-border-l1); border-radius: 14px; box-shadow: 0 12px 36px #0007; animation: dsc-pop-in 130ms ease both; -webkit-app-region: no-drag; }\n.dsc-ui.dsc-update-panel { padding: 14px; }\n.dsc-update-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.dsc-ui .dsc-update-close { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border-radius: 6px; color: var(--cx-text-2); }\n.dsc-update-close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-update-message { margin: 14px 0 10px; font-weight: 500; }\n.dsc-update-message[data-state=update] { color: #72b4ff; }\n.dsc-update-message[data-state=error] { color: #ff8585; }\n.dsc-update-versions { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 0; font-size: 13px; }\n.dsc-update-versions dt { color: var(--cx-text-2); }\n.dsc-update-versions dd { margin: 0; text-align: right; overflow-wrap: anywhere; }\n.dsc-update-note { margin: 10px 0 0; color: var(--cx-text-2); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }\n.dsc-update-note > span { display: block; color: var(--cx-text-3); }\n.dsc-ui .dsc-update-retry { margin-top: 14px; padding: 5px 10px; border: 1px solid var(--cx-border-l3); border-radius: 7px; }\n.dsc-update-retry:hover { background: var(--cx-hover); }\n.dsc-update-spin { animation: dsc-update-spin .9s linear infinite; }\n@keyframes dsc-update-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) { .dsc-update-spin { animation: none; } }\n.dsc-proto-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; line-height: 1; }\n.dsc-proto-icon svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }\n/* 同一内容 portal 到 body:常驻时跟随宿主列,退场与浮卡不受 0px 列裁剪。只动画透明度和位移。 */\n.dsc-sidebar-shell { visibility: hidden; position: fixed; z-index: 20; min-width: 0; min-height: 0; display: flex; opacity: 0; transform: translateX(-24px); pointer-events: none; transition: opacity 180ms ease, transform 240ms var(--cx-ease-out), visibility 0s 240ms; -webkit-app-region: no-drag; }\n.dsc-sidebar-shell[data-open=true] { visibility: visible; opacity: 1; transform: translateX(0); pointer-events: auto; transition: opacity 180ms ease, transform 260ms var(--cx-ease-out), visibility 0s; }\n.dsc-sidebar-shell[data-mode=docked] > .dsc-sidebar { border-top-left-radius: 16px; }\n.dsc-sidebar-shell[data-mode=floating] { z-index: 40; transform: translateX(-12px); transition: opacity 150ms ease, transform 180ms var(--cx-ease-out), visibility 0s 180ms; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] { transform: translateX(0); transition-delay: 0s; }\n.dsc-sidebar-shell[data-mode=floating]::before { content: ''; position: absolute; top: 0; bottom: 0; left: -12px; width: 12px; }\n.dsc-sidebar-shell[data-mode=floating] > .dsc-sidebar { --cx-sidebar: #2c2c2c; border: 1px solid var(--cx-border-l1); border-radius: 12px; box-shadow: 0 0 0 3px #18181880, 0 8px 28px #0005; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] > .dsc-sidebar { animation: dsc-sidebar-content-in 150ms var(--cx-ease-out) both; }\n@keyframes dsc-sidebar-content-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: translateX(0); } }\n.dsc-independent-rail { visibility: visible; position: fixed; top: var(--dsh-frame-chrome-top, 0px); bottom: 0; left: 0; width: var(--cx-rail-w); z-index: 25; min-height: 0; padding-bottom: 56px; -webkit-app-region: no-drag; }\n.dsc-extension-navigation-anchor { flex: 1; align-self: stretch; min-height: 0; }\n/* 原生 panellist 继续负责图标注入、选中态、点击和卸载;只重排其可见区域。\n 位置来自自有轨道的剩余空间,超高时独立滚动,不覆盖底部齿轮。\n Windows 原样式在 collapsed 时 display:none,并在开合时淡出,故明确覆盖这两项。 */\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] { visibility: visible; display: flex !important; position: fixed; left: var(--dsc-extension-left); top: var(--dsc-extension-top); width: var(--dsc-extension-width); max-height: var(--dsc-extension-height); min-height: 0; z-index: 26; flex-direction: column; align-items: center; gap: 4px !important; padding: 0; margin: 0 !important; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ffffff26 transparent; opacity: 1 !important; transform: none !important; animation: none !important; transition: none !important; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-hidden] { display: none !important; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row] { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; min-height: 36px; padding: 0; margin: 0; border: 0; border-radius: var(--cx-radius-md); background: transparent; color: var(--cx-text-3); cursor: pointer; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:hover { color: var(--cx-text); background: var(--cx-hover); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row][aria-current=page] { color: var(--cx-text); background: var(--cx-active); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-label] { display: none; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] svg { display: block; max-width: 20px; max-height: 20px; }\n.dsc-navigation-error { position: fixed; left: calc(var(--cx-rail-w) + 8px); top: var(--dsh-frame-overlay-top, 20px); z-index: 50; max-width: 320px; }\n.dsc-rail .dsc-rail__item { color: var(--cx-text-3); background: transparent; flex: none; padding: 0; }\n.dsc-rail .dsc-rail__item:hover { color: var(--cx-text); background: var(--cx-hover); }\n.dsc-rail .dsc-rail__item--active { color: var(--cx-text); background: var(--cx-active); }\n.dsc-rail__item[data-preview-enabled=true]::after { display: none; }\n.dsc-sidebar { flex: 1; min-width: 0; min-height: 0; overflow: hidden; }\n.dsc-sidebar .dsc-sidebar__header { flex: none; }\n.dsc-sidebar .dsc-brand { min-width: 0; gap: 6px; }\n.dsc-sidebar .dsc-brand__mark { height: 19px; max-width: 25px; }\n.dsc-sidebar .dsc-brand__badge { font-size: 8px; padding: 3px 4px 2px; }\n.dsc-sidebar .dsc-sidebar__header-actions { margin-left: auto; }\n/* Codex:新聊天默认无底色、不显示快捷键,仅悬停时出现灰色背景。 */\n.dsc-sidebar .dsc-new-chat { flex: none; justify-content: flex-start; width: 100%; font-size: 13px; font-weight: var(--cx-weight-medium); background: transparent; color: #c2c2c2; margin-bottom: 8px; }\n.dsc-sidebar .dsc-new-chat:hover { background: rgba(255,255,255,.08); }\n.dsc-sidebar .dsc-workspaces { flex: 1; min-height: 0; height: auto; }\n/* 同时限定两个自有类,避免后面的通用 .dsc-search 把它重新变成伸展项。 */\n.dsc-search.dsc-sidebar-search { flex: none; height: 32px; margin: 2px 0 8px; min-height: 32px; }\n.dsc-sidebar-search button { padding: 0 3px; }\n.dsc-sidebar .dsc-tree-tools { justify-content: space-between; padding: 12px 0 4px 8px; }\n.dsc-tree-tools-label { font-size: 12px; color: var(--cx-text-3); }\n.dsc-projects-toggle { display: flex; align-items: center; gap: 4px; height: 28px; padding: 0; }\n.dsc-projects-toggle svg { color: var(--cx-text-3); transform: rotate(-90deg); transition: transform 180ms ease; }\n.dsc-projects-toggle[aria-expanded=true] svg { transform: rotate(0); }\n.dsc-sidebar .dsc-tree-scroll { padding: 0; scrollbar-color: #ffffff26 transparent; scrollbar-width: thin; }\n.dsc-section-page-title { font-size: 17px; font-weight: 600; }\n.dsc-secondary-nav { display: flex; flex-direction: column; gap: 4px; min-height: 0; overflow: auto; padding-top: 6px; }\n.dsc-secondary-nav .dsc-secondary-item { display: flex; align-items: center; gap: 8px; min-height: 32px; width: 100%; padding: 0 8px; border-radius: var(--cx-radius-md); text-align: left; color: var(--cx-text-2); font-size: 13px; }\n.dsc-secondary-nav .dsc-secondary-item.is-active, .dsc-secondary-nav .dsc-secondary-item:hover { background: var(--cx-active); color: var(--cx-text); }\n.dsc-secondary-item .dsc-proto-icon svg { width: 16px; height: 16px; }\n.dsc-window-sidebar-toggle { visibility: visible; position: fixed; top: 6px; left: 12px; z-index: 35; -webkit-app-region: no-drag; }\n.dsc-collapsed-new-chat { display: none; align-items: center; margin-right: 8px; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] .dsc-collapsed-new-chat { display: flex; }\n.dsc-collapsed-new-chat .dsc-icon-button { width: 28px; height: 28px; }\n.dsc-gear-launcher { visibility: visible; position: fixed; left: 8px; bottom: 12px; z-index: 35; }\n.dsc-gear-launcher .dsc-gear-button { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border-radius: var(--cx-radius-md); color: var(--cx-text-3); }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: var(--cx-hover); color: var(--cx-text); }\n/* 自绘悬停提示,避免系统 title 突然出现;菜单打开时不叠加提示。 */\n.dsc-gear-launcher .dsc-gear-button { position: relative; isolation: isolate; transition: color 150ms ease; }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: none; }\n.dsc-gear-button::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--cx-hover); opacity: 0; transition: opacity 150ms ease; pointer-events: none; }\n.dsc-gear-button:hover::before, .dsc-gear-button[aria-expanded=true]::before, .dsc-gear-button:focus-visible::before { opacity: 1; }\n.dsc-gear-button::after { content: attr(aria-label); position: absolute; left: calc(100% + 8px); top: 50%; padding: 5px 9px; border: 1px solid var(--cx-border-l1); border-radius: 7px; background: var(--cx-popover-fill); color: var(--cx-text); font-size: 12px; line-height: 18px; white-space: nowrap; box-shadow: 0 4px 12px #0004; pointer-events: none; opacity: 0; transform: translate(-3px, -50%); transition: opacity 150ms ease, transform 150ms ease; }\n.dsc-gear-button[aria-expanded=false]:hover::after, .dsc-gear-button[aria-expanded=false]:focus-visible::after { opacity: 1; transform: translate(0, -50%); }\n.dsc-app-menu { padding: 5px; }\n.dsc-menu-separator { height: 1px; margin: 4px 6px; background: var(--cx-border-l1); }\n.dsc-app-menu .dsc-account-row { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px; text-align: left; border-radius: var(--cx-radius-md); }\n.dsc-account-row:hover { background: var(--cx-active); }\n.dsc-account-row > img, .dsc-account-avatar { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--cx-active); object-fit: cover; }\n.dsc-account-row > span:last-child { display: flex; flex-direction: column; min-width: 0; }\n.dsc-account-row strong { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-account-row small { font-size: 11px; color: var(--cx-text-3); }\n.dsc-empty-hero { visibility: visible; position: fixed; z-index: 2; display: flex; flex-direction: column; align-items: center; gap: 24px; transform: translate(-50%,-50%); pointer-events: none; }\n.dsc-empty-hero svg { color: #525252; }\n.dsc-empty-hero h1 { font-size: 26px; line-height: 34px; font-weight: 400; color: var(--cx-text-2); margin: 0; white-space: nowrap; }\n.dsc-hero-projectbar { visibility: visible; position: fixed; z-index: 6; height: calc(var(--cx-picker-h) + var(--cx-radius-composer)); padding: 4px 4px var(--cx-radius-composer); background: var(--cx-picker-fill); border-radius: 20px; }\n.dsc-hero-projectbar .dsc-project-picker { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; max-width: calc(100% - 180px); color: var(--cx-text-2); font-size: 13px; border-radius: var(--cx-radius-full); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-hero-projectbar .dsc-project-picker:hover, .dsc-hero-projectbar .dsc-project-picker[aria-expanded=true] { background: var(--cx-active); }\n.dsc-ui button:active:not(:disabled) { transform: translateY(1px); }\n.dsc-brand-mark { width: 25px; height: 20px; object-fit: contain; }\n.dsc-brand { display: inline-flex; align-items: center; gap: 7px; }\n.dsc-brand strong { font-size: 17px; letter-spacing: -.02em; }\n.dsc-brand small { font: 700 8px/16px var(--cx-font-sans); letter-spacing: .12em; padding: 0 4px; color: #131313; background: #eee; border-radius: 3px; }\n.dsc-settings-trigger { display: inline-flex; gap: 8px; align-items: center; }\n.dsc-workspaces { height: 100%; min-height: 0; width: 100%; display: flex; flex-direction: column; background: var(--cx-sidebar); }\n.dsc-tree-tools { display: flex; gap: 4px; align-items: center; padding: 6px 8px 12px; }\n.dsc-search { flex: 1; display: flex; align-items: center; gap: 6px; min-width: 0; border-radius: 8px; background: #ffffff08; padding: 3px 8px; color: var(--cx-text-3); }\n.dsc-search input { min-width: 0; width: 100%; border: 0; outline: none; background: none; font-size: 12px; }\n.dsc-search:focus-within { box-shadow: 0 0 0 1px var(--cx-border-l3); }\n.dsc-search input::placeholder { color: var(--cx-text-3); }\n.dsc-icon-button { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 7px; color: var(--cx-text-2); }\n.dsc-icon-button:hover { background: var(--cx-active); }\n.dsc-tree-scroll { flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; padding: 0 8px; }\n.dsc-section-title { padding: 12px 8px 6px; font-size: 12px; font-weight: 500; color: var(--cx-text-3); }\n.dsc-thread, .dsc-project { display: flex; align-items: center; height: 30px; border-radius: 7px; position: relative; }\n.dsc-thread:hover, .dsc-project:hover, .dsc-thread.is-active { background: #ffffff0d; }\n.dsc-ui .dsc-thread-main, .dsc-ui .dsc-project-main { display: flex; flex: 1; align-items: center; gap: 7px; min-width: 0; height: 100%; padding: 0 8px; text-align: left; color: #c2c2c2; }\n.dsc-thread-title, .dsc-project-main > span { min-width: 0; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-ui .dsc-thread-main { padding-right: 62px; }\n.dsc-thread-title-text { display: inline-block; width: max-content; max-width: none; vertical-align: top; }\n.dsc-thread-title[data-overflow=true] { text-overflow: clip; mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent); }\n.dsc-thread:hover .dsc-thread-title[data-overflow=true] .dsc-thread-title-text,\n.dsc-thread-main:focus-visible .dsc-thread-title[data-overflow=true] .dsc-thread-title-text { animation: dsc-title-scroll var(--dsc-title-duration) linear 250ms infinite alternate; }\n@keyframes dsc-title-scroll { from { transform: translateX(0); } to { transform: translateX(var(--dsc-title-shift)); } }\n.dsc-thread-time { position: absolute; right: 9px; font-size: 10px; color: var(--cx-text-4); transition: opacity 120ms ease; }\n.dsc-thread:hover .dsc-thread-time, .dsc-thread:has(:focus-visible) .dsc-thread-time { display: none; }\n.dsc-thread .dsc-state[data-state=idle] { display: none; }\n.dsc-thread-actions { position: absolute; top: 0; right: 4px; height: 100%; display: flex; align-items: center; gap: 1px; opacity: 0; pointer-events: none; transition: opacity 150ms ease; }\n.dsc-thread:hover .dsc-thread-actions, .dsc-thread:has(:focus-visible) .dsc-thread-actions { opacity: 1; pointer-events: auto; }\n.dsc-ui .dsc-thread-action { display: grid; place-items: center; flex: none; width: 25px; height: 26px; padding: 0; border-radius: 5px; color: #969696; transition: color 120ms ease; }\n.dsc-ui .dsc-thread-action:hover, .dsc-ui .dsc-thread-action:focus-visible { color: #d2d2d2; }\n.dsc-thread-action svg { width: 14px; height: 14px; }\n.dsc-row-tooltip { position: fixed; z-index: 10001; pointer-events: none; padding: 5px 11px; border: 1px solid #ffffff08; border-radius: 13px; color: #dfdfdf; background: #2d2d2d; font-size: 13px; line-height: 18px; white-space: nowrap; box-shadow: 0 2px 5px #0002; opacity: 0; visibility: hidden; transform: translateY(3px); transition: opacity 80ms ease, transform 80ms ease, visibility 0s 80ms; }\n.dsc-row-tooltip[data-open=true] { opacity: 1; visibility: visible; transform: translateY(0); transition: opacity 120ms ease 350ms, transform 120ms ease 350ms, visibility 0s 350ms; }\n.dsc-state { color: var(--cx-text-3); flex: none; display: flex; align-items: center; }\n.dsc-state[data-state=pending] { color: var(--cx-warning); }\n.dsc-state[data-state=running] { color: var(--cx-accent-soft); animation: dsc-status-pulse 1.2s ease-in-out infinite; }\n.dsc-state[data-state=done] { color: var(--cx-success); }\n.dsc-row-action { display: flex; opacity: 0; pointer-events: none; transition: opacity 150ms ease; width: 25px; height: 25px; align-items: center; justify-content: center; border-radius: 5px; padding: 0; margin-right: 3px; color: var(--cx-text-3); }\n.dsc-thread:hover .dsc-row-action, .dsc-project:hover .dsc-row-action, .dsc-thread:has(:focus-visible) .dsc-row-action, .dsc-project:has(:focus-visible) .dsc-row-action { opacity: 1; pointer-events: auto; }\n.dsc-row-action:hover { color: #fff; background: var(--cx-active); }\n/* 本轮按用户录像为项目内容局部开放布局动画;外壳与其他面板的尺寸规则保持原约定。 */\n.dsc-project-fold { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 180ms cubic-bezier(.2,.8,.2,1), opacity 150ms ease; }\n.dsc-project-fold[data-open=true] { grid-template-rows: 1fr; opacity: 1; }\n.dsc-project-clip { min-height: 0; overflow: hidden; }\n.dsc-project-children { padding-left: 14px; }\n.dsc-project-children, .dsc-project-list { transform: translateY(-4px); transition: transform 180ms cubic-bezier(.2,.8,.2,1); }\n.dsc-project-fold[data-open=true] > .dsc-project-clip > :is(.dsc-project-children, .dsc-project-list) { transform: translateY(0); }\n.dsc-project-children .dsc-state { display: none; }\n.dsc-empty { display: block; color: var(--cx-text-3); padding: 10px 12px; font-size: 12px; }\n.dsc-archive-toggle { padding: 8px 12px; display: flex; gap: 6px; align-items: center; font-size: 11px; color: var(--cx-text-3); }\n.dsc-archive-toggle input { accent-color: var(--cx-accent); }\n.dsc-thread.is-archived { opacity: .55; }\n.dsc-error { padding: 4px 8px; color: var(--cx-danger); font: 12px/17px var(--cx-font-sans); white-space: normal; max-width: 310px; }\n.dsc-menu { z-index: 10000; padding: 6px; background: var(--cx-popover-fill); border: 1px solid var(--cx-border-l1); border-radius: 14px; box-shadow: 0 12px 36px #0007; overflow: auto; animation: dsc-pop-in 130ms ease both; }\n.dsc-menu-heading { font-size: 12px; color: var(--cx-text-3); padding: 5px 8px 8px; }\n.dsc-option { width: 100%; display: flex; gap: 9px; align-items: center; text-align: left; padding: 7px 8px; border-radius: 7px; min-height: 32px; }\n.dsc-option:hover, .dsc-option:focus-visible { background: #ffffff12; }\n.dsc-option-copy { display: flex; flex: 1; min-width: 0; flex-direction: column; line-height: 20px; }\n.dsc-option-copy small { font-size: 12px; color: var(--cx-text-3); line-height: 17px; }\n/* 行悬停详情卡:几何按原型实测(项目卡 321×130、右侧 5px;会话卡 470 宽、右侧 12px、上移 6px)。 */\n.dsc-row-pop { position: fixed; z-index: 10000; max-width: calc(100vw - 32px); padding: 6px; border-radius: var(--cx-radius-xl); border: 1px solid var(--cx-border-l1); background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); animation: dsc-row-pop-in 120ms var(--cx-ease-out) both; }\n.dsc-row-pop--project { width: 321px; padding: 8px; }\n.dsc-row-pop--thread { width: 470px; }\n.dsc-row-pop__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 8px 8px 10px; border-bottom: 1px solid var(--cx-border-l2); }\n.dsc-row-pop--project .dsc-row-pop__head { align-items: center; height: 28px; padding: 0 8px; border-bottom: 0; }\n.dsc-row-pop__name { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 14px; font-weight: var(--cx-weight-regular); color: var(--cx-text); }\n.dsc-row-pop__name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop__pin { display: inline-flex; align-items: center; flex: none; color: var(--cx-text-3); }\n.dsc-row-pop__excerpt { margin: 0; font-size: 13px; line-height: 20px; color: var(--cx-text); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n.dsc-row-pop__meta { display: flex; align-items: center; gap: 6px; flex: none; font-size: 12px; color: var(--cx-text-3); white-space: nowrap; }\n.dsc-row-pop__line { display: flex; align-items: center; gap: 8px; width: 100%; height: 32px; padding: 0 8px; border-radius: var(--cx-radius-md); font-size: 13px; color: var(--cx-text-2); text-align: left; }\n.dsc-row-pop__line svg { flex: none; color: var(--cx-text-3); }\n.dsc-row-pop--project .dsc-row-pop__line { height: 28px; font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--project .dsc-row-pop__line svg { color: var(--cx-text-2); }\n.dsc-row-pop__line--path, .dsc-row-pop__line--action { cursor: pointer; }\n.dsc-row-pop__line--path:hover, .dsc-row-pop__line--action:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-row-pop__line--path:focus-visible, .dsc-row-pop__line--action:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\n.dsc-row-pop__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop--project .dsc-row-pop__path { font-family: var(--cx-font-sans); font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--thread .dsc-row-pop__path { font-family: var(--cx-font-mono); font-size: 12px; }\n.dsc-row-pop__arrow { margin-left: auto; opacity: 0; transition: opacity 120ms var(--cx-ease); }\n.dsc-row-pop__line--path:hover .dsc-row-pop__arrow, .dsc-row-pop__line--path:focus-visible .dsc-row-pop__arrow { opacity: 1; }\n.dsc-row-pop__sep { height: 1px; background: var(--cx-border-l1); }\n@keyframes dsc-row-pop-in { from { opacity: 0; } to { opacity: 1; } }\n/* 「编辑项目」对话框:按参考图(面板 #2d2d2d、16 圆角、保存白底、移除暗红底);源文件夹只读展示。 */\n.dsc-modal { position: fixed; inset: 0; z-index: 11000; display: flex; align-items: center; justify-content: center; padding: 24px; }\n.dsc-modal__mask { position: absolute; inset: 0; background: rgba(0,0,0,.12); }\n.dsc-modal__panel { position: relative; display: flex; flex-direction: column; width: min(520px, calc(100vw - 32px)); padding: 24px; border-radius: 16px; background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); color: var(--cx-text); }\n.dsc-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }\n.dsc-modal__head h2 { margin: 0; font-size: 20px; line-height: 28px; font-weight: 600; }\n.dsc-modal__close { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border-radius: var(--cx-radius-sm); color: var(--cx-text-2); }\n.dsc-modal__close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__field { display: flex; align-items: center; gap: 10px; height: 44px; margin-top: 20px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); }\n.dsc-modal__field:focus-within { border-color: var(--cx-focus); }\n.dsc-modal__input { flex: 1; min-width: 0; height: 100%; border: 0; outline: none; background: none; font: 15px/22px var(--cx-font-sans); color: var(--cx-text); }\n.dsc-modal__input::placeholder { color: var(--cx-text-3); }\n.dsc-modal__label { margin: 20px 0 8px; font-size: 13px; line-height: 18px; color: var(--cx-text-3); }\n.dsc-modal__source { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-modal .dsc-error { margin-top: 12px; }\n.dsc-modal__actions { display: flex; align-items: center; gap: 8px; margin-top: 24px; }\n.dsc-modal__spacer { flex: 1; }\n.dsc-modal__remove { height: 36px; padding: 0 16px; border-radius: 999px; background: rgba(250,102,101,.14); color: #fa6665; font-size: 14px; }\n.dsc-modal__remove:hover:not(:disabled) { background: rgba(250,102,101,.22); }\n.dsc-modal__cancel { height: 36px; padding: 0 14px; border-radius: 999px; color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__cancel:hover:not(:disabled) { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__save { height: 36px; padding: 0 20px; border-radius: 999px; background: #dfdfdf; color: #131313; font-size: 14px; font-weight: 500; }\n.dsc-modal__save:hover:not(:disabled) { background: #fff; }\n.dsc-modal__save:disabled, .dsc-modal__remove:disabled, .dsc-modal__cancel:disabled { opacity: .6; cursor: default; }\n.dsc-permission, .dsc-model { position: relative; display: inline-flex; align-items: center; min-width: 0; }\n.dsc-model { gap: 2px; }\n.dsc-control-status { display: inline-flex; align-items: center; gap: 5px; padding: 0 7px; color: var(--cx-text-3); font-size: 11px; }\n.dsc-control-status button { padding: 2px 4px; text-decoration: underline; }\n.dsc-control-status button:hover { color: var(--cx-text); }\n.dsc-pill { height: 28px; display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; border-radius: 999px; white-space: nowrap; color: var(--cx-text-2); font-size: 14px !important; }\n.dsc-pill:hover, .dsc-pill[aria-expanded=true] { background: #ffffff12; color: white; }\n.dsc-pill[data-warning=true] { color: var(--cx-warning); }\n.dsc-model-trigger { max-width: 260px; gap: 5px; }\n/* 右下角触发器:模型名偏白、思维强度灰色(参考图 4)。 */\n.dsc-model-trigger .dsc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: #dfdfdf; }\n.dsc-model-trigger .dsc-effort-name { flex: none; color: #989898; }\n.dsc-model-trigger svg { flex: none; color: #989898; }\n/* 「修改模型」面板:标题与列表样式明确区别于宿主原生菜单。 */\n.dsc-menu.dsc-model-panel { padding: 8px; }\n.dsc-menu.dsc-model-panel .dsc-menu-heading { font-size: 14px; line-height: 20px; color: var(--cx-text); font-weight: 500; padding: 6px 8px 10px; }\n.dsc-model-group { color: var(--cx-text-3); font-size: 11px; padding: 7px 8px 2px; }\n.dsc-model-search { border: 0; border-bottom: 1px solid var(--cx-border-l1); background: none; outline: none; padding: 6px 8px; width: 100%; margin-bottom: 3px; }\n.dsc-back { display: flex; align-items: center; gap: 4px; color: var(--cx-text-3); font-size: 12px !important; border-radius: 5px; }\n.dsc-back:hover { color: #fff; }\n/* 滑条弹层裁掉横向溢出:Max/Low/High 档的撒点动画会向右超出边界,触发多余的水平滚动条。 */\n.dsc-reasoning-pop { padding: 12px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); overflow: hidden; animation: dsc-reasoning-in 130ms var(--cx-ease) both; }\n.dsc-reasoning { position: relative; }\n.dsc-reasoning-heading { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0 20px 10px; text-align: center; }\n.dsc-reasoning-heading strong { font-size: 14px; font-weight: var(--cx-weight-semibold); line-height: 18px; color: var(--cx-reasoning-title); transition: color 150ms ease; }\n.dsc-reasoning-model { display: inline-flex; align-items: center; justify-content: center; gap: 2px; max-width: 100%; padding: 4px 10px; border-radius: var(--cx-radius-md); color: #ffffff9e !important; font-size: 12px !important; line-height: 16px !important; white-space: nowrap; transition: background-color 120ms var(--cx-ease), color 120ms var(--cx-ease); }\n.dsc-reasoning-model:hover { background: #3c3b3b; color: var(--cx-text) !important; }\n.dsc-reasoning-reset { position: absolute; top: -4px; right: -4px; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: var(--cx-radius-sm); color: var(--cx-text-3) !important; }\n.dsc-reasoning-reset:hover { background: var(--cx-hover); color: var(--cx-text) !important; }\n.dsc-reasoning[data-level=max] .dsc-reasoning-heading strong { color: var(--cx-reasoning-max-text); }\n.dsc-slider { height: 32px; position: relative; margin: 0 2px; border-radius: 999px; cursor: pointer; touch-action: none; outline: none; }\n/* 提交期间保持原有视觉:只禁用拖拽,不切换光标/透明度,避免切换档位时闪变。 */\n.dsc-slider[aria-disabled=true] { cursor: default; opacity: 1; }\n.dsc-slider-track { position: absolute; inset: 4px 0; height: 24px; border-radius: 999px; background: var(--cx-reasoning-track); overflow: hidden; }\n.dsc-slider-fill { position: absolute; inset: 0; border-radius: inherit; background: var(--cx-reasoning-blue); transform-origin: left center; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-slider-max { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg,var(--cx-reasoning-max-blue),var(--cx-reasoning-purple) 52%,var(--cx-reasoning-violet)); opacity: 0; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-slider-max { opacity: 1; }\n.dsc-slider-max::before, .dsc-slider-max::after { content: ''; position: absolute; animation-play-state: paused; }\n.dsc-slider-max::before { inset: -50% -25%; background: radial-gradient(ellipse,var(--cx-reasoning-max-ice),#a095ffd9 22%,transparent 58%); animation: dsc-max-flow 2.8s cubic-bezier(.4,0,.6,1) -.45s infinite alternate; animation-play-state: paused; }\n.dsc-slider-max::after { inset: -40% -30%; background: radial-gradient(ellipse,#d6a2ff,var(--cx-reasoning-max-glow) 20%,transparent 55%); animation: dsc-max-bloom 4.2s cubic-bezier(.4,0,.6,1) -1.65s infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-slider-max::before, .dsc-slider[data-level=max] .dsc-slider-max::after { animation-play-state: running; }\n.dsc-stars { position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-stars { opacity: 1; }\n.dsc-stars i { position: absolute; border-radius: 50%; background: #fff; box-shadow: 0 0 3px #e0d6ff8c; animation: dsc-star-drift var(--pdur) cubic-bezier(.4,0,.6,1) var(--pdelay) infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-stars i { animation-play-state: running; }\n.dsc-tick { position: absolute; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: #ffffff38; transform: translate(-50%,-50%); transition: opacity 150ms ease,transform 120ms ease; }\n.dsc-tick.is-on { background: #ffffff4d; }\n.dsc-slider[data-level=max] .dsc-tick { opacity: 0; }\n.dsc-thumb-position { position: absolute; inset: 0; pointer-events: none; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-thumb { position: absolute; top: 50%; left: 14px; width: 28px; height: 28px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px #0007,0 0 0 1px #0000000f; transform: translate(-50%,-50%); pointer-events: auto; transition: transform 140ms var(--cx-ease-out),box-shadow 140ms var(--cx-ease); }\n/* 固定白色滑块为正圆,避免宿主全局圆角规则覆盖插件外形。 */\n.dsc-ui .dsc-slider .dsc-thumb { border-radius: 50% !important; }\n.dsc-thumb:hover { transform: translate(-50%,-50%) scale(1.1); box-shadow: 0 1px 6px #0008,0 0 0 1px #0000000f; }\n.dsc-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.dsc-permission-menu { padding: 4px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); }\n.dsc-permission-heading { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px 8px; color: var(--cx-text-3); font-size: 13px; line-height: 18px; }\n.dsc-permission-heading button { padding: 0; white-space: nowrap; text-decoration: underline; text-underline-offset: 2px; }\n.dsc-permission-help { margin: 4px 8px 8px; color: var(--cx-text-2); font-size: 12px; line-height: 18px; }\n.dsc-permission-menu .dsc-option { align-items: flex-start; gap: 8px; padding: 6px 8px; border-radius: var(--cx-radius-xl); color: var(--cx-text-2); font-size: 14px; line-height: 19px; }\n.dsc-permission-menu .dsc-option > svg { margin-top: 3px; }\n.dsc-permission-menu .dsc-option-copy { line-height: 19px; }\n.dsc-permission-menu .dsc-option[data-warning=true], .dsc-permission-menu .dsc-option[data-warning=true] small { color: var(--cx-warning); }\n.dsc-burst { position: absolute; left: 14px; top: 50%; }\n.dsc-burst i { position: absolute; width: 3px; height: 3px; border-radius: 50%; background: #b98cff; animation: dsc-burst 280ms cubic-bezier(.22,1,.36,1) both; }\n.dsc-slider-labels { display: flex; justify-content: space-between; padding-top: 3px; }\n.dsc-slider-labels button { padding: 0 3px; font-size: 10px; color: var(--cx-text-3); }\n.dsc-slider-labels button:hover { color: white; }\n.dsc-reasoning-description { display: block; text-align: center; color: var(--cx-text-3); font-size: 11px; line-height: 16px; margin-top: 7px; }\n@keyframes dsc-pop-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }\n/* 退场淡出:关闭弹层时先播放 160ms,再卸载 DOM。 */\n@keyframes dsc-pop-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(3px); } }\n.dsc-menu.dsc-menu-closing { animation: dsc-pop-out 160ms ease both; pointer-events: none; }\n@keyframes dsc-reasoning-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dsc-status-pulse { 50% { opacity: .4; } }\n@keyframes dsc-max-flow { from { transform: translateX(-32%) scale(.82,1); opacity: .95; } to { transform: translateX(32%) scale(1.02,1); opacity: .8; } }\n@keyframes dsc-max-bloom { from { transform: translateX(28%) scale(.7,1); opacity: .9; } to { transform: translateX(-28%) scale(1,1); opacity: 1; } }\n@keyframes dsc-star-drift { from { transform: translate(-50%,-50%) translate(calc(var(--dx)*-.5),calc(var(--dy)*-.5)); opacity: .45; } to { transform: translate(-50%,-50%) translate(calc(var(--dx)*.5),calc(var(--dy)*.5)); opacity: .8; } }\n@keyframes dsc-burst { 0% { transform: translate(-50%,-50%) scale(.45); opacity: 0; } 22% { opacity: .65; } 100% { transform: translate(-50%,-50%) translate(var(--bx),var(--by)) scale(.45); opacity: 0; } }\n@media (prefers-reduced-motion: reduce) {\n .dsc-sidebar-shell, .dsc-row-tooltip, .dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { animation: none !important; transition: none !important; }\n .dsc-burst { display: none; }\n}\n\n/* Codex 几何(第 48 轮):\n 1) 侧边栏列左上角圆角、对话列左上角直角(与 Codex 蓝框/红框一致);\n 2) 对话正文列与输入框同宽:由插件测量输入框卡片的实际视觉宽度写入 --dsc-composer-visual-width,\n 正文列优先取该值,回退到宿主 --dsh-composer-card-max-width(正文 + 32px)。 */\nbody[data-dsh-codex-style] [data-conversation-scroll] [class*=\"_column\"] { max-width: var(--dsc-composer-visual-width, var(--dsh-composer-card-max-width)); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-sidebar-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-center-column] { border-radius: 0; }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] [data-dsc-center-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\n";
6
6
  const PROTOTYPE_ICONS = {"home":"<svg viewBox=\"0 0 16 16\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M3 7.2 8 3.2l5 4v5.3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z\"/></svg>","tasks":"<svg viewBox=\"0 0 16 16\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"8\" cy=\"8\" r=\"5.5\"/><path d=\"M8 5v3l2 1.5\"/></svg>","plugins":"<svg class=\"dsc-rail__plugin-icon\" viewBox=\"0 0 20 20\" width=\"20\" height=\"20\" stroke-width=\"1.5\" aria-hidden=\"true\"><path d=\"M7.5 3v4m5-4v4M5.5 7h9v2.8a4.5 4.5 0 0 1-9 0V7ZM10 14.3v3.2\"/></svg>","settings":"<svg viewBox=\"0 0 20 20\" width=\"18\" height=\"18\" stroke-width=\"1.4\" aria-hidden=\"true\">\n <path d=\"M8.80 3.20 L9.07 1.85 A8.20 8.20 0 0 1 10.93 1.85 L11.20 3.20 A6.90 6.90 0 0 1 13.96 4.35 L15.10 3.58 A8.20 8.20 0 0 1 16.42 4.90 L15.65 6.04 A6.90 6.90 0 0 1 16.80 8.80 L18.15 9.07 A8.20 8.20 0 0 1 18.15 10.93 L16.80 11.20 A6.90 6.90 0 0 1 15.65 13.96 L16.42 15.10 A8.20 8.20 0 0 1 15.10 16.42 L13.96 15.65 A6.90 6.90 0 0 1 11.20 16.80 L10.93 18.15 A8.20 8.20 0 0 1 9.07 18.15 L8.80 16.80 A6.90 6.90 0 0 1 6.04 15.65 L4.90 16.42 A8.20 8.20 0 0 1 3.58 15.10 L4.35 13.96 A6.90 6.90 0 0 1 3.20 11.20 L1.85 10.93 A8.20 8.20 0 0 1 1.85 9.07 L3.20 8.80 A6.90 6.90 0 0 1 4.35 6.04 L3.58 4.90 A8.20 8.20 0 0 1 4.90 3.58 L6.04 4.35 A6.90 6.90 0 0 1 8.80 3.20\"/>\n <circle cx=\"10\" cy=\"10\" r=\"2.3\"/>\n </svg>","newChat":"<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8.1 2.8H4.6a2 2 0 0 0-2 2v6.6a2 2 0 0 0 2 2h6.6a2 2 0 0 0 2-2V7.9\"/><path d=\"m10.9 2.7-5.3 5.3L5 10.4l2.4-.6 5.3-5.3a1.3 1.3 0 0 0-1.8-1.8ZM9.8 3.8l1.8 1.8\"/></svg>"};
7
7
  const BRAND_MARK = "data:image/png;base64,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";
8
- const PLUGIN_VERSION = "0.1.2";
8
+ const PLUGIN_VERSION = "0.1.3";
9
9
  const PLUGIN_PACKAGE = "@xinxing1708/dsh-codex-style";
10
10
  const module = { exports: {} };
11
11
  const exports = module.exports;
@@ -1165,7 +1165,10 @@ function apply(ctx) {
1165
1165
  'settings.trigger': () => h('span', { className: 'dsc-ui dsc-settings-trigger' }, h(Icon, { name: 'settings', size: 18 }), '设置')
1166
1166
  };
1167
1167
  for (const [name, component] of Object.entries(components)) register(ctx, name, component);
1168
- ctx.slots.inject('plugins.detail.actions', () => ctx.slots.register({ name: 'plugins.detail.actions', id: 'codex-style:check-update' }, PluginUpdateAction));
1168
+ const updates = createPluginUpdateService(PLUGIN_VERSION);
1169
+ ctx.effect(() => { updates.start(); return () => updates.dispose(); }, 'codex-style: automatic update check');
1170
+ ctx.slots.inject('plugins.detail.actions', () => ctx.slots.register({ name: 'plugins.detail.actions', id: 'codex-style:check-update' }, props => h(PluginUpdateAction, { ...props, updates })));
1171
+ ctx.inject(['pluginNavigation'], scope => scope.slots.inject('shell.overlay', () => scope.slots.register({ name: 'shell.overlay', id: 'codex-style:update-notice' }, () => h(PluginUpdateNotice, { updates, onOpen: () => scope.reflect.get('pluginNavigation').openBundle(PLUGIN_PACKAGE) }))));
1169
1172
  // 等各区域自己的服务就绪;权限 RPC 的命名空间不能由 remote 根服务存在来推断。
1170
1173
  ctx.inject(['sessions', 'uiSession', 'workspaces', 'uiWorkspace', 'layout', 'shortcuts', 'remote', 'locale'], scope => {
1171
1174
  const sidebar = createSidebarPresentation();
@@ -1259,7 +1262,7 @@ exports.name = 'codex-style';
1259
1262
  exports.inject = ['slots', 'theme'];
1260
1263
  exports.apply = apply;
1261
1264
 
1262
- // 手动只读检查;固定 scoped 身份,不能查询 npm 上同名的第三方包。
1265
+ // 只读检查与新版提醒;固定 scoped 身份,不能查询 npm 上同名的第三方包。
1263
1266
  const UPDATE_PACKAGE = '@xinxing1708/dsh-codex-style';
1264
1267
  const UPDATE_REGISTRIES = [
1265
1268
  { id: 'npmmirror', label: 'npmmirror 中国镜像', url: 'https://registry.npmmirror.com/' },
@@ -1331,42 +1334,114 @@ async function checkPluginUpdate(currentVersion, { signal, fetcher = globalThis.
1331
1334
  return { state: comparison > 0 ? 'update' : comparison < 0 ? 'ahead' : 'current', latestVersion: latest.version, source: latest.label, mirror: latest.id !== 'npm', sources };
1332
1335
  }
1333
1336
 
1334
- function PluginUpdateAction({ subject }) {
1337
+ const UPDATE_CACHE_KEY = 'dsh-codex-style:update-check:v1';
1338
+ const UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
1339
+ const UPDATE_RETRY_MS = 60 * 60 * 1000;
1340
+ function createPluginUpdateService(currentVersion, { query = checkPluginUpdate } = {}) {
1341
+ let lastSuccess = 0, retryAt = 0, timer, pending, controller, started = false, disposed = false;
1342
+ let cached = {};
1343
+ try { cached = JSON.parse(window.localStorage.getItem(UPDATE_CACHE_KEY) || '{}') || {}; } catch { /* 存储不可用时仍可在本次运行检查。 */ }
1344
+ const validCache = cached.currentVersion === currentVersion && Number.isFinite(cached.checkedAt) && cached.checkedAt > 0 && cached.checkedAt <= Date.now() && updateVersionParts(cached.latestVersion);
1345
+ if (validCache) lastSuccess = cached.checkedAt;
1346
+ const comparison = validCache ? compareUpdateVersions(cached.latestVersion, currentVersion) : null;
1347
+ const store = createSnapshotStore({
1348
+ state: validCache ? comparison > 0 ? 'update' : comparison < 0 ? 'ahead' : 'current' : 'idle',
1349
+ checking: false,
1350
+ ...(validCache ? { latestVersion: cached.latestVersion, source: UPDATE_REGISTRIES.find(row => row.label === cached.source)?.label, mirror: cached.mirror === true } : {}),
1351
+ dismissedVersion: updateVersionParts(cached.dismissedVersion) ? cached.dismissedVersion : undefined
1352
+ });
1353
+ const save = () => {
1354
+ const state = store.getSnapshot();
1355
+ try { window.localStorage.setItem(UPDATE_CACHE_KEY, JSON.stringify({ currentVersion, checkedAt: lastSuccess, latestVersion: state.latestVersion, source: state.source, mirror: state.mirror, dismissedVersion: state.dismissedVersion })); } catch { /* 不阻断检查或提醒。 */ }
1356
+ };
1357
+ const schedule = delay => {
1358
+ clearTimeout(timer);
1359
+ if (started && !disposed) timer = setTimeout(runScheduled, Math.max(0, delay));
1360
+ };
1361
+ const dueIn = () => Math.max(lastSuccess ? lastSuccess + UPDATE_INTERVAL_MS : 0, retryAt) - Date.now();
1362
+ async function runScheduled() {
1363
+ if (disposed) return;
1364
+ if (dueIn() > 0) { schedule(dueIn()); return; }
1365
+ if (window.navigator.onLine === false) { retryAt = Date.now() + UPDATE_RETRY_MS; schedule(dueIn()); return; }
1366
+ await check();
1367
+ }
1368
+ function check() {
1369
+ if (disposed) return Promise.reject(new Error('更新检查已停用'));
1370
+ if (pending) return pending;
1371
+ controller = new AbortController();
1372
+ const signal = controller.signal;
1373
+ store.set({ ...store.getSnapshot(), checking: true });
1374
+ pending = Promise.resolve().then(() => query(currentVersion, { signal })).then(result => {
1375
+ if (disposed || signal.aborted) return result;
1376
+ store.set({ ...store.getSnapshot(), message: undefined, ...result, checking: false });
1377
+ if (['current', 'update', 'ahead'].includes(result.state)) { lastSuccess = Date.now(); retryAt = 0; save(); }
1378
+ else retryAt = Date.now() + UPDATE_RETRY_MS;
1379
+ return result;
1380
+ }).catch(error => {
1381
+ if (!disposed && !signal.aborted) {
1382
+ store.set({ ...store.getSnapshot(), state: 'error', checking: false, message: '检查失败,请稍后重试。' });
1383
+ retryAt = Date.now() + UPDATE_RETRY_MS;
1384
+ }
1385
+ return { state: 'error' };
1386
+ }).finally(() => { pending = null; controller = null; if (!disposed) schedule(dueIn()); });
1387
+ return pending;
1388
+ }
1389
+ const onOnline = () => { retryAt = 0; schedule(dueIn()); };
1390
+ const onVisible = () => { if (document.visibilityState === 'visible') schedule(dueIn()); };
1391
+ return {
1392
+ store, check,
1393
+ dismiss() { store.set({ ...store.getSnapshot(), dismissedVersion: store.getSnapshot().latestVersion }); save(); },
1394
+ start() {
1395
+ if (started || disposed) return;
1396
+ started = true;
1397
+ window.addEventListener('online', onOnline);
1398
+ document.addEventListener('visibilitychange', onVisible);
1399
+ schedule(Math.max(8000, dueIn()));
1400
+ },
1401
+ dispose() {
1402
+ disposed = true; started = false;
1403
+ clearTimeout(timer); controller?.abort();
1404
+ window.removeEventListener('online', onOnline);
1405
+ document.removeEventListener('visibilitychange', onVisible);
1406
+ }
1407
+ };
1408
+ }
1409
+
1410
+ function PluginUpdateNotice({ updates, onOpen }) {
1411
+ const state = useSource(updates.store);
1412
+ if (compareUpdateVersions(state.latestVersion, PLUGIN_VERSION) !== 1 || state.dismissedVersion === state.latestVersion) return null;
1413
+ return h('aside', { className: 'dsc-ui dsc-update-notice', role: 'status', 'aria-live': 'polite', 'aria-label': 'Codex Style 更新提醒' },
1414
+ h('div', { className: 'dsc-update-heading' }, h('strong', null, 'Codex Style 有新版本'), h('button', { type: 'button', className: 'dsc-update-close', 'aria-label': '忽略此版本提醒', onClick: () => updates.dismiss() }, h(Icon, { name: 'close' }))),
1415
+ h('p', { className: 'dsc-update-note' }, 'v' + PLUGIN_VERSION + ' → v' + state.latestVersion),
1416
+ h('button', { type: 'button', className: 'dsc-update-retry', onClick: onOpen }, '查看更新'));
1417
+ }
1418
+
1419
+ function PluginUpdateAction({ subject, updates }) {
1335
1420
  const pkg = subject?.pkg;
1336
1421
  if (subject?.kind !== 'bundle' || !pkg?.installed) return null;
1337
1422
  const legacy = PLUGIN_PACKAGE === PLUGIN_ID && pkg.name === PLUGIN_ID && pkg.rows?.some(row => row.rowId === 'ui-skin-codex-style' && row.moduleName === PLUGIN_ID);
1338
1423
  if (pkg.name !== UPDATE_PACKAGE && !legacy) return null;
1339
- return h(PluginUpdateControl, { key: pkg.name + '@' + pkg.version, currentVersion: pkg.version });
1424
+ return h(PluginUpdateControl, { key: pkg.name + '@' + pkg.version, currentVersion: pkg.version, updates });
1340
1425
  }
1341
- function PluginUpdateControl({ currentVersion }) {
1342
- const anchor = useRef(null), request = useRef(null);
1426
+ function PluginUpdateControl({ currentVersion, updates }) {
1427
+ const anchor = useRef(null);
1428
+ const service = useMemo(() => updates || createPluginUpdateService(currentVersion), [updates, currentVersion]);
1343
1429
  const [open, setOpen] = useState(false);
1344
- const [result, setResult] = useState({ state: 'idle' });
1345
- const busy = result.state === 'checking';
1346
- useEffect(() => () => { request.current?.abort(); }, []);
1430
+ const result = useSource(service.store);
1431
+ const busy = result.checking;
1432
+ useEffect(() => () => { if (!updates) service.dispose(); }, [service, updates]);
1347
1433
  const close = useCallback(() => { setOpen(false); }, []);
1348
1434
  const check = async () => {
1349
- if (request.current) return;
1350
- const controller = new AbortController();
1351
- request.current = controller;
1352
1435
  setOpen(true);
1353
- setResult({ state: 'checking' });
1354
- try {
1355
- const next = await checkPluginUpdate(currentVersion, { signal: controller.signal });
1356
- if (!controller.signal.aborted) setResult(next);
1357
- } catch (error) {
1358
- if (!controller.signal.aborted) setResult({ state: 'error', message: '检查失败,请稍后重试。' });
1359
- } finally {
1360
- if (request.current === controller) request.current = null;
1361
- }
1436
+ await service.check();
1362
1437
  };
1363
1438
  const messages = { checking: '正在检查更新…', current: '已是最新版本', ahead: '当前版本较新', update: '发现新版本', unpublished: '暂未查到发布版本', error: '检查失败' };
1364
- return h('span', { className: 'dsc-update-control', 'data-update-state': result.state },
1439
+ return h('span', { className: 'dsc-update-control', 'data-update-state': busy ? 'checking' : result.state },
1365
1440
  h(Button, { variant: 'outline', size: 'sm', ref: anchor, disabled: busy, icon: h(IconRefreshOutlineRegular, { size: 13, className: busy ? 'dsc-update-spin' : undefined }), onClick: check, 'aria-haspopup': 'dialog', 'aria-expanded': open }, busy ? '检查中…' : '检查更新'),
1366
1441
  open && h(Popover, { anchor, onClose: close, label: '检查插件更新', role: 'dialog', width: 336, className: 'dsc-update-panel' },
1367
1442
  h('div', { className: 'dsc-update-heading' }, h('strong', null, '插件更新'), h('button', { type: 'button', className: 'dsc-update-close', onClick: close, 'aria-label': '关闭更新检查' }, h(Icon, { name: 'close' }))),
1368
- h('p', { className: 'dsc-update-message', role: 'status', 'aria-live': 'polite', 'data-state': result.state }, messages[result.state]),
1369
- h('dl', { className: 'dsc-update-versions' }, h('dt', null, '当前版本'), h('dd', null, currentVersion ? 'v' + currentVersion : '未知'), result.latestVersion && h(React.Fragment, null, h('dt', null, '最新版本'), h('dd', null, 'v' + result.latestVersion))),
1443
+ h('p', { className: 'dsc-update-message', role: 'status', 'aria-live': 'polite', 'data-state': result.state }, messages[busy ? 'checking' : result.state]),
1444
+ h('dl', { className: 'dsc-update-versions' }, h('dt', null, '当前版本'), h('dd', null, currentVersion ? 'v' + currentVersion : '未知'), result.latestVersion && h(React.Fragment, null, h('dt', null, busy || ['error', 'unpublished'].includes(result.state) ? '上次查到' : '最新版本'), h('dd', null, 'v' + result.latestVersion))),
1370
1445
  result.source && h('p', { className: 'dsc-update-note' }, '来源:' + result.source, result.mirror && h('span', null, '镜像同步可能稍有延迟。')),
1371
1446
  result.message && h('p', { className: 'dsc-update-note' }, result.message),
1372
1447
  result.state === 'update' && h('p', { className: 'dsc-update-note' }, '新版本已发布,请按安装说明更新插件。'),
package/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "@xinxing1708/dsh-codex-style",
3
- "version": "0.1.2",
4
- "description": "DSH 的 Codex 风格深色界面插件",
5
- "type": "module",
6
- "main": "lib/host.js",
7
- "exports": {
8
- ".": "./lib/host.js",
9
- "./client": "./lib/client.js",
10
- "./package.json": "./package.json"
11
- },
12
- "scripts": {
13
- "build": "node scripts/build.mjs"
14
- },
15
- "files": [
16
- "lib",
17
- "cordis.patch.yml",
18
- "skin.json",
19
- "README.md",
20
- "LICENSE",
21
- "CHANGELOG.md",
22
- "THIRD_PARTY_NOTICES.md",
23
- "install.cmd",
24
- "scripts/install.mjs",
25
- "scripts/rollback.mjs",
26
- "licenses"
27
- ],
28
- "license": "MIT",
29
- "publishConfig": {
30
- "access": "public",
31
- "registry": "https://registry.npmjs.org"
32
- },
33
- "dsh": {
34
- "engines": {
35
- "dsh": "0.2.1-rc.2"
36
- },
37
- "bundle": {
38
- "patch": "./cordis.patch.yml"
39
- },
40
- "client": {
41
- "platform": "web",
42
- "inject": [
43
- "@deepseek-ai/dsh-client-ui-theme",
44
- "@deepseek-ai/dsh-client-ui-sidebar",
45
- "@deepseek-ai/dsh-client-ui-workspace",
46
- "@deepseek-ai/dsh-client-ui-conversation",
47
- "@deepseek-ai/dsh-client-ui-model-selection",
48
- "@deepseek-ai/dsh-client-ui-permission-presets",
1
+ {
2
+ "name": "@xinxing1708/dsh-codex-style",
3
+ "version": "0.1.3",
4
+ "description": "DSH 的 Codex 风格深色界面插件",
5
+ "type": "module",
6
+ "main": "lib/host.js",
7
+ "exports": {
8
+ ".": "./lib/host.js",
9
+ "./client": "./lib/client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "scripts": {
13
+ "build": "node scripts/build.mjs"
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "cordis.patch.yml",
18
+ "skin.json",
19
+ "README.md",
20
+ "LICENSE",
21
+ "CHANGELOG.md",
22
+ "THIRD_PARTY_NOTICES.md",
23
+ "install.cmd",
24
+ "scripts/install.mjs",
25
+ "scripts/rollback.mjs",
26
+ "licenses"
27
+ ],
28
+ "license": "MIT",
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "registry": "https://registry.npmjs.org"
32
+ },
33
+ "dsh": {
34
+ "engines": {
35
+ "dsh": "0.2.1-rc.2"
36
+ },
37
+ "bundle": {
38
+ "patch": "./cordis.patch.yml"
39
+ },
40
+ "client": {
41
+ "platform": "web",
42
+ "inject": [
43
+ "@deepseek-ai/dsh-client-ui-theme",
44
+ "@deepseek-ai/dsh-client-ui-sidebar",
45
+ "@deepseek-ai/dsh-client-ui-workspace",
46
+ "@deepseek-ai/dsh-client-ui-conversation",
47
+ "@deepseek-ai/dsh-client-ui-model-selection",
48
+ "@deepseek-ai/dsh-client-ui-permission-presets",
49
49
  "@deepseek-ai/dsh-client-ui-settings-general",
50
- "@deepseek-ai/dsh-client-ui-plugin-manager"
51
- ]
52
- }
53
- },
54
- "repository": {
55
- "type": "git",
56
- "url": "git+https://github.com/xinxing1708/DSH-Codex-Style.git"
57
- },
58
- "homepage": "https://github.com/xinxing1708/DSH-Codex-Style#readme",
59
- "bugs": {
60
- "url": "https://github.com/xinxing1708/DSH-Codex-Style/issues"
61
- },
62
- "engines": {
63
- "node": ">=20"
64
- }
65
- }
50
+ "@deepseek-ai/dsh-client-ui-plugin-manager"
51
+ ]
52
+ }
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/xinxing1708/DSH-Codex-Style.git"
57
+ },
58
+ "homepage": "https://github.com/xinxing1708/DSH-Codex-Style#readme",
59
+ "bugs": {
60
+ "url": "https://github.com/xinxing1708/DSH-Codex-Style/issues"
61
+ },
62
+ "engines": {
63
+ "node": ">=20"
64
+ }
65
+ }
@@ -1,110 +1,110 @@
1
- // 安装到当前用户 desktop profile;只复制本包,旧主题保留以便回退。
2
- import { readFile, writeFile, mkdir, copyFile, lstat, symlink, realpath, access, unlink } from 'node:fs/promises';
3
- import { resolve, dirname, relative, isAbsolute } from 'node:path';
4
- import { homedir } from 'node:os';
5
- import { fileURLToPath } from 'node:url';
6
- import { createHash } from 'node:crypto';
7
-
8
- const source = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9
- const dshRoot = resolve(process.env.DSH_HOME || resolve(homedir(), '.dsh'));
10
- const profile = resolve(dshRoot, 'profiles/desktop');
11
- const manifest = JSON.parse(await readFile(resolve(source, 'package.json'), 'utf8'));
12
- const runtime = JSON.parse(await readFile(resolve(dshRoot, 'dsh-runtimes/dsh-primary-runtime/runtime.json'), 'utf8'));
13
- if (runtime.desktopVersion !== manifest.dsh.engines.dsh) throw new Error(`DSH 版本不匹配:${runtime.desktopVersion}`);
14
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
15
- const target = resolve(dshRoot, 'dsh-codex-style', manifest.version + '-' + stamp);
16
- const backup = resolve(dshRoot, 'codex-style-backups', stamp);
17
- const packagePath = resolve(profile, 'package.json');
18
- const patchPath = resolve(profile, 'cordis.patch.yml');
19
- const lockPath = resolve(profile, 'pnpm-lock.yaml');
20
- const linkPath = resolve(profile, 'node_modules', manifest.name);
21
- const hash = bytes => createHash('sha256').update(bytes).digest('hex');
22
- const originals = await Promise.all([packagePath, patchPath, lockPath].map(async path => ({ path, bytes: await readFile(path) })));
23
- const config = JSON.parse(originals[0].bytes.toString());
24
- const legacyName = 'dsh-codex-style';
25
- let previousTarget;
26
- let previousName;
27
- let previousLinkPath;
28
- // 仅迁移本安装器管理的旧包;npm 上的同名第三方包不能按版本号认领。
29
- for (const name of new Set([manifest.name, legacyName])) {
30
- const candidatePath = resolve(profile, 'node_modules', name);
31
- const previousDependency = config.dependencies?.[name];
32
- const previousLink = await lstat(candidatePath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
33
- if (!previousDependency && !previousLink) continue;
34
- if (!previousDependency) throw new Error('node_modules 中已存在同名路径,停止以免覆盖:' + name);
35
- if (!previousDependency.startsWith('link:') || !previousLink?.isSymbolicLink()) throw new Error('同名安装不是本插件的本地链接,请人工核对。');
36
- const candidateTarget = await realpath(candidatePath);
37
- const managedRelative = relative(resolve(dshRoot, 'dsh-codex-style'), candidateTarget);
38
- if (!managedRelative || managedRelative.startsWith('..') || isAbsolute(managedRelative) || resolve(profile, previousDependency.slice(5)) !== resolve(candidateTarget)) throw new Error('现有插件链接不在受管理目录,停止升级。');
39
- const previousManifest = JSON.parse(await readFile(resolve(candidateTarget, 'package.json'), 'utf8'));
40
- if (previousManifest.name !== name) throw new Error('旧插件身份不匹配');
41
- if (previousTarget) throw new Error('新旧包同时存在,请先核对安装来源。');
42
- previousTarget = candidateTarget;
43
- previousName = name;
44
- previousLinkPath = candidatePath;
45
- }
46
- await access(resolve(source, 'lib/client.js'));
47
- await access(resolve(source, 'lib/host.js'));
48
- await mkdir(backup, { recursive: true });
49
- for (const entry of originals) await writeFile(resolve(backup, entry.path.split(/[\\/]/).pop()), entry.bytes);
50
- await mkdir(resolve(target, 'lib'), { recursive: true });
51
- await mkdir(resolve(target, 'licenses'), { recursive: true });
52
- for (const file of ['package.json', 'cordis.patch.yml', 'skin.json', 'README.md', 'LICENSE', 'CHANGELOG.md', 'THIRD_PARTY_NOTICES.md', 'licenses/DeepSeek-Harness-LICENSE', 'lib/client.js', 'lib/host.js']) await copyFile(resolve(source, file), resolve(target, file));
53
-
54
- const localLink = 'link:' + relative(profile, target).replaceAll('\\', '/');
55
- if (previousName && previousName !== manifest.name) delete config.dependencies[previousName];
56
- config.dependencies[manifest.name] = localLink;
57
- const oldSkins = new Set(['dsh-claude-style', '@michengai/dsh-codex-ui']);
58
- config.dsh.profile.bundles = config.dsh.profile.bundles.filter(name => !oldSkins.has(name) && name !== manifest.name && name !== previousName);
59
- config.dsh.profile.bundles.push(manifest.name);
60
- const eol = originals[1].bytes.includes(Buffer.from('\r\n')) ? '\r\n' : '\n';
61
- const patchBefore = originals[1].bytes.toString();
62
- const ownBlock = /^- id: ui-skin-codex-style\r?\n(?:[ \t]+[^\r\n]*(?:\r?\n|$))*/gm;
63
- const ownBlocks = [...patchBefore.matchAll(ownBlock)];
64
- if (ownBlocks.length > 1 || (previousTarget && ownBlocks.length !== 1)) throw new Error('未识别唯一的皮肤配置行;未改 profile。');
65
- const patch = ownBlocks.length ? patchBefore.replace(ownBlock, block => {
66
- const nameLine = /^ name: (.+)$/m;
67
- const oldName = block.match(nameLine)?.[1].trim().replace(/^(['"])(.*)\1$/, '$2');
68
- if (oldName !== manifest.name && oldName !== previousName) throw new Error('皮肤配置指向其他包,未改 profile。');
69
- return block.replace(nameLine, () => ' name: ' + JSON.stringify(manifest.name));
70
- }) : patchBefore + eol + [
71
- '# Codex Style:旧主题依赖保留,仅停用其界面行。',
72
- '- id: ui-skin-claude-style',
73
- ' disabled: true',
74
- '- id: ui-skin-codex-style',
75
- ' name: ' + JSON.stringify(manifest.name),
76
- ' disabled: false',
77
- ''
78
- ].join(eol);
79
- const lockBefore = originals[2].bytes.toString();
80
- const header = /(importers:\r?\n\r?\n \.:\r?\n dependencies:\r?\n)/;
81
- if (!header.test(lockBefore)) throw new Error('未识别 desktop 锁文件结构;备份已保留,未改 profile。');
82
- const lockEntry = ` '${manifest.name}':${eol} specifier: ${localLink}${eol} version: ${localLink}${eol}`;
83
- const escapedPreviousName = (previousName || manifest.name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
84
- const previousLockEntry = new RegExp(`^ (?:'${escapedPreviousName}'|"${escapedPreviousName}"|${escapedPreviousName}):\\r?\\n specifier: [^\\r\\n]+\\r?\\n version: [^\\r\\n]+(?:\\r?\\n|$)`, 'm');
85
- if (previousTarget && !previousLockEntry.test(lockBefore)) throw new Error('未识别旧插件锁文件条目;未改 profile。');
86
- const lockFinal = previousTarget ? lockBefore.replace(previousLockEntry, () => lockEntry) : lockBefore.replace(header, match => match + lockEntry);
87
- const outputs = [Buffer.from(JSON.stringify(config, null, 2) + '\n'), Buffer.from(patch), Buffer.from(lockFinal)];
88
- const receipt = { name: manifest.name, version: manifest.version, target, previousTarget, previousName, previousLinkPath, linkPath, profile, backup, files: originals.map((entry, index) => ({ path: entry.path, backup: resolve(backup, entry.path.split(/[\\/]/).pop()), before: hash(entry.bytes), after: hash(outputs[index]) })), clientHash: hash(await readFile(resolve(target, 'lib/client.js'))) };
89
- await writeFile(resolve(backup, 'installation.json'), JSON.stringify(receipt, null, 2) + '\n');
90
- await copyFile(resolve(source, 'scripts/rollback.mjs'), resolve(backup, 'rollback.mjs'));
91
- try {
92
- for (const entry of receipt.files) if (hash(await readFile(entry.path)) !== entry.before) throw new Error('安装前配置发生更改,停止:' + entry.path);
93
- await mkdir(dirname(linkPath), { recursive: true });
94
- if (previousTarget) await unlink(previousLinkPath);
95
- await symlink(target, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
96
- for (let i = 0; i < originals.length; i++) {
97
- if (hash(await readFile(originals[i].path)) !== receipt.files[i].before) throw new Error('安装期间配置发生更改,停止以保留新内容:' + originals[i].path);
98
- await writeFile(originals[i].path, outputs[i]);
99
- }
100
- } catch (error) {
101
- // 仅恢复仍保持为本次写入内容的文件,避免覆盖同时发生的人工更改。
102
- for (const entry of receipt.files) if (hash(await readFile(entry.path)) === entry.after) await copyFile(entry.backup, entry.path);
103
- const linked = await realpath(linkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
104
- if (linked && resolve(linked) === target) await unlink(linkPath);
105
- if (previousTarget && !(await lstat(previousLinkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; }))) await symlink(previousTarget, previousLinkPath, process.platform === 'win32' ? 'junction' : 'dir');
106
- throw error;
107
- }
108
- const installed = await realpath(linkPath);
109
- if (resolve(installed) !== target) throw new Error('插件链接目标不匹配');
110
- console.log(JSON.stringify({ installed: true, name: manifest.name, version: manifest.version, target, profile, backup, rollback: resolve(backup, 'rollback.mjs'), clientHash: receipt.clientHash, restartRequired: true }, null, 2));
1
+ // 安装到当前用户 desktop profile;只复制本包,旧主题保留以便回退。
2
+ import { readFile, writeFile, mkdir, copyFile, lstat, symlink, realpath, access, unlink } from 'node:fs/promises';
3
+ import { resolve, dirname, relative, isAbsolute } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createHash } from 'node:crypto';
7
+
8
+ const source = resolve(dirname(fileURLToPath(import.meta.url)), '..');
9
+ const dshRoot = resolve(process.env.DSH_HOME || resolve(homedir(), '.dsh'));
10
+ const profile = resolve(dshRoot, 'profiles/desktop');
11
+ const manifest = JSON.parse(await readFile(resolve(source, 'package.json'), 'utf8'));
12
+ const runtime = JSON.parse(await readFile(resolve(dshRoot, 'dsh-runtimes/dsh-primary-runtime/runtime.json'), 'utf8'));
13
+ if (runtime.desktopVersion !== manifest.dsh.engines.dsh) throw new Error(`DSH 版本不匹配:${runtime.desktopVersion}`);
14
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
15
+ const target = resolve(dshRoot, 'dsh-codex-style', manifest.version + '-' + stamp);
16
+ const backup = resolve(dshRoot, 'codex-style-backups', stamp);
17
+ const packagePath = resolve(profile, 'package.json');
18
+ const patchPath = resolve(profile, 'cordis.patch.yml');
19
+ const lockPath = resolve(profile, 'pnpm-lock.yaml');
20
+ const linkPath = resolve(profile, 'node_modules', manifest.name);
21
+ const hash = bytes => createHash('sha256').update(bytes).digest('hex');
22
+ const originals = await Promise.all([packagePath, patchPath, lockPath].map(async path => ({ path, bytes: await readFile(path) })));
23
+ const config = JSON.parse(originals[0].bytes.toString());
24
+ const legacyName = 'dsh-codex-style';
25
+ let previousTarget;
26
+ let previousName;
27
+ let previousLinkPath;
28
+ // 仅迁移本安装器管理的旧包;npm 上的同名第三方包不能按版本号认领。
29
+ for (const name of new Set([manifest.name, legacyName])) {
30
+ const candidatePath = resolve(profile, 'node_modules', name);
31
+ const previousDependency = config.dependencies?.[name];
32
+ const previousLink = await lstat(candidatePath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
33
+ if (!previousDependency && !previousLink) continue;
34
+ if (!previousDependency) throw new Error('node_modules 中已存在同名路径,停止以免覆盖:' + name);
35
+ if (!previousDependency.startsWith('link:') || !previousLink?.isSymbolicLink()) throw new Error('同名安装不是本插件的本地链接,请人工核对。');
36
+ const candidateTarget = await realpath(candidatePath);
37
+ const managedRelative = relative(resolve(dshRoot, 'dsh-codex-style'), candidateTarget);
38
+ if (!managedRelative || managedRelative.startsWith('..') || isAbsolute(managedRelative) || resolve(profile, previousDependency.slice(5)) !== resolve(candidateTarget)) throw new Error('现有插件链接不在受管理目录,停止升级。');
39
+ const previousManifest = JSON.parse(await readFile(resolve(candidateTarget, 'package.json'), 'utf8'));
40
+ if (previousManifest.name !== name) throw new Error('旧插件身份不匹配');
41
+ if (previousTarget) throw new Error('新旧包同时存在,请先核对安装来源。');
42
+ previousTarget = candidateTarget;
43
+ previousName = name;
44
+ previousLinkPath = candidatePath;
45
+ }
46
+ await access(resolve(source, 'lib/client.js'));
47
+ await access(resolve(source, 'lib/host.js'));
48
+ await mkdir(backup, { recursive: true });
49
+ for (const entry of originals) await writeFile(resolve(backup, entry.path.split(/[\\/]/).pop()), entry.bytes);
50
+ await mkdir(resolve(target, 'lib'), { recursive: true });
51
+ await mkdir(resolve(target, 'licenses'), { recursive: true });
52
+ for (const file of ['package.json', 'cordis.patch.yml', 'skin.json', 'README.md', 'LICENSE', 'CHANGELOG.md', 'THIRD_PARTY_NOTICES.md', 'licenses/DeepSeek-Harness-LICENSE', 'lib/client.js', 'lib/host.js']) await copyFile(resolve(source, file), resolve(target, file));
53
+
54
+ const localLink = 'link:' + relative(profile, target).replaceAll('\\', '/');
55
+ if (previousName && previousName !== manifest.name) delete config.dependencies[previousName];
56
+ config.dependencies[manifest.name] = localLink;
57
+ const oldSkins = new Set(['dsh-claude-style', '@michengai/dsh-codex-ui']);
58
+ config.dsh.profile.bundles = config.dsh.profile.bundles.filter(name => !oldSkins.has(name) && name !== manifest.name && name !== previousName);
59
+ config.dsh.profile.bundles.push(manifest.name);
60
+ const eol = originals[1].bytes.includes(Buffer.from('\r\n')) ? '\r\n' : '\n';
61
+ const patchBefore = originals[1].bytes.toString();
62
+ const ownBlock = /^- id: ui-skin-codex-style\r?\n(?:[ \t]+[^\r\n]*(?:\r?\n|$))*/gm;
63
+ const ownBlocks = [...patchBefore.matchAll(ownBlock)];
64
+ if (ownBlocks.length > 1 || (previousTarget && ownBlocks.length !== 1)) throw new Error('未识别唯一的皮肤配置行;未改 profile。');
65
+ const patch = ownBlocks.length ? patchBefore.replace(ownBlock, block => {
66
+ const nameLine = /^ name: (.+)$/m;
67
+ const oldName = block.match(nameLine)?.[1].trim().replace(/^(['"])(.*)\1$/, '$2');
68
+ if (oldName !== manifest.name && oldName !== previousName) throw new Error('皮肤配置指向其他包,未改 profile。');
69
+ return block.replace(nameLine, () => ' name: ' + JSON.stringify(manifest.name));
70
+ }) : patchBefore + eol + [
71
+ '# Codex Style:旧主题依赖保留,仅停用其界面行。',
72
+ '- id: ui-skin-claude-style',
73
+ ' disabled: true',
74
+ '- id: ui-skin-codex-style',
75
+ ' name: ' + JSON.stringify(manifest.name),
76
+ ' disabled: false',
77
+ ''
78
+ ].join(eol);
79
+ const lockBefore = originals[2].bytes.toString();
80
+ const header = /(importers:\r?\n\r?\n \.:\r?\n dependencies:\r?\n)/;
81
+ if (!header.test(lockBefore)) throw new Error('未识别 desktop 锁文件结构;备份已保留,未改 profile。');
82
+ const lockEntry = ` '${manifest.name}':${eol} specifier: ${localLink}${eol} version: ${localLink}${eol}`;
83
+ const escapedPreviousName = (previousName || manifest.name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
84
+ const previousLockEntry = new RegExp(`^ (?:'${escapedPreviousName}'|"${escapedPreviousName}"|${escapedPreviousName}):\\r?\\n specifier: [^\\r\\n]+\\r?\\n version: [^\\r\\n]+(?:\\r?\\n|$)`, 'm');
85
+ if (previousTarget && !previousLockEntry.test(lockBefore)) throw new Error('未识别旧插件锁文件条目;未改 profile。');
86
+ const lockFinal = previousTarget ? lockBefore.replace(previousLockEntry, () => lockEntry) : lockBefore.replace(header, match => match + lockEntry);
87
+ const outputs = [Buffer.from(JSON.stringify(config, null, 2) + '\n'), Buffer.from(patch), Buffer.from(lockFinal)];
88
+ const receipt = { name: manifest.name, version: manifest.version, target, previousTarget, previousName, previousLinkPath, linkPath, profile, backup, files: originals.map((entry, index) => ({ path: entry.path, backup: resolve(backup, entry.path.split(/[\\/]/).pop()), before: hash(entry.bytes), after: hash(outputs[index]) })), clientHash: hash(await readFile(resolve(target, 'lib/client.js'))) };
89
+ await writeFile(resolve(backup, 'installation.json'), JSON.stringify(receipt, null, 2) + '\n');
90
+ await copyFile(resolve(source, 'scripts/rollback.mjs'), resolve(backup, 'rollback.mjs'));
91
+ try {
92
+ for (const entry of receipt.files) if (hash(await readFile(entry.path)) !== entry.before) throw new Error('安装前配置发生更改,停止:' + entry.path);
93
+ await mkdir(dirname(linkPath), { recursive: true });
94
+ if (previousTarget) await unlink(previousLinkPath);
95
+ await symlink(target, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
96
+ for (let i = 0; i < originals.length; i++) {
97
+ if (hash(await readFile(originals[i].path)) !== receipt.files[i].before) throw new Error('安装期间配置发生更改,停止以保留新内容:' + originals[i].path);
98
+ await writeFile(originals[i].path, outputs[i]);
99
+ }
100
+ } catch (error) {
101
+ // 仅恢复仍保持为本次写入内容的文件,避免覆盖同时发生的人工更改。
102
+ for (const entry of receipt.files) if (hash(await readFile(entry.path)) === entry.after) await copyFile(entry.backup, entry.path);
103
+ const linked = await realpath(linkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; });
104
+ if (linked && resolve(linked) === target) await unlink(linkPath);
105
+ if (previousTarget && !(await lstat(previousLinkPath).catch(e => { if (e.code === 'ENOENT') return null; throw e; }))) await symlink(previousTarget, previousLinkPath, process.platform === 'win32' ? 'junction' : 'dir');
106
+ throw error;
107
+ }
108
+ const installed = await realpath(linkPath);
109
+ if (resolve(installed) !== target) throw new Error('插件链接目标不匹配');
110
+ console.log(JSON.stringify({ installed: true, name: manifest.name, version: manifest.version, target, profile, backup, rollback: resolve(backup, 'rollback.mjs'), clientHash: receipt.clientHash, restartRequired: true }, null, 2));
package/skin.json CHANGED
@@ -1,11 +1,11 @@
1
- {
2
- "id": "codex-style",
3
- "name": "Codex Style",
4
- "tagline": "为 DSH 提供 Codex 风格的深色界面",
5
- "package": "@xinxing1708/dsh-codex-style",
6
- "bodyAttr": "data-dsh-codex-style",
7
- "accent": "#0285ff",
8
- "wiring": {
9
- "id": "ui-skin-codex-style"
10
- }
11
- }
1
+ {
2
+ "id": "codex-style",
3
+ "name": "Codex Style",
4
+ "tagline": "为 DSH 提供 Codex 风格的深色界面",
5
+ "package": "@xinxing1708/dsh-codex-style",
6
+ "bodyAttr": "data-dsh-codex-style",
7
+ "accent": "#0285ff",
8
+ "wiring": {
9
+ "id": "ui-skin-codex-style"
10
+ }
11
+ }