@michengai/dsh-codex-ui 1.1.10 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,54 @@
4
4
 
5
5
  This changelog records recent releases of DSH Codex UI and its one-click installer. Earlier changes remain available in the [Git history](https://github.com/MichengAI/dsh-codex-ui/commits/main).
6
6
 
7
+ ## 1.1.12 - 2026-09-17
8
+
9
+ - Session rows show compact relative time on the right (now, minutes, hours, days, weeks, months, years). Hover or the action menu yields to the archive shortcut; a pending label hides the inline time.
10
+ - Truncated session titles scroll on hover or when the menu is open. Reduced motion keeps the ellipsis.
11
+ - Relative times refresh every minute. Inline English uses now; hover cards use Just now.
12
+
13
+ ## suite-installer-v1.0.14 - 2026-09-16
14
+
15
+ Install or maintain 11 first-party plugins in one step. Included plugins and versions:
16
+
17
+ - `@michengai/dsh-archive-manager@0.1.43`
18
+ - `@michengai/dsh-codex-ui@1.1.11`
19
+ - `@michengai/dsh-skills-manager@0.1.52`
20
+ - `@michengai/dsh-agency-agents@0.1.43`
21
+ - `@michengai/dsh-im-connect@0.1.50`
22
+ - `@michengai/dsh-automation@0.1.44`
23
+ - `@michengai/dsh-btw@0.1.8`
24
+ - `@michengai/dsh-simplify@0.1.5`
25
+ - `@michengai/dsh-pua@0.3.13`
26
+ - `@michengai/dsh-code-review@0.1.2`
27
+ - `@michengai/dsh-codex-pet@0.1.6`
28
+
29
+ The installer uses these exact versions for reproducible installation.
30
+
31
+ ## 1.1.11 - 2026-09-16
32
+
33
+ - Open the settings page (and usage statistics) through a `document.body` portal so a collapsed 56px rail cannot trap the overlay in a transform containing block.
34
+ - Skip the dsh-context `context-overview` footer action by slot id and render the remaining footer actions one by one. Usage statistics stays visible. Footer actions without an id are dropped. The per-session Context tab is unchanged.
35
+ - Pin a dragged project into an empty pinned section even when dragleave clears the insertion target.
36
+
37
+ ## suite-installer-v1.0.13 - 2026-09-16
38
+
39
+ Install or maintain 11 first-party plugins in one step. Included plugins and versions:
40
+
41
+ - `@michengai/dsh-archive-manager@0.1.43`
42
+ - `@michengai/dsh-codex-ui@1.1.10`
43
+ - `@michengai/dsh-skills-manager@0.1.52`
44
+ - `@michengai/dsh-agency-agents@0.1.43`
45
+ - `@michengai/dsh-im-connect@0.1.50`
46
+ - `@michengai/dsh-automation@0.1.44`
47
+ - `@michengai/dsh-btw@0.1.8`
48
+ - `@michengai/dsh-simplify@0.1.5`
49
+ - `@michengai/dsh-pua@0.3.13`
50
+ - `@michengai/dsh-code-review@0.1.2`
51
+ - `@michengai/dsh-codex-pet@0.1.6`
52
+
53
+ The installer uses these exact versions for reproducible installation.
54
+
7
55
  ## 1.1.10 - 2026-09-16
8
56
 
9
57
  - Cold start registers session titles through a dedicated `@michengai/dsh-codex-ui/session-title` plugin after `sessionTitle` and `llm` are ready, and recycles the provider on unload.
@@ -4,6 +4,54 @@
4
4
 
5
5
  本日志记录 DSH Codex UI 及其一键安装器的最近发布;更早的变更可查看 [Git 提交历史](https://github.com/MichengAI/dsh-codex-ui/commits/main)。
6
6
 
7
+ ## 1.1.12 - 2026-09-17
8
+
9
+ - 会话行右侧显示紧凑相对时间(刚刚、分、小时、天、周、月、年)。悬停或打开菜单时让位给归档快捷图标;待处理标签优先于行内时间。
10
+ - 标题被截断时,悬停或打开菜单会横向滚动全文;系统减动效时保持省略号。
11
+ - 相对时间每分钟对齐刷新。行内英文用 now,悬停卡片用 Just now。
12
+
13
+ ## suite-installer-v1.0.14 - 2026-09-16
14
+
15
+ 一键维护安装 11 个自研插件。包含以下插件及版本:
16
+
17
+ - `@michengai/dsh-archive-manager@0.1.43`
18
+ - `@michengai/dsh-codex-ui@1.1.11`
19
+ - `@michengai/dsh-skills-manager@0.1.52`
20
+ - `@michengai/dsh-agency-agents@0.1.43`
21
+ - `@michengai/dsh-im-connect@0.1.50`
22
+ - `@michengai/dsh-automation@0.1.44`
23
+ - `@michengai/dsh-btw@0.1.8`
24
+ - `@michengai/dsh-simplify@0.1.5`
25
+ - `@michengai/dsh-pua@0.3.13`
26
+ - `@michengai/dsh-code-review@0.1.2`
27
+ - `@michengai/dsh-codex-pet@0.1.6`
28
+
29
+ 安装器使用这些精确版本执行安装,确保可复现。
30
+
31
+ ## 1.1.11 - 2026-09-16
32
+
33
+ - 设置页和用量统计改为挂到 `document.body`。侧栏收缩后,宿主或插件若给窄轨加了 transform,全屏页不再被困在 56px 里挤成竖排。
34
+ - 侧栏底部按 slot id 跳过 dsh-context 的 `context-overview`,其余底部动作(含费用统计)仍按 id 单独渲染。没有 id 的底部动作不渲染。会话里的上下文页还在。
35
+ - 置顶为空时,从项目区拖入仍会置顶;松手时即使蓝线落点被清掉也不丢。
36
+
37
+ ## suite-installer-v1.0.13 - 2026-09-16
38
+
39
+ 一键维护安装 11 个自研插件。包含以下插件及版本:
40
+
41
+ - `@michengai/dsh-archive-manager@0.1.43`
42
+ - `@michengai/dsh-codex-ui@1.1.10`
43
+ - `@michengai/dsh-skills-manager@0.1.52`
44
+ - `@michengai/dsh-agency-agents@0.1.43`
45
+ - `@michengai/dsh-im-connect@0.1.50`
46
+ - `@michengai/dsh-automation@0.1.44`
47
+ - `@michengai/dsh-btw@0.1.8`
48
+ - `@michengai/dsh-simplify@0.1.5`
49
+ - `@michengai/dsh-pua@0.3.13`
50
+ - `@michengai/dsh-code-review@0.1.2`
51
+ - `@michengai/dsh-codex-pet@0.1.6`
52
+
53
+ 安装器使用这些精确版本执行安装,确保可复现。
54
+
7
55
  ## 1.1.10 - 2026-09-16
8
56
 
9
57
  - 冷启动也能自动命名:独立挂上 `@michengai/dsh-codex-ui/session-title`,等 `sessionTitle` 与 `llm` 就绪后再占 first-prompt;卸载时回收提供方。
package/lib/client.js CHANGED
@@ -1246,6 +1246,23 @@ window.__ModuleLoader__.load({
1246
1246
  return parseChannelGroups(payload, fallbackLabel);
1247
1247
  }
1248
1248
  //#endregion
1249
+ //#region src/client/session-title-scroll.ts
1250
+ /** 会话行只展示标题第一行,避免换行把胶囊撑高。 */
1251
+ function sessionTitleLine(title) {
1252
+ return title.split(/\r?\n/)[0] ?? title;
1253
+ }
1254
+ /** 标题比可视槽多出来的像素;1px 内当作测量误差。 */
1255
+ function titleOverflowPx(scrollWidth, clientWidth) {
1256
+ const extra = Math.ceil(scrollWidth - clientWidth);
1257
+ return extra > 1 ? extra : 0;
1258
+ }
1259
+ const PX_PER_SECOND = 42;
1260
+ /** 按溢出长度估算单程时长,长标题更慢,避免一闪而过。 */
1261
+ function titleScrollDurationMs(overflowPx) {
1262
+ if (overflowPx <= 0) return 0;
1263
+ return Math.min(8e3, Math.max(1600, Math.round(overflowPx / PX_PER_SECOND * 1e3)));
1264
+ }
1265
+ //#endregion
1249
1266
  //#region src/client/session-tree.tsx
1250
1267
  function PinIcon() {
1251
1268
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pin, {
@@ -1416,7 +1433,58 @@ window.__ModuleLoader__.load({
1416
1433
  });
1417
1434
  return null;
1418
1435
  }
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 }) {
1436
+ function SessionTime({ time }) {
1437
+ if (time === void 0 || time === "") return null;
1438
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1439
+ className: "dcu-wb-session-time",
1440
+ children: time
1441
+ });
1442
+ }
1443
+ function SessionRowTitle({ title }) {
1444
+ const text = sessionTitleLine(title);
1445
+ const wrapRef = (0, react.useRef)(null);
1446
+ const textRef = (0, react.useRef)(null);
1447
+ const [shift, setShift] = (0, react.useState)(0);
1448
+ (0, react.useLayoutEffect)(() => {
1449
+ const wrap = wrapRef.current;
1450
+ const inner = textRef.current;
1451
+ if (wrap === null || inner === null) return;
1452
+ const measure = () => {
1453
+ setShift(titleOverflowPx(inner.scrollWidth, wrap.clientWidth));
1454
+ };
1455
+ measure();
1456
+ let cancelled = false;
1457
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
1458
+ if (fonts !== void 0) fonts.ready.then(() => {
1459
+ if (!cancelled) measure();
1460
+ });
1461
+ if (typeof ResizeObserver === "undefined") return () => {
1462
+ cancelled = true;
1463
+ };
1464
+ const observer = new ResizeObserver(measure);
1465
+ observer.observe(wrap);
1466
+ return () => {
1467
+ cancelled = true;
1468
+ observer.disconnect();
1469
+ };
1470
+ }, [text]);
1471
+ const style = shift > 0 ? {
1472
+ "--dcu-title-shift": `${shift}px`,
1473
+ "--dcu-title-duration": `${titleScrollDurationMs(shift)}ms`
1474
+ } : void 0;
1475
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1476
+ ref: wrapRef,
1477
+ className: "dcu-wb-session-title",
1478
+ "data-overflow": shift > 0 || void 0,
1479
+ style,
1480
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1481
+ ref: textRef,
1482
+ className: "dcu-wb-session-title-text",
1483
+ children: text
1484
+ })
1485
+ });
1486
+ }
1487
+ 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
1488
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1421
1489
  "data-dcu-session": id,
