@michengai/dsh-codex-ui 0.2.86 → 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,23 @@
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
+
7
24
  ## 0.2.86 — 2026-08-25
8
25
 
9
26
  Companion release: `@michengai/dsh-codex-suite-installer@0.1.3`.
@@ -4,6 +4,23 @@
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
+
7
24
  ## 0.2.86 — 2026-08-25
8
25
 
9
26
  配套版本:`@michengai/dsh-codex-suite-installer@0.1.3`。
package/dist/client.js CHANGED
@@ -1334,8 +1334,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1334
1334
  .dcu-wb-rename-actions{display:flex;justify-content:flex-end;gap:8px}
1335
1335
  .dcu-wb-delete-button{color:var(--dsw-alias-state-error-primary)!important}
1336
1336
  .dcu-wb-delete-copy{margin:0;color:var(--dcu-sidebar-secondary);font-size:13px;line-height:20px}
1337
- .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}
1338
- .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)}
1339
1340
  `;
1340
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}}`;
1341
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}`;
@@ -1605,6 +1606,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1605
1606
  await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(value);
1606
1607
  });
1607
1608
  };
1609
+ const openPathImmediately = (path) => {
1610
+ setMenu(void 0);
1611
+ setHeaderMenu(void 0);
1612
+ run("open-path", () => openPath(path));
1613
+ };
1608
1614
  const toggleGroup = (key, defaultOpen = true) => {
1609
1615
  setExpanded((current) => ({
1610
1616
  ...current,
@@ -1816,7 +1822,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1816
1822
  setPinnedWorkspaceIds((ids) => togglePinnedWorkspace(ids, workspace.workspaceId));
1817
1823
  setMenu(void 0);
1818
1824
  }
1819
- if (id === "openPath") run("open-path", () => openPath(workspace.path));
1825
+ if (id === "openPath") openPathImmediately(workspace.path);
1820
1826
  if (id === "delete") {
1821
1827
  setDeleteTarget({
1822
1828
  id: workspace.workspaceId,
@@ -2040,7 +2046,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2040
2046
  setMenu(void 0);
2041
2047
  }
2042
2048
  if (action === "fork") run("fork", () => forkSession(id));
2043
- if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
2049
+ if (action === "openPath" && path !== void 0) openPathImmediately(path);
2044
2050
  if (action === "copyPath") copy(path);
2045
2051
  if (action === "copyTitle") copy(session.displayTitle);
2046
2052
  if (action === "copyId") copy(id);
@@ -2380,7 +2386,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2380
2386
  setMenu(void 0);
2381
2387
  }
2382
2388
  if (action === "fork") run("fork", () => forkSession(id));
2383
- if (action === "openPath" && session.cwd !== void 0) run("open-path", () => openPath(session.cwd));
2389
+ if (action === "openPath" && session.cwd !== void 0) openPathImmediately(session.cwd);
2384
2390
  if (action === "copyPath") copy(session.cwd);
2385
2391
  if (action === "copyTitle") copy(title);
2386
2392
  if (action === "copyId") copy(id);
@@ -2417,7 +2423,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2417
2423
  setError(void 0);
2418
2424
  }
2419
2425
  if (action === "fork") run("fork", () => forkSession(id));
2420
- if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
2426
+ if (action === "openPath" && path !== void 0) openPathImmediately(path);
2421
2427
  if (action === "copyPath") copy(path);
2422
2428
  if (action === "copyTitle") copy(session.displayTitle);
2423
2429
  if (action === "copyId") copy(id);
@@ -2446,6 +2452,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2446
2452
  },
2447
2453
  closeLabel: t("sessions.close"),
2448
2454
  title: renameTarget?.kind === "workspace" ? t("workspace.rename") : t("sessions.rename"),
2455
+ description: t("sessions.renameDescription"),
2449
2456
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2450
2457
  className: "dcu-wb-rename-actions",
2451
2458
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -2463,6 +2470,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2463
2470
  }),
2464
2471
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2465
2472
  className: "dcu-wb-rename-input",
2473
+ "aria-label": renameTarget?.kind === "workspace" ? t("workspace.rename") : t("sessions.rename"),
2466
2474
  value: renameDraft,
2467
2475
  autoFocus: true,
2468
2476
  onFocus: (event) => {
@@ -2675,6 +2683,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2675
2683
  },
2676
2684
  closeLabel: t("sessions.close"),
2677
2685
  title: t("sessions.rename"),
2686
+ description: t("sessions.renameDescription"),
2678
2687
  footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2679
2688
  className: "dcu-wb-rename-actions",
2680
2689
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -2692,6 +2701,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2692
2701
  }),
2693
2702
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2694
2703
  className: "dcu-wb-rename-input",
2704
+ "aria-label": t("sessions.rename"),
2695
2705
  value: renameDraft,
2696
2706
  autoFocus: true,
2697
2707
  onFocus: (event) => {
@@ -4327,6 +4337,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4327
4337
  "sessions.markRead": "标记为已读",
4328
4338
  "sessions.unread": "未读",
4329
4339
  "sessions.rename": "重命名会话",
4340
+ "sessions.renameDescription": "保持简短且易于识别",
4330
4341
  "sessions.archive": "归档会话",
4331
4342
  "sessions.delete": "删除会话",
4332
4343
  "sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
@@ -4449,6 +4460,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4449
4460
  "sessions.markRead": "Mark as read",
4450
4461
  "sessions.unread": "Unread",
4451
4462
  "sessions.rename": "Rename conversation",
4463
+ "sessions.renameDescription": "Keep it short and easy to recognize.",
4452
4464
  "sessions.archive": "Archive conversation",
4453
4465
  "sessions.delete": "Delete conversation",
4454
4466
  "sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
@@ -4505,6 +4517,30 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4505
4517
  "turns.untitled": "Untitled prompt",
4506
4518
  "turns.jump": "Jump to turn {index}: {summary}"
4507
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";
4508
4544
  const SETTINGS_NAV_ICON_HTML = {
4509
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\"/>",
4510
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\"/>",
@@ -4534,9 +4570,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4534
4570
  function applySettingsNavIcon(button) {
4535
4571
  const id = settingsNavIconId(button.textContent ?? "");
4536
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
+ }
4537
4584
  const svg = button.querySelector("svg");
4538
- if (svg === null) return false;
4539
- 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;
4540
4587
  svg.setAttribute("data-dcu-nav-icon", id);
4541
4588
  svg.setAttribute("viewBox", id === "archive" ? "0 0 20 20" : id === "about" ? "0 0 14 14" : "0 0 16 16");
4542
4589
  svg.setAttribute("fill", "none");
@@ -4581,6 +4628,105 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4581
4628
  };
4582
4629
  }
4583
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
4584
4730
  //#region src/client/conversation-dom.ts
4585
4731
  /**
4586
4732
  * DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
@@ -4828,7 +4974,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4828
4974
  "sessions",
4829
4975
  "workspaces",
4830
4976
  "layout",
4831
- "locale"
4977
+ "locale",
4978
+ "connection"
4832
4979
  ];
4833
4980
  function hasDeleteSession(value) {
4834
4981
  return value !== null && typeof value === "object" && "deleteSession" in value && typeof value.deleteSession === "function";
@@ -4840,8 +4987,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4840
4987
  en
4841
4988
  }), "michengai-codex-ui: dictionaries");
4842
4989
  const t = ctx.locale.bind(NS);
4990
+ const connection = ctx.get("connection");
4991
+ const openPath = (path) => openPathInHost(connection, path);
4843
4992
  ctx.effect(() => observeSlimSidebar(), "michengai-codex-ui: slim sidebar");
4844
4993
  ctx.effect(() => observeSettingsNavIcons(), "michengai-codex-ui: settings nav icons");
4994
+ ctx.effect(() => observePermissionLabels(ctx.locale), "michengai-codex-ui: permission labels");
4845
4995
  ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
4846
4996
  const companionSlots = createCompanionTabSource(ctx.slots);
4847
4997
  ctx.slots.inject("sidebar", () => ctx.slots.register({
@@ -4884,7 +5034,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4884
5034
  deleteSession,
4885
5035
  forkSession,
4886
5036
  renameSession,
4887
- openPath: (path) => ctx.workspaces.openPath(path),
5037
+ openPath,
4888
5038
  companionSlots
4889
5039
  })
4890
5040
  }, CodexSidebar));
@@ -4922,7 +5072,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4922
5072
  deleteSession,
4923
5073
  deleteWorkspace: (workspaceId) => ctx.workspaces.delete(workspaceId),
4924
5074
  forkSession,
4925
- openPath: (path) => ctx.workspaces.openPath(path),
5075
+ openPath,
4926
5076
  openSession: (sessionId) => {
4927
5077
  ctx.sessions.open(sessionId);
4928
5078
  },
@@ -4962,7 +5112,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4962
5112
  ctx.slots.inject("settings.section", () => ctx.slots.register({
4963
5113
  name: "settings.section",
4964
5114
  id: "about",
4965
- order: 100,
5115
+ order: Number.MAX_SAFE_INTEGER,
4966
5116
  label: () => t("about.nav"),
4967
5117
  locale: NS
4968
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.86",
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
+ }