@michengai/dsh-codex-ui 1.1.11 → 1.1.13

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/lib/client.js CHANGED
@@ -8,11 +8,167 @@ window.__ModuleLoader__.load({
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
10
  let react_dom = require("react-dom");
11
+ //#region src/client/session-host.ts
12
+ var UnknownSessionError = class extends Error {
13
+ constructor() {
14
+ super("unknown session");
15
+ this.name = "UnknownSessionError";
16
+ }
17
+ };
18
+ /** 旧宿主读 list.current;alpha.2 主视图改由 retainedBy.mainView 标记。 */
19
+ function currentSessionId(state) {
20
+ if (state === void 0) return void 0;
21
+ if (typeof state.current === "string" && state.current !== "") return state.current;
22
+ const byId = state.byId ?? {};
23
+ const ids = Array.isArray(state.ids) && state.ids.length > 0 ? state.ids : Object.keys(byId);
24
+ const pick = (id, row) => {
25
+ if (row === void 0 || (row.retainedBy?.mainView ?? 0) <= 0) return void 0;
26
+ const explicit = row.id?.trim();
27
+ return explicit !== void 0 && explicit !== "" ? explicit : id;
28
+ };
29
+ for (const id of ids) {
30
+ const found = pick(id, byId[id]);
31
+ if (found !== void 0) return found;
32
+ }
33
+ for (const id of Object.keys(byId)) {
34
+ if (ids.includes(id)) continue;
35
+ const found = pick(id, byId[id]);
36
+ if (found !== void 0) return found;
37
+ }
38
+ }
39
+ /** 官方树在全局面板打开时取消会话选中。 */
40
+ function visibleSelectedSessionId(state, panelActive = false) {
41
+ return panelActive ? void 0 : currentSessionId(state);
42
+ }
43
+ function sessionRowUnread(localUnread, status, selected = false) {
44
+ return localUnread || status?.completionUnread === true && !selected;
45
+ }
46
+ function sessionRunningFlags(byId, status) {
47
+ const ids = new Set(Object.keys(byId));
48
+ if (status !== void 0) for (const id of status.keys()) ids.add(id);
49
+ const next = {};
50
+ for (const id of ids) next[id] = sessionIsRunning(byId[id], status?.get(id));
51
+ return next;
52
+ }
53
+ /** 旧宿主看 current 是否空会话;alpha.2 看主视图 retain 的 blank。 */
54
+ function isBlankOnboardingSession(state) {
55
+ if (state?.phase !== "ready") return false;
56
+ const id = currentSessionId(state);
57
+ return id === void 0 || state.byId?.[id]?.blank === true;
58
+ }
59
+ function hostAccess(ctx) {
60
+ return ctx;
61
+ }
62
+ /** 有 reflect 时只 probe,不能硬读未注入服务,否则 Cordis 会挡住插件激活。 */
63
+ function probeService(ctx, name) {
64
+ if (ctx === void 0) return void 0;
65
+ const access = hostAccess(ctx);
66
+ const reflectGet = access.reflect?.get;
67
+ const hasReflect = typeof reflectGet === "function";
68
+ if (hasReflect) try {
69
+ const found = reflectGet.call(access.reflect, name);
70
+ if (found !== void 0) return found;
71
+ } catch {}
72
+ if (typeof access.get === "function") try {
73
+ const found = access.get(name);
74
+ if (found !== void 0) return found;
75
+ } catch {
76
+ return;
77
+ }
78
+ if (hasReflect) return void 0;
79
+ try {
80
+ return access[name];
81
+ } catch {
82
+ return;
83
+ }
84
+ }
85
+ function openHostSession(ctx, id) {
86
+ if (id === "") return false;
87
+ const access = hostAccess(ctx);
88
+ const uiWorkspace = probeService(access, "uiWorkspace");
89
+ if (typeof uiWorkspace?.openSession === "function") {
90
+ uiWorkspace.openSession(id);
91
+ return true;
92
+ }
93
+ if (typeof access.sessions?.retain === "function") return false;
94
+ if (typeof access.sessions?.open === "function") {
95
+ access.sessions.open(id);
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+ async function withSessionBinding(sessions, sessionId, operation) {
101
+ const host = sessions === void 0 ? void 0 : hostAccess({ sessions }).sessions;
102
+ if (host === void 0) throw new UnknownSessionError();
103
+ if (typeof host.using === "function") return host.using(sessionId, { source: "controllerOperation" }, async (reference) => {
104
+ if (reference.ready !== void 0) await reference.ready;
105
+ if (reference.binding === void 0) throw new UnknownSessionError();
106
+ return operation(reference.binding);
107
+ });
108
+ const existing = host.binding?.(sessionId);
109
+ if (existing !== void 0) return operation(existing);
110
+ if (typeof host.retain !== "function") throw new UnknownSessionError();
111
+ const reference = host.retain(sessionId, { source: "controllerOperation" });
112
+ try {
113
+ if (reference.ready !== void 0) await reference.ready;
114
+ if (reference.binding === void 0) throw new UnknownSessionError();
115
+ return await operation(reference.binding);
116
+ } finally {
117
+ reference.release?.();
118
+ }
119
+ }
120
+ async function renameHostSession(ctx, sessionId, title) {
121
+ await withSessionBinding(hostAccess(ctx).sessions, sessionId, async (binding) => {
122
+ if (typeof binding.session?.rename !== "function") throw new UnknownSessionError();
123
+ const result = await binding.session.rename(title);
124
+ if (result.ok === false) throw result.error ?? /* @__PURE__ */ new Error("rename failed");
125
+ });
126
+ }
127
+ async function forkHostSession(ctx, sessionId) {
128
+ const access = hostAccess(ctx);
129
+ const uiWorkspace = probeService(access, "uiWorkspace");
130
+ if (typeof uiWorkspace?.forkSession === "function") {
131
+ await uiWorkspace.forkSession(sessionId);
132
+ return;
133
+ }
134
+ const childId = await access.sessions?.fork?.({
135
+ sessionId,
136
+ increaseTitle: true
137
+ });
138
+ if (typeof childId === "string" && childId !== "") openHostSession(access, childId);
139
+ }
140
+ async function archiveHostSession(ctx, sessionId) {
141
+ const access = hostAccess(ctx);
142
+ const uiWorkspace = probeService(access, "uiWorkspace");
143
+ if (typeof uiWorkspace?.archiveSession === "function") {
144
+ await uiWorkspace.archiveSession(sessionId);
145
+ return;
146
+ }
147
+ await access.workspaces?.archiveSession?.(sessionId);
148
+ }
149
+ function mergePendingInteractions(legacy, status) {
150
+ const next = new Map(legacy);
151
+ for (const [id, row] of status) if (row?.pendingInteraction !== void 0) next.set(id, row.pendingInteraction);
152
+ return next;
153
+ }
154
+ function sessionIsRunning(session, status) {
155
+ if (status?.running !== void 0) return status.running === true;
156
+ return session?.running === true;
157
+ }
158
+ //#endregion
11
159
  //#region src/client/session-navigation.ts
12
- /** 新版会话控制器不再负责主面板切换;打开会话时同时退出全局面板。 */
13
- function openConversation(sessions, layout, id) {
14
- sessions.open(id);
15
- selectGlobalPanel(layout, null);
160
+ function openConversation(host, layout, id) {
161
+ const opened = openHostSession(host, id);
162
+ if (opened) selectGlobalPanel(layout, null);
163
+ return opened;
164
+ }
165
+ /** 先打开再写草稿,避免 using 释放后 composer 换新 binding。 */
166
+ async function openConversationWithDraft(host, layout, sessions, sessionId, writeDraft) {
167
+ return withSessionBinding(sessions, sessionId, (binding) => {
168
+ const opened = openConversation(host, layout, sessionId);
169
+ writeDraft(binding);
170
+ return opened;
171
+ });
16
172
  }
17
173
  /** 统一检测旧宿主是否提供面板切换能力,保留宿主方法的 this。 */
18
174
  function selectGlobalPanel(layout, id) {
@@ -720,17 +876,112 @@ window.__ModuleLoader__.load({
720
876
  key: "17ys0d"
721
877
  }]]);
722
878
  //#endregion
879
+ //#region src/client/settings-navigation.ts
880
+ /**
881
+ * DSH 设置壳暂未提供按 section id 打开的公开 API。该兼容层只从本插件渲染
882
+ * 的设置入口触发,再按壳的可访问名称选择页面;宿主升级时可集中替换。
883
+ */
884
+ function pickSettingsSectionButton(buttons, labels) {
885
+ for (const label of labels) {
886
+ const match = buttons.find((button) => button.textContent?.trim() === label);
887
+ if (match !== void 0) return match;
888
+ }
889
+ }
890
+ /**
891
+ * 可选插件未注册设置入口时,直接打开兜底页,避免先展示通用设置并等待导航超时。
892
+ */
893
+ function routeOptionalSettingsSection(available, openRequested, openFallback) {
894
+ if (available) {
895
+ openRequested();
896
+ return;
897
+ }
898
+ openFallback();
899
+ }
900
+ /** 官方 alpha.2 插件管理页占用 sidebar.panellist / main 的同一 id。 */
901
+ const OFFICIAL_PLUGINS_PANEL_ID = "plugins";
902
+ let cancelPendingNavigation;
903
+ const SETTINGS_NAVIGATION_TIMEOUT_MS = 4e3;
904
+ const SETTINGS_TRIGGER_SELECTOR = "[data-dcu-settings-trigger],[aria-haspopup=\"dialog\"]";
905
+ const SETTINGS_OPEN_SECTION_EVENT = "dcu-settings-open-section";
906
+ /** 根入口与分区跳转使用同一触发器合约,兼容保留的宿主设置壳。 */
907
+ function openSettingsRoot(root) {
908
+ root?.querySelector(SETTINGS_TRIGGER_SELECTOR)?.click();
909
+ }
910
+ function openSettingsSection(root, label, onMissing, onSelected) {
911
+ const labels = typeof label === "string" ? [label] : label;
912
+ const trigger = root?.querySelector(SETTINGS_TRIGGER_SELECTOR);
913
+ if (trigger === null || trigger === void 0) {
914
+ onMissing?.();
915
+ return;
916
+ }
917
+ const opening = cancelPendingNavigation !== void 0;
918
+ cancelPendingNavigation?.();
919
+ const request = new CustomEvent(SETTINGS_OPEN_SECTION_EVENT, {
920
+ detail: { labels },
921
+ cancelable: true
922
+ });
923
+ if (!trigger.dispatchEvent(request)) {
924
+ onSelected?.();
925
+ return;
926
+ }
927
+ const pageSelector = trigger.hasAttribute("data-dcu-settings-trigger") ? "[data-dcu-settings-page]" : "[role=\"dialog\"]";
928
+ if (!opening && document.querySelector(pageSelector) === null) trigger.click();
929
+ let frame;
930
+ let finished = false;
931
+ const observer = new MutationObserver(() => {
932
+ schedule();
933
+ });
934
+ const cleanup = () => {
935
+ if (finished) return;
936
+ finished = true;
937
+ observer.disconnect();
938
+ window.clearTimeout(timeout);
939
+ if (frame !== void 0) window.cancelAnimationFrame(frame);
940
+ if (cancelPendingNavigation === cleanup) cancelPendingNavigation = void 0;
941
+ };
942
+ const select = () => {
943
+ const target = pickSettingsSectionButton([...document.querySelectorAll(`${pageSelector} nav button`)], labels);
944
+ if (target === void 0) return false;
945
+ cleanup();
946
+ target.click();
947
+ onSelected?.();
948
+ return true;
949
+ };
950
+ const schedule = () => {
951
+ if (finished || frame !== void 0) return;
952
+ frame = window.requestAnimationFrame(() => {
953
+ frame = void 0;
954
+ select();
955
+ });
956
+ };
957
+ const timeout = window.setTimeout(() => {
958
+ if (select()) return;
959
+ cleanup();
960
+ console.warn(`[michengai-codex-ui] 未找到设置分区:${labels.join(" / ")}`);
961
+ onMissing?.();
962
+ }, SETTINGS_NAVIGATION_TIMEOUT_MS);
963
+ observer.observe(document.body, {
964
+ childList: true,
965
+ subtree: true
966
+ });
967
+ cancelPendingNavigation = cleanup;
968
+ schedule();
969
+ }
970
+ //#endregion
723
971
  //#region src/client/global-panels.tsx
724
972
  /** 从宿主注册表读取面板元数据;缓存快照以满足 React 外部状态订阅契约。 */
725
973
  function createGlobalPanelSource(slots, locale) {
726
974
  let cached = [];
727
975
  return {
728
976
  getSnapshot() {
729
- const next = slots.entriesOfSlot("sidebar.panellist").flatMap(({ options }) => options.id === void 0 ? [] : [{
730
- id: options.id,
731
- order: options.order ?? 0,
732
- label: (typeof options.label === "function" ? options.label() : options.label) ?? options.id
733
- }]).sort((a, b) => a.order - b.order);
977
+ const next = slots.entriesOfSlot("sidebar.panellist").flatMap(({ options }) => {
978
+ if (options.id === void 0 || options.id === "plugins") return [];
979
+ return [{
980
+ id: options.id,
981
+ order: options.order ?? 0,
982
+ label: (typeof options.label === "function" ? options.label() : options.label) ?? options.id
983
+ }];
984
+ }).sort((a, b) => a.order - b.order);
734
985
  if (next.length !== cached.length || next.some((panel, index) => {
735
986
  const previous = cached[index];
736
987
  return previous === void 0 || panel.id !== previous.id || panel.label !== previous.label || panel.order !== previous.order;
@@ -907,96 +1158,6 @@ window.__ModuleLoader__.load({
907
1158
  } catch {}
908
1159
  }
909
1160
  //#endregion
910
- //#region src/client/settings-navigation.ts
911
- /**
912
- * DSH 设置壳暂未提供按 section id 打开的公开 API。该兼容层只从本插件渲染
913
- * 的设置入口触发,再按壳的可访问名称选择页面;宿主升级时可集中替换。
914
- */
915
- function pickSettingsSectionButton(buttons, labels) {
916
- for (const label of labels) {
917
- const match = buttons.find((button) => button.textContent?.trim() === label);
918
- if (match !== void 0) return match;
919
- }
920
- }
921
- /**
922
- * 可选插件未注册设置入口时,直接打开兜底页,避免先展示通用设置并等待导航超时。
923
- */
924
- function routeOptionalSettingsSection(available, openRequested, openFallback) {
925
- if (available) {
926
- openRequested();
927
- return;
928
- }
929
- openFallback();
930
- }
931
- let cancelPendingNavigation;
932
- const SETTINGS_NAVIGATION_TIMEOUT_MS = 4e3;
933
- const SETTINGS_TRIGGER_SELECTOR = "[data-dcu-settings-trigger],[aria-haspopup=\"dialog\"]";
934
- const SETTINGS_OPEN_SECTION_EVENT = "dcu-settings-open-section";
935
- /** 根入口与分区跳转使用同一触发器合约,兼容保留的宿主设置壳。 */
936
- function openSettingsRoot(root) {
937
- root?.querySelector(SETTINGS_TRIGGER_SELECTOR)?.click();
938
- }
939
- function openSettingsSection(root, label, onMissing, onSelected) {
940
- const labels = typeof label === "string" ? [label] : label;
941
- const trigger = root?.querySelector(SETTINGS_TRIGGER_SELECTOR);
942
- if (trigger === null || trigger === void 0) {
943
- onMissing?.();
944
- return;
945
- }
946
- const opening = cancelPendingNavigation !== void 0;
947
- cancelPendingNavigation?.();
948
- const request = new CustomEvent(SETTINGS_OPEN_SECTION_EVENT, {
949
- detail: { labels },
950
- cancelable: true
951
- });
952
- if (!trigger.dispatchEvent(request)) {
953
- onSelected?.();
954
- return;
955
- }
956
- const pageSelector = trigger.hasAttribute("data-dcu-settings-trigger") ? "[data-dcu-settings-page]" : "[role=\"dialog\"]";
957
- if (!opening && document.querySelector(pageSelector) === null) trigger.click();
958
- let frame;
959
- let finished = false;
960
- const observer = new MutationObserver(() => {
961
- schedule();
962
- });
963
- const cleanup = () => {
964
- if (finished) return;
965
- finished = true;
966
- observer.disconnect();
967
- window.clearTimeout(timeout);
968
- if (frame !== void 0) window.cancelAnimationFrame(frame);
969
- if (cancelPendingNavigation === cleanup) cancelPendingNavigation = void 0;
970
- };
971
- const select = () => {
972
- const target = pickSettingsSectionButton([...document.querySelectorAll(`${pageSelector} nav button`)], labels);
973
- if (target === void 0) return false;
974
- cleanup();
975
- target.click();
976
- onSelected?.();
977
- return true;
978
- };
979
- const schedule = () => {
980
- if (finished || frame !== void 0) return;
981
- frame = window.requestAnimationFrame(() => {
982
- frame = void 0;
983
- select();
984
- });
985
- };
986
- const timeout = window.setTimeout(() => {
987
- if (select()) return;
988
- cleanup();
989
- console.warn(`[michengai-codex-ui] 未找到设置分区:${labels.join(" / ")}`);
990
- onMissing?.();
991
- }, SETTINGS_NAVIGATION_TIMEOUT_MS);
992
- observer.observe(document.body, {
993
- childList: true,
994
- subtree: true
995
- });
996
- cancelPendingNavigation = cleanup;
997
- schedule();
998
- }
999
- //#endregion
1000
1161
  //#region src/client/sidebar-search.ts
1001
1162
  /** 按用户可见名称和辅助关键词筛选侧栏搜索结果。 */
1002
1163
  function filterSidebarSearchItems(items, query) {
@@ -1246,6 +1407,23 @@ window.__ModuleLoader__.load({
1246
1407
  return parseChannelGroups(payload, fallbackLabel);
1247
1408
  }
1248
1409
  //#endregion
1410
+ //#region src/client/session-title-scroll.ts
1411
+ /** 会话行只展示标题第一行,避免换行把胶囊撑高。 */
1412
+ function sessionTitleLine(title) {
1413
+ return title.split(/\r?\n/)[0] ?? title;
1414
+ }
1415
+ /** 标题比可视槽多出来的像素;1px 内当作测量误差。 */
1416
+ function titleOverflowPx(scrollWidth, clientWidth) {
1417
+ const extra = Math.ceil(scrollWidth - clientWidth);
1418
+ return extra > 1 ? extra : 0;
1419
+ }
1420
+ const PX_PER_SECOND = 42;
1421
+ /** 按溢出长度估算单程时长,长标题更慢,避免一闪而过。 */
1422
+ function titleScrollDurationMs(overflowPx) {
1423
+ if (overflowPx <= 0) return 0;
1424
+ return Math.min(8e3, Math.max(1600, Math.round(overflowPx / PX_PER_SECOND * 1e3)));
1425
+ }
1426
+ //#endregion
1249
1427
  //#region src/client/session-tree.tsx
1250
1428
  function PinIcon() {
1251
1429
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pin, {
@@ -1416,7 +1594,58 @@ window.__ModuleLoader__.load({
1416
1594
  });
1417
1595
  return null;
1418
1596
  }
1419
- function SessionRow({ id, title, selected, menuOpen, unread, running, pendingInteraction, t, menuItems, onOpen, onMenuChange, onSelectAction, onArchive, onHover, onLeave, onContextMenu, menuPoint, subtitle, flat, draggable, dropActive, onDragStart, onDragEnd, onDragOver, onDrop }) {
1597
+ function SessionTime({ time }) {
1598
+ if (time === void 0 || time === "") return null;
1599
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1600
+ className: "dcu-wb-session-time",
1601
+ children: time
1602
+ });
1603
+ }
1604
+ function SessionRowTitle({ title }) {
1605
+ const text = sessionTitleLine(title);
1606
+ const wrapRef = (0, react.useRef)(null);
1607
+ const textRef = (0, react.useRef)(null);
1608
+ const [shift, setShift] = (0, react.useState)(0);
1609
+ (0, react.useLayoutEffect)(() => {
1610
+ const wrap = wrapRef.current;
1611
+ const inner = textRef.current;
1612
+ if (wrap === null || inner === null) return;
1613
+ const measure = () => {
1614
+ setShift(titleOverflowPx(inner.scrollWidth, wrap.clientWidth));
1615
+ };
1616
+ measure();
1617
+ let cancelled = false;
1618
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
1619
+ if (fonts !== void 0) fonts.ready.then(() => {
1620
+ if (!cancelled) measure();
1621
+ });
1622
+ if (typeof ResizeObserver === "undefined") return () => {
1623
+ cancelled = true;
1624
+ };
1625
+ const observer = new ResizeObserver(measure);
1626
+ observer.observe(wrap);
1627
+ return () => {
1628
+ cancelled = true;
1629
+ observer.disconnect();
1630
+ };
1631
+ }, [text]);
1632
+ const style = shift > 0 ? {
1633
+ "--dcu-title-shift": `${shift}px`,
1634
+ "--dcu-title-duration": `${titleScrollDurationMs(shift)}ms`
1635
+ } : void 0;
1636
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1637
+ ref: wrapRef,
1638
+ className: "dcu-wb-session-title",
1639
+ "data-overflow": shift > 0 || void 0,
1640
+ style,
1641
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1642
+ ref: textRef,
1643
+ className: "dcu-wb-session-title-text",
1644
+ children: text
1645
+ })
1646
+ });
1647
+ }
1648
+ function SessionRow({ id, title, selected, menuOpen, unread, running, pendingInteraction, t, menuItems, onOpen, onMenuChange, onSelectAction, onArchive, onHover, onLeave, onContextMenu, menuPoint, subtitle, flat, draggable, dropActive, onDragStart, onDragEnd, onDragOver, onDrop, time }) {
1420
1649
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1421
1650
  "data-dcu-session": id,
1422
1651
  className: `dcu-wb-session${selected ? " dcu-wb-selected" : ""}${menuOpen ? " dcu-wb-menu-open" : ""}${dropActive === true ? " dcu-wb-drop" : ""}${flat === true ? " dcu-wb-session-flat" : ""}`,
@@ -1434,23 +1663,18 @@ window.__ModuleLoader__.load({
1434
1663
  children: [
1435
1664
  subtitle !== void 0 && subtitle !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1436
1665
  className: "dcu-wb-session-copy",
1437
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1438
- className: "dcu-wb-session-title",
1439
- children: title.split(/\r?\n/)[0] ?? title
1440
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1666
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1441
1667
  className: "dcu-wb-session-sub",
1442
1668
  children: subtitle
1443
1669
  })]
1444
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1445
- className: "dcu-wb-session-title",
1446
- children: title.split(/\r?\n/)[0] ?? title
1447
- }),
1670
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title }),
1448
1671
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
1449
1672
  pendingInteraction,
1450
1673
  unread,
1451
1674
  running,
1452
1675
  t
1453
1676
  }),
1677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionTime, { time }),
1454
1678
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1455
1679
  className: "dcu-wb-quick-actions",
1456
1680
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1524,9 +1748,20 @@ window.__ModuleLoader__.load({
1524
1748
  //#endregion
1525
1749
  //#region src/client/session-pending.ts
1526
1750
  const EMPTY_PENDING_INTERACTIONS = /* @__PURE__ */ new Map();
1751
+ const EMPTY_SESSION_STATUS = /* @__PURE__ */ new Map();
1527
1752
  function useEmptySessionPendingInteraction(selector) {
1528
1753
  return selector(EMPTY_PENDING_INTERACTIONS);
1529
1754
  }
1755
+ function useEmptySessionStatus(selector) {
1756
+ return selector(EMPTY_SESSION_STATUS);
1757
+ }
1758
+ function useHostSessionStatus(useSessionStatus) {
1759
+ return (useSessionStatus ?? useEmptySessionStatus)((state) => state);
1760
+ }
1761
+ /** 旧宿主订阅 pending Store;alpha.2 改走 useSessionStatus。两路都要订阅,避免条件 Hook。 */
1762
+ function useHostPendingInteractions(useSessionPendingInteraction, useSessionStatus) {
1763
+ return mergePendingInteractions((useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state), (useSessionStatus ?? useEmptySessionStatus)((state) => state));
1764
+ }
1530
1765
  function visiblePendingKind(kind) {
1531
1766
  switch (kind) {
1532
1767
  case "approval":
@@ -1915,13 +2150,84 @@ window.__ModuleLoader__.load({
1915
2150
  top: rect.top
1916
2151
  };
1917
2152
  }
1918
- /** Codex 会话卡片右上角的紧凑相对时间。 */
2153
+ const HOUR = 3600;
2154
+ const DAY = HOUR * 24;
2155
+ const WEEK = DAY * 7;
2156
+ const MONTH = DAY * 30;
2157
+ const YEAR = DAY * 365;
2158
+ function formatRelativeTime(updatedAt, t, now, justNow) {
2159
+ const seconds = Math.max(0, Math.floor((now - updatedAt) / 1e3));
2160
+ if (seconds < 60) return t(justNow);
2161
+ if (seconds < HOUR) return t("time.minutes", { count: Math.floor(seconds / 60) });
2162
+ if (seconds < DAY) return t("time.hours", { count: Math.floor(seconds / HOUR) });
2163
+ if (seconds < WEEK) return t("time.days", { count: Math.floor(seconds / DAY) });
2164
+ if (seconds < MONTH) return t("time.weeks", { count: Math.floor(seconds / WEEK) });
2165
+ if (seconds < YEAR) return t("time.months", { count: Math.floor(seconds / MONTH) });
2166
+ return t("time.years", { count: Math.floor(seconds / YEAR) });
2167
+ }
2168
+ /** 悬停卡片右上角。分档与 ChatGPT `$wa` 一致;英文刚刚用 Just now。 */
1919
2169
  function formatHoverTime(updatedAt, t, now = Date.now()) {
1920
- const seconds = Math.max(0, Math.round((now - updatedAt) / 1e3));
1921
- if (seconds < 60) return t("time.justNow");
1922
- if (seconds < 3600) return t("time.minutes", { count: Math.floor(seconds / 60) });
1923
- if (seconds < 86400) return t("time.hours", { count: Math.floor(seconds / 3600) });
1924
- return t("time.days", { count: Math.floor(seconds / 86400) });
2170
+ return formatRelativeTime(updatedAt, t, now, "time.justNow");
2171
+ }
2172
+ /** 会话行右侧。分档相同;英文刚刚用 now,避免挤占标题。 */
2173
+ function formatCompactTime(updatedAt, t, now = Date.now()) {
2174
+ return formatRelativeTime(updatedAt, t, now, "time.justNowShort");
2175
+ }
2176
+ //#endregion
2177
+ //#region src/client/shared-now.ts
2178
+ const SHARED_NOW_INTERVAL_MS = 6e4;
2179
+ /** 对齐到下一个整档,避免「刚刚」在整分钟边界之后还多停将近一轮。 */
2180
+ function msUntilNextNowTick(now, intervalMs = SHARED_NOW_INTERVAL_MS) {
2181
+ const remainder = now % intervalMs;
2182
+ return remainder === 0 ? intervalMs : intervalMs - remainder;
2183
+ }
2184
+ const listeners = /* @__PURE__ */ new Set();
2185
+ let sharedNow = Date.now();
2186
+ let timeoutId = 0;
2187
+ let intervalId = 0;
2188
+ let cancelled = true;
2189
+ function publish(now) {
2190
+ sharedNow = now;
2191
+ for (const listener of listeners) listener();
2192
+ }
2193
+ function startClock() {
2194
+ cancelled = false;
2195
+ timeoutId = window.setTimeout(() => {
2196
+ if (cancelled) return;
2197
+ publish(Date.now());
2198
+ intervalId = window.setInterval(() => {
2199
+ publish(Date.now());
2200
+ }, SHARED_NOW_INTERVAL_MS);
2201
+ if (cancelled) {
2202
+ window.clearInterval(intervalId);
2203
+ intervalId = 0;
2204
+ }
2205
+ }, msUntilNextNowTick(Date.now()));
2206
+ }
2207
+ function stopClock() {
2208
+ cancelled = true;
2209
+ window.clearTimeout(timeoutId);
2210
+ window.clearInterval(intervalId);
2211
+ timeoutId = 0;
2212
+ intervalId = 0;
2213
+ }
2214
+ function subscribe(listener) {
2215
+ if (listeners.size === 0) {
2216
+ sharedNow = Date.now();
2217
+ startClock();
2218
+ }
2219
+ listeners.add(listener);
2220
+ return () => {
2221
+ listeners.delete(listener);
2222
+ if (listeners.size === 0) stopClock();
2223
+ };
2224
+ }
2225
+ function getSnapshot() {
2226
+ return sharedNow;
2227
+ }
2228
+ /** 模块级当前时刻。几棵树挂着都只走一套定时器,每分钟刷新一次。 */
2229
+ function useSharedNow() {
2230
+ return (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1925
2231
  }
1926
2232
  //#endregion
1927
2233
  //#region src/client/conversation-bubbles.ts
@@ -2595,6 +2901,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2595
2901
  function hasConnectWorkspace(value) {
2596
2902
  return value !== null && typeof value === "object" && "connectWorkspace" in value && typeof value.connectWorkspace === "function";
2597
2903
  }
2904
+ function hasOpenWorkspace(value) {
2905
+ return value !== null && typeof value === "object" && "openWorkspace" in value && typeof value.openWorkspace === "function";
2906
+ }
2598
2907
  /** 与 DSH 官方最近工作区策略一致;时间相同时保留 Host 工作区顺序。 */
2599
2908
  function recentWorkspaceId(workspaces, sessions) {
2600
2909
  let selected;
@@ -2829,10 +3138,23 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2829
3138
  lower = after ? rows[index + 1]?.rect.top : edge.rect.top;
2830
3139
  } else if (target.matches(".dcu-wb-pin-start,.dcu-wb-pin-end")) {
2831
3140
  if (anchor.width <= 0 || anchor.height <= 0) return void 0;
2832
- upper = rows.filter((row) => row.rect.bottom <= anchor.top).at(-1)?.rect.bottom;
2833
- lower = rows.find((row) => row.rect.top >= anchor.bottom)?.rect.top;
2834
- if (lower === void 0) lower = anchor.bottom;
2835
- if (upper === void 0) upper = anchor.top;
3141
+ const itemRows = rows.filter((row) => !row.node.matches(".dcu-wb-section-head,.dcu-wb-empty,.dcu-wb-nochat"));
3142
+ if (target.matches(".dcu-wb-pin-end") && itemRows.length > 0) {
3143
+ const last = itemRows[itemRows.length - 1];
3144
+ anchor = last.rect;
3145
+ upper = last.rect.bottom;
3146
+ lower = rows.find((row) => row.rect.top >= last.rect.bottom - .5)?.rect.top;
3147
+ } else if (target.matches(".dcu-wb-pin-start") && itemRows.length > 0) {
3148
+ const first = itemRows[0];
3149
+ anchor = first.rect;
3150
+ lower = first.rect.top;
3151
+ upper = rows.filter((row) => row.rect.bottom <= first.rect.top + .5).at(-1)?.rect.bottom;
3152
+ } else {
3153
+ upper = rows.filter((row) => row.rect.bottom <= anchor.top).at(-1)?.rect.bottom;
3154
+ lower = rows.find((row) => row.rect.top >= anchor.bottom)?.rect.top;
3155
+ if (lower === void 0) lower = anchor.bottom;
3156
+ if (upper === void 0) upper = anchor.top;
3157
+ }
2836
3158
  } else return void 0;
2837
3159
  const center = upper === void 0 ? lower - 4 : lower === void 0 ? upper + 4 : (upper + lower) / 2;
2838
3160
  const box = root.getBoundingClientRect();
@@ -2901,7 +3223,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2901
3223
  }
2902
3224
  const previewTitle = document.createElement("span");
2903
3225
  previewTitle.className = "dcu-wb-drag-ghost-title";
2904
- previewTitle.textContent = title.split(/\r?\n/)[0] ?? title;
3226
+ previewTitle.textContent = sessionTitleLine(title);
2905
3227
  preview.appendChild(previewTitle);
2906
3228
  document.body.appendChild(preview);
2907
3229
  preview.offsetWidth;
@@ -3011,16 +3333,16 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3011
3333
  .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.dcu-wb-session-drop>.dcu-wb-project-head{border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent);outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.dcu-wb-project.dcu-wb-session-move-drop{border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent);outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.dcu-wb-project.dcu-wb-session-move-drop>.dcu-wb-project-head{background:transparent;outline:0}.dcu-wb-project-body>.dcu-wb-session:first-child,.dcu-wb-project-body>.dcu-wb-nochat:first-child{margin-top:4px}.dcu-wb-session+.dcu-wb-session{margin-top:2px}
3012
3334
  .dcu-wb-project-head[draggable=true],.dcu-wb-session[draggable=true]{cursor:grab}
3013
3335
  .dcu-wb-project-head[draggable=true]:active,.dcu-wb-session[draggable=true]:active{cursor:grabbing}
3014
- .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.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;z-index:2;left:7px;right:8px;top:-8px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,transparent 1.75px,var(--dsw-alias-state-business-primary) 2px 3.75px,transparent 4px),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-pinned-list>.dcu-wb-project:first-child.dcu-wb-drop::before{top:-4px}.dcu-wb-project.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:-8px}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:0;left:0;transform:translate(-200vw,-200vh);z-index:10040;display:flex;align-items:center;gap:8px;max-width:220px;height:30px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);box-shadow:0 4px 12px rgba(0,0,0,.24);color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font,inherit);white-space:nowrap;pointer-events:none}.dcu-wb-drag-ghost-icon{display:grid;place-items:center;flex:none;width:16px;height:16px;color:var(--dcu-sidebar-icon)}.dcu-wb-drag-ghost-icon svg{display:block;width:16px;height:16px}.dcu-wb-drag-ghost-title{min-width:0;overflow:hidden;text-overflow:ellipsis}
3336
+ .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-pinned-list{position:relative}.dcu-wb-pin-end,.dcu-wb-pin-start{position:absolute;left:0;right:0;height:8px;z-index:2;pointer-events:none}.dcu-wb-pin-end.dcu-wb-drop,.dcu-wb-pin-start.dcu-wb-drop{pointer-events:auto}.dcu-wb-pin-start{top:0}.dcu-wb-pin-end{bottom:-8px}.dcu-wb-project.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;z-index:2;left:7px;right:8px;top:-8px;height:8px;pointer-events:none;background:radial-gradient(circle at 4px 50%,transparent 1.75px,var(--dsw-alias-state-business-primary) 2px 3.75px,transparent 4px),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-pinned-list>.dcu-wb-project:first-child.dcu-wb-drop::before{top:-4px}.dcu-wb-project.dcu-wb-drop-after::before,.dcu-wb-session.dcu-wb-drop-after::before{top:auto;bottom:-8px}.dcu-wb-dragging{opacity:.28}.dcu-wb-drag-ghost{position:fixed;top:8px;left:-9999px;z-index:10040;display:flex;align-items:center;gap:8px;max-width:220px;height:30px;padding:0 10px;border:1px solid var(--dcu-sidebar-border);border-radius:8px;background:var(--dcu-sidebar-hover);box-shadow:0 4px 12px rgba(0,0,0,.24);color:var(--dcu-sidebar-primary);font:14px/20px var(--dcu-font,inherit);white-space:nowrap;pointer-events:none}.dcu-wb-drag-ghost-icon{display:grid;place-items:center;flex:none;width:16px;height:16px;color:var(--dcu-sidebar-icon)}.dcu-wb-drag-ghost-icon svg{display:block;width:16px;height:16px}.dcu-wb-drag-ghost-title{min-width:0;overflow:hidden;text-overflow:ellipsis}
3015
3337
  .dcu-wb-folder{display:grid;place-items:center;flex:none;width:16px;height:20px;color:var(--dcu-sidebar-icon)}.dcu-wb-folder.dcu-wb-folder-current{color:var(--dsw-alias-state-business-primary)}.dcu-wb-brand{display:block;width:16px;height:16px}
3016
3338
  .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}
3017
3339
  .dcu-wb-project-title{flex:1;font-weight:400;color:var(--dcu-sidebar-primary)}
3018
3340
  .dcu-wb-session{position:relative;min-width:0;min-height:30px;gap:0;overflow:hidden;padding-left:30px;padding-right:28px}
3019
- .dcu-wb-session-title{flex:1;margin-left:0}.dcu-wb-session-copy{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.dcu-wb-session-sub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:16px}.dcu-wb-session-flat,.dcu-wb-session-top{padding-left:8px}.dcu-wb-session-flat:has(.dcu-wb-running),.dcu-wb-session-top:has(.dcu-wb-running){padding-left:30px}
3341
+ .dcu-wb-session-title{flex:1;margin-left:0}.dcu-wb-session-title-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (prefers-reduced-motion:no-preference){.dcu-wb-session:hover .dcu-wb-session-title[data-overflow] .dcu-wb-session-title-text,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-session-title[data-overflow] .dcu-wb-session-title-text{display:inline-block;width:max-content;max-width:none;overflow:visible;text-overflow:clip;animation:dcu-wb-title-scroll var(--dcu-title-duration,2.4s) linear 320ms infinite alternate}}@keyframes dcu-wb-title-scroll{0%,18%{transform:translateX(0)}82%,100%{transform:translateX(calc(-1 * var(--dcu-title-shift,0px)))}}.dcu-wb-session-time{position:absolute;right:8px;top:50%;transform:translateY(-50%);color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:18px;font-weight:400;font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none}.dcu-wb-session:has(.dcu-wb-session-time){padding-right:64px}.dcu-wb-session:has(.dcu-wb-unread) .dcu-wb-session-time{right:22px}.dcu-wb-session:has(.dcu-wb-unread):has(.dcu-wb-session-time){padding-right:78px}.dcu-wb-session-copy{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.dcu-wb-session-sub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dcu-sidebar-tertiary);font-size:12px;line-height:16px}.dcu-wb-session-flat,.dcu-wb-session-top{padding-left:8px}.dcu-wb-session-flat:has(.dcu-wb-running),.dcu-wb-session-top:has(.dcu-wb-running){padding-left:30px}
3020
3342
  .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
3021
3343
  .dcu-wb-quick-actions{position:absolute;right:8px;top:50%;display:flex;align-items:center;gap:2px;opacity:0;pointer-events:none;transform:translateY(-50%);background:var(--dcu-sidebar-hover);border-radius:6px}
3022
3344
  .dcu-wb-project-head:hover .dcu-wb-actions,.dcu-wb-project-head.dcu-wb-menu-open .dcu-wb-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-actions{display:flex}
3023
- .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{opacity:1;pointer-events:auto}.dcu-wb-session:hover .dcu-wb-pending,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-pending,.dcu-wb-session:hover .dcu-wb-unread,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-unread{visibility:hidden}
3345
+ .dcu-wb-session:hover .dcu-wb-quick-actions,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-quick-actions{opacity:1;pointer-events:auto}.dcu-wb-session:hover .dcu-wb-pending,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-pending,.dcu-wb-session:hover .dcu-wb-unread,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-unread,.dcu-wb-session:hover .dcu-wb-session-time,.dcu-wb-session.dcu-wb-menu-open .dcu-wb-session-time{visibility:hidden}.dcu-wb-session:has(.dcu-wb-pending) .dcu-wb-session-time{display:none}
3024
3346
  .dcu-wb-more{display:grid;place-items:center;width:20px;height:20px;border:0;border-radius:4px;padding:0;background:transparent;color:var(--dcu-sidebar-tertiary);cursor:pointer}
3025
3347
  .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
3026
3348
  .dcu-wb-context-anchor{opacity:0;pointer-events:none}
@@ -3047,7 +3369,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3047
3369
  .dcu-wb-drop-indicator::before{content:"";position:absolute;left:6px;right:0;top:3px;height:2px;border-radius:999px;background:currentColor}
3048
3370
  .dcu-wb-drop-indicator::after{content:"";position:absolute;box-sizing:border-box;left:0;top:0;width:8px;height:8px;border:2px solid currentColor;border-radius:50%}
3049
3371
  .dcu-wb-project-body:has(>.dcu-wb-session)::after{content:"";display:block;height:4px;pointer-events:none}
3050
- .dcu-wb-section-body[data-open=true]:has(.dcu-wb-group-order-drop){overflow:visible}
3372
+ .dcu-wb-section-body[data-open=true]:has(.dcu-wb-group-order-drop),.dcu-wb-section-body[data-open=true]:has(.dcu-wb-pin-end),.dcu-wb-section-body[data-open=true]:has(.dcu-wb-pin-start){overflow:visible}
3051
3373
  .dcu-wb-collection-body{padding-left:0}
3052
3374
  .dcu-wb-collection-body>.dcu-wb-group-member:first-child{padding-top:4px}
3053
3375
  .dcu-wb-collection-body .dcu-wb-project-head{padding-left:8px}
@@ -3089,9 +3411,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3089
3411
  function CodexWorkspaceBrowser(props) {
3090
3412
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverShell, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexWorkspaceTree, { ...props }) });
3091
3413
  }
3092
- function CodexWorkspaceTree({ wide, useSessions, useSessionPendingInteraction, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, moveSession, openPath, openSession, renameSession, renameWorkspace, startSession, canDeleteSession }) {
3414
+ function CodexWorkspaceTree({ wide, useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, moveSession, openPath, openSession, renameSession, renameWorkspace, startSession, canDeleteSession, panelActive = false }) {
3093
3415
  const sessions = useSessions((state) => state);
3094
- const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
3416
+ const now = useSharedNow();
3417
+ const pendingInteractions = useHostPendingInteractions(useSessionPendingInteraction, useSessionStatus);
3418
+ const sessionStatus = useHostSessionStatus(useSessionStatus);
3419
+ const selectedId = visibleSelectedSessionId(sessions, panelActive);
3095
3420
  const workspaces = useWorkspaces((state) => state);
3096
3421
  const baselinesReady = workspaceBaselinesReady(workspaces, sessions);
3097
3422
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(storage$1(), WORKSPACE_EXPANSION_STORAGE_KEY));
@@ -3153,6 +3478,8 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3153
3478
  setWorkspaceGroupsState(next);
3154
3479
  };
3155
3480
  const [unreadSessionIds, setUnreadSessionIds] = (0, react.useState)(() => readSessionIds(storage$1(), SESSION_UNREAD_STORAGE_KEY));
3481
+ const rowUnread = (id) => sessionRowUnread(unreadSessionIds.includes(id), sessionStatus.get(id), selectedId === id);
3482
+ const rowRunning = (id, session) => sessionIsRunning(session ?? sessions.byId[id], sessionStatus.get(id));
3156
3483
  const previousSessionRunningRef = (0, react.useRef)();
3157
3484
  const [menu, setMenu] = (0, react.useState)();
3158
3485
  const [renameTarget, setRenameTarget] = (0, react.useState)();
@@ -3272,17 +3599,20 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3272
3599
  }
3273
3600
  }, [baselinesReady, workspaces.items]);
3274
3601
  (0, react.useEffect)(() => {
3275
- const current = sessions.current;
3276
- if (current !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== current));
3277
- }, [sessions.current]);
3602
+ if (selectedId !== void 0) setUnreadSessionIds((ids) => ids.filter((id) => id !== selectedId));
3603
+ }, [selectedId]);
3278
3604
  (0, react.useEffect)(() => {
3279
- const next = Object.fromEntries(Object.entries(sessions.byId).map(([id, session]) => [id, session.running === true]));
3605
+ const next = sessionRunningFlags(sessions.byId, sessionStatus);
3280
3606
  const previous = previousSessionRunningRef.current;
3281
3607
  previousSessionRunningRef.current = next;
3282
3608
  if (previous === void 0) return;
3283
- const completed = completedBackgroundSessionIds(previous, next, sessions.current);
3609
+ const completed = completedBackgroundSessionIds(previous, next, selectedId);
3284
3610
  if (completed.length > 0) setUnreadSessionIds((ids) => [...completed, ...ids.filter((id) => !completed.includes(id))]);
3285
- }, [sessions.byId, sessions.current]);
3611
+ }, [
3612
+ selectedId,
3613
+ sessionStatus,
3614
+ sessions.byId
3615
+ ]);
3286
3616
  const groups = (0, react.useMemo)(() => {
3287
3617
  const archived = workspaces.archivedSessionIds;
3288
3618
  const visible = (ids) => visibleSessionIds(ids, sessions.byId, archived);
@@ -3313,7 +3643,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3313
3643
  (0, react.useEffect)(() => {
3314
3644
  const onProject = (event) => {
3315
3645
  const detail = event.detail;
3316
- const current = sessions.current;
3646
+ const current = selectedId;
3317
3647
  const workspace = groups.items.find((item) => current !== void 0 && item.visibleIds.includes(String(current)));
3318
3648
  if (workspace === void 0 || detail === void 0) return;
3319
3649
  if (detail.toggle === true && isShowing("workspace", workspace.workspaceId)) {
@@ -3326,7 +3656,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3326
3656
  title: workspace.title,
3327
3657
  path: workspace.path,
3328
3658
  count: workspace.visibleIds.length,
3329
- unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
3659
+ unreadCount: workspace.visibleIds.filter((id) => rowUnread(String(id))).length,
3330
3660
  pinned: pinnedWorkspaceIds.includes(String(workspace.workspaceId)),
3331
3661
  left: detail.left,
3332
3662
  top: detail.top
@@ -3334,7 +3664,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3334
3664
  };
3335
3665
  const onMenu = (event) => {
3336
3666
  const detail = event.detail;
3337
- const current = sessions.current;
3667
+ const current = selectedId;
3338
3668
  if (current === void 0 || detail === void 0) return;
3339
3669
  dismissTip();
3340
3670
  if (performance.now() - headerMenuPointerAt.current < 500) {
@@ -3355,9 +3685,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3355
3685
  };
3356
3686
  }, [
3357
3687
  groups.items,
3358
- sessions.current,
3359
3688
  menu,
3360
3689
  pinnedWorkspaceIds,
3690
+ selectedId,
3361
3691
  unreadSessionIds
3362
3692
  ]);
3363
3693
  const projectPinned = (id) => pinnedWorkspaceIds.includes(String(id));
@@ -3586,7 +3916,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3586
3916
  };
3587
3917
  const canDelete = canDeleteSession?.() === true;
3588
3918
  const sessionMenu = (sessionId, path) => sessionMenuItems(t, {
3589
- unread: unreadSessionIds.includes(sessionId),
3919
+ unread: rowUnread(sessionId),
3590
3920
  path,
3591
3921
  includePath: true,
3592
3922
  moveTargets: sessionMoveTargets(groups.items, sessionId).map((target) => ({
@@ -3641,7 +3971,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3641
3971
  const renderGroup = (workspace, zone) => {
3642
3972
  const expandKey = zone === "pinned" ? `pin:${workspace.workspaceId}` : String(workspace.workspaceId);
3643
3973
  const isExpanded = expanded[expandKey] ?? true;
3644
- const currentSessionId = sessions.current === void 0 ? void 0 : String(sessions.current);
3974
+ const currentSessionId = selectedId;
3645
3975
  const folder = projectFolderPresentation(isExpanded, workspace.visibleIds.some((id) => id === currentSessionId));
3646
3976
  const shownIds = workspace.visibleIds;
3647
3977
  const menuOpen = menu?.type === "workspace" && menu.id === workspace.workspaceId;
@@ -3769,7 +4099,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3769
4099
  title: workspace.title,
3770
4100
  path: workspace.path,
3771
4101
  count: workspace.visibleIds.length,
3772
- unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
4102
+ unreadCount: workspace.visibleIds.filter((id) => rowUnread(String(id))).length,
3773
4103
  pinned: projectPinned(workspace.workspaceId),
3774
4104
  left: box.left,
3775
4105
  top: box.top
@@ -3798,7 +4128,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3798
4128
  title: workspace.title,
3799
4129
  path: workspace.path,
3800
4130
  count: workspace.visibleIds.length,
3801
- unreadCount: workspace.visibleIds.filter((id) => unreadSessionIds.includes(String(id))).length,
4131
+ unreadCount: workspace.visibleIds.filter((id) => rowUnread(String(id))).length,
3802
4132
  pinned: projectPinned(workspace.workspaceId),
3803
4133
  left: box.left,
3804
4134
  top: box.top
@@ -3866,7 +4196,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3866
4196
  if (session === void 0) return null;
3867
4197
  const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
3868
4198
  const path = session.cwd ?? workspace.path;
3869
- const selected = sessions.current === id;
4199
+ const selected = selectedId === id;
3870
4200
  const sessionMenuOpen = menu?.type === "session" && menu.id === id;
3871
4201
  const sessionMenuAt = sessionMenuOpen && menu.x !== void 0 && menu.y !== void 0 ? {
3872
4202
  x: menu.x,
@@ -3951,23 +4281,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3951
4281
  project: workspace.title,
3952
4282
  path,
3953
4283
  branch,
3954
- time: formatHoverTime(session.updatedAt, t),
4284
+ time: formatHoverTime(session.updatedAt, t, now),
3955
4285
  left: box.left,
3956
4286
  top: box.top
3957
4287
  });
3958
4288
  },
3959
4289
  onMouseLeave: hideTip,
3960
4290
  children: [
3961
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3962
- className: "dcu-wb-session-title",
3963
- children: session.displayTitle.split(/\r?\n/)[0] ?? session.displayTitle
3964
- }),
4291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title: session.displayTitle }),
3965
4292
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
3966
4293
  pendingInteraction,
3967
- unread: unreadSessionIds.includes(id),
3968
- running: session.running === true,
4294
+ unread: rowUnread(id),
4295
+ running: rowRunning(id, session),
3969
4296
  t
3970
4297
  }),
4298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionTime, { time: formatCompactTime(session.updatedAt, t, now) }),
3971
4299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3972
4300
  className: "dcu-wb-quick-actions",
3973
4301
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4423,7 +4751,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4423
4751
  return mountWorkspaceDropIndicator(treeRef.current);
4424
4752
  }, [wide, sorting]);
4425
4753
  (0, react.useEffect)(() => {
4426
- const current = sessions.current === void 0 ? void 0 : String(sessions.current);
4754
+ const current = selectedId;
4427
4755
  if (current === void 0 || lastRevealedSessionIdRef.current === current) return;
4428
4756
  const tree = {
4429
4757
  workspaces: workspaces.items.map((item) => ({
@@ -4444,7 +4772,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4444
4772
  }, [
4445
4773
  pinnedWorkspaceIds,
4446
4774
  recentIds,
4447
- sessions.current,
4775
+ selectedId,
4448
4776
  workspaceGroups,
4449
4777
  workspaces.items
4450
4778
  ]);
@@ -4714,16 +5042,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4714
5042
  const session = sessions.byId[id];
4715
5043
  if (session === void 0) return null;
4716
5044
  const title = session.displayTitle;
4717
- const unread = unreadSessionIds.includes(id);
5045
+ const unread = rowUnread(id);
4718
5046
  const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, legacyPendingInteraction(session));
4719
5047
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
4720
5048
  id,
4721
5049
  title,
4722
- selected: sessions.current === id,
5050
+ selected: selectedId === id,
4723
5051
  menuOpen: menu?.type === "session" && menu.id === id,
4724
5052
  unread,
4725
- running: session.running === true,
5053
+ running: rowRunning(id, session),
4726
5054
  pendingInteraction,
5055
+ time: formatCompactTime(session.updatedAt, t, now),
4727
5056
  t,
4728
5057
  menuItems: sessionMenu(id, session.cwd),
4729
5058
  draggable: true,
@@ -4769,7 +5098,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4769
5098
  kind: "session",
4770
5099
  id,
4771
5100
  title,
4772
- time: formatHoverTime(session.updatedAt, t),
5101
+ time: formatHoverTime(session.updatedAt, t, now),
4773
5102
  left: box.left,
4774
5103
  top: box.top
4775
5104
  });
@@ -5416,14 +5745,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5416
5745
  })
5417
5746
  });
5418
5747
  }
5419
- function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, canDeleteSession, menu, setMenu }) {
5748
+ function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces, t, canDeleteSession, panelActive = false, menu, setMenu }) {
5420
5749
  const sessions = useSessions((state) => state);
5750
+ const now = useSharedNow();
5421
5751
  const workspaces = useWorkspaces?.((state) => state);
5422
- const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
5752
+ const pendingInteractions = useHostPendingInteractions(useSessionPendingInteraction, useSessionStatus);
5753
+ const sessionStatus = useHostSessionStatus(useSessionStatus);
5754
+ const selectedId = visibleSelectedSessionId(sessions, panelActive);
5423
5755
  const [groups, setGroups] = (0, react.useState)([]);
5424
5756
  const [pollError, setPollError] = (0, react.useState)();
5425
5757
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), CHANNEL_EXPANSION_STORAGE_KEY));
5426
- const flags = useSessionFlags(sessions.current);
5758
+ const flags = useSessionFlags(selectedId);
5427
5759
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
5428
5760
  const { busy, error, setError, run } = useBusyAction(t, () => {
5429
5761
  setMenu(void 0);
@@ -5519,10 +5851,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5519
5851
  children: group.sessions.map((session) => {
5520
5852
  const id = session.sessionId;
5521
5853
  const title = session.title;
5522
- const selected = sessions.current === id;
5523
- const running = session.running || sessions.byId[id]?.running === true;
5854
+ const selected = selectedId === id;
5855
+ const status = sessionStatus.get(id);
5856
+ const running = sessionIsRunning({ running: session.running === true || sessions.byId[id]?.running === true }, status);
5524
5857
  const updatedAt = session.updatedAt ?? sessions.byId[id]?.updatedAt;
5525
- const unread = flags.unreadSessionIds.includes(id);
5858
+ const unread = sessionRowUnread(flags.unreadSessionIds.includes(id), status, selected);
5526
5859
  const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, sessions.byId[id]?.pendingInteraction);
5527
5860
  const moveTargets = moveSession === void 0 || workspaces === void 0 ? void 0 : sessionMoveTargets(workspaces.items, id).map((target) => ({
5528
5861
  ...target,
@@ -5537,6 +5870,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5537
5870
  unread,
5538
5871
  running,
5539
5872
  pendingInteraction,
5873
+ time: updatedAt === void 0 ? void 0 : formatCompactTime(updatedAt, t, now),
5540
5874
  t,
5541
5875
  menuItems: sessionMenuItems(t, {
5542
5876
  unread,
@@ -5562,7 +5896,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5562
5896
  showTip({
5563
5897
  title,
5564
5898
  project: label,
5565
- time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t),
5899
+ time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t, now),
5566
5900
  left: box.left,
5567
5901
  top: box.top
5568
5902
  });
@@ -5690,14 +6024,17 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5690
6024
  })]
5691
6025
  });
5692
6026
  }
5693
- function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, canDeleteSession, openTaskSettings, menu, setMenu }) {
6027
+ function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces, t, canDeleteSession, panelActive = false, openTaskSettings, menu, setMenu }) {
5694
6028
  const sessions = useSessions((state) => state);
5695
- const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
6029
+ const now = useSharedNow();
6030
+ const pendingInteractions = useHostPendingInteractions(useSessionPendingInteraction, useSessionStatus);
6031
+ const sessionStatus = useHostSessionStatus(useSessionStatus);
6032
+ const selectedId = visibleSelectedSessionId(sessions, panelActive);
5696
6033
  const workspaces = useWorkspaces((state) => state);
5697
6034
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), SCHEDULE_EXPANSION_STORAGE_KEY));
5698
6035
  const [groupMenu, setGroupMenu] = (0, react.useState)();
5699
6036
  const [archiveGroupTarget, setArchiveGroupTarget] = (0, react.useState)();
5700
- const flags = useSessionFlags(sessions.current);
6037
+ const flags = useSessionFlags(selectedId);
5701
6038
  const { showTip, hideTip, dismissTip } = useHoverDispatch();
5702
6039
  const { busy, error, setError, run } = useBusyAction(t, () => {
5703
6040
  setMenu(void 0);
@@ -5826,7 +6163,9 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5826
6163
  children: group.sessions.map((session) => {
5827
6164
  const id = session.id;
5828
6165
  const title = session.title;
5829
- const unread = flags.unreadSessionIds.includes(id);
6166
+ const selected = selectedId === id;
6167
+ const status = sessionStatus.get(id);
6168
+ const unread = sessionRowUnread(flags.unreadSessionIds.includes(id), status, selected);
5830
6169
  const pendingInteraction = pendingInteractionForSession(id, pendingInteractions, sessions.byId[id]?.pendingInteraction);
5831
6170
  const moveTargets = moveSession === void 0 ? void 0 : sessionMoveTargets(workspaces.items ?? [], id).map((target) => ({
5832
6171
  ...target,
@@ -5836,11 +6175,12 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5836
6175
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
5837
6176
  id,
5838
6177
  title,
5839
- selected: sessions.current === id,
6178
+ selected,
5840
6179
  menuOpen: menu?.id === id,
5841
6180
  unread,
5842
- running: session.running,
6181
+ running: sessionIsRunning(sessions.byId[id], status),
5843
6182
  pendingInteraction,
6183
+ time: session.updatedAt === void 0 ? void 0 : formatCompactTime(session.updatedAt, t, now),
5844
6184
  t,
5845
6185
  menuItems: sessionMenuItems(t, {
5846
6186
  unread,
@@ -5866,7 +6206,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5866
6206
  showTip({
5867
6207
  title,
5868
6208
  project: group.label,
5869
- time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t),
6209
+ time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t, now),
5870
6210
  left: box.left,
5871
6211
  top: box.top
5872
6212
  });
@@ -6602,7 +6942,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6602
6942
  });
6603
6943
  }
6604
6944
  /** 搜索状态与大侧栏隔离:输入、悬停和开关弹窗都不能让工作区树跟着重渲染。 */
6605
- const SidebarSearch = (0, react.forwardRef)(function SidebarSearch({ imSettingsAvailable, openSession, startSession, t, useSessions, useWorkspaces, settingsSeat }, ref) {
6945
+ const SidebarSearch = (0, react.forwardRef)(function SidebarSearch({ imSettingsAvailable, openPlugins, openSession, startSession, t, useSessions, useWorkspaces, settingsSeat }, ref) {
6606
6946
  const [open, setOpen] = (0, react.useState)(false);
6607
6947
  const [query, setQuery] = (0, react.useState)("");
6608
6948
  const [activeIndex, setActiveIndex] = (0, react.useState)(0);
@@ -6620,9 +6960,6 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6620
6960
  const selectSection = (0, react.useCallback)((label) => {
6621
6961
  openSettingsSection(settingsSeat.current, label);
6622
6962
  }, [settingsSeat]);
6623
- const selectPluginSection = (0, react.useCallback)(() => {
6624
- openSettingsSection(settingsSeat.current, [t("sidebar.marketplace"), t("sidebar.plugins")]);
6625
- }, [settingsSeat, t]);
6626
6963
  const selectExternalSection = (0, react.useCallback)((label) => {
6627
6964
  openSettingsSection(settingsSeat.current, label, () => {
6628
6965
  selectSection(t("about.nav"));
@@ -6699,7 +7036,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6699
7036
  keywords: t("search.settings"),
6700
7037
  run: () => {
6701
7038
  close();
6702
- selectPluginSection();
7039
+ openPlugins();
7040
+ }
7041
+ },
7042
+ {
7043
+ id: "settings:plugin-config",
7044
+ group: "settings",
7045
+ label: t("settings.pluginConfig"),
7046
+ keywords: t("search.settings"),
7047
+ run: () => {
7048
+ close();
7049
+ openSettingsSection(settingsSeat.current, t("settings.pluginConfig"));
6703
7050
  }
6704
7051
  },
6705
7052
  {
@@ -6760,10 +7107,10 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6760
7107
  }, [
6761
7108
  close,
6762
7109
  openImSettings,
7110
+ openPlugins,
6763
7111
  openSession,
6764
7112
  openSettings,
6765
7113
  selectExternalSection,
6766
- selectPluginSection,
6767
7114
  selectSection,
6768
7115
  sessions.byId,
6769
7116
  sessions.ids,
@@ -6874,10 +7221,11 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6874
7221
  });
6875
7222
  });
6876
7223
  /** Codex 风格的 DSH 侧栏,只替换导航外观,项目浏览和设置仍由 DSH 官方组件提供。 */
6877
- function CodexSidebar({ globalPanels, footerActions, selectPanel, usePanelInfo = useLegacyPanelInfo, collapsed, width, openSession, startSession, toggleSidebar, archiveSession, canDeleteSession, deleteSession, forkSession, moveSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useWorkspaces, prefillNewConversation, newConversationDraft }) {
7224
+ function CodexSidebar({ globalPanels, footerActions, selectPanel, usePanelInfo = useLegacyPanelInfo, collapsed, width, openSession, startSession, toggleSidebar, archiveSession, canDeleteSession, deleteSession, forkSession, moveSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useSessionStatus, useWorkspaces, prefillNewConversation, newConversationDraft }) {
6878
7225
  const panels = (0, react.useSyncExternalStore)(globalPanels?.subscribe ?? subscribeEmptyCompanionTabs, globalPanels?.getSnapshot ?? getEmptyPanels, globalPanels?.getSnapshot ?? getEmptyPanels);
6879
7226
  const visibleFooterActions = (0, react.useSyncExternalStore)(footerActions?.subscribe ?? subscribeEmptyCompanionTabs, footerActions?.getSnapshot ?? getEmptyFooterActions, footerActions?.getSnapshot ?? getEmptyFooterActions);
6880
7227
  const activePanelId = usePanelInfo((info) => info.activePanelId);
7228
+ const panelActive = activePanelId !== null && activePanelId !== "";
6881
7229
  const panelButtons = (wide) => selectPanel === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalPanelButtons, {
6882
7230
  panels,
6883
7231
  activeId: activePanelId,
@@ -6908,8 +7256,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6908
7256
  const selectSection = (label) => {
6909
7257
  openSettingsSection(settingsSeat.current, label);
6910
7258
  };
6911
- const selectPluginSection = () => {
6912
- openSettingsSection(settingsSeat.current, [t("sidebar.marketplace"), t("sidebar.plugins")]);
7259
+ const openPlugins = () => {
7260
+ openSettingsSection(settingsSeat.current, [t("sidebar.marketplace"), t("sidebar.builtinPlugins")]);
6913
7261
  };
6914
7262
  const selectExternalSection = (label) => {
6915
7263
  openSettingsSection(settingsSeat.current, label, () => {
@@ -7028,10 +7376,17 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7028
7376
  window.clearTimeout(timer);
7029
7377
  };
7030
7378
  }, [compact, visualCompact]);
7031
- const workspaceSlot = (0, react.useMemo)(() => renderSlot("sidebar.workspaces", {
7032
- wide: true,
7033
- expandSidebar
7034
- }), [expandSidebar, renderSlot]);
7379
+ const workspaceSlot = (0, react.useMemo)(() => {
7380
+ return renderSlot("sidebar.workspaces", {
7381
+ wide: true,
7382
+ expandSidebar,
7383
+ panelActive
7384
+ });
7385
+ }, [
7386
+ expandSidebar,
7387
+ panelActive,
7388
+ renderSlot
7389
+ ]);
7035
7390
  const scheduleOverviewSlot = (0, react.useMemo)(() => renderSlot("sidebar.schedule", {
7036
7391
  wide: true,
7037
7392
  expandSidebar,
@@ -7167,7 +7522,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7167
7522
  type: "button",
7168
7523
  tabIndex: extensionsOpen ? 0 : -1,
7169
7524
  onClick: () => {
7170
- selectPluginSection();
7525
+ openPlugins();
7171
7526
  },
7172
7527
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuIcon, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, { size: 16 }) }), t("sidebar.plugins")]
7173
7528
  }),
@@ -7243,7 +7598,9 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7243
7598
  renameSession,
7244
7599
  useSessions,
7245
7600
  useSessionPendingInteraction,
7601
+ useSessionStatus,
7246
7602
  useWorkspaces,
7603
+ panelActive,
7247
7604
  t
7248
7605
  })
7249
7606
  }) : imTab === "schedule" && showSchedule ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -7258,7 +7615,9 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7258
7615
  renameSession,
7259
7616
  useSessions,
7260
7617
  useSessionPendingInteraction,
7618
+ useSessionStatus,
7261
7619
  useWorkspaces,
7620
+ panelActive,
7262
7621
  t,
7263
7622
  overviewContent: scheduleOverviewSlot,
7264
7623
  openTaskSettings: (request) => {
@@ -7331,7 +7690,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7331
7690
  className: "dcu-icon",
7332
7691
  "aria-label": t("sidebar.plugins"),
7333
7692
  onClick: () => {
7334
- selectPluginSection();
7693
+ openPlugins();
7335
7694
  },
7336
7695
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, { size: 16 })
7337
7696
  }),
@@ -7377,6 +7736,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7377
7736
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSearch, {
7378
7737
  ref: search,
7379
7738
  imSettingsAvailable: showChannels,
7739
+ openPlugins,
7380
7740
  settingsSeat,
7381
7741
  openSession,
7382
7742
  startSession,
@@ -7916,7 +8276,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7916
8276
  });
7917
8277
  }
7918
8278
  function NativeConnectorList({ sessionStore, t }) {
7919
- const sessionId = (0, react.useSyncExternalStore)(sessionStore.subscribe, () => sessionStore.getSnapshot().current);
8279
+ const sessionId = (0, react.useSyncExternalStore)(sessionStore.subscribe, () => currentSessionId(sessionStore.getSnapshot()));
7920
8280
  const [connectors, setConnectors] = (0, react.useState)([]);
7921
8281
  const [state, setState] = (0, react.useState)("loading");
7922
8282
  const [failure, setFailure] = (0, react.useState)();
@@ -8071,6 +8431,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8071
8431
  "settings.search": "搜索设置…",
8072
8432
  "settings.personal": "个人",
8073
8433
  "settings.integrations": "集成",
8434
+ "settings.pluginConfig": "插件配置",
8074
8435
  "settings.records": "记录与应用",
8075
8436
  "settings.general": "常规",
8076
8437
  "settings.permissions": "权限",
@@ -8093,6 +8454,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8093
8454
  "sidebar.skills": "技能",
8094
8455
  "sidebar.plugins": "插件",
8095
8456
  "sidebar.marketplace": "插件市场",
8457
+ "sidebar.builtinPlugins": "内置插件",
8096
8458
  "sidebar.connectors": "连接器",
8097
8459
  "sidebar.schedule": "定时任务",
8098
8460
  "sidebar.assistant": "IM助理",
@@ -8284,9 +8646,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8284
8646
  "turns.untitled": "未命名提问",
8285
8647
  "turns.jump": "跳转到第 {index} 轮:{summary}",
8286
8648
  "time.justNow": "刚刚",
8649
+ "time.justNowShort": "刚刚",
8287
8650
  "time.minutes": "{count}分",
8288
8651
  "time.hours": "{count}小时",
8289
8652
  "time.days": "{count}天",
8653
+ "time.weeks": "{count}周",
8654
+ "time.months": "{count}个月",
8655
+ "time.years": "{count}年",
8290
8656
  "errors.unauthorized": "请先登录 DSH,再重试。",
8291
8657
  "errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
8292
8658
  "errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
@@ -8355,6 +8721,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8355
8721
  "settings.search": "Search settings…",
8356
8722
  "settings.personal": "Personal",
8357
8723
  "settings.integrations": "Integrations",
8724
+ "settings.pluginConfig": "Plugin configuration",
8358
8725
  "settings.records": "Records and app",
8359
8726
  "settings.general": "General",
8360
8727
  "settings.permissions": "Permissions",
@@ -8377,6 +8744,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8377
8744
  "sidebar.skills": "Skills",
8378
8745
  "sidebar.plugins": "Plugins",
8379
8746
  "sidebar.marketplace": "Plugin Market",
8747
+ "sidebar.builtinPlugins": "Built-in plugins",
8380
8748
  "sidebar.connectors": "Connectors",
8381
8749
  "sidebar.schedule": "Scheduled tasks",
8382
8750
  "sidebar.assistant": "IM Assistant",
@@ -8568,9 +8936,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8568
8936
  "turns.untitled": "Untitled prompt",
8569
8937
  "turns.jump": "Jump to turn {index}: {summary}",
8570
8938
  "time.justNow": "Just now",
8939
+ "time.justNowShort": "now",
8571
8940
  "time.minutes": "{count}m",
8572
8941
  "time.hours": "{count}h",
8573
8942
  "time.days": "{count}d",
8943
+ "time.weeks": "{count}w",
8944
+ "time.months": "{count}mo",
8945
+ "time.years": "{count}y",
8574
8946
  "errors.unauthorized": "Sign in to DSH, then try again.",
8575
8947
  "errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
8576
8948
  "errors.serviceUnavailable": "The service is unavailable. Try again later.",
@@ -8665,6 +9037,8 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8665
9037
  };
8666
9038
  const LABEL_ALIASES = [
8667
9039
  [["插件市场", "Plugin Market"], "plugins"],
9040
+ [["内置插件", "Built-in plugins"], "plugins"],
9041
+ [["插件配置", "Plugin configuration"], "plugins"],
8668
9042
  [["专家", "Experts"], "experts"],
8669
9043
  [["技能", "Skills"], "skills"],
8670
9044
  [["插件", "Plugins"], "plugins"],
@@ -8966,6 +9340,160 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8966
9340
  }, "michengai-codex-ui: usage navigation");
8967
9341
  }
8968
9342
  //#endregion
9343
+ //#region src/client/PluginConfigSection.tsx
9344
+ function bindPluginConfigLocale(bind) {
9345
+ const cache = /* @__PURE__ */ new Map();
9346
+ return (ns) => {
9347
+ const existing = cache.get(ns);
9348
+ if (existing !== void 0) return existing;
9349
+ const translate = bind(ns);
9350
+ cache.set(ns, translate);
9351
+ return translate;
9352
+ };
9353
+ }
9354
+ const FORWARDED_CONFIG_SLOTS = /* @__PURE__ */ new Set([
9355
+ "plugins.item",
9356
+ "plugins.bundle.config",
9357
+ "plugins.row.config"
9358
+ ]);
9359
+ var SlotEntryBoundary = class extends react.Component {
9360
+ state = { failed: false };
9361
+ static getDerivedStateFromError() {
9362
+ return { failed: true };
9363
+ }
9364
+ render() {
9365
+ return this.state.failed ? this.props.fallback : this.props.children;
9366
+ }
9367
+ };
9368
+ function isSnapshotSource(value) {
9369
+ return typeof value === "object" && value !== null && typeof value.getSnapshot === "function" && typeof value.subscribe === "function";
9370
+ }
9371
+ function hookPropName(name) {
9372
+ return `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
9373
+ }
9374
+ function bindSnapshotSelector(source) {
9375
+ const subscribe = (listener) => source.subscribe(listener);
9376
+ const getSnapshot = () => source.getSnapshot();
9377
+ return function useSelected(selector = (value) => value) {
9378
+ return (0, react.useSyncExternalStore)(subscribe, () => selector(getSnapshot()), () => selector(getSnapshot()));
9379
+ };
9380
+ }
9381
+ function bindHookSources(hooks, bound) {
9382
+ for (const [name, source] of Object.entries(hooks ?? {})) if (isSnapshotSource(source)) bound[hookPropName(name)] = bindSnapshotSelector(source);
9383
+ }
9384
+ function bindInjectFace(inject) {
9385
+ try {
9386
+ if (typeof inject !== "function") return {};
9387
+ const face = inject();
9388
+ if (typeof face !== "object" || face === null) return {};
9389
+ const { hooks, keyedHooks, ...rest } = face;
9390
+ const bound = { ...rest };
9391
+ if (keyedHooks !== void 0) bound.keyedHooks = keyedHooks;
9392
+ bindHookSources(hooks, bound);
9393
+ bindHookSources(keyedHooks, bound);
9394
+ return bound;
9395
+ } catch {
9396
+ return {};
9397
+ }
9398
+ }
9399
+ function DelegatedOfficialSlot({ slots, name, owner, opts, bindLocale }) {
9400
+ (0, react.useSyncExternalStore)((listener) => slots.subscribe(name, listener), () => slots.getVersion(name));
9401
+ const nodes = slots.entriesOfSlot(name).filter((entry) => opts?.entryKey !== void 0 ? entry.options.key === opts.entryKey : opts?.only === void 0 || entry.options.id === opts.only).flatMap((entry, index) => {
9402
+ if (typeof entry.component !== "function") return [];
9403
+ const t = entry.locale !== void 0 && bindLocale !== void 0 ? bindLocale(entry.locale) : void 0;
9404
+ return [(0, react.createElement)(SlotEntryBoundary, {
9405
+ key: entry.options.key ?? entry.options.id ?? String(index),
9406
+ fallback: opts?.fallback ?? null,
9407
+ children: (0, react.createElement)(entry.component, {
9408
+ ...owner,
9409
+ ...bindInjectFace(entry.inject),
9410
+ ...t === void 0 ? {} : { t }
9411
+ })
9412
+ })];
9413
+ });
9414
+ return nodes.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9415
+ "data-slot": name,
9416
+ style: { display: "contents" },
9417
+ children: nodes
9418
+ }) : opts?.fallback ?? null;
9419
+ }
9420
+ function wrapRenderSlot(settingsRenderSlot, slots, bindLocale) {
9421
+ return (name, owner = {}, opts) => {
9422
+ if (slots !== void 0 && FORWARDED_CONFIG_SLOTS.has(name)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DelegatedOfficialSlot, {
9423
+ slots,
9424
+ name,
9425
+ owner,
9426
+ opts,
9427
+ bindLocale
9428
+ });
9429
+ return typeof settingsRenderSlot === "function" ? settingsRenderSlot(name, owner, opts) : null;
9430
+ };
9431
+ }
9432
+ /** 设置里的官方插件管理页副本。官方卡片要 inject 和文案;单卡崩溃不得拆掉整页。 */
9433
+ function PluginConfigSection({ Official, officialLabel, slots, bindLocale, ...official }) {
9434
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
9435
+ className: "dcu-plugin-config",
9436
+ "aria-label": officialLabel,
9437
+ children: (0, react.createElement)(Official, {
9438
+ ...official,
9439
+ renderSlot: wrapRenderSlot(official.renderSlot, slots, bindLocale)
9440
+ })
9441
+ });
9442
+ }
9443
+ function createPluginConfigSection(Official, officialLabel, slots, bindLocale) {
9444
+ return function PluginConfigSectionBound(props) {
9445
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginConfigSection, {
9446
+ Official,
9447
+ officialLabel: officialLabel(),
9448
+ slots,
9449
+ bindLocale,
9450
+ ...props
9451
+ });
9452
+ };
9453
+ }
9454
+ //#endregion
9455
+ //#region src/client/plugin-config.ts
9456
+ const PLUGIN_CONFIG_SECTION_ID = "plugin-config";
9457
+ const OFFICIAL_PLUGIN_MANAGER_NS = "pluginManager";
9458
+ function officialPluginsPage(entry) {
9459
+ if (entry === void 0 || entry.options.key !== "plugins") return void 0;
9460
+ if (typeof entry.component !== "function" || typeof entry.inject !== "function" || entry.locale !== OFFICIAL_PLUGIN_MANAGER_NS) return void 0;
9461
+ return entry.component;
9462
+ }
9463
+ function registerPluginConfigSection(ctx) {
9464
+ const t = ctx.locale.bind(NS);
9465
+ ctx.slots.inject("settings.section", () => {
9466
+ let current;
9467
+ let remove;
9468
+ const refresh = () => {
9469
+ const entry = ctx.slots.entriesOfSlot("main").find((item) => item.options.key === OFFICIAL_PLUGINS_PANEL_ID);
9470
+ if (entry === current) return;
9471
+ remove?.();
9472
+ remove = void 0;
9473
+ current = entry;
9474
+ const Official = officialPluginsPage(entry);
9475
+ if (Official === void 0 || entry?.inject === void 0 || entry.locale !== OFFICIAL_PLUGIN_MANAGER_NS) return;
9476
+ remove = ctx.slots.register({
9477
+ name: "settings.section",
9478
+ id: PLUGIN_CONFIG_SECTION_ID,
9479
+ order: 16,
9480
+ label: () => t("settings.pluginConfig"),
9481
+ locale: entry.locale,
9482
+ inject: entry.inject
9483
+ }, createPluginConfigSection(Official, () => t("settings.pluginConfig"), ctx.slots, bindPluginConfigLocale((ns) => {
9484
+ const translate = ctx.locale.bind(ns);
9485
+ return (key, params) => translate(key, params);
9486
+ })));
9487
+ };
9488
+ const unsubscribe = ctx.slots.subscribe("main", refresh);
9489
+ refresh();
9490
+ return () => {
9491
+ unsubscribe();
9492
+ remove?.();
9493
+ };
9494
+ });
9495
+ }
9496
+ //#endregion
8969
9497
  //#region src/client/settings-page-model.ts
8970
9498
  function settingsGroup(id) {
8971
9499
  if (/archive|about|^usage-statistics$/.test(id)) return "records";
@@ -9067,6 +9595,10 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9067
9595
  .dcu-settings-inner:has(.dcu-usage-section){height:100%;display:flex;flex-direction:column;padding-bottom:0}
9068
9596
  .dcu-settings-inner:has(.dcu-usage-section)>.dcu-settings-heading{display:none}
9069
9597
  .dcu-settings-inner:has(.dcu-usage-section)>[data-slot="settings.section"]{display:flex;flex:1;min-height:0;flex-direction:column}
9598
+ .dcu-plugin-config [data-plugin-panel]{height:auto;padding:0;overflow:visible;align-items:stretch;gap:24px}
9599
+ .dcu-plugin-config [data-plugin-panel]>*{max-width:none}
9600
+ .dcu-plugin-config [data-plugin-panel]>header :is(h1,p){display:none}
9601
+ .dcu-plugin-config [data-plugin-panel]>header{justify-content:flex-end}
9070
9602
  .dcu-usage-section{display:flex;flex-direction:column;flex:1;min-height:0;gap:16px}
9071
9603
  .dcu-usage-section h1{font-size:24px;font-weight:500;margin:0}
9072
9604
  .dcu-usage-section p{color:var(--sp-muted)}
@@ -9127,7 +9659,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9127
9659
  const rows = (0, react.useSyncExternalStore)(sections.subscribe, sections.getSnapshot);
9128
9660
  const steps = (0, react.useSyncExternalStore)(onboarding.subscribe, onboarding.getSnapshot);
9129
9661
  const connection = (0, react.useSyncExternalStore)(connectionState.subscribe, connectionState.getSnapshot);
9130
- const onboardingActive = useSessions((state) => state.phase === "ready" && (state.current === void 0 || state.byId[state.current]?.blank === true));
9662
+ const onboardingActive = useSessions((state) => isBlankOnboardingSession(state));
9131
9663
  const [completed, setCompleted] = (0, react.useState)(() => /* @__PURE__ */ new Set());
9132
9664
  const [open, setOpen] = (0, react.useState)(false);
9133
9665
  const [activeId, setActiveId] = (0, react.useState)("general");
@@ -9282,6 +9814,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9282
9814
  if (main.current !== null) main.current.scrollTop = 0;
9283
9815
  }, [active?.id]);
9284
9816
  const visible = filterSettingsRows(rows, query);
9817
+ const ownTitle = active?.id === "general" ? t("settings.general") : active?.id === "plugin-config" ? t("settings.pluginConfig") : void 0;
9285
9818
  const connectionIndicator = connection === "disconnected" ? "disconnected" : connection === "connecting" ? "connecting" : recovered ? "recovered" : void 0;
9286
9819
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9287
9820
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: settingsPageStyles }),
@@ -9391,8 +9924,8 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9391
9924
  "data-settings-section": active?.id,
9392
9925
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
9393
9926
  className: "dcu-settings-heading",
9394
- "data-own-title": active?.id === "general",
9395
- children: [active?.id === "general" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: t("settings.general") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9927
+ "data-own-title": ownTitle !== void 0,
9928
+ children: [ownTitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: ownTitle }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9396
9929
  className: "dcu-settings-actions",
9397
9930
  children: renderSlot("settings.action", {})
9398
9931
  })]
@@ -10324,7 +10857,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10324
10857
  }
10325
10858
  /** Archive Manager replaces the official ui-workspace row with this optional service. */
10326
10859
  function startWorkspaceSession(ctx, workspaceId) {
10327
- const uiWorkspace = ctx.get("uiWorkspace");
10860
+ const uiWorkspace = probeService(ctx, "uiWorkspace");
10328
10861
  if (hasStartSession(uiWorkspace)) {
10329
10862
  uiWorkspace.startSession(workspaceId);
10330
10863
  return;
@@ -10337,6 +10870,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10337
10870
  }
10338
10871
  /** 替换 DSH 的官方 sidebar 插槽,不修改 DSH 源码或会话数据。 */
10339
10872
  function apply(ctx) {
10873
+ Object.assign(globalThis, { __dcuCurrentSessionId: currentSessionId });
10340
10874
  const widthStorage = browserStorage();
10341
10875
  if (widthStorage) initializeComposerWidth(widthStorage);
10342
10876
  ctx.effect(() => ctx.locale.register(NS, {
@@ -10348,6 +10882,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10348
10882
  registerInputHistory(ctx);
10349
10883
  registerSettingsPage(ctx);
10350
10884
  registerUsageStatistics(ctx);
10885
+ registerPluginConfigSection(ctx);
10351
10886
  const connection = ctx.get("connection");
10352
10887
  const openPath = (path) => openPathInHost(connection, path);
10353
10888
  ctx.effect(() => observeSlimSidebar(), "michengai-codex-ui: slim sidebar");
@@ -10359,7 +10894,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10359
10894
  ctx.effect(() => observeConversationHeader(), "michengai-codex-ui: conversation header");
10360
10895
  ctx.effect(() => observeOfficialTurnNavigators(), "michengai-codex-ui: official turn navigator");
10361
10896
  const newConversationDraft = createDraftPresenceSource(ctx.sessions.list, () => {
10362
- const id = ctx.sessions.list.getSnapshot().current;
10897
+ const id = currentSessionId(ctx.sessions.list.getSnapshot());
10363
10898
  const binding = id === void 0 ? void 0 : ctx.sessions.binding(id);
10364
10899
  return binding === void 0 ? void 0 : ctx.conversation.input.for(binding.ctx).state;
10365
10900
  });
@@ -10399,12 +10934,12 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10399
10934
  inject: () => ({
10400
10935
  newConversationDraft,
10401
10936
  prefillNewConversation: (text) => {
10402
- const id = ctx.sessions.list.getSnapshot().current;
10937
+ const id = currentSessionId(ctx.sessions.list.getSnapshot());
10403
10938
  const binding = id === void 0 ? void 0 : ctx.sessions.binding(id);
10404
10939
  return prefillNewConversation(binding === void 0 ? void 0 : ctx.conversation.input.for(binding.ctx), text);
10405
10940
  },
10406
10941
  openSession: (sessionId) => {
10407
- openConversation(ctx.sessions, ctx.layout, sessionId);
10942
+ openConversation(ctx, ctx.layout, sessionId);
10408
10943
  },
10409
10944
  startSession: (workspaceId) => {
10410
10945
  startWorkspaceSession(ctx, workspaceId);
@@ -10434,20 +10969,16 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10434
10969
  locale: NS
10435
10970
  }, TurnNavigator));
10436
10971
  const forkSession = async (sessionId) => {
10437
- await runHostAction("fork", async () => {
10438
- const childId = await ctx.sessions.fork({
10439
- sessionId,
10440
- increaseTitle: true
10441
- });
10442
- openConversation(ctx.sessions, ctx.layout, childId);
10443
- });
10972
+ await runHostAction("fork", () => forkHostSession(ctx, sessionId));
10444
10973
  };
10445
10974
  const renameSession = async (sessionId, title) => {
10446
- const session = ctx.sessions.binding(sessionId)?.session;
10447
- if (session === void 0) throw new UserFacingError(t("sessions.unknown"));
10448
10975
  await runHostAction("rename", async () => {
10449
- const result = await session.rename(title);
10450
- if (!result.ok) throw result.error;
10976
+ try {
10977
+ await renameHostSession(ctx, sessionId, title);
10978
+ } catch (reason) {
10979
+ if (reason instanceof UnknownSessionError) throw new UserFacingError(t("sessions.unknown"));
10980
+ throw reason;
10981
+ }
10451
10982
  });
10452
10983
  };
10453
10984
  const deleteSession = async (sessionId) => {
@@ -10458,7 +10989,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10458
10989
  if (!result.ok) throw result.error === void 0 ? new UserFacingError(t("sessions.deleteUnavailable")) : result.error;
10459
10990
  });
10460
10991
  };
10461
- const archiveSession = (sessionId) => runHostAction("archive", () => ctx.workspaces.archiveSession(sessionId));
10992
+ const archiveSession = (sessionId) => runHostAction("archive", () => archiveHostSession(ctx, sessionId));
10462
10993
  const moveSession = async (sessionId, targetWorkspaceId) => {
10463
10994
  try {
10464
10995
  await requestSessionMove(sessionId, targetWorkspaceId);
@@ -10479,22 +11010,36 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10479
11010
  if (prompt === "") throw new UserFacingError(t("connectors.promptRequired"));
10480
11011
  const workspaces = ctx.workspaces.list.getSnapshot();
10481
11012
  const sessionSnapshot = ctx.sessions.list.getSnapshot();
10482
- const currentSessionId = sessionSnapshot.current;
10483
- const currentWorkspaceId = currentSessionId === void 0 ? void 0 : workspaces.items.find((workspace) => workspace.sessionIds.includes(currentSessionId))?.workspaceId;
11013
+ const selectedSessionId = currentSessionId(sessionSnapshot);
11014
+ const currentWorkspaceId = selectedSessionId === void 0 ? void 0 : workspaces.items.find((workspace) => workspace.sessionIds.includes(selectedSessionId))?.workspaceId;
10484
11015
  const baselinesReady = workspaceBaselinesReady(workspaces, sessionSnapshot);
10485
11016
  const targetWorkspaceId = currentWorkspaceId ?? (baselinesReady ? recentWorkspaceId(workspaces.items, sessionSnapshot.byId) : void 0);
10486
11017
  if (targetWorkspaceId === void 0 && !baselinesReady) throw new UserFacingError(t("connectors.workspacesLoading"));
10487
11018
  if (targetWorkspaceId === void 0) throw new UserFacingError(t("connectors.workspaceRequired"));
10488
- const uiWorkspace = ctx.get("uiWorkspace");
10489
- const workspaceNavigation = hasConnectWorkspace(uiWorkspace) ? uiWorkspace : hasConnectWorkspace(ctx.workspaces) ? ctx.workspaces : void 0;
10490
- if (workspaceNavigation === void 0) throw new UserFacingError(t("connectors.workspaceUnavailable"));
10491
- const sessionId = await workspaceNavigation.connectWorkspace(targetWorkspaceId);
11019
+ const uiWorkspace = probeService(ctx, "uiWorkspace");
10492
11020
  const conversation = ctx.get("conversation");
10493
11021
  if (conversation === void 0) throw new UserFacingError(t("connectors.conversationUnavailable"));
10494
- const binding = ctx.sessions.binding(sessionId);
10495
- if (binding === void 0) throw new UserFacingError(t("connectors.sessionPending"));
10496
- conversation.input.for(binding.ctx).setDraft(prompt);
10497
- openConversation(ctx.sessions, ctx.layout, sessionId);
11022
+ try {
11023
+ if (hasOpenWorkspace(uiWorkspace)) {
11024
+ await Promise.resolve(uiWorkspace.openWorkspace(targetWorkspaceId, (id) => {
11025
+ const binding = ctx.sessions.binding(id);
11026
+ if (binding?.ctx === void 0) throw new UnknownSessionError();
11027
+ conversation.input.for(binding.ctx).setDraft(prompt);
11028
+ }));
11029
+ selectGlobalPanel(ctx.layout, null);
11030
+ return;
11031
+ }
11032
+ const workspaceNavigation = hasConnectWorkspace(uiWorkspace) ? uiWorkspace : hasConnectWorkspace(ctx.workspaces) ? ctx.workspaces : void 0;
11033
+ if (workspaceNavigation === void 0) throw new UserFacingError(t("connectors.workspaceUnavailable"));
11034
+ const sessionId = await workspaceNavigation.connectWorkspace(targetWorkspaceId);
11035
+ await openConversationWithDraft(ctx, ctx.layout, ctx.sessions, sessionId, (binding) => {
11036
+ if (binding.ctx === void 0) throw new UnknownSessionError();
11037
+ conversation.input.for(binding.ctx).setDraft(prompt);
11038
+ });
11039
+ } catch (reason) {
11040
+ if (reason instanceof UnknownSessionError) throw new UserFacingError(t("connectors.sessionPending"));
11041
+ throw reason;
11042
+ }
10498
11043
  };
10499
11044
  ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
10500
11045
  name: "sidebar.workspaces",
@@ -10509,7 +11054,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10509
11054
  moveSession,
10510
11055
  openPath,
10511
11056
  openSession: (sessionId) => {
10512
- openConversation(ctx.sessions, ctx.layout, sessionId);
11057
+ openConversation(ctx, ctx.layout, sessionId);
10513
11058
  },
10514
11059
  renameSession,
10515
11060
  renameWorkspace: (workspaceId, title) => ctx.workspaces.rename(workspaceId, title),
@@ -10528,7 +11073,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10528
11073
  const openDeepLink = () => {
10529
11074
  if (opened || ctx.sessions.list.getSnapshot().byId[sessionId] === void 0) return;
10530
11075
  opened = true;
10531
- openConversation(ctx.sessions, ctx.layout, sessionId);
11076
+ openConversation(ctx, ctx.layout, sessionId);
10532
11077
  };
10533
11078
  openDeepLink();
10534
11079
  return ctx.sessions.list.subscribe(openDeepLink);