1422
1490
  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 +1502,18 @@ window.__ModuleLoader__.load({
1434
1502
  children: [
1435
1503
  subtitle !== void 0 && subtitle !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1436
1504
  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", {
1505
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1441
1506
  className: "dcu-wb-session-sub",
1442
1507
  children: subtitle
1443
1508
  })]
1444
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1445
- className: "dcu-wb-session-title",
1446
- children: title.split(/\r?\n/)[0] ?? title
1447
- }),
1509
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title }),
1448
1510
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
1449
1511
  pendingInteraction,
1450
1512
  unread,
1451
1513
  running,
1452
1514
  t
1453
1515
  }),
1516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionTime, { time }),
1454
1517
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1455
1518
  className: "dcu-wb-quick-actions",
1456
1519
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1915,13 +1978,84 @@ window.__ModuleLoader__.load({
1915
1978
  top: rect.top
1916
1979
  };
1917
1980
  }
1918
- /** Codex 会话卡片右上角的紧凑相对时间。 */
1981
+ const HOUR = 3600;
1982
+ const DAY = HOUR * 24;
1983
+ const WEEK = DAY * 7;
1984
+ const MONTH = DAY * 30;
1985
+ const YEAR = DAY * 365;
1986
+ function formatRelativeTime(updatedAt, t, now, justNow) {
1987
+ const seconds = Math.max(0, Math.floor((now - updatedAt) / 1e3));
1988
+ if (seconds < 60) return t(justNow);
1989
+ if (seconds < HOUR) return t("time.minutes", { count: Math.floor(seconds / 60) });
1990
+ if (seconds < DAY) return t("time.hours", { count: Math.floor(seconds / HOUR) });
1991
+ if (seconds < WEEK) return t("time.days", { count: Math.floor(seconds / DAY) });
1992
+ if (seconds < MONTH) return t("time.weeks", { count: Math.floor(seconds / WEEK) });
1993
+ if (seconds < YEAR) return t("time.months", { count: Math.floor(seconds / MONTH) });
1994
+ return t("time.years", { count: Math.floor(seconds / YEAR) });
1995
+ }
1996
+ /** 悬停卡片右上角。分档与 ChatGPT `$wa` 一致;英文刚刚用 Just now。 */
1919
1997
  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) });
1998
+ return formatRelativeTime(updatedAt, t, now, "time.justNow");
1999
+ }
2000
+ /** 会话行右侧。分档相同;英文刚刚用 now,避免挤占标题。 */
2001
+ function formatCompactTime(updatedAt, t, now = Date.now()) {
2002
+ return formatRelativeTime(updatedAt, t, now, "time.justNowShort");
2003
+ }
2004
+ //#endregion
2005
+ //#region src/client/shared-now.ts
2006
+ const SHARED_NOW_INTERVAL_MS = 6e4;
2007
+ /** 对齐到下一个整档,避免「刚刚」在整分钟边界之后还多停将近一轮。 */
2008
+ function msUntilNextNowTick(now, intervalMs = SHARED_NOW_INTERVAL_MS) {
2009
+ const remainder = now % intervalMs;
2010
+ return remainder === 0 ? intervalMs : intervalMs - remainder;
2011
+ }
2012
+ const listeners = /* @__PURE__ */ new Set();
2013
+ let sharedNow = Date.now();
2014
+ let timeoutId = 0;
2015
+ let intervalId = 0;
2016
+ let cancelled = true;
2017
+ function publish(now) {
2018
+ sharedNow = now;
2019
+ for (const listener of listeners) listener();
2020
+ }
2021
+ function startClock() {
2022
+ cancelled = false;
2023
+ timeoutId = window.setTimeout(() => {
2024
+ if (cancelled) return;
2025
+ publish(Date.now());
2026
+ intervalId = window.setInterval(() => {
2027
+ publish(Date.now());
2028
+ }, SHARED_NOW_INTERVAL_MS);
2029
+ if (cancelled) {
2030
+ window.clearInterval(intervalId);
2031
+ intervalId = 0;
2032
+ }
2033
+ }, msUntilNextNowTick(Date.now()));
2034
+ }
2035
+ function stopClock() {
2036
+ cancelled = true;
2037
+ window.clearTimeout(timeoutId);
2038
+ window.clearInterval(intervalId);
2039
+ timeoutId = 0;
2040
+ intervalId = 0;
2041
+ }
2042
+ function subscribe(listener) {
2043
+ if (listeners.size === 0) {
2044
+ sharedNow = Date.now();
2045
+ startClock();
2046
+ }
2047
+ listeners.add(listener);
2048
+ return () => {
2049
+ listeners.delete(listener);
2050
+ if (listeners.size === 0) stopClock();
2051
+ };
2052
+ }
2053
+ function getSnapshot() {
2054
+ return sharedNow;
2055
+ }
2056
+ /** 模块级当前时刻。几棵树挂着都只走一套定时器,每分钟刷新一次。 */
2057
+ function useSharedNow() {
2058
+ return (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1925
2059
  }
1926
2060
  //#endregion
1927
2061
  //#region src/client/conversation-bubbles.ts
@@ -2451,6 +2585,18 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2451
2585
  workspaceId
2452
2586
  };
2453
2587
  }
2588
+ /**
2589
+ * 空置顶没有项目行可自行计算落点,松手时 dragleave 又常把 relatedTarget 置空并清掉蓝线。
2590
+ * 只要这次 drop 发生在置顶区,空列表也必须把项目置顶。
2591
+ */
2592
+ function resolvePinnedSectionDrop(draggedWorkspace, target, pinnedEmpty) {
2593
+ if (draggedWorkspace === void 0) return void 0;
2594
+ if (target?.zone === "pinned") return {
2595
+ id: draggedWorkspace,
2596
+ beforeId: target.beforeId
2597
+ };
2598
+ return pinnedEmpty ? { id: draggedWorkspace } : void 0;
2599
+ }
2454
2600
  /** 按指定 id 顺序取出对应项;未出现在 ids 中的项丢弃。 */
2455
2601
  function orderByIds(items, ids, idOf) {
2456
2602
  const byId = new Map(items.map((item) => [idOf(item), item]));
@@ -2889,7 +3035,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
2889
3035
  }
2890
3036
  const previewTitle = document.createElement("span");
2891
3037
  previewTitle.className = "dcu-wb-drag-ghost-title";
2892
- previewTitle.textContent = title.split(/\r?\n/)[0] ?? title;
3038
+ previewTitle.textContent = sessionTitleLine(title);
2893
3039
  preview.appendChild(previewTitle);
2894
3040
  document.body.appendChild(preview);
2895
3041
  preview.offsetWidth;
@@ -3004,11 +3150,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3004
3150
  .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}
3005
3151
  .dcu-wb-project-title{flex:1;font-weight:400;color:var(--dcu-sidebar-primary)}
3006
3152
  .dcu-wb-session{position:relative;min-width:0;min-height:30px;gap:0;overflow:hidden;padding-left:30px;padding-right:28px}
3007
- .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}
3153
+ .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}
3008
3154
  .dcu-wb-actions{display:none;align-items:center;gap:8px;flex:none}
3009
3155
  .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}
3010
3156
  .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}
3011
- .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}
3157
+ .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}
3012
3158
  .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}
3013
3159
  .dcu-wb-more:hover{color:var(--dcu-sidebar-primary)}
3014
3160
  .dcu-wb-context-anchor{opacity:0;pointer-events:none}
@@ -3079,6 +3225,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3079
3225
  }
3080
3226
  function CodexWorkspaceTree({ wide, useSessions, useSessionPendingInteraction, useWorkspaces, t, archiveSession, deleteSession, deleteWorkspace, forkSession, insertSessionBefore, insertWorkspaceBefore, moveSession, openPath, openSession, renameSession, renameWorkspace, startSession, canDeleteSession }) {
3081
3227
  const sessions = useSessions((state) => state);
3228
+ const now = useSharedNow();
3082
3229
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
3083
3230
  const workspaces = useWorkspaces((state) => state);
3084
3231
  const baselinesReady = workspaceBaselinesReady(workspaces, sessions);
@@ -3939,23 +4086,21 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
3939
4086
  project: workspace.title,
3940
4087
  path,
3941
4088
  branch,
3942
- time: formatHoverTime(session.updatedAt, t),
4089
+ time: formatHoverTime(session.updatedAt, t, now),
3943
4090
  left: box.left,
3944
4091
  top: box.top
3945
4092
  });
3946
4093
  },
3947
4094
  onMouseLeave: hideTip,
3948
4095
  children: [
3949
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3950
- className: "dcu-wb-session-title",
3951
- children: session.displayTitle.split(/\r?\n/)[0] ?? session.displayTitle
3952
- }),
4096
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRowTitle, { title: session.displayTitle }),
3953
4097
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionState, {
3954
4098
  pendingInteraction,
3955
4099
  unread: unreadSessionIds.includes(id),
3956
4100
  running: session.running === true,
3957
4101
  t
3958
4102
  }),
4103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionTime, { time: formatCompactTime(session.updatedAt, t, now) }),
3959
4104
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3960
4105
  className: "dcu-wb-quick-actions",
3961
4106
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4492,10 +4637,11 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4492
4637
  const target = workspaceDropTargetRef.current;
4493
4638
  setWorkspaceDragId(void 0);
4494
4639
  setWorkspaceDropTarget(void 0);
4495
- if (draggedWorkspace !== void 0 && target?.zone === "pinned") pinWorkspaceAt(draggedWorkspace, target.beforeId);
4640
+ const drop = resolvePinnedSectionDrop(draggedWorkspace, target, pinnedGroups.length === 0);
4641
+ if (drop !== void 0) pinWorkspaceAt(drop.id, drop.beforeId);
4496
4642
  },
4497
4643
  onDragLeave: (event) => {
4498
- if (event.currentTarget.contains(event.relatedTarget)) return;
4644
+ if (!(event.relatedTarget instanceof Node) || event.currentTarget.contains(event.relatedTarget)) return;
4499
4645
  setWorkspaceDropTarget(void 0);
4500
4646
  },
4501
4647
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4541,10 +4687,36 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4541
4687
  zone: "pinned",
4542
4688
  beforeId: void 0
4543
4689
  });
4690
+ },
4691
+ onDrop: (event) => {
4692
+ event.preventDefault();
4693
+ event.stopPropagation();
4694
+ const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
4695
+ setWorkspaceDragId(void 0);
4696
+ setWorkspaceDropTarget(void 0);
4697
+ if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace);
4544
4698
  }
4545
4699
  }),
4546
4700
  pinnedGroups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4547
4701
  className: "dcu-wb-empty",
4702
+ onDragOver: (event) => {
4703
+ if (!pinDragActive) return;
4704
+ event.preventDefault();
4705
+ event.stopPropagation();
4706
+ event.dataTransfer.dropEffect = "move";
4707
+ setWorkspaceDropTarget({
4708
+ zone: "pinned",
4709
+ beforeId: void 0
4710
+ });
4711
+ },
4712
+ onDrop: (event) => {
4713
+ event.preventDefault();
4714
+ event.stopPropagation();
4715
+ const draggedWorkspace = readWorkspaceDrag(event.dataTransfer, workspaceDragId);
4716
+ setWorkspaceDragId(void 0);
4717
+ setWorkspaceDropTarget(void 0);
4718
+ if (draggedWorkspace !== void 0) pinWorkspaceAt(draggedWorkspace);
4719
+ },
4548
4720
  children: t("workspace.pinnedEmpty")
4549
4721
  }),
4550
4722
  pinnedGroups.map((workspace) => renderGroup(workspace, "pinned")),
@@ -4685,6 +4857,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4685
4857
  unread,
4686
4858
  running: session.running === true,
4687
4859
  pendingInteraction,
4860
+ time: formatCompactTime(session.updatedAt, t, now),
4688
4861
  t,
4689
4862
  menuItems: sessionMenu(id, session.cwd),
4690
4863
  draggable: true,
@@ -4730,7 +4903,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
4730
4903
  kind: "session",
4731
4904
  id,
4732
4905
  title,
4733
- time: formatHoverTime(session.updatedAt, t),
4906
+ time: formatHoverTime(session.updatedAt, t, now),
4734
4907
  left: box.left,
4735
4908
  top: box.top
4736
4909
  });
@@ -5379,6 +5552,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5379
5552
  }
5380
5553
  function ChannelBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, canDeleteSession, menu, setMenu }) {
5381
5554
  const sessions = useSessions((state) => state);
5555
+ const now = useSharedNow();
5382
5556
  const workspaces = useWorkspaces?.((state) => state);
5383
5557
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
5384
5558
  const [groups, setGroups] = (0, react.useState)([]);
@@ -5498,6 +5672,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5498
5672
  unread,
5499
5673
  running,
5500
5674
  pendingInteraction,
5675
+ time: updatedAt === void 0 ? void 0 : formatCompactTime(updatedAt, t, now),
5501
5676
  t,
5502
5677
  menuItems: sessionMenuItems(t, {
5503
5678
  unread,
@@ -5523,7 +5698,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5523
5698
  showTip({
5524
5699
  title,
5525
5700
  project: label,
5526
- time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t),
5701
+ time: updatedAt === void 0 ? void 0 : formatHoverTime(updatedAt, t, now),
5527
5702
  left: box.left,
5528
5703
  top: box.top
5529
5704
  });
@@ -5653,6 +5828,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5653
5828
  }
5654
5829
  function ScheduleBrowserTree({ openSession, archiveSession, deleteSession, forkSession, moveSession, renameSession, useSessions, useSessionPendingInteraction, useWorkspaces, t, canDeleteSession, openTaskSettings, menu, setMenu }) {
5655
5830
  const sessions = useSessions((state) => state);
5831
+ const now = useSharedNow();
5656
5832
  const pendingInteractions = (useSessionPendingInteraction ?? useEmptySessionPendingInteraction)((state) => state);
5657
5833
  const workspaces = useWorkspaces((state) => state);
5658
5834
  const [expanded, setExpanded] = (0, react.useState)(() => readTreeExpansionState(browserStorage(), SCHEDULE_EXPANSION_STORAGE_KEY));
@@ -5802,6 +5978,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5802
5978
  unread,
5803
5979
  running: session.running,
5804
5980
  pendingInteraction,
5981
+ time: session.updatedAt === void 0 ? void 0 : formatCompactTime(session.updatedAt, t, now),
5805
5982
  t,
5806
5983
  menuItems: sessionMenuItems(t, {
5807
5984
  unread,
@@ -5827,7 +6004,7 @@ header [data-dcu-title-folder] svg,header [data-dcu-title-more] svg{display:bloc
5827
6004
  showTip({
5828
6005
  title,
5829
6006
  project: group.label,
5830
- time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t),
6007
+ time: session.updatedAt === void 0 ? void 0 : formatHoverTime(session.updatedAt, t, now),
5831
6008
  left: box.left,
5832
6009
  top: box.top
5833
6010
  });
@@ -6449,6 +6626,8 @@ body[data-ds-dark-theme] [data-conversation-scroll] [data-trigger-menu]{backgrou
6449
6626
  //#region src/client/CodexSidebar.tsx
6450
6627
  const subscribeEmptyCompanionTabs = () => () => {};
6451
6628
  const getEmptyCompanionTabs = () => EMPTY_COMPANION_TABS;
6629
+ const emptyFooterActions = [];
6630
+ const getEmptyFooterActions = () => emptyFooterActions;
6452
6631
  const SIDEBAR_COLLAPSE_SETTLE_MS = 500;
6453
6632
  const SIDEBAR_EXPANSION_STORAGE_KEY = "dsh-codex-ui.sidebar-expansion.v1";
6454
6633
  const EXTENSIONS_EXPANSION_KEY = "extensions";
@@ -6833,8 +7012,9 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
6833
7012
  });
6834
7013
  });
6835
7014
  /** Codex 风格的 DSH 侧栏,只替换导航外观,项目浏览和设置仍由 DSH 官方组件提供。 */
6836
- function CodexSidebar({ globalPanels, selectPanel, usePanelInfo = useLegacyPanelInfo, collapsed, width, openSession, startSession, toggleSidebar, archiveSession, canDeleteSession, deleteSession, forkSession, moveSession, renameSession, openPath, companionSlots, renderSlot, t, useSessions, useSessionPendingInteraction, useWorkspaces, prefillNewConversation, newConversationDraft }) {
7015
+ 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 }) {
6837
7016
  const panels = (0, react.useSyncExternalStore)(globalPanels?.subscribe ?? subscribeEmptyCompanionTabs, globalPanels?.getSnapshot ?? getEmptyPanels, globalPanels?.getSnapshot ?? getEmptyPanels);
7017
+ const visibleFooterActions = (0, react.useSyncExternalStore)(footerActions?.subscribe ?? subscribeEmptyCompanionTabs, footerActions?.getSnapshot ?? getEmptyFooterActions, footerActions?.getSnapshot ?? getEmptyFooterActions);
6838
7018
  const activePanelId = usePanelInfo((info) => info.activePanelId);
6839
7019
  const panelButtons = (wide) => selectPanel === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalPanelButtons, {
6840
7020
  panels,
@@ -7325,7 +7505,7 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
7325
7505
  className: "dcu-foot",
7326
7506
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7327
7507
  className: "dcu-footer-actions",
7328
- children: renderSlot("sidebar.footer.action", { wide: !visualCompact })
7508
+ children: footerActions === void 0 ? renderSlot("sidebar.footer.action", { wide: !visualCompact }) : visibleFooterActions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderSlot("sidebar.footer.action", { wide: !visualCompact }, { only: action.id }) }, action.id))
7329
7509
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7330
7510
  ref: settingsSeat,
7331
7511
  className: "dcu-settings-seat",
@@ -8242,9 +8422,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8242
8422
  "turns.untitled": "未命名提问",
8243
8423
  "turns.jump": "跳转到第 {index} 轮:{summary}",
8244
8424
  "time.justNow": "刚刚",
8425
+ "time.justNowShort": "刚刚",
8245
8426
  "time.minutes": "{count}分",
8246
8427
  "time.hours": "{count}小时",
8247
8428
  "time.days": "{count}天",
8429
+ "time.weeks": "{count}周",
8430
+ "time.months": "{count}个月",
8431
+ "time.years": "{count}年",
8248
8432
  "errors.unauthorized": "请先登录 DSH,再重试。",
8249
8433
  "errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
8250
8434
  "errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
@@ -8526,9 +8710,13 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8526
8710
  "turns.untitled": "Untitled prompt",
8527
8711
  "turns.jump": "Jump to turn {index}: {summary}",
8528
8712
  "time.justNow": "Just now",
8713
+ "time.justNowShort": "now",
8529
8714
  "time.minutes": "{count}m",
8530
8715
  "time.hours": "{count}h",
8531
8716
  "time.days": "{count}d",
8717
+ "time.weeks": "{count}w",
8718
+ "time.months": "{count}mo",
8719
+ "time.years": "{count}y",
8532
8720
  "errors.unauthorized": "Sign in to DSH, then try again.",
8533
8721
  "errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
8534
8722
  "errors.serviceUnavailable": "The service is unavailable. Try again later.",
@@ -8550,6 +8738,38 @@ html[data-dcu-official-turn-navigator-supported=true] .dcu-turn-navigator,html:h
8550
8738
  "sessionTitle.type.research": "Research",
8551
8739
  "meta.locale": "en-US"
8552
8740
  };
8741
+ const HIDDEN_FOOTER_ACTION_IDS = /* @__PURE__ */ new Set(["context-overview"]);
8742
+ function visibleFooterActions(slots) {
8743
+ return slots.entriesOfSlot("sidebar.footer.action").flatMap(({ options }) => {
8744
+ const id = options.id;
8745
+ if (id === void 0 || HIDDEN_FOOTER_ACTION_IDS.has(id)) return [];
8746
+ return [{
8747
+ id,
8748
+ order: options.order ?? 0
8749
+ }];
8750
+ }).sort((left, right) => left.order - right.order);
8751
+ }
8752
+ function sameFooterActions(left, right) {
8753
+ return left.length === right.length && left.every((action, index) => {
8754
+ const previous = right[index];
8755
+ return previous !== void 0 && action.id === previous.id && action.order === previous.order;
8756
+ });
8757
+ }
8758
+ /** 给 React useSyncExternalStore 用的底部动作快照;按 slot id 过滤,不靠 DOM class。 */
8759
+ function createFooterActionSource(slots) {
8760
+ let cached = [];
8761
+ return {
8762
+ getSnapshot() {
8763
+ const next = visibleFooterActions(slots);
8764
+ if (sameFooterActions(next, cached)) return cached;
8765
+ cached = next;
8766
+ return cached;
8767
+ },
8768
+ subscribe(listener) {
8769
+ return slots.subscribe("sidebar.footer.action", listener);
8770
+ }
8771
+ };
8772
+ }
8553
8773
  //#endregion
8554
8774
  //#region src/client/host-open-path.ts
8555
8775
  const OPEN_IN_EXPLORER_ENDPOINT = CODEX_UI_API_ENDPOINTS.openInExplorer;
@@ -8985,10 +9205,10 @@ html[data-dsh-native-backdrop=mica] #root :is([class$="_centerCol"],[class$="_de
8985
9205
  html[data-dsh-native-backdrop=mica] :is(.dcu-root,.dcu-settings-nav){background:rgba(255,255,255,.18)}
8986
9206
  html[data-dsh-native-backdrop=mica] body[data-ds-dark-theme] :is(.dcu-root,.dcu-settings-nav){background:rgba(20,23,22,.18)}
8987
9207
  /* 会话折叠容器显式设了 visible,必须同时隐藏隔离分支的后代,避免穿透透明设置页。 */
8988
- html[data-dsh-native-backdrop=mica] .dcu-root:has(.dcu-settings-page) :is([inert],[inert] *){visibility:hidden!important}
9208
+ html[data-dsh-native-backdrop=mica] body:has(.dcu-settings-page) .dcu-root :is([inert],[inert] *){visibility:hidden!important}
8989
9209
  /* 同步隐藏本设置页隔离的外部 portal,保留引导模态及其他组件自己的 inert 语义。 */
8990
9210
  body:has(.dcu-settings-page) :is([data-dcu-settings-isolated],[data-dcu-settings-isolated] *){visibility:hidden!important}
8991
- html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-backdrop=mica] .dcu-root:has(.dcu-settings-page){background:transparent}
9211
+ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-backdrop=mica] body:has(.dcu-settings-page) .dcu-root{background:transparent}
8992
9212
 
8993
9213
  .dcu-settings-inner:has(.dcu-usage-section){height:100%;display:flex;flex-direction:column;padding-bottom:0}
8994
9214
  .dcu-settings-inner:has(.dcu-usage-section)>.dcu-settings-heading{display:none}
@@ -9233,7 +9453,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9233
9453
  restartActionLabel: t("settings.reconnect"),
9234
9454
  onReconnect: reconnect
9235
9455
  }),
9236
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9456
+ (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9237
9457
  ref: page,
9238
9458
  className: "dcu-settings-page",
9239
9459
  "data-dcu-settings-page": true,
@@ -9325,8 +9545,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9325
9545
  }), active !== void 0 && renderSlot("settings.section", { close }, { only: active.id })]
9326
9546
  })
9327
9547
  })]
9328
- }),
9329
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9548
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9330
9549
  ref: onboardingRoot,
9331
9550
  style: { display: "contents" },
9332
9551
  children: step !== void 0 && renderSlot("settings.onboarding", {
@@ -9336,7 +9555,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
9336
9555
  },
9337
9556
  openSection
9338
9557
  }, { only: step.id })
9339
- })
9558
+ })] }), document.body)
9340
9559
  ] });
9341
9560
  }
9342
9561
  /** 每条偏好仍由原插件渲染和保存,只为公共条目添加可扩展的分组容器。 */
@@ -10292,6 +10511,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10292
10511
  });
10293
10512
  const companionSlots = createCompanionTabSource(ctx.slots);
10294
10513
  const globalPanels = createGlobalPanelSource(ctx.slots, ctx.locale);
10514
+ const footerActions = createFooterActionSource(ctx.slots);
10295
10515
  ctx.slots.inject("sidebar", () => ctx.slots.register({
10296
10516
  name: "sidebar",
10297
10517
  registrant: "michengai-codex-ui",
@@ -10347,6 +10567,7 @@ html[data-dsh-native-backdrop=mica] .dcu-settings-page,html[data-dsh-native-back
10347
10567
  openPath,
10348
10568
  companionSlots,
10349
10569
  globalPanels,
10570
+ footerActions,
10350
10571
  selectPanel: (id) => {
10351
10572
  selectGlobalPanel(ctx.layout, id);
10352
10573
  }
@@ -259,9 +259,13 @@ const zh = {
259
259
  "turns.untitled": "未命名提问",
260
260
  "turns.jump": "跳转到第 {index} 轮:{summary}",
261
261
  "time.justNow": "刚刚",
262
+ "time.justNowShort": "刚刚",
262
263
  "time.minutes": "{count}分",
263
264
  "time.hours": "{count}小时",
264
265
  "time.days": "{count}天",
266
+ "time.weeks": "{count}周",
267
+ "time.months": "{count}个月",
268
+ "time.years": "{count}年",
265
269
  "errors.unauthorized": "请先登录 DSH,再重试。",
266
270
  "errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
267
271
  "errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
@@ -543,9 +547,13 @@ const en = {
543
547
  "turns.untitled": "Untitled prompt",
544
548
  "turns.jump": "Jump to turn {index}: {summary}",
545
549
  "time.justNow": "Just now",
550
+ "time.justNowShort": "now",
546
551
  "time.minutes": "{count}m",
547
552
  "time.hours": "{count}h",
548
553
  "time.days": "{count}d",
554
+ "time.weeks": "{count}w",
555
+ "time.months": "{count}mo",
556
+ "time.years": "{count}y",
549
557
  "errors.unauthorized": "Sign in to DSH, then try again.",
550
558
  "errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
551
559
  "errors.serviceUnavailable": "The service is unavailable. Try again later.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-ui",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "description": "以 Codex 风格重构 DSH Web 侧栏的独立客户端插件",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {