@michengai/dsh-codex-ui 0.2.71 → 0.2.73

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.
Files changed (3) hide show
  1. package/dist/client.js +211 -264
  2. package/dist/index.mjs +127 -44
  3. package/package.json +7 -6
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
- trigger.click();
29
- window.requestAnimationFrame(() => {
30
- window.requestAnimationFrame(() => {
31
- const target = pickSettingsSectionButton([...document.querySelectorAll("[role=\"dialog\"] nav button")], labels);
32
- if (target !== void 0) {
33
- target.click();
34
- return;
35
- }
36
- console.warn(`[michengai-codex-ui] 未找到设置分区:${labels.join(" / ")}`);
37
- onMissing?.();
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,18 +287,25 @@ window.__ModuleLoader__.load({
251
287
  });
252
288
  }
253
289
  /** 读取 IM 频道分组;失败时交给界面显示空态或错误。 */
254
- async function loadChannelGroups() {
255
- const response = await fetch(CHANNELS_ENDPOINT, { cache: "no-store" });
256
- const payload = await response.json();
257
- if (!response.ok) throw new Error("无法读取频道会话");
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
  }
262
306
  //#endregion
263
307
  //#region src/client/session-manager.ts
264
308
  const SESSION_PINS_STORAGE_KEY = "dsh.session-pins.v1";
265
- const SESSION_SECTION_PINS_STORAGE_KEY = "dsh-codex-ui.pinned-section-sessions.v1";
266
309
  const SESSION_UNREAD_STORAGE_KEY = "dsh.session-unread.v1";
267
310
  function normalizeSessionIds(ids) {
268
311
  return [...new Set(ids.filter((id) => id.trim() !== ""))];
@@ -295,15 +338,6 @@ window.__ModuleLoader__.load({
295
338
  url.searchParams.set("session", sessionId);
296
339
  return url.toString();
297
340
  }
298
- /** 把会话插入置顶列表的指定位置;省略锚点时追加到末尾。 */
299
- function insertSessionId(ids, id, beforeId) {
300
- const next = ids.filter((item) => item !== id);
301
- if (beforeId === void 0) return [...next, id];
302
- if (beforeId === id) return ids.includes(id) ? [...ids] : [...next, id];
303
- const index = next.indexOf(beforeId);
304
- next.splice(index < 0 ? next.length : index, 0, id);
305
- return next;
306
- }
307
341
  //#endregion
308
342
  //#region src/client/session-tree.tsx
309
343
  function PinIcon() {
@@ -689,7 +723,6 @@ window.__ModuleLoader__.load({
689
723
  card.append(sub);
690
724
  }
691
725
  card.dataset.dcuUserText = text;
692
- card.title = text;
693
726
  }
694
727
  }
695
728
  function ensureUserBubbleStyle(doc) {
@@ -800,19 +833,25 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
800
833
  slider.style.transform = `translateX(${Math.max(0, tabBox.left - listBox.left)}px)`;
801
834
  }
802
835
  function watchTabSelection(tabs) {
803
- if (tabs.dataset.dcuTabWatch === "") return;
804
836
  tabs.dataset.dcuTabWatch = "";
805
837
  const sync = () => {
806
838
  syncTabSlider(tabs);
807
839
  };
808
- new MutationObserver(sync).observe(tabs, {
840
+ const onClick = () => {
841
+ window.requestAnimationFrame(sync);
842
+ };
843
+ const observer = new MutationObserver(sync);
844
+ observer.observe(tabs, {
809
845
  attributes: true,
810
846
  subtree: true,
811
847
  attributeFilter: ["aria-selected", "data-state"]
812
848
  });
813
- tabs.addEventListener("click", () => {
814
- window.requestAnimationFrame(sync);
815
- });
849
+ tabs.addEventListener("click", onClick);
850
+ return () => {
851
+ observer.disconnect();
852
+ tabs.removeEventListener("click", onClick);
853
+ delete tabs.dataset.dcuTabWatch;
854
+ };
816
855
  }
817
856
  function ensureStyle(doc) {
818
857
  if (doc.getElementById("dcu-conversation-header-style") !== null) return;
@@ -828,6 +867,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
828
867
  ensureUserBubbleStyle(doc);
829
868
  let applying = false;
830
869
  let frame;
870
+ let watchedTabs;
871
+ let stopWatchingTabs;
831
872
  const run = () => {
832
873
  frame = void 0;
833
874
  if (applying) return;
@@ -835,7 +876,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
835
876
  try {
836
877
  placeConversationTabs(doc);
837
878
  const tabs = findConversationTablist(doc);
838
- if (tabs !== void 0) watchTabSelection(tabs);
879
+ if (tabs !== watchedTabs) {
880
+ stopWatchingTabs?.();
881
+ watchedTabs = tabs;
882
+ stopWatchingTabs = tabs === void 0 ? void 0 : watchTabSelection(tabs);
883
+ }
839
884
  syncTabSlider(doc);
840
885
  decorateConversationTitle(doc);
841
886
  decorateUserBubbles(doc);
@@ -855,6 +900,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
855
900
  });
856
901
  return () => {
857
902
  observer.disconnect();
903
+ stopWatchingTabs?.();
858
904
  if (frame !== void 0) window.cancelAnimationFrame(frame);
859
905
  };
860
906
  }
@@ -913,6 +959,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
913
959
  window.clearTimeout(showTipTimer.current);
914
960
  showTipTimer.current = void 0;
915
961
  }
962
+ if (hideTipTimer.current !== void 0) window.clearTimeout(hideTipTimer.current);
916
963
  hideTipTimer.current = window.setTimeout(() => {
917
964
  setHoverTip(void 0);
918
965
  }, 120);
@@ -1121,22 +1168,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1121
1168
  return item === void 0 ? [] : [item];
1122
1169
  });
1123
1170
  }
1124
- /** 置顶区的会话按拖入顺序展示;不因父项目已置顶而隐藏。 */
1125
- function standalonePinnedSessionIds(pinnedSessionIds) {
1126
- return [...pinnedSessionIds];
1127
- }
1128
- /** 置顶区已有会话时,不再同时展示任何项目文件夹,避免旧置顶项目叠成两条。 */
1129
- function workspaceIdsHiddenByPinnedSessions(workspaces, pinnedSessionIds) {
1130
- if (pinnedSessionIds.length === 0) return [];
1131
- return workspaces.map((workspace) => String(workspace.workspaceId));
1132
- }
1133
1171
  const SESSION_DRAG_TYPE = "application/x-dcu-session";
1134
1172
  const WORKSPACE_DRAG_TYPE = "application/x-dcu-workspace";
1135
1173
  const SESSION_DRAG_PREFIX = "dcu-session:";
1136
1174
  const WORKSPACE_DRAG_PREFIX = "dcu-workspace:";
1137
- function dragTypes(data) {
1138
- return data === void 0 ? [] : Array.from(data.types);
1139
- }
1140
1175
  function textPayload(data) {
1141
1176
  try {
1142
1177
  return data?.getData("text/plain") ?? "";
@@ -1144,11 +1179,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1144
1179
  return "";
1145
1180
  }
1146
1181
  }
1147
- /** 拖过置顶区时用来决定是否 preventDefault;只看 types,不读数据。 */
1148
- function isSidebarItemDrag(data) {
1149
- const types = dragTypes(data);
1150
- return types.includes("application/x-dcu-session") || types.includes("application/x-dcu-workspace") || types.includes("text/plain");
1151
- }
1152
1182
  /** 写入会话拖拽载荷;drop 时以这个为准,不依赖尚未刷新的 React 状态。 */
1153
1183
  function writeSessionDrag(data, sessionId, title) {
1154
1184
  data.effectAllowed = "move";
@@ -1190,11 +1220,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1190
1220
  .dcu-wb-section-head{position:relative;display:flex;align-items:center;min-height:24px;padding:0 4px;border-radius:6px}.dcu-wb-section-head:hover .dcu-wb-actions{display:flex}.dcu-wb-section-label{display:flex;align-items:center;gap:4px;flex:1;min-width:0;min-height:24px;border:0;padding:2px 4px;background:transparent;color:var(--dcu-sidebar-tertiary);font:13px/20px var(--dcu-font,inherit);font-weight:400;letter-spacing:.02em;text-align:left;cursor:pointer}.dcu-wb-section-caret{display:grid;place-items:center;flex:none;width:12px;height:12px;color:currentColor;opacity:0;transform:rotate(0deg);transform-origin:50% 50%;transition:opacity 140ms ease,transform 180ms ease}.dcu-wb-section-caret svg{display:block}.dcu-wb-section-head:hover .dcu-wb-section-caret,.dcu-wb-section-label:focus-visible .dcu-wb-section-caret{opacity:.78}.dcu-wb-section-label[aria-expanded=true] .dcu-wb-section-caret{transform:rotate(90deg)}.dcu-wb-section-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows 220ms ease}.dcu-wb-section-body[data-open=true]{grid-template-rows:1fr}.dcu-wb-section-body>div{overflow:hidden;min-height:0}@media (prefers-reduced-motion:reduce){.dcu-wb-section-caret,.dcu-wb-section-body{transition:none}}
1191
1221
  .dcu-wb-project{position:relative}
1192
1222
  .dcu-wb-project-head,.dcu-wb-session{position:relative;display:flex;align-items:center;gap:6px;width:100%;border-radius:8px;padding:0 8px;color:var(--dcu-sidebar-primary);cursor:pointer}
1193
- .dcu-wb-project-head{height:34px;background:transparent;font:inherit;text-align:left}
1194
- .dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project-head+.dcu-wb-session,.dcu-wb-project-head+.dcu-wb-nochat{margin-top:4px}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
1223
+ .dcu-wb-project-head{height:28px;background:transparent;font:inherit;text-align:left}
1224
+ .dcu-wb-project-head:hover,.dcu-wb-project-head.dcu-wb-menu-open,.dcu-wb-session:hover,.dcu-wb-session.dcu-wb-selected,.dcu-wb-session.dcu-wb-menu-open{background:var(--dcu-sidebar-hover)}.dcu-wb-project-head+.dcu-wb-session,.dcu-wb-project-head+.dcu-wb-nochat{margin-top:0}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
1195
1225
  .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
1196
1226
  .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
1197
- .dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;left:8px;right:8px;top:-4px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,var(--dsw-alias-state-business-primary) 3.25px,transparent 3.45px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before{top:0}.dcu-wb-project-head.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:-4px}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:-120px;left:-240px;z-index:10040;max-width:220px;height:32px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);font:13px/32px var(--dcu-font,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;opacity:0}
1227
+ .dcu-wb-section,.dcu-wb-section:focus,.dcu-wb-section:focus-visible,.dcu-wb-project-head:focus,.dcu-wb-session:focus{outline:0}.dcu-wb-pin-end,.dcu-wb-pin-start{position:relative;height:8px}.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before,.dcu-wb-pin-end.dcu-wb-drop::before,.dcu-wb-pin-start.dcu-wb-drop::before{content:"";position:absolute;left:8px;right:8px;top:-4px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,var(--dsw-alias-state-business-primary) 3.25px,transparent 3.45px),linear-gradient(var(--dsw-alias-state-business-primary),var(--dsw-alias-state-business-primary)) 10px 50%/calc(100% - 10px) 2px no-repeat}.dcu-wb-pin-start.dcu-wb-drop::before,.dcu-wb-project-head.dcu-wb-drop::before,.dcu-wb-session.dcu-wb-drop::before{top:0}.dcu-wb-project-head.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:0}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:-120px;left:-240px;z-index:10040;max-width:220px;height:32px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);color:var(--dcu-sidebar-primary);font:13px/32px var(--dcu-font,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;opacity:0}
1198
1228
  .dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}.dcu-wb-brand{display:block;width:16px;height:16px}
1199
1229
  .dcu-wb-project-current .dcu-wb-folder{color:var(--dsw-alias-state-business-primary)}
1200
1230
  .dcu-wb-project-title,.dcu-wb-session-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px}
@@ -1222,7 +1252,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1222
1252
  .dcu-wb-rename-input:focus{border-color:var(--dsw-alias-button-info-fill)}
1223
1253
  `;
1224
1254
  const runningStyles = `.dcu-wb-running{flex:none;width:12px;height:12px;border:2px solid color-mix(in srgb,var(--dcu-sidebar-secondary) 25%,transparent);border-top-color:var(--dcu-sidebar-secondary);border-radius:50%;animation:dcu-wb-spin .8s linear infinite}@keyframes dcu-wb-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.dcu-wb-running{animation:none}}`;
1225
- const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:4px 8px 6px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
1255
+ const typographyStyles = `.dcu-wb{font:14px/20px var(--dcu-font,var(--dsw-font-family))}.dcu-wb-section-label{color:var(--dcu-sidebar-tertiary);font-size:13px;font-weight:400;letter-spacing:.02em}.dcu-wb-project-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-primary)}.dcu-wb-session-title{font-size:14px;line-height:20px;font-weight:400;color:var(--dcu-sidebar-secondary)}.dcu-wb-session.dcu-wb-selected .dcu-wb-session-title,.dcu-wb-session:hover .dcu-wb-session-title{color:var(--dcu-sidebar-primary)}.dcu-wb-empty{color:var(--dcu-sidebar-tertiary);font-size:13px;line-height:18px}.dcu-wb-nochat{padding:0 8px 4px 28px;color:var(--dcu-sidebar-tertiary);font-size:14px;line-height:20px}`;
1226
1256
  const WORKSPACE_TREE_STYLE = stylesheet$4 + runningStyles + typographyStyles;
1227
1257
  function storage$1() {
1228
1258
  return typeof window === "undefined" ? void 0 : window.localStorage;
@@ -1245,7 +1275,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1245
1275
  const [expanded, setExpanded] = (0, react.useState)({});
1246
1276
  const [pinnedWorkspaceIds, setPinnedWorkspaceIds] = (0, react.useState)(() => readPinnedWorkspaceIds(storage$1()));
1247
1277
  const [pinnedSessionIds, setPinnedSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY));
1248
- const [sectionSessionIds, setSectionSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_SECTION_PINS_STORAGE_KEY));
1249
1278
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY));
1250
1279
  const [menu, setMenu] = (0, react.useState)();
1251
1280
  const [renameTarget, setRenameTarget] = (0, react.useState)();
@@ -1257,6 +1286,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1257
1286
  };
1258
1287
  const [renameDraft, setRenameDraft] = (0, react.useState)("");
1259
1288
  const [busy, setBusy] = (0, react.useState)();
1289
+ const busyRef = (0, react.useRef)();
1260
1290
  const [error, setError] = (0, react.useState)();
1261
1291
  const [workspaceDragId, setWorkspaceDragId] = (0, react.useState)();
1262
1292
  const [workspaceDropId, setWorkspaceDropId] = (0, react.useState)();
@@ -1274,13 +1304,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1274
1304
  (0, react.useEffect)(() => {
1275
1305
  writeSessionIds(storage$1(), SESSION_PINS_STORAGE_KEY, pinnedSessionIds);
1276
1306
  }, [pinnedSessionIds]);
1277
- (0, react.useEffect)(() => {
1278
- writeSessionIds(storage$1(), SESSION_SECTION_PINS_STORAGE_KEY, sectionSessionIds);
1279
- }, [sectionSessionIds]);
1280
- (0, react.useEffect)(() => {
1281
- if (sectionSessionIds.length === 0) return;
1282
- setPinnedWorkspaceIds((ids) => ids.length === 0 ? ids : []);
1283
- }, [sectionSessionIds]);
1284
1307
  (0, react.useEffect)(() => {
1285
1308
  writeSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY, unreadSessionIds);
1286
1309
  }, [unreadSessionIds]);
@@ -1367,15 +1390,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1367
1390
  sessions.current,
1368
1391
  menu
1369
1392
  ]);
1370
- const pinSectionSessions = standalonePinnedSessionIds(sectionSessionIds);
1371
- const hiddenPinnedWorkspaceIds = new Set(workspaceIdsHiddenByPinnedSessions(groups.items, pinSectionSessions));
1372
- const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id)) && !hiddenPinnedWorkspaceIds.has(String(id));
1373
- const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId)).filter((workspace) => !hiddenPinnedWorkspaceIds.has(String(workspace.workspaceId)));
1393
+ const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
1394
+ const pinnedGroups = orderByIds(groups.items, pinnedWorkspaceIds, (workspace) => String(workspace.workspaceId));
1374
1395
  const regularGroups = groups.items.filter((workspace) => !projectPinned(workspace.workspaceId));
1375
- const pinDragActive = workspaceDragId !== void 0 || sessionDrag !== void 0;
1396
+ const pinDragActive = workspaceDragId !== void 0;
1376
1397
  const assignedIds = workspaces.items.flatMap((workspace) => workspace.sessionIds.map((id) => String(id)));
1377
- const recentIds = ungroupedSessionIds(sessions.ids ?? Object.keys(sessions.byId), sessions.byId, assignedIds, workspaces.archivedSessionIds).filter((id) => !pinSectionSessions.includes(id)).sort((left, right) => (sessions.byId[right]?.updatedAt ?? 0) - (sessions.byId[left]?.updatedAt ?? 0));
1398
+ 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));
1378
1399
  const run = async (key, action) => {
1400
+ if (busyRef.current !== void 0) return;
1401
+ busyRef.current = key;
1379
1402
  setBusy(key);
1380
1403
  setError(void 0);
1381
1404
  try {
@@ -1384,6 +1407,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1384
1407
  } catch (reason) {
1385
1408
  setError(reason instanceof Error ? reason.message : String(reason));
1386
1409
  } finally {
1410
+ busyRef.current = void 0;
1387
1411
  setBusy(void 0);
1388
1412
  }
1389
1413
  };
@@ -1412,7 +1436,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1412
1436
  if (target.kind === "session") {
1413
1437
  await deleteSession(target.id);
1414
1438
  setPinnedSessionIds((ids) => ids.filter((id) => id !== target.id));
1415
- setSectionSessionIds((ids) => ids.filter((id) => id !== target.id));
1416
1439
  setUnreadSessionIds((ids) => ids.filter((id) => id !== target.id));
1417
1440
  } else {
1418
1441
  await deleteWorkspace(target.id);
@@ -1481,14 +1504,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1481
1504
  const pinWorkspaceAt = (id, beforeId) => {
1482
1505
  setPinnedWorkspaceIds((ids) => insertPinnedWorkspace(ids, id, beforeId));
1483
1506
  };
1484
- const pinSessionAt = (id, beforeId) => {
1485
- setSectionSessionIds((ids) => insertSessionId(ids, id, beforeId));
1486
- setPinnedWorkspaceIds((ids) => ids.length === 0 ? ids : []);
1487
- };
1488
1507
  const renderGroup = (workspace, zone) => {
1489
1508
  const expandKey = zone === "pinned" ? `pin:${workspace.workspaceId}` : String(workspace.workspaceId);
1490
1509
  const isExpanded = expanded[expandKey] ?? true;
1491
- const shownIds = workspace.visibleIds.filter((id) => !pinSectionSessions.includes(id));
1510
+ const shownIds = workspace.visibleIds;
1492
1511
  const menuOpen = menu?.type === "workspace" && menu.id === workspace.workspaceId;
1493
1512
  const menuAt = menuOpen && menu.x !== void 0 && menu.y !== void 0 ? {
1494
1513
  x: menu.x,
@@ -1498,7 +1517,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1498
1517
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1499
1518
  className: `dcu-wb-project${isCurrentWorkspace ? " dcu-wb-project-current" : ""}`,
1500
1519
  onDragOver: (event) => {
1501
- if (workspaceDragId === void 0 && !isSidebarItemDrag(event.dataTransfer) && !(zone === "pinned" && sessionDrag !== void 0)) return;
1520
+ if (workspaceDragId === void 0) return;
1502
1521
  event.preventDefault();
1503
1522
  event.stopPropagation();
1504
1523
  event.dataTransfer.dropEffect = "move";
@@ -1519,17 +1538,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1519
1538
  onDrop: (event) => {
1520
1539
  event.preventDefault();
1521
1540
  event.stopPropagation();
1522
- const droppedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
1523
- if (zone === "pinned" && droppedSession !== void 0) {
1524
- setSessionDrag(void 0);
1525
- setSessionDropId(void 0);
1526
- setWorkspaceDragId(void 0);
1527
- setWorkspaceDropId(void 0);
1528
- setWorkspaceDropAfter(false);
1529
- setPinSlot(void 0);
1530
- pinSessionAt(droppedSession);
1531
- return;
1532
- }
1533
1541
  const dragged = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
1534
1542
  const after = event.clientY > event.currentTarget.getBoundingClientRect().top + event.currentTarget.getBoundingClientRect().height / 2;
1535
1543
  setWorkspaceDragId(void 0);
@@ -1918,27 +1926,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1918
1926
  className: "dcu-wb-section",
1919
1927
  "aria-label": t("workspace.pinned"),
1920
1928
  onDragOver: (event) => {
1921
- if (!pinDragActive && !isSidebarItemDrag(event.dataTransfer)) return;
1929
+ if (!pinDragActive) return;
1922
1930
  event.preventDefault();
1923
1931
  event.dataTransfer.dropEffect = "move";
1924
1932
  if (event.target === event.currentTarget || event.target instanceof Element && event.target.closest(".dcu-wb-section-head") !== null) setPinSlot("header");
1925
1933
  },
1926
1934
  onDrop: (event) => {
1927
1935
  event.preventDefault();
1928
- const draggedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
1929
1936
  const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
1930
1937
  const slot = pinSlot;
1931
1938
  setWorkspaceDragId(void 0);
1932
1939
  setWorkspaceDropId(void 0);
1933
1940
  setWorkspaceDropAfter(false);
1934
- setSessionDrag(void 0);
1935
- setSessionDropId(void 0);
1936
1941
  setPinSlot(void 0);
1937
- if (draggedSession !== void 0) {
1938
- pinSessionAt(draggedSession, slot === "header" ? pinSectionSessions[0] : slot === "end" || slot === void 0 || !pinSectionSessions.includes(slot) ? void 0 : slot);
1939
- return;
1940
- }
1941
- if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace, slot === "header" ? pinnedGroups[0] === void 0 ? void 0 : String(pinnedGroups[0].workspaceId) : slot === "end" || slot === void 0 || pinSectionSessions.includes(slot) ? void 0 : slot);
1942
+ if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace, slot === "header" ? pinnedGroups[0] === void 0 ? void 0 : String(pinnedGroups[0].workspaceId) : slot === "end" || slot === void 0 ? void 0 : slot);
1942
1943
  },
1943
1944
  onDragLeave: (event) => {
1944
1945
  if (event.currentTarget.contains(event.relatedTarget)) return;
@@ -1978,7 +1979,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1978
1979
  (pinDragActive || pinSlot === "header") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1979
1980
  className: `dcu-wb-pin-start${pinSlot === "header" ? " dcu-wb-drop" : ""}`,
1980
1981
  onDragOver: (event) => {
1981
- if (!pinDragActive && !isSidebarItemDrag(event.dataTransfer)) return;
1982
+ if (!pinDragActive) return;
1982
1983
  event.preventDefault();
1983
1984
  event.stopPropagation();
1984
1985
  event.dataTransfer.dropEffect = "move";
@@ -1987,149 +1988,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
1987
1988
  setWorkspaceDropAfter(false);
1988
1989
  }
1989
1990
  }),
1990
- pinSectionSessions.length === 0 && pinnedGroups.length === 0 && pinSlot !== "header" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1991
+ pinnedGroups.length === 0 && pinSlot !== "header" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1991
1992
  className: "dcu-wb-empty",
1992
1993
  children: t("workspace.pinnedEmpty")
1993
1994
  }),
1994
- pinSectionSessions.map((id) => {
1995
- const session = sessions.byId[id];
1996
- if (session === void 0) return null;
1997
- const title = session.displayTitle;
1998
- const workspace = groups.items.find((item) => item.visibleIds.includes(id));
1999
- const path = session.cwd ?? workspace?.path;
2000
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
2001
- id,
2002
- title,
2003
- flat: true,
2004
- selected: sessions.current === id,
2005
- menuOpen: menu?.type === "session" && menu.id === id,
2006
- pinned: true,
2007
- unread: unreadSessionIds.includes(id),
2008
- running: session.running === true,
2009
- t,
2010
- menuItems: sessionMenu(id, title, path),
2011
- menuPoint: menu?.type === "session" && menu.id === id && menu.x !== void 0 && menu.y !== void 0 ? {
2012
- x: menu.x,
2013
- y: menu.y
2014
- } : void 0,
2015
- draggable: true,
2016
- dropActive: sessionDropId === id,
2017
- onDragStart: (event) => {
2018
- event.stopPropagation();
2019
- setWorkspaceDragId(void 0);
2020
- writeSessionDrag(event.dataTransfer, id, title);
2021
- const preview = document.createElement("div");
2022
- preview.className = "dcu-wb-drag-ghost";
2023
- preview.textContent = title;
2024
- document.body.appendChild(preview);
2025
- event.dataTransfer.setDragImage(preview, 16, 18);
2026
- window.requestAnimationFrame(() => {
2027
- preview.remove();
2028
- });
2029
- setSessionDrag({
2030
- sessionId: id,
2031
- workspaceId: workspace?.workspaceId ?? ""
2032
- });
2033
- },
2034
- onDragEnd: () => {
2035
- setSessionDrag(void 0);
2036
- setSessionDropId(void 0);
2037
- setPinSlot(void 0);
2038
- },
2039
- onDragOver: (event) => {
2040
- if (sessionDrag === void 0) return;
2041
- event.preventDefault();
2042
- event.stopPropagation();
2043
- setSessionDropId(id);
2044
- setPinSlot(id);
2045
- },
2046
- onDrop: (event) => {
2047
- event.preventDefault();
2048
- event.stopPropagation();
2049
- const draggedSession = readSessionDrag(event.dataTransfer, sessionDrag?.sessionId);
2050
- const after = event.clientY > event.currentTarget.getBoundingClientRect().top + event.currentTarget.getBoundingClientRect().height / 2;
2051
- setSessionDrag(void 0);
2052
- setSessionDropId(void 0);
2053
- setPinSlot(void 0);
2054
- if (draggedSession === void 0) return;
2055
- pinSessionAt(draggedSession, dropBeforeId(pinSectionSessions, id, after));
2056
- },
2057
- onOpen: () => {
2058
- setUnreadSessionIds((ids) => ids.filter((item) => item !== id));
2059
- openSession(id);
2060
- },
2061
- onMenuChange: (open) => {
2062
- setMenu(open ? {
2063
- id,
2064
- type: "session"
2065
- } : void 0);
2066
- },
2067
- onPin: () => {
2068
- setSectionSessionIds((ids) => ids.filter((item) => item !== id));
2069
- },
2070
- onArchive: () => {
2071
- run("archive", () => archiveSession(id));
2072
- },
2073
- onHover: (event) => {
2074
- const box = hoverCardAnchor(event.currentTarget.getBoundingClientRect());
2075
- showTip({
2076
- kind: "session",
2077
- id,
2078
- title,
2079
- project: workspace?.title,
2080
- path,
2081
- time: formatHoverTime(session.updatedAt),
2082
- left: box.left,
2083
- top: box.top
2084
- });
2085
- },
2086
- onLeave: hideTip,
2087
- onContextMenu: (event) => {
2088
- event.preventDefault();
2089
- event.stopPropagation();
2090
- dismissTip();
2091
- setMenu({
2092
- id,
2093
- type: "session",
2094
- x: event.clientX,
2095
- y: event.clientY
2096
- });
2097
- },
2098
- onSelectAction: (action) => {
2099
- if (busy !== void 0) return;
2100
- if (action === "rename") beginRename("session", id, title);
2101
- if (action === "pin") {
2102
- setSectionSessionIds((ids) => ids.filter((item) => item !== id));
2103
- setMenu(void 0);
2104
- }
2105
- if (action === "unread") {
2106
- setUnreadSessionIds((ids) => toggleSessionId(ids, id));
2107
- setMenu(void 0);
2108
- }
2109
- if (action === "archive") run("archive", () => archiveSession(id));
2110
- if (action === "delete") {
2111
- setDeleteTarget({
2112
- id,
2113
- kind: "session",
2114
- title
2115
- });
2116
- setError(void 0);
2117
- setMenu(void 0);
2118
- }
2119
- if (action === "fork") run("fork", () => forkSession(id));
2120
- if (action === "openPath" && path !== void 0) run("open-path", () => openPath(path));
2121
- if (action === "copyPath") copy(path);
2122
- if (action === "copyTitle") copy(title);
2123
- if (action === "copyId") copy(id);
2124
- if (action === "copyLink") copy(sessionDeepLink(browserBase(), id));
2125
- }
2126
- }, id);
2127
- }),
2128
1995
  pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
2129
1996
  pinDragActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2130
1997
  className: `dcu-wb-pin-end${pinSlot === "end" ? " dcu-wb-drop" : ""}`,
2131
1998
  onDragOver: (event) => {
2132
- if (!pinDragActive && !isSidebarItemDrag(event.dataTransfer)) return;
1999
+ if (!pinDragActive) return;
2133
2000
  event.preventDefault();
2134
2001
  event.stopPropagation();
2135
2002
  event.dataTransfer.dropEffect = "move";
@@ -2524,7 +2391,10 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2524
2391
  function useBusyAction(onSuccess) {
2525
2392
  const [busy, setBusy] = (0, react.useState)();
2526
2393
  const [error, setError] = (0, react.useState)();
2394
+ const busyRef = (0, react.useRef)();
2527
2395
  const run = async (key, action) => {
2396
+ if (busyRef.current !== void 0) return;
2397
+ busyRef.current = key;
2528
2398
  setBusy(key);
2529
2399
  setError(void 0);
2530
2400
  try {
@@ -2533,6 +2403,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2533
2403
  } catch (reason) {
2534
2404
  setError(reason instanceof Error ? reason.message : String(reason));
2535
2405
  } finally {
2406
+ busyRef.current = void 0;
2536
2407
  setBusy(void 0);
2537
2408
  }
2538
2409
  };
@@ -2751,11 +2622,15 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2751
2622
  });
2752
2623
  (0, react.useEffect)(() => {
2753
2624
  let disposed = false;
2754
- let loading = false;
2625
+ let active;
2755
2626
  const load = () => {
2756
- if (loading) return;
2757
- loading = true;
2758
- loadChannelGroups().then((next) => {
2627
+ if (active !== void 0 || document.visibilityState === "hidden") return;
2628
+ const controller = new AbortController();
2629
+ active = controller;
2630
+ const timeout = window.setTimeout(() => {
2631
+ controller.abort();
2632
+ }, 8e3);
2633
+ loadChannelGroups(controller.signal).then((next) => {
2759
2634
  if (!disposed) {
2760
2635
  setGroups(next);
2761
2636
  setPollError(void 0);
@@ -2763,16 +2638,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2763
2638
  }).catch(() => {
2764
2639
  if (!disposed) setPollError(t("channels.loadError"));
2765
2640
  }).finally(() => {
2766
- loading = false;
2641
+ window.clearTimeout(timeout);
2642
+ if (active === controller) active = void 0;
2767
2643
  });
2768
2644
  };
2769
2645
  load();
2770
2646
  const timer = window.setInterval(load, 4e3);
2647
+ const onVisibilityChange = () => {
2648
+ if (document.visibilityState === "visible") load();
2649
+ };
2650
+ document.addEventListener("visibilitychange", onVisibilityChange);
2771
2651
  return () => {
2772
2652
  disposed = true;
2653
+ active?.abort();
2773
2654
  window.clearInterval(timer);
2655
+ document.removeEventListener("visibilitychange", onVisibilityChange);
2774
2656
  };
2775
- }, []);
2657
+ }, [t]);
2776
2658
  const banner = pollError ?? error;
2777
2659
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2778
2660
  className: "dcu-wb",
@@ -3069,6 +2951,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3069
2951
  }
3070
2952
  //#endregion
3071
2953
  //#region src/client/CodexSidebar.tsx
2954
+ const subscribeEmptyCompanionTabs = () => () => {};
2955
+ const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
3072
2956
  const stylesheet$3 = `
3073
2957
  .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)}
3074
2958
  .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}
@@ -3126,7 +3010,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3126
3010
  const [searchQuery, setSearchQuery] = (0, react.useState)("");
3127
3011
  const [activeSearchIndex, setActiveSearchIndex] = (0, react.useState)(0);
3128
3012
  const [imTab, setImTab] = (0, react.useState)("tasks");
3129
- const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? (() => () => {}), companionSlots?.getSnapshot ?? (() => EMPTY_COMPANION_TABS), companionSlots?.getSnapshot ?? (() => EMPTY_COMPANION_TABS));
3013
+ const companionTabs = (0, react.useSyncExternalStore)(companionSlots?.subscribe ?? subscribeEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs, companionSlots?.getSnapshot ?? getEmptyCompanionTabs);
3130
3014
  const showChannels = companionTabs.channels;
3131
3015
  const showSchedule = companionTabs.schedule;
3132
3016
  const showCompanionTabs = showChannels || showSchedule;
@@ -3320,14 +3204,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3320
3204
  (0, react.useEffect)(() => {
3321
3205
  let startX = 0;
3322
3206
  let dragging = false;
3207
+ let pointerId;
3323
3208
  const onDown = (event) => {
3324
- if (!isSidebarDragHandle(event.target)) return;
3209
+ if (dragging || !isSidebarDragHandle(event.target)) return;
3325
3210
  dragging = true;
3211
+ pointerId = event.pointerId;
3326
3212
  startX = event.clientX;
3327
3213
  };
3328
3214
  const onUp = (event) => {
3329
- if (!dragging) return;
3215
+ if (!dragging || event.pointerId !== pointerId) return;
3330
3216
  dragging = false;
3217
+ pointerId = void 0;
3331
3218
  if (!shouldCollapseOnSidebarDrag(startX, event.clientX)) return;
3332
3219
  window.requestAnimationFrame(() => {
3333
3220
  window.requestAnimationFrame(() => {
@@ -3335,11 +3222,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3335
3222
  });
3336
3223
  });
3337
3224
  };
3225
+ const onCancel = (event) => {
3226
+ if (event.pointerId !== pointerId) return;
3227
+ dragging = false;
3228
+ pointerId = void 0;
3229
+ };
3338
3230
  window.addEventListener("pointerdown", onDown, true);
3339
3231
  window.addEventListener("pointerup", onUp, true);
3232
+ window.addEventListener("pointercancel", onCancel, true);
3340
3233
  return () => {
3341
3234
  window.removeEventListener("pointerdown", onDown, true);
3342
3235
  window.removeEventListener("pointerup", onUp, true);
3236
+ window.removeEventListener("pointercancel", onCancel, true);
3343
3237
  };
3344
3238
  }, [toggleSidebar]);
3345
3239
  if (collapsed || width < 80) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
@@ -3801,9 +3695,13 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3801
3695
  const root = (0, react.useRef)(null);
3802
3696
  const stateRef = (0, react.useRef)("loading");
3803
3697
  const requestId = (0, react.useRef)(0);
3698
+ const installingRef = (0, react.useRef)();
3804
3699
  stateRef.current = state;
3805
- (0, react.useEffect)(() => () => {
3806
- alive.current = false;
3700
+ (0, react.useEffect)(() => {
3701
+ alive.current = true;
3702
+ return () => {
3703
+ alive.current = false;
3704
+ };
3807
3705
  }, []);
3808
3706
  const load = (0, react.useCallback)(async (signal) => {
3809
3707
  const currentRequest = ++requestId.current;
@@ -3833,8 +3731,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3833
3731
  if (node === null || typeof IntersectionObserver === "undefined") return () => {
3834
3732
  controller.abort();
3835
3733
  };
3734
+ let initialized = false;
3735
+ let wasVisible = false;
3836
3736
  const observer = new IntersectionObserver((entries) => {
3837
- if (entries.some((entry) => entry.isIntersecting)) refresh();
3737
+ const visible = entries.some((entry) => entry.isIntersecting);
3738
+ if (!initialized) {
3739
+ initialized = true;
3740
+ wasVisible = visible;
3741
+ return;
3742
+ }
3743
+ if (visible && !wasVisible) refresh();
3744
+ wasVisible = visible;
3838
3745
  }, { threshold: .2 });
3839
3746
  observer.observe(node);
3840
3747
  return () => {
@@ -3843,6 +3750,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3843
3750
  };
3844
3751
  }, [load]);
3845
3752
  const install = async (id) => {
3753
+ if (installingRef.current !== void 0) return;
3754
+ installingRef.current = id;
3846
3755
  setInstalling(id);
3847
3756
  setMessage(void 0);
3848
3757
  try {
@@ -3863,6 +3772,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3863
3772
  text: error instanceof Error ? error.message : t("about.installFailed")
3864
3773
  });
3865
3774
  } finally {
3775
+ installingRef.current = void 0;
3866
3776
  if (alive.current) setInstalling(void 0);
3867
3777
  }
3868
3778
  };
@@ -3930,7 +3840,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3930
3840
  (!dependency.installed || dependency.updateAvailable) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3931
3841
  className: "dcu-about-install",
3932
3842
  type: "button",
3933
- disabled: installing === dependency.id,
3843
+ disabled: installing !== void 0,
3934
3844
  onClick: () => {
3935
3845
  install(dependency.id);
3936
3846
  },
@@ -4066,7 +3976,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4066
3976
  "search.actions": "快捷操作",
4067
3977
  "workspace.label": "工作区会话",
4068
3978
  "workspace.pinned": "置顶",
4069
- "workspace.pinnedEmpty": "拖动项目或会话到此处置顶",
3979
+ "workspace.pinnedEmpty": "拖动项目到此处置顶",
4070
3980
  "workspace.projects": "项目",
4071
3981
  "workspace.recent": "最近",
4072
3982
  "workspace.recentEmpty": "无聊天",
@@ -4194,7 +4104,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4194
4104
  "workspace.pin": "Pin project",
4195
4105
  "workspace.unpin": "Unpin",
4196
4106
  "workspace.pinned": "Pinned",
4197
- "workspace.pinnedEmpty": "Drag a project or chat here to pin it",
4107
+ "workspace.pinnedEmpty": "Drag a project here to pin it",
4198
4108
  "workspace.openPath": "Open in file explorer",
4199
4109
  "workspace.delete": "Delete project",
4200
4110
  "workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
@@ -4384,11 +4294,27 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4384
4294
  let applying = false;
4385
4295
  let frame;
4386
4296
  let pending;
4297
+ let frameObserver;
4298
+ const watchFrame = (next) => {
4299
+ if (frame === next) return;
4300
+ frameObserver?.disconnect();
4301
+ frame = next;
4302
+ if (frame === void 0) return;
4303
+ frameObserver = new MutationObserver(schedule);
4304
+ frameObserver.observe(frame, {
4305
+ attributes: true,
4306
+ attributeFilter: [
4307
+ "style",
4308
+ "data-sidebar-collapsed",
4309
+ "data-dragging"
4310
+ ]
4311
+ });
4312
+ };
4387
4313
  const apply = () => {
4388
4314
  if (applying) return;
4389
4315
  applying = true;
4390
4316
  try {
4391
- if (frame === void 0 || !frame.isConnected) frame = findSidebarFrame(document);
4317
+ if (frame === void 0 || !frame.isConnected) watchFrame(findSidebarFrame(document));
4392
4318
  if (frame !== void 0) applySlimSidebar(frame);
4393
4319
  } finally {
4394
4320
  applying = false;
@@ -4402,19 +4328,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4402
4328
  });
4403
4329
  };
4404
4330
  apply();
4405
- const observer = new MutationObserver(schedule);
4331
+ const observer = new MutationObserver(() => {
4332
+ if (frame === void 0 || !frame.isConnected) schedule();
4333
+ });
4406
4334
  observer.observe(document.body, {
4407
- attributes: true,
4408
- attributeFilter: [
4409
- "style",
4410
- "data-sidebar-collapsed",
4411
- "data-dragging"
4412
- ],
4413
4335
  childList: true,
4414
4336
  subtree: true
4415
4337
  });
4416
4338
  return () => {
4417
4339
  observer.disconnect();
4340
+ frameObserver?.disconnect();
4418
4341
  if (pending !== void 0) window.cancelAnimationFrame(pending);
4419
4342
  };
4420
4343
  }
@@ -4428,8 +4351,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4428
4351
  return document.querySelector("[data-conversation-scroll]");
4429
4352
  }
4430
4353
  function conversationAnchor(root, key) {
4431
- for (const anchor of root.querySelectorAll("[data-chat-anchor-key]")) if (anchor.dataset.chatAnchorKey === key) return anchor;
4432
- return null;
4354
+ return conversationAnchors(root).get(key) ?? null;
4355
+ }
4356
+ /** 一次扫描生成锚点索引,滚动帧内不得为每个轮次重复遍历 DOM。 */
4357
+ function conversationAnchors(root) {
4358
+ const anchors = /* @__PURE__ */ new Map();
4359
+ for (const anchor of root.querySelectorAll("[data-chat-anchor-key]")) {
4360
+ const key = anchor.dataset.chatAnchorKey;
4361
+ if (key !== void 0 && !anchors.has(key)) anchors.set(key, anchor);
4362
+ }
4363
+ return anchors;
4433
4364
  }
4434
4365
  //#endregion
4435
4366
  //#region src/client/TurnNavigator.tsx
@@ -4531,27 +4462,44 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4531
4462
  };
4532
4463
  }, []);
4533
4464
  (0, react.useEffect)(() => {
4534
- const host = conversationScrollRoot();
4535
- if (host === null || turns.length === 0) return;
4465
+ if (turns.length === 0) return;
4466
+ let host = null;
4536
4467
  let frame = null;
4537
4468
  const update = () => {
4538
4469
  frame = null;
4470
+ if (host === null) return;
4539
4471
  const threshold = host.getBoundingClientRect().top + Math.min(180, host.clientHeight * .35);
4472
+ const anchors = conversationAnchors(host);
4540
4473
  let next = turns[0]?.key ?? null;
4541
4474
  for (const turn of turns) {
4542
- const anchor = conversationAnchor(host, turn.key);
4543
- if (anchor !== null && anchor.getBoundingClientRect().top <= threshold) next = turn.key;
4475
+ const anchor = anchors.get(turn.key);
4476
+ if (anchor !== void 0 && anchor.getBoundingClientRect().top <= threshold) next = turn.key;
4544
4477
  }
4545
4478
  setCurrent((previous) => previous === next ? previous : next);
4546
4479
  };
4547
4480
  const schedule = () => {
4548
4481
  if (frame === null) frame = window.requestAnimationFrame(update);
4549
4482
  };
4550
- host.addEventListener("scroll", schedule, { passive: true });
4483
+ const bindHost = () => {
4484
+ const next = conversationScrollRoot();
4485
+ if (next === host) return;
4486
+ host?.removeEventListener("scroll", schedule);
4487
+ host = next;
4488
+ host?.addEventListener("scroll", schedule, { passive: true });
4489
+ schedule();
4490
+ };
4491
+ const observer = new MutationObserver(() => {
4492
+ if (host === null || !host.isConnected) bindHost();
4493
+ });
4494
+ observer.observe(document.body, {
4495
+ childList: true,
4496
+ subtree: true
4497
+ });
4551
4498
  window.addEventListener("resize", schedule);
4552
- schedule();
4499
+ bindHost();
4553
4500
  return () => {
4554
- host.removeEventListener("scroll", schedule);
4501
+ observer.disconnect();
4502
+ host?.removeEventListener("scroll", schedule);
4555
4503
  window.removeEventListener("resize", schedule);
4556
4504
  if (frame !== null) window.cancelAnimationFrame(frame);
4557
4505
  };
@@ -4606,7 +4554,6 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4606
4554
  index: index + 1,
4607
4555
  summary: turn.summary
4608
4556
  }),
4609
- title: turn.summary,
4610
4557
  style: tickStyle,
4611
4558
  onFocus: () => {
4612
4559
  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 (versionParts(version) === void 0) throw new Error("npm 返回了无法识别的最新版本。");
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 versionParts(version) {
189
- const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version);
190
- return match === null ? void 0 : [
191
- Number(match[1]),
192
- Number(match[2]),
193
- Number(match[3])
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 prereleaseRank(version) {
197
- const match = /-rc\.(\d+)/i.exec(version);
198
- return match === null ? Number.POSITIVE_INFINITY : Number(match[1]);
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 = versionParts(installed);
202
- const candidate = versionParts(latest);
218
+ const current = parseSemver(installed);
219
+ const candidate = parseSemver(latest);
203
220
  if (current === void 0 || candidate === void 0) return false;
204
- if (candidate[0] !== current[0]) return candidate[0] > current[0];
205
- if (candidate[1] !== current[1]) return candidate[1] > current[1];
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
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
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 runDshPlugin(args) {
297
- const entry = resolveDshCliEntry();
298
- return new Promise((resolvePromise, reject) => {
299
- const child = spawn(process.execPath, [
300
- ...process.execArgv,
301
- entry,
302
- "plugin",
303
- "--profile",
304
- "web",
305
- ...args
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 += String(chunk);
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
- reject(/* @__PURE__ */ new Error("无法启动 DSH 插件安装命令。请确认 Node.js 与 pnpm 可用后重试。"));
379
+ finish(/* @__PURE__ */ new Error("无法启动 DSH 插件安装命令。请确认 Node.js 与 pnpm 可用后重试。"));
327
380
  });
328
381
  child.once("exit", (code) => {
329
- code === 0 ? resolvePromise() : reject(pluginCommandError(output));
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.71",
3
+ "version": "0.2.73",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -51,6 +51,11 @@
51
51
  "platform": "web"
52
52
  }
53
53
  },
54
+ "scripts": {
55
+ "build": "tsc --noEmit && tsdown",
56
+ "typecheck": "tsc --noEmit",
57
+ "test": "pnpm run typecheck && vitest run tests/client-runtime.integration.spec.ts && tsdown && node tests/run-assertions.mjs"
58
+ },
54
59
  "peerDependencies": {
55
60
  "@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
56
61
  "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.0 <0.2.0",
@@ -96,9 +101,5 @@
96
101
  "tsx": "^4.22.4",
97
102
  "typescript": "^6.0.3",
98
103
  "vitest": "^4.1.8"
99
- },
100
- "scripts": {
101
- "build": "tsc --noEmit && tsdown",
102
- "test": "tsx tests/hover-tip.assert.ts && tsx tests/companion-slots.assert.ts && tsx tests/channel-api.assert.ts && tsx tests/schedule-sessions.assert.ts && tsx tests/session-tree.assert.ts && tsx tests/pinned-sessions.assert.ts && tsx tests/session-manager.assert.ts && tsx tests/workspace-browser.assert.ts && tsx tests/sidebar-search.assert.ts && tsx tests/settings-navigation.assert.ts && tsx tests/settings-nav-icons.assert.ts && tsx tests/locales.assert.ts && vitest run tests/client-runtime.integration.spec.ts && tsx tests/settings-integration.assert.ts && tsx tests/about-dependencies.assert.ts && tsx tests/dependency-manager.assert.ts && tsx tests/conversation-bubbles.assert.ts && tsx tests/conversation-header.assert.ts && tsx tests/conversation-visuals.assert.ts && tsdown && tsx tests/client-bundle.assert.ts && tsx tests/codex-suite.assert.ts"
103
104
  }
104
- }
105
+ }