@michengai/dsh-codex-ui 0.2.96 → 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 +20 -0
- package/CHANGELOG.zh-CN.md +20 -0
- package/dist/client.js +215 -102
- package/dist/index.mjs +260 -46
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@
|
|
|
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
|
+
|
|
7
27
|
## 0.2.96 — 2026-09-01
|
|
8
28
|
|
|
9
29
|
Companion release: `@michengai/dsh-codex-suite-installer@0.1.13`.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,26 @@
|
|
|
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
|
+
|
|
7
27
|
## 0.2.96 — 2026-09-01
|
|
8
28
|
|
|
9
29
|
配套版本:`@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
|
-
/**
|
|
737
|
+
/** 撤回旧版 Codex 用户卡片,恢复 DSH 官方问题气泡。 */
|
|
738
738
|
const USER_BUBBLE_STYLE_ID = "dcu-user-bubble-style";
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
|
@@ -965,16 +890,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
965
890
|
style.textContent = CONVERSATION_HEADER_STYLE;
|
|
966
891
|
doc.head.append(style);
|
|
967
892
|
}
|
|
968
|
-
const CONVERSATION_DECORATION_SELECTOR = "header
|
|
893
|
+
const CONVERSATION_DECORATION_SELECTOR = "header";
|
|
969
894
|
function conversationDecorationMutation(records) {
|
|
970
|
-
const relevant = (node) => node instanceof Element && (node.matches(CONVERSATION_DECORATION_SELECTOR) || node.closest(
|
|
895
|
+
const relevant = (node) => node instanceof Element && (node.matches(CONVERSATION_DECORATION_SELECTOR) || node.closest(CONVERSATION_DECORATION_SELECTOR) !== null || node.querySelector(CONVERSATION_DECORATION_SELECTOR) !== null);
|
|
971
896
|
return records.some((record) => relevant(record.target) || [...record.addedNodes].some(relevant) || [...record.removedNodes].some(relevant));
|
|
972
897
|
}
|
|
973
|
-
/**
|
|
898
|
+
/** 观察会话顶栏;只对相关子树变更按帧合并,流式回答不会触发全文档扫描。 */
|
|
974
899
|
function observeConversationHeader(doc = document) {
|
|
975
900
|
if (doc.head === null || doc.body === null) return () => {};
|
|
976
901
|
ensureStyle(doc);
|
|
977
|
-
|
|
902
|
+
restoreOfficialUserBubbles(doc);
|
|
978
903
|
let applying = false;
|
|
979
904
|
let frame;
|
|
980
905
|
let watchedTabs;
|
|
@@ -994,7 +919,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
994
919
|
syncTabSlider(doc);
|
|
995
920
|
decorateConversationTitle(doc);
|
|
996
921
|
decorateSessionLogDownload(doc);
|
|
997
|
-
decorateUserBubbles(doc);
|
|
998
922
|
} finally {
|
|
999
923
|
applying = false;
|
|
1000
924
|
}
|
|
@@ -3542,7 +3466,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3542
3466
|
const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
|
|
3543
3467
|
const SIDEBAR_COLLAPSE_SETTLE_MS = 140;
|
|
3544
3468
|
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
|
|
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)}
|
|
3546
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}}
|
|
3547
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}
|
|
3548
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}
|
|
@@ -3555,7 +3479,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3555
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}
|
|
3556
3480
|
[role="dialog"][aria-labelledby]{width:min(1000px,calc(100vw - 48px));max-width:calc(100vw - 48px);height:min(800px,calc(100vh - 48px))}
|
|
3557
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}}
|
|
3558
|
-
[data-conversation-scroll]{--dsh-
|
|
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}
|
|
3559
3488
|
`;
|
|
3560
3489
|
function MenuIcon({ children }) {
|
|
3561
3490
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -4327,20 +4256,50 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4327
4256
|
];
|
|
4328
4257
|
//#endregion
|
|
4329
4258
|
//#region src/client/AboutSection.tsx
|
|
4259
|
+
const PROGRESS_PHASES = /* @__PURE__ */ new Set([
|
|
4260
|
+
"resolving",
|
|
4261
|
+
"downloading",
|
|
4262
|
+
"linking",
|
|
4263
|
+
"building"
|
|
4264
|
+
]);
|
|
4330
4265
|
const endpoint = "/api/michengai/codex-ui/dependencies";
|
|
4331
4266
|
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)}
|
|
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%)}}
|
|
4333
4268
|
`;
|
|
4334
4269
|
function isDependencyStatus(value) {
|
|
4335
4270
|
if (value === null || typeof value !== "object") return false;
|
|
4336
4271
|
const status = value;
|
|
4337
4272
|
return MANAGED_DEPENDENCIES.some((dependency) => dependency.id === status.id) && typeof status.packageName === "string" && typeof status.installed === "boolean" && typeof status.updateAvailable === "boolean";
|
|
4338
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
|
+
}
|
|
4339
4297
|
/** Codex UI 的功能说明、配套管理插件状态与受限安装入口。 */
|
|
4340
4298
|
function AboutSection({ t }) {
|
|
4341
4299
|
const [dependencies, setDependencies] = (0, react.useState)([]);
|
|
4342
4300
|
const [state, setState] = (0, react.useState)("loading");
|
|
4343
4301
|
const [installing, setInstalling] = (0, react.useState)();
|
|
4302
|
+
const [progress, setProgress] = (0, react.useState)();
|
|
4344
4303
|
const [message, setMessage] = (0, react.useState)();
|
|
4345
4304
|
const alive = (0, react.useRef)(true);
|
|
4346
4305
|
const root = (0, react.useRef)(null);
|
|
@@ -4400,6 +4359,31 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4400
4359
|
observer.disconnect();
|
|
4401
4360
|
};
|
|
4402
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]);
|
|
4403
4387
|
const install = async (id) => {
|
|
4404
4388
|
if (installingRef.current !== void 0) return;
|
|
4405
4389
|
installingRef.current = id;
|
|
@@ -4414,13 +4398,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4414
4398
|
setState("ready");
|
|
4415
4399
|
setMessage({
|
|
4416
4400
|
error: false,
|
|
4417
|
-
text: t("about.restartRequired")
|
|
4401
|
+
text: payload.autoReload === false ? t("about.restartManually") : t("about.restartRequired")
|
|
4418
4402
|
});
|
|
4419
4403
|
} catch (error) {
|
|
4420
4404
|
if (!alive.current) return;
|
|
4421
4405
|
setMessage({
|
|
4422
4406
|
error: true,
|
|
4423
|
-
text: error
|
|
4407
|
+
text: installErrorText(error, t)
|
|
4424
4408
|
});
|
|
4425
4409
|
} finally {
|
|
4426
4410
|
installingRef.current = void 0;
|
|
@@ -4441,20 +4425,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4441
4425
|
setState("ready");
|
|
4442
4426
|
setMessage({
|
|
4443
4427
|
error: false,
|
|
4444
|
-
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")
|
|
4445
4429
|
});
|
|
4446
4430
|
} catch (error) {
|
|
4447
4431
|
if (!alive.current) return;
|
|
4448
4432
|
setMessage({
|
|
4449
4433
|
error: true,
|
|
4450
|
-
text: error
|
|
4434
|
+
text: installErrorText(error, t)
|
|
4451
4435
|
});
|
|
4452
4436
|
} finally {
|
|
4453
4437
|
installingRef.current = void 0;
|
|
4454
4438
|
if (alive.current) setInstalling(void 0);
|
|
4455
4439
|
}
|
|
4456
4440
|
};
|
|
4457
|
-
const
|
|
4441
|
+
const actionableCount = dependencies.filter((dependency) => !dependency.installed || dependency.updateAvailable).length;
|
|
4458
4442
|
const title = (id) => t(`about.dependency.${id}`);
|
|
4459
4443
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4460
4444
|
ref: root,
|
|
@@ -4481,7 +4465,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4481
4465
|
}),
|
|
4482
4466
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4483
4467
|
className: "dcu-about-dependencies-heading",
|
|
4484
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" &&
|
|
4468
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("about.dependencies") }), state === "ready" && actionableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4485
4469
|
className: "dcu-about-install dcu-about-update-all",
|
|
4486
4470
|
type: "button",
|
|
4487
4471
|
"aria-busy": installing === "all",
|
|
@@ -4543,6 +4527,26 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4543
4527
|
]
|
|
4544
4528
|
}, dependency.id))
|
|
4545
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
|
+
}),
|
|
4546
4550
|
message !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4547
4551
|
className: "dcu-about-message",
|
|
4548
4552
|
"data-error": message.error,
|
|
@@ -4834,7 +4838,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4834
4838
|
"about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
|
|
4835
4839
|
"about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
|
|
4836
4840
|
"about.dependencies": "配套管理插件",
|
|
4837
|
-
"about.dependenciesDescription": "
|
|
4841
|
+
"about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键安装缺失项并更新旧版本。可分别检查 Codex UI、专家、技能、归档、IM、定时任务和第三方插件市场。",
|
|
4838
4842
|
"about.loading": "正在读取依赖安装状态…",
|
|
4839
4843
|
"about.statusFailed": "暂时无法读取依赖安装状态。",
|
|
4840
4844
|
"about.installed": "已安装",
|
|
@@ -4843,11 +4847,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4843
4847
|
"about.install": "安装",
|
|
4844
4848
|
"about.update": "更新",
|
|
4845
4849
|
"about.installing": "安装中",
|
|
4846
|
-
"about.updateAll": "
|
|
4847
|
-
"about.updatingAll": "
|
|
4848
|
-
"about.upToDate": "
|
|
4850
|
+
"about.updateAll": "全部安装/更新",
|
|
4851
|
+
"about.updatingAll": "安装/更新中",
|
|
4852
|
+
"about.upToDate": "所有配套插件均已安装并为最新版本。",
|
|
4849
4853
|
"about.installFailed": "依赖安装失败,请稍后重试。",
|
|
4854
|
+
"about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
|
|
4850
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": "构建中",
|
|
4851
4863
|
"about.dependency.dsh": "DeepSeek Harness",
|
|
4852
4864
|
"about.dependency.suite": "Codex 套件",
|
|
4853
4865
|
"about.dependency.ui": "Codex UI",
|
|
@@ -4966,7 +4978,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4966
4978
|
"about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
|
|
4967
4979
|
"about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
|
|
4968
4980
|
"about.dependencies": "Companion management plugins",
|
|
4969
|
-
"about.dependenciesDescription": "Install or update each companion
|
|
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.",
|
|
4970
4982
|
"about.loading": "Loading dependency status…",
|
|
4971
4983
|
"about.statusFailed": "Dependency status is temporarily unavailable.",
|
|
4972
4984
|
"about.installed": "Installed",
|
|
@@ -4975,11 +4987,19 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4975
4987
|
"about.install": "Install",
|
|
4976
4988
|
"about.update": "Update",
|
|
4977
4989
|
"about.installing": "Installing",
|
|
4978
|
-
"about.updateAll": "
|
|
4979
|
-
"about.updatingAll": "
|
|
4980
|
-
"about.upToDate": "All
|
|
4990
|
+
"about.updateAll": "Install/update all",
|
|
4991
|
+
"about.updatingAll": "Installing/updating",
|
|
4992
|
+
"about.upToDate": "All companion plugins are installed and up to date.",
|
|
4981
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.",
|
|
4982
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",
|
|
4983
5003
|
"about.dependency.dsh": "DeepSeek Harness",
|
|
4984
5004
|
"about.dependency.suite": "Codex suite",
|
|
4985
5005
|
"about.dependency.ui": "Codex UI",
|
|
@@ -5233,6 +5253,98 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
5233
5253
|
};
|
|
5234
5254
|
}
|
|
5235
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
|
|
5236
5348
|
//#region src/client/conversation-dom.ts
|
|
5237
5349
|
/**
|
|
5238
5350
|
* DSH 尚未公开会话滚动容器和消息锚点服务;把兼容读取集中在这里,方便
|
|
@@ -5512,6 +5624,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
5512
5624
|
ctx.effect(() => observeSettingsNavIcons(), "michengai-codex-ui: settings nav icons");
|
|
5513
5625
|
ctx.effect(() => observePermissionLabels(ctx.locale), "michengai-codex-ui: permission labels");
|
|
5514
5626
|
ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
|
|
5627
|
+
ctx.effect(() => observeOfficialTurnNavigators(), "michengai-codex-ui: official turn navigator");
|
|
5515
5628
|
const companionSlots = createCompanionTabSource(ctx.slots);
|
|
5516
5629
|
ctx.slots.inject("sidebar", () => ctx.slots.register({
|
|
5517
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
|
|
116
|
+
async function readProfileManifest(runtime) {
|
|
115
117
|
try {
|
|
116
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
await
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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 =
|
|
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 =
|
|
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();
|