@michengai/dsh-codex-ui 0.2.91 → 0.2.93

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
@@ -4,6 +4,29 @@
4
4
 
5
5
  This changelog records recent releases of DSH Codex UI and its one-click installer. Earlier changes remain available in the [Git history](https://github.com/MichengAI/dsh-codex-ui/commits/main).
6
6
 
7
+ ## 0.2.93 — 2026-08-30
8
+
9
+ ### Compatibility
10
+
11
+ - Resolved the active DSH Desktop profile through the public `desktopProfiles` service and delegated installs and updates to the public `desktopPnpm` package manager instead of hard-coding the `web` profile.
12
+ - Preserved the ordinary Web/CLI profile fallback while recognizing Desktop-provided runtime and companion-package state across restarts.
13
+ - Routed new-session actions through Archive Manager's optional `uiWorkspace.startSession()` service, with the standard workspace service retained as the fallback.
14
+
15
+ ### Safety
16
+
17
+ - Removed the dependency on the launcher-private `desktopPnpmBootstrap` implementation detail.
18
+ - Failed safely when a Desktop generation exposes incomplete public services instead of falling back to an ambient CLI that could mutate another profile.
19
+ - Reported the running Desktop DSH runtime as installed without fabricating a version or update when the Host exposes no supported runtime path.
20
+
21
+ ## 0.2.92 — 2026-08-28
22
+
23
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.9`.
24
+
25
+ ### Security
26
+
27
+ - Updated the legacy Suite and its installer manifest to `@michengai/dsh-im-connect@0.1.26`.
28
+ - Pinned the workspace dependency graph to the compatible patched `ansi-regex@5.0.1`, resolving CVE-2021-3807 in both the Suite and test dependency paths.
29
+
7
30
  ## 0.2.91 — 2026-08-28
8
31
 
9
32
  ### Release process
@@ -4,6 +4,29 @@
4
4
 
5
5
  本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
6
6
 
7
+ ## 0.2.93 — 2026-08-30
8
+
9
+ ### 兼容性
10
+
11
+ - 通过公开的 `desktopProfiles` service 读取 DSH Desktop 当前 Profile,并把安装与更新交给公开的 `desktopPnpm` 包管理服务,不再硬编码 `web` Profile。
12
+ - 保留普通 Web/CLI 的 Profile 回退,同时在 Desktop 重启前后正确识别宿主 Runtime 与配套插件状态。
13
+ - 新建会话优先调用 Archive Manager 可选提供的 `uiWorkspace.startSession()` service,缺失时继续使用标准工作区服务。
14
+
15
+ ### 安全性
16
+
17
+ - 移除对 Launcher 私有实现细节 `desktopPnpmBootstrap` 的依赖。
18
+ - Desktop generation 的公开服务不完整时安全失败,不再回退到可能修改其他 Profile 的环境 CLI。
19
+ - 宿主没有通过受支持路径暴露 Runtime 目录时,只确认正在运行的 Desktop DSH Runtime 已安装,不伪造版本或升级状态。
20
+
21
+ ## 0.2.92 — 2026-08-28
22
+
23
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.9`。
24
+
25
+ ### 安全修复
26
+
27
+ - 旧版 Suite 及其安装器清单更新为 `@michengai/dsh-im-connect@0.1.26`。
28
+ - 工作区依赖图固定使用兼容的修复版 `ansi-regex@5.0.1`,消除 Suite 与测试依赖路径中的 CVE-2021-3807。
29
+
7
30
  ## 0.2.91 — 2026-08-28
8
31
 
9
32
  ### 发布流程
package/README.md CHANGED
@@ -53,49 +53,23 @@ Conversation menu: rename, pin, unread, archive, fork, copy, and delete.
53
53
  - Examples use the `web` profile; replace it with the target profile.
54
54
  - Source installation and development require Node.js 22+ and pnpm. Installing from npm does not require running `pnpm install` in an arbitrary directory.
55
55
 
56
- ## Plugin combo
57
-
58
- `@michengai/dsh-codex-suite-installer` is the lightweight one-click installer for the `@michengai/dsh-codex-suite` member set. It adds these six plugins to the same profile as **direct dependencies**:
59
-
60
- | Plugin | npm package | Role |
61
- | --- | --- | --- |
62
- | Codex UI | `@michengai/dsh-codex-ui` | Codex-style sidebar, workspace tree, search, and turn navigation |
63
- | Expert management | `@michengai/dsh-agency-agents` | Settings page opened from **Experts** |
64
- | Skill management | `@michengai/dsh-skills-manager` | Settings page opened from **Skills** |
65
- | Archive management | `@michengai/dsh-archive-manager` | Permanent deletion and archived-session management |
66
- | IM Assistant | `@michengai/dsh-im-connect` | IM settings and the **Channels** tab |
67
- | Scheduled tasks | `@michengai/dsh-automation` | Scheduled-task settings and the **Schedule** tab |
68
-
69
- `dshmarket` stays optional. When it is installed, **Plugins** opens the market first.
70
-
71
- Direct dependencies let **Settings → About** detect and update every plugin independently. The lightweight installer carries only the version manifest, and it migrates the legacy aggregate Suite without leaving duplicate patches behind.
72
-
73
56
  ## DSH product ecosystem
74
57
 
75
- Codex UI can be installed independently or used through the desktop app or Web suite. They share the same DSH core but serve different ways of working:
58
+ Codex UI can be installed independently or used through the desktop app. They share the same DSH core but serve different ways of working:
76
59
 
77
60
  | Product | Relationship to Codex UI |
78
61
  | --- | --- |
79
62
  | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | The host runtime that provides models, sessions, tools, and the plugin system |
80
63
  | [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop) | A ready-to-install desktop product with Codex UI and the other five feature products built in |
81
- | [DSH Codex Suite](https://github.com/MichengAI/dsh-codex-ui/tree/main/packages/dsh-codex-suite) | A one-click suite for existing DSH Web environments that installs Codex UI and the other five feature products |
82
64
  | Six feature products | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) · [IM Connect](https://github.com/MichengAI/dsh-im-connect) · [Automation](https://github.com/MichengAI/dsh-automation) · [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) · [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) · [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) |
83
65
 
84
66
  ## Installation
85
67
 
86
68
  `dsh plugin add` forwards to `pnpm add` in the profile directory. Without a version and official registry, a local mirror or minimum-release-age policy can leave you on an older build.
87
69
 
88
- ### Ask another agent to install the suite
89
-
90
- Copy one of the sentences below into DSH, Codex, or WorkBuddy and let that agent install the combo into your local `web` profile.
70
+ ### Ask another agent to install Codex UI
91
71
 
92
- From npm:
93
-
94
- ```text
95
- Use the one-click installer to add all six DSH Codex Suite members as direct dependencies of my local web profile: npx --yes @michengai/dsh-codex-suite-installer@latest --profile web. Confirm its configuration check succeeds, then remind me to restart DSH Web and hard-refresh the browser.
96
- ```
97
-
98
- UI only:
72
+ Copy the sentence below into DSH, Codex, or WorkBuddy and let that agent install Codex UI into your local `web` profile.
99
73
 
100
74
  ```text
101
75
  Install the latest DSH plugin @michengai/dsh-codex-ui into my local web profile using the official npm registry: dsh plugin --profile web add @michengai/dsh-codex-ui@latest --registry=https://registry.npmjs.org/. Then run dsh --profile web --dump-config, confirm codex-ui is mounted, and remind me to restart DSH Web and hard-refresh the browser.
@@ -103,33 +77,9 @@ Install the latest DSH plugin @michengai/dsh-codex-ui into my local web profile
103
77
 
104
78
  | Product | How to use it |
105
79
  | --- | --- |
106
- | DSH | Send one of the sentences above to the current session. |
107
- | Codex | Send one of the sentences above to Codex and let it install locally. |
108
- | WorkBuddy | Send one of the sentences above to WorkBuddy. |
109
-
110
- The installer needs the current `dsh` on PATH. Set `DSH_BIN` when the executable lives elsewhere.
111
-
112
- ### Install the complete suite
113
-
114
- This is the one-click combo. Run it from any PowerShell directory:
115
-
116
- ```powershell
117
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
118
- $OutputEncoding = [System.Text.Encoding]::UTF8
119
- npx --yes @michengai/dsh-codex-suite-installer@latest --profile web
120
- ```
121
-
122
- The installer reads its exact verified member versions, adds all six members as direct profile dependencies in one `dsh plugin add`, and finishes with `dsh --profile web --dump-config`. Replace `@latest` with an installer version to pin the whole set.
123
-
124
- Restart DSH Web and hard-refresh the browser. Existing members are aligned in place; a legacy `@michengai/dsh-codex-suite` aggregate dependency is removed only after all six direct members have been staged.
125
-
126
- Use the same command with another profile name for a clean custom Web profile:
127
-
128
- ```powershell
129
- npx --yes @michengai/dsh-codex-suite-installer@latest --profile codex
130
- ```
131
-
132
- The installer stays in the current `DSH_HOME` and places DSH's built-in `@deepseek-ai/dsh-web-app` before the member bundles. It neither creates a separate Home nor reinstalls the official Web package.
80
+ | DSH | Send the sentence above to the current session. |
81
+ | Codex | Send the sentence above to Codex and let it install locally. |
82
+ | WorkBuddy | Send the sentence above to WorkBuddy. |
133
83
 
134
84
  ### Install Codex UI only
135
85
 
@@ -173,7 +123,7 @@ Open DSH Web. The left navigation is rendered by this plugin.
173
123
  | Manage a conversation | Open the conversation menu to rename, pin, mark unread, archive, fork, copy, or delete. |
174
124
  | Jump between turns | Use the turn marks on the left of the current conversation. |
175
125
  | Inspect connectors | Open **Settings → Connectors**. Addresses, commands, and credentials are never shown. |
176
- | Check companion plugins | Open **Settings → About** to install or update the combo plugins. |
126
+ | Check companion plugins | Open **Settings → About** to install or update individual companion plugins. |
177
127
 
178
128
  Deleting a workspace registration does not delete its folder or conversation records. Pinned and unread state is stored only in the current browser.
179
129
 
package/README.zh-CN.md CHANGED
@@ -53,49 +53,23 @@
53
53
  - 以下示例使用 `web` profile;请替换为实际目标 profile。
54
54
  - 从源码安装或二次开发需要 Node.js 22+ 与 pnpm;仅从 npm 安装无需在任意目录执行 `pnpm install`。
55
55
 
56
- ## 插件组合
57
-
58
- `@michengai/dsh-codex-suite-installer` 是 `@michengai/dsh-codex-suite` 成员组合的轻量一键安装器,会把下面 6 个插件作为**直接依赖**装进同一个 profile:
59
-
60
- | 插件 | npm 包 | 作用 |
61
- | --- | --- | --- |
62
- | Codex UI | `@michengai/dsh-codex-ui` | Codex 风格侧栏、工作区会话树、搜索和轮次导航 |
63
- | 专家管理 | `@michengai/dsh-agency-agents` | 从「专家」进入对应设置页 |
64
- | 技能管理 | `@michengai/dsh-skills-manager` | 从「技能」进入对应设置页 |
65
- | 归档管理 | `@michengai/dsh-archive-manager` | 提供永久删除和已归档会话管理 |
66
- | IM 助理 | `@michengai/dsh-im-connect` | IM 设置页和「频道」页签 |
67
- | 定时任务 | `@michengai/dsh-automation` | 定时任务设置页和「定时」页签 |
68
-
69
- `dshmarket` 仍是可选项。已安装时,「插件」入口会优先进入市场。
70
-
71
- 直接依赖让「设置 → 关于」能够分别检测和升级每个插件。轻量安装器只携带版本清单,也会自动迁移旧版聚合 Suite,避免聚合 patch 与独立 patch 冲突。
72
-
73
56
  ## DSH 产品生态
74
57
 
75
- Codex UI 既可以独立安装,也可以随桌面端或 Web 套件一起使用。它们共享同一个 DSH 核心,但面向不同的使用方式:
58
+ Codex UI 既可以独立安装,也可以随桌面端一起使用。它们共享同一个 DSH 核心,但面向不同的使用方式:
76
59
 
77
60
  | 产品 | 与 Codex UI 的关系 |
78
61
  | --- | --- |
79
62
  | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | Codex UI 的运行宿主,提供模型、会话、工具和插件系统 |
80
63
  | [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop) | 下载安装即用的桌面产品,已内置 Codex UI 和其他 5 个功能产品 |
81
- | [DSH Codex Suite](https://github.com/MichengAI/dsh-codex-ui/tree/main/packages/dsh-codex-suite) | 面向已有 DSH Web 环境的一键套件,会安装 Codex UI 和其他 5 个功能产品 |
82
64
  | 6 个功能产品 | [Codex UI](https://github.com/MichengAI/dsh-codex-ui) · [IM Connect](https://github.com/MichengAI/dsh-im-connect) · [Automation](https://github.com/MichengAI/dsh-automation) · [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) · [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) · [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) |
83
65
 
84
66
  ## 安装
85
67
 
86
68
  `dsh plugin add` 会转发到 profile 目录里的 `pnpm add`。不写版本、不指定官方源时,本机镜像和最短发布间隔可能让你停在旧版。
87
69
 
88
- ### 交给其他 Agent 一句话安装套件
89
-
90
- 把下面其中一句复制到 DSH、Codex 或 WorkBuddy,让它代你安装到本机 `web` profile。
70
+ ### 交给其他 Agent 一句话安装 Codex UI
91
71
 
92
- npm 安装完整套件:
93
-
94
- ```text
95
- 请用一键安装器把 DSH Codex Suite 的六个成员作为直接依赖装进本机 web profile,执行:npx --yes @michengai/dsh-codex-suite-installer@latest --profile web。确认安装器完成配置检查,并提醒我重启 DSH Web 后硬刷新浏览器。
96
- ```
97
-
98
- 只安装 Codex UI:
72
+ 把下面这句话复制到 DSH、Codex 或 WorkBuddy,让它代你安装到本机 `web` profile。
99
73
 
100
74
  ```text
101
75
  请把 DSH 插件 @michengai/dsh-codex-ui 最新版装进本机 web profile,使用官方 npm 源执行:dsh plugin --profile web add @michengai/dsh-codex-ui@latest --registry=https://registry.npmjs.org/。装完执行 dsh --profile web --dump-config,确认已挂载 codex-ui,并提醒我重启 DSH Web 后硬刷新浏览器。
@@ -103,33 +77,9 @@ Codex UI 既可以独立安装,也可以随桌面端或 Web 套件一起使用
103
77
 
104
78
  | 产品 | 怎么用 |
105
79
  | --- | --- |
106
- | DSH | 把上面其中一句发给当前会话。 |
107
- | Codex | 把上面其中一句发给 Codex,让它在本机执行安装。 |
108
- | WorkBuddy | 把上面其中一句发给 WorkBuddy。 |
109
-
110
- 一键安装器需要能从 PATH 找到当前 DSH;也可以用 `DSH_BIN` 指定 `dsh` 可执行文件。
111
-
112
- ### 安装完整套件
113
-
114
- 这是一键组合安装。在任意 PowerShell 目录执行:
115
-
116
- ```powershell
117
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
118
- $OutputEncoding = [System.Text.Encoding]::UTF8
119
- npx --yes @michengai/dsh-codex-suite-installer@latest --profile web
120
- ```
121
-
122
- 安装器会读取自身锁定的精确成员版本,用一次 `dsh plugin add` 将六个成员写成 profile 的直接依赖,并在最后运行 `dsh --profile web --dump-config`。需要钉死一组版本时,把 `@latest` 换成具体安装器版本。
123
-
124
- 安装后重启 DSH Web 并硬刷新浏览器。已有成员会原地对齐版本;旧版 `@michengai/dsh-codex-suite` 聚合依赖会在六个成员提升完成后自动移除。
125
-
126
- 新建自定义 Web profile 也使用同一命令,只需改 profile 名:
127
-
128
- ```powershell
129
- npx --yes @michengai/dsh-codex-suite-installer@latest --profile codex
130
- ```
131
-
132
- 安装器会复用当前 `DSH_HOME`,并把 DSH 自带的 `@deepseek-ai/dsh-web-app` 放到成员 bundle 之前;不会创建独立 Home,也不会重新安装官方 Web 包。
80
+ | DSH | 把上面这句话发给当前会话。 |
81
+ | Codex | 把上面这句话发给 Codex,让它在本机执行安装。 |
82
+ | WorkBuddy | 把上面这句话发给 WorkBuddy。 |
133
83
 
134
84
  ### 只安装 Codex UI
135
85
 
@@ -173,7 +123,7 @@ dsh --profile web --dump-config
173
123
  | 管理会话 | 打开会话菜单,进行重命名、置顶、未读、归档、派生、复制或删除。 |
174
124
  | 跳转轮次 | 使用当前会话左侧的轮次刻度跳转到对应提问。 |
175
125
  | 查看连接器 | 打开「设置 → 连接器」。不会展示地址、命令或凭证。 |
176
- | 查看配套插件 | 打开「设置 → 关于」,安装或更新套件中的配套插件。 |
126
+ | 查看配套插件 | 打开「设置 → 关于」,安装或更新各个配套插件。 |
177
127
 
178
128
  删除项目注册不会删除项目目录或会话记录。置顶和未读状态只保存在当前浏览器。
179
129
 
package/dist/client.js CHANGED
@@ -5445,6 +5445,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5445
5445
  function hasDeleteSession(value) {
5446
5446
  return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
5447
5447
  }
5448
+ function hasStartSession(value) {
5449
+ return value !== null && typeof value === "object" && "startSession" in value && typeof value.startSession === "function";
5450
+ }
5451
+ /** Archive Manager replaces the official ui-workspace row with this optional service. */
5452
+ function startWorkspaceSession(ctx, workspaceId) {
5453
+ const uiWorkspace = ctx.get("uiWorkspace");
5454
+ if (hasStartSession(uiWorkspace)) {
5455
+ uiWorkspace.startSession(workspaceId);
5456
+ return;
5457
+ }
5458
+ ctx.workspaces.startSession(workspaceId);
5459
+ }
5448
5460
  /** 替换 DSH 的官方 sidebar 插槽,不修改 DSH 源码或会话数据。 */
5449
5461
  function apply(ctx) {
5450
5462
  ctx.effect(() => ctx.locale.register(NS, {
@@ -5490,7 +5502,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5490
5502
  ctx.sessions.open(sessionId);
5491
5503
  },
5492
5504
  startSession: (workspaceId) => {
5493
- ctx.workspaces.startSession(workspaceId);
5505
+ startWorkspaceSession(ctx, workspaceId);
5494
5506
  },
5495
5507
  toggleSidebar: () => {
5496
5508
  ctx.layout.toggleSidebar();
@@ -5561,7 +5573,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5561
5573
  insertWorkspaceBefore: (workspaceId, beforeWorkspaceId) => ctx.workspaces.insertBefore(workspaceId, beforeWorkspaceId),
5562
5574
  insertSessionBefore: (workspaceId, sessionId, beforeSessionId) => ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId),
5563
5575
  startSession: (workspaceId) => {
5564
- ctx.workspaces.startSession(workspaceId);
5576
+ startWorkspaceSession(ctx, workspaceId);
5565
5577
  }
5566
5578
  })
5567
5579
  }, CodexWorkspaceBrowser));
@@ -5601,6 +5613,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5601
5613
  //#endregion
5602
5614
  exports.apply = apply;
5603
5615
  exports.inject = inject;
5616
+ exports.startWorkspaceSession = startWorkspaceSession;
5604
5617
  return module.exports;
5605
5618
  }
5606
5619
  });
package/dist/index.mjs CHANGED
@@ -56,13 +56,63 @@ function managedDependency(id) {
56
56
  const PROFILE_PENDING_UPDATES_FILE = ".dsh-pending-updates.json";
57
57
  const APPLY_PLUGIN_UPDATES_IPC = "apply-plugin-updates";
58
58
  const PLUGIN_INSTALL_TIMEOUT_MS = 6e5;
59
- function profileDirectory() {
60
- if (process.env.DSH_PROFILE_DIR !== void 0) return process.env.DSH_PROFILE_DIR;
61
- return resolve(homedir(), ".dsh", "profiles", "web");
59
+ function optionalService(ctx, name) {
60
+ if (ctx === void 0) return void 0;
61
+ return typeof ctx.get === "function" ? ctx.get(name) : ctx[name];
62
62
  }
63
- async function declaredPluginNames() {
63
+ function validProfileName(value) {
64
+ return typeof value === "string" && value !== "" && value !== "." && value !== ".." && value !== "node_modules" && !value.includes("/") && !value.includes("\\") && !/[\0-\x1f\x7f]/.test(value);
65
+ }
66
+ function profileNameFromArgv(argv) {
67
+ for (let index = 2; index < argv.length; index += 1) {
68
+ const argument = argv[index];
69
+ if (argument === "--profile") return argv[index + 1];
70
+ if (argument?.startsWith("--profile=")) return argument.slice(10);
71
+ }
72
+ return argv[2] === "web" ? "web" : void 0;
73
+ }
74
+ /**
75
+ * Desktop 通过可选 Host service 暴露当前 profile;普通 Web/CLI 继续使用
76
+ * DSH_PROFILE_DIR、命令行 profile 与默认 web profile。
77
+ */
78
+ function resolveDependencyRuntime(ctx, options = {}) {
79
+ const env = options.env ?? process.env;
80
+ const argv = options.argv ?? process.argv;
81
+ const cwd = options.cwd ?? process.cwd();
82
+ const home = options.homeDir ?? homedir();
83
+ const profiles = optionalService(ctx, "desktopProfiles");
84
+ const desktopPnpm = optionalService(ctx, "desktopPnpm");
85
+ if (profiles !== void 0) {
86
+ const current = profiles.current;
87
+ if (!validProfileName(current?.name) || typeof current.dir !== "string" || !isAbsolute(current.dir)) throw new Error("DSH Desktop 当前 Profile 信息无效,请重启 Desktop 后重试。");
88
+ if (typeof desktopPnpm?.runPlugin !== "function") throw new Error("DSH Desktop 包管理服务尚未就绪,请重启 Desktop 后重试。");
89
+ const profileDir = resolve(current.dir);
90
+ const runtimeRoots = typeof env.DSH_RUNTIME_DIR === "string" && isAbsolute(env.DSH_RUNTIME_DIR) ? [resolve(env.DSH_RUNTIME_DIR)] : [];
91
+ return {
92
+ environmentKind: "desktop",
93
+ profileName: current.name,
94
+ profileDir,
95
+ runtimeRoots,
96
+ desktopPnpm
97
+ };
98
+ }
99
+ const profileDir = resolve(env.DSH_PROFILE_DIR ?? resolve(home, ".dsh", "profiles", "web"));
100
+ const selected = profileNameFromArgv(argv);
101
+ const profileName = validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
102
+ const cliRuntimeRoot = argv[1] === void 0 ? void 0 : resolveDshRuntimeRoot(argv[1], cwd);
103
+ return {
104
+ environmentKind: "cli",
105
+ profileName,
106
+ profileDir,
107
+ runtimeRoots: [env.DSH_RUNTIME_DIR, cliRuntimeRoot].filter((root) => root !== void 0 && root !== "")
108
+ };
109
+ }
110
+ function profileDirectory(runtime) {
111
+ return runtime.profileDir;
112
+ }
113
+ async function declaredPluginNames(runtime) {
64
114
  try {
65
- const manifest = JSON.parse(await readFile(resolve(profileDirectory(), "package.json"), "utf8"));
115
+ const manifest = JSON.parse(await readFile(resolve(profileDirectory(runtime), "package.json"), "utf8"));
66
116
  return [.../* @__PURE__ */ new Set([...Object.keys({
67
117
  ...manifest.devDependencies,
68
118
  ...manifest.dependencies
@@ -101,17 +151,12 @@ function resolveDshRuntimeRoot(entry = process.argv[1], cwd = process.cwd()) {
101
151
  const root = cliEntry.slice(0, index);
102
152
  return root.endsWith(sep) ? root.slice(0, -1) : root;
103
153
  }
104
- function packageLookupRoots(packageName) {
105
- if (!isOfficialRuntimePackage(packageName)) return [profileDirectory()];
106
- const runtimeDir = process.env.DSH_RUNTIME_DIR;
107
- return [...new Set([
108
- runtimeDir,
109
- resolveDshRuntimeRoot(),
110
- profileDirectory()
111
- ].filter((root) => root !== void 0 && root !== ""))];
154
+ function packageLookupRoots(packageName, runtime) {
155
+ if (!isOfficialRuntimePackage(packageName)) return [profileDirectory(runtime)];
156
+ return [.../* @__PURE__ */ new Set([...runtime.runtimeRoots, profileDirectory(runtime)])];
112
157
  }
113
- async function installedPackageVersion(packageName) {
114
- for (const root of packageLookupRoots(packageName)) try {
158
+ async function installedPackageVersion(packageName, runtime) {
159
+ for (const root of packageLookupRoots(packageName, runtime)) try {
115
160
  const manifest = JSON.parse(await readFile(resolve(root, "node_modules", ...packageName.split("/"), "package.json"), "utf8"));
116
161
  if (typeof manifest.version === "string" && manifest.version !== "") return manifest.version;
117
162
  } catch (error) {
@@ -183,9 +228,9 @@ function applyReleaseExclude(source, packageName, version) {
183
228
  return `${source}${source === "" || source.endsWith("\n") ? "" : eol}minimumReleaseAgeExclude:${eol}${entry}${eol}`;
184
229
  }
185
230
  /** 将用户本次确认的精确版本加入 Profile 的 pnpm 发布时间保护例外。 */
186
- async function ensureLatestReleaseAllowed(packageName, version) {
231
+ async function ensureLatestReleaseAllowed(packageName, version, runtime) {
187
232
  if (parseSemver(version) === void 0) throw new Error("npm 返回了无法识别的最新版本。");
188
- const path = resolve(profileDirectory(), "pnpm-workspace.yaml");
233
+ const path = resolve(profileDirectory(runtime), "pnpm-workspace.yaml");
189
234
  let source;
190
235
  try {
191
236
  source = await readFile(path, "utf8");
@@ -231,11 +276,16 @@ function newerVersion(installed, latest) {
231
276
  for (let index = 0; index < current.core.length; index += 1) if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index];
232
277
  return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
233
278
  }
234
- /** 返回 Web profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
235
- async function dependencyStatuses() {
236
- const declaredNames = await declaredPluginNames();
279
+ /** 返回当前 profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
280
+ async function dependencyStatuses(runtime = resolveDependencyRuntime()) {
281
+ const declaredNames = await declaredPluginNames(runtime);
237
282
  return Promise.all(MANAGED_DEPENDENCIES.map(async (dependency) => {
238
- const version = await installedPackageVersion(dependency.packageName);
283
+ const version = await installedPackageVersion(dependency.packageName, runtime);
284
+ if (version === void 0 && runtime.environmentKind === "desktop" && isOfficialRuntimePackage(dependency.packageName)) return {
285
+ ...dependency,
286
+ installed: true,
287
+ updateAvailable: false
288
+ };
239
289
  const declared = isOfficialRuntimePackage(dependency.packageName) || isManagedPackageDeclared(dependency.packageName, declaredNames);
240
290
  if (version === void 0 || !isManagedPackageInstalled({
241
291
  installedVersion: version,
@@ -273,8 +323,8 @@ function isRestartableInstallError(error) {
273
323
  const message = error instanceof Error ? error.message : String(error);
274
324
  return /完全退出桌面端|正在运行的插件|pnpm 仓库不一致/.test(message);
275
325
  }
276
- async function recordPendingUpdate(packageName, version) {
277
- const pendingPath = resolve(profileDirectory(), PROFILE_PENDING_UPDATES_FILE);
326
+ async function recordPendingUpdate(packageName, version, runtime) {
327
+ const pendingPath = resolve(profileDirectory(runtime), PROFILE_PENDING_UPDATES_FILE);
278
328
  let packages = [];
279
329
  try {
280
330
  const parsed = JSON.parse(await readFile(pendingPath, "utf8"));
@@ -297,8 +347,8 @@ async function recordPendingUpdate(packageName, version) {
297
347
  });
298
348
  await writeFile(pendingPath, `${JSON.stringify({ packages }, void 0, 2)}\n`, "utf8");
299
349
  }
300
- async function removePendingUpdate(packageName) {
301
- const pendingPath = resolve(profileDirectory(), PROFILE_PENDING_UPDATES_FILE);
350
+ async function removePendingUpdate(packageName, runtime) {
351
+ const pendingPath = resolve(profileDirectory(runtime), PROFILE_PENDING_UPDATES_FILE);
302
352
  try {
303
353
  const parsed = JSON.parse(await readFile(pendingPath, "utf8"));
304
354
  if (!Array.isArray(parsed.packages)) return;
@@ -313,8 +363,8 @@ async function removePendingUpdate(packageName) {
313
363
  if (error.code !== "ENOENT") throw error;
314
364
  }
315
365
  }
316
- async function recordDeclaredVersion(packageName, version) {
317
- const manifestPath = resolve(profileDirectory(), "package.json");
366
+ async function recordDeclaredVersion(packageName, version, runtime) {
367
+ const manifestPath = resolve(profileDirectory(runtime), "package.json");
318
368
  let manifest;
319
369
  try {
320
370
  manifest = JSON.parse(await readFile(manifestPath, "utf8"));
@@ -344,6 +394,7 @@ function pluginExecArgv(args = process.execArgv) {
344
394
  return args.filter((arg) => !/^--(?:inspect|inspect-brk|debug|debug-brk)(?:=|$)/.test(arg));
345
395
  }
346
396
  const activePluginChildren = /* @__PURE__ */ new Set();
397
+ const activeDesktopPluginHandles = /* @__PURE__ */ new Set();
347
398
  function terminatePluginChild(child) {
348
399
  if (child.exitCode !== null || child.killed) return;
349
400
  if (process.platform === "win32" && child.pid !== void 0) {
@@ -367,6 +418,7 @@ function terminatePluginChild(child) {
367
418
  /** 插件停用时终止仍在运行的安装进程,避免热更新后遗留 pnpm。 */
368
419
  function disposeDependencyInstaller() {
369
420
  for (const child of activePluginChildren) terminatePluginChild(child);
421
+ for (const handle of activeDesktopPluginHandles) handle.cancel();
370
422
  }
371
423
  function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
372
424
  return new Promise((resolvePromise, reject) => {
@@ -398,14 +450,52 @@ function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
398
450
  timeout.unref?.();
399
451
  });
400
452
  }
401
- function runDshPlugin(args, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
453
+ function monitorDesktopPlugin(handle, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
454
+ return new Promise((resolvePromise, reject) => {
455
+ activeDesktopPluginHandles.add(handle);
456
+ let output = "";
457
+ let settled = false;
458
+ const collect = (chunk) => {
459
+ output = `${output}${String(chunk)}`.slice(-65536);
460
+ };
461
+ const finish = (error) => {
462
+ if (settled) return;
463
+ settled = true;
464
+ clearTimeout(timeout);
465
+ activeDesktopPluginHandles.delete(handle);
466
+ error === void 0 ? resolvePromise() : reject(error);
467
+ };
468
+ handle.stdout?.on("data", collect);
469
+ handle.stderr?.on("data", collect);
470
+ handle.stdout?.once("error", () => {
471
+ handle.cancel();
472
+ finish(/* @__PURE__ */ new Error("无法读取 DSH Desktop 插件安装输出。"));
473
+ });
474
+ handle.stderr?.once("error", () => {
475
+ handle.cancel();
476
+ finish(/* @__PURE__ */ new Error("无法读取 DSH Desktop 插件安装输出。"));
477
+ });
478
+ handle.done.then((outcome) => {
479
+ finish(outcome.exitCode === 0 && outcome.signal === null ? void 0 : pluginCommandError(output));
480
+ }, () => {
481
+ finish(pluginCommandError(output));
482
+ });
483
+ const timeout = setTimeout(() => {
484
+ handle.cancel();
485
+ finish(/* @__PURE__ */ new Error("插件安装超时,已终止安装进程。请检查网络后重试。"));
486
+ }, timeoutMs);
487
+ timeout.unref?.();
488
+ });
489
+ }
490
+ function runDshPlugin(args, runtime = resolveDependencyRuntime(), timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
491
+ if (runtime.desktopPnpm !== void 0) return monitorDesktopPlugin(runtime.desktopPnpm.runPlugin(args, runtime.profileDir), timeoutMs);
402
492
  const entry = resolveDshCliEntry();
403
493
  return monitorPluginChild(spawn(process.execPath, [
404
494
  ...pluginExecArgv(),
405
495
  entry,
406
496
  "plugin",
407
497
  "--profile",
408
- "web",
498
+ runtime.profileName,
409
499
  ...args
410
500
  ], {
411
501
  cwd: process.cwd(),
@@ -424,11 +514,11 @@ function runDshPlugin(args, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
424
514
  /** 并发安装互斥:pnpm 锁文件竞争会触发 EPERM/EBUSY,同一时间只允许一个安装进程。 */
425
515
  let installing = false;
426
516
  /** 仅允许安装固定依赖,避免把浏览器输入转成任意命令。 */
427
- async function installDependency(id, requestHotUpdate = requestDesktopHotUpdate) {
517
+ async function installDependency(id, requestHotUpdate = requestDesktopHotUpdate, runtime = resolveDependencyRuntime()) {
428
518
  if (installing) throw new Error("已有依赖安装正在进行,请等待完成后再试。");
429
519
  installing = true;
430
520
  try {
431
- return await installDependenciesLocked([id], requestHotUpdate);
521
+ return await installDependenciesLocked([id], requestHotUpdate, runtime);
432
522
  } finally {
433
523
  installing = false;
434
524
  }
@@ -438,31 +528,31 @@ function updatableDependencyIds(statuses) {
438
528
  return statuses.filter((status) => status.installed && status.updateAvailable).map((status) => status.id);
439
529
  }
440
530
  /** 把所有待更新版本一次写入清单,最后只请求一次桌面热更新。 */
441
- async function updateAllDependencies(requestHotUpdate = requestDesktopHotUpdate) {
531
+ async function updateAllDependencies(requestHotUpdate = requestDesktopHotUpdate, runtime = resolveDependencyRuntime()) {
442
532
  if (installing) throw new Error("已有依赖安装正在进行,请等待完成后再试。");
443
533
  installing = true;
444
534
  try {
445
- const current = await dependencyStatuses();
535
+ const current = await dependencyStatuses(runtime);
446
536
  const ids = updatableDependencyIds(current);
447
537
  if (ids.length === 0) return {
448
538
  dependencies: current,
449
539
  updatedCount: 0
450
540
  };
451
541
  return {
452
- dependencies: await installDependenciesLocked(ids, requestHotUpdate),
542
+ dependencies: await installDependenciesLocked(ids, requestHotUpdate, runtime),
453
543
  updatedCount: ids.length
454
544
  };
455
545
  } finally {
456
546
  installing = false;
457
547
  }
458
548
  }
459
- async function installDependenciesLocked(ids, requestHotUpdate) {
549
+ async function installDependenciesLocked(ids, requestHotUpdate, runtime) {
460
550
  const dependencies = [...new Set(ids)].map((id) => {
461
551
  const dependency = managedDependency(id);
462
552
  if (dependency === void 0) throw new Error("不支持安装该依赖。");
463
553
  return dependency;
464
554
  });
465
- const declared = await declaredPluginNames();
555
+ const declared = await declaredPluginNames(runtime);
466
556
  const requestedTargets = await Promise.all(dependencies.map(async (dependency) => {
467
557
  const latestVersion = await npmLatestVersion(dependency.packageName);
468
558
  if (latestVersion === void 0) throw new Error("无法获取 npm 最新版本,请检查网络或 npm registry 后重试。");
@@ -482,7 +572,7 @@ async function installDependenciesLocked(ids, requestHotUpdate) {
482
572
  packageName,
483
573
  version: requestedVersion
484
574
  };
485
- const version = await installedPackageVersion(packageName) ?? await npmLatestVersion(packageName);
575
+ const version = await installedPackageVersion(packageName, runtime) ?? await npmLatestVersion(packageName);
486
576
  if (version === void 0) throw new Error("无法读取 Suite 成员版本,请检查 npm 安装后重试。");
487
577
  return {
488
578
  packageName,
@@ -491,27 +581,27 @@ async function installDependenciesLocked(ids, requestHotUpdate) {
491
581
  }));
492
582
  const remove = [...new Set(targets.flatMap((target) => pluginsToRemoveBeforeInstall(declared, target.packageName)))];
493
583
  for (const target of targets) {
494
- await ensureLatestReleaseAllowed(target.packageName, target.version);
495
- if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version);
496
- await recordPendingUpdate(target.packageName, target.version);
584
+ await ensureLatestReleaseAllowed(target.packageName, target.version, runtime);
585
+ if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version, runtime);
586
+ await recordPendingUpdate(target.packageName, target.version, runtime);
497
587
  }
498
- if (remove.length > 0) await runDshPlugin(["remove", ...remove]);
499
- if (requestHotUpdate()) return dependencyStatuses();
588
+ if (remove.length > 0) await runDshPlugin(["remove", ...remove], runtime);
589
+ if (runtime.desktopPnpm === void 0 && requestHotUpdate()) return dependencyStatuses(runtime);
500
590
  for (const target of targets) {
501
591
  try {
502
592
  await runDshPlugin([
503
593
  "add",
504
594
  `${target.packageName}@${target.version}`,
505
595
  "--registry=https://registry.npmjs.org/"
506
- ]);
596
+ ], runtime);
507
597
  } catch (error) {
508
- if (isRestartableInstallError(error)) return dependencyStatuses();
598
+ if (isRestartableInstallError(error)) return dependencyStatuses(runtime);
509
599
  throw error;
510
600
  }
511
- if (await installedPackageVersion(target.packageName) !== target.version) return dependencyStatuses();
512
- await removePendingUpdate(target.packageName);
601
+ if (await installedPackageVersion(target.packageName, runtime) !== target.version) return dependencyStatuses(runtime);
602
+ await removePendingUpdate(target.packageName, runtime);
513
603
  }
514
- return dependencyStatuses();
604
+ return dependencyStatuses(runtime);
515
605
  }
516
606
  //#endregion
517
607
  //#region src/explorer-path-policy.ts
@@ -950,7 +1040,7 @@ function apply(ctx) {
950
1040
  const url = new URL(request.url ?? "/", "http://localhost");
951
1041
  try {
952
1042
  if (request.method === "GET") {
953
- const dependencies = await dependencyStatuses();
1043
+ const dependencies = await dependencyStatuses(resolveDependencyRuntime(ctx));
954
1044
  response.writeHead(200, {
955
1045
  "content-type": "application/json; charset=utf-8",
956
1046
  "cache-control": "no-store"
@@ -972,7 +1062,7 @@ function apply(ctx) {
972
1062
  const { dependencies, updatedCount } = await updateAllDependencies(() => {
973
1063
  restartAfterResponse = typeof process.send === "function";
974
1064
  return restartAfterResponse;
975
- });
1065
+ }, resolveDependencyRuntime(ctx));
976
1066
  response.writeHead(200, {
977
1067
  "content-type": "application/json; charset=utf-8",
978
1068
  "cache-control": "no-store"
@@ -990,7 +1080,7 @@ function apply(ctx) {
990
1080
  const dependencies = await installDependency(url.searchParams.get("dependency"), () => {
991
1081
  restartAfterResponse = typeof process.send === "function";
992
1082
  return restartAfterResponse;
993
- });
1083
+ }, resolveDependencyRuntime(ctx));
994
1084
  response.writeHead(200, {
995
1085
  "content-type": "application/json; charset=utf-8",
996
1086
  "cache-control": "no-store"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-ui",
3
- "version": "0.2.91",
3
+ "version": "0.2.93",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {