@michengai/dsh-codex-ui 0.2.96 → 0.2.98

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,38 @@
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.98 — 2026-09-01
8
+
9
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.15`.
10
+
11
+ ### Fixed
12
+
13
+ - Kept the segmented conversation-tab slider aligned with the host-selected view by recognizing the tab list itself as a synchronization root, preventing the highlight from remaining on Chat after selecting Trace or Context.
14
+
15
+ ### Dependencies
16
+
17
+ - Prepared Suite Installer `0.1.15` to resolve Codex UI `0.2.98` after this release is published.
18
+
19
+ ## 0.2.97 — 2026-09-01
20
+
21
+ Companion release: `@michengai/dsh-codex-suite-installer@0.1.14`.
22
+
23
+ ### Compatibility
24
+
25
+ - 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.
26
+ - Preserved upstream conversation width dragging, adaptive transcript sizing, and turn navigation while retaining the Codex composer treatment.
27
+
28
+ ### Fixed
29
+
30
+ - 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.
31
+ - Hardened pnpm discovery for GUI environments without trusting unrelated package-manager entry points or leaking the inferred entry into later child processes.
32
+ - Added strict progress payload validation and extended slow Desktop bundle reconciliation tolerance.
33
+ - Filtered and frame-coalesced navigator DOM observation so streaming responses do not trigger repeated document-wide scans.
34
+
35
+ ### Dependencies
36
+
37
+ - Prepared Suite Installer `0.1.14` to resolve the latest exact companion versions after this release is published.
38
+
7
39
  ## 0.2.96 — 2026-09-01
8
40
 
9
41
  Companion release: `@michengai/dsh-codex-suite-installer@0.1.13`.
@@ -4,6 +4,38 @@
4
4
 
5
5
  本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
6
6
 
7
+ ## 0.2.98 — 2026-09-01
8
+
9
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.15`。
10
+
11
+ ### 修复
12
+
13
+ - 让会话分段页签的滑块能够以页签容器自身作为同步根节点,选中“轨迹”或“上下文”后高亮背景不再停留在“对话”。
14
+
15
+ ### 依赖
16
+
17
+ - 准备 Suite Installer `0.1.15`,在本版本发布后解析 Codex UI `0.2.98`。
18
+
19
+ ## 0.2.97 — 2026-09-01
20
+
21
+ 配套版本:`@michengai/dsh-codex-suite-installer@0.1.14`。
22
+
23
+ ### 兼容性
24
+
25
+ - 在新版 DSH 中采用官方轮次导航,并为旧运行时保留自定义导航;通过运行时能力判断和更稳健的左侧镜像,不再依赖私有样式文件名或宿主 DOM 层级。
26
+ - 保留官方会话宽度拖拽、自适应正文宽度与轮次导航,同时继续使用 Codex 风格输入区。
27
+
28
+ ### 修复
29
+
30
+ - 重构“关于”页配套插件安装:显示 pnpm 实时进度、以实际进入 Profile 为成功标准、批量安装缺失或可更新插件、清理残留 Junction,并正确区分 Desktop 自动重载与独立 Web 手动重启。
31
+ - 强化 GUI 环境下的 pnpm 定位,不再信任其他包管理器入口,也不会把推断出的入口泄漏给后续子进程。
32
+ - 严格校验安装进度数据,并为 Desktop 较慢的 bundles 写入留出更充足的等待时间。
33
+ - 对轮次导航 DOM 监听增加相关变更过滤和逐帧合并,避免流式回答触发反复全文档扫描。
34
+
35
+ ### 依赖
36
+
37
+ - 准备 Suite Installer `0.1.14`,在本版本发布后解析全部配套插件的最新精确版本。
38
+
7
39
  ## 0.2.96 — 2026-09-01
8
40
 
9
41
  配套版本:`@michengai/dsh-codex-suite-installer@0.1.13`。
package/dist/client.js CHANGED
@@ -734,87 +734,12 @@ window.__ModuleLoader__.load({
734
734
  }
735
735
  //#endregion
736
736
  //#region src/client/conversation-bubbles.ts
737
- /** DSH 右侧气泡改成 Codex 的左对齐紧凑卡片,不改会话数据。 */
737
+ /** 撤回旧版 Codex 用户卡片,恢复 DSH 官方问题气泡。 */
738
738
  const USER_BUBBLE_STYLE_ID = "dcu-user-bubble-style";
739
- const USER_BUBBLE_STYLE = `
740
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root],
741
- [data-conversation-scroll] [data-pending-steering][data-time-hover-root]{align-items:flex-start!important}
742
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root]>div,
743
- [data-conversation-scroll] [data-pending-steering][data-time-hover-root]>div{align-items:flex-start!important;max-width:min(640px,100%)!important}
744
- [data-conversation-scroll] [data-dcu-user-source]{display:none!important}
745
- [data-conversation-scroll] [data-dcu-user-card],
746
- [data-conversation-scroll] [data-chat-flow-kind="user"] [data-time-hover-root]>div>[class*="bubble"],
747
- [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}
748
- [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}
749
- [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}
750
- `;
751
- function splitUserCard(text) {
752
- const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "");
753
- if (lines.length >= 2) return {
754
- title: lines[0] ?? text,
755
- sub: lines.slice(1).join(" ")
756
- };
757
- return { title: text.trim() };
758
- }
759
- function findPlainBubble(stack) {
760
- for (const child of stack.children) {
761
- if (!(child instanceof HTMLElement)) continue;
762
- if (child.dataset.dcuUserCard !== void 0) continue;
763
- if (child.matches("[class*=\"bubble\"]")) return child;
764
- }
765
- }
766
- function isPlainTextBubble(bubble) {
767
- return bubble.querySelector("pre,table,img,[class*=\"Json\"]") === null;
768
- }
769
- /** 撤销某个气泡的替换装饰:删除卡片并恢复原气泡显示。 */
770
- function rollbackUserCard(stack) {
771
- for (const card of stack.querySelectorAll("[data-dcu-user-card]")) card.remove();
772
- for (const source of stack.querySelectorAll("[data-dcu-user-source]")) delete source.dataset.dcuUserSource;
773
- }
774
- function decorateUserBubbles(root) {
775
- const rows = root.querySelectorAll("[data-chat-flow-kind=\"user\"] [data-time-hover-root], [data-pending-steering][data-time-hover-root]");
776
- for (const row of rows) {
777
- const stack = row.firstElementChild;
778
- if (!(stack instanceof HTMLElement)) continue;
779
- const bubble = findPlainBubble(stack);
780
- if (bubble === void 0 || !isPlainTextBubble(bubble)) {
781
- rollbackUserCard(stack);
782
- continue;
783
- }
784
- const text = (bubble.textContent ?? "").replace(/\s+\n/g, "\n").trim();
785
- if (text === "") {
786
- rollbackUserCard(stack);
787
- continue;
788
- }
789
- bubble.dataset.dcuUserSource = "";
790
- let card = stack.querySelector(":scope > [data-dcu-user-card]");
791
- if (card === null) {
792
- card = bubble.ownerDocument.createElement("div");
793
- card.dataset.dcuUserCard = "";
794
- bubble.after(card);
795
- }
796
- if (card.dataset.dcuUserText === text) continue;
797
- const parts = splitUserCard(text);
798
- const title = bubble.ownerDocument.createElement("span");
799
- title.dataset.dcuUserTitle = "";
800
- title.textContent = parts.title;
801
- card.replaceChildren(title);
802
- if (parts.sub !== void 0 && parts.sub !== "") {
803
- const sub = bubble.ownerDocument.createElement("span");
804
- sub.dataset.dcuUserSub = "";
805
- sub.textContent = parts.sub;
806
- card.append(sub);
807
- }
808
- card.dataset.dcuUserText = text;
809
- }
810
- }
811
- function ensureUserBubbleStyle(doc) {
812
- if (doc.getElementById("dcu-user-bubble-style") !== null) return;
813
- if (doc.head === null) return;
814
- const style = doc.createElement("style");
815
- style.id = USER_BUBBLE_STYLE_ID;
816
- style.textContent = USER_BUBBLE_STYLE;
817
- 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;
818
743
  }
819
744
  //#endregion
820
745
  //#region src/client/conversation-header.ts
@@ -851,6 +776,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
851
776
  const FOLDER_SVG = "<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" aria-hidden=\"true\"><path fill=\"currentColor\" transform=\"translate(1.5 2.429)\" d=\"M5.05582 0.518756L4.50669 0.86654L5.05582 0.518756ZM13 9.4837L13.65 9.4837L13.65 3.53962L13 3.53962L12.35 3.53962L12.35 9.4837L13 9.4837ZM11.3264 1.86603L11.3264 1.21603L6.52313 1.21603L6.52313 1.86603L6.52313 2.51603L11.3264 2.51603L11.3264 1.86603ZM5.58054 1.34727L6.12968 0.999489L5.60495 0.170972L5.05582 0.518756L4.50669 0.86654L5.03141 1.69506L5.58054 1.34727ZM4.11323 1.23058e-13L4.11323 -0.65L1.67359 -0.65L1.67359 5.00699e-14L1.67359 0.65L4.11323 0.65L4.11323 1.23058e-13ZM0 1.67359L-0.65 1.67359L-0.65 9.4837L0 9.4837L0.65 9.4837L0.65 1.67359L0 1.67359ZM11.3264 11.1573L11.3264 10.5073L1.67359 10.5073L1.67359 11.1573L1.67359 11.8073L11.3264 11.8073L11.3264 11.1573ZM0 9.4837L-0.65 9.4837C-0.65 10.767 0.390308 11.8073 1.67359 11.8073L1.67359 11.1573L1.67359 10.5073C1.10828 10.5073 0.65 10.049 0.65 9.4837L0 9.4837ZM1.67359 5.00699e-14L1.67359 -0.65C0.390307 -0.65 -0.65 0.390309 -0.65 1.67359L0 1.67359L0.65 1.67359C0.65 1.10828 1.10828 0.65 1.67359 0.65L1.67359 5.00699e-14ZM5.05582 0.518756L5.60495 0.170972C5.28121 -0.340193 4.71829 -0.65 4.11323 -0.65L4.11323 1.23058e-13L4.11323 0.65C4.27282 0.65 4.4213 0.731715 4.50669 0.86654L5.05582 0.518756ZM6.52313 1.86603L6.52313 1.21603C6.36354 1.21603 6.21507 1.13431 6.12968 0.999489L5.58054 1.34727L5.03141 1.69506C5.35515 2.20622 5.91808 2.51603 6.52313 2.51603L6.52313 1.86603ZM13 3.53962L13.65 3.53962C13.65 2.25634 12.6097 1.21603 11.3264 1.21603L11.3264 1.86603L11.3264 2.51603C11.8917 2.51603 12.35 2.97431 12.35 3.53962L13 3.53962ZM13 9.4837L12.35 9.4837C12.35 10.049 11.8917 10.5073 11.3264 10.5073L11.3264 11.1573L11.3264 11.8073C12.6097 11.8073 13.65 10.767 13.65 9.4837L13 9.4837Z\"/></svg>";
852
777
  const MORE_SVG = "<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><circle cx=\"4\" cy=\"8\" r=\"1.15\" fill=\"currentColor\"/><circle cx=\"8\" cy=\"8\" r=\"1.15\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"8\" r=\"1.15\" fill=\"currentColor\"/></svg>";
853
778
  function findConversationTablist(root) {
779
+ if ("matches" in root && typeof root.matches === "function" && root.matches("[role=tablist]")) return root;
854
780
  return root.querySelector("header [role=tablist]") ?? void 0;
855
781
  }
856
782
  /** 只给宿主页签打内联标记,视觉重排交给样式表;DOM 结构保持宿主原样。 */
@@ -965,16 +891,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
965
891
  style.textContent = CONVERSATION_HEADER_STYLE;
966
892
  doc.head.append(style);
967
893
  }
968
- const CONVERSATION_DECORATION_SELECTOR = "header, [data-conversation-scroll], [data-chat-flow-kind=\"user\"], [data-pending-steering]";
894
+ const CONVERSATION_DECORATION_SELECTOR = "header";
969
895
  function conversationDecorationMutation(records) {
970
- 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);
896
+ const relevant = (node) => node instanceof Element && (node.matches(CONVERSATION_DECORATION_SELECTOR) || node.closest(CONVERSATION_DECORATION_SELECTOR) !== null || node.querySelector(CONVERSATION_DECORATION_SELECTOR) !== null);
971
897
  return records.some((record) => relevant(record.target) || [...record.addedNodes].some(relevant) || [...record.removedNodes].some(relevant));
972
898
  }
973
- /** 观察会话顶栏与用户气泡;只对相关子树变更按帧合并,流式回答不会触发全文档扫描。 */
899
+ /** 观察会话顶栏;只对相关子树变更按帧合并,流式回答不会触发全文档扫描。 */
974
900
  function observeConversationHeader(doc = document) {
975
901
  if (doc.head === null || doc.body === null) return () => {};
976
902
  ensureStyle(doc);
977
- ensureUserBubbleStyle(doc);
903
+ restoreOfficialUserBubbles(doc);
978
904
  let applying = false;
979
905
  let frame;
980
906
  let watchedTabs;
@@ -994,7 +920,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
994
920
  syncTabSlider(doc);
995
921
  decorateConversationTitle(doc);
996
922
  decorateSessionLogDownload(doc);
997
- decorateUserBubbles(doc);
998
923
  } finally {
999
924
  applying = false;
1000
925
  }
@@ -3542,7 +3467,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3542
3467
  const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
3543
3468
  const SIDEBAR_COLLAPSE_SETTLE_MS = 140;
3544
3469
  const stylesheet$3 = `
3545
- .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)}
3470
+ .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)}
3546
3471
  .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}}
3547
3472
  .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}
3548
3473
  .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}
@@ -3555,7 +3480,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3555
3480
  .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}
3556
3481
  [role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
3557
3482
  .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}}
3558
- [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}}
3483
+ [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}}
3484
+ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:has([data-dcu-official-turn-navigator]) .dcu-turn-navigator{display:none}
3485
+ [data-dcu-official-turn-navigator]{right:auto!important;left:calc(12px - (var(--dsh-composer-side-clearance) + 16px))!important}
3486
+ [data-dcu-official-turn-mark]{inset:0 auto 0 0!important}
3487
+ [data-dcu-official-turn-mark]:before{right:auto!important;left:0!important}
3488
+ [data-dcu-official-turn-tooltip]{right:auto!important;left:calc(100% + 10px)!important;animation:none!important}
3559
3489
  `;
3560
3490
  function MenuIcon({ children }) {
3561
3491
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -4327,20 +4257,50 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4327
4257
  ];
4328
4258
  //#endregion
4329
4259
  //#region src/client/AboutSection.tsx
4260
+ const PROGRESS_PHASES = /* @__PURE__ */ new Set([
4261
+ "resolving",
4262
+ "downloading",
4263
+ "linking",
4264
+ "building"
4265
+ ]);
4330
4266
  const endpoint = "/api/michengai/codex-ui/dependencies";
4331
4267
  const stylesheet$2 = `
4332
- .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)}
4268
+ .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%)}}
4333
4269
  `;
4334
4270
  function isDependencyStatus(value) {
4335
4271
  if (value === null || typeof value !== "object") return false;
4336
4272
  const status = value;
4337
4273
  return MANAGED_DEPENDENCIES.some((dependency) => dependency.id === status.id) && typeof status.packageName === "string" && typeof status.installed === "boolean" && typeof status.updateAvailable === "boolean";
4338
4274
  }
4275
+ function isInstallProgress(value) {
4276
+ if (value === null || typeof value !== "object") return false;
4277
+ const progress = value;
4278
+ const phase = progress.phase;
4279
+ const total = progress.total;
4280
+ const percent = progress.percent;
4281
+ const currentPackage = progress.currentPackage;
4282
+ 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");
4283
+ }
4284
+ function installErrorText(error, t) {
4285
+ const message = error instanceof Error ? error.message : "";
4286
+ if (message.includes("没有进入当前 Profile") || message.includes("did not enter this profile")) return t("about.installUnchanged");
4287
+ return message !== "" ? message : t("about.installFailed");
4288
+ }
4289
+ function progressLabel(progress, t) {
4290
+ if (progress.phase !== null) {
4291
+ const current = progress.currentPackage === null || progress.currentPackage === "" ? "" : ` · ${progress.currentPackage}`;
4292
+ const done = progress.done > 0 ? ` · ${t("about.packagesDone").replace("{0}", String(progress.done))}` : "";
4293
+ return `${t(`about.progress.${progress.phase}`)}${current}${done}`;
4294
+ }
4295
+ if (progress.lastLine !== "") return `${progress.lastLine} (${progress.seconds}s)`;
4296
+ return t("about.progressHint");
4297
+ }
4339
4298
  /** Codex UI 的功能说明、配套管理插件状态与受限安装入口。 */
4340
4299
  function AboutSection({ t }) {
4341
4300
  const [dependencies, setDependencies] = (0, react.useState)([]);
4342
4301
  const [state, setState] = (0, react.useState)("loading");
4343
4302
  const [installing, setInstalling] = (0, react.useState)();
4303
+ const [progress, setProgress] = (0, react.useState)();
4344
4304
  const [message, setMessage] = (0, react.useState)();
4345
4305
  const alive = (0, react.useRef)(true);
4346
4306
  const root = (0, react.useRef)(null);
@@ -4400,6 +4360,31 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4400
4360
  observer.disconnect();
4401
4361
  };
4402
4362
  }, [load]);
4363
+ (0, react.useEffect)(() => {
4364
+ if (installing === void 0) {
4365
+ setProgress(void 0);
4366
+ return;
4367
+ }
4368
+ let cancelled = false;
4369
+ const pull = async () => {
4370
+ try {
4371
+ const response = await fetch(`${endpoint}?action=progress`, { cache: "no-store" });
4372
+ const payload = await response.json();
4373
+ if (cancelled || !response.ok || !isInstallProgress(payload.progress)) return;
4374
+ setProgress(payload.progress);
4375
+ } catch {
4376
+ if (!cancelled) return;
4377
+ }
4378
+ };
4379
+ pull();
4380
+ const timer = window.setInterval(() => {
4381
+ pull();
4382
+ }, 800);
4383
+ return () => {
4384
+ cancelled = true;
4385
+ window.clearInterval(timer);
4386
+ };
4387
+ }, [installing]);
4403
4388
  const install = async (id) => {
4404
4389
  if (installingRef.current !== void 0) return;
4405
4390
  installingRef.current = id;
@@ -4414,13 +4399,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4414
4399
  setState("ready");
4415
4400
  setMessage({
4416
4401
  error: false,
4417
- text: t("about.restartRequired")
4402
+ text: payload.autoReload === false ? t("about.restartManually") : t("about.restartRequired")
4418
4403
  });
4419
4404
  } catch (error) {
4420
4405
  if (!alive.current) return;
4421
4406
  setMessage({
4422
4407
  error: true,
4423
- text: error instanceof Error ? error.message : t("about.installFailed")
4408
+ text: installErrorText(error, t)
4424
4409
  });
4425
4410
  } finally {
4426
4411
  installingRef.current = void 0;
@@ -4441,20 +4426,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4441
4426
  setState("ready");
4442
4427
  setMessage({
4443
4428
  error: false,
4444
- text: payload.restartRequired === false ? t("about.upToDate") : t("about.restartRequired")
4429
+ text: payload.restartRequired === false ? t("about.upToDate") : payload.autoReload === false ? t("about.restartManually") : t("about.restartRequired")
4445
4430
  });
4446
4431
  } catch (error) {
4447
4432
  if (!alive.current) return;
4448
4433
  setMessage({
4449
4434
  error: true,
4450
- text: error instanceof Error ? error.message : t("about.installFailed")
4435
+ text: installErrorText(error, t)
4451
4436
  });
4452
4437
  } finally {
4453
4438
  installingRef.current = void 0;
4454
4439
  if (alive.current) setInstalling(void 0);
4455
4440
  }
4456
4441
  };
4457
- const updateCount = dependencies.filter((dependency) => dependency.installed && dependency.updateAvailable).length;
4442
+ const actionableCount = dependencies.filter((dependency) => !dependency.installed || dependency.updateAvailable).length;
4458
4443
  const title = (id) => t(`about.dependency.${id}`);
4459
4444
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4460
4445
  ref: root,
@@ -4481,7 +4466,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4481
4466
  }),
4482
4467
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4483
4468
  className: "dcu-about-dependencies-heading",
4484
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" && updateCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4469
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" && actionableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4485
4470
  className: "dcu-about-install dcu-about-update-all",
4486
4471
  type: "button",
4487
4472
  "aria-busy": installing === "all",
@@ -4543,6 +4528,26 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4543
4528
  ]
4544
4529
  }, dependency.id))
4545
4530
  }),
4531
+ installing !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4532
+ className: "dcu-about-progress",
4533
+ role: "status",
4534
+ children: [
4535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, { size: 14 }) }),
4536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: progress === void 0 ? t("about.progressHint") : progressLabel(progress, t) }),
4537
+ progress?.percent !== null && progress?.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4538
+ className: "dcu-about-progress-pct",
4539
+ children: [progress.percent, "%"]
4540
+ }),
4541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4542
+ className: "dcu-about-progress-bar",
4543
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4544
+ className: "dcu-about-progress-fill",
4545
+ "data-wave": progress?.percent === null || progress?.percent === void 0,
4546
+ style: progress?.percent === null || progress?.percent === void 0 ? void 0 : { width: `${progress.percent}%` }
4547
+ })
4548
+ })
4549
+ ]
4550
+ }),
4546
4551
  message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4547
4552
  className: "dcu-about-message",
4548
4553
  "data-error": message.error,
@@ -4834,7 +4839,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4834
4839
  "about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
4835
4840
  "about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
4836
4841
  "about.dependencies": "配套管理插件",
4837
- "about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键更新所有可更新项。可分别检查 Codex UI、专家、技能、归档、IM、定时任务和第三方插件市场。",
4842
+ "about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键安装缺失项并更新旧版本。可分别检查 Codex UI、专家、技能、归档、IM、定时任务和第三方插件市场。",
4838
4843
  "about.loading": "正在读取依赖安装状态…",
4839
4844
  "about.statusFailed": "暂时无法读取依赖安装状态。",
4840
4845
  "about.installed": "已安装",
@@ -4843,11 +4848,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4843
4848
  "about.install": "安装",
4844
4849
  "about.update": "更新",
4845
4850
  "about.installing": "安装中",
4846
- "about.updateAll": "全部更新",
4847
- "about.updatingAll": "更新中",
4848
- "about.upToDate": "所有已安装插件均为最新版本。",
4851
+ "about.updateAll": "全部安装/更新",
4852
+ "about.updatingAll": "安装/更新中",
4853
+ "about.upToDate": "所有配套插件均已安装并为最新版本。",
4849
4854
  "about.installFailed": "依赖安装失败,请稍后重试。",
4855
+ "about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
4850
4856
  "about.restartRequired": "正在热更新插件,窗口会自动刷新。",
4857
+ "about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
4858
+ "about.progressHint": "正在安装插件…",
4859
+ "about.packagesDone": "已完成 {0} 个包",
4860
+ "about.progress.resolving": "解析依赖",
4861
+ "about.progress.downloading": "下载中",
4862
+ "about.progress.linking": "写入文件",
4863
+ "about.progress.building": "构建中",
4851
4864
  "about.dependency.dsh": "DeepSeek Harness",
4852
4865
  "about.dependency.suite": "Codex 套件",
4853
4866
  "about.dependency.ui": "Codex UI",
@@ -4966,7 +4979,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4966
4979
  "about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
4967
4980
  "about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
4968
4981
  "about.dependencies": "Companion management plugins",
4969
- "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.",
4982
+ "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.",
4970
4983
  "about.loading": "Loading dependency status…",
4971
4984
  "about.statusFailed": "Dependency status is temporarily unavailable.",
4972
4985
  "about.installed": "Installed",
@@ -4975,11 +4988,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4975
4988
  "about.install": "Install",
4976
4989
  "about.update": "Update",
4977
4990
  "about.installing": "Installing",
4978
- "about.updateAll": "Update all",
4979
- "about.updatingAll": "Updating",
4980
- "about.upToDate": "All installed plugins are up to date.",
4991
+ "about.updateAll": "Install/update all",
4992
+ "about.updatingAll": "Installing/updating",
4993
+ "about.upToDate": "All companion plugins are installed and up to date.",
4981
4994
  "about.installFailed": "Dependency installation failed. Try again later.",
4995
+ "about.installUnchanged": "The install command finished, but the plugin did not enter this profile. Try again.",
4982
4996
  "about.restartRequired": "Updating plugins now. The window will refresh automatically.",
4997
+ "about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
4998
+ "about.progressHint": "Installing plugin…",
4999
+ "about.packagesDone": "{0} packages done",
5000
+ "about.progress.resolving": "Resolving",
5001
+ "about.progress.downloading": "Downloading",
5002
+ "about.progress.linking": "Writing files",
5003
+ "about.progress.building": "Building",
4983
5004
  "about.dependency.dsh": "DeepSeek Harness",
4984
5005
  "about.dependency.suite": "Codex suite",
4985
5006
  "about.dependency.ui": "Codex UI",
@@ -5233,6 +5254,98 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5233
5254
  };
5234
5255
  }
5235
5256
  //#endregion
5257
+ //#region src/client/official-turn-navigator.ts
5258
+ const OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE = "data-dcu-official-turn-navigator";
5259
+ const OFFICIAL_TURN_MARK_ATTRIBUTE = "data-dcu-official-turn-mark";
5260
+ const OFFICIAL_TURN_TOOLTIP_ATTRIBUTE = "data-dcu-official-turn-tooltip";
5261
+ const OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE = "data-dcu-official-turn-navigator-supported";
5262
+ const CAPABILITIES_ENDPOINT = "/api/michengai/codex-ui/dependencies?action=capabilities";
5263
+ function isOfficialTurnNavigator(element) {
5264
+ return element instanceof HTMLElement && element.tagName === "NAV" && element.style.getPropertyValue("--turn-natural-height") !== "" && element.querySelector("button[type=\"button\"][aria-label]") !== null;
5265
+ }
5266
+ function markOfficialTurnNavigatorParts(navigator) {
5267
+ for (const button of navigator.querySelectorAll("button[type=\"button\"][aria-label]")) button.setAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE, "true");
5268
+ for (const tooltip of navigator.querySelectorAll("[role=\"tooltip\"]")) tooltip.setAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE, "true");
5269
+ }
5270
+ /** 标记实际挂载的官方轮次导航,避免依赖宿主样式文件名或会话 DOM 层级。 */
5271
+ function markOfficialTurnNavigators(root = document) {
5272
+ for (const element of root.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`)) {
5273
+ if (isOfficialTurnNavigator(element)) continue;
5274
+ element.removeAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE);
5275
+ for (const part of element.querySelectorAll(`[${OFFICIAL_TURN_MARK_ATTRIBUTE}],[${OFFICIAL_TURN_TOOLTIP_ATTRIBUTE}]`)) {
5276
+ part.removeAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE);
5277
+ part.removeAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE);
5278
+ }
5279
+ }
5280
+ for (const element of root.querySelectorAll("nav")) {
5281
+ if (!isOfficialTurnNavigator(element)) continue;
5282
+ element.setAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE, "true");
5283
+ markOfficialTurnNavigatorParts(element);
5284
+ }
5285
+ return root.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`).length;
5286
+ }
5287
+ function nodeTouchesTurnNavigator(node) {
5288
+ 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);
5289
+ }
5290
+ function nodeInsideTurnNavigator(node) {
5291
+ return node instanceof Element && (node.matches(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) || node.closest(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}]`) !== null);
5292
+ }
5293
+ function turnNavigatorMutationRelevant(records) {
5294
+ 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));
5295
+ }
5296
+ async function runtimeSupportsOfficialTurnNavigator(fetchImpl) {
5297
+ try {
5298
+ const response = await fetchImpl(CAPABILITIES_ENDPOINT, { cache: "no-store" });
5299
+ if (!response.ok) return false;
5300
+ const payload = await response.json();
5301
+ if (payload === null || typeof payload !== "object") return false;
5302
+ const capabilities = payload.capabilities;
5303
+ return capabilities !== null && typeof capabilities === "object" && capabilities.officialTurnNavigator === true;
5304
+ } catch {
5305
+ return false;
5306
+ }
5307
+ }
5308
+ /** 官方导航可能在会话切换后异步挂载;仅对相关变更按帧检测,并在停用时清理标记。 */
5309
+ function observeOfficialTurnNavigators(doc = document, fetchImpl = fetch) {
5310
+ if (doc.body === null || doc.documentElement === null) return () => {};
5311
+ let active = true;
5312
+ let frame;
5313
+ const run = () => {
5314
+ frame = void 0;
5315
+ markOfficialTurnNavigators(doc);
5316
+ };
5317
+ const schedule = () => {
5318
+ if (frame !== void 0) return;
5319
+ frame = window.requestAnimationFrame(run);
5320
+ };
5321
+ const observer = new MutationObserver((records) => {
5322
+ if (turnNavigatorMutationRelevant(records)) schedule();
5323
+ });
5324
+ observer.observe(doc.body, {
5325
+ childList: true,
5326
+ subtree: true,
5327
+ attributes: true,
5328
+ attributeFilter: ["style"]
5329
+ });
5330
+ schedule();
5331
+ runtimeSupportsOfficialTurnNavigator(fetchImpl).then((supported) => {
5332
+ if (!active) return;
5333
+ if (supported) doc.documentElement.setAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE, "true");
5334
+ else doc.documentElement.removeAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE);
5335
+ });
5336
+ return () => {
5337
+ active = false;
5338
+ observer.disconnect();
5339
+ if (frame !== void 0) window.cancelAnimationFrame(frame);
5340
+ doc.documentElement.removeAttribute(OFFICIAL_TURN_NAVIGATOR_SUPPORTED_ATTRIBUTE);
5341
+ for (const element of doc.querySelectorAll(`[${OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE}],[${OFFICIAL_TURN_MARK_ATTRIBUTE}],[${OFFICIAL_TURN_TOOLTIP_ATTRIBUTE}]`)) {
5342
+ element.removeAttribute(OFFICIAL_TURN_NAVIGATOR_ATTRIBUTE);
5343
+ element.removeAttribute(OFFICIAL_TURN_MARK_ATTRIBUTE);
5344
+ element.removeAttribute(OFFICIAL_TURN_TOOLTIP_ATTRIBUTE);
5345
+ }
5346
+ };
5347
+ }
5348
+ //#endregion
5236
5349
  //#region src/client/conversation-dom.ts
5237
5350
  /**
5238
5351
  * DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
@@ -5512,6 +5625,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5512
5625
  ctx.effect(() => observeSettingsNavIcons(), "michengai-codex-ui: settings nav icons");
5513
5626
  ctx.effect(() => observePermissionLabels(ctx.locale), "michengai-codex-ui: permission labels");
5514
5627
  ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
5628
+ ctx.effect(() => observeOfficialTurnNavigators(), "michengai-codex-ui: official turn navigator");
5515
5629
  const companionSlots = createCompanionTabSource(ctx.slots);
5516
5630
  ctx.slots.inject("sidebar", () => ctx.slots.register({
5517
5631
  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];
@@ -305,9 +326,19 @@ function newerVersion(installed, latest) {
305
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];
306
327
  return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
307
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
+ }
308
339
  /** 返回当前 profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
309
340
  async function dependencyStatuses(runtime = resolveDependencyRuntime()) {
310
- const declaredNames = await declaredPluginNames(runtime);
341
+ const bundleNames = await profileBundleNames(runtime);
311
342
  return Promise.all(MANAGED_DEPENDENCIES.map(async (dependency) => {
312
343
  const version = await installedPackageVersion(dependency.packageName, runtime);
313
344
  if (version === void 0 && runtime.environmentKind === "desktop" && isOfficialRuntimePackage(dependency.packageName)) return {
@@ -315,7 +346,7 @@ async function dependencyStatuses(runtime = resolveDependencyRuntime()) {
315
346
  installed: true,
316
347
  updateAvailable: false
317
348
  };
318
- const declared = isOfficialRuntimePackage(dependency.packageName) || isManagedPackageDeclared(dependency.packageName, declaredNames);
349
+ const declared = isOfficialRuntimePackage(dependency.packageName) || isManagedPackageDeclared(dependency.packageName, bundleNames);
319
350
  if (version === void 0 || !isManagedPackageInstalled({
320
351
  installedVersion: version,
321
352
  declared
@@ -348,10 +379,6 @@ function requestDesktopHotUpdate(send = process.send) {
348
379
  send(APPLY_PLUGIN_UPDATES_IPC);
349
380
  return true;
350
381
  }
351
- function isRestartableInstallError(error) {
352
- const message = error instanceof Error ? error.message : String(error);
353
- return /完全退出桌面端|正在运行的插件|pnpm 仓库不一致/.test(message);
354
- }
355
382
  async function recordPendingUpdate(packageName, version, runtime) {
356
383
  const pendingPath = resolve(profileDirectory(runtime), PROFILE_PENDING_UPDATES_FILE);
357
384
  let packages = [];
@@ -407,16 +434,171 @@ async function recordDeclaredVersion(packageName, version, runtime) {
407
434
  };
408
435
  await writeFile(manifestPath, `${JSON.stringify(manifest, void 0, 2)}\n`, "utf8");
409
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
+ }
410
543
  function pluginCommandError(stderr) {
411
544
  const detail = stderr.replace(/\s+/g, " ").trim();
412
545
  if (detail.includes("minimumReleaseAge") || detail.includes("Release age")) return /* @__PURE__ */ new Error("更新被 pnpm 发布时间保护拦截。请确认已写入当前版本白名单后重试。");
413
546
  if (/EPERM|EBUSY|EACCES|unable to unlink|ERR_PNPM_LOCKED|Lock/i.test(detail)) return /* @__PURE__ */ new Error("无法覆盖正在运行的插件文件。请先完全退出桌面端,再重新打开后更新。");
414
547
  if (/UNEXPECTED_STORE|Unexpected store location/i.test(detail)) return /* @__PURE__ */ new Error("插件目录和 pnpm 仓库不一致。请完全退出桌面端后再更新。");
415
548
  if (/ERR_PNPM_IGNORED_BUILDS|Ignored build scripts/i.test(detail)) return /* @__PURE__ */ new Error("插件依赖的 pnpm 构建脚本策略尚未确认。请更新 Profile 的 allowBuilds 配置后重试。");
416
- if (/pnpm not found/i.test(detail)) return /* @__PURE__ */ new Error("当前环境找不到 pnpm。请从桌面端启动后再更新。");
549
+ if (/pnpm not found|未找到 pnpm/i.test(detail)) return /* @__PURE__ */ new Error("当前环境找不到 pnpm。请确认已安装 pnpm 后重启 DSH 再试。");
417
550
  return /* @__PURE__ */ new Error("无法在应用运行时更新插件。请先完全退出桌面端,再重新打开后更新。");
418
551
  }
419
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
+ /**
420
602
  * 复用启动当前服务的 DSH CLI:它会通过 pnpm 从 npm 安装或更新,并自动维护
421
603
  * dsh.profile.bundles,避免浏览器端直接管理 profile 文件。
422
604
  */
@@ -456,7 +638,9 @@ function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
456
638
  let output = "";
457
639
  let settled = false;
458
640
  const collect = (chunk) => {
459
- output = `${output}${String(chunk)}`.slice(-65536);
641
+ const text = String(chunk);
642
+ output = `${output}${text}`.slice(-65536);
643
+ noteInstallOutput(text);
460
644
  };
461
645
  const finish = (error) => {
462
646
  if (settled) return;
@@ -486,7 +670,9 @@ function monitorDesktopPlugin(handle, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
486
670
  let output = "";
487
671
  let settled = false;
488
672
  const collect = (chunk) => {
489
- output = `${output}${String(chunk)}`.slice(-65536);
673
+ const text = String(chunk);
674
+ output = `${output}${text}`.slice(-65536);
675
+ noteInstallOutput(text);
490
676
  };
491
677
  const finish = (error) => {
492
678
  if (settled) return;
@@ -518,7 +704,8 @@ function monitorDesktopPlugin(handle, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
518
704
  });
519
705
  }
520
706
  function runDshPlugin(args, runtime = resolveDependencyRuntime(), timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
521
- 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);
522
709
  const entry = resolveDshCliEntry();
523
710
  return monitorPluginChild(spawn(process.execPath, [
524
711
  ...pluginExecArgv(),
@@ -529,10 +716,7 @@ function runDshPlugin(args, runtime = resolveDependencyRuntime(), timeoutMs = PL
529
716
  ...args
530
717
  ], {
531
718
  cwd: process.cwd(),
532
- env: {
533
- ...process.env,
534
- CI: "true"
535
- },
719
+ env: pluginSpawnEnv(),
536
720
  windowsHide: true,
537
721
  stdio: [
538
722
  "ignore",
@@ -553,11 +737,11 @@ async function installDependency(id, requestHotUpdate = requestDesktopHotUpdate,
553
737
  installing = false;
554
738
  }
555
739
  }
556
- /** 只挑选已安装且确有新版本的插件,缺失插件仍交给各行的“安装”按钮。 */
740
+ /** 顶部批量操作同时安装缺失插件并更新落后版本。 */
557
741
  function updatableDependencyIds(statuses) {
558
- 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);
559
743
  }
560
- /** 把所有待更新版本一次写入清单,最后只请求一次桌面热更新。 */
744
+ /** 把所有待安装/更新版本一次写入清单,最后只请求一次桌面热更新。 */
561
745
  async function updateAllDependencies(requestHotUpdate = requestDesktopHotUpdate, runtime = resolveDependencyRuntime()) {
562
746
  if (installing) throw new Error("已有依赖安装正在进行,请等待完成后再试。");
563
747
  installing = true;
@@ -609,30 +793,39 @@ async function installDependenciesLocked(ids, requestHotUpdate, runtime) {
609
793
  version
610
794
  };
611
795
  }));
612
- const remove = [...new Set(targets.flatMap((target) => pluginsToRemoveBeforeInstall(declared, target.packageName)))];
613
- await ensureRequiredBuildPolicies(runtime);
614
- for (const target of targets) {
615
- await ensureLatestReleaseAllowed(target.packageName, target.version, runtime);
616
- if (!isOfficialRuntimePackage(target.packageName)) await recordDeclaredVersion(target.packageName, target.version, runtime);
617
- await recordPendingUpdate(target.packageName, target.version, runtime);
618
- }
619
- if (remove.length > 0) await runDshPlugin(["remove", ...remove], runtime);
620
- if (runtime.desktopPnpm === void 0 && requestHotUpdate()) return dependencyStatuses(runtime);
621
- for (const target of targets) {
622
- 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) {
623
814
  await runDshPlugin([
624
815
  "add",
625
- `${target.packageName}@${target.version}`,
816
+ ...batch.map((target) => `${target.packageName}@${target.version}`),
626
817
  "--registry=https://registry.npmjs.org/"
627
818
  ], runtime);
628
- } catch (error) {
629
- if (isRestartableInstallError(error)) return dependencyStatuses(runtime);
630
- 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
+ }
631
824
  }
632
- if (await installedPackageVersion(target.packageName, runtime) !== target.version) return dependencyStatuses(runtime);
633
- await removePendingUpdate(target.packageName, runtime);
825
+ return dependencyStatuses(runtime);
826
+ } finally {
827
+ endInstallProgress();
634
828
  }
635
- return dependencyStatuses(runtime);
636
829
  }
637
830
  //#endregion
638
831
  //#region src/explorer-path-policy.ts
@@ -1071,6 +1264,23 @@ function apply(ctx) {
1071
1264
  const url = new URL(request.url ?? "/", "http://localhost");
1072
1265
  try {
1073
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
+ }
1074
1284
  const dependencies = await dependencyStatuses(resolveDependencyRuntime(ctx));
1075
1285
  response.writeHead(200, {
1076
1286
  "content-type": "application/json; charset=utf-8",
@@ -1089,9 +1299,10 @@ function apply(ctx) {
1089
1299
  return;
1090
1300
  }
1091
1301
  if (url.searchParams.get("action") === "update-all") {
1302
+ const autoReload = typeof process.send === "function";
1092
1303
  let restartAfterResponse = false;
1093
1304
  const { dependencies, updatedCount } = await updateAllDependencies(() => {
1094
- restartAfterResponse = typeof process.send === "function";
1305
+ restartAfterResponse = autoReload;
1095
1306
  return restartAfterResponse;
1096
1307
  }, resolveDependencyRuntime(ctx));
1097
1308
  response.writeHead(200, {
@@ -1100,16 +1311,18 @@ function apply(ctx) {
1100
1311
  });
1101
1312
  response.end(JSON.stringify({
1102
1313
  dependencies,
1103
- restartRequired: updatedCount > 0
1314
+ restartRequired: updatedCount > 0,
1315
+ autoReload: typeof process.send === "function"
1104
1316
  }));
1105
1317
  if (restartAfterResponse) setTimeout(() => {
1106
1318
  requestDesktopHotUpdate();
1107
1319
  }, 150).unref?.();
1108
1320
  return;
1109
1321
  }
1322
+ const autoReload = typeof process.send === "function";
1110
1323
  let restartAfterResponse = false;
1111
1324
  const dependencies = await installDependency(url.searchParams.get("dependency"), () => {
1112
- restartAfterResponse = typeof process.send === "function";
1325
+ restartAfterResponse = autoReload;
1113
1326
  return restartAfterResponse;
1114
1327
  }, resolveDependencyRuntime(ctx));
1115
1328
  response.writeHead(200, {
@@ -1118,7 +1331,8 @@ function apply(ctx) {
1118
1331
  });
1119
1332
  response.end(JSON.stringify({
1120
1333
  dependencies,
1121
- restartRequired: true
1334
+ restartRequired: true,
1335
+ autoReload: typeof process.send === "function"
1122
1336
  }));
1123
1337
  if (restartAfterResponse) setTimeout(() => {
1124
1338
  requestDesktopHotUpdate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-ui",
3
- "version": "0.2.96",
3
+ "version": "0.2.98",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {