@michengai/dsh-codex-ui 0.2.85 → 0.2.87

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,41 @@
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.87 — 2026-08-26
8
+
9
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.4`.
10
+
11
+ ### Fixed
12
+
13
+ - Restored Chinese labels for the built-in permission presets in Settings and Chat with an exact-match compatibility adapter that leaves custom presets, prose, icons, and Host menu layout untouched.
14
+ - Made **Open in File Explorer** bypass in-app file previews, close its menu immediately, and open Windows Explorer in the foreground and maximized.
15
+ - Restyled conversation and workspace rename fields to match the native settings form, including helper copy, accessible labels, and a visible focus state.
16
+ - Kept the Codex UI **About** section at the bottom of Settings even when third-party plugins add later sections.
17
+
18
+ ### Compatibility
19
+
20
+ - Updated the lightweight installer to pin Codex UI `0.2.87`, IM Connect `0.1.24`, and Automation `0.1.15`; the legacy aggregate Suite remains private and unpublished.
21
+
22
+ Published packages: [`@michengai/dsh-codex-ui@0.2.87`](https://www.npmjs.com/package/@michengai/dsh-codex-ui/v/0.2.87) and [`@michengai/dsh-codex-suite-installer@0.1.4`](https://www.npmjs.com/package/@michengai/dsh-codex-suite-installer/v/0.1.4).
23
+
24
+ ## 0.2.86 — 2026-08-25
25
+
26
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.3`.
27
+
28
+ ### Changed
29
+
30
+ - Reworked **Conversation / Trajectory / Context** into a compact three-part segmented control while preserving the Host-managed tab DOM and behavior.
31
+ - Replaced the wide **Session log** capsule with an accessible 28 px download icon that retains its original text for assistive technology.
32
+ - Removed the conversation-header divider and placed all controls in a compact 34 px band with symmetric 3 px spacing.
33
+
34
+ ### Compatibility
35
+
36
+ - Aligned the 28 px mode and utility controls with `DSH-better-sidebar` in both expanded and collapsed layouts without moving the external plugin's persistent buttons.
37
+ - Added regression coverage for stable DOM ownership, exact border-box sizing, accessible labels, and header spacing.
38
+ - Updated the lightweight installer to pin Codex UI `0.2.86`; the legacy aggregate Suite remains private and is not released.
39
+
40
+ Published packages: [`@michengai/dsh-codex-ui@0.2.86`](https://www.npmjs.com/package/@michengai/dsh-codex-ui/v/0.2.86) and [`@michengai/dsh-codex-suite-installer@0.1.3`](https://www.npmjs.com/package/@michengai/dsh-codex-suite-installer/v/0.1.3).
41
+
7
42
  ## 0.2.85 — 2026-08-25
8
43
 
9
44
  Companion release: `@michengai/dsh-codex-suite-installer@0.1.2`.
@@ -4,6 +4,41 @@
4
4
 
5
5
  本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
6
6
 
7
+ ## 0.2.87 — 2026-08-26
8
+
9
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.4`。
10
+
11
+ ### 修复
12
+
13
+ - 恢复设置与 Chat 中三个内置权限预设的中文显示;兼容逻辑只精确替换权限控件文案,不改动自定义预设、正文、图标或宿主菜单布局。
14
+ - “在资源管理器中打开”不再被应用内文件预览拦截;点击后立即收起菜单,并在前台最大化打开 Windows 资源管理器。
15
+ - 将会话和工作区重命名输入框调整为原生设置表单样式,补充辅助说明、无障碍名称和清晰的焦点状态。
16
+ - 即使第三方插件继续注册设置分区,也保证 Codex UI 自带的“关于”入口始终位于最底部。
17
+
18
+ ### 兼容性
19
+
20
+ - 轻量安装器同步固定 Codex UI `0.2.87`、IM Connect `0.1.24` 和 Automation `0.1.15`;旧聚合 Suite 继续保持私有且不发布。
21
+
22
+ 发布包:[`@michengai/dsh-codex-ui@0.2.87`](https://www.npmjs.com/package/@michengai/dsh-codex-ui/v/0.2.87) 与 [`@michengai/dsh-codex-suite-installer@0.1.4`](https://www.npmjs.com/package/@michengai/dsh-codex-suite-installer/v/0.1.4)。
23
+
24
+ ## 0.2.86 — 2026-08-25
25
+
26
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.3`。
27
+
28
+ ### 调整
29
+
30
+ - 将“对话 / 轨迹 / 上下文”改为紧凑的三段式控件,同时保持宿主管理的页签 DOM 和原有交互不变。
31
+ - 将宽版 **Session log** 胶囊改为 28px 下载图标,并保留原始文本供辅助技术读取。
32
+ - 移除会话顶栏分割线,将全部控件放入上下各留 3px 的紧凑 34px 控件带。
33
+
34
+ ### 兼容性
35
+
36
+ - 在展开与收缩布局中与 `DSH-better-sidebar` 的 28px 控件保持同一中心线,不移动外部插件的常驻按钮。
37
+ - 增加宿主 DOM 归属、精确盒模型尺寸、无障碍名称和顶栏间距的回归测试。
38
+ - 轻量安装器同步固定 Codex UI `0.2.86`;旧聚合 Suite 继续保持私有,不参与发布。
39
+
40
+ 发布包:[`@michengai/dsh-codex-ui@0.2.86`](https://www.npmjs.com/package/@michengai/dsh-codex-ui/v/0.2.86) 与 [`@michengai/dsh-codex-suite-installer@0.1.3`](https://www.npmjs.com/package/@michengai/dsh-codex-suite-installer/v/0.1.3)。
41
+
7
42
  ## 0.2.85 — 2026-08-25
8
43
 
9
44
  配套版本:`@michengai/dsh-codex-suite-installer@0.1.2`。
package/dist/client.js CHANGED
@@ -788,16 +788,26 @@ window.__ModuleLoader__.load({
788
788
  const HEADER_PROJECT_TIP_EVENT = "dcu-header-project-tip";
789
789
  const HEADER_SESSION_MENU_EVENT = "dcu-header-session-menu";
790
790
  const CONVERSATION_HEADER_STYLE = `
791
- header:has([data-dcu-inline-tabs]){display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding-bottom:12px}
791
+ header:has([data-dcu-inline-tabs]){box-sizing:border-box;display:flex;flex-wrap:wrap;align-items:center;gap:10px;min-height:34px;padding-top:3px;padding-bottom:3px;border-bottom:0}
792
+ header:has([data-dcu-inline-tabs]):after{display:none;content:none}
792
793
  header:has([data-dcu-inline-tabs]) [class*="titleRow"],header:has([data-dcu-inline-tabs]) [class*="titleCluster"]{display:contents}
793
794
  header:has([data-dcu-inline-tabs]) [class*="crumbs"]{order:1;flex:1;min-width:0}
794
795
  header:has([data-dcu-inline-tabs]) [class*="headerActions"]{order:2;flex:none}
795
- header [data-dcu-inline-tabs]{order:3;flex:none;display:flex;align-items:center;gap:4px;margin:0;padding:0;position:relative;z-index:1}
796
+ header [data-dcu-inline-tabs]{box-sizing:border-box;order:3;flex:none;display:flex;align-items:center;gap:0;margin:0;padding:0;height:28px;position:relative;z-index:1;overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,.12));border-radius:8px;background:var(--dsw-alias-background-secondary,rgba(255,255,255,.025))}
796
797
  header:has([data-dcu-inline-tabs]) [class*="headerUtilities"]{order:4;flex:none}
797
- header [data-dcu-tab-slider]{position:absolute;left:0;top:50%;height:28px;margin-top:-14px;border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-button-info-fill,#4c8dff) 18%,transparent);pointer-events:none;z-index:0;opacity:0;transform:translateX(0);width:0;transition:transform 280ms cubic-bezier(.16,1,.3,1),width 280ms cubic-bezier(.16,1,.3,1),opacity 160ms ease}
798
- header [data-dcu-inline-tabs] [role=tab]{position:relative;z-index:1;padding:8px 10px 10px;margin:0;line-height:20px;color:var(--dsw-alias-label-tertiary);border:0;box-shadow:none;background:transparent}
798
+ header [data-dcu-tab-slider]{position:absolute;left:0;top:0;height:26px;border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-button-info-fill,#4c8dff) 18%,transparent);pointer-events:none;z-index:0;opacity:0;transform:translateX(0);width:0;transition:transform 240ms cubic-bezier(.16,1,.3,1),width 240ms cubic-bezier(.16,1,.3,1),opacity 160ms ease}
799
+ /* 顶栏统一使用 34px 控件带:与宿主扩展常用的 top:3px + 28px 图标按钮同心,
800
+ 外部插件按钮无需在展开/收起时改变位置。 */
801
+ header [data-dcu-inline-tabs] [role=tab]{box-sizing:border-box;position:relative;z-index:1;height:26px;padding:3px 10px;margin:0;line-height:20px;color:var(--dsw-alias-label-tertiary);border:0;box-shadow:none;background:transparent;font-size:13px;white-space:nowrap}
799
802
  header [data-dcu-inline-tabs] [role=tab][aria-selected=true],header [data-dcu-inline-tabs] [role=tab][data-state=active]{color:var(--dsw-alias-button-info-fill,#4c8dff);font-weight:500}
803
+ header [data-dcu-inline-tabs] [role=tab]+[role=tab]{border-left:1px solid var(--dsw-alias-border-subtle,rgba(255,255,255,.08))}
800
804
  header [data-dcu-inline-tabs] [role=tab]:after,header [data-dcu-inline-tabs] [role=tab]:before{display:none!important;content:none!important;background:transparent!important;height:0!important}
805
+ header [data-dcu-inline-tabs] [role=tab]:focus-visible,header [data-dcu-session-log-download]:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#4c8dff);outline-offset:-2px}
806
+ header [data-dcu-session-log-download]{appearance:none;min-width:28px;width:28px;height:28px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,currentColor);display:inline-grid;place-items:center;cursor:pointer}
807
+ header [data-dcu-session-log-download]:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));color:var(--dsw-alias-label-primary,currentColor)}
808
+ header [data-dcu-session-log-download]:disabled{color:var(--dsw-alias-label-dimmed,currentColor);cursor:wait}
809
+ header [data-dcu-session-log-download] > span{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
810
+ header [data-dcu-session-log-download] svg{width:16px;height:16px;flex:none}
801
811
  header [data-dcu-title-folder],header [data-dcu-title-more]{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-tertiary,currentColor);display:inline-grid;place-items:center;padding:0;cursor:pointer;border-radius:4px}
802
812
  header [data-dcu-title-folder]{width:16px;height:20px}
803
813
  header [data-dcu-title-more]{width:20px;height:20px}
@@ -816,6 +826,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
816
826
  tabs.dataset.dcuInlineTabs = "";
817
827
  return true;
818
828
  }
829
+ /** 把宿主的 Session log 胶囊标记成紧凑下载按钮;保留文本供无障碍名称使用。 */
830
+ function decorateSessionLogDownload(root) {
831
+ const button = root.querySelector("header [class*=\"headerUtilities\"] button[class*=\"sessionLogButton\"]");
832
+ if (button === null || button.dataset.dcuSessionLogDownload === "") return false;
833
+ button.dataset.dcuSessionLogDownload = "";
834
+ const label = button.querySelector("span")?.textContent?.trim();
835
+ if (label !== void 0 && label !== "") {
836
+ if (!button.hasAttribute("aria-label")) button.setAttribute("aria-label", label);
837
+ if (!button.hasAttribute("title")) button.setAttribute("title", label);
838
+ }
839
+ return true;
840
+ }
819
841
  function emit(name, target, extra = {}) {
820
842
  const box = target.getBoundingClientRect();
821
843
  target.dispatchEvent(new CustomEvent(name, {
@@ -932,6 +954,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
932
954
  }
933
955
  syncTabSlider(doc);
934
956
  decorateConversationTitle(doc);
957
+ decorateSessionLogDownload(doc);
935
958
  decorateUserBubbles(doc);
936
959
  } finally {
937
960
  applying = false;
@@ -1311,8 +1334,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1311
1334
  .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
1312
1335
  .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
1313
1336
  .dcu-wb-delete-copy{margin:0;color:var(--dcu-sidebar-secondary);font-size:13px;line-height:20px}
1314
- .dcu-wb-rename-input{width:100%;height:44px;border:1px solid var(--dcu-sidebar-border);border-radius:22px;padding:7px 14px;outline:0;background:transparent;color:var(--dcu-sidebar-primary);font:inherit}
1315
- .dcu-wb-rename-input:focus{border-color:var(--dsw-alias-button-info-fill)}
1337
+ .dcu-wb-rename-input{box-sizing:border-box;width:100%;height:36px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:0;background:var(--dsw-alias-background-secondary);color:var(--dsw-alias-label-primary);font:14px/20px var(--dsw-font-family,inherit);caret-color:var(--dsw-alias-state-business-primary);transition:border-color 120ms ease,box-shadow 120ms ease}
1338
+ .dcu-wb-rename-input:hover{border-color:color-mix(in srgb,var(--dsw-alias-label-primary) 32%,var(--dsw-alias-border-l2))}
1339
+ .dcu-wb-rename-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 28%,transparent)}
1316
1340
  `;
1317
1341
  const runningStyles = `.dcu-wb-running{flex:none;width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
1318
1342
  const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:0 8px 4px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
@@ -1582,6 +1606,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1582
1606
  await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(value);
1583
1607
  });
1584
1608
  };
1609
+ const openPathImmediately = (path) => {
1610
+ setMenu(void 0);
1611
+ setHeaderMenu(void 0);
1612
+ run("open-path", () => openPath(path));
1613
+ };
1585
1614
  const toggleGroup = (key, defaultOpen = true) => {
1586
1615
  setExpanded((current) => ({
1587
1616
  ...current,
@@ -1793,7 +1822,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1793
1822
  setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
1794
1823
  setMenu(void 0);
1795
1824
  }
1796
- if (id === "openPath") run("open-path", () => openPath(workspace.path));
1825
+ if (id === "openPath") openPathImmediately(workspace.path);
1797
1826
  if (id === "delete") {
1798
1827
  setDeleteTarget({
1799
1828
  id: workspace.workspaceId,
@@ -2017,7 +2046,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2017
2046
  setMenu(void 0);
2018
2047
  }
2019
2048
  if (action === "fork") run("fork", () => forkSession(id));
2020
- if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
2049
+ if (action === "openPath" && path !== void 0) openPathImmediately(path);
2021
2050
  if (action === "copyPath") copy(path);
2022
2051
  if (action === "copyTitle") copy(session.displayTitle);
2023
2052
  if (action === "copyId") copy(id);
@@ -2357,7 +2386,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2357
2386
  setMenu(void 0);
2358
2387
  }
2359
2388
  if (action === "fork") run("fork", () => forkSession(id));
2360
- if (action === "openPath" && session.cwd !== void 0) run("open-path", () => openPath(session.cwd));
2389
+ if (action === "openPath" && session.cwd !== void 0) openPathImmediately(session.cwd);
2361
2390
  if (action === "copyPath") copy(session.cwd);
2362
2391
  if (action === "copyTitle") copy(title);
2363
2392
  if (action === "copyId") copy(id);
@@ -2394,7 +2423,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2394
2423
  setError(void 0);
2395
2424
  }
2396
2425
  if (action === "fork") run("fork", () => forkSession(id));
2397
- if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
2426
+ if (action === "openPath" && path !== void 0) openPathImmediately(path);
2398
2427
  if (action === "copyPath") copy(path);
2399
2428
  if (action === "copyTitle") copy(session.displayTitle);
2400
2429
  if (action === "copyId") copy(id);
@@ -2423,6 +2452,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2423
2452
  },
2424
2453
  closeLabel: t("sessions.close"),
2425
2454
  title: renameTarget?.kind === "workspace" ? t("workspace.rename") : t("sessions.rename"),
2455
+ description: t("sessions.renameDescription"),
2426
2456
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2427
2457
  className: "dcu-wb-rename-actions",
2428
2458
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -2440,6 +2470,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2440
2470
  }),
2441
2471
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2442
2472
  className: "dcu-wb-rename-input",
2473
+ "aria-label": renameTarget?.kind === "workspace" ? t("workspace.rename") : t("sessions.rename"),
2443
2474
  value: renameDraft,
2444
2475
  autoFocus: true,
2445
2476
  onFocus: (event) => {
@@ -2652,6 +2683,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2652
2683
  },
2653
2684
  closeLabel: t("sessions.close"),
2654
2685
  title: t("sessions.rename"),
2686
+ description: t("sessions.renameDescription"),
2655
2687
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2656
2688
  className: "dcu-wb-rename-actions",
2657
2689
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -2669,6 +2701,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2669
2701
  }),
2670
2702
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2671
2703
  className: "dcu-wb-rename-input",
2704
+ "aria-label": t("sessions.rename"),
2672
2705
  value: renameDraft,
2673
2706
  autoFocus: true,
2674
2707
  onFocus: (event) => {
@@ -4304,6 +4337,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4304
4337
  "sessions.markRead": "标记为已读",
4305
4338
  "sessions.unread": "未读",
4306
4339
  "sessions.rename": "重命名会话",
4340
+ "sessions.renameDescription": "保持简短且易于识别",
4307
4341
  "sessions.archive": "归档会话",
4308
4342
  "sessions.delete": "删除会话",
4309
4343
  "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
@@ -4426,6 +4460,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4426
4460
  "sessions.markRead": "Mark as read",
4427
4461
  "sessions.unread": "Unread",
4428
4462
  "sessions.rename": "Rename conversation",
4463
+ "sessions.renameDescription": "Keep it short and easy to recognize.",
4429
4464
  "sessions.archive": "Archive conversation",
4430
4465
  "sessions.delete": "Delete conversation",
4431
4466
  "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
@@ -4482,6 +4517,30 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4482
4517
  "turns.untitled": "Untitled prompt",
4483
4518
  "turns.jump": "Jump to turn {index}: {summary}"
4484
4519
  };
4520
+ //#endregion
4521
+ //#region src/client/host-open-path.ts
4522
+ const OPEN_IN_EXPLORER_ENDPOINT = "/api/michengai/codex-ui/open-in-explorer";
4523
+ /** “在资源管理器中打开”属于明确的系统动作,不应被侧边栏文件预览器接管。 */
4524
+ async function openPathInHost(connection, path, fetcher = fetch) {
4525
+ let foregroundResponse;
4526
+ try {
4527
+ foregroundResponse = await fetcher(OPEN_IN_EXPLORER_ENDPOINT, {
4528
+ method: "POST",
4529
+ headers: { "content-type": "application/json" },
4530
+ body: JSON.stringify({ path })
4531
+ });
4532
+ } catch {}
4533
+ if (foregroundResponse?.ok) return;
4534
+ if (foregroundResponse !== void 0 && foregroundResponse.status !== 404 && foregroundResponse.status !== 501) {
4535
+ const payload = await foregroundResponse.json().catch(() => ({}));
4536
+ throw new Error(typeof payload.error === "string" ? payload.error : "无法在前台打开资源管理器。");
4537
+ }
4538
+ const response = await connection.api.host.openPath({ path });
4539
+ if (!response.result.ok) throw new Error(`path open failed: ${response.result.error.message}`);
4540
+ }
4541
+ //#endregion
4542
+ //#region src/client/settings-nav-icons.ts
4543
+ const ABOUT_SETTINGS_NAV_ORDER = "2147483647";
4485
4544
  const SETTINGS_NAV_ICON_HTML = {
4486
4545
  experts: "<path fill=\"currentColor\" d=\"M11.0307 5.46369C11.0305 3.78995 9.6734 2.43357 7.99961 2.43357C6.32601 2.43379 4.96972 3.79009 4.96949 5.46369C4.96949 7.13748 6.32587 8.49455 7.99961 8.49477C9.67354 8.49477 11.0307 7.13762 11.0307 5.46369ZM12.3163 5.46369C12.3163 7.84777 10.3837 9.78042 7.99961 9.78042C5.61572 9.7802 3.68288 7.84763 3.68288 5.46369C3.6831 3.07993 5.61586 1.14718 7.99961 1.14695C10.3836 1.14695 12.3161 3.0798 12.3163 5.46369Z\"/><path fill=\"currentColor\" d=\"M8.00002 10.3316C11.7343 10.3316 14.1864 11.8997 15.0387 14.4445L14.4292 14.6483L13.8197 14.8531C13.1955 12.9893 11.3673 11.6182 8.00002 11.6182C4.63277 11.6182 2.80455 12.9893 2.18031 14.8531L1.5708 14.6483L0.961304 14.4445C1.81368 11.8997 4.26579 10.3316 8.00002 10.3316Z\"/>",
4487
4546
  skills: "<path fill=\"currentColor\" d=\"M12.5113 15.4067C12.4395 15.6249 12.1308 15.6249 12.059 15.4067L11.643 14.1416C11.454 13.567 11.0033 13.1164 10.4288 12.9274L9.16369 12.5113C8.94544 12.4395 8.94544 12.1308 9.16369 12.059L10.4288 11.643C11.0033 11.454 11.454 11.0033 11.643 10.4288L12.059 9.16369C12.1308 8.94544 12.4395 8.94544 12.5113 9.16369L12.9274 10.4288C13.1164 11.0033 13.567 11.454 14.1416 11.643L15.4067 12.059C15.6249 12.1308 15.6249 12.4395 15.4067 12.5113L14.1416 12.9274C13.567 13.1164 13.1164 13.567 12.9274 14.1416L12.5113 15.4067Z\"/><path fill=\"currentColor\" d=\"M9.02246 0.546878C9.9822 0.546878 10.7564 0.545403 11.374 0.612307C12.0042 0.680586 12.5515 0.826244 13.0273 1.17188C13.3052 1.37376 13.5501 1.61868 13.752 1.89649C14.0975 2.37225 14.2432 2.91984 14.3115 3.54981C14.3784 4.16727 14.377 4.94206 14.377 5.90137V8.51367C13.9611 8.29533 13.5071 8.13985 13.0273 8.06055V5.90137C13.0273 4.9121 13.0259 4.22322 12.9688 3.69532C12.9129 3.18044 12.8098 2.89782 12.6592 2.69043C12.5406 2.52724 12.3966 2.38326 12.2334 2.26465C12.026 2.11404 11.7437 2.0109 11.2285 1.95508C10.7005 1.89789 10.0122 1.89649 9.02246 1.89649H6.55371C5.56395 1.89649 4.87569 1.89787 4.34766 1.95508C3.83242 2.01092 3.55022 2.11398 3.34278 2.26465C3.17953 2.38329 3.03564 2.52719 2.91699 2.69043C2.76642 2.89782 2.66325 3.18042 2.60742 3.69532C2.55027 4.22322 2.54883 4.9121 2.54883 5.90137V10.0986C2.54883 11.0878 2.55031 11.7768 2.60742 12.3047C2.66326 12.8196 2.76642 13.1032 2.91699 13.3105C3.03558 13.4736 3.17966 13.6178 3.34278 13.7363C3.5502 13.8869 3.83265 13.9901 4.34766 14.0459C4.87568 14.1031 5.56398 14.1035 6.55371 14.1035H8.08399C8.27443 14.6025 8.55077 15.0585 8.89551 15.4541H6.55371C5.59402 15.4541 4.81976 15.4546 4.20215 15.3877C3.57204 15.3194 3.02468 15.1738 2.54883 14.8281C2.27111 14.6263 2.02606 14.3813 1.82422 14.1035C1.47883 13.6278 1.33293 13.08 1.26465 12.4502C1.19783 11.8327 1.19922 11.0579 1.19922 10.0986V5.90137C1.19922 4.94206 1.1978 4.16727 1.26465 3.54981C1.33295 2.91984 1.47867 2.37225 1.82422 1.89649C2.02613 1.61864 2.27098 1.37379 2.54883 1.17188C3.02472 0.826181 3.57197 0.6806 4.20215 0.612307C4.81976 0.545393 5.594 0.546877 6.55371 0.546878H9.02246ZM9.19629 9.14649H4.5459V7.84571H9.19629V9.14649ZM11.0303 6.10645H4.5459V4.80567H11.0303V6.10645Z\"/>",
@@ -4511,9 +4570,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4511
4570
  function applySettingsNavIcon(button) {
4512
4571
  const id = settingsNavIconId(button.textContent ?? "");
4513
4572
  if (id === void 0) return false;
4573
+ let changed = false;
4574
+ if (id === "about") {
4575
+ if (!button.hasAttribute("data-dcu-settings-about-last")) {
4576
+ button.setAttribute("data-dcu-settings-about-last", "");
4577
+ changed = true;
4578
+ }
4579
+ if (button.style.getPropertyValue("order") !== "2147483647" || button.style.getPropertyPriority("order") !== "important") {
4580
+ button.style.setProperty("order", ABOUT_SETTINGS_NAV_ORDER, "important");
4581
+ changed = true;
4582
+ }
4583
+ }
4514
4584
  const svg = button.querySelector("svg");
4515
- if (svg === null) return false;
4516
- if (svg.getAttribute("data-dcu-nav-icon") === id) return false;
4585
+ if (svg === null) return changed;
4586
+ if (svg.getAttribute("data-dcu-nav-icon") === id) return changed;
4517
4587
  svg.setAttribute("data-dcu-nav-icon", id);
4518
4588
  svg.setAttribute("viewBox", id === "archive" ? "0 0 20 20" : id === "about" ? "0 0 14 14" : "0 0 16 16");
4519
4589
  svg.setAttribute("fill", "none");
@@ -4558,6 +4628,105 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4558
4628
  };
4559
4629
  }
4560
4630
  //#endregion
4631
+ //#region src/client/permission-labels.ts
4632
+ const PERMISSION_LABELS = [
4633
+ {
4634
+ aliases: [
4635
+ "Read Only",
4636
+ "preset.readOnly",
4637
+ "只读"
4638
+ ],
4639
+ en: "Read Only",
4640
+ zh: "只读"
4641
+ },
4642
+ {
4643
+ aliases: [
4644
+ "Workspace Write",
4645
+ "preset.workspaceWrite",
4646
+ "工作区写入"
4647
+ ],
4648
+ en: "Workspace Write",
4649
+ zh: "工作区写入"
4650
+ },
4651
+ {
4652
+ aliases: [
4653
+ "Full access",
4654
+ "preset.fullAccess",
4655
+ "完全访问"
4656
+ ],
4657
+ en: "Full access",
4658
+ zh: "完全访问"
4659
+ }
4660
+ ];
4661
+ const INTERACTIVE_SELECTOR = "button, [role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"option\"]";
4662
+ function targetLabel(value, activeLocale) {
4663
+ const definition = PERMISSION_LABELS.find((item) => item.aliases.includes(value));
4664
+ if (definition === void 0) return void 0;
4665
+ return activeLocale.toLowerCase().startsWith("zh") ? definition.zh : definition.en;
4666
+ }
4667
+ function applyPermissionLabel(element, activeLocale) {
4668
+ const document = element.ownerDocument;
4669
+ const showText = document.defaultView?.NodeFilter.SHOW_TEXT ?? 4;
4670
+ const walker = document.createTreeWalker(element, showText);
4671
+ let changed = false;
4672
+ let node = walker.nextNode();
4673
+ while (node !== null) {
4674
+ const value = node.nodeValue ?? "";
4675
+ const trimmed = value.trim();
4676
+ const next = targetLabel(trimmed, activeLocale);
4677
+ if (next !== void 0 && next !== trimmed) {
4678
+ node.nodeValue = value.replace(trimmed, next);
4679
+ changed = true;
4680
+ }
4681
+ node = walker.nextNode();
4682
+ }
4683
+ return changed;
4684
+ }
4685
+ /** 只修正权限选择控件里的内置预设名称,不改动正文或自定义权限名称。 */
4686
+ function localizePermissionLabels(root, activeLocale) {
4687
+ let changed = 0;
4688
+ const rootElement = root.nodeType === 1 ? root : void 0;
4689
+ const elements = rootElement?.matches(INTERACTIVE_SELECTOR) === true ? [rootElement, ...rootElement.querySelectorAll(INTERACTIVE_SELECTOR)] : [...root.querySelectorAll(INTERACTIVE_SELECTOR)];
4690
+ for (const element of elements) if (applyPermissionLabel(element, activeLocale)) changed += 1;
4691
+ return changed;
4692
+ }
4693
+ /** 兼容宿主设置页和 Chat 中由官方组件直接输出的英文权限名称。 */
4694
+ function observePermissionLabels(locale) {
4695
+ if (typeof document === "undefined" || document.body === null) return () => {};
4696
+ let applying = false;
4697
+ let frame;
4698
+ const apply = () => {
4699
+ if (applying) return;
4700
+ applying = true;
4701
+ const active = locale.getSnapshot?.().active ?? document.documentElement.lang ?? window.navigator.language ?? "zh";
4702
+ try {
4703
+ localizePermissionLabels(document, active);
4704
+ } finally {
4705
+ applying = false;
4706
+ }
4707
+ };
4708
+ const schedule = () => {
4709
+ if (frame !== void 0) return;
4710
+ frame = window.requestAnimationFrame(() => {
4711
+ frame = void 0;
4712
+ apply();
4713
+ });
4714
+ };
4715
+ apply();
4716
+ const observer = new MutationObserver(schedule);
4717
+ observer.observe(document.body, {
4718
+ childList: true,
4719
+ characterData: true,
4720
+ subtree: true
4721
+ });
4722
+ const unsubscribe = locale.subscribe?.(schedule) ?? (() => {});
4723
+ return () => {
4724
+ observer.disconnect();
4725
+ unsubscribe();
4726
+ if (frame !== void 0) window.cancelAnimationFrame(frame);
4727
+ };
4728
+ }
4729
+ //#endregion
4561
4730
  //#region src/client/conversation-dom.ts
4562
4731
  /**
4563
4732
  * DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
@@ -4805,7 +4974,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4805
4974
  "sessions",
4806
4975
  "workspaces",
4807
4976
  "layout",
4808
- "locale"
4977
+ "locale",
4978
+ "connection"
4809
4979
  ];
4810
4980
  function hasDeleteSession(value) {
4811
4981
  return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
@@ -4817,8 +4987,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4817
4987
  en
4818
4988
  }), "michengai-codex-ui: dictionaries");
4819
4989
  const t = ctx.locale.bind(NS);
4990
+ const connection = ctx.get("connection");
4991
+ const openPath = (path) => openPathInHost(connection, path);
4820
4992
  ctx.effect(() => observeSlimSidebar(), "michengai-codex-ui: slim sidebar");
4821
4993
  ctx.effect(() => observeSettingsNavIcons(), "michengai-codex-ui: settings nav icons");
4994
+ ctx.effect(() => observePermissionLabels(ctx.locale), "michengai-codex-ui: permission labels");
4822
4995
  ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
4823
4996
  const companionSlots = createCompanionTabSource(ctx.slots);
4824
4997
  ctx.slots.inject("sidebar", () => ctx.slots.register({
@@ -4861,7 +5034,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4861
5034
  deleteSession,
4862
5035
  forkSession,
4863
5036
  renameSession,
4864
- openPath: (path) => ctx.workspaces.openPath(path),
5037
+ openPath,
4865
5038
  companionSlots
4866
5039
  })
4867
5040
  }, CodexSidebar));
@@ -4899,7 +5072,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4899
5072
  deleteSession,
4900
5073
  deleteWorkspace: (workspaceId) => ctx.workspaces.delete(workspaceId),
4901
5074
  forkSession,
4902
- openPath: (path) => ctx.workspaces.openPath(path),
5075
+ openPath,
4903
5076
  openSession: (sessionId) => {
4904
5077
  ctx.sessions.open(sessionId);
4905
5078
  },
@@ -4939,7 +5112,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4939
5112
  ctx.slots.inject("settings.section", () => ctx.slots.register({
4940
5113
  name: "settings.section",
4941
5114
  id: "about",
4942
- order: 100,
5115
+ order: Number.MAX_SAFE_INTEGER,
4943
5116
  label: () => t("about.nav"),
4944
5117
  locale: NS
4945
5118
  }, AboutSection));
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { mkdir, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { basename, dirname, join, resolve, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { randomUUID } from "node:crypto";
7
7
  //#region src/dependencies.ts
@@ -529,6 +529,224 @@ function hostServices(ctx) {
529
529
  };
530
530
  }
531
531
  //#endregion
532
+ //#region src/native-explorer.ts
533
+ const WINDOWS_EXPLORER_TIMEOUT_MS = 8e3;
534
+ const WINDOWS_FOREGROUND_EXPLORER_SCRIPT = String.raw`
535
+ $ErrorActionPreference = 'Stop'
536
+
537
+ Add-Type -TypeDefinition @'
538
+ using System;
539
+ using System.Runtime.InteropServices;
540
+ public static class DcuWindowFocus {
541
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
542
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint pid);
543
+ [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
544
+ [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool attach);
545
+ [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int command);
546
+ [DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr hWnd);
547
+ [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
548
+ }
549
+ '@
550
+
551
+ $shell = New-Object -ComObject Shell.Application
552
+
553
+ function Find-ExplorerWindow([string]$path) {
554
+ foreach ($item in @($shell.Windows())) {
555
+ try {
556
+ $candidate = [IO.Path]::GetFullPath([string]$item.Document.Folder.Self.Path).TrimEnd('\')
557
+ if ($candidate -ieq $path) { return $item }
558
+ } catch {}
559
+ }
560
+ return $null
561
+ }
562
+
563
+ function Open-ExplorerInForeground([string]$target) {
564
+ $target = [IO.Path]::GetFullPath($target).TrimEnd('\')
565
+ $window = Find-ExplorerWindow $target
566
+ if ($null -eq $window) {
567
+ $shell.Explore($target)
568
+ for ($attempt = 0; $attempt -lt 120 -and $null -eq $window; $attempt += 1) {
569
+ Start-Sleep -Milliseconds 25
570
+ $window = Find-ExplorerWindow $target
571
+ }
572
+ }
573
+ if ($null -eq $window) { throw 'Explorer window was not found.' }
574
+
575
+ $handle = [IntPtr]([long]$window.HWND)
576
+ $foreground = [DcuWindowFocus]::GetForegroundWindow()
577
+ $foregroundProcessId = 0
578
+ $foregroundThread = [DcuWindowFocus]::GetWindowThreadProcessId($foreground, [ref]$foregroundProcessId)
579
+ $currentThread = [DcuWindowFocus]::GetCurrentThreadId()
580
+ $attached = $foregroundThread -ne 0 -and [DcuWindowFocus]::AttachThreadInput($currentThread, $foregroundThread, $true)
581
+ try {
582
+ [void][DcuWindowFocus]::ShowWindowAsync($handle, 3)
583
+ [void][DcuWindowFocus]::BringWindowToTop($handle)
584
+ if (-not [DcuWindowFocus]::SetForegroundWindow($handle)) { throw 'Explorer could not be activated.' }
585
+ } finally {
586
+ if ($attached) { [void][DcuWindowFocus]::AttachThreadInput($currentThread, $foregroundThread, $false) }
587
+ }
588
+ }
589
+
590
+ [Console]::Out.WriteLine('READY')
591
+ [Console]::Out.Flush()
592
+ while (($line = [Console]::In.ReadLine()) -ne $null) {
593
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
594
+ $parts = $line.Split([char]9, 2)
595
+ $requestId = $parts[0]
596
+ try {
597
+ if ($parts.Count -ne 2) { throw 'Invalid request.' }
598
+ $target = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))
599
+ Open-ExplorerInForeground $target
600
+ [Console]::Out.WriteLine(('OK' + [char]9 + $requestId))
601
+ } catch {
602
+ $message = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$_.Exception.Message))
603
+ [Console]::Out.WriteLine(('ERR' + [char]9 + $requestId + [char]9 + $message))
604
+ }
605
+ [Console]::Out.Flush()
606
+ }
607
+ `;
608
+ /**
609
+ * 常驻的 Windows Explorer 前台激活助手。
610
+ * PowerShell 与窗口 API 只在 Host 启动时初始化一次,点击时仅发送一行路径请求。
611
+ */
612
+ var ForegroundExplorer = class {
613
+ run;
614
+ platform;
615
+ child;
616
+ startupChild;
617
+ starting;
618
+ stdout = "";
619
+ sequence = 0;
620
+ pending = /* @__PURE__ */ new Map();
621
+ disposed = false;
622
+ constructor(run = spawn, platform = process.platform) {
623
+ this.run = run;
624
+ this.platform = platform;
625
+ }
626
+ /** 在用户点击前完成 PowerShell 与窗口 API 初始化。 */
627
+ warmup() {
628
+ if (this.platform !== "win32") return Promise.resolve();
629
+ return this.ensureStarted().then(() => void 0);
630
+ }
631
+ async open(path) {
632
+ if (this.platform !== "win32") throw new Error("foreground Explorer is only available on Windows");
633
+ if (!isAbsolute(path)) throw new Error("Explorer path must be absolute");
634
+ if (this.disposed) throw new Error("Explorer helper has been disposed");
635
+ const child = await this.ensureStarted();
636
+ const requestId = String(++this.sequence);
637
+ const encodedPath = Buffer.from(path, "utf8").toString("base64");
638
+ return await new Promise((resolve, reject) => {
639
+ const timeout = setTimeout(() => {
640
+ this.pending.delete(requestId);
641
+ reject(/* @__PURE__ */ new Error("Explorer activation timed out"));
642
+ }, WINDOWS_EXPLORER_TIMEOUT_MS);
643
+ timeout.unref?.();
644
+ this.pending.set(requestId, {
645
+ resolve,
646
+ reject,
647
+ timeout
648
+ });
649
+ child.stdin.write(`${requestId}\t${encodedPath}\n`, (error) => {
650
+ if (error === null || error === void 0) return;
651
+ const request = this.pending.get(requestId);
652
+ if (request === void 0) return;
653
+ clearTimeout(request.timeout);
654
+ this.pending.delete(requestId);
655
+ request.reject(error);
656
+ });
657
+ });
658
+ }
659
+ dispose() {
660
+ this.disposed = true;
661
+ this.failAll(/* @__PURE__ */ new Error("Explorer helper has been disposed"));
662
+ this.startupChild?.kill();
663
+ this.child?.kill();
664
+ this.startupChild = void 0;
665
+ this.child = void 0;
666
+ this.starting = void 0;
667
+ }
668
+ ensureStarted() {
669
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Explorer helper has been disposed"));
670
+ if (this.child !== void 0) return Promise.resolve(this.child);
671
+ if (this.starting !== void 0) return this.starting;
672
+ const encoded = Buffer.from(WINDOWS_FOREGROUND_EXPLORER_SCRIPT, "utf16le").toString("base64");
673
+ this.starting = new Promise((resolve, reject) => {
674
+ const child = this.run("powershell.exe", [
675
+ "-NoLogo",
676
+ "-NoProfile",
677
+ "-NonInteractive",
678
+ "-EncodedCommand",
679
+ encoded
680
+ ], { windowsHide: true });
681
+ this.startupChild = child;
682
+ let settled = false;
683
+ const finishStart = (error) => {
684
+ if (settled) return;
685
+ settled = true;
686
+ clearTimeout(startupTimeout);
687
+ if (error !== void 0) {
688
+ this.starting = void 0;
689
+ if (this.startupChild === child) this.startupChild = void 0;
690
+ child.kill();
691
+ reject(error);
692
+ return;
693
+ }
694
+ if (this.disposed) {
695
+ this.starting = void 0;
696
+ if (this.startupChild === child) this.startupChild = void 0;
697
+ child.kill();
698
+ reject(/* @__PURE__ */ new Error("Explorer helper has been disposed"));
699
+ return;
700
+ }
701
+ if (this.startupChild === child) this.startupChild = void 0;
702
+ this.child = child;
703
+ this.starting = void 0;
704
+ resolve(child);
705
+ };
706
+ const startupTimeout = setTimeout(() => finishStart(/* @__PURE__ */ new Error("Explorer helper startup timed out")), WINDOWS_EXPLORER_TIMEOUT_MS);
707
+ startupTimeout.unref?.();
708
+ child.stdout.on("data", (chunk) => {
709
+ this.stdout += String(chunk);
710
+ let newline = this.stdout.indexOf("\n");
711
+ while (newline >= 0) {
712
+ const line = this.stdout.slice(0, newline).trimEnd();
713
+ this.stdout = this.stdout.slice(newline + 1);
714
+ if (line === "READY") finishStart();
715
+ else this.handleResponse(line);
716
+ newline = this.stdout.indexOf("\n");
717
+ }
718
+ });
719
+ child.once("error", (error) => finishStart(error));
720
+ child.once("exit", () => {
721
+ if (!settled) finishStart(/* @__PURE__ */ new Error("Explorer helper exited during startup"));
722
+ if (this.child === child) this.child = void 0;
723
+ this.failAll(/* @__PURE__ */ new Error("Explorer helper exited"));
724
+ });
725
+ });
726
+ return this.starting;
727
+ }
728
+ handleResponse(line) {
729
+ const [status, requestId, encodedMessage] = line.split(" ");
730
+ const request = requestId === void 0 ? void 0 : this.pending.get(requestId);
731
+ if (request === void 0) return;
732
+ clearTimeout(request.timeout);
733
+ this.pending.delete(requestId);
734
+ if (status === "OK") {
735
+ request.resolve();
736
+ return;
737
+ }
738
+ const message = encodedMessage === void 0 ? "Explorer activation failed" : Buffer.from(encodedMessage, "base64").toString("utf8");
739
+ request.reject(new Error(message));
740
+ }
741
+ failAll(error) {
742
+ for (const request of this.pending.values()) {
743
+ clearTimeout(request.timeout);
744
+ request.reject(error);
745
+ }
746
+ this.pending.clear();
747
+ }
748
+ };
749
+ //#endregion
532
750
  //#region src/workspace-preferences.ts
533
751
  const WORKSPACE_PREFERENCES_FILE = ".dsh-codex-ui-preferences.json";
534
752
  /** Desktop 和普通 DSH Web 共用 Profile;服务换端口或重启后目录仍保持稳定。 */
@@ -596,6 +814,7 @@ function writeWorkspacePreferences(pinnedWorkspaceIds, path = workspacePreferenc
596
814
  //#region src/index.ts
597
815
  const connectorsEndpoint = "/api/michengai/codex-ui/connectors";
598
816
  const dependenciesEndpoint = "/api/michengai/codex-ui/dependencies";
817
+ const explorerEndpoint = "/api/michengai/codex-ui/open-in-explorer";
599
818
  const preferencesEndpoint = "/api/michengai/codex-ui/preferences";
600
819
  const maxPreferencesBodyBytes = 32768;
601
820
  function headerValue(headers, name) {
@@ -656,6 +875,8 @@ const inject = [
656
875
  function apply(ctx) {
657
876
  const host = hostServices(ctx);
658
877
  ctx.effect(() => {
878
+ const foregroundExplorer = new ForegroundExplorer();
879
+ foregroundExplorer.warmup().catch((error) => ctx.logger.warn("foreground explorer warmup failed: %s", error));
659
880
  const disposeConnectors = host.webServer.register({
660
881
  kind: "exact",
661
882
  path: connectorsEndpoint,
@@ -844,10 +1065,66 @@ function apply(ctx) {
844
1065
  }
845
1066
  }
846
1067
  });
1068
+ const disposeExplorer = host.webServer.register({
1069
+ kind: "exact",
1070
+ path: explorerEndpoint,
1071
+ handler: async (request, response) => {
1072
+ if (request.method !== "POST") {
1073
+ response.writeHead(405, { allow: "POST" });
1074
+ response.end();
1075
+ return;
1076
+ }
1077
+ if (crossSiteRequest(request)) {
1078
+ response.writeHead(403, {
1079
+ "content-type": "application/json; charset=utf-8",
1080
+ "cache-control": "no-store"
1081
+ });
1082
+ response.end(JSON.stringify({ error: "已拒绝跨站请求。" }));
1083
+ return;
1084
+ }
1085
+ if (process.platform !== "win32") {
1086
+ response.writeHead(501, {
1087
+ "content-type": "application/json; charset=utf-8",
1088
+ "cache-control": "no-store"
1089
+ });
1090
+ response.end(JSON.stringify({ error: "当前平台使用系统默认打开方式。" }));
1091
+ return;
1092
+ }
1093
+ try {
1094
+ const body = JSON.parse(await readRequestBody(request));
1095
+ if (typeof body.path !== "string" || body.path.trim() === "") {
1096
+ response.writeHead(400, {
1097
+ "content-type": "application/json; charset=utf-8",
1098
+ "cache-control": "no-store"
1099
+ });
1100
+ response.end(JSON.stringify({ error: "目录路径无效。" }));
1101
+ return;
1102
+ }
1103
+ await foregroundExplorer.open(body.path);
1104
+ response.writeHead(200, {
1105
+ "content-type": "application/json; charset=utf-8",
1106
+ "cache-control": "no-store"
1107
+ });
1108
+ response.end(JSON.stringify({
1109
+ opened: true,
1110
+ foreground: true
1111
+ }));
1112
+ } catch (error) {
1113
+ ctx.logger.warn("foreground explorer open failed: %s", error);
1114
+ response.writeHead(error instanceof SyntaxError ? 400 : 503, {
1115
+ "content-type": "application/json; charset=utf-8",
1116
+ "cache-control": "no-store"
1117
+ });
1118
+ response.end(JSON.stringify({ error: error instanceof SyntaxError ? "目录路径格式无效。" : "无法在前台打开资源管理器。" }));
1119
+ }
1120
+ }
1121
+ });
847
1122
  return () => {
848
1123
  disposeDependencyInstaller();
1124
+ foregroundExplorer.dispose();
849
1125
  disposeConnectors();
850
1126
  disposeDependencies();
1127
+ disposeExplorer();
851
1128
  disposePreferences();
852
1129
  };
853
1130
  }, "michengai-codex-ui: catalogs");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-ui",
3
- "version": "0.2.85",
3
+ "version": "0.2.87",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -53,6 +53,11 @@
53
53
  "platform": "web"
54
54
  }
55
55
  },
56
+ "scripts": {
57
+ "build": "tsc --noEmit && tsdown",
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "pnpm run typecheck && vitest run tests/client-runtime.integration.spec.ts && tsdown && node tests/run-assertions.mjs"
60
+ },
56
61
  "peerDependencies": {
57
62
  "@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
58
63
  "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.0 <0.2.0",
@@ -98,10 +103,5 @@
98
103
  "tsx": "^4.22.4",
99
104
  "typescript": "^6.0.3",
100
105
  "vitest": "^4.1.8"
101
- },
102
- "scripts": {
103
- "build": "tsc --noEmit && tsdown",
104
- "typecheck": "tsc --noEmit",
105
- "test": "pnpm run typecheck && vitest run tests/client-runtime.integration.spec.ts && tsdown && node tests/run-assertions.mjs"
106
106
  }
107
- }
107
+ }