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