@michengai/dsh-codex-ui 0.2.95 → 0.2.97

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,39 @@
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.97 — 2026-09-01
8
+
9
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.14`.
10
+
11
+ ### Compatibility
12
+
13
+ - Adopted the official DSH alpha turn navigator while preserving the legacy navigator on older runtimes, with runtime capability gating and resilient left-side mirroring that no longer depends on private stylesheet names or host DOM nesting.
14
+ - Preserved upstream conversation width dragging, adaptive transcript sizing, and turn navigation while retaining the Codex composer treatment.
15
+
16
+ ### Fixed
17
+
18
+ - Reworked About-page companion installs with live pnpm progress, accurate Profile mount verification, bulk installation of missing or outdated plugins, stale junction cleanup, and correct Desktop versus standalone Web restart guidance.
19
+ - Hardened pnpm discovery for GUI environments without trusting unrelated package-manager entry points or leaking the inferred entry into later child processes.
20
+ - Added strict progress payload validation and extended slow Desktop bundle reconciliation tolerance.
21
+ - Filtered and frame-coalesced navigator DOM observation so streaming responses do not trigger repeated document-wide scans.
22
+
23
+ ### Dependencies
24
+
25
+ - Prepared Suite Installer `0.1.14` to resolve the latest exact companion versions after this release is published.
26
+
27
+ ## 0.2.96 — 2026-09-01
28
+
29
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.13`.
30
+
31
+ ### Fixed
32
+
33
+ - Opened About immediately when the IM companion is not installed instead of showing General settings while waiting for a missing section to time out; the installed IM settings route is unchanged.
34
+ - Predeclared the nonessential `protobufjs` and `koffi` install scripts as disabled before About-page plugin installs, preventing pnpm's ignored-build placeholder from blocking an otherwise safe install.
35
+
36
+ ### Dependencies
37
+
38
+ - Refreshed the Suite Installer against the latest exact member versions from the official npm registry at release time.
39
+
7
40
  ## 0.2.95 — 2026-09-01
8
41
 
9
42
  Companion release: `@michengai/dsh-codex-suite-installer@0.1.12`.
@@ -4,6 +4,39 @@
4
4
 
5
5
  本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
6
6
 
7
+ ## 0.2.97 — 2026-09-01
8
+
9
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.14`。
10
+
11
+ ### 兼容性
12
+
13
+ - 在新版 DSH 中采用官方轮次导航,并为旧运行时保留自定义导航;通过运行时能力判断和更稳健的左侧镜像,不再依赖私有样式文件名或宿主 DOM 层级。
14
+ - 保留官方会话宽度拖拽、自适应正文宽度与轮次导航,同时继续使用 Codex 风格输入区。
15
+
16
+ ### 修复
17
+
18
+ - 重构“关于”页配套插件安装:显示 pnpm 实时进度、以实际进入 Profile 为成功标准、批量安装缺失或可更新插件、清理残留 Junction,并正确区分 Desktop 自动重载与独立 Web 手动重启。
19
+ - 强化 GUI 环境下的 pnpm 定位,不再信任其他包管理器入口,也不会把推断出的入口泄漏给后续子进程。
20
+ - 严格校验安装进度数据,并为 Desktop 较慢的 bundles 写入留出更充足的等待时间。
21
+ - 对轮次导航 DOM 监听增加相关变更过滤和逐帧合并,避免流式回答触发反复全文档扫描。
22
+
23
+ ### 依赖
24
+
25
+ - 准备 Suite Installer `0.1.14`,在本版本发布后解析全部配套插件的最新精确版本。
26
+
27
+ ## 0.2.96 — 2026-09-01
28
+
29
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.13`。
30
+
31
+ ### 修复
32
+
33
+ - 未安装 IM 配套插件时,点击 IM 助理会立即进入“关于”,不再先显示通用设置并等待缺失分区超时;已安装时仍正常进入 IM 设置。
34
+ - “关于”页安装插件前会预先将非必要的 `protobufjs` 与 `koffi` 安装脚本声明为禁用,避免 pnpm 写入 ignored-build 占位配置并阻断安全安装。
35
+
36
+ ### 依赖
37
+
38
+ - 发布时通过官方 npm 源重新解析 Suite Installer 全部成员的最新精确版本。
39
+
7
40
  ## 0.2.95 — 2026-09-01
8
41
 
9
42
  配套版本:`@michengai/dsh-codex-suite-installer@0.1.12`。
package/dist/client.js CHANGED
@@ -18,6 +18,16 @@ window.__ModuleLoader__.load({
18
18
  if (match !== void 0) return match;
19
19
  }
20
20
  }
21
+ /**
22
+ * 可选插件未注册设置入口时,直接打开兜底页,避免先展示通用设置并等待导航超时。
23
+ */
24
+ function routeOptionalSettingsSection(available, openRequested, openFallback) {
25
+ if (available) {
26
+ openRequested();
27
+ return;
28
+ }
29
+ openFallback();
30
+ }
21
31
  let cancelPendingNavigation;
22
32
  const SETTINGS_NAVIGATION_TIMEOUT_MS = 4e3;
23
33
  function openSettingsSection(root, label, onMissing, onSelected) {
@@ -724,87 +734,12 @@ window.__ModuleLoader__.load({
724
734
  }
725
735
  //#endregion
726
736
  //#region src/client/conversation-bubbles.ts
727
- /** DSH 右侧气泡改成 Codex 的左对齐紧凑卡片,不改会话数据。 */
737
+ /** 撤回旧版 Codex 用户卡片,恢复 DSH 官方问题气泡。 */
728
738
  const USER_BUBBLE_STYLE_ID = "dcu-user-bubble-style";
729
- const USER_BUBBLE_STYLE = `
730
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root],
731
- [data-conversation-scroll] [data-pending-steering][data-time-hover-root]{align-items:flex-start!important}
732
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root]>div,
733
- [data-conversation-scroll] [data-pending-steering][data-time-hover-root]>div{align-items:flex-start!important;max-width:min(640px,100%)!important}
734
- [data-conversation-scroll] [data-dcu-user-source]{display:none!important}
735
- [data-conversation-scroll] [data-dcu-user-card],
736
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root]>div>[class*="bubble"],
737
- [data-conversation-scroll] [data-pending-steering][data-time-hover-root]>div>[class*="bubble"]{background:color-mix(in srgb,var(--dsw-specific-bubble,rgba(255,255,255,.08)) 72%,transparent);color:var(--dsw-alias-label-primary,currentColor);border-radius:16px;padding:10px 14px;font-size:14px;line-height:20px;max-width:100%;box-sizing:border-box}
738
- [data-conversation-scroll] [data-dcu-user-title]{display:block;color:var(--dsw-alias-label-primary,currentColor);font-size:14px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
739
- [data-conversation-scroll] [data-dcu-user-sub]{display:block;margin-top:2px;color:var(--dsw-alias-label-tertiary,currentColor);font-size:13px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
740
- `;
741
- function splitUserCard(text) {
742
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "");
743
- if (lines.length >= 2) return {
744
- title: lines[0] ?? text,
745
- sub: lines.slice(1).join(" ")
746
- };
747
- return { title: text.trim() };
748
- }
749
- function findPlainBubble(stack) {
750
- for (const child of stack.children) {
751
- if (!(child instanceof HTMLElement)) continue;
752
- if (child.dataset.dcuUserCard !== void 0) continue;
753
- if (child.matches("[class*=\"bubble\"]")) return child;
754
- }
755
- }
756
- function isPlainTextBubble(bubble) {
757
- return bubble.querySelector("pre,table,img,[class*=\"Json\"]") === null;
758
- }
759
- /** 撤销某个气泡的替换装饰:删除卡片并恢复原气泡显示。 */
760
- function rollbackUserCard(stack) {
761
- for (const card of stack.querySelectorAll("[data-dcu-user-card]")) card.remove();
762
- for (const source of stack.querySelectorAll("[data-dcu-user-source]")) delete source.dataset.dcuUserSource;
763
- }
764
- function decorateUserBubbles(root) {
765
- const rows = root.querySelectorAll("[data-chat-flow-kind=\"user\"] [data-time-hover-root], [data-pending-steering][data-time-hover-root]");
766
- for (const row of rows) {
767
- const stack = row.firstElementChild;
768
- if (!(stack instanceof HTMLElement)) continue;
769
- const bubble = findPlainBubble(stack);
770
- if (bubble === void 0 || !isPlainTextBubble(bubble)) {
771
- rollbackUserCard(stack);
772
- continue;
773
- }
774
- const text = (bubble.textContent ?? "").replace(/\s+\n/g, "\n").trim();
775
- if (text === "") {
776
- rollbackUserCard(stack);
777
- continue;
778
- }
779
- bubble.dataset.dcuUserSource = "";
780
- let card = stack.querySelector(":scope > [data-dcu-user-card]");
781
- if (card === null) {
782
- card = bubble.ownerDocument.createElement("div");
783
- card.dataset.dcuUserCard = "";
784
- bubble.after(card);
785
- }
786
- if (card.dataset.dcuUserText === text) continue;
787
- const parts = splitUserCard(text);
788
- const title = bubble.ownerDocument.createElement("span");
789
- title.dataset.dcuUserTitle = "";
790
- title.textContent = parts.title;
791
- card.replaceChildren(title);
792
- if (parts.sub !== void 0 && parts.sub !== "") {
793
- const sub = bubble.ownerDocument.createElement("span");
794
- sub.dataset.dcuUserSub = "";
795
- sub.textContent = parts.sub;
796
- card.append(sub);
797
- }
798
- card.dataset.dcuUserText = text;
799
- }
800
- }
801
- function ensureUserBubbleStyle(doc) {
802
- if (doc.getElementById("dcu-user-bubble-style") !== null) return;
803
- if (doc.head === null) return;
804
- const style = doc.createElement("style");
805
- style.id = USER_BUBBLE_STYLE_ID;
806
- style.textContent = USER_BUBBLE_STYLE;
807
- doc.head.append(style);
739
+ function restoreOfficialUserBubbles(root) {
740
+ ("getElementById" in root ? root : root.ownerDocument)?.getElementById(USER_BUBBLE_STYLE_ID)?.remove();
741
+ for (const card of root.querySelectorAll("[data-dcu-user-card]")) card.remove();
742
+ for (const source of root.querySelectorAll("[data-dcu-user-source]")) delete source.dataset.dcuUserSource;
808
743
  }
809
744
  //#endregion
810
745
  //#region src/client/conversation-header.ts
@@ -955,16 +890,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
955
890
  style.textContent = CONVERSATION_HEADER_STYLE;
956
891
  doc.head.append(style);
957
892
  }
958
- const CONVERSATION_DECORATION_SELECTOR = "header, [data-conversation-scroll], [data-chat-flow-kind=\"user\"], [data-pending-steering]";
893
+ const CONVERSATION_DECORATION_SELECTOR = "header";
959
894
  function conversationDecorationMutation(records) {
960
- const relevant = (node) => node instanceof Element && (node.matches(CONVERSATION_DECORATION_SELECTOR) || node.closest("header, [data-chat-flow-kind=\"user\"], [data-pending-steering]") !== null || node.querySelector(CONVERSATION_DECORATION_SELECTOR) !== null);
895
+ const relevant = (node) => node instanceof Element && (node.matches(CONVERSATION_DECORATION_SELECTOR) || node.closest(CONVERSATION_DECORATION_SELECTOR) !== null || node.querySelector(CONVERSATION_DECORATION_SELECTOR) !== null);
961
896
  return records.some((record) => relevant(record.target) || [...record.addedNodes].some(relevant) || [...record.removedNodes].some(relevant));
962
897
  }
963
- /** 观察会话顶栏与用户气泡;只对相关子树变更按帧合并,流式回答不会触发全文档扫描。 */
898
+ /** 观察会话顶栏;只对相关子树变更按帧合并,流式回答不会触发全文档扫描。 */
964
899
  function observeConversationHeader(doc = document) {
965
900
  if (doc.head === null || doc.body === null) return () => {};
966
901
  ensureStyle(doc);
967
- ensureUserBubbleStyle(doc);
902
+ restoreOfficialUserBubbles(doc);
968
903
  let applying = false;
969
904
  let frame;
970
905
  let watchedTabs;
@@ -984,7 +919,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
984
919
  syncTabSlider(doc);
985
920
  decorateConversationTitle(doc);
986
921
  decorateSessionLogDownload(doc);
987
- decorateUserBubbles(doc);
988
922
  } finally {
989
923
  applying = false;
990
924
  }
@@ -3532,7 +3466,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3532
3466
  const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
3533
3467
  const SIDEBAR_COLLAPSE_SETTLE_MS = 140;
3534
3468
  const stylesheet$3 = `
3535
- .dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-wide-width:240px;--dcu-sidebar-primary:#393d3e;--dcu-sidebar-secondary:#676b6c;--dcu-sidebar-tertiary:#9a9f9f;--dcu-sidebar-navigation:#4e5253;--dcu-sidebar-icon:#4e5253;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);--dcu-tip-bg:#ffffff;--dcu-tip-shadow:0 10px 32px rgba(31,39,36,.22);height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;background:var(--dsw-alias-bg-base);color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{--dcu-sidebar-primary:#b9bab9;--dcu-sidebar-secondary:#909191;--dcu-sidebar-tertiary:#666867;--dcu-sidebar-navigation:#b9bab9;--dcu-sidebar-icon:#afafaf;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08);--dcu-tip-bg:#2a2e2c;--dcu-tip-shadow:0 10px 30px rgba(0,0,0,.28)}
3469
+ .dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--dcu-sidebar-wide-width:240px;--dcu-sidebar-primary:#393d3e;--dcu-sidebar-secondary:#676b6c;--dcu-sidebar-tertiary:#9a9f9f;--dcu-sidebar-navigation:#4e5253;--dcu-sidebar-icon:#4e5253;--dcu-sidebar-hover:#dfe8e5;--dcu-sidebar-border:rgba(37,46,41,.10);--dcu-tip-bg:#ffffff;--dcu-tip-shadow:0 10px 32px rgba(31,39,36,.22);height:100%;min-width:0;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;background:#eef7f5;color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font)}body[data-ds-dark-theme] .dcu-root{background:#1d2120;--dcu-sidebar-primary:#b9bab9;--dcu-sidebar-secondary:#909191;--dcu-sidebar-tertiary:#666867;--dcu-sidebar-navigation:#b9bab9;--dcu-sidebar-icon:#afafaf;--dcu-sidebar-hover:#303432;--dcu-sidebar-border:rgba(255,255,255,.08);--dcu-tip-bg:#2a2e2c;--dcu-tip-shadow:0 10px 30px rgba(0,0,0,.28)}
3536
3470
  .dcu-expanded-shell{display:flex;width:var(--dcu-sidebar-wide-width);min-height:0;flex:1 1 0;overflow:hidden;flex-direction:column;transform-origin:left center;transition:opacity 140ms ease-out,transform 180ms cubic-bezier(.16,1,.3,1);animation:dcu-sidebar-expanded-in 180ms cubic-bezier(.16,1,.3,1)}.dcu-compact-shell{display:none;width:56px}.dcu-root.dcu-collapsing .dcu-expanded-shell{opacity:0;transform:translateX(-6px);pointer-events:none}.dcu-root.dcu-compact .dcu-expanded-shell{display:none}.dcu-root.dcu-compact .dcu-compact-shell{display:flex;min-height:0;flex:1;flex-direction:column;align-items:center;animation:dcu-sidebar-compact-in 140ms ease-out}@keyframes dcu-sidebar-expanded-in{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}@keyframes dcu-sidebar-compact-in{from{opacity:0;transform:translateX(-4px)}to{opacity:1;transform:none}}
3537
3471
  .dcu-root *{box-sizing:border-box}.dcu-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:8px;height:60px;padding:8px 8px 8px 12px}.dcu-brand{border:0;background:transparent;color:inherit;padding:0;display:flex;align-items:center;min-width:0;overflow:hidden}.dcu-brand svg{display:block;width:auto;max-width:100%;height:24px;min-width:0}.dcu-head-actions{display:grid;grid-auto-flow:column;grid-auto-columns:28px;align-items:center;column-gap:8px;height:28px}
3538
3472
  .dcu-icon,.dcu-menu button,.dcu-footer-link{appearance:none;border:0;background:transparent;color:inherit;font:inherit;cursor:pointer}.dcu-icon{display:grid;place-items:center;width:36px;height:36px;border-radius:8px;color:var(--dcu-sidebar-icon)}.dcu-head .dcu-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:0;padding:0;border-radius:50%;line-height:0}.dcu-head .dcu-icon svg{display:block;width:16px;height:16px}
@@ -3545,7 +3479,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3545
3479
  .dcu-dependency-notice{margin:0 10px 8px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;padding:8px;color:var(--dcu-sidebar-secondary);font-size:12px;line-height:18px}
3546
3480
  [role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
3547
3481
  .dcu-search-scrim{position:fixed;z-index:10020;inset:0;display:flex;justify-content:center;align-items:flex-start;padding:72px 20px;background:color-mix(in srgb,#000 48%,transparent);animation:dcu-search-scrim-in 140ms ease-out}.dcu-search-dialog{width:min(560px,100%);max-height:min(640px,calc(100vh - 120px));overflow:auto;border:1px solid var(--dcu-sidebar-border);border-radius:16px;padding:10px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv4);animation:dcu-search-dialog-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes dcu-search-scrim-in{from{opacity:0}to{opacity:1}}@keyframes dcu-search-dialog-in{from{opacity:0;transform:translateY(-6px) scale(.985)}to{opacity:1;transform:none}}.dcu-search-input{margin-bottom:8px}.dcu-search-section{padding:6px 0}.dcu-search-title{padding:0 8px 4px;color:var(--dcu-sidebar-tertiary);font-size:12px;font-weight:600}.dcu-search-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;width:100%;min-height:34px;border:0;border-radius:8px;padding:6px 8px;background:transparent;color:var(--dcu-sidebar-primary);font:inherit;text-align:left;cursor:pointer}.dcu-search-row:hover,.dcu-search-row[data-active=true]{background:var(--dcu-sidebar-hover)}.dcu-search-main{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-search-detail{max-width:160px;overflow:hidden;color:var(--dcu-sidebar-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-search-empty{padding:18px 8px;color:var(--dcu-sidebar-tertiary);font-size:13px}@media (prefers-reduced-motion:reduce){.dcu-expanded-shell,.dcu-root.dcu-compact .dcu-compact-shell,.dcu-foot,.dcu-search-scrim,.dcu-search-dialog{animation:none;transition:none}}
3548
- [data-conversation-scroll]{--dsh-chat-content-width:800px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:24px}[data-conversation-scroll] [data-chat-flow]{gap:20px}[data-conversation-scroll] [data-composer-card]{min-height:96px;padding-top:10px;border-radius:16px;box-shadow:var(--dsw-shadow-lv3);transition:border-color 180ms ease,box-shadow 180ms ease}[data-conversation-scroll] [data-composer-card]:focus-within{border-color:var(--dsw-alias-button-info-fill);box-shadow:0 0 0 2px var(--dsw-static-deepseek-50),var(--dsw-shadow-lv3)}[data-conversation-scroll] [data-input-mirror]{min-height:44px}@media (prefers-reduced-motion:reduce){[data-conversation-scroll] [data-composer-card]{transition:none}}
3482
+ [data-conversation-scroll]{--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:24px}[data-conversation-scroll] [data-composer-card]{min-height:96px;padding-top:10px;border-radius:16px;box-shadow:var(--dsw-shadow-lv3);transition:border-color 180ms ease,box-shadow 180ms ease}[data-conversation-scroll] [data-composer-card]:focus-within{border-color:var(--dsw-alias-button-info-fill);box-shadow:0 0 0 2px var(--dsw-static-deepseek-50),var(--dsw-shadow-lv3)}[data-conversation-scroll] [data-input-mirror]{min-height:44px}@media (prefers-reduced-motion:reduce){[data-conversation-scroll] [data-composer-card]{transition:none}}
3483
+ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:has([data-dcu-official-turn-navigator]) .dcu-turn-navigator{display:none}
3484
+ [data-dcu-official-turn-navigator]{right:auto!important;left:calc(12px - (var(--dsh-composer-side-clearance) + 16px))!important}
3485
+ [data-dcu-official-turn-mark]{inset:0 auto 0 0!important}
3486
+ [data-dcu-official-turn-mark]:before{right:auto!important;left:0!important}
3487
+ [data-dcu-official-turn-tooltip]{right:auto!important;left:calc(100% + 10px)!important;animation:none!important}
3549
3488
  `;
3550
3489
  function MenuIcon({ children }) {
3551
3490
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3583,7 +3522,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3583
3522
  });
3584
3523
  }
3585
3524
  /** 搜索状态与大侧栏隔离:输入、悬停和开关弹窗都不能让工作区树跟着重渲染。 */
3586
- const SidebarSearch = (0, react.forwardRef)(function SidebarSearch({ openSession, startSession, t, useSessions, useWorkspaces, settingsSeat }, ref) {
3525
+ const SidebarSearch = (0, react.forwardRef)(function SidebarSearch({ imSettingsAvailable, openSession, startSession, t, useSessions, useWorkspaces, settingsSeat }, ref) {
3587
3526
  const [open, setOpen] = (0, react.useState)(false);
3588
3527
  const [query, setQuery] = (0, react.useState)("");
3589
3528
  const [activeIndex, setActiveIndex] = (0, react.useState)(0);
@@ -3613,6 +3552,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3613
3552
  settingsSeat,
3614
3553
  t
3615
3554
  ]);
3555
+ const openImSettings = (0, react.useCallback)(() => {
3556
+ routeOptionalSettingsSection(imSettingsAvailable, () => {
3557
+ selectExternalSection([t("sidebar.imSettings"), "IM助理"]);
3558
+ }, () => {
3559
+ selectSection(t("about.nav"));
3560
+ });
3561
+ }, [
3562
+ imSettingsAvailable,
3563
+ selectExternalSection,
3564
+ selectSection,
3565
+ t
3566
+ ]);
3616
3567
  const openSettings = (0, react.useCallback)(() => {
3617
3568
  settingsSeat.current?.querySelector("[aria-haspopup=\"dialog\"]")?.click();
3618
3569
  }, [settingsSeat]);
@@ -3698,7 +3649,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3698
3649
  keywords: t("search.settings"),
3699
3650
  run: () => {
3700
3651
  close();
3701
- selectExternalSection([t("sidebar.imSettings"), "IM助理"]);
3652
+ openImSettings();
3702
3653
  }
3703
3654
  },
3704
3655
  {
@@ -3728,6 +3679,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3728
3679
  ];
3729
3680
  }, [
3730
3681
  close,
3682
+ openImSettings,
3731
3683
  openSession,
3732
3684
  openSettings,
3733
3685
  selectExternalSection,
@@ -3872,6 +3824,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3872
3824
  selectSection(t("about.nav"));
3873
3825
  });
3874
3826
  };
3827
+ const openImSettings = () => {
3828
+ routeOptionalSettingsSection(showChannels, () => {
3829
+ selectExternalSection([t("sidebar.imSettings"), "IM助理"]);
3830
+ }, () => {
3831
+ selectSection(t("about.nav"));
3832
+ });
3833
+ };
3875
3834
  (0, react.useEffect)(() => {
3876
3835
  if (imTab === "channels" && !showChannels) setImTab("tasks");
3877
3836
  if (imTab === "schedule" && !showSchedule) setImTab("tasks");
@@ -4072,9 +4031,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4072
4031
  }),
4073
4032
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4074
4033
  type: "button",
4075
- onClick: () => {
4076
- selectExternalSection([t("sidebar.imSettings"), "IM助理"]);
4077
- },
4034
+ onClick: openImSettings,
4078
4035
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImAssistantIcon, {}) }), t("sidebar.assistant")]
4079
4036
  })
4080
4037
  ]
@@ -4230,9 +4187,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4230
4187
  type: "button",
4231
4188
  className: "dcu-icon",
4232
4189
  "aria-label": t("sidebar.assistant"),
4233
- onClick: () => {
4234
- selectExternalSection([t("sidebar.imSettings"), "IM助理"]);
4235
- },
4190
+ onClick: openImSettings,
4236
4191
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImAssistantIcon, {})
4237
4192
  })
4238
4193
  ]
@@ -4251,6 +4206,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4251
4206
  }),
4252
4207
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSearch, {
4253
4208
  ref: search,
4209
+ imSettingsAvailable: showChannels,
4254
4210
  settingsSeat,
4255
4211
  openSession,
4256
4212
  startSession,
@@ -4300,20 +4256,50 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4300
4256
  ];
4301
4257
  //#endregion
4302
4258
  //#region src/client/AboutSection.tsx
4259
+ const PROGRESS_PHASES = /* @__PURE__ */ new Set([
4260
+ "resolving",
4261
+ "downloading",
4262
+ "linking",
4263
+ "building"
4264
+ ]);
4303
4265
  const endpoint = "/api/michengai/codex-ui/dependencies";
4304
4266
  const stylesheet$2 = `
4305
- .dcu-about{color:var(--dsw-alias-label-primary)}.dcu-about h2{margin:0;font-size:20px;line-height:28px}.dcu-about-intro{margin:6px 0 22px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcu-about h3{margin:24px 0 8px;font-size:14px;line-height:20px}.dcu-about-features{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}.dcu-about-dependencies-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:24px}.dcu-about-dependencies-heading h3{margin:0}.dcu-about-dependencies-heading+.dcu-about-intro{margin-bottom:14px}.dcu-about-dependencies{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}.dcu-about-dependency{display:flex;align-items:center;gap:12px;min-height:64px;padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-about-dependency:last-child{border-bottom:0}.dcu-about-copy{min-width:0;flex:1}.dcu-about-name{font-size:13px;font-weight:600}.dcu-about-package{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-about-status{display:flex;align-items:center;gap:5px;font-size:12px}.dcu-about-status[data-installed=true]{color:var(--dsw-alias-state-success-primary)}.dcu-about-status[data-installed=false]{color:var(--dsw-alias-state-error-primary)}.dcu-about-status[data-update=true]{color:var(--dsw-alias-state-warning-primary)}.dcu-about-install{display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.dcu-about-install:hover:not(:disabled){background:var(--dsw-specific-menu-item-hover)}.dcu-about-install:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.dcu-about-install:disabled{cursor:wait;opacity:.65}.dcu-about-update-all{min-width:92px}.dcu-about-message{margin:10px 0 0;border-radius:8px;padding:9px 10px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dcu-about-message[data-error=true]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}
4267
+ .dcu-about{color:var(--dsw-alias-label-primary)}.dcu-about h2{margin:0;font-size:20px;line-height:28px}.dcu-about-intro{margin:6px 0 22px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.dcu-about h3{margin:24px 0 8px;font-size:14px;line-height:20px}.dcu-about-features{margin:0;padding-left:20px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}.dcu-about-dependencies-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:24px}.dcu-about-dependencies-heading h3{margin:0}.dcu-about-dependencies-heading+.dcu-about-intro{margin-bottom:14px}.dcu-about-dependencies{overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px}.dcu-about-dependency{display:flex;align-items:center;gap:12px;min-height:64px;padding:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.dcu-about-dependency:last-child{border-bottom:0}.dcu-about-copy{min-width:0;flex:1}.dcu-about-name{font-size:13px;line-height:20px;font-weight:600}.dcu-about-package{overflow:hidden;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.dcu-about-status{display:flex;align-items:center;gap:5px;font-size:12px}.dcu-about-status[data-installed=true]{color:var(--dsw-alias-state-success-primary)}.dcu-about-status[data-installed=false]{color:var(--dsw-alias-state-error-primary)}.dcu-about-status[data-update=true]{color:var(--dsw-alias-state-warning-primary)}.dcu-about-install{display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:32px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:6px 9px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.dcu-about-install:hover:not(:disabled){background:var(--dsw-specific-menu-item-hover)}.dcu-about-install:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.dcu-about-install:disabled{cursor:wait;opacity:.65}.dcu-about-update-all{min-width:92px}.dcu-about-message{margin:10px 0 0;border-radius:8px;padding:9px 10px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.dcu-about-message[data-error=true]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}.dcu-about-progress{display:grid;grid-template-columns:auto 1fr auto;gap:8px 10px;align-items:center;margin-top:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:9px 10px}.dcu-about-progress code{min-width:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dcu-about-progress-pct{color:var(--dsw-alias-label-tertiary);font-size:12px;font-variant-numeric:tabular-nums}.dcu-about-progress-bar{grid-column:1/-1;height:4px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2)}.dcu-about-progress-fill{height:100%;background:var(--dsw-alias-state-business-primary);transition:width .2s ease}.dcu-about-progress-fill[data-wave=true]{width:28%;animation:dcu-about-progress-wave 1.2s ease-in-out infinite}@keyframes dcu-about-progress-wave{0%{transform:translateX(-60%)}100%{transform:translateX(280%)}}
4306
4268
  `;
4307
4269
  function isDependencyStatus(value) {
4308
4270
  if (value === null || typeof value !== "object") return false;
4309
4271
  const status = value;
4310
4272
  return MANAGED_DEPENDENCIES.some((dependency) => dependency.id === status.id) && typeof status.packageName === "string" && typeof status.installed === "boolean" && typeof status.updateAvailable === "boolean";
4311
4273
  }
4274
+ function isInstallProgress(value) {
4275
+ if (value === null || typeof value !== "object") return false;
4276
+ const progress = value;
4277
+ const phase = progress.phase;
4278
+ const total = progress.total;
4279
+ const percent = progress.percent;
4280
+ const currentPackage = progress.currentPackage;
4281
+ return typeof progress.active === "boolean" && typeof progress.target === "string" && typeof progress.seconds === "number" && Number.isInteger(progress.seconds) && progress.seconds >= 0 && typeof progress.lastLine === "string" && (phase === null || typeof phase === "string" && PROGRESS_PHASES.has(phase)) && typeof progress.done === "number" && Number.isInteger(progress.done) && progress.done >= 0 && (total === null || typeof total === "number" && Number.isInteger(total) && total >= 0) && (percent === null || typeof percent === "number" && Number.isFinite(percent) && percent >= 0 && percent <= 100) && (currentPackage === null || typeof currentPackage === "string");
4282
+ }
4283
+ function installErrorText(error, t) {
4284
+ const message = error instanceof Error ? error.message : "";
4285
+ if (message.includes("没有进入当前 Profile") || message.includes("did not enter this profile")) return t("about.installUnchanged");
4286
+ return message !== "" ? message : t("about.installFailed");
4287
+ }
4288
+ function progressLabel(progress, t) {
4289
+ if (progress.phase !== null) {
4290
+ const current = progress.currentPackage === null || progress.currentPackage === "" ? "" : ` · ${progress.currentPackage}`;
4291
+ const done = progress.done > 0 ? ` · ${t("about.packagesDone").replace("{0}", String(progress.done))}` : "";
4292
+ return `${t(`about.progress.${progress.phase}`)}${current}${done}`;
4293
+ }
4294
+ if (progress.lastLine !== "") return `${progress.lastLine} (${progress.seconds}s)`;
4295
+ return t("about.progressHint");
4296
+ }
4312
4297
  /** Codex UI 的功能说明、配套管理插件状态与受限安装入口。 */
4313
4298
  function AboutSection({ t }) {
4314
4299
  const [dependencies, setDependencies] = (0, react.useState)([]);
4315
4300
  const [state, setState] = (0, react.useState)("loading");
4316
4301
  const [installing, setInstalling] = (0, react.useState)();
4302
+ const [progress, setProgress] = (0, react.useState)();
4317
4303
  const [message, setMessage] = (0, react.useState)();
4318
4304
  const alive = (0, react.useRef)(true);
4319
4305
  const root = (0, react.useRef)(null);
@@ -4373,6 +4359,31 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4373
4359
  observer.disconnect();
4374
4360
  };
4375
4361
  }, [load]);
4362
+ (0, react.useEffect)(() => {
4363
+ if (installing === void 0) {
4364
+ setProgress(void 0);
4365
+ return;
4366
+ }
4367
+ let cancelled = false;
4368
+ const pull = async () => {
4369
+ try {
4370
+ const response = await fetch(`${endpoint}?action=progress`, { cache: "no-store" });
4371
+ const payload = await response.json();
4372
+ if (cancelled || !response.ok || !isInstallProgress(payload.progress)) return;
4373
+ setProgress(payload.progress);
4374
+ } catch {
4375
+ if (!cancelled) return;
4376
+ }
4377
+ };
4378
+ pull();
4379
+ const timer = window.setInterval(() => {
4380
+ pull();
4381
+ }, 800);
4382
+ return () => {
4383
+ cancelled = true;
4384
+ window.clearInterval(timer);
4385
+ };
4386
+ }, [installing]);
4376
4387
  const install = async (id) => {
4377
4388
  if (installingRef.current !== void 0) return;
4378
4389
  installingRef.current = id;
@@ -4387,13 +4398,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4387
4398
  setState("ready");
4388
4399
  setMessage({
4389
4400
  error: false,
4390
- text: t("about.restartRequired")
4401
+ text: payload.autoReload === false ? t("about.restartManually") : t("about.restartRequired")
4391
4402
  });
4392
4403
  } catch (error) {
4393
4404
  if (!alive.current) return;
4394
4405
  setMessage({
4395
4406
  error: true,
4396
- text: error instanceof Error ? error.message : t("about.installFailed")
4407
+ text: installErrorText(error, t)
4397
4408
  });
4398
4409
  } finally {
4399
4410
  installingRef.current = void 0;
@@ -4414,20 +4425,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4414
4425
  setState("ready");
4415
4426
  setMessage({
4416
4427
  error: false,
4417
- text: payload.restartRequired === false ? t("about.upToDate") : t("about.restartRequired")
4428
+ text: payload.restartRequired === false ? t("about.upToDate") : payload.autoReload === false ? t("about.restartManually") : t("about.restartRequired")
4418
4429
  });
4419
4430
  } catch (error) {
4420
4431
  if (!alive.current) return;
4421
4432
  setMessage({
4422
4433
  error: true,
4423
- text: error instanceof Error ? error.message : t("about.installFailed")
4434
+ text: installErrorText(error, t)
4424
4435
  });
4425
4436
  } finally {
4426
4437
  installingRef.current = void 0;
4427
4438
  if (alive.current) setInstalling(void 0);
4428
4439
  }
4429
4440
  };
4430
- const updateCount = dependencies.filter((dependency) => dependency.installed && dependency.updateAvailable).length;
4441
+ const actionableCount = dependencies.filter((dependency) => !dependency.installed || dependency.updateAvailable).length;
4431
4442
  const title = (id) => t(`about.dependency.${id}`);
4432
4443
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4433
4444
  ref: root,
@@ -4454,7 +4465,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4454
4465
  }),
4455
4466
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4456
4467
  className: "dcu-about-dependencies-heading",
4457
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" && updateCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4468
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" && actionableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4458
4469
  className: "dcu-about-install dcu-about-update-all",
4459
4470
  type: "button",
4460
4471
  "aria-busy": installing === "all",
@@ -4516,6 +4527,26 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4516
4527
  ]
4517
4528
  }, dependency.id))
4518
4529
  }),
4530
+ installing !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4531
+ className: "dcu-about-progress",
4532
+ role: "status",
4533
+ children: [
4534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, { size: 14 }) }),
4535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: progress === void 0 ? t("about.progressHint") : progressLabel(progress, t) }),
4536
+ progress?.percent !== null && progress?.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4537
+ className: "dcu-about-progress-pct",
4538
+ children: [progress.percent, "%"]
4539
+ }),
4540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4541
+ className: "dcu-about-progress-bar",
4542
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4543
+ className: "dcu-about-progress-fill",
4544
+ "data-wave": progress?.percent === null || progress?.percent === void 0,
4545
+ style: progress?.percent === null || progress?.percent === void 0 ? void 0 : { width: `${progress.percent}%` }
4546
+ })
4547
+ })
4548
+ ]
4549
+ }),
4519
4550
  message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4520
4551
  className: "dcu-about-message",
4521
4552
  "data-error": message.error,
@@ -4807,7 +4838,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4807
4838
  "about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
4808
4839
  "about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
4809
4840
  "about.dependencies": "配套管理插件",
4810
- "about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键更新所有可更新项。可分别检查 Codex UI、专家、技能、归档、IM、定时任务和第三方插件市场。",
4841
+ "about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键安装缺失项并更新旧版本。可分别检查 Codex UI、专家、技能、归档、IM、定时任务和第三方插件市场。",
4811
4842
  "about.loading": "正在读取依赖安装状态…",
4812
4843
  "about.statusFailed": "暂时无法读取依赖安装状态。",
4813
4844
  "about.installed": "已安装",
@@ -4816,11 +4847,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4816
4847
  "about.install": "安装",
4817
4848
  "about.update": "更新",
4818
4849
  "about.installing": "安装中",
4819
- "about.updateAll": "全部更新",
4820
- "about.updatingAll": "更新中",
4821
- "about.upToDate": "所有已安装插件均为最新版本。",
4850
+ "about.updateAll": "全部安装/更新",
4851
+ "about.updatingAll": "安装/更新中",
4852
+ "about.upToDate": "所有配套插件均已安装并为最新版本。",
4822
4853
  "about.installFailed": "依赖安装失败,请稍后重试。",
4854
+ "about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
4823
4855
  "about.restartRequired": "正在热更新插件,窗口会自动刷新。",
4856
+ "about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
4857
+ "about.progressHint": "正在安装插件…",
4858
+ "about.packagesDone": "已完成 {0} 个包",
4859
+ "about.progress.resolving": "解析依赖",
4860
+ "about.progress.downloading": "下载中",
4861
+ "about.progress.linking": "写入文件",
4862
+ "about.progress.building": "构建中",
4824
4863
  "about.dependency.dsh": "DeepSeek Harness",
4825
4864
  "about.dependency.suite": "Codex 套件",
4826
4865
  "about.dependency.ui": "Codex UI",
@@ -4939,7 +4978,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4939
4978
  "about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
4940
4979
  "about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
4941
4980
  "about.dependencies": "Companion management plugins",
4942
- "about.dependenciesDescription": "Install or update each companion plugin separately, or update every available plugin at once. Check Codex UI, expert, skill, archive, IM, scheduled-task, and marketplace plugins on their own.",
4981
+ "about.dependenciesDescription": "Install or update each companion separately, or install missing plugins and update old versions in one action. Check Codex UI, expert, skill, archive, IM, scheduled-task, and marketplace plugins on their own.",
4943
4982
  "about.loading": "Loading dependency status…",
4944
4983
  "about.statusFailed": "Dependency status is temporarily unavailable.",
4945
4984
  "about.installed": "Installed",
@@ -4948,11 +4987,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4948
4987
  "about.install": "Install",
4949
4988
  "about.update": "Update",
4950
4989
  "about.installing": "Installing",
4951
- "about.updateAll": "Update all",
4952
- "about.updatingAll": "Updating",
4953
- "about.upToDate": "All installed plugins are up to date.",
4990
+ "about.updateAll": "Install/update all",
4991
+ "about.updatingAll": "Installing/updating",
4992
+ "about.upToDate": "All companion plugins are installed and up to date.",
4954
4993
  "about.installFailed": "Dependency installation failed. Try again later.",
4994
+ "about.installUnchanged": "The install command finished, but the plugin did not enter this profile. Try again.",
4955
4995
  "about.restartRequired": "Updating plugins now. The window will refresh automatically.",
4996
+ "about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
4997
+ "about.progressHint": "Installing plugin…",
4998
+ "about.packagesDone": "{0} packages done",
4999
+ "about.progress.resolving": "Resolving",
5000
+ "about.progress.downloading": "Downloading",
5001
+ "about.progress.linking": "Writing files",
5002
+ "about.progress.building": "Building",
4956
5003
  "about.dependency.dsh": "DeepSeek Harness",
4957
5004
  "about.dependency.suite": "Codex suite",
4958
5005
  "about.dependency.ui": "Codex UI",
@@ -5206,6 +5253,98 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5206
5253
  };
5207
5254
  }
5208
5255
  //#endregion
5256
+ //#region src/client/official-turn-navigator.ts
5257
+ const OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE = "data-dcu-official-turn-navigator";
5258
+ const OFFICIAL_TURN_MARK_ATTRIBUTE = "data-dcu-official-turn-mark";
5259
+ const OFFICIAL_TURN_TOOLTIP_ATTRIBUTE = "data-dcu-official-turn-tooltip";
5260
+ const OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE = "data-dcu-official-turn-navigator-supported";
5261
+ const CAPABILITIES_ENDPOINT = "/api/michengai/codex-ui/dependencies?action=capabilities";
5262
+ function isOfficialTurnNavigator(element) {
5263
+ return element instanceof HTMLElement && element.tagName === "NAV" && element.style.getPropertyValue("--turn-natural-height") !== "" && element.querySelector("button[type=\"button\"][aria-label]") !== null;
5264
+ }
5265
+ function markOfficialTurnNavigatorParts(navigator) {
5266
+ for (const button of navigator.querySelectorAll("button[type=\"button\"][aria-label]")) button.setAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE, "true");
5267
+ for (const tooltip of navigator.querySelectorAll("[role=\"tooltip\"]")) tooltip.setAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE, "true");
5268
+ }
5269
+ /** 标记实际挂载的官方轮次导航,避免依赖宿主样式文件名或会话 DOM 层级。 */
5270
+ function markOfficialTurnNavigators(root = document) {
5271
+ for (const element of root.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`)) {
5272
+ if (isOfficialTurnNavigator(element)) continue;
5273
+ element.removeAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE);
5274
+ for (const part of element.querySelectorAll(`[${OFFICIAL_TURN_MARK_ATTRIBUTE}],[${OFFICIAL_TURN_TOOLTIP_ATTRIBUTE}]`)) {
5275
+ part.removeAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE);
5276
+ part.removeAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE);
5277
+ }
5278
+ }
5279
+ for (const element of root.querySelectorAll("nav")) {
5280
+ if (!isOfficialTurnNavigator(element)) continue;
5281
+ element.setAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE, "true");
5282
+ markOfficialTurnNavigatorParts(element);
5283
+ }
5284
+ return root.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`).length;
5285
+ }
5286
+ function nodeTouchesTurnNavigator(node) {
5287
+ return node instanceof Element && (node.matches(`nav,[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) || node.closest(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) !== null || node.querySelector(`nav,[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) !== null);
5288
+ }
5289
+ function nodeInsideTurnNavigator(node) {
5290
+ return node instanceof Element && (node.matches(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) || node.closest(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) !== null);
5291
+ }
5292
+ function turnNavigatorMutationRelevant(records) {
5293
+ return records.some((record) => record.type === "attributes" ? record.target instanceof Element && record.target.matches("nav") : nodeInsideTurnNavigator(record.target) || [...record.addedNodes].some(nodeTouchesTurnNavigator) || [...record.removedNodes].some(nodeTouchesTurnNavigator));
5294
+ }
5295
+ async function runtimeSupportsOfficialTurnNavigator(fetchImpl) {
5296
+ try {
5297
+ const response = await fetchImpl(CAPABILITIES_ENDPOINT, { cache: "no-store" });
5298
+ if (!response.ok) return false;
5299
+ const payload = await response.json();
5300
+ if (payload === null || typeof payload !== "object") return false;
5301
+ const capabilities = payload.capabilities;
5302
+ return capabilities !== null && typeof capabilities === "object" && capabilities.officialTurnNavigator === true;
5303
+ } catch {
5304
+ return false;
5305
+ }
5306
+ }
5307
+ /** 官方导航可能在会话切换后异步挂载;仅对相关变更按帧检测,并在停用时清理标记。 */
5308
+ function observeOfficialTurnNavigators(doc = document, fetchImpl = fetch) {
5309
+ if (doc.body === null || doc.documentElement === null) return () => {};
5310
+ let active = true;
5311
+ let frame;
5312
+ const run = () => {
5313
+ frame = void 0;
5314
+ markOfficialTurnNavigators(doc);
5315
+ };
5316
+ const schedule = () => {
5317
+ if (frame !== void 0) return;
5318
+ frame = window.requestAnimationFrame(run);
5319
+ };
5320
+ const observer = new MutationObserver((records) => {
5321
+ if (turnNavigatorMutationRelevant(records)) schedule();
5322
+ });
5323
+ observer.observe(doc.body, {
5324
+ childList: true,
5325
+ subtree: true,
5326
+ attributes: true,
5327
+ attributeFilter: ["style"]
5328
+ });
5329
+ schedule();
5330
+ runtimeSupportsOfficialTurnNavigator(fetchImpl).then((supported) => {
5331
+ if (!active) return;
5332
+ if (supported) doc.documentElement.setAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE, "true");
5333
+ else doc.documentElement.removeAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE);
5334
+ });
5335
+ return () => {
5336
+ active = false;
5337
+ observer.disconnect();
5338
+ if (frame !== void 0) window.cancelAnimationFrame(frame);
5339
+ doc.documentElement.removeAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE);
5340
+ for (const element of doc.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}],[${OFFICIAL_TURN_MARK_ATTRIBUTE}],[${OFFICIAL_TURN_TOOLTIP_ATTRIBUTE}]`)) {
5341
+ element.removeAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE);
5342
+ element.removeAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE);
5343
+ element.removeAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE);
5344
+ }
5345
+ };
5346
+ }
5347
+ //#endregion
5209
5348
  //#region src/client/conversation-dom.ts
5210
5349
  /**
5211
5350
  * DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
@@ -5485,6 +5624,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5485
5624
  ctx.effect(() => observeSettingsNavIcons(), "michengai-codex-ui: settings nav icons");
5486
5625
  ctx.effect(() => observePermissionLabels(ctx.locale), "michengai-codex-ui: permission labels");
5487
5626
  ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
5627
+ ctx.effect(() => observeOfficialTurnNavigators(), "michengai-codex-ui: official turn navigator");
5488
5628
  const companionSlots = createCompanionTabSource(ctx.slots);
5489
5629
  ctx.slots.inject("sidebar", () => ctx.slots.register({
5490
5630
  name: "sidebar",
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
2
3
  import { mkdir, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
4
5
  import { basename, dirname, isAbsolute, join, resolve, sep, win32 } from "node:path";
@@ -56,6 +57,7 @@ function managedDependency(id) {
56
57
  const PROFILE_PENDING_UPDATES_FILE = ".dsh-pending-updates.json";
57
58
  const APPLY_PLUGIN_UPDATES_IPC = "apply-plugin-updates";
58
59
  const PLUGIN_INSTALL_TIMEOUT_MS = 6e5;
60
+ const PLUGIN_MOUNT_TIMEOUT_MS = 15e3;
59
61
  function optionalService(ctx, name) {
60
62
  if (ctx === void 0) return void 0;
61
63
  return typeof ctx.get === "function" ? ctx.get(name) : ctx[name];
@@ -111,18 +113,37 @@ function resolveDependencyRuntime(ctx, options = {}) {
111
113
  function profileDirectory(runtime) {
112
114
  return runtime.profileDir;
113
115
  }
114
- async function declaredPluginNames(runtime) {
116
+ async function readProfileManifest(runtime) {
115
117
  try {
116
- const manifest = JSON.parse(await readFile(resolve(profileDirectory(runtime), "package.json"), "utf8"));
117
- return [.../* @__PURE__ */ new Set([...Object.keys({
118
- ...manifest.devDependencies,
119
- ...manifest.dependencies
120
- }), ...manifest.dsh?.profile?.bundles ?? []])];
118
+ return JSON.parse(await readFile(resolve(profileDirectory(runtime), "package.json"), "utf8"));
121
119
  } catch (error) {
122
- if (error.code === "ENOENT") return [];
120
+ if (error.code === "ENOENT") return {};
123
121
  throw error;
124
122
  }
125
123
  }
124
+ async function declaredPluginNames(runtime) {
125
+ const manifest = await readProfileManifest(runtime);
126
+ return [.../* @__PURE__ */ new Set([...Object.keys({
127
+ ...manifest.devDependencies,
128
+ ...manifest.dependencies
129
+ }), ...manifest.dsh?.profile?.bundles ?? []])];
130
+ }
131
+ /** 真正挂进 Profile 的插件名单;残留 node_modules 或仅写在 dependencies 里都不算。 */
132
+ async function profileBundleNames(runtime) {
133
+ return (await readProfileManifest(runtime)).dsh?.profile?.bundles ?? [];
134
+ }
135
+ /** 安装缺失插件前移除卸载遗留的目录或 Junction;已挂载插件绝不触碰。 */
136
+ async function removeUnmountedPackagePath(packageName, runtime) {
137
+ if (!MANAGED_DEPENDENCIES.some((dependency) => dependency.packageName === packageName)) throw new Error("不支持清理该依赖。");
138
+ if (isManagedPackageDeclared(packageName, await profileBundleNames(runtime))) return;
139
+ const packagePath = resolve(profileDirectory(runtime), "node_modules", ...packageName.split("/"));
140
+ await rm(packagePath, {
141
+ recursive: true,
142
+ force: true,
143
+ maxRetries: 2,
144
+ retryDelay: 50
145
+ });
146
+ }
126
147
  /** 单独更新子插件时先卸套件,避免两套 patch 冲突。 */
127
148
  function pluginsToRemoveBeforeInstall(declared, installing) {
128
149
  if (SUITE_MEMBER_PACKAGES.includes(installing) && declared.includes("@michengai/dsh-codex-suite")) return [SUITE_PACKAGE];
@@ -228,6 +249,34 @@ function applyReleaseExclude(source, packageName, version) {
228
249
  if (section.test(source)) return source.replace(section, (match) => `${match.endsWith("\n") ? match : `${match}${eol}`}${entry}${eol}`);
229
250
  return `${source}${source === "" || source.endsWith("\n") ? "" : eol}minimumReleaseAgeExclude:${eol}${entry}${eol}`;
230
251
  }
252
+ /**
253
+ * pnpm 11 会在首次解析带 install script 的传递依赖时中止并写入占位值。
254
+ * 这些依赖的运行时不需要 postinstall,因此在调用 DSH plugin add 之前显式
255
+ * 记录拒绝执行,保证“关于”页单独安装和 Suite Installer 行为一致。
256
+ */
257
+ function applyRequiredBuildPolicies(source) {
258
+ const eol = source.includes("\r\n") ? "\r\n" : "\n";
259
+ const lines = source.split(/\r?\n/);
260
+ const start = lines.findIndex((line) => line === "allowBuilds:");
261
+ if (start === -1) return `${source}${source === "" || source.endsWith("\n") ? "" : eol}allowBuilds:${eol} protobufjs: false${eol} koffi: false${eol}`;
262
+ let end = start + 1;
263
+ while (end < lines.length && (lines[end] === "" || /^\s/.test(lines[end]))) end += 1;
264
+ const body = lines.slice(start + 1, end).filter((line) => !/^\s{2}(?:protobufjs|koffi):/.test(line));
265
+ lines.splice(start + 1, end - start - 1, " protobufjs: false", " koffi: false", ...body);
266
+ return lines.join(eol);
267
+ }
268
+ async function ensureRequiredBuildPolicies(runtime) {
269
+ const path = resolve(profileDirectory(runtime), "pnpm-workspace.yaml");
270
+ let source;
271
+ try {
272
+ source = await readFile(path, "utf8");
273
+ } catch (error) {
274
+ if (error.code !== "ENOENT") throw error;
275
+ source = "";
276
+ }
277
+ const next = applyRequiredBuildPolicies(source);
278
+ if (next !== source) await writeFile(path, next, "utf8");
279
+ }
231
280
  /** 将用户本次确认的精确版本加入 Profile 的 pnpm 发布时间保护例外。 */
232
281
  async function ensureLatestReleaseAllowed(packageName, version, runtime) {
233
282
  if (parseSemver(version) === void 0) throw new Error("npm 返回了无法识别的最新版本。");
@@ -277,9 +326,19 @@ function newerVersion(installed, latest) {
277
326
  for (let index = 0; index < current.core.length; index += 1) if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index];
278
327
  return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
279
328
  }
329
+ const OFFICIAL_TURN_NAVIGATOR_MIN_VERSION = "0.1.2-alpha.2";
330
+ /** 官方轮次导航从 DSH alpha.2 起成为运行时能力;版本判断不依赖其私有 DOM。 */
331
+ function supportsOfficialTurnNavigator(version) {
332
+ return version === OFFICIAL_TURN_NAVIGATOR_MIN_VERSION || newerVersion(OFFICIAL_TURN_NAVIGATOR_MIN_VERSION, version);
333
+ }
334
+ /** 只读取本地运行时清单,不访问 npm registry。 */
335
+ async function runtimeSupportsOfficialTurnNavigator(runtime = resolveDependencyRuntime()) {
336
+ const version = await installedPackageVersion("@deepseek-ai/dsh", runtime);
337
+ return version !== void 0 && supportsOfficialTurnNavigator(version);
338
+ }
280
339
  /** 返回当前 profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
281
340
  async function dependencyStatuses(runtime = resolveDependencyRuntime()) {
282
- const declaredNames = await declaredPluginNames(runtime);
341
+ const bundleNames = await profileBundleNames(runtime);
283
342
  return Promise.all(MANAGED_DEPENDENCIES.map(async (dependency) => {
284
343
  const version = await installedPackageVersion(dependency.packageName, runtime);
285
344
  if (version === void 0 && runtime.environmentKind === "desktop" && isOfficialRuntimePackage(dependency.packageName)) return {
@@ -287,7 +346,7 @@ async function dependencyStatuses(runtime = resolveDependencyRuntime()) {
287
346
  installed: true,
288
347
  updateAvailable: false
289
348
  };
290
- const declared = isOfficialRuntimePackage(dependency.packageName) || isManagedPackageDeclared(dependency.packageName, declaredNames);
349
+ const declared = isOfficialRuntimePackage(dependency.packageName) || isManagedPackageDeclared(dependency.packageName, bundleNames);
291
350
  if (version === void 0 || !isManagedPackageInstalled({
292
351
  installedVersion: version,
293
352
  declared
@@ -320,10 +379,6 @@ function requestDesktopHotUpdate(send = process.send) {
320
379
  send(APPLY_PLUGIN_UPDATES_IPC);
321
380
  return true;
322
381
  }
323
- function isRestartableInstallError(error) {
324
- const message = error instanceof Error ? error.message : String(error);
325
- return /完全退出桌面端|正在运行的插件|pnpm 仓库不一致/.test(message);
326
- }
327
382
  async function recordPendingUpdate(packageName, version, runtime) {
328
383
  const pendingPath = resolve(profileDirectory(runtime), PROFILE_PENDING_UPDATES_FILE);
329
384
  let packages = [];
@@ -379,15 +434,171 @@ async function recordDeclaredVersion(packageName, version, runtime) {
379
434
  };
380
435
  await writeFile(manifestPath, `${JSON.stringify(manifest, void 0, 2)}\n`, "utf8");
381
436
  }
437
+ const progressState = {
438
+ active: false,
439
+ target: "",
440
+ seconds: 0,
441
+ lastLine: "",
442
+ phase: null,
443
+ done: 0,
444
+ total: null,
445
+ percent: null,
446
+ currentPackage: null,
447
+ leftover: "",
448
+ startedAt: 0
449
+ };
450
+ function beginInstallProgress(target) {
451
+ progressState.active = true;
452
+ progressState.target = target;
453
+ progressState.startedAt = Date.now();
454
+ progressState.seconds = 0;
455
+ progressState.lastLine = "";
456
+ progressState.phase = null;
457
+ progressState.done = 0;
458
+ progressState.total = null;
459
+ progressState.percent = null;
460
+ progressState.currentPackage = null;
461
+ progressState.leftover = "";
462
+ }
463
+ function endInstallProgress() {
464
+ if (progressState.leftover !== "") applyProgressLine(progressState.leftover.trim());
465
+ progressState.active = false;
466
+ progressState.leftover = "";
467
+ }
468
+ function installProgressSnapshot() {
469
+ return {
470
+ active: progressState.active,
471
+ target: progressState.target,
472
+ seconds: progressState.active && progressState.startedAt > 0 ? Math.max(0, Math.round((Date.now() - progressState.startedAt) / 1e3)) : 0,
473
+ lastLine: progressState.lastLine,
474
+ phase: progressState.phase,
475
+ done: progressState.done,
476
+ total: progressState.total,
477
+ percent: progressState.percent,
478
+ currentPackage: progressState.currentPackage
479
+ };
480
+ }
481
+ function applyProgressLine(line) {
482
+ if (line === "") return;
483
+ const human = /Progress:\s*resolved\s+(\d+),\s*reused\s+(\d+),\s*downloaded\s+(\d+),\s*added\s+(\d+)/.exec(line);
484
+ if (human !== null) {
485
+ const resolved = Number(human[1]);
486
+ const added = Number(human[4]);
487
+ progressState.lastLine = line.slice(0, 200);
488
+ progressState.total = resolved;
489
+ progressState.done = added;
490
+ progressState.percent = resolved > 0 ? Math.max(1, Math.min(/done/i.test(line) ? 100 : 99, Math.round(added / resolved * 100))) : null;
491
+ if (added > 0) progressState.phase = "linking";
492
+ else if (Number(human[3]) > 0) progressState.phase = "downloading";
493
+ else progressState.phase = "resolving";
494
+ return;
495
+ }
496
+ if (/^Done in /i.test(line)) {
497
+ progressState.lastLine = line.slice(0, 200);
498
+ if (progressState.percent !== null) progressState.percent = 100;
499
+ progressState.phase = "linking";
500
+ return;
501
+ }
502
+ if (line.startsWith("{")) {
503
+ try {
504
+ const event = JSON.parse(line);
505
+ if (typeof event.name !== "string" || !event.name.startsWith("pnpm:")) return;
506
+ if (event.name === "pnpm:stage" && typeof event.stage === "string") {
507
+ if (event.stage.includes("resolution")) progressState.phase = "resolving";
508
+ else if (event.stage.includes("import")) progressState.phase = "linking";
509
+ else if (event.stage.includes("build")) progressState.phase = "building";
510
+ }
511
+ if (event.name === "pnpm:progress" && typeof event.packageId === "string" && event.packageId !== "") {
512
+ progressState.currentPackage = event.packageId.split(">")[0] ?? event.packageId;
513
+ if (event.status === "fetched") progressState.phase = "downloading";
514
+ }
515
+ } catch {
516
+ return;
517
+ }
518
+ return;
519
+ }
520
+ progressState.lastLine = line.slice(0, 200);
521
+ }
522
+ /** 把 pnpm / dsh plugin 的输出收成关于页可轮询的进度。 */
523
+ function noteInstallOutput(chunk) {
524
+ if (!progressState.active) return;
525
+ const lines = `${progressState.leftover}${chunk}`.split(/\r?\n/);
526
+ progressState.leftover = lines.pop() ?? "";
527
+ for (const line of lines) applyProgressLine(line.trim());
528
+ }
529
+ function pluginUnchangedError() {
530
+ return /* @__PURE__ */ new Error("安装命令已结束,但插件没有进入当前 Profile。请重试。");
531
+ }
532
+ async function waitUntilPluginMounted(packageName, version, runtime, timeoutMs = PLUGIN_MOUNT_TIMEOUT_MS) {
533
+ const deadline = Date.now() + timeoutMs;
534
+ while (Date.now() <= deadline) {
535
+ const bundles = await profileBundleNames(runtime);
536
+ const inBundles = isOfficialRuntimePackage(packageName) || isManagedPackageDeclared(packageName, bundles);
537
+ const installed = await installedPackageVersion(packageName, runtime);
538
+ if (inBundles && installed === version) return;
539
+ await new Promise((resolve) => setTimeout(resolve, 80));
540
+ }
541
+ throw pluginUnchangedError();
542
+ }
382
543
  function pluginCommandError(stderr) {
383
544
  const detail = stderr.replace(/\s+/g, " ").trim();
384
545
  if (detail.includes("minimumReleaseAge") || detail.includes("Release age")) return /* @__PURE__ */ new Error("更新被 pnpm 发布时间保护拦截。请确认已写入当前版本白名单后重试。");
385
546
  if (/EPERM|EBUSY|EACCES|unable to unlink|ERR_PNPM_LOCKED|Lock/i.test(detail)) return /* @__PURE__ */ new Error("无法覆盖正在运行的插件文件。请先完全退出桌面端,再重新打开后更新。");
386
547
  if (/UNEXPECTED_STORE|Unexpected store location/i.test(detail)) return /* @__PURE__ */ new Error("插件目录和 pnpm 仓库不一致。请完全退出桌面端后再更新。");
387
- if (/pnpm not found/i.test(detail)) return /* @__PURE__ */ new Error("当前环境找不到 pnpm。请从桌面端启动后再更新。");
548
+ if (/ERR_PNPM_IGNORED_BUILDS|Ignored build scripts/i.test(detail)) return /* @__PURE__ */ new Error("插件依赖的 pnpm 构建脚本策略尚未确认。请更新 Profile 的 allowBuilds 配置后重试。");
549
+ if (/pnpm not found|未找到 pnpm/i.test(detail)) return /* @__PURE__ */ new Error("当前环境找不到 pnpm。请确认已安装 pnpm 后重启 DSH 再试。");
388
550
  return /* @__PURE__ */ new Error("无法在应用运行时更新插件。请先完全退出桌面端,再重新打开后更新。");
389
551
  }
390
552
  /**
553
+ * 图形界面或桌面启动往往没有 shell PATH。把 pnpm 常见安装目录补进去,
554
+ * 让 `dsh plugin add` 能找到本机已有的 pnpm,而不是直接启动它。
555
+ */
556
+ function pluginToolSearchDirs(platform = process.platform, env = process.env, home = homedir(), nodeDir = dirname(process.execPath)) {
557
+ const dirs = [];
558
+ const pnpmHome = (env.PNPM_HOME ?? "").trim();
559
+ if (pnpmHome !== "") dirs.push(pnpmHome);
560
+ if (platform === "win32") {
561
+ const local = (env.LOCALAPPDATA ?? "").trim();
562
+ const roaming = (env.APPDATA ?? "").trim();
563
+ if (local !== "") dirs.push(join(local, "pnpm"));
564
+ if (roaming !== "") dirs.push(join(roaming, "npm"));
565
+ } else dirs.push("/opt/homebrew/bin", "/usr/local/bin", join(home, ".local", "bin"), join(home, ".local", "share", "pnpm"));
566
+ if (nodeDir.trim() !== "") dirs.push(nodeDir);
567
+ return [...new Set(dirs.filter((dir) => dir.trim() !== ""))];
568
+ }
569
+ function pluginSpawnEnv(env = process.env, platform = process.platform, home = homedir(), nodeDir = dirname(process.execPath)) {
570
+ const separator = platform === "win32" ? ";" : ":";
571
+ const parts = (env.PATH ?? env.Path ?? "").split(separator).filter((part) => part !== "");
572
+ for (const dir of pluginToolSearchDirs(platform, env, home, nodeDir)) if (!parts.includes(dir)) parts.push(dir);
573
+ return {
574
+ ...env,
575
+ CI: "true",
576
+ PATH: parts.join(separator)
577
+ };
578
+ }
579
+ /** Desktop 桥接在 Web 下不会设置 DSH_PNPM_ENTRY;补上 Node 自带的 corepack pnpm。 */
580
+ function ensurePnpmEntry(env = process.env, nodeDir = dirname(process.execPath)) {
581
+ const existing = (env.DSH_PNPM_ENTRY ?? "").trim();
582
+ if (existing !== "") return existing;
583
+ const candidate = join(nodeDir, "node_modules", "corepack", "dist", "pnpm.js");
584
+ if (!existsSync(candidate)) return void 0;
585
+ return candidate;
586
+ }
587
+ /** Desktop 桥接当前从 process.env 同步读取 pnpm 入口;调用结束后立即恢复,避免污染后续子进程。 */
588
+ function withPnpmEntry(callback, env = process.env, nodeDir = dirname(process.execPath)) {
589
+ const entry = ensurePnpmEntry(env, nodeDir);
590
+ if (entry === void 0) return callback();
591
+ const hadEntry = Object.prototype.hasOwnProperty.call(env, "DSH_PNPM_ENTRY");
592
+ const previous = env.DSH_PNPM_ENTRY;
593
+ env.DSH_PNPM_ENTRY = entry;
594
+ try {
595
+ return callback();
596
+ } finally {
597
+ if (hadEntry) env.DSH_PNPM_ENTRY = previous;
598
+ else delete env.DSH_PNPM_ENTRY;
599
+ }
600
+ }
601
+ /**
391
602
  * 复用启动当前服务的 DSH CLI:它会通过 pnpm 从 npm 安装或更新,并自动维护
392
603
  * dsh.profile.bundles,避免浏览器端直接管理 profile 文件。
393
604
  */
@@ -427,7 +638,9 @@ function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
427
638
  let output = "";
428
639
  let settled = false;
429
640
  const collect = (chunk) => {
430
- output = `${output}${String(chunk)}`.slice(-65536);
641
+ const text = String(chunk);
642
+ output = `${output}${text}`.slice(-65536);
643
+ noteInstallOutput(text);
431
644
  };
432
645
  const finish = (error) => {
433
646
  if (settled) return;
@@ -457,7 +670,9 @@ function monitorDesktopPlugin(handle, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
457
670
  let output = "";
458
671
  let settled = false;
459
672
  const collect = (chunk) => {
460
- output = `${output}${String(chunk)}`.slice(-65536);
673
+ const text = String(chunk);
674
+ output = `${output}${text}`.slice(-65536);
675
+ noteInstallOutput(text);
461
676
  };
462
677
  const finish = (error) => {
463
678
  if (settled) return;
@@ -489,7 +704,8 @@ function monitorDesktopPlugin(handle, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
489
704
  });
490
705
  }
491
706
  function runDshPlugin(args, runtime = resolveDependencyRuntime(), timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
492
- if (runtime.desktopPnpm !== void 0) return monitorDesktopPlugin(runtime.desktopPnpm.runPlugin(args, runtime.profileDir), timeoutMs);
707
+ const desktopPnpm = runtime.desktopPnpm;
708
+ if (desktopPnpm !== void 0) return monitorDesktopPlugin(withPnpmEntry(() => desktopPnpm.runPlugin(args, runtime.profileDir)), timeoutMs);
493
709
  const entry = resolveDshCliEntry();
494
710
  return monitorPluginChild(spawn(process.execPath, [
495
711
  ...pluginExecArgv(),
@@ -500,10 +716,7 @@ function runDshPlugin(args, runtime = resolveDependencyRuntime(), timeoutMs = PL
500
716
  ...args
501
717
  ], {
502
718
  cwd: process.cwd(),
503
- env: {
504
- ...process.env,
505
- CI: "true"
506
- },
719
+ env: pluginSpawnEnv(),
507
720
  windowsHide: true,
508
721
  stdio: [
509
722
  "ignore",
@@ -524,11 +737,11 @@ async function installDependency(id, requestHotUpdate = requestDesktopHotUpdate,
524
737
  installing = false;
525
738
  }
526
739
  }
527
- /** 只挑选已安装且确有新版本的插件,缺失插件仍交给各行的“安装”按钮。 */
740
+ /** 顶部批量操作同时安装缺失插件并更新落后版本。 */
528
741
  function updatableDependencyIds(statuses) {
529
- return statuses.filter((status) => status.installed && status.updateAvailable).map((status) => status.id);
742
+ return statuses.filter((status) => !status.installed || status.updateAvailable).map((status) => status.id);
530
743
  }
531
- /** 把所有待更新版本一次写入清单,最后只请求一次桌面热更新。 */
744
+ /** 把所有待安装/更新版本一次写入清单,最后只请求一次桌面热更新。 */
532
745
  async function updateAllDependencies(requestHotUpdate = requestDesktopHotUpdate, runtime = resolveDependencyRuntime()) {
533
746
  if (installing) throw new Error("已有依赖安装正在进行,请等待完成后再试。");
534
747
  installing = true;
@@ -580,29 +793,39 @@ async function installDependenciesLocked(ids, requestHotUpdate, runtime) {
580
793
  version
581
794
  };
582
795
  }));
583
- const remove = [...new Set(targets.flatMap((target) => pluginsToRemoveBeforeInstall(declared, target.packageName)))];
584
- for (const target of targets) {
585
- await ensureLatestReleaseAllowed(target.packageName, target.version, runtime);
586
- if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version, runtime);
587
- await recordPendingUpdate(target.packageName, target.version, runtime);
588
- }
589
- if (remove.length > 0) await runDshPlugin(["remove", ...remove], runtime);
590
- if (runtime.desktopPnpm === void 0 && requestHotUpdate()) return dependencyStatuses(runtime);
591
- for (const target of targets) {
592
- try {
796
+ beginInstallProgress(targets.map((target) => `${target.packageName}@${target.version}`).join(", "));
797
+ try {
798
+ const remove = [...new Set(targets.flatMap((target) => pluginsToRemoveBeforeInstall(declared, target.packageName)))];
799
+ await ensureRequiredBuildPolicies(runtime);
800
+ for (const target of targets) {
801
+ await ensureLatestReleaseAllowed(target.packageName, target.version, runtime);
802
+ if (!isOfficialRuntimePackage(target.packageName)) await removeUnmountedPackagePath(target.packageName, runtime);
803
+ await recordPendingUpdate(target.packageName, target.version, runtime);
804
+ }
805
+ if (remove.length > 0) await runDshPlugin(["remove", ...remove], runtime);
806
+ if (runtime.desktopPnpm === void 0 && requestHotUpdate()) {
807
+ for (const target of targets) if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version, runtime);
808
+ return dependencyStatuses(runtime);
809
+ }
810
+ const officialTargets = targets.filter((target) => isOfficialRuntimePackage(target.packageName));
811
+ const communityTargets = targets.filter((target) => !isOfficialRuntimePackage(target.packageName));
812
+ const batches = [...officialTargets.map((target) => [target]), ...communityTargets.length === 0 ? [] : [communityTargets]];
813
+ for (const batch of batches) {
593
814
  await runDshPlugin([
594
815
  "add",
595
- `${target.packageName}@${target.version}`,
816
+ ...batch.map((target) => `${target.packageName}@${target.version}`),
596
817
  "--registry=https://registry.npmjs.org/"
597
818
  ], runtime);
598
- } catch (error) {
599
- if (isRestartableInstallError(error)) return dependencyStatuses(runtime);
600
- throw error;
819
+ for (const target of batch) {
820
+ await waitUntilPluginMounted(target.packageName, target.version, runtime);
821
+ if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version, runtime);
822
+ await removePendingUpdate(target.packageName, runtime);
823
+ }
601
824
  }
602
- if (await installedPackageVersion(target.packageName, runtime) !== target.version) return dependencyStatuses(runtime);
603
- await removePendingUpdate(target.packageName, runtime);
825
+ return dependencyStatuses(runtime);
826
+ } finally {
827
+ endInstallProgress();
604
828
  }
605
- return dependencyStatuses(runtime);
606
829
  }
607
830
  //#endregion
608
831
  //#region src/explorer-path-policy.ts
@@ -1041,6 +1264,23 @@ function apply(ctx) {
1041
1264
  const url = new URL(request.url ?? "/", "http://localhost");
1042
1265
  try {
1043
1266
  if (request.method === "GET") {
1267
+ if (url.searchParams.get("action") === "capabilities") {
1268
+ const capabilities = { officialTurnNavigator: await runtimeSupportsOfficialTurnNavigator(resolveDependencyRuntime(ctx)) };
1269
+ response.writeHead(200, {
1270
+ "content-type": "application/json; charset=utf-8",
1271
+ "cache-control": "no-store"
1272
+ });
1273
+ response.end(JSON.stringify({ capabilities }));
1274
+ return;
1275
+ }
1276
+ if (url.searchParams.get("action") === "progress") {
1277
+ response.writeHead(200, {
1278
+ "content-type": "application/json; charset=utf-8",
1279
+ "cache-control": "no-store"
1280
+ });
1281
+ response.end(JSON.stringify({ progress: installProgressSnapshot() }));
1282
+ return;
1283
+ }
1044
1284
  const dependencies = await dependencyStatuses(resolveDependencyRuntime(ctx));
1045
1285
  response.writeHead(200, {
1046
1286
  "content-type": "application/json; charset=utf-8",
@@ -1059,9 +1299,10 @@ function apply(ctx) {
1059
1299
  return;
1060
1300
  }
1061
1301
  if (url.searchParams.get("action") === "update-all") {
1302
+ const autoReload = typeof process.send === "function";
1062
1303
  let restartAfterResponse = false;
1063
1304
  const { dependencies, updatedCount } = await updateAllDependencies(() => {
1064
- restartAfterResponse = typeof process.send === "function";
1305
+ restartAfterResponse = autoReload;
1065
1306
  return restartAfterResponse;
1066
1307
  }, resolveDependencyRuntime(ctx));
1067
1308
  response.writeHead(200, {
@@ -1070,16 +1311,18 @@ function apply(ctx) {
1070
1311
  });
1071
1312
  response.end(JSON.stringify({
1072
1313
  dependencies,
1073
- restartRequired: updatedCount > 0
1314
+ restartRequired: updatedCount > 0,
1315
+ autoReload: typeof process.send === "function"
1074
1316
  }));
1075
1317
  if (restartAfterResponse) setTimeout(() => {
1076
1318
  requestDesktopHotUpdate();
1077
1319
  }, 150).unref?.();
1078
1320
  return;
1079
1321
  }
1322
+ const autoReload = typeof process.send === "function";
1080
1323
  let restartAfterResponse = false;
1081
1324
  const dependencies = await installDependency(url.searchParams.get("dependency"), () => {
1082
- restartAfterResponse = typeof process.send === "function";
1325
+ restartAfterResponse = autoReload;
1083
1326
  return restartAfterResponse;
1084
1327
  }, resolveDependencyRuntime(ctx));
1085
1328
  response.writeHead(200, {
@@ -1088,7 +1331,8 @@ function apply(ctx) {
1088
1331
  });
1089
1332
  response.end(JSON.stringify({
1090
1333
  dependencies,
1091
- restartRequired: true
1334
+ restartRequired: true,
1335
+ autoReload: typeof process.send === "function"
1092
1336
  }));
1093
1337
  if (restartAfterResponse) setTimeout(() => {
1094
1338
  requestDesktopHotUpdate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-ui",
3
- "version": "0.2.95",
3
+ "version": "0.2.97",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {