@michengai/dsh-codex-ui 0.2.72 → 0.2.74
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/dist/client.js +215 -58
- package/dist/index.mjs +127 -44
- package/package.json +3 -2
package/dist/client.js
CHANGED
|
@@ -18,6 +18,7 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
if (match !== void 0) return match;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
let cancelPendingNavigation;
|
|
21
22
|
function openSettingsSection(root, label, onMissing) {
|
|
22
23
|
const labels = typeof label === "string" ? [label] : label;
|
|
23
24
|
const trigger = root?.querySelector("[aria-haspopup=\"dialog\"]");
|
|
@@ -25,18 +26,48 @@ window.__ModuleLoader__.load({
|
|
|
25
26
|
onMissing?.();
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
const opening = cancelPendingNavigation !== void 0;
|
|
30
|
+
cancelPendingNavigation?.();
|
|
31
|
+
if (!opening && document.querySelector("[role=\"dialog\"]") === null) trigger.click();
|
|
32
|
+
let frame;
|
|
33
|
+
let finished = false;
|
|
34
|
+
const observer = new MutationObserver(() => {
|
|
35
|
+
schedule();
|
|
36
|
+
});
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
if (finished) return;
|
|
39
|
+
finished = true;
|
|
40
|
+
observer.disconnect();
|
|
41
|
+
window.clearTimeout(timeout);
|
|
42
|
+
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
43
|
+
if (cancelPendingNavigation === cleanup) cancelPendingNavigation = void 0;
|
|
44
|
+
};
|
|
45
|
+
const select = () => {
|
|
46
|
+
const target = pickSettingsSectionButton([...document.querySelectorAll("[role=\"dialog\"] nav button")], labels);
|
|
47
|
+
if (target === void 0) return false;
|
|
48
|
+
cleanup();
|
|
49
|
+
target.click();
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
const schedule = () => {
|
|
53
|
+
if (finished || frame !== void 0) return;
|
|
54
|
+
frame = window.requestAnimationFrame(() => {
|
|
55
|
+
frame = void 0;
|
|
56
|
+
select();
|
|
38
57
|
});
|
|
58
|
+
};
|
|
59
|
+
const timeout = window.setTimeout(() => {
|
|
60
|
+
if (select()) return;
|
|
61
|
+
cleanup();
|
|
62
|
+
console.warn(`[michengai-codex-ui] 未找到设置分区:${labels.join(" / ")}`);
|
|
63
|
+
onMissing?.();
|
|
64
|
+
}, 1500);
|
|
65
|
+
observer.observe(document.body, {
|
|
66
|
+
childList: true,
|
|
67
|
+
subtree: true
|
|
39
68
|
});
|
|
69
|
+
cancelPendingNavigation = cleanup;
|
|
70
|
+
schedule();
|
|
40
71
|
}
|
|
41
72
|
//#endregion
|
|
42
73
|
//#region src/client/sidebar-search.ts
|
|
@@ -52,6 +83,7 @@ window.__ModuleLoader__.load({
|
|
|
52
83
|
channels: false,
|
|
53
84
|
schedule: false
|
|
54
85
|
};
|
|
86
|
+
const reportedSlotErrors = /* @__PURE__ */ new WeakSet();
|
|
55
87
|
/** 插槽声明本身不算占用;只有其他插件 register 后才视为已安装。 */
|
|
56
88
|
function readSlotEntries(slots, name) {
|
|
57
89
|
if (slots === void 0) return [];
|
|
@@ -59,7 +91,11 @@ window.__ModuleLoader__.load({
|
|
|
59
91
|
if (typeof read !== "function") return [];
|
|
60
92
|
try {
|
|
61
93
|
return read.call(slots, name) ?? [];
|
|
62
|
-
} catch {
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (!reportedSlotErrors.has(slots)) {
|
|
96
|
+
reportedSlotErrors.add(slots);
|
|
97
|
+
console.warn("[michengai-codex-ui] 无法读取配套插件插槽。", error);
|
|
98
|
+
}
|
|
63
99
|
return [];
|
|
64
100
|
}
|
|
65
101
|
}
|
|
@@ -251,11 +287,19 @@ window.__ModuleLoader__.load({
|
|
|
251
287
|
});
|
|
252
288
|
}
|
|
253
289
|
/** 读取 IM 频道分组;失败时交给界面显示空态或错误。 */
|
|
254
|
-
async function loadChannelGroups() {
|
|
255
|
-
const response = await fetch(CHANNELS_ENDPOINT, {
|
|
256
|
-
|
|
257
|
-
|
|
290
|
+
async function loadChannelGroups(signal) {
|
|
291
|
+
const response = await fetch(CHANNELS_ENDPOINT, {
|
|
292
|
+
cache: "no-store",
|
|
293
|
+
signal
|
|
294
|
+
});
|
|
295
|
+
let payload;
|
|
296
|
+
try {
|
|
297
|
+
payload = await response.json();
|
|
298
|
+
} catch {
|
|
299
|
+
throw new Error(response.ok ? "频道会话数据格式无效" : "无法读取频道会话");
|
|
300
|
+
}
|
|
258
301
|
const root = payload !== null && typeof payload === "object" ? payload : {};
|
|
302
|
+
if (!response.ok) throw new Error(text(root.error, "无法读取频道会话"));
|
|
259
303
|
if (root.ok === false) throw new Error(text(root.error, "无法读取频道会话"));
|
|
260
304
|
return parseChannelGroups(payload);
|
|
261
305
|
}
|
|
@@ -679,7 +723,6 @@ window.__ModuleLoader__.load({
|
|
|
679
723
|
card.append(sub);
|
|
680
724
|
}
|
|
681
725
|
card.dataset.dcuUserText = text;
|
|
682
|
-
card.title = text;
|
|
683
726
|
}
|
|
684
727
|
}
|
|
685
728
|
function ensureUserBubbleStyle(doc) {
|
|
@@ -790,19 +833,25 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
790
833
|
slider.style.transform = `translateX(${Math.max(0, tabBox.left - listBox.left)}px)`;
|
|
791
834
|
}
|
|
792
835
|
function watchTabSelection(tabs) {
|
|
793
|
-
if (tabs.dataset.dcuTabWatch === "") return;
|
|
794
836
|
tabs.dataset.dcuTabWatch = "";
|
|
795
837
|
const sync = () => {
|
|
796
838
|
syncTabSlider(tabs);
|
|
797
839
|
};
|
|
798
|
-
|
|
840
|
+
const onClick = () => {
|
|
841
|
+
window.requestAnimationFrame(sync);
|
|
842
|
+
};
|
|
843
|
+
const observer = new MutationObserver(sync);
|
|
844
|
+
observer.observe(tabs, {
|
|
799
845
|
attributes: true,
|
|
800
846
|
subtree: true,
|
|
801
847
|
attributeFilter: ["aria-selected", "data-state"]
|
|
802
848
|
});
|
|
803
|
-
tabs.addEventListener("click",
|
|
804
|
-
|
|
805
|
-
|
|
849
|
+
tabs.addEventListener("click", onClick);
|
|
850
|
+
return () => {
|
|
851
|
+
observer.disconnect();
|
|
852
|
+
tabs.removeEventListener("click", onClick);
|
|
853
|
+
delete tabs.dataset.dcuTabWatch;
|
|
854
|
+
};
|
|
806
855
|
}
|
|
807
856
|
function ensureStyle(doc) {
|
|
808
857
|
if (doc.getElementById("dcu-conversation-header-style") !== null) return;
|
|
@@ -818,6 +867,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
818
867
|
ensureUserBubbleStyle(doc);
|
|
819
868
|
let applying = false;
|
|
820
869
|
let frame;
|
|
870
|
+
let watchedTabs;
|
|
871
|
+
let stopWatchingTabs;
|
|
821
872
|
const run = () => {
|
|
822
873
|
frame = void 0;
|
|
823
874
|
if (applying) return;
|
|
@@ -825,7 +876,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
825
876
|
try {
|
|
826
877
|
placeConversationTabs(doc);
|
|
827
878
|
const tabs = findConversationTablist(doc);
|
|
828
|
-
if (tabs !==
|
|
879
|
+
if (tabs !== watchedTabs) {
|
|
880
|
+
stopWatchingTabs?.();
|
|
881
|
+
watchedTabs = tabs;
|
|
882
|
+
stopWatchingTabs = tabs === void 0 ? void 0 : watchTabSelection(tabs);
|
|
883
|
+
}
|
|
829
884
|
syncTabSlider(doc);
|
|
830
885
|
decorateConversationTitle(doc);
|
|
831
886
|
decorateUserBubbles(doc);
|
|
@@ -845,6 +900,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
845
900
|
});
|
|
846
901
|
return () => {
|
|
847
902
|
observer.disconnect();
|
|
903
|
+
stopWatchingTabs?.();
|
|
848
904
|
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
849
905
|
};
|
|
850
906
|
}
|
|
@@ -903,6 +959,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
903
959
|
window.clearTimeout(showTipTimer.current);
|
|
904
960
|
showTipTimer.current = void 0;
|
|
905
961
|
}
|
|
962
|
+
if (hideTipTimer.current !== void 0) window.clearTimeout(hideTipTimer.current);
|
|
906
963
|
hideTipTimer.current = window.setTimeout(() => {
|
|
907
964
|
setHoverTip(void 0);
|
|
908
965
|
}, 120);
|
|
@@ -1103,6 +1160,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1103
1160
|
if (!after) return hoveredId;
|
|
1104
1161
|
return index >= ids.length - 1 ? void 0 : ids[index + 1];
|
|
1105
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* 置顶标题区与首项目的上半区都表示“插到第一项之前”,因此必须共用同一条指示线。
|
|
1165
|
+
* 非空列表把指示线贴到首项目顶部;空列表才使用独立的起始占位线。
|
|
1166
|
+
*/
|
|
1167
|
+
function pinnedHeaderDropIndicator(ids) {
|
|
1168
|
+
const workspaceId = ids[0];
|
|
1169
|
+
return workspaceId === void 0 ? { kind: "empty" } : {
|
|
1170
|
+
kind: "workspace",
|
|
1171
|
+
workspaceId
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1106
1174
|
/** 按指定 id 顺序取出对应项;未出现在 ids 中的项丢弃。 */
|
|
1107
1175
|
function orderByIds(items, ids, idOf) {
|
|
1108
1176
|
const byId = new Map(items.map((item) => [idOf(item), item]));
|
|
@@ -1229,6 +1297,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1229
1297
|
};
|
|
1230
1298
|
const [renameDraft, setRenameDraft] = (0, react.useState)("");
|
|
1231
1299
|
const [busy, setBusy] = (0, react.useState)();
|
|
1300
|
+
const busyRef = (0, react.useRef)();
|
|
1232
1301
|
const [error, setError] = (0, react.useState)();
|
|
1233
1302
|
const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
|
|
1234
1303
|
const [workspaceDropId, setWorkspaceDropId] = (0, react.useState)();
|
|
@@ -1336,9 +1405,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1336
1405
|
const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId));
|
|
1337
1406
|
const regularGroups = groups.items.filter((workspace) => !projectPinned(workspace.workspaceId));
|
|
1338
1407
|
const pinDragActive = workspaceDragId !== void 0;
|
|
1408
|
+
const pinnedHeaderDrop = pinSlot === "header" ? pinnedHeaderDropIndicator(pinnedGroups.map((workspace) => String(workspace.workspaceId))) : void 0;
|
|
1339
1409
|
const assignedIds = workspaces.items.flatMap((workspace) => workspace.sessionIds.map((id) => String(id)));
|
|
1340
1410
|
const recentIds = ungroupedSessionIds(sessions.ids ?? Object.keys(sessions.byId), sessions.byId, assignedIds, workspaces.archivedSessionIds).sort((left, right) => (sessions.byId[right]?.updatedAt ?? 0) - (sessions.byId[left]?.updatedAt ?? 0));
|
|
1341
1411
|
const run = async (key, action) => {
|
|
1412
|
+
if (busyRef.current !== void 0) return;
|
|
1413
|
+
busyRef.current = key;
|
|
1342
1414
|
setBusy(key);
|
|
1343
1415
|
setError(void 0);
|
|
1344
1416
|
try {
|
|
@@ -1347,6 +1419,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1347
1419
|
} catch (reason) {
|
|
1348
1420
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
1349
1421
|
} finally {
|
|
1422
|
+
busyRef.current = void 0;
|
|
1350
1423
|
setBusy(void 0);
|
|
1351
1424
|
}
|
|
1352
1425
|
};
|
|
@@ -1453,6 +1526,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1453
1526
|
y: menu.y
|
|
1454
1527
|
} : void 0;
|
|
1455
1528
|
const isCurrentWorkspace = sessions.current !== void 0 && workspace.visibleIds.includes(sessions.current);
|
|
1529
|
+
const isPinnedHeaderDrop = zone === "pinned" && pinnedHeaderDrop?.kind === "workspace" && pinnedHeaderDrop.workspaceId === String(workspace.workspaceId);
|
|
1456
1530
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1457
1531
|
className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}`,
|
|
1458
1532
|
onDragOver: (event) => {
|
|
@@ -1496,7 +1570,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1496
1570
|
},
|
|
1497
1571
|
children: [
|
|
1498
1572
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1499
|
-
className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${workspaceDropId === workspace.workspaceId ? workspaceDropAfter ? " dcu-wb-drop dcu-wb-drop-after" : " dcu-wb-drop" : ""}${workspaceDragId === String(workspace.workspaceId) ? " dcu-wb-dragging" : ""}`,
|
|
1573
|
+
className: `dcu-wb-project-head${menuOpen ? " dcu-wb-menu-open" : ""}${isPinnedHeaderDrop || workspaceDropId === workspace.workspaceId ? workspaceDropAfter && !isPinnedHeaderDrop ? " dcu-wb-drop dcu-wb-drop-after" : " dcu-wb-drop" : ""}${workspaceDragId === String(workspace.workspaceId) ? " dcu-wb-dragging" : ""}`,
|
|
1500
1574
|
role: "treeitem",
|
|
1501
1575
|
"aria-expanded": isExpanded,
|
|
1502
1576
|
tabIndex: 0,
|
|
@@ -1868,7 +1942,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1868
1942
|
if (!pinDragActive) return;
|
|
1869
1943
|
event.preventDefault();
|
|
1870
1944
|
event.dataTransfer.dropEffect = "move";
|
|
1871
|
-
if (event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null)
|
|
1945
|
+
if (event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null) {
|
|
1946
|
+
setPinSlot("header");
|
|
1947
|
+
setWorkspaceDropId(void 0);
|
|
1948
|
+
setWorkspaceDropAfter(false);
|
|
1949
|
+
}
|
|
1872
1950
|
},
|
|
1873
1951
|
onDrop: (event) => {
|
|
1874
1952
|
event.preventDefault();
|
|
@@ -1915,10 +1993,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
1915
1993
|
className: "dcu-wb-section-body",
|
|
1916
1994
|
"data-open": sectionOpen("pinned"),
|
|
1917
1995
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
1918
|
-
|
|
1919
|
-
className: `dcu-wb-pin-start${
|
|
1996
|
+
pinDragActive && pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1997
|
+
className: `dcu-wb-pin-start${pinnedHeaderDrop?.kind === "empty" ? " dcu-wb-drop" : ""}`,
|
|
1920
1998
|
onDragOver: (event) => {
|
|
1921
|
-
if (!pinDragActive) return;
|
|
1922
1999
|
event.preventDefault();
|
|
1923
2000
|
event.stopPropagation();
|
|
1924
2001
|
event.dataTransfer.dropEffect = "move";
|
|
@@ -2330,7 +2407,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2330
2407
|
function useBusyAction(onSuccess) {
|
|
2331
2408
|
const [busy, setBusy] = (0, react.useState)();
|
|
2332
2409
|
const [error, setError] = (0, react.useState)();
|
|
2410
|
+
const busyRef = (0, react.useRef)();
|
|
2333
2411
|
const run = async (key, action) => {
|
|
2412
|
+
if (busyRef.current !== void 0) return;
|
|
2413
|
+
busyRef.current = key;
|
|
2334
2414
|
setBusy(key);
|
|
2335
2415
|
setError(void 0);
|
|
2336
2416
|
try {
|
|
@@ -2339,6 +2419,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2339
2419
|
} catch (reason) {
|
|
2340
2420
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2341
2421
|
} finally {
|
|
2422
|
+
busyRef.current = void 0;
|
|
2342
2423
|
setBusy(void 0);
|
|
2343
2424
|
}
|
|
2344
2425
|
};
|
|
@@ -2557,11 +2638,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2557
2638
|
});
|
|
2558
2639
|
(0, react.useEffect)(() => {
|
|
2559
2640
|
let disposed = false;
|
|
2560
|
-
let
|
|
2641
|
+
let active;
|
|
2561
2642
|
const load = () => {
|
|
2562
|
-
if (
|
|
2563
|
-
|
|
2564
|
-
|
|
2643
|
+
if (active !== void 0 || document.visibilityState === "hidden") return;
|
|
2644
|
+
const controller = new AbortController();
|
|
2645
|
+
active = controller;
|
|
2646
|
+
const timeout = window.setTimeout(() => {
|
|
2647
|
+
controller.abort();
|
|
2648
|
+
}, 8e3);
|
|
2649
|
+
loadChannelGroups(controller.signal).then((next) => {
|
|
2565
2650
|
if (!disposed) {
|
|
2566
2651
|
setGroups(next);
|
|
2567
2652
|
setPollError(void 0);
|
|
@@ -2569,16 +2654,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2569
2654
|
}).catch(() => {
|
|
2570
2655
|
if (!disposed) setPollError(t("channels.loadError"));
|
|
2571
2656
|
}).finally(() => {
|
|
2572
|
-
|
|
2657
|
+
window.clearTimeout(timeout);
|
|
2658
|
+
if (active === controller) active = void 0;
|
|
2573
2659
|
});
|
|
2574
2660
|
};
|
|
2575
2661
|
load();
|
|
2576
2662
|
const timer = window.setInterval(load, 4e3);
|
|
2663
|
+
const onVisibilityChange = () => {
|
|
2664
|
+
if (document.visibilityState === "visible") load();
|
|
2665
|
+
};
|
|
2666
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
2577
2667
|
return () => {
|
|
2578
2668
|
disposed = true;
|
|
2669
|
+
active?.abort();
|
|
2579
2670
|
window.clearInterval(timer);
|
|
2671
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
2580
2672
|
};
|
|
2581
|
-
}, []);
|
|
2673
|
+
}, [t]);
|
|
2582
2674
|
const banner = pollError ?? error;
|
|
2583
2675
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2584
2676
|
className: "dcu-wb",
|
|
@@ -2875,6 +2967,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2875
2967
|
}
|
|
2876
2968
|
//#endregion
|
|
2877
2969
|
//#region src/client/CodexSidebar.tsx
|
|
2970
|
+
const subscribeEmptyCompanionTabs = () => () => {};
|
|
2971
|
+
const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
|
|
2878
2972
|
const stylesheet$3 = `
|
|
2879
2973
|
.dcu-root{--dcu-font:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei UI",sans-serif;--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;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)}
|
|
2880
2974
|
.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}
|
|
@@ -2932,7 +3026,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
2932
3026
|
const [searchQuery, setSearchQuery] = (0, react.useState)("");
|
|
2933
3027
|
const [activeSearchIndex, setActiveSearchIndex] = (0, react.useState)(0);
|
|
2934
3028
|
const [imTab, setImTab] = (0, react.useState)("tasks");
|
|
2935
|
-
const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ??
|
|
3029
|
+
const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? subscribeEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs);
|
|
2936
3030
|
const showChannels = companionTabs.channels;
|
|
2937
3031
|
const showSchedule = companionTabs.schedule;
|
|
2938
3032
|
const showCompanionTabs = showChannels || showSchedule;
|
|
@@ -3126,14 +3220,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3126
3220
|
(0, react.useEffect)(() => {
|
|
3127
3221
|
let startX = 0;
|
|
3128
3222
|
let dragging = false;
|
|
3223
|
+
let pointerId;
|
|
3129
3224
|
const onDown = (event) => {
|
|
3130
|
-
if (!isSidebarDragHandle(event.target)) return;
|
|
3225
|
+
if (dragging || !isSidebarDragHandle(event.target)) return;
|
|
3131
3226
|
dragging = true;
|
|
3227
|
+
pointerId = event.pointerId;
|
|
3132
3228
|
startX = event.clientX;
|
|
3133
3229
|
};
|
|
3134
3230
|
const onUp = (event) => {
|
|
3135
|
-
if (!dragging) return;
|
|
3231
|
+
if (!dragging || event.pointerId !== pointerId) return;
|
|
3136
3232
|
dragging = false;
|
|
3233
|
+
pointerId = void 0;
|
|
3137
3234
|
if (!shouldCollapseOnSidebarDrag(startX, event.clientX)) return;
|
|
3138
3235
|
window.requestAnimationFrame(() => {
|
|
3139
3236
|
window.requestAnimationFrame(() => {
|
|
@@ -3141,11 +3238,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3141
3238
|
});
|
|
3142
3239
|
});
|
|
3143
3240
|
};
|
|
3241
|
+
const onCancel = (event) => {
|
|
3242
|
+
if (event.pointerId !== pointerId) return;
|
|
3243
|
+
dragging = false;
|
|
3244
|
+
pointerId = void 0;
|
|
3245
|
+
};
|
|
3144
3246
|
window.addEventListener("pointerdown", onDown, true);
|
|
3145
3247
|
window.addEventListener("pointerup", onUp, true);
|
|
3248
|
+
window.addEventListener("pointercancel", onCancel, true);
|
|
3146
3249
|
return () => {
|
|
3147
3250
|
window.removeEventListener("pointerdown", onDown, true);
|
|
3148
3251
|
window.removeEventListener("pointerup", onUp, true);
|
|
3252
|
+
window.removeEventListener("pointercancel", onCancel, true);
|
|
3149
3253
|
};
|
|
3150
3254
|
}, [toggleSidebar]);
|
|
3151
3255
|
if (collapsed || width < 80) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
@@ -3607,9 +3711,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3607
3711
|
const root = (0, react.useRef)(null);
|
|
3608
3712
|
const stateRef = (0, react.useRef)("loading");
|
|
3609
3713
|
const requestId = (0, react.useRef)(0);
|
|
3714
|
+
const installingRef = (0, react.useRef)();
|
|
3610
3715
|
stateRef.current = state;
|
|
3611
|
-
(0, react.useEffect)(() =>
|
|
3612
|
-
alive.current =
|
|
3716
|
+
(0, react.useEffect)(() => {
|
|
3717
|
+
alive.current = true;
|
|
3718
|
+
return () => {
|
|
3719
|
+
alive.current = false;
|
|
3720
|
+
};
|
|
3613
3721
|
}, []);
|
|
3614
3722
|
const load = (0, react.useCallback)(async (signal) => {
|
|
3615
3723
|
const currentRequest = ++requestId.current;
|
|
@@ -3639,8 +3747,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3639
3747
|
if (node === null || typeof IntersectionObserver === "undefined") return () => {
|
|
3640
3748
|
controller.abort();
|
|
3641
3749
|
};
|
|
3750
|
+
let initialized = false;
|
|
3751
|
+
let wasVisible = false;
|
|
3642
3752
|
const observer = new IntersectionObserver((entries) => {
|
|
3643
|
-
|
|
3753
|
+
const visible = entries.some((entry) => entry.isIntersecting);
|
|
3754
|
+
if (!initialized) {
|
|
3755
|
+
initialized = true;
|
|
3756
|
+
wasVisible = visible;
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
if (visible && !wasVisible) refresh();
|
|
3760
|
+
wasVisible = visible;
|
|
3644
3761
|
}, { threshold: .2 });
|
|
3645
3762
|
observer.observe(node);
|
|
3646
3763
|
return () => {
|
|
@@ -3649,6 +3766,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3649
3766
|
};
|
|
3650
3767
|
}, [load]);
|
|
3651
3768
|
const install = async (id) => {
|
|
3769
|
+
if (installingRef.current !== void 0) return;
|
|
3770
|
+
installingRef.current = id;
|
|
3652
3771
|
setInstalling(id);
|
|
3653
3772
|
setMessage(void 0);
|
|
3654
3773
|
try {
|
|
@@ -3669,6 +3788,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3669
3788
|
text: error instanceof Error ? error.message : t("about.installFailed")
|
|
3670
3789
|
});
|
|
3671
3790
|
} finally {
|
|
3791
|
+
installingRef.current = void 0;
|
|
3672
3792
|
if (alive.current) setInstalling(void 0);
|
|
3673
3793
|
}
|
|
3674
3794
|
};
|
|
@@ -3736,7 +3856,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
3736
3856
|
(!dependency.installed || dependency.updateAvailable) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3737
3857
|
className: "dcu-about-install",
|
|
3738
3858
|
type: "button",
|
|
3739
|
-
disabled: installing
|
|
3859
|
+
disabled: installing !== void 0,
|
|
3740
3860
|
onClick: () => {
|
|
3741
3861
|
install(dependency.id);
|
|
3742
3862
|
},
|
|
@@ -4190,11 +4310,27 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4190
4310
|
let applying = false;
|
|
4191
4311
|
let frame;
|
|
4192
4312
|
let pending;
|
|
4313
|
+
let frameObserver;
|
|
4314
|
+
const watchFrame = (next) => {
|
|
4315
|
+
if (frame === next) return;
|
|
4316
|
+
frameObserver?.disconnect();
|
|
4317
|
+
frame = next;
|
|
4318
|
+
if (frame === void 0) return;
|
|
4319
|
+
frameObserver = new MutationObserver(schedule);
|
|
4320
|
+
frameObserver.observe(frame, {
|
|
4321
|
+
attributes: true,
|
|
4322
|
+
attributeFilter: [
|
|
4323
|
+
"style",
|
|
4324
|
+
"data-sidebar-collapsed",
|
|
4325
|
+
"data-dragging"
|
|
4326
|
+
]
|
|
4327
|
+
});
|
|
4328
|
+
};
|
|
4193
4329
|
const apply = () => {
|
|
4194
4330
|
if (applying) return;
|
|
4195
4331
|
applying = true;
|
|
4196
4332
|
try {
|
|
4197
|
-
if (frame === void 0 || !frame.isConnected)
|
|
4333
|
+
if (frame === void 0 || !frame.isConnected) watchFrame(findSidebarFrame(document));
|
|
4198
4334
|
if (frame !== void 0) applySlimSidebar(frame);
|
|
4199
4335
|
} finally {
|
|
4200
4336
|
applying = false;
|
|
@@ -4208,19 +4344,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4208
4344
|
});
|
|
4209
4345
|
};
|
|
4210
4346
|
apply();
|
|
4211
|
-
const observer = new MutationObserver(
|
|
4347
|
+
const observer = new MutationObserver(() => {
|
|
4348
|
+
if (frame === void 0 || !frame.isConnected) schedule();
|
|
4349
|
+
});
|
|
4212
4350
|
observer.observe(document.body, {
|
|
4213
|
-
attributes: true,
|
|
4214
|
-
attributeFilter: [
|
|
4215
|
-
"style",
|
|
4216
|
-
"data-sidebar-collapsed",
|
|
4217
|
-
"data-dragging"
|
|
4218
|
-
],
|
|
4219
4351
|
childList: true,
|
|
4220
4352
|
subtree: true
|
|
4221
4353
|
});
|
|
4222
4354
|
return () => {
|
|
4223
4355
|
observer.disconnect();
|
|
4356
|
+
frameObserver?.disconnect();
|
|
4224
4357
|
if (pending !== void 0) window.cancelAnimationFrame(pending);
|
|
4225
4358
|
};
|
|
4226
4359
|
}
|
|
@@ -4234,8 +4367,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4234
4367
|
return document.querySelector("[data-conversation-scroll]");
|
|
4235
4368
|
}
|
|
4236
4369
|
function conversationAnchor(root, key) {
|
|
4237
|
-
|
|
4238
|
-
|
|
4370
|
+
return conversationAnchors(root).get(key) ?? null;
|
|
4371
|
+
}
|
|
4372
|
+
/** 一次扫描生成锚点索引,滚动帧内不得为每个轮次重复遍历 DOM。 */
|
|
4373
|
+
function conversationAnchors(root) {
|
|
4374
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
4375
|
+
for (const anchor of root.querySelectorAll("[data-chat-anchor-key]")) {
|
|
4376
|
+
const key = anchor.dataset.chatAnchorKey;
|
|
4377
|
+
if (key !== void 0 && !anchors.has(key)) anchors.set(key, anchor);
|
|
4378
|
+
}
|
|
4379
|
+
return anchors;
|
|
4239
4380
|
}
|
|
4240
4381
|
//#endregion
|
|
4241
4382
|
//#region src/client/TurnNavigator.tsx
|
|
@@ -4337,27 +4478,44 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4337
4478
|
};
|
|
4338
4479
|
}, []);
|
|
4339
4480
|
(0, react.useEffect)(() => {
|
|
4340
|
-
|
|
4341
|
-
|
|
4481
|
+
if (turns.length === 0) return;
|
|
4482
|
+
let host = null;
|
|
4342
4483
|
let frame = null;
|
|
4343
4484
|
const update = () => {
|
|
4344
4485
|
frame = null;
|
|
4486
|
+
if (host === null) return;
|
|
4345
4487
|
const threshold = host.getBoundingClientRect().top + Math.min(180, host.clientHeight * .35);
|
|
4488
|
+
const anchors = conversationAnchors(host);
|
|
4346
4489
|
let next = turns[0]?.key ?? null;
|
|
4347
4490
|
for (const turn of turns) {
|
|
4348
|
-
const anchor =
|
|
4349
|
-
if (anchor !==
|
|
4491
|
+
const anchor = anchors.get(turn.key);
|
|
4492
|
+
if (anchor !== void 0 && anchor.getBoundingClientRect().top <= threshold) next = turn.key;
|
|
4350
4493
|
}
|
|
4351
4494
|
setCurrent((previous) => previous === next ? previous : next);
|
|
4352
4495
|
};
|
|
4353
4496
|
const schedule = () => {
|
|
4354
4497
|
if (frame === null) frame = window.requestAnimationFrame(update);
|
|
4355
4498
|
};
|
|
4356
|
-
|
|
4499
|
+
const bindHost = () => {
|
|
4500
|
+
const next = conversationScrollRoot();
|
|
4501
|
+
if (next === host) return;
|
|
4502
|
+
host?.removeEventListener("scroll", schedule);
|
|
4503
|
+
host = next;
|
|
4504
|
+
host?.addEventListener("scroll", schedule, { passive: true });
|
|
4505
|
+
schedule();
|
|
4506
|
+
};
|
|
4507
|
+
const observer = new MutationObserver(() => {
|
|
4508
|
+
if (host === null || !host.isConnected) bindHost();
|
|
4509
|
+
});
|
|
4510
|
+
observer.observe(document.body, {
|
|
4511
|
+
childList: true,
|
|
4512
|
+
subtree: true
|
|
4513
|
+
});
|
|
4357
4514
|
window.addEventListener("resize", schedule);
|
|
4358
|
-
|
|
4515
|
+
bindHost();
|
|
4359
4516
|
return () => {
|
|
4360
|
-
|
|
4517
|
+
observer.disconnect();
|
|
4518
|
+
host?.removeEventListener("scroll", schedule);
|
|
4361
4519
|
window.removeEventListener("resize", schedule);
|
|
4362
4520
|
if (frame !== null) window.cancelAnimationFrame(frame);
|
|
4363
4521
|
};
|
|
@@ -4412,7 +4570,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
|
|
|
4412
4570
|
index: index + 1,
|
|
4413
4571
|
summary: turn.summary
|
|
4414
4572
|
}),
|
|
4415
|
-
title: turn.summary,
|
|
4416
4573
|
style: tickStyle,
|
|
4417
4574
|
onFocus: () => {
|
|
4418
4575
|
setHoverAt(index);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -54,6 +54,7 @@ function managedDependency(id) {
|
|
|
54
54
|
//#region src/dependency-manager.ts
|
|
55
55
|
const PROFILE_PENDING_UPDATES_FILE = ".dsh-pending-updates.json";
|
|
56
56
|
const APPLY_PLUGIN_UPDATES_IPC = "apply-plugin-updates";
|
|
57
|
+
const PLUGIN_INSTALL_TIMEOUT_MS = 6e5;
|
|
57
58
|
function profileDirectory() {
|
|
58
59
|
if (process.env.DSH_PROFILE_DIR !== void 0) return process.env.DSH_PROFILE_DIR;
|
|
59
60
|
return resolve(homedir(), ".dsh", "profiles", "web");
|
|
@@ -173,7 +174,7 @@ function applyReleaseExclude(source, packageName, version) {
|
|
|
173
174
|
}
|
|
174
175
|
/** 将用户本次确认的精确版本加入 Profile 的 pnpm 发布时间保护例外。 */
|
|
175
176
|
async function ensureLatestReleaseAllowed(packageName, version) {
|
|
176
|
-
if (
|
|
177
|
+
if (parseSemver(version) === void 0) throw new Error("npm 返回了无法识别的最新版本。");
|
|
177
178
|
const path = resolve(profileDirectory(), "pnpm-workspace.yaml");
|
|
178
179
|
let source;
|
|
179
180
|
try {
|
|
@@ -185,26 +186,40 @@ async function ensureLatestReleaseAllowed(packageName, version) {
|
|
|
185
186
|
const next = applyReleaseExclude(source, packageName, version);
|
|
186
187
|
if (next !== source) await writeFile(path, next, "utf8");
|
|
187
188
|
}
|
|
188
|
-
function
|
|
189
|
-
const match = /^v?(\d+)\.(\d+)\.(\d+)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
function parseSemver(version) {
|
|
190
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
|
191
|
+
if (match === null) return void 0;
|
|
192
|
+
return {
|
|
193
|
+
core: [
|
|
194
|
+
Number(match[1]),
|
|
195
|
+
Number(match[2]),
|
|
196
|
+
Number(match[3])
|
|
197
|
+
],
|
|
198
|
+
prerelease: match[4] === void 0 ? [] : match[4].split(".")
|
|
199
|
+
};
|
|
195
200
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
function comparePrerelease(left, right) {
|
|
202
|
+
if (left.length === 0 || right.length === 0) return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
|
|
203
|
+
const length = Math.max(left.length, right.length);
|
|
204
|
+
for (let index = 0; index < length; index += 1) {
|
|
205
|
+
const a = left[index];
|
|
206
|
+
const b = right[index];
|
|
207
|
+
if (a === void 0 || b === void 0) return a === b ? 0 : a === void 0 ? -1 : 1;
|
|
208
|
+
if (a === b) continue;
|
|
209
|
+
const aNumeric = /^\d+$/.test(a);
|
|
210
|
+
const bNumeric = /^\d+$/.test(b);
|
|
211
|
+
if (aNumeric && bNumeric) return Number(a) > Number(b) ? 1 : -1;
|
|
212
|
+
if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
|
|
213
|
+
return a > b ? 1 : -1;
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
199
216
|
}
|
|
200
217
|
function newerVersion(installed, latest) {
|
|
201
|
-
const current =
|
|
202
|
-
const candidate =
|
|
218
|
+
const current = parseSemver(installed);
|
|
219
|
+
const candidate = parseSemver(latest);
|
|
203
220
|
if (current === void 0 || candidate === void 0) return false;
|
|
204
|
-
if (candidate[
|
|
205
|
-
|
|
206
|
-
if (candidate[2] !== current[2]) return candidate[2] > current[2];
|
|
207
|
-
return prereleaseRank(latest) > prereleaseRank(installed);
|
|
221
|
+
for (let index = 0; index < current.core.length; index += 1) if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index];
|
|
222
|
+
return comparePrerelease(candidate.prerelease, current.prerelease) > 0;
|
|
208
223
|
}
|
|
209
224
|
/** 返回 Web profile 中固定管理插件的实际安装版本与 npm latest 状态。 */
|
|
210
225
|
async function dependencyStatuses() {
|
|
@@ -272,9 +287,31 @@ async function recordPendingUpdate(packageName, version) {
|
|
|
272
287
|
});
|
|
273
288
|
await writeFile(pendingPath, `${JSON.stringify({ packages }, void 0, 2)}\n`, "utf8");
|
|
274
289
|
}
|
|
290
|
+
async function removePendingUpdate(packageName) {
|
|
291
|
+
const pendingPath = resolve(profileDirectory(), PROFILE_PENDING_UPDATES_FILE);
|
|
292
|
+
try {
|
|
293
|
+
const parsed = JSON.parse(await readFile(pendingPath, "utf8"));
|
|
294
|
+
if (!Array.isArray(parsed.packages)) return;
|
|
295
|
+
const packages = parsed.packages.filter((item) => item === null || typeof item !== "object" || item.packageName !== packageName);
|
|
296
|
+
if (packages.length === parsed.packages.length) return;
|
|
297
|
+
if (packages.length === 0) {
|
|
298
|
+
await unlink(pendingPath);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await writeFile(pendingPath, `${JSON.stringify({ packages }, void 0, 2)}\n`, "utf8");
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error.code !== "ENOENT") throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
275
306
|
async function recordDeclaredVersion(packageName, version) {
|
|
276
307
|
const manifestPath = resolve(profileDirectory(), "package.json");
|
|
277
|
-
|
|
308
|
+
let manifest;
|
|
309
|
+
try {
|
|
310
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
311
|
+
} catch (error) {
|
|
312
|
+
if (error.code !== "ENOENT") throw error;
|
|
313
|
+
manifest = {};
|
|
314
|
+
}
|
|
278
315
|
manifest.dependencies = {
|
|
279
316
|
...manifest.dependencies,
|
|
280
317
|
[packageName]: version
|
|
@@ -293,43 +330,87 @@ function pluginCommandError(stderr) {
|
|
|
293
330
|
* 复用启动当前服务的 DSH CLI:它会通过 pnpm 从 npm 安装或更新,并自动维护
|
|
294
331
|
* dsh.profile.bundles,避免浏览器端直接管理 profile 文件。
|
|
295
332
|
*/
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"
|
|
305
|
-
|
|
333
|
+
function pluginExecArgv(args = process.execArgv) {
|
|
334
|
+
return args.filter((arg) => !/^--(?:inspect|inspect-brk|debug|debug-brk)(?:=|$)/.test(arg));
|
|
335
|
+
}
|
|
336
|
+
const activePluginChildren = /* @__PURE__ */ new Set();
|
|
337
|
+
function terminatePluginChild(child) {
|
|
338
|
+
if (child.exitCode !== null || child.killed) return;
|
|
339
|
+
if (process.platform === "win32" && child.pid !== void 0) {
|
|
340
|
+
const killer = spawn("taskkill", [
|
|
341
|
+
"/pid",
|
|
342
|
+
String(child.pid),
|
|
343
|
+
"/T",
|
|
344
|
+
"/F"
|
|
306
345
|
], {
|
|
307
|
-
cwd: process.cwd(),
|
|
308
|
-
env: {
|
|
309
|
-
...process.env,
|
|
310
|
-
CI: "true"
|
|
311
|
-
},
|
|
312
346
|
windowsHide: true,
|
|
313
|
-
stdio:
|
|
314
|
-
"ignore",
|
|
315
|
-
"pipe",
|
|
316
|
-
"pipe"
|
|
317
|
-
]
|
|
347
|
+
stdio: "ignore"
|
|
318
348
|
});
|
|
349
|
+
killer.once("error", () => {
|
|
350
|
+
child.kill("SIGTERM");
|
|
351
|
+
});
|
|
352
|
+
killer.unref();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
child.kill("SIGTERM");
|
|
356
|
+
}
|
|
357
|
+
/** 插件停用时终止仍在运行的安装进程,避免热更新后遗留 pnpm。 */
|
|
358
|
+
function disposeDependencyInstaller() {
|
|
359
|
+
for (const child of activePluginChildren) terminatePluginChild(child);
|
|
360
|
+
}
|
|
361
|
+
function monitorPluginChild(child, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
|
|
362
|
+
return new Promise((resolvePromise, reject) => {
|
|
363
|
+
activePluginChildren.add(child);
|
|
319
364
|
let output = "";
|
|
365
|
+
let settled = false;
|
|
320
366
|
const collect = (chunk) => {
|
|
321
|
-
output
|
|
367
|
+
output = `${output}${String(chunk)}`.slice(-65536);
|
|
368
|
+
};
|
|
369
|
+
const finish = (error) => {
|
|
370
|
+
if (settled) return;
|
|
371
|
+
settled = true;
|
|
372
|
+
clearTimeout(timeout);
|
|
373
|
+
activePluginChildren.delete(child);
|
|
374
|
+
error === void 0 ? resolvePromise() : reject(error);
|
|
322
375
|
};
|
|
323
376
|
child.stdout?.on("data", collect);
|
|
324
377
|
child.stderr?.on("data", collect);
|
|
325
378
|
child.once("error", () => {
|
|
326
|
-
|
|
379
|
+
finish(/* @__PURE__ */ new Error("无法启动 DSH 插件安装命令。请确认 Node.js 与 pnpm 可用后重试。"));
|
|
327
380
|
});
|
|
328
381
|
child.once("exit", (code) => {
|
|
329
|
-
code === 0 ?
|
|
382
|
+
finish(code === 0 ? void 0 : pluginCommandError(output));
|
|
330
383
|
});
|
|
384
|
+
const timeout = setTimeout(() => {
|
|
385
|
+
terminatePluginChild(child);
|
|
386
|
+
finish(/* @__PURE__ */ new Error("插件安装超时,已终止安装进程。请检查网络后重试。"));
|
|
387
|
+
}, timeoutMs);
|
|
388
|
+
timeout.unref?.();
|
|
331
389
|
});
|
|
332
390
|
}
|
|
391
|
+
function runDshPlugin(args, timeoutMs = PLUGIN_INSTALL_TIMEOUT_MS) {
|
|
392
|
+
const entry = resolveDshCliEntry();
|
|
393
|
+
return monitorPluginChild(spawn(process.execPath, [
|
|
394
|
+
...pluginExecArgv(),
|
|
395
|
+
entry,
|
|
396
|
+
"plugin",
|
|
397
|
+
"--profile",
|
|
398
|
+
"web",
|
|
399
|
+
...args
|
|
400
|
+
], {
|
|
401
|
+
cwd: process.cwd(),
|
|
402
|
+
env: {
|
|
403
|
+
...process.env,
|
|
404
|
+
CI: "true"
|
|
405
|
+
},
|
|
406
|
+
windowsHide: true,
|
|
407
|
+
stdio: [
|
|
408
|
+
"ignore",
|
|
409
|
+
"pipe",
|
|
410
|
+
"pipe"
|
|
411
|
+
]
|
|
412
|
+
}), timeoutMs);
|
|
413
|
+
}
|
|
333
414
|
/** 并发安装互斥:pnpm 锁文件竞争会触发 EPERM/EBUSY,同一时间只允许一个安装进程。 */
|
|
334
415
|
let installing = false;
|
|
335
416
|
/** 仅允许安装固定依赖,避免把浏览器输入转成任意命令。 */
|
|
@@ -352,10 +433,10 @@ async function installDependencyLocked(id, requestHotUpdate) {
|
|
|
352
433
|
const targetVersion = target === dependency.packageName ? latestVersion : await npmLatestVersion(target);
|
|
353
434
|
if (targetVersion === void 0) throw new Error("无法获取 npm 最新版本,请检查网络或 npm registry 后重试。");
|
|
354
435
|
const remove = pluginsToRemoveBeforeInstall(declared, target);
|
|
355
|
-
if (remove.length > 0) await runDshPlugin(["remove", ...remove]);
|
|
356
436
|
await ensureLatestReleaseAllowed(target, targetVersion);
|
|
357
437
|
if (!isOfficialRuntimePackage(target)) await recordDeclaredVersion(target, targetVersion);
|
|
358
438
|
await recordPendingUpdate(target, targetVersion);
|
|
439
|
+
if (remove.length > 0) await runDshPlugin(["remove", ...remove]);
|
|
359
440
|
if (requestHotUpdate()) return dependencyStatuses();
|
|
360
441
|
try {
|
|
361
442
|
await runDshPlugin([
|
|
@@ -368,6 +449,7 @@ async function installDependencyLocked(id, requestHotUpdate) {
|
|
|
368
449
|
throw error;
|
|
369
450
|
}
|
|
370
451
|
if (await installedPackageVersion(target) !== targetVersion) return dependencyStatuses();
|
|
452
|
+
await removePendingUpdate(target);
|
|
371
453
|
return dependencyStatuses();
|
|
372
454
|
}
|
|
373
455
|
//#endregion
|
|
@@ -419,7 +501,7 @@ function crossSiteRequest(request) {
|
|
|
419
501
|
/** 把安装错误收成可给浏览器看的文案:我们自己的中文说明保留,带本地路径的底层错误脱敏。 */
|
|
420
502
|
function publicDependencyError(error) {
|
|
421
503
|
const message = error instanceof Error ? error.message : "依赖管理暂不可用。";
|
|
422
|
-
if (/[A-Za-z]:[\\/]|\/(?:home|Users|var|tmp)\//.test(message)) return "依赖管理暂不可用,请查看服务端日志。";
|
|
504
|
+
if (/[A-Za-z]:[\\/]|\/(?:home|root|Users|var|tmp)\//.test(message)) return "依赖管理暂不可用,请查看服务端日志。";
|
|
423
505
|
return message;
|
|
424
506
|
}
|
|
425
507
|
const inject = [
|
|
@@ -528,6 +610,7 @@ function apply(ctx) {
|
|
|
528
610
|
}
|
|
529
611
|
});
|
|
530
612
|
return () => {
|
|
613
|
+
disposeDependencyInstaller();
|
|
531
614
|
disposeConnectors();
|
|
532
615
|
disposeDependencies();
|
|
533
616
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-codex-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.74",
|
|
4
4
|
"description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsc --noEmit && tsdown",
|
|
56
|
-
"
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "pnpm run typecheck && vitest run tests/client-runtime.integration.spec.ts && tsdown && node tests/run-assertions.mjs"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
60
|
"@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
|