@xinxing1708/dsh-codex-style 0.0.0-stage → 0.1.2

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 ADDED
@@ -0,0 +1,1378 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@xinxing1708/dsh-codex-style",
3
+ factory: function(require) {
4
+ 'use strict';
5
+ const CSS_TEXT = "/* 自研界面令牌;尺寸与色值参考界面测量。来源说明见 THIRD_PARTY_NOTICES.md。 */\r\n\r\nbody[data-dsh-codex-style] {\r\n /* ---- 间距 ---- */\r\n --cx-spacing: 4px;\r\n --cx-space-1: 4px;\r\n --cx-space-2: 8px;\r\n --cx-space-3: 12px;\r\n --cx-space-4: 16px;\r\n --cx-space-5: 20px;\r\n --cx-space-6: 24px;\r\n --cx-space-8: 32px;\r\n\r\n /* ---- 圆角阶梯 ---- */\r\n --cx-radius-2xs: 2px;\r\n --cx-radius-xs: 4px;\r\n --cx-radius-sm: 6px;\r\n --cx-radius-md: 8px;\r\n --cx-radius-lg: 10px;\r\n --cx-radius-xl: 12px;\r\n --cx-radius-2xl: 16px;\r\n --cx-radius-3xl: 20px;\r\n --cx-radius-4xl: 24px;\r\n --cx-radius-composer: 25px;\r\n --cx-radius-full: 9999px;\r\n\r\n /* ---- 暗色面(深色界面:导航/顶栏 #202020、侧栏 #1b1b1b、对话区 #181818) ---- */\r\n --cx-window: #202020;\r\n --cx-sidebar: #1b1b1b;\r\n --cx-surface: #181818;\r\n --cx-thread: #171717;\r\n --cx-nested: #212121;\r\n --cx-elevated: #282828;\r\n --cx-summary: #2d2d2d;\r\n --cx-hover: rgba(255, 255, 255, 0.045);\r\n --cx-active: rgba(255, 255, 255, 0.08);\r\n\r\n /* ---- 边框 ---- */\r\n --cx-border-l1: rgba(255, 255, 255, 0.08);\r\n --cx-border-l2: rgba(255, 255, 255, 0.04);\r\n --cx-border-l3: rgba(255, 255, 255, 0.16);\r\n --cx-border-strong: rgba(255, 255, 255, 0.16);\r\n\r\n /* ---- 文本 ---- */\r\n --cx-text: #ffffff;\r\n --cx-text-2: rgba(255, 255, 255, 0.7);\r\n --cx-text-3: rgba(255, 255, 255, 0.5);\r\n --cx-text-4: rgba(255, 255, 255, 0.32);\r\n --cx-text-inverse: #0d0d0d;\r\n\r\n /* ---- 强调 / 状态 ---- */\r\n --cx-accent: #0285ff;\r\n --cx-accent-soft: #339cff;\r\n --cx-focus: #339cff;\r\n --cx-success: #40c977;\r\n --cx-danger: #ff6764;\r\n --cx-warning: #ff8549;\r\n --cx-attention: #3a83f7;\r\n\r\n /* ---- diff ---- */\r\n --cx-diff-add-bg: rgba(64, 201, 119, 0.23);\r\n --cx-diff-del-bg: rgba(255, 103, 100, 0.23);\r\n --cx-diff-add-mark: #40c977;\r\n --cx-diff-del-mark: #ff6764;\r\n\r\n /* ---- 字体 ---- */\r\n --cx-font-sans: -apple-system, system-ui, \"Segoe UI\", sans-serif;\r\n --cx-font-mono: ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n\r\n /* ---- 字号 / 行高 ---- */\r\n --cx-text-xs: 12px;\r\n --cx-text-sm: 14px;\r\n --cx-text-md: 16px;\r\n --cx-text-lg: 18px;\r\n --cx-leading-xs: 16px;\r\n --cx-leading-sm: 22px;\r\n --cx-leading-md: 24px;\r\n --cx-leading-lg: 28px;\r\n\r\n /* ---- 字重 ---- */\r\n --cx-weight-regular: 400;\r\n --cx-weight-medium: 500;\r\n --cx-weight-semibold: 600;\r\n --cx-weight-bold: 700;\r\n --cx-weight-shell: 445; /* 外壳专用非标准字重 */\r\n\r\n /* ---- 动效 ---- */\r\n --cx-dur-fast: 120ms;\r\n --cx-dur: 150ms;\r\n --cx-dur-relaxed: 300ms;\r\n --cx-ease: ease;\r\n --cx-ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);\r\n --cx-ease-scroll: cubic-bezier(0.22, 1, 0.36, 1);\r\n\r\n /* ---- 布局几何 ----\r\n 以真实 Codex 窗口截图(1988×1292 @1.5x)实测回填:\r\n 侧栏 288、标题栏 44、输入栈 710,详见 02-开发日志 第 10 轮。 */\r\n --cx-titlebar-h: 44px;\r\n --cx-rail-w: 52px;\r\n --cx-sidebar-w: 288px;\r\n --cx-thread-max: 768px;\r\n --cx-right-w: 667px;\r\n --cx-header-h: 48px;\r\n --cx-row-h: 30px;\r\n --cx-control-h: 28px;\r\n --cx-send-h: 32px;\r\n --cx-composer-stack-w: 742px; /* 含左右 16px 内边距 → 内容 710 */\r\n --cx-composer-h: 97px;\r\n --cx-picker-h: 37px;\r\n\r\n /* ---- 输入区实测色(真实截图取色) ---- */\r\n --cx-picker-fill: #282828;\r\n --cx-composer-fill: rgba(55, 55, 55, 0.94);\r\n\r\n /* ---- 应用视口 ----\r\n 真实 DSH 窗口截图 1988×1292(Windows 150% 缩放)换算为 1325×861 CSS。\r\n 原型按此尺寸渲染,各栏占比才与真实界面一致(旧 1600 视口下主区偏宽)。 */\r\n --cx-app-w: 1325px;\r\n --cx-app-h: 861px;\r\n\r\n /* ---- 推理强度选择器 ----\r\n 尺寸/颜色按用户真实 Codex 录屏帧实测(1992×1296 @1.503x)。 */\r\n --cx-reasoning-w: 255px;\r\n --cx-reasoning-snap-dur: 140ms; /* 第 20 轮节点切换过渡,待用户验收 */\r\n --cx-reasoning-fx-dur: 180ms;\r\n --cx-reasoning-text-dur: 220ms;\r\n /* 第 23 轮按用户新设计口径增强流速,两个亮带错速运行。 */\r\n --cx-reasoning-flow-dur: 2.8s;\r\n --cx-reasoning-bloom-dur: 4.2s;\r\n --cx-reasoning-blue: #4c9bfe;\r\n --cx-reasoning-track: #3e3e3e;\r\n /* 第 22 轮:新录屏 100/660/900/1120 帧取色,深底与两种柔亮色分层。 */\r\n --cx-reasoning-violet: #5134ad;\r\n --cx-reasoning-purple: #844fcc;\r\n --cx-reasoning-max-blue: #3a4ebd;\r\n --cx-reasoning-max-glow: #bd8dff;\r\n --cx-reasoning-max-ice: #adaaff;\r\n --cx-reasoning-max-text: #b98cff;\r\n --cx-reasoning-title: #4c9bfe;\r\n /* 浮层/菜单底色:真实录屏帧取色 rgb(45,45,45) */\r\n --cx-popover-fill: #2d2d2d;\r\n\r\n /* ---- 阴影 ---- */\r\n --cx-shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 0.08);\r\n --cx-shadow-md: 0 2px 4px -1px rgb(0 0 0 / 0.08);\r\n --cx-shadow-lg: 0 4px 8px -2px rgb(0 0 0 / 0.1);\r\n --cx-shadow-xl: 0 8px 16px -4px rgb(0 0 0 / 0.12);\r\n --cx-shadow-2xl: 0 16px 32px -8px rgb(0 0 0 / 0.19);\r\n --cx-composer-shadow:\r\n 0 0 0 0.5px var(--cx-border-strong),\r\n 0 3px 7.5px rgb(0 0 0 / 0.04),\r\n 0 0 20px rgb(0 0 0 / 0.05);\r\n\r\n /* ---- z 轴 ---- */\r\n --cx-z-ground: 0;\r\n --cx-z-panel: 10;\r\n --cx-z-floating: 20;\r\n --cx-z-drawer: 40;\r\n --cx-z-popover: 50;\r\n --cx-z-overlay: 70;\r\n}\r\n\n/* ============ 导航栏 ============ */\r\n.dsc-rail {\r\n grid-area: rail;\r\n position: relative;\r\n z-index: 30;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 8px 0;\r\n background: var(--cx-window);\r\n border-right: 1px solid var(--cx-border-l2);\r\n}\r\n.dsc-rail__spacer { flex: 1; }\r\n.dsc-rail__item {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: var(--cx-radius-md);\r\n color: var(--cx-text-3);\r\n transition: background-color var(--cx-dur-fast) var(--cx-ease), color var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-rail__item:hover { background: var(--cx-hover); color: var(--cx-text); }\r\n.dsc-rail__item--active { background: var(--cx-active); color: var(--cx-text); }\r\n/* 插头先以不透明白色统一描边,再整体降低透明度,避免相交线段叠色发亮。 */\r\n.dsc-rail__plugin-icon { color: #fff; opacity: .5; stroke-width: 1.5; transition: opacity var(--cx-dur-fast) var(--cx-ease); }\r\n.dsc-rail__item:hover .dsc-rail__plugin-icon,\r\n.dsc-rail__item--active .dsc-rail__plugin-icon { opacity: 1; }\r\n.dsc-rail__item::after {\r\n content: attr(data-tip);\r\n position: absolute;\r\n left: 44px;\r\n top: 50%;\r\n z-index: var(--cx-z-popover);\r\n padding: 4px 8px;\r\n border-radius: var(--cx-radius-sm);\r\n border: 1px solid var(--cx-border-l1);\r\n background: #2b2b2b;\r\n color: var(--cx-text);\r\n font-size: 12px;\r\n line-height: 16px;\r\n white-space: nowrap;\r\n box-shadow: var(--cx-shadow-lg);\r\n opacity: 0;\r\n transform: translateY(-50%) translateX(-2px);\r\n pointer-events: none;\r\n transition: opacity var(--cx-dur-fast) var(--cx-ease), transform var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-rail__item:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }\r\n\r\n/* ============ 侧栏 ============ */\r\n.dsc-sidebar {\r\n grid-area: sidebar;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n padding: 8px;\r\n background: var(--cx-sidebar);\r\n border-right: 1px solid var(--cx-border-l2);\r\n overflow: hidden;\r\n}\r\n.dsc-sidebar__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n height: 44px;\r\n margin: -8px -8px 6px;\r\n padding: 0 6px 0 10px;\r\n}\r\n.dsc-brand { display: flex; align-items: center; gap: 7px; }\r\n.dsc-brand__mark { display: block; height: 19px; width: auto; }\r\n.dsc-brand__wordmark {\r\n font-size: 17px;\r\n font-weight: 700;\r\n letter-spacing: -0.02em;\r\n color: var(--cx-text);\r\n line-height: 1;\r\n}\r\n.dsc-brand__badge {\r\n font-size: 9px;\r\n font-weight: 700;\r\n letter-spacing: 0.14em;\r\n line-height: 1;\r\n padding: 3px 5px 2px;\r\n border-radius: 3px;\r\n background: var(--cx-text);\r\n color: var(--cx-window);\r\n}\r\n.dsc-sidebar__header-actions { display: flex; align-items: center; gap: 2px; }\r\n.dsc-new-chat {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n height: 32px;\r\n padding: 0 8px;\r\n border-radius: var(--cx-radius-md);\r\n font-size: 13px;\r\n font-weight: var(--cx-weight-medium);\r\n color: var(--cx-text);\r\n background: var(--cx-active);\r\n transition: background-color var(--cx-dur-fast) var(--cx-ease);\r\n}\r\n.dsc-new-chat:hover { background: rgba(255, 255, 255, 0.12); }\r\n.dsc-new-chat kbd { margin-left: auto; font-size: 11px; color: var(--cx-text-4); }\r\n\n/* 自有组件与公开 slot/region 锚点;外壳几何适配通过自有 data-dsc-* 标记集中处理。 */\nbody[data-dsh-codex-style] { background: var(--cx-window); color-scheme: dark; --shiki-token-constant: #4dabf7; --shiki-token-string: #69db7c; --shiki-token-comment: #adb5bd; --shiki-token-keyword: #faa2c1; --shiki-token-function: #b197fc; }\n/* 整个三列外壳实际缩窄 52px,让宿主 ResizeObserver、右栏与拖拽使用同一可用宽度。\n 不能用 padding-left 代替:宿主测量的是 getBoundingClientRect().width。 */\nbody[data-dsh-codex-chrome] [data-dsc-frame] { margin-left: var(--cx-rail-w); width: calc(100% - var(--cx-rail-w)); }\n/* 全屏右栏原本按 100vw 展开;扣掉独立导航后,继续尊重宿主二级侧栏的真实宽度。 */\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame] [data-sidebar-right-panel=\"fullscreen\"] { max-width: max(0px, calc(100vw - var(--cx-rail-w) - var(--dsh-windows-sidebar-width, 0px))); }\n/* data-slot 是 renderer 声明的可寻址面;这里只传递变量,不选择/搬移宿主内部节点。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] { --dsw-specific-input-major: var(--cx-composer-fill); --dsw-radius-panel: var(--cx-radius-composer); --dsw-alias-border-l2: var(--cx-border-strong); --dsw-elevation-soft: var(--cx-composer-shadow); --dsh-composer-card-max-width: 710px; }\n/* DSH 0.2.1-rc.2 中英文发送标签;只覆盖发送色,保留停止按钮与宿主提交状态。 */\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]) { --dsw-alias-button-info-fill: #dfdfdf; --dsw-alias-button-info-hover: #dfdfdf; color: #363636; opacity: 1; }\nbody[data-dsh-codex-style] [data-slot=\"conversation.composer.bar\"] [data-composer-card] button:is([aria-label=\"发送消息\"], [aria-label=\"排队发送\"], [aria-label=\"插话发送\"], [aria-label=\"Send message\"], [aria-label=\"Queue message\"], [aria-label=\"Steer message\"]):disabled { --dsw-alias-button-info-fill: #8b8b8b; }\n/* 正文字体颜色:按 Codex 截图采样 #dfdfdf(原 #ffffff 偏亮)。仅覆盖对话内容区,不影响按钮、菜单等其它主文本。 */\nbody[data-dsh-codex-style] [data-conversation-content] { --dsw-alias-label-primary: #dfdfdf; }\n/* 原 sidebar/settings 父级继续挂载以保留原生页面与子槽位所有权;只隐藏其旧外观。 */\nbody[data-dsh-codex-chrome] [data-slot=\"sidebar\"] { display: block !important; position: relative; width: 100%; height: 100%; visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-slot=\"conversation.composer.bar\"] { --dsh-composer-side-clearance: 0px; display: flex !important; flex-direction: column; align-items: stretch; align-self: center; width: min(calc(100% - 32px),710px); max-width: 710px; position: relative; }\n/* 空态保留官方编辑器、草稿、附件、“+”与 chain;没有重建输入状态或移动节点。 */\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] { visibility: hidden; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-region=\"composer\"] { position: absolute; inset: auto 0 0; z-index: 7; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.composer.bar\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-slot=\"conversation.input.dock\"],\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"] [data-conversation-composer-overlay] { visibility: visible; }\nbody[data-dsh-codex-chrome] [data-conversation-content][data-content-phase=\"hero\"][data-dsc-hero-layout] [data-slot=\"conversation.hero.agentPreset\"] { visibility: visible; display: inline-flex !important; position: fixed; top: calc(var(--dsc-hero-card-top) - 33px); right: calc(100vw - var(--dsc-hero-card-right) + 18px); z-index: 8; height: 28px; max-width: 180px; }\n.dsc-ui { font: 14px/22px var(--cx-font-sans); color: var(--cx-text); box-sizing: border-box; }\n.dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { box-sizing: border-box; }\n.dsc-ui button, .dsc-ui input { font: inherit; color: inherit; }\n.dsc-ui button { border: 0; cursor: pointer; background: none; }\n.dsc-ui button:disabled { cursor: default; opacity: .45; }\n.dsc-ui button:focus-visible, .dsc-ui [role=slider]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: 2px; }\n.dsc-ui svg { flex: none; }\n.dsc-update-control { display: inline-flex; align-items: center; -webkit-app-region: no-drag; }\n.dsc-ui.dsc-update-panel { padding: 14px; }\n.dsc-update-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }\n.dsc-ui .dsc-update-close { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border-radius: 6px; color: var(--cx-text-2); }\n.dsc-update-close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-update-message { margin: 14px 0 10px; font-weight: 500; }\n.dsc-update-message[data-state=update] { color: #72b4ff; }\n.dsc-update-message[data-state=error] { color: #ff8585; }\n.dsc-update-versions { display: grid; grid-template-columns: auto 1fr; gap: 4px 16px; margin: 0; font-size: 13px; }\n.dsc-update-versions dt { color: var(--cx-text-2); }\n.dsc-update-versions dd { margin: 0; text-align: right; overflow-wrap: anywhere; }\n.dsc-update-note { margin: 10px 0 0; color: var(--cx-text-2); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }\n.dsc-update-note > span { display: block; color: var(--cx-text-3); }\n.dsc-ui .dsc-update-retry { margin-top: 14px; padding: 5px 10px; border: 1px solid var(--cx-border-l3); border-radius: 7px; }\n.dsc-update-retry:hover { background: var(--cx-hover); }\n.dsc-update-spin { animation: dsc-update-spin .9s linear infinite; }\n@keyframes dsc-update-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) { .dsc-update-spin { animation: none; } }\n.dsc-proto-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; line-height: 1; }\n.dsc-proto-icon svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }\n/* 同一内容 portal 到 body:常驻时跟随宿主列,退场与浮卡不受 0px 列裁剪。只动画透明度和位移。 */\n.dsc-sidebar-shell { visibility: hidden; position: fixed; z-index: 20; min-width: 0; min-height: 0; display: flex; opacity: 0; transform: translateX(-24px); pointer-events: none; transition: opacity 180ms ease, transform 240ms var(--cx-ease-out), visibility 0s 240ms; -webkit-app-region: no-drag; }\n.dsc-sidebar-shell[data-open=true] { visibility: visible; opacity: 1; transform: translateX(0); pointer-events: auto; transition: opacity 180ms ease, transform 260ms var(--cx-ease-out), visibility 0s; }\n.dsc-sidebar-shell[data-mode=docked] > .dsc-sidebar { border-top-left-radius: 16px; }\n.dsc-sidebar-shell[data-mode=floating] { z-index: 40; transform: translateX(-12px); transition: opacity 150ms ease, transform 180ms var(--cx-ease-out), visibility 0s 180ms; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] { transform: translateX(0); transition-delay: 0s; }\n.dsc-sidebar-shell[data-mode=floating]::before { content: ''; position: absolute; top: 0; bottom: 0; left: -12px; width: 12px; }\n.dsc-sidebar-shell[data-mode=floating] > .dsc-sidebar { --cx-sidebar: #2c2c2c; border: 1px solid var(--cx-border-l1); border-radius: 12px; box-shadow: 0 0 0 3px #18181880, 0 8px 28px #0005; }\n.dsc-sidebar-shell[data-mode=floating][data-open=true] > .dsc-sidebar { animation: dsc-sidebar-content-in 150ms var(--cx-ease-out) both; }\n@keyframes dsc-sidebar-content-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: translateX(0); } }\n.dsc-independent-rail { visibility: visible; position: fixed; top: var(--dsh-frame-chrome-top, 0px); bottom: 0; left: 0; width: var(--cx-rail-w); z-index: 25; min-height: 0; padding-bottom: 56px; -webkit-app-region: no-drag; }\n.dsc-extension-navigation-anchor { flex: 1; align-self: stretch; min-height: 0; }\n/* 原生 panellist 继续负责图标注入、选中态、点击和卸载;只重排其可见区域。\n 位置来自自有轨道的剩余空间,超高时独立滚动,不覆盖底部齿轮。\n Windows 原样式在 collapsed 时 display:none,并在开合时淡出,故明确覆盖这两项。 */\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] { visibility: visible; display: flex !important; position: fixed; left: var(--dsc-extension-left); top: var(--dsc-extension-top); width: var(--dsc-extension-width); max-height: var(--dsc-extension-height); min-height: 0; z-index: 26; flex-direction: column; align-items: center; gap: 4px !important; padding: 0; margin: 0 !important; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ffffff26 transparent; opacity: 1 !important; transform: none !important; animation: none !important; transition: none !important; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-hidden] { display: none !important; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row] { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; min-height: 36px; padding: 0; margin: 0; border: 0; border-radius: var(--cx-radius-md); background: transparent; color: var(--cx-text-3); cursor: pointer; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:hover { color: var(--cx-text); background: var(--cx-hover); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row][aria-current=page] { color: var(--cx-text); background: var(--cx-active); }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-row]:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-label] { display: none; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }\nbody[data-dsh-codex-chrome] [data-dsc-extension-nav] [data-dsc-extension-glyph] svg { display: block; max-width: 20px; max-height: 20px; }\n.dsc-navigation-error { position: fixed; left: calc(var(--cx-rail-w) + 8px); top: var(--dsh-frame-overlay-top, 20px); z-index: 50; max-width: 320px; }\n.dsc-rail .dsc-rail__item { color: var(--cx-text-3); background: transparent; flex: none; padding: 0; }\n.dsc-rail .dsc-rail__item:hover { color: var(--cx-text); background: var(--cx-hover); }\n.dsc-rail .dsc-rail__item--active { color: var(--cx-text); background: var(--cx-active); }\n.dsc-rail__item[data-preview-enabled=true]::after { display: none; }\n.dsc-sidebar { flex: 1; min-width: 0; min-height: 0; overflow: hidden; }\n.dsc-sidebar .dsc-sidebar__header { flex: none; }\n.dsc-sidebar .dsc-brand { min-width: 0; gap: 6px; }\n.dsc-sidebar .dsc-brand__mark { height: 19px; max-width: 25px; }\n.dsc-sidebar .dsc-brand__badge { font-size: 8px; padding: 3px 4px 2px; }\n.dsc-sidebar .dsc-sidebar__header-actions { margin-left: auto; }\n/* Codex:新聊天默认无底色、不显示快捷键,仅悬停时出现灰色背景。 */\n.dsc-sidebar .dsc-new-chat { flex: none; justify-content: flex-start; width: 100%; font-size: 13px; font-weight: var(--cx-weight-medium); background: transparent; color: #c2c2c2; margin-bottom: 8px; }\n.dsc-sidebar .dsc-new-chat:hover { background: rgba(255,255,255,.08); }\n.dsc-sidebar .dsc-workspaces { flex: 1; min-height: 0; height: auto; }\n/* 同时限定两个自有类,避免后面的通用 .dsc-search 把它重新变成伸展项。 */\n.dsc-search.dsc-sidebar-search { flex: none; height: 32px; margin: 2px 0 8px; min-height: 32px; }\n.dsc-sidebar-search button { padding: 0 3px; }\n.dsc-sidebar .dsc-tree-tools { justify-content: space-between; padding: 12px 0 4px 8px; }\n.dsc-tree-tools-label { font-size: 12px; color: var(--cx-text-3); }\n.dsc-projects-toggle { display: flex; align-items: center; gap: 4px; height: 28px; padding: 0; }\n.dsc-projects-toggle svg { color: var(--cx-text-3); transform: rotate(-90deg); transition: transform 180ms ease; }\n.dsc-projects-toggle[aria-expanded=true] svg { transform: rotate(0); }\n.dsc-sidebar .dsc-tree-scroll { padding: 0; scrollbar-color: #ffffff26 transparent; scrollbar-width: thin; }\n.dsc-section-page-title { font-size: 17px; font-weight: 600; }\n.dsc-secondary-nav { display: flex; flex-direction: column; gap: 4px; min-height: 0; overflow: auto; padding-top: 6px; }\n.dsc-secondary-nav .dsc-secondary-item { display: flex; align-items: center; gap: 8px; min-height: 32px; width: 100%; padding: 0 8px; border-radius: var(--cx-radius-md); text-align: left; color: var(--cx-text-2); font-size: 13px; }\n.dsc-secondary-nav .dsc-secondary-item.is-active, .dsc-secondary-nav .dsc-secondary-item:hover { background: var(--cx-active); color: var(--cx-text); }\n.dsc-secondary-item .dsc-proto-icon svg { width: 16px; height: 16px; }\n.dsc-window-sidebar-toggle { visibility: visible; position: fixed; top: 6px; left: 12px; z-index: 35; -webkit-app-region: no-drag; }\n.dsc-collapsed-new-chat { display: none; align-items: center; margin-right: 8px; -webkit-app-region: no-drag; }\nbody[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] .dsc-collapsed-new-chat { display: flex; }\n.dsc-collapsed-new-chat .dsc-icon-button { width: 28px; height: 28px; }\n.dsc-gear-launcher { visibility: visible; position: fixed; left: 8px; bottom: 12px; z-index: 35; }\n.dsc-gear-launcher .dsc-gear-button { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border-radius: var(--cx-radius-md); color: var(--cx-text-3); }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: var(--cx-hover); color: var(--cx-text); }\n/* 自绘悬停提示,避免系统 title 突然出现;菜单打开时不叠加提示。 */\n.dsc-gear-launcher .dsc-gear-button { position: relative; isolation: isolate; transition: color 150ms ease; }\n.dsc-gear-launcher .dsc-gear-button:hover, .dsc-gear-launcher .dsc-gear-button[aria-expanded=true] { background: none; }\n.dsc-gear-button::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--cx-hover); opacity: 0; transition: opacity 150ms ease; pointer-events: none; }\n.dsc-gear-button:hover::before, .dsc-gear-button[aria-expanded=true]::before, .dsc-gear-button:focus-visible::before { opacity: 1; }\n.dsc-gear-button::after { content: attr(aria-label); position: absolute; left: calc(100% + 8px); top: 50%; padding: 5px 9px; border: 1px solid var(--cx-border-l1); border-radius: 7px; background: var(--cx-popover-fill); color: var(--cx-text); font-size: 12px; line-height: 18px; white-space: nowrap; box-shadow: 0 4px 12px #0004; pointer-events: none; opacity: 0; transform: translate(-3px, -50%); transition: opacity 150ms ease, transform 150ms ease; }\n.dsc-gear-button[aria-expanded=false]:hover::after, .dsc-gear-button[aria-expanded=false]:focus-visible::after { opacity: 1; transform: translate(0, -50%); }\n.dsc-app-menu { padding: 5px; }\n.dsc-menu-separator { height: 1px; margin: 4px 6px; background: var(--cx-border-l1); }\n.dsc-app-menu .dsc-account-row { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px; text-align: left; border-radius: var(--cx-radius-md); }\n.dsc-account-row:hover { background: var(--cx-active); }\n.dsc-account-row > img, .dsc-account-avatar { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--cx-active); object-fit: cover; }\n.dsc-account-row > span:last-child { display: flex; flex-direction: column; min-width: 0; }\n.dsc-account-row strong { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-account-row small { font-size: 11px; color: var(--cx-text-3); }\n.dsc-empty-hero { visibility: visible; position: fixed; z-index: 2; display: flex; flex-direction: column; align-items: center; gap: 24px; transform: translate(-50%,-50%); pointer-events: none; }\n.dsc-empty-hero svg { color: #525252; }\n.dsc-empty-hero h1 { font-size: 26px; line-height: 34px; font-weight: 400; color: var(--cx-text-2); margin: 0; white-space: nowrap; }\n.dsc-hero-projectbar { visibility: visible; position: fixed; z-index: 6; height: calc(var(--cx-picker-h) + var(--cx-radius-composer)); padding: 4px 4px var(--cx-radius-composer); background: var(--cx-picker-fill); border-radius: 20px; }\n.dsc-hero-projectbar .dsc-project-picker { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; max-width: calc(100% - 180px); color: var(--cx-text-2); font-size: 13px; border-radius: var(--cx-radius-full); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-hero-projectbar .dsc-project-picker:hover, .dsc-hero-projectbar .dsc-project-picker[aria-expanded=true] { background: var(--cx-active); }\n.dsc-ui button:active:not(:disabled) { transform: translateY(1px); }\n.dsc-brand-mark { width: 25px; height: 20px; object-fit: contain; }\n.dsc-brand { display: inline-flex; align-items: center; gap: 7px; }\n.dsc-brand strong { font-size: 17px; letter-spacing: -.02em; }\n.dsc-brand small { font: 700 8px/16px var(--cx-font-sans); letter-spacing: .12em; padding: 0 4px; color: #131313; background: #eee; border-radius: 3px; }\n.dsc-settings-trigger { display: inline-flex; gap: 8px; align-items: center; }\n.dsc-workspaces { height: 100%; min-height: 0; width: 100%; display: flex; flex-direction: column; background: var(--cx-sidebar); }\n.dsc-tree-tools { display: flex; gap: 4px; align-items: center; padding: 6px 8px 12px; }\n.dsc-search { flex: 1; display: flex; align-items: center; gap: 6px; min-width: 0; border-radius: 8px; background: #ffffff08; padding: 3px 8px; color: var(--cx-text-3); }\n.dsc-search input { min-width: 0; width: 100%; border: 0; outline: none; background: none; font-size: 12px; }\n.dsc-search:focus-within { box-shadow: 0 0 0 1px var(--cx-border-l3); }\n.dsc-search input::placeholder { color: var(--cx-text-3); }\n.dsc-icon-button { width: 28px; height: 28px; display: grid; place-items: center; border-radius: 7px; color: var(--cx-text-2); }\n.dsc-icon-button:hover { background: var(--cx-active); }\n.dsc-tree-scroll { flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; padding: 0 8px; }\n.dsc-section-title { padding: 12px 8px 6px; font-size: 12px; font-weight: 500; color: var(--cx-text-3); }\n.dsc-thread, .dsc-project { display: flex; align-items: center; height: 30px; border-radius: 7px; position: relative; }\n.dsc-thread:hover, .dsc-project:hover, .dsc-thread.is-active { background: #ffffff0d; }\n.dsc-ui .dsc-thread-main, .dsc-ui .dsc-project-main { display: flex; flex: 1; align-items: center; gap: 7px; min-width: 0; height: 100%; padding: 0 8px; text-align: left; color: #c2c2c2; }\n.dsc-thread-title, .dsc-project-main > span { min-width: 0; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.dsc-ui .dsc-thread-main { padding-right: 62px; }\n.dsc-thread-title-text { display: inline-block; width: max-content; max-width: none; vertical-align: top; }\n.dsc-thread-title[data-overflow=true] { text-overflow: clip; mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent); }\n.dsc-thread:hover .dsc-thread-title[data-overflow=true] .dsc-thread-title-text,\n.dsc-thread-main:focus-visible .dsc-thread-title[data-overflow=true] .dsc-thread-title-text { animation: dsc-title-scroll var(--dsc-title-duration) linear 250ms infinite alternate; }\n@keyframes dsc-title-scroll { from { transform: translateX(0); } to { transform: translateX(var(--dsc-title-shift)); } }\n.dsc-thread-time { position: absolute; right: 9px; font-size: 10px; color: var(--cx-text-4); transition: opacity 120ms ease; }\n.dsc-thread:hover .dsc-thread-time, .dsc-thread:has(:focus-visible) .dsc-thread-time { display: none; }\n.dsc-thread .dsc-state[data-state=idle] { display: none; }\n.dsc-thread-actions { position: absolute; top: 0; right: 4px; height: 100%; display: flex; align-items: center; gap: 1px; opacity: 0; pointer-events: none; transition: opacity 150ms ease; }\n.dsc-thread:hover .dsc-thread-actions, .dsc-thread:has(:focus-visible) .dsc-thread-actions { opacity: 1; pointer-events: auto; }\n.dsc-ui .dsc-thread-action { display: grid; place-items: center; flex: none; width: 25px; height: 26px; padding: 0; border-radius: 5px; color: #969696; transition: color 120ms ease; }\n.dsc-ui .dsc-thread-action:hover, .dsc-ui .dsc-thread-action:focus-visible { color: #d2d2d2; }\n.dsc-thread-action svg { width: 14px; height: 14px; }\n.dsc-row-tooltip { position: fixed; z-index: 10001; pointer-events: none; padding: 5px 11px; border: 1px solid #ffffff08; border-radius: 13px; color: #dfdfdf; background: #2d2d2d; font-size: 13px; line-height: 18px; white-space: nowrap; box-shadow: 0 2px 5px #0002; opacity: 0; visibility: hidden; transform: translateY(3px); transition: opacity 80ms ease, transform 80ms ease, visibility 0s 80ms; }\n.dsc-row-tooltip[data-open=true] { opacity: 1; visibility: visible; transform: translateY(0); transition: opacity 120ms ease 350ms, transform 120ms ease 350ms, visibility 0s 350ms; }\n.dsc-state { color: var(--cx-text-3); flex: none; display: flex; align-items: center; }\n.dsc-state[data-state=pending] { color: var(--cx-warning); }\n.dsc-state[data-state=running] { color: var(--cx-accent-soft); animation: dsc-status-pulse 1.2s ease-in-out infinite; }\n.dsc-state[data-state=done] { color: var(--cx-success); }\n.dsc-row-action { display: flex; opacity: 0; pointer-events: none; transition: opacity 150ms ease; width: 25px; height: 25px; align-items: center; justify-content: center; border-radius: 5px; padding: 0; margin-right: 3px; color: var(--cx-text-3); }\n.dsc-thread:hover .dsc-row-action, .dsc-project:hover .dsc-row-action, .dsc-thread:has(:focus-visible) .dsc-row-action, .dsc-project:has(:focus-visible) .dsc-row-action { opacity: 1; pointer-events: auto; }\n.dsc-row-action:hover { color: #fff; background: var(--cx-active); }\n/* 本轮按用户录像为项目内容局部开放布局动画;外壳与其他面板的尺寸规则保持原约定。 */\n.dsc-project-fold { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 180ms cubic-bezier(.2,.8,.2,1), opacity 150ms ease; }\n.dsc-project-fold[data-open=true] { grid-template-rows: 1fr; opacity: 1; }\n.dsc-project-clip { min-height: 0; overflow: hidden; }\n.dsc-project-children { padding-left: 14px; }\n.dsc-project-children, .dsc-project-list { transform: translateY(-4px); transition: transform 180ms cubic-bezier(.2,.8,.2,1); }\n.dsc-project-fold[data-open=true] > .dsc-project-clip > :is(.dsc-project-children, .dsc-project-list) { transform: translateY(0); }\n.dsc-project-children .dsc-state { display: none; }\n.dsc-empty { display: block; color: var(--cx-text-3); padding: 10px 12px; font-size: 12px; }\n.dsc-archive-toggle { padding: 8px 12px; display: flex; gap: 6px; align-items: center; font-size: 11px; color: var(--cx-text-3); }\n.dsc-archive-toggle input { accent-color: var(--cx-accent); }\n.dsc-thread.is-archived { opacity: .55; }\n.dsc-error { padding: 4px 8px; color: var(--cx-danger); font: 12px/17px var(--cx-font-sans); white-space: normal; max-width: 310px; }\n.dsc-menu { z-index: 10000; padding: 6px; background: var(--cx-popover-fill); border: 1px solid var(--cx-border-l1); border-radius: 14px; box-shadow: 0 12px 36px #0007; overflow: auto; animation: dsc-pop-in 130ms ease both; }\n.dsc-menu-heading { font-size: 12px; color: var(--cx-text-3); padding: 5px 8px 8px; }\n.dsc-option { width: 100%; display: flex; gap: 9px; align-items: center; text-align: left; padding: 7px 8px; border-radius: 7px; min-height: 32px; }\n.dsc-option:hover, .dsc-option:focus-visible { background: #ffffff12; }\n.dsc-option-copy { display: flex; flex: 1; min-width: 0; flex-direction: column; line-height: 20px; }\n.dsc-option-copy small { font-size: 12px; color: var(--cx-text-3); line-height: 17px; }\n/* 行悬停详情卡:几何按原型实测(项目卡 321×130、右侧 5px;会话卡 470 宽、右侧 12px、上移 6px)。 */\n.dsc-row-pop { position: fixed; z-index: 10000; max-width: calc(100vw - 32px); padding: 6px; border-radius: var(--cx-radius-xl); border: 1px solid var(--cx-border-l1); background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); animation: dsc-row-pop-in 120ms var(--cx-ease-out) both; }\n.dsc-row-pop--project { width: 321px; padding: 8px; }\n.dsc-row-pop--thread { width: 470px; }\n.dsc-row-pop__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 8px 8px 10px; border-bottom: 1px solid var(--cx-border-l2); }\n.dsc-row-pop--project .dsc-row-pop__head { align-items: center; height: 28px; padding: 0 8px; border-bottom: 0; }\n.dsc-row-pop__name { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 14px; font-weight: var(--cx-weight-regular); color: var(--cx-text); }\n.dsc-row-pop__name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop__pin { display: inline-flex; align-items: center; flex: none; color: var(--cx-text-3); }\n.dsc-row-pop__excerpt { margin: 0; font-size: 13px; line-height: 20px; color: var(--cx-text); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n.dsc-row-pop__meta { display: flex; align-items: center; gap: 6px; flex: none; font-size: 12px; color: var(--cx-text-3); white-space: nowrap; }\n.dsc-row-pop__line { display: flex; align-items: center; gap: 8px; width: 100%; height: 32px; padding: 0 8px; border-radius: var(--cx-radius-md); font-size: 13px; color: var(--cx-text-2); text-align: left; }\n.dsc-row-pop__line svg { flex: none; color: var(--cx-text-3); }\n.dsc-row-pop--project .dsc-row-pop__line { height: 28px; font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--project .dsc-row-pop__line svg { color: var(--cx-text-2); }\n.dsc-row-pop__line--path, .dsc-row-pop__line--action { cursor: pointer; }\n.dsc-row-pop__line--path:hover, .dsc-row-pop__line--action:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-row-pop__line--path:focus-visible, .dsc-row-pop__line--action:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: -2px; }\n.dsc-row-pop__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-row-pop--project .dsc-row-pop__path { font-family: var(--cx-font-sans); font-size: 14px; color: var(--cx-text); }\n.dsc-row-pop--thread .dsc-row-pop__path { font-family: var(--cx-font-mono); font-size: 12px; }\n.dsc-row-pop__arrow { margin-left: auto; opacity: 0; transition: opacity 120ms var(--cx-ease); }\n.dsc-row-pop__line--path:hover .dsc-row-pop__arrow, .dsc-row-pop__line--path:focus-visible .dsc-row-pop__arrow { opacity: 1; }\n.dsc-row-pop__sep { height: 1px; background: var(--cx-border-l1); }\n@keyframes dsc-row-pop-in { from { opacity: 0; } to { opacity: 1; } }\n/* 「编辑项目」对话框:按参考图(面板 #2d2d2d、16 圆角、保存白底、移除暗红底);源文件夹只读展示。 */\n.dsc-modal { position: fixed; inset: 0; z-index: 11000; display: flex; align-items: center; justify-content: center; padding: 24px; }\n.dsc-modal__mask { position: absolute; inset: 0; background: rgba(0,0,0,.12); }\n.dsc-modal__panel { position: relative; display: flex; flex-direction: column; width: min(520px, calc(100vw - 32px)); padding: 24px; border-radius: 16px; background: var(--cx-popover-fill); box-shadow: var(--cx-shadow-2xl); color: var(--cx-text); }\n.dsc-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }\n.dsc-modal__head h2 { margin: 0; font-size: 20px; line-height: 28px; font-weight: 600; }\n.dsc-modal__close { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border-radius: var(--cx-radius-sm); color: var(--cx-text-2); }\n.dsc-modal__close:hover { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__field { display: flex; align-items: center; gap: 10px; height: 44px; margin-top: 20px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); }\n.dsc-modal__field:focus-within { border-color: var(--cx-focus); }\n.dsc-modal__input { flex: 1; min-width: 0; height: 100%; border: 0; outline: none; background: none; font: 15px/22px var(--cx-font-sans); color: var(--cx-text); }\n.dsc-modal__input::placeholder { color: var(--cx-text-3); }\n.dsc-modal__label { margin: 20px 0 8px; font-size: 13px; line-height: 18px; color: var(--cx-text-3); }\n.dsc-modal__source { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 12px; border: 1px solid var(--cx-border-strong); border-radius: var(--cx-radius-md); color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsc-modal .dsc-error { margin-top: 12px; }\n.dsc-modal__actions { display: flex; align-items: center; gap: 8px; margin-top: 24px; }\n.dsc-modal__spacer { flex: 1; }\n.dsc-modal__remove { height: 36px; padding: 0 16px; border-radius: 999px; background: rgba(250,102,101,.14); color: #fa6665; font-size: 14px; }\n.dsc-modal__remove:hover:not(:disabled) { background: rgba(250,102,101,.22); }\n.dsc-modal__cancel { height: 36px; padding: 0 14px; border-radius: 999px; color: var(--cx-text-2); font-size: 14px; }\n.dsc-modal__cancel:hover:not(:disabled) { background: var(--cx-hover); color: var(--cx-text); }\n.dsc-modal__save { height: 36px; padding: 0 20px; border-radius: 999px; background: #dfdfdf; color: #131313; font-size: 14px; font-weight: 500; }\n.dsc-modal__save:hover:not(:disabled) { background: #fff; }\n.dsc-modal__save:disabled, .dsc-modal__remove:disabled, .dsc-modal__cancel:disabled { opacity: .6; cursor: default; }\n.dsc-permission, .dsc-model { position: relative; display: inline-flex; align-items: center; min-width: 0; }\n.dsc-model { gap: 2px; }\n.dsc-control-status { display: inline-flex; align-items: center; gap: 5px; padding: 0 7px; color: var(--cx-text-3); font-size: 11px; }\n.dsc-control-status button { padding: 2px 4px; text-decoration: underline; }\n.dsc-control-status button:hover { color: var(--cx-text); }\n.dsc-pill { height: 28px; display: inline-flex; align-items: center; gap: 6px; padding: 0 10px; border-radius: 999px; white-space: nowrap; color: var(--cx-text-2); font-size: 14px !important; }\n.dsc-pill:hover, .dsc-pill[aria-expanded=true] { background: #ffffff12; color: white; }\n.dsc-pill[data-warning=true] { color: var(--cx-warning); }\n.dsc-model-trigger { max-width: 260px; gap: 5px; }\n/* 右下角触发器:模型名偏白、思维强度灰色(参考图 4)。 */\n.dsc-model-trigger .dsc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: #dfdfdf; }\n.dsc-model-trigger .dsc-effort-name { flex: none; color: #989898; }\n.dsc-model-trigger svg { flex: none; color: #989898; }\n/* 「修改模型」面板:标题与列表样式明确区别于宿主原生菜单。 */\n.dsc-menu.dsc-model-panel { padding: 8px; }\n.dsc-menu.dsc-model-panel .dsc-menu-heading { font-size: 14px; line-height: 20px; color: var(--cx-text); font-weight: 500; padding: 6px 8px 10px; }\n.dsc-model-group { color: var(--cx-text-3); font-size: 11px; padding: 7px 8px 2px; }\n.dsc-model-search { border: 0; border-bottom: 1px solid var(--cx-border-l1); background: none; outline: none; padding: 6px 8px; width: 100%; margin-bottom: 3px; }\n.dsc-back { display: flex; align-items: center; gap: 4px; color: var(--cx-text-3); font-size: 12px !important; border-radius: 5px; }\n.dsc-back:hover { color: #fff; }\n/* 滑条弹层裁掉横向溢出:Max/Low/High 档的撒点动画会向右超出边界,触发多余的水平滚动条。 */\n.dsc-reasoning-pop { padding: 12px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); overflow: hidden; animation: dsc-reasoning-in 130ms var(--cx-ease) both; }\n.dsc-reasoning { position: relative; }\n.dsc-reasoning-heading { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0 20px 10px; text-align: center; }\n.dsc-reasoning-heading strong { font-size: 14px; font-weight: var(--cx-weight-semibold); line-height: 18px; color: var(--cx-reasoning-title); transition: color 150ms ease; }\n.dsc-reasoning-model { display: inline-flex; align-items: center; justify-content: center; gap: 2px; max-width: 100%; padding: 4px 10px; border-radius: var(--cx-radius-md); color: #ffffff9e !important; font-size: 12px !important; line-height: 16px !important; white-space: nowrap; transition: background-color 120ms var(--cx-ease), color 120ms var(--cx-ease); }\n.dsc-reasoning-model:hover { background: #3c3b3b; color: var(--cx-text) !important; }\n.dsc-reasoning-reset { position: absolute; top: -4px; right: -4px; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: var(--cx-radius-sm); color: var(--cx-text-3) !important; }\n.dsc-reasoning-reset:hover { background: var(--cx-hover); color: var(--cx-text) !important; }\n.dsc-reasoning[data-level=max] .dsc-reasoning-heading strong { color: var(--cx-reasoning-max-text); }\n.dsc-slider { height: 32px; position: relative; margin: 0 2px; border-radius: 999px; cursor: pointer; touch-action: none; outline: none; }\n/* 提交期间保持原有视觉:只禁用拖拽,不切换光标/透明度,避免切换档位时闪变。 */\n.dsc-slider[aria-disabled=true] { cursor: default; opacity: 1; }\n.dsc-slider-track { position: absolute; inset: 4px 0; height: 24px; border-radius: 999px; background: var(--cx-reasoning-track); overflow: hidden; }\n.dsc-slider-fill { position: absolute; inset: 0; border-radius: inherit; background: var(--cx-reasoning-blue); transform-origin: left center; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-slider-max { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg,var(--cx-reasoning-max-blue),var(--cx-reasoning-purple) 52%,var(--cx-reasoning-violet)); opacity: 0; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-slider-max { opacity: 1; }\n.dsc-slider-max::before, .dsc-slider-max::after { content: ''; position: absolute; animation-play-state: paused; }\n.dsc-slider-max::before { inset: -50% -25%; background: radial-gradient(ellipse,var(--cx-reasoning-max-ice),#a095ffd9 22%,transparent 58%); animation: dsc-max-flow 2.8s cubic-bezier(.4,0,.6,1) -.45s infinite alternate; animation-play-state: paused; }\n.dsc-slider-max::after { inset: -40% -30%; background: radial-gradient(ellipse,#d6a2ff,var(--cx-reasoning-max-glow) 20%,transparent 55%); animation: dsc-max-bloom 4.2s cubic-bezier(.4,0,.6,1) -1.65s infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-slider-max::before, .dsc-slider[data-level=max] .dsc-slider-max::after { animation-play-state: running; }\n.dsc-stars { position: absolute; inset: 0; opacity: 0; pointer-events: none; transition: opacity 180ms ease; }\n.dsc-slider[data-level=max] .dsc-stars { opacity: 1; }\n.dsc-stars i { position: absolute; border-radius: 50%; background: #fff; box-shadow: 0 0 3px #e0d6ff8c; animation: dsc-star-drift var(--pdur) cubic-bezier(.4,0,.6,1) var(--pdelay) infinite alternate; animation-play-state: paused; }\n.dsc-slider[data-level=max] .dsc-stars i { animation-play-state: running; }\n.dsc-tick { position: absolute; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: #ffffff38; transform: translate(-50%,-50%); transition: opacity 150ms ease,transform 120ms ease; }\n.dsc-tick.is-on { background: #ffffff4d; }\n.dsc-slider[data-level=max] .dsc-tick { opacity: 0; }\n.dsc-thumb-position { position: absolute; inset: 0; pointer-events: none; transition: transform 140ms var(--cx-ease-scroll); }\n.dsc-thumb { position: absolute; top: 50%; left: 14px; width: 28px; height: 28px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px #0007,0 0 0 1px #0000000f; transform: translate(-50%,-50%); pointer-events: auto; transition: transform 140ms var(--cx-ease-out),box-shadow 140ms var(--cx-ease); }\n/* 固定白色滑块为正圆,避免宿主全局圆角规则覆盖插件外形。 */\n.dsc-ui .dsc-slider .dsc-thumb { border-radius: 50% !important; }\n.dsc-thumb:hover { transform: translate(-50%,-50%) scale(1.1); box-shadow: 0 1px 6px #0008,0 0 0 1px #0000000f; }\n.dsc-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.dsc-permission-menu { padding: 4px; border-radius: var(--cx-radius-2xl); box-shadow: var(--cx-shadow-2xl); }\n.dsc-permission-heading { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px 8px; color: var(--cx-text-3); font-size: 13px; line-height: 18px; }\n.dsc-permission-heading button { padding: 0; white-space: nowrap; text-decoration: underline; text-underline-offset: 2px; }\n.dsc-permission-help { margin: 4px 8px 8px; color: var(--cx-text-2); font-size: 12px; line-height: 18px; }\n.dsc-permission-menu .dsc-option { align-items: flex-start; gap: 8px; padding: 6px 8px; border-radius: var(--cx-radius-xl); color: var(--cx-text-2); font-size: 14px; line-height: 19px; }\n.dsc-permission-menu .dsc-option > svg { margin-top: 3px; }\n.dsc-permission-menu .dsc-option-copy { line-height: 19px; }\n.dsc-permission-menu .dsc-option[data-warning=true], .dsc-permission-menu .dsc-option[data-warning=true] small { color: var(--cx-warning); }\n.dsc-burst { position: absolute; left: 14px; top: 50%; }\n.dsc-burst i { position: absolute; width: 3px; height: 3px; border-radius: 50%; background: #b98cff; animation: dsc-burst 280ms cubic-bezier(.22,1,.36,1) both; }\n.dsc-slider-labels { display: flex; justify-content: space-between; padding-top: 3px; }\n.dsc-slider-labels button { padding: 0 3px; font-size: 10px; color: var(--cx-text-3); }\n.dsc-slider-labels button:hover { color: white; }\n.dsc-reasoning-description { display: block; text-align: center; color: var(--cx-text-3); font-size: 11px; line-height: 16px; margin-top: 7px; }\n@keyframes dsc-pop-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }\n/* 退场淡出:关闭弹层时先播放 160ms,再卸载 DOM。 */\n@keyframes dsc-pop-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(3px); } }\n.dsc-menu.dsc-menu-closing { animation: dsc-pop-out 160ms ease both; pointer-events: none; }\n@keyframes dsc-reasoning-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes dsc-status-pulse { 50% { opacity: .4; } }\n@keyframes dsc-max-flow { from { transform: translateX(-32%) scale(.82,1); opacity: .95; } to { transform: translateX(32%) scale(1.02,1); opacity: .8; } }\n@keyframes dsc-max-bloom { from { transform: translateX(28%) scale(.7,1); opacity: .9; } to { transform: translateX(-28%) scale(1,1); opacity: 1; } }\n@keyframes dsc-star-drift { from { transform: translate(-50%,-50%) translate(calc(var(--dx)*-.5),calc(var(--dy)*-.5)); opacity: .45; } to { transform: translate(-50%,-50%) translate(calc(var(--dx)*.5),calc(var(--dy)*.5)); opacity: .8; } }\n@keyframes dsc-burst { 0% { transform: translate(-50%,-50%) scale(.45); opacity: 0; } 22% { opacity: .65; } 100% { transform: translate(-50%,-50%) translate(var(--bx),var(--by)) scale(.45); opacity: 0; } }\n@media (prefers-reduced-motion: reduce) {\n .dsc-sidebar-shell, .dsc-row-tooltip, .dsc-ui *, .dsc-ui *::before, .dsc-ui *::after { animation: none !important; transition: none !important; }\n .dsc-burst { display: none; }\n}\n\n/* Codex 几何(第 48 轮):\n 1) 侧边栏列左上角圆角、对话列左上角直角(与 Codex 蓝框/红框一致);\n 2) 对话正文列与输入框同宽:由插件测量输入框卡片的实际视觉宽度写入 --dsc-composer-visual-width,\n 正文列优先取该值,回退到宿主 --dsh-composer-card-max-width(正文 + 32px)。 */\nbody[data-dsh-codex-style] [data-conversation-scroll] [class*=\"_column\"] { max-width: var(--dsc-composer-visual-width, var(--dsh-composer-card-max-width)); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-sidebar-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-center-column] { border-radius: 0; }\nhtml[data-windows-titlebar] body[data-dsh-codex-chrome] [data-dsc-frame][data-sidebar-collapsed] [data-dsc-center-column] { border-top-left-radius: var(--dsh-windows-content-radius, 16px); }\n";
6
+ const PROTOTYPE_ICONS = {"home":"<svg viewBox=\"0 0 16 16\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M3 7.2 8 3.2l5 4v5.3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z\"/></svg>","tasks":"<svg viewBox=\"0 0 16 16\" width=\"20\" height=\"20\" aria-hidden=\"true\"><circle cx=\"8\" cy=\"8\" r=\"5.5\"/><path d=\"M8 5v3l2 1.5\"/></svg>","plugins":"<svg class=\"dsc-rail__plugin-icon\" viewBox=\"0 0 20 20\" width=\"20\" height=\"20\" stroke-width=\"1.5\" aria-hidden=\"true\"><path d=\"M7.5 3v4m5-4v4M5.5 7h9v2.8a4.5 4.5 0 0 1-9 0V7ZM10 14.3v3.2\"/></svg>","settings":"<svg viewBox=\"0 0 20 20\" width=\"18\" height=\"18\" stroke-width=\"1.4\" aria-hidden=\"true\">\n <path d=\"M8.80 3.20 L9.07 1.85 A8.20 8.20 0 0 1 10.93 1.85 L11.20 3.20 A6.90 6.90 0 0 1 13.96 4.35 L15.10 3.58 A8.20 8.20 0 0 1 16.42 4.90 L15.65 6.04 A6.90 6.90 0 0 1 16.80 8.80 L18.15 9.07 A8.20 8.20 0 0 1 18.15 10.93 L16.80 11.20 A6.90 6.90 0 0 1 15.65 13.96 L16.42 15.10 A8.20 8.20 0 0 1 15.10 16.42 L13.96 15.65 A6.90 6.90 0 0 1 11.20 16.80 L10.93 18.15 A8.20 8.20 0 0 1 9.07 18.15 L8.80 16.80 A6.90 6.90 0 0 1 6.04 15.65 L4.90 16.42 A8.20 8.20 0 0 1 3.58 15.10 L4.35 13.96 A6.90 6.90 0 0 1 3.20 11.20 L1.85 10.93 A8.20 8.20 0 0 1 1.85 9.07 L3.20 8.80 A6.90 6.90 0 0 1 4.35 6.04 L3.58 4.90 A8.20 8.20 0 0 1 4.90 3.58 L6.04 4.35 A6.90 6.90 0 0 1 8.80 3.20\"/>\n <circle cx=\"10\" cy=\"10\" r=\"2.3\"/>\n </svg>","newChat":"<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8.1 2.8H4.6a2 2 0 0 0-2 2v6.6a2 2 0 0 0 2 2h6.6a2 2 0 0 0 2-2V7.9\"/><path d=\"m10.9 2.7-5.3 5.3L5 10.4l2.4-.6 5.3-5.3a1.3 1.3 0 0 0-1.8-1.8ZM9.8 3.8l1.8 1.8\"/></svg>"};
7
+ const BRAND_MARK = "data:image/png;base64,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";
8
+ const PLUGIN_VERSION = "0.1.2";
9
+ const PLUGIN_PACKAGE = "@xinxing1708/dsh-codex-style";
10
+ const module = { exports: {} };
11
+ const exports = module.exports;
12
+ // 保留官方 sidebar/settings/composer 的所有者;导航在 shell.overlay 独立挂载。
13
+ // 二级侧栏复用同一内容与真实动作;收起态只改变呈现方式,不导航或展开宿主列。
14
+ const DEFAULT_SIDEBAR_WIDTH = 288;
15
+
16
+ // 0.2.1-rc.2 的 root store handle.create() 返回 AppFrame 已持有的同一布局实例。
17
+ // 仅将启动/重新展开时的默认值 280 改为 288;真实 grid、分隔条和宽度变量仍由宿主计算。
18
+ function initializeSidebarWidth(ctx) {
19
+ const entry = ctx.slots.entriesOfSlot('root')[0];
20
+ const store = entry?.store?.create();
21
+ if (!store?.actions?.setSidebar || !store.getSnapshot().layoutInfo) {
22
+ console.warn('codex-style: 当前 root 未提供已核实的侧栏宽度动作');
23
+ return;
24
+ }
25
+ let previous = store.getSnapshot().layoutInfo.sidebar;
26
+ let owned = false;
27
+ const sync = initial => {
28
+ const width = store.getSnapshot().layoutInfo.sidebar;
29
+ const reopening = previous === 0 && width === 280;
30
+ if (width !== previous) owned = false;
31
+ previous = width;
32
+ if (width !== 280 || (!initial && !reopening)) return;
33
+ // 先更新本地记录,避免 setSidebar 的同步通知被误认为用户拖动。
34
+ previous = DEFAULT_SIDEBAR_WIDTH;
35
+ owned = true;
36
+ store.actions.setSidebar(DEFAULT_SIDEBAR_WIDTH);
37
+ };
38
+ const unsubscribe = store.subscribe(() => sync(false));
39
+ sync(true);
40
+ return () => {
41
+ // 卸载时只恢复本次仍在生效的默认值;保留用户后续拖动值和 0px 收起态。
42
+ unsubscribe();
43
+ if (owned && store.getSnapshot().layoutInfo.sidebar === DEFAULT_SIDEBAR_WIDTH) store.actions.setSidebar(280);
44
+ };
45
+ }
46
+
47
+ function PrototypeIcon({ name, className = '' }) {
48
+ return h('span', { className: 'dsc-proto-icon ' + className, 'aria-hidden': true, dangerouslySetInnerHTML: { __html: PROTOTYPE_ICONS[name] } });
49
+ }
50
+ function useSlotVersion(ctx, names) {
51
+ const axis = names.join('|');
52
+ const subscribe = useCallback(listener => {
53
+ const disposers = names.map(name => ctx.slots.subscribe(name, listener));
54
+ return () => disposers.forEach(dispose => dispose());
55
+ }, [ctx, axis]);
56
+ const snapshot = useCallback(() => names.map(name => ctx.slots.getVersion(name)).join(':'), [ctx, axis]);
57
+ return useSyncExternalStore(subscribe, snapshot, snapshot);
58
+ }
59
+ function usePanelDirectory(ctx) {
60
+ const version = useSlotVersion(ctx, ['main', 'sidebar.panellist']);
61
+ const locale = useSource(ctx.locale);
62
+ return useMemo(() => {
63
+ const mains = new Set(ctx.slots.entriesOfSlot('main').map(entry => entry.options.key));
64
+ return ctx.slots.entriesOfSlot('sidebar.panellist').map(({ options }) => ({
65
+ id: options.id, order: options.order ?? 0,
66
+ label: (typeof options.label === 'function' ? options.label() : options.label) ?? options.id,
67
+ available: mains.has(options.id)
68
+ })).sort((a, b) => a.order - b.order);
69
+ }, [ctx, version, locale]);
70
+ }
71
+ function useNavigationDirectory(ctx) {
72
+ const panel = useSource(ctx.layout.panelInfo);
73
+ const panels = usePanelDirectory(ctx);
74
+ // 官方分组只按已核实的 main id 识别;社区入口的名称不能决定它属于自定义。
75
+ const pluginRows = panels.filter(row => row.available && row.id === 'plugins');
76
+ const taskRows = panels.filter(row => row.available && row.id === 'schedules');
77
+ const category = panel.activePanelId === null ? 'home' : panel.activePanelId === 'plugins' ? 'plugins' : panel.activePanelId === 'schedules' ? 'tasks' : 'extension';
78
+ return { panel, panels, pluginRows, taskRows, category };
79
+ }
80
+
81
+ // 原 sidebar entry 拥有 panellist 的 renderSlot 授权。保留原按钮、图标及注入,
82
+ // 仅以公开 slot 锚点定位原 nav,给第三方行添加可释放标记并用 CSS 呈现在轨道中。
83
+ // 不复制 component、不借用私有 renderer、不搬移 React 所拥有的 DOM。
84
+ function useNativeExtensionNavigation(sidebar, target, panels) {
85
+ useLayoutEffect(() => {
86
+ const host = document.querySelector('[data-slot="sidebar"]');
87
+ if (!host || !target.current) return;
88
+ const attributes = new Map();
89
+ const properties = new Map();
90
+ let nav;
91
+ let frame;
92
+ const mark = (node, name, value) => {
93
+ let saved = attributes.get(node);
94
+ if (!saved) attributes.set(node, saved = new Map());
95
+ if (!saved.has(name)) saved.set(name, node.getAttribute(name));
96
+ if (value === null) node.removeAttribute(name);
97
+ else if (node.getAttribute(name) !== value) node.setAttribute(name, value);
98
+ };
99
+ const property = (node, name, value) => {
100
+ let saved = properties.get(node);
101
+ if (!saved) properties.set(node, saved = new Map());
102
+ if (!saved.has(name)) saved.set(name, [node.style.getPropertyValue(name), node.style.getPropertyPriority(name)]);
103
+ node.style.setProperty(name, value);
104
+ };
105
+ const hidePreview = () => sidebar.hide();
106
+ const detach = () => {
107
+ nav?.removeEventListener('pointerenter', hidePreview);
108
+ nav?.removeEventListener('focusin', hidePreview);
109
+ nav?.removeEventListener('click', hidePreview, true);
110
+ };
111
+ const sync = () => {
112
+ frame = undefined;
113
+ const glyphs = [...host.querySelectorAll('[data-slot="sidebar.panellist"]')];
114
+ const buttons = glyphs.map(glyph => glyph.closest('button'));
115
+ // 注册表通知可以先于原 SidebarRoot 的 React commit;等真实行与目录对齐再标记。
116
+ if (!buttons.length || buttons.length !== panels.length || buttons.some((button, i) => !button || button.getAttribute('aria-label') !== panels[i].label)) return;
117
+ const nextNav = buttons[0].closest('nav');
118
+ if (!nextNav || buttons.some(button => button.closest('nav') !== nextNav)) return;
119
+ if (nextNav !== nav) {
120
+ detach();
121
+ nav = nextNav;
122
+ nav.addEventListener('pointerenter', hidePreview);
123
+ nav.addEventListener('focusin', hidePreview);
124
+ nav.addEventListener('click', hidePreview, true);
125
+ }
126
+ mark(nav, 'data-dsc-extension-nav', '');
127
+ buttons.forEach((button, i) => {
128
+ const row = panels[i];
129
+ const extension = row.available && row.id !== 'plugins' && row.id !== 'schedules';
130
+ mark(button, 'data-dsc-extension-row', row.id);
131
+ mark(button, 'data-dsc-extension-hidden', extension ? null : '');
132
+ if (!extension) return;
133
+ mark(button, 'title', row.label);
134
+ const glyph = glyphs[i].parentElement;
135
+ mark(glyph, 'data-dsc-extension-glyph', '');
136
+ [...button.children].filter(child => child !== glyph).forEach(child => mark(child, 'data-dsc-extension-label', ''));
137
+ });
138
+ const box = target.current.getBoundingClientRect();
139
+ property(nav, '--dsc-extension-left', box.left + 'px');
140
+ property(nav, '--dsc-extension-top', box.top + 'px');
141
+ property(nav, '--dsc-extension-width', box.width + 'px');
142
+ property(nav, '--dsc-extension-height', Math.max(0, box.height) + 'px');
143
+ };
144
+ const schedule = () => { if (frame === undefined) frame = requestAnimationFrame(sync); };
145
+ const content = new MutationObserver(schedule);
146
+ content.observe(host, { childList: true, characterData: true, attributes: true, attributeFilter: ['aria-label'], subtree: true });
147
+ const size = new ResizeObserver(schedule);
148
+ size.observe(target.current);
149
+ window.addEventListener('resize', schedule);
150
+ schedule();
151
+ return () => {
152
+ cancelAnimationFrame(frame);
153
+ content.disconnect(); size.disconnect();
154
+ window.removeEventListener('resize', schedule);
155
+ detach();
156
+ for (const [node, saved] of attributes) for (const [name, value] of saved) {
157
+ if (value === null) node.removeAttribute(name); else node.setAttribute(name, value);
158
+ }
159
+ for (const [node, saved] of properties) for (const [name, [value, priority]] of saved) {
160
+ if (value) node.style.setProperty(name, value, priority); else node.style.removeProperty(name);
161
+ }
162
+ };
163
+ }, [sidebar, target, panels]);
164
+ }
165
+
166
+ // 导航与二级侧栏共享呈现状态;悬停计时不调用 layout,也不发起模型请求。
167
+ function createSidebarPresentation() {
168
+ const store = createSnapshotStore({ collapsed: true, preview: null });
169
+ let opening;
170
+ let closing;
171
+ const cancel = () => { clearTimeout(opening); clearTimeout(closing); };
172
+ const hide = () => { cancel(); if (store.getSnapshot().preview !== null) store.set({ ...store.getSnapshot(), preview: null }); };
173
+ const show = category => {
174
+ cancel();
175
+ if (store.getSnapshot().collapsed) store.set({ ...store.getSnapshot(), preview: category });
176
+ };
177
+ return {
178
+ store, hide, show, hold: cancel, dispose: cancel,
179
+ setCollapsed(collapsed) {
180
+ if (store.getSnapshot().collapsed === collapsed) return;
181
+ cancel();
182
+ store.set({ collapsed, preview: null });
183
+ },
184
+ enter(category) {
185
+ cancel();
186
+ if (!store.getSnapshot().collapsed || store.getSnapshot().preview === category) return;
187
+ opening = setTimeout(() => show(category), category === 'home' ? 300 : 100);
188
+ },
189
+ leave() {
190
+ cancel();
191
+ closing = setTimeout(hide, 140);
192
+ }
193
+ };
194
+ }
195
+
196
+ function makeNavigationRail(ctx, sidebar) {
197
+ return function NavigationRail() {
198
+ const { panels, pluginRows, taskRows, category } = useNavigationDirectory(ctx);
199
+ const { collapsed, preview } = useSource(sidebar.store);
200
+ const [error, setError] = useState('');
201
+ const extensions = useRef(null);
202
+ useNativeExtensionNavigation(sidebar, extensions, panels);
203
+ const pick = id => {
204
+ sidebar.hide();
205
+ setError('');
206
+ try { ctx.layout.selectPanel(id); } catch (e) { setError(errorText(e)); }
207
+ };
208
+ const item = (id, name, icon, action) => h('button', {
209
+ key: id, type: 'button', className: 'dsc-rail__item' + (category === id ? ' dsc-rail__item--active' : ''),
210
+ title: collapsed ? undefined : name, 'data-tip': name, 'data-dsc-preview-trigger': id, 'data-preview-enabled': collapsed,
211
+ 'aria-label': name, 'aria-current': category === id ? 'page' : undefined,
212
+ 'aria-haspopup': collapsed ? 'dialog' : undefined, 'aria-expanded': collapsed ? preview === id : undefined,
213
+ 'aria-controls': collapsed ? 'dsc-sidebar-card' : undefined,
214
+ onPointerEnter: e => { if (e.pointerType !== 'touch') sidebar.enter(id); }, onPointerLeave: sidebar.leave,
215
+ onFocus: () => sidebar.enter(id), onBlur: sidebar.leave,
216
+ onKeyDown: e => { if (e.key === 'Escape') { sidebar.hide(); e.preventDefault(); } }, onClick: action
217
+ }, h(PrototypeIcon, { name: icon }));
218
+ return createPortal(h(React.Fragment, null,
219
+ h('nav', { className: 'dsc-ui dsc-rail dsc-independent-rail', 'aria-label': '主导航' },
220
+ item('home', '首页', 'home', () => pick(null)),
221
+ taskRows.length > 0 && item('tasks', '定时任务', 'tasks', () => pick(taskRows[0].id)),
222
+ pluginRows.length > 0 && item('plugins', '自定义', 'plugins', () => pick('plugins')),
223
+ h('div', { ref: extensions, className: 'dsc-extension-navigation-anchor', 'aria-hidden': true })),
224
+ error && h('div', { className: 'dsc-ui dsc-navigation-error' }, h(ErrorLine, { text: error }))), document.body);
225
+ };
226
+ }
227
+
228
+ function makeSidebarChrome(ctx, sidebar) {
229
+ const Tree = makeWorkspaceTree(ctx);
230
+ return function SidebarChrome() {
231
+ const { panel, pluginRows, taskRows, category: selectedCategory } = useNavigationDirectory(ctx);
232
+ const { collapsed, preview } = useSource(sidebar.store);
233
+ const [searchOpen, setSearchOpen] = useState(false);
234
+ const [query, setQuery] = useState('');
235
+ const [error, setError] = useState('');
236
+ const [geometry, setGeometry] = useState(null);
237
+ const marker = useRef(null);
238
+ const insidePointer = useRef(null);
239
+ const lastWidth = useRef(DEFAULT_SIDEBAR_WIDTH);
240
+ const lastSection = useRef('home');
241
+ if (selectedCategory !== 'extension') lastSection.current = selectedCategory;
242
+ const presentation = useRef({ category: lastSection.current, mode: 'docked' });
243
+ const search = useRef(null);
244
+ const searchAnchor = useRef(null);
245
+ useLayoutEffect(() => {
246
+ const column = marker.current?.closest('[data-slot="sidebar"]')?.parentElement;
247
+ const frame = column?.parentElement;
248
+ if (!column || !frame) return;
249
+ const measure = () => {
250
+ const isCollapsed = frame.hasAttribute('data-sidebar-collapsed');
251
+ const box = column.getBoundingClientRect();
252
+ const targetWidth = parseFloat(getComputedStyle(frame).getPropertyValue('--dsh-windows-sidebar-width')) || box.width;
253
+ if (!isCollapsed && targetWidth >= 264) lastWidth.current = targetWidth;
254
+ // 退场仍保留完整内容宽度;不让宿主收起的 0px 列裁掉淡出过程。
255
+ setGeometry({ left: box.left, top: box.top, height: box.height, width: lastWidth.current });
256
+ sidebar.setCollapsed(isCollapsed);
257
+ };
258
+ measure();
259
+ const size = new ResizeObserver(measure);
260
+ size.observe(column);
261
+ const state = new MutationObserver(measure);
262
+ state.observe(frame, { attributes: true, attributeFilter: ['data-sidebar-collapsed'] });
263
+ window.addEventListener('resize', measure);
264
+ return () => { size.disconnect(); state.disconnect(); window.removeEventListener('resize', measure); };
265
+ }, []);
266
+ // 第三方直接打开原 main,保留最近的二级区域,不给它再造一层目录。
267
+ const visibleCategory = collapsed ? preview : lastSection.current;
268
+ const shown = geometry !== null && visibleCategory !== null;
269
+ if (shown) presentation.current = { category: visibleCategory, mode: collapsed ? 'floating' : 'docked' };
270
+ const { category, mode } = presentation.current;
271
+ useEffect(() => { if (shown && searchOpen && category === 'home') search.current?.focus(); }, [shown, searchOpen, category]);
272
+ useEffect(() => {
273
+ if (!collapsed || !preview) return;
274
+ const outside = e => {
275
+ // React 的 capture 会沿 portal 祖先传播,项目菜单/详情卡也属于侧栏。
276
+ if (insidePointer.current !== e && !e.target.closest?.('[data-dsc-preview-trigger]')) sidebar.hide();
277
+ };
278
+ const blur = () => sidebar.hide();
279
+ const escape = e => { if (e.key === 'Escape' && !e.defaultPrevented) { sidebar.hide(); e.preventDefault(); } };
280
+ document.addEventListener('pointerdown', outside);
281
+ document.addEventListener('keydown', escape);
282
+ window.addEventListener('blur', blur);
283
+ return () => { document.removeEventListener('pointerdown', outside); document.removeEventListener('keydown', escape); window.removeEventListener('blur', blur); };
284
+ }, [collapsed, preview]);
285
+ const run = async action => { setError(''); try { await action(); } catch (e) { setError(errorText(e)); } };
286
+ const pick = id => run(() => { ctx.layout.selectPanel(id); sidebar.hide(); });
287
+ const openSearch = () => { if (collapsed) sidebar.show('home'); else pick(null); setSearchOpen(true); };
288
+ const closeSearch = () => { setSearchOpen(false); setQuery(''); searchAnchor.current?.focus(); };
289
+ const pageRows = category === 'tasks' ? taskRows : pluginRows;
290
+ const inset = mode === 'floating' ? 4 : 0;
291
+ const style = geometry ? { left: geometry.left + inset, top: geometry.top + inset, width: Math.min(geometry.width, Math.max(0, window.innerWidth - geometry.left - inset - 8)), height: Math.max(0, geometry.height - inset * 2) } : { visibility: 'hidden' };
292
+ const shell = h('div', {
293
+ ref: node => { if (node) node.inert = !shown; }, id: 'dsc-sidebar-card', className: 'dsc-ui dsc-sidebar-shell', style,
294
+ 'data-mode': mode, 'data-open': shown, 'aria-hidden': !shown,
295
+ role: mode === 'floating' ? 'dialog' : undefined, 'aria-label': mode === 'floating' ? (category === 'home' ? '首页侧栏' : category === 'tasks' ? '定时任务侧栏' : '自定义侧栏') : undefined,
296
+ onPointerEnter: sidebar.hold, onPointerLeave: sidebar.leave,
297
+ onPointerDownCapture: e => { insidePointer.current = e.nativeEvent; },
298
+ onFocusCapture: sidebar.hold, onBlurCapture: sidebar.leave,
299
+ onKeyDown: e => { if (collapsed && e.key === 'Escape' && !e.defaultPrevented) { sidebar.hide(); e.preventDefault(); } }
300
+ },
301
+ h('aside', { key: category, className: 'dsc-sidebar', 'aria-label': category === 'home' ? '项目与对话' : category === 'tasks' ? '定时任务' : '自定义' },
302
+ h('div', { className: 'dsc-sidebar__header' }, category === 'home' ? h('div', { className: 'dsc-brand' }, h('img', { className: 'dsc-brand__mark', src: BRAND_MARK, alt: '' }), h('span', { className: 'dsc-brand__wordmark' }, 'deepseek'), h('span', { className: 'dsc-brand__badge' }, 'HARNESS')) : h('strong', { className: 'dsc-section-page-title' }, category === 'tasks' ? '定时任务' : '自定义'),
303
+ h('div', { className: 'dsc-sidebar__header-actions' }, h('button', { type: 'button', ref: searchAnchor, className: 'dsc-icon-button', title: '搜索对话与项目', 'aria-label': '搜索对话与项目', 'aria-expanded': searchOpen, onClick: openSearch }, h(Icon, { name: 'search' })))),
304
+ category === 'home' && h('button', { type: 'button', className: 'dsc-new-chat', title: '新聊天', onClick: () => { setSearchOpen(false); setQuery(''); run(() => { ctx.uiWorkspace.startSession(); sidebar.hide(); }); } }, h(PrototypeIcon, { name: 'newChat' }), h('span', null, '新聊天')),
305
+ category === 'home' && searchOpen && h('label', { className: 'dsc-search dsc-sidebar-search' }, h(Icon, { name: 'search' }), h('input', { ref: search, value: query, onChange: e => setQuery(e.target.value), onKeyDown: e => { if (e.key === 'Escape') { e.preventDefault(); closeSearch(); } }, placeholder: '搜索标题或项目', 'aria-label': '搜索标题或项目' }), h('button', { type: 'button', onClick: closeSearch, 'aria-label': '关闭搜索' }, '×')),
306
+ h(ErrorLine, { text: error }),
307
+ category === 'home' ? h(Tree, { query, tools: false, active: shown, onNavigate: sidebar.hide }) : h('div', { className: 'dsc-secondary-nav' }, pageRows.map(row => h('button', { key: row.id, type: 'button', className: 'dsc-secondary-item' + (row.id === panel.activePanelId ? ' is-active' : ''), 'aria-current': row.id === panel.activePanelId ? 'page' : undefined, onClick: () => pick(row.id) }, h(PrototypeIcon, { name: category === 'tasks' ? 'tasks' : 'plugins' }), row.label)))));
308
+ return h(React.Fragment, null, h('span', { ref: marker, 'aria-hidden': true }), createPortal(shell, document.body));
309
+ };
310
+ }
311
+
312
+ function makeShellChrome(ctx, sidebar) {
313
+ const Navigation = makeNavigationRail(ctx, sidebar);
314
+ const Toggle = makeSidebarToggle(ctx);
315
+ return function ShellChrome() {
316
+ const marker = useRef(null);
317
+ useLayoutEffect(() => {
318
+ // 0.2.1-rc.2 的窄适配:由公开 overlay/slot 锚点找到它们所属的列,
319
+ // 外壳仅增加插件标记;宽度通过已有布局动作初始化,不改写 grid 或搬移 DOM。
320
+ const overlay = marker.current?.closest('[data-shell-overlay]');
321
+ const frame = overlay?.parentElement;
322
+ if (!frame) return;
323
+ const saved = [];
324
+ const mark = (element, name, value = '') => {
325
+ if (!element) return;
326
+ saved.push([element, name, element.getAttribute(name)]);
327
+ element.setAttribute(name, value);
328
+ };
329
+ mark(document.body, 'data-dsh-codex-chrome', PLUGIN_VERSION);
330
+ mark(frame, 'data-dsc-frame');
331
+ mark(frame.querySelector('[data-slot="sidebar"]')?.parentElement, 'data-dsc-sidebar-column');
332
+ mark(frame.querySelector('[data-slot="main"]')?.parentElement, 'data-dsc-center-column');
333
+ const restoreWidth = initializeSidebarWidth(ctx);
334
+ return () => {
335
+ restoreWidth?.();
336
+ saved.reverse().forEach(([element, name, previous]) => {
337
+ if (previous === null) element.removeAttribute(name); else element.setAttribute(name, previous);
338
+ });
339
+ };
340
+ }, []);
341
+ return h(React.Fragment, null, h('span', { ref: marker, hidden: true }), h(Navigation), h(Toggle));
342
+ };
343
+ }
344
+
345
+ // 此常驻入口不替换 AppFrame,也不修改系统标题栏;collapsed 时仍可重新打开侧栏。
346
+ function makeSidebarToggle(ctx) {
347
+ return function SidebarToggle() {
348
+ return createPortal(h('div', { className: 'dsc-ui dsc-window-sidebar-toggle' }, h('button', { type: 'button', className: 'dsc-icon-button', title: '显示或隐藏侧边栏', 'aria-label': '显示或隐藏侧边栏', onClick: () => ctx.layout.toggleSidebar() }, h('svg', { viewBox: '0 0 16 16', width: 16, height: 16, fill: 'none', stroke: 'currentColor', strokeWidth: 1.2, 'aria-hidden': true }, h('rect', { x: 2, y: 3, width: 12, height: 10, rx: 1.5 }), h('path', { d: 'M6 3v10' })))), document.body);
349
+ };
350
+ }
351
+
352
+ // 公开的会话头 leading 座负责留位;仅收起二级侧栏时显示,不覆盖标题。
353
+ function makeCollapsedNewChat(ctx) {
354
+ return function CollapsedNewChat() {
355
+ // startSession 返回 void;异步创建、导航和失败通知都由宿主处理。
356
+ return h('div', { className: 'dsc-ui dsc-collapsed-new-chat' },
357
+ h('button', { type: 'button', className: 'dsc-icon-button', title: '新建对话', 'aria-label': '新建对话', onClick: () => ctx.uiWorkspace.startSession() }, h(PrototypeIcon, { name: 'newChat' })));
358
+ };
359
+ }
360
+
361
+ function makeGearLauncher(ctx) {
362
+ return function GearLauncher({ openSettings, settingsOpen }) {
363
+ const [open, setOpen] = useState(false);
364
+ const [profile, setProfile] = useState(null);
365
+ const [failed, setFailed] = useState(false);
366
+ const [avatarFailed, setAvatarFailed] = useState(false);
367
+ const anchor = useRef(null);
368
+ useEffect(() => { if (settingsOpen) setOpen(false); }, [settingsOpen]);
369
+ useEffect(() => {
370
+ if (!open) return;
371
+ let active = true;
372
+ setProfile(null); setFailed(false); setAvatarFailed(false);
373
+ const client = { version: '0.2.1-rc.2', locale: ctx.locale.getSnapshot().active, timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60 };
374
+ ctx.remote.account.getProfile(client).then(result => {
375
+ if (!result.ok) throw result.error;
376
+ if (active) { setProfile(result.value?.status === 'ready' ? result.value.value : null); setFailed(result.value?.status !== 'ready'); }
377
+ }).catch(() => { if (active) setFailed(true); });
378
+ return () => { active = false; };
379
+ }, [open]);
380
+ const showSettings = () => { setOpen(false); openSettings(); };
381
+ return createPortal(h('div', { className: 'dsc-ui dsc-gear-launcher' },
382
+ h('button', { ref: anchor, type: 'button', className: 'dsc-gear-button', 'aria-label': '设置', 'aria-haspopup': 'menu', 'aria-expanded': open, onClick: () => setOpen(!open) }, h(PrototypeIcon, { name: 'settings' })),
383
+ open && h(Popover, { anchor, onClose: () => setOpen(false), label: '设置与账号', width: 228, className: 'dsc-app-menu' },
384
+ h('button', { type: 'button', className: 'dsc-account-row', role: 'menuitem', onClick: showSettings, title: '在账号子页面中管理' }, profile?.avatarUrl && !avatarFailed ? h('img', { src: profile.avatarUrl, alt: '', onError: () => setAvatarFailed(true) }) : h('span', { className: 'dsc-account-avatar' }, h(Icon, { name: 'chat' })), h('span', null, h('strong', null, profile?.name || profile?.contact || '账号'), h('small', null, profile ? '账号与设置' : failed ? '打开账号设置' : '正在读取账号…'))),
385
+ h('div', { className: 'dsc-menu-separator', role: 'separator' }),
386
+ h(Option, { title: '设置', icon: 'settings', onClick: showSettings }))), document.body);
387
+ };
388
+ }
389
+
390
+ function CloudMark() {
391
+ return h('svg', { viewBox: '0 0 64 64', width: 48, height: 48, fill: 'none', stroke: 'currentColor', strokeWidth: 3.1, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, h('path', { d: 'M21 9c5-6 13-6 18-1 9-1 17 6 17 14 8 6 9 17 2 24-1 10-10 17-20 14-8 6-19 3-23-5C4 54-1 44 4 35c-4-8 1-18 9-20 1-3 4-5 8-6Z' }), h('path', { d: 'm20 24 6 9-6 9m16 1h11' }));
392
+ }
393
+ // 公开 region/slot 锚点用于测量;不查私有 class,不搬移原编辑器或其它节点。
394
+ function useHeroGeometry(ref) {
395
+ const [geometry, setGeometry] = useState(null);
396
+ useLayoutEffect(() => {
397
+ const region = ref.current?.closest('[data-conversation-content]');
398
+ const composer = region?.querySelector('[data-slot="conversation.composer.bar"]');
399
+ if (!region || !composer) return;
400
+ const measure = () => {
401
+ const area = region.getBoundingClientRect();
402
+ const card = composer.getBoundingClientRect();
403
+ const fallback = composer.closest('[data-chain-overlay-fallback]');
404
+ const visible = area.width > 0 && card.width > 0 && (!fallback || fallback.style.display !== 'none');
405
+ setGeometry(previous => {
406
+ const next = visible ? { left: card.left, top: card.top, width: card.width, centerX: area.left + area.width / 2, centerY: area.top + area.height * .45 } : null;
407
+ return JSON.stringify(previous) === JSON.stringify(next) ? previous : next;
408
+ });
409
+ };
410
+ measure();
411
+ const resize = new ResizeObserver(measure); resize.observe(region); resize.observe(composer);
412
+ const mutation = new MutationObserver(measure); mutation.observe(region, { subtree: true, attributes: true, attributeFilter: ['data-content-phase', 'style'] });
413
+ window.addEventListener('resize', measure); window.addEventListener('scroll', measure, true);
414
+ return () => { resize.disconnect(); mutation.disconnect(); window.removeEventListener('resize', measure); window.removeEventListener('scroll', measure, true); };
415
+ }, [ref]);
416
+ return geometry;
417
+ }
418
+ function EmptyHero() {
419
+ const ref = useRef(null);
420
+ const geometry = useHeroGeometry(ref);
421
+ return h(React.Fragment, null, h('span', { ref, 'aria-hidden': true }), geometry && createPortal(h('div', { className: 'dsc-ui dsc-empty-hero', style: { left: geometry.centerX, top: geometry.centerY } }, h(CloudMark), h('h1', null, '我们要构建什么?')), document.body));
422
+ }
423
+ function makeHeroWorkspace(ctx) {
424
+ return function HeroWorkspace({ open: ownerOpen, selectedId, onPick, onClose }) {
425
+ const workspaces = useSource(ctx.workspaces.list);
426
+ const [open, setOpen] = useState(false);
427
+ const ref = useRef(null);
428
+ const anchor = useRef(null);
429
+ const geometry = useHeroGeometry(ref);
430
+ const current = workspaces.items.find(row => row.workspaceId === selectedId);
431
+ const shown = ownerOpen || open;
432
+ useLayoutEffect(() => {
433
+ if (!geometry) return;
434
+ const region = ref.current?.closest('[data-conversation-content]');
435
+ if (!region) return;
436
+ const names = ['--dsc-hero-card-top', '--dsc-hero-card-right'];
437
+ const previous = names.map(name => region.style.getPropertyValue(name));
438
+ region.style.setProperty(names[0], geometry.top + 'px');
439
+ region.style.setProperty(names[1], geometry.left + geometry.width + 'px');
440
+ region.setAttribute('data-dsc-hero-layout', '');
441
+ return () => { names.forEach((name, i) => { if (previous[i]) region.style.setProperty(name, previous[i]); else region.style.removeProperty(name); }); region.removeAttribute('data-dsc-hero-layout'); };
442
+ }, [geometry]);
443
+ const close = () => { setOpen(false); onClose(); };
444
+ const menu = shown && h(Popover, { anchor, label: '选择项目', onClose: close, width: 280 },
445
+ h('div', { className: 'dsc-menu-heading' }, '选择项目'),
446
+ workspaces.items.map(row => h(Option, { key: row.workspaceId, title: row.title, detail: row.path, icon: 'folder', selected: row.workspaceId === selectedId, onClick: () => { setOpen(false); onPick(row.workspaceId); } })),
447
+ !workspaces.items.length && h('div', { className: 'dsc-empty' }, '先在左侧添加工作区'));
448
+ const bar = geometry && createPortal(h('div', { className: 'dsc-ui dsc-hero-projectbar', style: { left: geometry.left + 12, top: geometry.top - 37, width: Math.max(120, geometry.width - 24) } },
449
+ h('button', { type: 'button', ref: anchor, className: 'dsc-project-picker', 'aria-haspopup': 'menu', 'aria-expanded': shown, onClick: () => { if (shown) close(); else setOpen(true); } }, h(Icon, { name: 'folder' }), current?.title || '选择项目'), menu), document.body);
450
+ return h(React.Fragment, null, h('span', { ref, 'aria-hidden': true }), bar);
451
+ };
452
+ }
453
+
454
+ const React = require('react');
455
+ const { createPortal } = require('react-dom');
456
+ const { RiskConfirmation, Button, IconRefreshOutlineRegular } = require('@deepseek-ai/dsh-client-ui-primitives');
457
+ const { createSnapshotStore } = require('@deepseek-ai/dsh-client-store');
458
+ const h = React.createElement;
459
+ const { useState, useEffect, useLayoutEffect, useRef, useMemo, useCallback, useSyncExternalStore } = React;
460
+ const PLUGIN_ID = 'dsh-codex-style';
461
+
462
+ // 自绘图标沿用本项目原型;功能与目录全部来自宿主。
463
+ const paths = {
464
+ folder: 'M2.3 6.1c0-.75.6-1.35 1.35-1.35H6.2l1.5 1.6h4.85c.75 0 1.35.6 1.35 1.35v4.9c0 .75-.6 1.35-1.35 1.35h-8.9c-.75 0-1.35-.6-1.35-1.35Z',
465
+ folderOpen: 'M2.1 12.2V4.4c0-.8.6-1.4 1.4-1.4h2.9l1.4 1.6h4c.8 0 1.4.6 1.4 1.4v1M2.1 12.2l2.2-4.8h9.3a.8.8 0 0 1 .7 1.1l-1.8 4a1.5 1.5 0 0 1-1.4.9H3.5a1.4 1.4 0 0 1-1.4-1.2Z',
466
+ chat: 'M13 7.5a5 5 0 0 1-5 5H3l-1 1V7.5a5.5 5.5 0 1 1 11 0Z',
467
+ pencil: 'M8.1 2.8H4.6a2 2 0 0 0-2 2v6.6a2 2 0 0 0 2 2h6.6a2 2 0 0 0 2-2V7.9m-2.3-5.2-5.3 5.3L5 10.4l2.4-.6 5.3-5.3a1.3 1.3 0 0 0-1.8-1.8ZM9.8 3.8l1.8 1.8',
468
+ search: 'M10.5 10.5 14 14M11.5 7a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z',
469
+ plus: 'M8 3v10M3 8h10',
470
+ chevron: 'm5 6 3 3 3-3',
471
+ back: 'm10 3-5 5 5 5',
472
+ check: 'm3.5 8 3 3 6-6',
473
+ shield: 'M8 1.8 13 4v4.2c0 2.8-2.3 4.8-5 6-2.7-1.2-5-3.2-5-6V4ZM8 5v3.6m0 2.1v.1',
474
+ pin: 'm5 2 6 0-1 4 2 2H9v5l-1 1V8H4l2-2Z',
475
+ archive: 'M3 5.5v6.3c0 .9.7 1.7 1.7 1.7h6.6c1 0 1.7-.8 1.7-1.7V5.5M2.5 2.5h11v3h-11ZM6.2 8h3.6',
476
+ monitor: 'M2 3h12v8H2zM5.5 13h5M8 11v2',
477
+ arrowUpRight: 'M4.8 11.2 11.2 4.8M5.9 4.8h5.3v5.3',
478
+ close: 'M4 4l8 8M12 4l-8 8',
479
+ settings: 'M6.5 2.5 7 1h2l.5 1.5 1.6.9 1.5-.3 1 1.8-1 1.2v1.8l1 1.2-1 1.8-1.5-.3-1.6.9L9 13H7l-.5-1.5-1.6-.9-1.5.3-1-1.8 1-1.2V6.1l-1-1.2 1-1.8 1.5.3ZM10.2 7a2.2 2.2 0 1 1-4.4 0 2.2 2.2 0 0 1 4.4 0Z'
480
+ };
481
+ function Icon({ name, size = 16, ...props }) {
482
+ return h('svg', { viewBox: '0 0 16 16', width: size, height: size, fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true, ...props }, h('path', { d: paths[name] || paths.chat }));
483
+ }
484
+ function useSource(source) {
485
+ const subscribe = useCallback(fn => source.subscribe(fn), [source]);
486
+ const snapshot = useCallback(() => source.getSnapshot(), [source]);
487
+ return useSyncExternalStore(subscribe, snapshot, snapshot);
488
+ }
489
+ function errorText(error) { return error?.message || String(error); }
490
+ function ErrorLine({ text }) { return text ? h('div', { className: 'dsc-error', role: 'alert' }, text) : null; }
491
+ // 用 aria-disabled 代替原生 disabled:避免全局 `.dsc-ui button:disabled { opacity: .45 }` 在提交瞬间造成变暗闪烁。
492
+ function Option({ title, detail, icon, selected, onClick, disabled, warning }) {
493
+ return h('button', { type: 'button', className: 'dsc-option', role: 'menuitemradio', 'aria-checked': !!selected, 'aria-disabled': disabled ? 'true' : undefined, 'data-warning': !!warning, onClick: e => { if (disabled) return; onClick?.(e); } },
494
+ icon && h(Icon, { name: icon }),
495
+ h('span', { className: 'dsc-option-copy' }, h('span', null, title), detail && h('small', null, detail)),
496
+ selected && h(Icon, { name: 'check' }));
497
+ }
498
+
499
+ // 所有浮层位于自己的 portal 根;只读取 anchor 与自己的 DOM,不查宿主私有选择器。
500
+ // placement: 'top' 为默认(锚点上方优先),'right' 用于行尾菜单(锚点右下方展开)。
501
+ function Popover({ anchor, point, onClose, label, width = 300, children, className = '', autoFocus = true, role = 'menu', placement = 'top' }) {
502
+ const ref = useRef(null);
503
+ const [position, setPosition] = useState({ visibility: 'hidden' });
504
+ useLayoutEffect(() => {
505
+ const place = () => {
506
+ if (!anchor.current || !ref.current) return;
507
+ const box = anchor.current.getBoundingClientRect();
508
+ const w = Math.min(width, window.innerWidth - 24);
509
+ const size = ref.current.getBoundingClientRect();
510
+ if (point) {
511
+ setPosition({ position: 'fixed', left: Math.max(12, Math.min(point.x, window.innerWidth - w - 12)), top: Math.max(12, Math.min(point.y, window.innerHeight - size.height - 12)), width: w, maxHeight: Math.max(100, window.innerHeight - 24) });
512
+ return;
513
+ }
514
+ if (placement === 'right') {
515
+ const spaceRight = window.innerWidth - box.right - 12;
516
+ const left = spaceRight >= w ? box.right + 6 : Math.max(12, box.left - w - 6);
517
+ const top = Math.max(12, Math.min(window.innerHeight - size.height - 12, box.top - 4));
518
+ setPosition({ position: 'fixed', left, top, width: w, maxHeight: Math.max(100, window.innerHeight - 24) });
519
+ return;
520
+ }
521
+ const left = Math.max(12, Math.min(window.innerWidth - w - 12, box.right - w));
522
+ const top = box.top - size.height - 9 >= 12 ? box.top - size.height - 9 : Math.max(12, Math.min(box.bottom + 9, window.innerHeight - size.height - 12));
523
+ setPosition({ position: 'fixed', left, top, width: w, maxHeight: Math.max(100, window.innerHeight - 24) });
524
+ };
525
+ place();
526
+ const observer = new ResizeObserver(place);
527
+ if (ref.current) observer.observe(ref.current);
528
+ window.addEventListener('resize', place);
529
+ window.addEventListener('scroll', place, true);
530
+ return () => { observer.disconnect(); window.removeEventListener('resize', place); window.removeEventListener('scroll', place, true); };
531
+ }, [anchor, point, width, placement]);
532
+ useEffect(() => {
533
+ if (autoFocus) (ref.current?.querySelector('input, [aria-checked="true"], button, [role="slider"]'))?.focus({ preventScroll: true });
534
+ const pointer = e => { if (!ref.current?.contains(e.target) && !anchor.current?.contains(e.target)) onClose(); };
535
+ const key = e => {
536
+ if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); onClose(); anchor.current?.focus(); return; }
537
+ if (!ref.current?.contains(e.target) || e.target.matches('input, [role="slider"]') || !['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) return;
538
+ const items = [...ref.current.querySelectorAll('button:not(:disabled)')];
539
+ if (!items.length) return;
540
+ e.preventDefault();
541
+ const index = items.indexOf(document.activeElement);
542
+ const next = e.key === 'Home' ? 0 : e.key === 'End' ? items.length - 1 : (index + (e.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
543
+ items[next]?.focus();
544
+ };
545
+ document.addEventListener('pointerdown', pointer, true);
546
+ document.addEventListener('keydown', key, true);
547
+ return () => { document.removeEventListener('pointerdown', pointer, true); document.removeEventListener('keydown', key, true); };
548
+ }, [anchor, onClose, autoFocus]);
549
+ return createPortal(h('div', { ref, className: 'dsc-ui dsc-menu ' + className, style: position, role, 'aria-label': label }, children), document.body);
550
+ }
551
+
552
+ // 行悬停详情卡:按原型实测放置(右侧、与行顶对齐),不复用菜单的向上翻转定位。
553
+ // 项目卡 321×130:名称+图钉 / 聊天数 / 分隔线 / 路径 / 分隔线 / 编辑项目;会话卡 470 宽与行顶差 6px。
554
+ function HoverCard({ anchor, kind, onClose, onEnter, onLeave, children }) {
555
+ const ref = useRef(null);
556
+ const [position, setPosition] = useState({ visibility: 'hidden' });
557
+ useLayoutEffect(() => {
558
+ const place = () => {
559
+ if (!anchor.current || !ref.current) return;
560
+ const box = anchor.current.getBoundingClientRect();
561
+ const width = ref.current.offsetWidth;
562
+ const height = ref.current.offsetHeight;
563
+ const gap = kind === 'project' ? 5 : 12;
564
+ let left = box.right + gap;
565
+ if (left + width > window.innerWidth - 8) left = Math.max(8, Math.min(box.left - gap - width, window.innerWidth - width - 8));
566
+ let top = kind === 'project' ? box.top : box.top - 6;
567
+ if (top + height > window.innerHeight - 8) top = Math.max(8, window.innerHeight - height - 8);
568
+ if (top < 8) top = 8;
569
+ setPosition({ position: 'fixed', left: Math.round(left), top: Math.round(top), visibility: 'visible' });
570
+ };
571
+ place();
572
+ const observer = new ResizeObserver(place);
573
+ observer.observe(ref.current);
574
+ return () => observer.disconnect();
575
+ }, [anchor, kind]);
576
+ useEffect(() => {
577
+ const onKey = event => { if (event.key === 'Escape') { event.preventDefault(); onClose(); } };
578
+ window.addEventListener('resize', onClose);
579
+ window.addEventListener('scroll', onClose, true);
580
+ document.addEventListener('keydown', onKey, true);
581
+ return () => { window.removeEventListener('resize', onClose); window.removeEventListener('scroll', onClose, true); document.removeEventListener('keydown', onKey, true); };
582
+ }, [onClose]);
583
+ return createPortal(h('div', { ref, className: 'dsc-ui dsc-row-pop dsc-row-pop--' + kind, style: position, role: 'dialog', 'aria-label': kind === 'project' ? '项目详情' : '对话详情', onPointerEnter: onEnter, onPointerLeave: onLeave }, children), document.body);
584
+ }
585
+ // 图钉为真实状态指示:DSH 只有会话置顶、没有工作区置顶,因此工作区始终显示空心「未置顶」。
586
+ function PinMark({ filled }) {
587
+ return h('svg', { viewBox: '0 0 16 16', width: 14, height: 14, fill: filled ? 'currentColor' : 'none', stroke: 'currentColor', strokeWidth: 1.3, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
588
+ h('g', { transform: 'rotate(45 8 8)' }, h('path', { d: 'M6 2.5h4v3L12 8.5H4L6 5.5ZM8 8.5V14' })));
589
+ }
590
+ function ProjectHoverCard({ data, anchor, onReveal, onEdit, ...root }) {
591
+ return h(HoverCard, { anchor, kind: 'project', ...root },
592
+ h('div', { className: 'dsc-row-pop__head' },
593
+ h('span', { className: 'dsc-row-pop__name' }, h(Icon, { name: 'folder', size: 15 }), h('span', null, data.title)),
594
+ h('span', { className: 'dsc-row-pop__pin', role: 'img', title: data.pinned ? '已置顶' : '未置顶(DSH 暂不支持工作区置顶)', 'aria-label': data.pinned ? '已置顶' : '未置顶' }, h(PinMark, { filled: data.pinned }))),
595
+ h('div', { className: 'dsc-row-pop__line' }, h(Icon, { name: 'chat', size: 15 }), h('span', null, `${data.count} 次聊天`)),
596
+ data.path && h('div', { className: 'dsc-row-pop__sep' }),
597
+ data.path && h('button', { type: 'button', className: 'dsc-row-pop__line dsc-row-pop__line--path', title: '在文件资源管理器中显示', onClick: onReveal },
598
+ h(Icon, { name: 'folder', size: 15 }), h('span', { className: 'dsc-row-pop__path' }, data.path), h(Icon, { name: 'arrowUpRight', size: 14, className: 'dsc-row-pop__arrow' })),
599
+ h('div', { className: 'dsc-row-pop__sep' }),
600
+ h('button', { type: 'button', className: 'dsc-row-pop__line dsc-row-pop__line--action', onClick: onEdit }, h(Icon, { name: 'settings', size: 15 }), h('span', null, '编辑项目')));
601
+ }
602
+ function ThreadHoverCard({ data, anchor, ...root }) {
603
+ return h(HoverCard, { anchor, kind: 'thread', ...root },
604
+ h('div', { className: 'dsc-row-pop__head' },
605
+ h('p', { className: 'dsc-row-pop__excerpt' }, data.title),
606
+ h('span', { className: 'dsc-row-pop__meta' }, h(Icon, { name: 'monitor', size: 14 }), data.status)),
607
+ data.workspaceTitle && h('div', { className: 'dsc-row-pop__line' }, h(Icon, { name: 'folder', size: 15 }), h('span', null, data.workspaceTitle)),
608
+ data.path && h('div', { className: 'dsc-row-pop__line' }, h(Icon, { name: 'folder', size: 15 }), h('span', { className: 'dsc-row-pop__path' }, data.path)));
609
+ }
610
+
611
+ // 「编辑项目」对话框:名称输入接真实工作区重命名,底部「移除本地项目」接真实工作区删除;
612
+ // 源文件夹只读展示当前路径,不提供添加/更换文件夹(DSH 无该能力)。
613
+ function EditWorkspaceDialog({ workspace, onClose, onRename, onDelete }) {
614
+ const [name, setName] = useState(workspace.title || '');
615
+ const [error, setError] = useState('');
616
+ const [busy, setBusy] = useState('');
617
+ const input = useRef(null);
618
+ useEffect(() => { input.current?.focus(); input.current?.select(); }, []);
619
+ useEffect(() => {
620
+ const onKey = event => { if (event.key === 'Escape') { event.preventDefault(); onClose(); } };
621
+ document.addEventListener('keydown', onKey, true);
622
+ return () => document.removeEventListener('keydown', onKey, true);
623
+ }, [onClose]);
624
+ const save = async () => {
625
+ const title = name.trim();
626
+ if (!title) { setError('项目名称不能为空'); return; }
627
+ if (busy) return;
628
+ if (title === (workspace.title || '')) { onClose(); return; }
629
+ setBusy('save'); setError('');
630
+ try { await onRename(title); onClose(); } catch (e) { setError(errorText(e)); setBusy(''); }
631
+ };
632
+ const remove = async () => {
633
+ if (busy) return;
634
+ if (!window.confirm(`移除本地项目「${workspace.title || ''}」?项目中的对话会保留在“最近”中。`)) return;
635
+ setBusy('remove'); setError('');
636
+ try { await onDelete(); onClose(); } catch (e) { setError(errorText(e)); setBusy(''); }
637
+ };
638
+ return createPortal(h('div', { className: 'dsc-ui dsc-modal', role: 'presentation' },
639
+ h('div', { className: 'dsc-modal__mask', 'aria-hidden': true, onClick: onClose }),
640
+ h('div', { className: 'dsc-modal__panel', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'dsc-edit-workspace-title' },
641
+ h('div', { className: 'dsc-modal__head' },
642
+ h('h2', { id: 'dsc-edit-workspace-title' }, '编辑项目'),
643
+ h('button', { type: 'button', className: 'dsc-modal__close', title: '关闭', 'aria-label': '关闭', onClick: onClose }, h(Icon, { name: 'close', size: 16 }))),
644
+ h('div', { className: 'dsc-modal__field' },
645
+ h(Icon, { name: 'folder', size: 16 }),
646
+ h('input', { ref: input, className: 'dsc-modal__input', value: name, onChange: e => setName(e.target.value), onKeyDown: e => { if (e.key === 'Enter') { e.preventDefault(); save(); } }, 'aria-label': '项目名称', placeholder: '项目名称' })),
647
+ h('div', { className: 'dsc-modal__label' }, '源文件夹'),
648
+ h('div', { className: 'dsc-modal__source' }, h(Icon, { name: 'folder', size: 16 }), h('span', { className: 'dsc-modal__path', title: workspace.path || '' }, workspace.path || '未记录路径')),
649
+ h(ErrorLine, { text: error }),
650
+ h('div', { className: 'dsc-modal__actions' },
651
+ h('button', { type: 'button', className: 'dsc-modal__remove', onClick: remove, disabled: !!busy }, busy === 'remove' ? '正在移除…' : '移除本地项目'),
652
+ h('div', { className: 'dsc-modal__spacer' }),
653
+ h('button', { type: 'button', className: 'dsc-modal__cancel', onClick: onClose, disabled: !!busy }, '取消'),
654
+ h('button', { type: 'button', className: 'dsc-modal__save', onClick: save, disabled: !!busy }, busy === 'save' ? '保存中…' : '保存')))), document.body);
655
+ }
656
+
657
+ function relativeTime(value) {
658
+ const timestamp = typeof value === 'number' ? value : Date.parse(value);
659
+ if (!Number.isFinite(timestamp)) return '';
660
+ const minutes = Math.max(0, Math.floor((Date.now() - timestamp) / 60000));
661
+ return minutes < 1 ? '刚刚' : minutes < 60 ? minutes + '分钟前' : minutes < 1440 ? Math.floor(minutes / 60) + '小时前' : Math.floor(minutes / 1440) + '天前';
662
+ }
663
+
664
+ // 测量标题当前可用宽度;侧栏拖宽、重命名都会重新计算。
665
+ function ThreadTitle({ title }) {
666
+ const viewport = useRef(null);
667
+ const text = useRef(null);
668
+ useLayoutEffect(() => {
669
+ const measure = () => {
670
+ const overflow = Math.max(0, text.current.scrollWidth - viewport.current.clientWidth);
671
+ viewport.current.dataset.overflow = String(overflow > 1);
672
+ viewport.current.style.setProperty('--dsc-title-shift', -overflow + 'px');
673
+ viewport.current.style.setProperty('--dsc-title-duration', Math.max(2.5, overflow / 30) + 's');
674
+ };
675
+ measure();
676
+ const observer = new ResizeObserver(measure);
677
+ observer.observe(viewport.current);
678
+ observer.observe(text.current);
679
+ return () => observer.disconnect();
680
+ }, [title]);
681
+ return h('span', { ref: viewport, className: 'dsc-thread-title' }, h('span', { ref: text, className: 'dsc-thread-title-text' }, title));
682
+ }
683
+
684
+ // 提示 portal 到 body,避免被滚动区或正在开合的项目裁掉;延迟和进退由 CSS 控制。
685
+ function RowTooltip({ anchor, open, label, onClose }) {
686
+ const ref = useRef(null);
687
+ const [position, setPosition] = useState({});
688
+ useLayoutEffect(() => {
689
+ if (!open) return;
690
+ const box = anchor.current.getBoundingClientRect();
691
+ const tip = ref.current.getBoundingClientRect();
692
+ setPosition({ left: Math.max(8, Math.min(box.left + (box.width - tip.width) / 2, window.innerWidth - tip.width - 8)), top: box.top - tip.height - 7 >= 8 ? box.top - tip.height - 7 : box.bottom + 7 });
693
+ window.addEventListener('resize', onClose);
694
+ window.addEventListener('scroll', onClose, true);
695
+ return () => { window.removeEventListener('resize', onClose); window.removeEventListener('scroll', onClose, true); };
696
+ }, [anchor, open, label, onClose]);
697
+ return createPortal(h('div', { ref, className: 'dsc-ui dsc-row-tooltip', role: 'tooltip', 'aria-hidden': !open, 'data-open': open, style: position }, label), document.body);
698
+ }
699
+
700
+ function ThreadAction({ label, active, onIntent, onClick, className = '', pressed, children }) {
701
+ const anchor = useRef(null);
702
+ const [hovered, setHovered] = useState(false);
703
+ const [focused, setFocused] = useState(false);
704
+ const close = useCallback(() => { setHovered(false); setFocused(false); }, []);
705
+ useEffect(() => { if (!active) close(); }, [active, close]);
706
+ return h(React.Fragment, null,
707
+ h('button', { ref: anchor, type: 'button', className: 'dsc-thread-action ' + className, 'aria-label': label, 'aria-pressed': pressed,
708
+ onPointerEnter: () => { onIntent(); setHovered(true); }, onPointerLeave: () => setHovered(false),
709
+ onFocus: e => { if (e.currentTarget.matches(':focus-visible')) { onIntent(); setFocused(true); } }, onBlur: () => setFocused(false),
710
+ onClick: () => { close(); onIntent(); onClick(); } }, children),
711
+ h(RowTooltip, { anchor, open: active && (hovered || focused), label, onClose: close }));
712
+ }
713
+
714
+ function ProjectFold({ open, children, className = '' }) {
715
+ return h('div', { className: 'dsc-project-fold ' + className, 'data-open': open, 'aria-hidden': !open, ref: node => { if (node) node.inert = !open; } },
716
+ h('div', { className: 'dsc-project-clip' }, children));
717
+ }
718
+
719
+ function makeWorkspaceTree(ctx) {
720
+ return function WorkspaceTree({ query = '', tools = true, active = true, onNavigate }) {
721
+ const catalog = useSource(ctx.sessions.list);
722
+ const workspaces = useSource(ctx.workspaces.list);
723
+ const status = useSource(ctx.uiSession.sessionStatus);
724
+ const panel = useSource(ctx.layout.panelInfo);
725
+ const [expanded, setExpanded] = useState({});
726
+ const [projectsOpen, setProjectsOpen] = useState(true);
727
+ const [showArchived, setShowArchived] = useState(false);
728
+ const [message, setMessage] = useState('');
729
+ const [adding, setAdding] = useState(false);
730
+ const [menu, setMenu] = useState(null);
731
+ const [projectMenu, setProjectMenu] = useState(null);
732
+ const [hover, setHover] = useState(null);
733
+ const [editing, setEditing] = useState(null);
734
+ const menuAnchor = useRef(null);
735
+ const projectAnchor = useRef(null);
736
+ const hoverAnchor = useRef(null);
737
+ const hoverTimer = useRef(null);
738
+ const alive = useRef(true);
739
+ useEffect(() => { alive.current = true; return () => { alive.current = false; clearTimeout(hoverTimer.current); }; }, []);
740
+ useEffect(() => {
741
+ if (active) return;
742
+ clearTimeout(hoverTimer.current);
743
+ setHover(null); setMenu(null); setProjectMenu(null); setEditing(null);
744
+ }, [active]);
745
+ const run = async action => { setMessage(''); try { await action(); } catch (e) { if (alive.current) setMessage(errorText(e)); } };
746
+ const archived = new Set(workspaces.archivedSessionIds);
747
+ const pinned = new Set(workspaces.pinnedSessionIds);
748
+ const items = catalog.ids.map(id => catalog.byId[id]).filter(s => s && s.origin !== 'subagent' && !s.blank && (showArchived || !archived.has(s.id))).map(s => ({ ...s, sessionId: s.id, title: s.title || s.displayTitle }));
749
+ const currentId = catalog.ids.find(id => (catalog.byId[id]?.retainedBy?.mainView ?? 0) > 0);
750
+ const workspaceOf = id => workspaces.items.find(w => w.sessionIds.includes(id));
751
+ const matches = s => !query.trim() || `${s.title || ''} ${workspaceOf(s.sessionId)?.title || ''}`.toLowerCase().includes(query.trim().toLowerCase());
752
+ const ordered = [...items].sort((a, b) => (Number(b.updatedAt) || Date.parse(b.updatedAt) || 0) - (Number(a.updatedAt) || Date.parse(a.updatedAt) || 0));
753
+ const visible = ordered.filter(matches);
754
+ const cancelHover = () => clearTimeout(hoverTimer.current);
755
+ const hideHover = () => { clearTimeout(hoverTimer.current); setHover(null); };
756
+ const closeHover = () => { cancelHover(); hoverTimer.current = setTimeout(() => setHover(null), 140); };
757
+ const enterHover = (target, data) => {
758
+ cancelHover();
759
+ hoverAnchor.current = target;
760
+ if (hover) setHover(data);
761
+ else hoverTimer.current = setTimeout(() => setHover(data), 300);
762
+ };
763
+ const revealWorkspace = hoverData => {
764
+ hideHover();
765
+ run(async () => {
766
+ const sessionRemote = ctx.remote?.session;
767
+ if (typeof sessionRemote?.openWorkspacePath !== 'function') throw new Error('当前宿主不支持在文件管理器中显示文件夹');
768
+ const result = await sessionRemote.openWorkspacePath({ path: hoverData.path, action: 'reveal' });
769
+ if (result?.ok === false) throw result.error;
770
+ });
771
+ };
772
+ const editWorkspace = workspace => {
773
+ hideHover();
774
+ setEditing(workspace);
775
+ };
776
+ const rename = async row => {
777
+ const next = window.prompt('重命名对话', row.title || '未命名对话');
778
+ if (next === null) return;
779
+ const result = await ctx.sessions.rename(row.sessionId, next);
780
+ if (result?.ok === false) throw result.error;
781
+ };
782
+ const togglePin = s => run(() => pinned.has(s.sessionId) ? ctx.uiWorkspace.unpinSession(s.sessionId) : ctx.uiWorkspace.pinSession(s.sessionId));
783
+ const toggleArchive = s => run(() => archived.has(s.sessionId) ? ctx.uiWorkspace.unarchiveSession(s.sessionId) : ctx.uiWorkspace.archiveSession(s.sessionId));
784
+ // 继续使用原来的菜单与动作,只把入口改为整行右键(含 Shift+F10 / 菜单键)。
785
+ const openThreadMenu = (event, s) => {
786
+ event.preventDefault(); event.stopPropagation();
787
+ menuAnchor.current = event.currentTarget.querySelector('.dsc-thread-main');
788
+ const box = menuAnchor.current.getBoundingClientRect();
789
+ const point = event.type === 'contextmenu' && (event.clientX || event.clientY) ? { x: event.clientX, y: event.clientY } : { x: box.left + 16, y: box.bottom };
790
+ hideHover(); setProjectMenu(null); setMenu({ ...s, point });
791
+ };
792
+ const row = (s, rowActive = active) => {
793
+ const state = status.get(s.sessionId);
794
+ const actionsActive = rowActive && !menu && !projectMenu && !editing;
795
+ const pending = state?.pendingInteraction;
796
+ const activity = pending ? '等待处理' : state?.running ? '运行中' : state?.completionUnread ? '已完成' : relativeTime(s.updatedAt);
797
+ return h('div', { key: s.sessionId, className: 'dsc-thread' + (s.sessionId === currentId && panel.activePanelId === null ? ' is-active' : '') + (archived.has(s.sessionId) ? ' is-archived' : '') + (pinned.has(s.sessionId) ? ' is-pinned' : ''),
798
+ onContextMenu: e => openThreadMenu(e, s), onKeyDown: e => { if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) openThreadMenu(e, s); },
799
+ onPointerEnter: e => enterHover(e.currentTarget, { kind: 'thread', id: s.sessionId, title: s.title || '未命名对话', status: activity, workspaceTitle: workspaceOf(s.sessionId)?.title, path: workspaceOf(s.sessionId)?.path }), onPointerLeave: closeHover },
800
+ h('button', { type: 'button', className: 'dsc-thread-main', 'aria-haspopup': 'menu', onClick: () => { if (archived.has(s.sessionId)) run(() => ctx.uiWorkspace.unarchiveSession(s.sessionId)); else { ctx.uiWorkspace.openSession(s.sessionId); onNavigate?.(); } } },
801
+ h('span', { className: 'dsc-state', 'data-state': pending ? 'pending' : state?.running ? 'running' : state?.completionUnread ? 'done' : 'idle' }, (pending || state?.running || state?.completionUnread) && h(Icon, { name: 'chat', size: 14 })),
802
+ h(ThreadTitle, { title: s.title || '未命名对话' }),
803
+ h('small', { className: 'dsc-thread-time' }, activity)),
804
+ h('div', { className: 'dsc-thread-actions' },
805
+ h(ThreadAction, { label: pinned.has(s.sessionId) ? '取消置顶聊天' : '置顶聊天', className: 'dsc-pin-action', pressed: pinned.has(s.sessionId), active: actionsActive, onIntent: hideHover, onClick: () => togglePin(s) }, h(PinMark, { filled: pinned.has(s.sessionId) })),
806
+ h(ThreadAction, { label: archived.has(s.sessionId) ? '取消归档聊天' : '归档聊天', active: actionsActive, onIntent: hideHover, onClick: () => toggleArchive(s) }, h(Icon, { name: 'archive', size: 14 }))));
807
+ };
808
+ const addWorkspace = async () => {
809
+ setAdding(true);
810
+ await run(async () => {
811
+ const picked = await ctx.uiWorkspace.pickDirectory();
812
+ if (!picked) return;
813
+ const path = typeof picked === 'string' ? picked : picked.path;
814
+ if (!path) return;
815
+ const created = await ctx.workspaces.create({ path });
816
+ await ctx.uiWorkspace.openWorkspace(created.workspaceId);
817
+ onNavigate?.();
818
+ });
819
+ if (alive.current) setAdding(false);
820
+ };
821
+ const projectHeading = h('div', { className: 'dsc-tree-tools', 'data-compact': !tools },
822
+ h('button', { type: 'button', className: 'dsc-projects-toggle', 'aria-label': '项目', 'aria-expanded': projectsOpen, onClick: () => { hideHover(); setMenu(null); setProjectMenu(null); setProjectsOpen(!projectsOpen); } }, h('span', { className: 'dsc-tree-tools-label' }, '项目'), h(Icon, { name: 'chevron', size: 12 })),
823
+ h('button', { type: 'button', className: 'dsc-icon-button', title: '添加工作区', onClick: addWorkspace, disabled: adding }, h(Icon, { name: 'plus' })));
824
+ return h('div', { className: 'dsc-ui dsc-workspaces' },
825
+ query && projectHeading,
826
+ h(ErrorLine, { text: message || (catalog.error && errorText(catalog.error)) || (workspaces.error && errorText(workspaces.error)) }),
827
+ h('div', { className: 'dsc-tree-scroll' },
828
+ query ? h(React.Fragment, null, h('div', { className: 'dsc-section-title' }, '搜索结果'), visible.map(s => row(s))) : h(React.Fragment, null,
829
+ pinned.size > 0 && h('section', null, h('div', { className: 'dsc-section-title' }, '置顶'), visible.filter(s => pinned.has(s.sessionId)).map(s => row(s))),
830
+ projectHeading,
831
+ h(ProjectFold, { open: projectsOpen, className: 'dsc-project-list-fold' }, h('div', { className: 'dsc-project-list' }, workspaces.items.map(w => {
832
+ const children = visible.filter(s => w.sessionIds.includes(s.sessionId) && !pinned.has(s.sessionId));
833
+ const open = expanded[w.workspaceId] ?? w.sessionIds.includes(currentId);
834
+ return h('section', { key: w.workspaceId }, h('div', { className: 'dsc-project', onPointerEnter: e => enterHover(e.currentTarget, { kind: 'project', id: w.workspaceId, workspace: w, title: w.title || '未命名项目', count: children.length, path: w.path, pinned: w.pinned === true }), onPointerLeave: closeHover },
835
+ h('button', { type: 'button', className: 'dsc-project-main', onClick: () => { hideHover(); setMenu(null); setProjectMenu(null); setExpanded({ ...expanded, [w.workspaceId]: !open }); }, 'aria-expanded': open }, h(Icon, { name: open ? 'folderOpen' : 'folder' }), h('span', null, w.title || '未命名项目')),
836
+ h('button', { type: 'button', className: 'dsc-row-action', title: '项目菜单', 'aria-label': '项目菜单', 'aria-haspopup': 'menu', 'aria-expanded': projectMenu?.workspaceId === w.workspaceId, onClick: e => { projectAnchor.current = e.currentTarget; setHover(null); setProjectMenu(projectMenu?.workspaceId === w.workspaceId ? null : w); } }, '⋯'),
837
+ h('button', { type: 'button', className: 'dsc-row-action', title: '在此项目新建对话', 'aria-label': '在此项目新建对话', onClick: () => { ctx.uiWorkspace.startSession(w.workspaceId); onNavigate?.(); } }, h(Icon, { name: 'pencil', size: 14 }))),
838
+ h(ProjectFold, { open }, h('div', { className: 'dsc-project-children' }, children.length ? children.map(s => row(s, active && projectsOpen && open)) : h('small', { className: 'dsc-empty' }, '暂无对话'))));
839
+ }))),
840
+ h('div', { className: 'dsc-section-title' }, '最近'), visible.filter(s => !workspaceOf(s.sessionId) && !pinned.has(s.sessionId)).map(s => row(s))),
841
+ !visible.length && catalog.phase === 'ready' && h('div', { className: 'dsc-empty' }, query ? '没有匹配的对话' : '选择项目,开始新对话'),
842
+ catalog.phase !== 'ready' && h('div', { className: 'dsc-empty', role: 'status' }, '正在加载会话…')),
843
+ h('label', { className: 'dsc-archive-toggle' }, h('input', { type: 'checkbox', checked: showArchived, onChange: e => setShowArchived(e.target.checked) }), '显示已归档对话'),
844
+ menu && h(Popover, { anchor: menuAnchor, point: menu.point, onClose: () => setMenu(null), label: '对话菜单', width: 228 },
845
+ h(Option, { title: '重命名', icon: 'pencil', onClick: () => { const target = menu; setMenu(null); run(() => rename(target)); } }),
846
+ h(Option, { title: pinned.has(menu.sessionId) ? '取消置顶' : '置顶', icon: 'pin', onClick: () => { const target = menu; setMenu(null); togglePin(target); } }),
847
+ h(Option, { title: '分叉对话', icon: 'chat', onClick: () => { const target = menu; setMenu(null); run(() => ctx.uiWorkspace.forkSession(target.sessionId)); } }),
848
+ h(Option, { title: archived.has(menu.sessionId) ? '取消归档' : '归档', icon: 'archive', onClick: () => { const target = menu; setMenu(null); toggleArchive(target); } })),
849
+ // Codex 风格项目菜单:仅提供编辑与移除项目两项,编辑复用现有编辑项目对话框;从行右侧下方展开。
850
+ projectMenu && h(Popover, { anchor: projectAnchor, onClose: () => setProjectMenu(null), label: '项目菜单', width: 228, placement: 'right' },
851
+ h(Option, { title: '编辑', icon: 'pencil', onClick: () => { const target = projectMenu; setProjectMenu(null); editWorkspace(target); } }),
852
+ h(Option, { title: '移除项目', icon: 'close', onClick: () => { const target = projectMenu; setProjectMenu(null); if (!window.confirm(`移除项目“${target.title || '未命名项目'}”?`)) return; run(() => ctx.workspaces.delete(target.workspaceId)); } })),
853
+ hover && !menu && !projectMenu && (hover.kind === 'project'
854
+ ? h(ProjectHoverCard, { key: hover.id, data: hover, anchor: hoverAnchor, onClose: hideHover, onEnter: cancelHover, onLeave: closeHover, onReveal: () => revealWorkspace(hover), onEdit: () => editWorkspace(hover.workspace) })
855
+ : h(ThreadHoverCard, { key: hover.id, data: hover, anchor: hoverAnchor, onClose: hideHover, onEnter: cancelHover, onLeave: closeHover })),
856
+ editing && h(EditWorkspaceDialog, { key: editing.workspaceId, workspace: editing, onClose: () => setEditing(null), onRename: title => ctx.workspaces.rename(editing.workspaceId, title), onDelete: () => ctx.workspaces.delete(editing.workspaceId) }));
857
+ };
858
+ }
859
+
860
+ // 权限目录只读 RPC + 事件通知;世代改变时清空旧 Host 的目录。
861
+ function permissionDirectory(ctx) {
862
+ const store = createSnapshotStore({ value: null, error: null, revision: 0 });
863
+ let epoch = 0;
864
+ let disposed = false;
865
+ let generation;
866
+ const refresh = () => {
867
+ const id = ctx.connection.generation.getSnapshot()?.id;
868
+ const currentEpoch = ++epoch;
869
+ store.set({ value: null, error: null, revision: currentEpoch });
870
+ generation = id;
871
+ if (id === undefined) return;
872
+ ctx.remote.permissionPresets.catalog().then(result => {
873
+ if (!result.ok) throw result.error;
874
+ if (!disposed && epoch === currentEpoch && ctx.connection.generation.getSnapshot()?.id === id) store.set({ value: result.value, error: null, revision: currentEpoch });
875
+ }).catch(error => {
876
+ if (!disposed && epoch === currentEpoch) store.set({ value: null, error: errorText(error), revision: currentEpoch });
877
+ });
878
+ };
879
+ const offCatalog = ctx.remote.$on('permission-presets/catalog-changed', refresh);
880
+ const offGeneration = ctx.connection.generation.subscribe(() => {
881
+ if (ctx.connection.generation.getSnapshot()?.id !== generation) refresh();
882
+ });
883
+ refresh();
884
+ return { store, refresh, dispose() { disposed = true; ++epoch; offCatalog(); offGeneration(); } };
885
+ }
886
+
887
+ // 基础 bundle 只配置 sandbox/approval,不提供 description;内置项在宿主缺省时补回可核实的范围说明。
888
+ const PERMISSION_COPY = {
889
+ 'read-only': {
890
+ title: '仅可查看',
891
+ hostNames: ['Read Only'],
892
+ description: '只读沙箱会阻止文件修改;需要扩大访问范围时会请求批准。'
893
+ },
894
+ 'workspace-write': {
895
+ title: '工作区内修改',
896
+ hostNames: ['Workspace Write'],
897
+ description: '可在工作区及允许的临时目录内修改文件;扩大访问范围前会请求批准。'
898
+ },
899
+ 'danger-full-access': {
900
+ title: '完全权限',
901
+ hostNames: ['Full Access', 'Full access'],
902
+ description: '可访问完整文件系统,且不再逐项请求批准。'
903
+ },
904
+ auto: {
905
+ title: '自动审查 · EXP',
906
+ hostNames: [],
907
+ description: '无沙箱运行;每次原生工具调用和 PTC 内层调用前,由同一模型进行实验性审查。'
908
+ },
909
+ custom: {
910
+ title: '自定义',
911
+ hostNames: ['Custom'],
912
+ description: '当前沙箱与审批设置不匹配任何已配置的权限预设。'
913
+ }
914
+ };
915
+ function permissionCopy(option) {
916
+ const fallback = PERMISSION_COPY[option.value];
917
+ const hostName = typeof option.name === 'string' && option.name.trim() ? option.name : option.value;
918
+ const useLocalizedTitle = fallback && (option.value === 'auto' || hostName === option.value || fallback.hostNames.includes(hostName));
919
+ return {
920
+ title: useLocalizedTitle ? fallback.title : hostName,
921
+ description: option.description || fallback?.description || '具体范围由当前 DSH 配置提供。'
922
+ };
923
+ }
924
+
925
+ function makePermissionPicker(ctx, directory) {
926
+ return function PermissionPicker({ sessionId, useProjection, locked }) {
927
+ const selection = useProjection('permissions');
928
+ const catalog = useSource(directory.store);
929
+ const [open, setOpen] = useState(false);
930
+ const [busy, setBusy] = useState(false);
931
+ const [confirmation, setConfirmation] = useState(null);
932
+ const [acknowledged, setAcknowledged] = useState(false);
933
+ const [help, setHelp] = useState(false);
934
+ const [error, setError] = useState('');
935
+ const anchor = useRef(null);
936
+ const alive = useRef(true);
937
+ useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []);
938
+ useEffect(() => { setOpen(false); setConfirmation(null); setAcknowledged(false); }, [catalog.revision, locked, sessionId]);
939
+ // 不用消失的控件掩盖目录失败;没有真实会话投影时只显示状态,不伪造可选权限。
940
+ if (!selection) return h('span', { className: 'dsc-ui dsc-control-status', role: 'status', title: '宿主尚未提供此会话的权限状态' }, h(Icon, { name: 'shield' }), '权限未就绪');
941
+ if (!catalog.value) return h('span', { className: 'dsc-ui dsc-control-status', role: 'status' }, h(Icon, { name: 'shield' }), catalog.error ? '权限加载失败' : '权限加载中…', catalog.error && h('button', { type: 'button', onClick: directory.refresh, title: catalog.error }, '重试'));
942
+ const options = catalog.value.options;
943
+ const current = options.find(x => x.value === selection.currentValue);
944
+ const currentCopy = current ? permissionCopy(current) : permissionCopy({ value: selection.currentValue });
945
+ const submit = async value => {
946
+ const binding = ctx.sessions.binding(sessionId);
947
+ if (!binding || locked || busy || !options.some(x => x.value === value)) return;
948
+ setBusy(true); setError('');
949
+ try {
950
+ const result = await binding.session.command('/permission ' + value);
951
+ if (!result.ok) throw result.error;
952
+ if (!result.value.matched) throw new Error('宿主没有接受权限命令');
953
+ } catch (e) { if (alive.current) setError(errorText(e)); }
954
+ finally { if (alive.current) setBusy(false); }
955
+ };
956
+ const choose = value => {
957
+ if (value === selection.currentValue) { setOpen(false); return; }
958
+ if (value === 'danger-full-access' || value === 'auto') { setOpen(false); setAcknowledged(false); setConfirmation(value); return; }
959
+ setOpen(false); submit(value);
960
+ };
961
+ return h('div', { className: 'dsc-ui dsc-permission' },
962
+ h('button', { type: 'button', ref: anchor, className: 'dsc-pill', 'data-warning': selection.currentValue === 'danger-full-access', title: '更改权限', disabled: locked || busy, 'aria-haspopup': 'menu', 'aria-expanded': open, onClick: () => setOpen(!open) }, h(Icon, { name: 'shield' }), busy ? '保存中…' : currentCopy.title),
963
+ open && h(Popover, { anchor, onClose: () => setOpen(false), label: '更改权限', width: 322, className: 'dsc-permission-menu' },
964
+ h('div', { className: 'dsc-permission-heading' }, h('span', null, '应如何批准 DSH 操作?'), h('button', { type: 'button', 'aria-expanded': help, onClick: () => setHelp(!help) }, '了解更多')),
965
+ help && h('p', { className: 'dsc-permission-help' }, '权限决定 DSH 可执行的操作范围。选择完全访问或自动审查时,请阅读确认说明;每个选项的具体范围见下方描述。'),
966
+ options.map(option => {
967
+ const copy = permissionCopy(option);
968
+ return h(Option, { key: option.value, title: copy.title, detail: copy.description, icon: 'shield', warning: option.value === 'danger-full-access', selected: option.value === selection.currentValue, onClick: () => choose(option.value) });
969
+ })),
970
+ h(ErrorLine, { text: error }),
971
+ confirmation && h(RiskConfirmation, { open: true, title: confirmation === 'auto' ? '启用自动审查' : '启用完全访问权限',
972
+ description: confirmation === 'auto' ? '实验性自动审查会在完整沙箱访问下处理审批。请确认你理解自动审查的执行范围。' : 'DSH 将获得完整文件系统和命令执行访问,且不再逐项请求批准。',
973
+ acknowledgeLabel: '我理解并接受此权限范围', cancelLabel: '取消', closeLabel: '关闭', confirmLabel: '确认启用', acknowledged, disabled: locked || busy,
974
+ onAcknowledgedChange: setAcknowledged, onCancel: () => setConfirmation(null), onConfirm: () => { if (!acknowledged) return; const value = confirmation; setConfirmation(null); submit(value); } }));
975
+ };
976
+ }
977
+
978
+ function ReasoningSlider({ levels, index, modelName, pending, onCommit, onSelectModel, onReset }) {
979
+ const [preview, setPreview] = useState(index);
980
+ const [dragging, setDragging] = useState(false);
981
+ const [burst, setBurst] = useState(0);
982
+ const track = useRef(null);
983
+ const current = useRef(index);
984
+ const target = useRef(null);
985
+ const count = levels.length;
986
+ // 提交期间宿主状态可能先回旧档再落到新档;锁定目标档位,避免预览被中途值拉回造成闪烁。
987
+ useEffect(() => {
988
+ if (dragging) return;
989
+ const wanted = target.current;
990
+ if (wanted === null) { current.current = index; setPreview(index); return; }
991
+ if (index === wanted) { target.current = null; current.current = index; setPreview(index); return; }
992
+ if (pending) return;
993
+ const timer = setTimeout(() => {
994
+ if (target.current !== null && !pending) {
995
+ target.current = null;
996
+ current.current = index;
997
+ setPreview(index);
998
+ }
999
+ }, 200);
1000
+ return () => clearTimeout(timer);
1001
+ }, [index, dragging, pending]);
1002
+ const stars = useMemo(() => Array.from({ length: 18 }, (_, i) => ({ left: `${8 + (i * 37) % 85}%`, top: `${22 + (i * 29) % 57}%`, width: `${1.2 + (i % 5) * .24}px`, height: `${1.2 + (i % 5) * .24}px`, '--dx': `${12 + i % 13}px`, '--dy': `${2 + i % 5}px`, '--pdur': `${2.6 + (i % 9) * .2}s`, '--pdelay': `${-(i % 6) * .7}s` })), []);
1003
+ const ratio = count > 1 ? preview / (count - 1) : 0;
1004
+ const level = levels[preview];
1005
+ const update = next => {
1006
+ const n = Math.max(0, Math.min(count - 1, next));
1007
+ if (levels[n].id === 'max' && levels[current.current]?.id !== 'max') setBurst(x => x + 1);
1008
+ current.current = n; setPreview(n);
1009
+ };
1010
+ const fromPointer = e => { const box = track.current.getBoundingClientRect(); return Math.round(Math.max(0, Math.min(1, (e.clientX - box.left - 14) / Math.max(1, box.width - 28))) * (count - 1)); };
1011
+ const commit = () => {
1012
+ if (pending || levels[current.current].id === levels[index]?.id) return;
1013
+ target.current = current.current;
1014
+ onCommit(levels[current.current]);
1015
+ };
1016
+ return h('div', { className: 'dsc-reasoning', 'data-level': level.id },
1017
+ h('button', { type: 'button', className: 'dsc-reasoning-reset', 'aria-disabled': pending ? 'true' : undefined, onClick: () => { if (!pending) onReset(); }, title: '重置为默认推理强度', 'aria-label': '重置为默认推理强度' }, h('svg', { viewBox: '0 0 16 16', width: 15, height: 15, fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, h('path', { d: 'M13.2 8a5.2 5.2 0 1 1-1.6-3.7M13.4 2.7v2.9h-2.9' }))),
1018
+ h('div', { className: 'dsc-reasoning-heading' }, h('strong', null, level.name), h('button', { type: 'button', className: 'dsc-reasoning-model', onClick: () => { if (!pending) onSelectModel(); }, 'aria-disabled': pending ? 'true' : undefined, title: '修改模型' }, modelName, h(Icon, { name: 'chevron', size: 11, style: { transform: 'rotate(-90deg)' } }))),
1019
+ h('div', { ref: track, role: 'slider', tabIndex: pending ? -1 : 0, className: 'dsc-slider', 'data-level': level.id, 'aria-label': '思考强度', 'aria-valuemin': 0, 'aria-valuemax': count - 1, 'aria-valuenow': preview, 'aria-valuetext': level.name, 'aria-disabled': !!pending,
1020
+ style: { '--ratio': ratio, '--fill-ratio': `calc((14px + (100% - 28px) * ${ratio}) / 100%)` },
1021
+ onPointerDown: e => { if (pending) return; e.preventDefault(); e.currentTarget.focus(); e.currentTarget.setPointerCapture(e.pointerId); setDragging(true); update(fromPointer(e)); },
1022
+ onPointerMove: e => { if (e.currentTarget.hasPointerCapture(e.pointerId)) update(fromPointer(e)); },
1023
+ onPointerUp: e => { if (!e.currentTarget.hasPointerCapture(e.pointerId)) return; update(fromPointer(e)); e.currentTarget.releasePointerCapture(e.pointerId); setDragging(false); commit(); },
1024
+ onPointerCancel: e => { if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId); setDragging(false); current.current = index; setPreview(index); },
1025
+ onKeyDown: e => { if (pending) return; const n = e.key === 'Home' ? 0 : e.key === 'End' ? count - 1 : ['ArrowLeft', 'ArrowDown'].includes(e.key) ? current.current - 1 : ['ArrowRight', 'ArrowUp'].includes(e.key) ? current.current + 1 : null; if (n === null) return; e.preventDefault(); e.stopPropagation(); update(n); commit(); } },
1026
+ h('div', { className: 'dsc-slider-track' }, h('div', { className: 'dsc-slider-fill', style: { transform: `scaleX(${.061 + .939 * ratio})` } }), h('div', { className: 'dsc-slider-max' }), h('div', { className: 'dsc-stars' }, stars.map((style, i) => h('i', { key: i, style })))),
1027
+ levels.map((x, i) => h('span', { key: x.id ?? 'default', className: 'dsc-tick' + (i <= preview ? ' is-on' : ''), style: { left: `calc(14px + (100% - 28px) * ${count > 1 ? i / (count - 1) : 0})` } })),
1028
+ h('div', { className: 'dsc-thumb-position', style: { transform: `translateX(calc((100% - 28px) * ${ratio}))` } }, h('span', { className: 'dsc-thumb' }), burst > 0 && level.id === 'max' && h('span', { key: burst, className: 'dsc-burst' }, Array.from({ length: 12 }, (_, i) => h('i', { key: i, style: { '--bx': `${Math.cos(i * Math.PI / 6) * 27}px`, '--by': `${Math.sin(i * Math.PI / 6) * 27}px` } }))))),
1029
+ h('span', { className: 'dsc-sr-only', role: 'status' }, pending ? '正在保存…' : level.description || level.name));
1030
+ }
1031
+
1032
+ // 模型控件渲染异常时在页面内显示原因,避免静默让位给宿主原生控件。
1033
+ class ModelPickerBoundary extends React.Component {
1034
+ constructor(props) { super(props); this.state = { error: null }; }
1035
+ static getDerivedStateFromError(error) { return { error }; }
1036
+ componentDidCatch(error, info) { console.error('codex-style: 模型控件渲染失败', error, info); }
1037
+ render() {
1038
+ if (!this.state.error) return this.props.children;
1039
+ const message = this.state.error?.message || String(this.state.error);
1040
+ return h('div', { className: 'dsc-ui dsc-error', role: 'alert', style: { maxWidth: 360 } }, `模型控件渲染失败:${message}`);
1041
+ }
1042
+ }
1043
+
1044
+ function makeModelPicker(ctx) {
1045
+ function ModelPicker({ sessionId, locked, directory }) {
1046
+ const rawState = useSource(directory.store);
1047
+ const state = { current: rawState?.current ?? null, groups: rawState?.groups ?? [], failures: rawState?.failures ?? [], status: rawState?.status, error: rawState?.error, pending: rawState?.pending, retainedEffort: rawState?.retainedEffort };
1048
+ const [pane, setPane] = useState(null);
1049
+ const [shown, setShown] = useState(null);
1050
+ const [closing, setClosing] = useState(false);
1051
+ const [query, setQuery] = useState('');
1052
+ const [error, setError] = useState('');
1053
+ const anchor = useRef(null);
1054
+ const alive = useRef(true);
1055
+ const subagent = ctx.sessions.subagentAddress(sessionId) !== undefined;
1056
+ useEffect(() => { alive.current = true; if (!subagent && typeof directory.load === 'function') directory.load().catch(e => { if (alive.current) setError(errorText(e)); }); return () => { alive.current = false; }; }, [directory, subagent]);
1057
+ const hasCurrent = state.current !== null;
1058
+ useEffect(() => { if (locked || !hasCurrent) setPane(null); }, [locked, hasCurrent]);
1059
+ // 关闭时保留一小段淡出,避免弹层硬切除;视图切换由 Popover 的 key 重放进场动画。
1060
+ useEffect(() => {
1061
+ if (pane) { setShown(pane); setClosing(false); return; }
1062
+ if (!shown) return;
1063
+ setClosing(true);
1064
+ const timer = setTimeout(() => { setShown(null); setClosing(false); }, 160);
1065
+ return () => clearTimeout(timer);
1066
+ }, [pane, shown]);
1067
+ const current = state.current;
1068
+ const group = state.groups.find(x => x.id === current?.provider);
1069
+ const model = group?.models?.find(x => x.id === current?.model);
1070
+ const reasoning = model?.reasoning;
1071
+ const levels = reasoning ? [...(reasoning.defaultEffort === undefined ? [{ id: undefined, name: '默认' }] : []), ...(reasoning.efforts ?? [])] : [];
1072
+ const index = Math.max(0, levels.findIndex(x => x.id === (current?.reasoningEffort ?? reasoning?.defaultEffort)));
1073
+ const pending = !!state.pending;
1074
+ const select = async selection => {
1075
+ if (locked || pending || subagent) return false;
1076
+ setError('');
1077
+ try { const result = typeof directory.select === 'function' ? await directory.select(selection) : { ok: false, error: new Error('当前宿主不支持切换模型') }; if (!result.ok) throw result.error; return true; }
1078
+ catch (e) { if (alive.current) setError(errorText(e)); return false; }
1079
+ };
1080
+ if (subagent) return null;
1081
+ return h('div', { className: 'dsc-ui dsc-model' }, h('button', { ref: anchor, type: 'button', className: 'dsc-pill dsc-model-trigger', disabled: locked, 'aria-haspopup': 'dialog', 'aria-expanded': pane !== null, 'aria-busy': pending, title: '选择推理强度', onClick: () => setPane(pane ? null : reasoning ? 'effort' : 'root') },
1082
+ model ? h(React.Fragment, null,
1083
+ h('span', { className: 'dsc-model-name' }, model.name),
1084
+ levels[index] && h('span', { className: 'dsc-effort-name' }, levels[index].name)) : h('span', { className: 'dsc-model-name' }, '选择模型'), h(Icon, { name: 'chevron', size: 12 })),
1085
+ shown && h(Popover, { key: shown, anchor, onClose: () => setPane(null), width: shown === 'effort' ? 255 : 330, label: shown === 'effort' ? '选择推理强度' : '修改模型', role: 'dialog', className: (shown === 'effort' ? 'dsc-reasoning-pop' : 'dsc-model-panel') + (closing ? ' dsc-menu-closing' : '') },
1086
+ shown === 'effort' ? (model && levels.length ? h(ReasoningSlider, { levels, index, modelName: model.name, pending, onSelectModel: () => setPane('root'), onReset: () => select({ provider: current.provider, model: current.model, ...(reasoning.defaultEffort === undefined ? {} : { reasoningEffort: reasoning.defaultEffort }) }), onCommit: level => select({ provider: current.provider, model: current.model, ...(level.id === undefined ? {} : { reasoningEffort: level.id }) }) }) : h('div', { className: 'dsc-empty' }, pending ? '正在加载推理档位…' : '该模型暂无可用的推理档位')) : h(React.Fragment, null,
1087
+ h('div', { className: 'dsc-menu-heading' }, '修改模型'),
1088
+ h('input', { className: 'dsc-model-search', placeholder: '搜索模型或渠道', value: query, readOnly: pending, onChange: e => setQuery(e.target.value) }),
1089
+ state.groups.map(g => {
1090
+ const models = (g.models ?? []).filter(m => `${g.name} ${m.name}`.toLowerCase().includes(query.toLowerCase()));
1091
+ return models.length ? h('section', { key: g.id }, h('div', { className: 'dsc-model-group' }, g.name), models.map(m => h(Option, { key: m.id, title: m.name, detail: m.description, selected: g.id === current?.provider && m.id === current?.model, disabled: pending, onClick: async () => { const isCurrent = g.id === current?.provider && m.id === current?.model; if (isCurrent) { const currentReasoning = m.reasoning; const hasLevels = currentReasoning ? (currentReasoning.defaultEffort === undefined ? 1 : 0) + (currentReasoning.efforts?.length ?? 0) : 0; setPane(hasLevels > 0 ? 'effort' : null); return; } const ok = await select({ provider: g.id, model: m.id, ...(m.reasoning?.defaultEffort === undefined ? {} : { reasoningEffort: m.reasoning.defaultEffort }) }); if (!ok) return; const snapshot = directory.store.getSnapshot(); const nextReasoning = (snapshot?.groups ?? []).find(item => item.id === g.id)?.models?.find(item => item.id === m.id)?.reasoning; const nextLevels = nextReasoning ? (nextReasoning.defaultEffort === undefined ? 1 : 0) + (nextReasoning.efforts?.length ?? 0) : 0; setPane(nextLevels > 0 ? 'effort' : null); } }))) : null;
1092
+ }),
1093
+ state.status === 'loading' && h('div', { className: 'dsc-empty' }, '正在加载模型…'),
1094
+ !state.groups.length && state.status !== 'loading' && h('div', { className: 'dsc-empty' }, '暂无可用模型'),
1095
+ state.failures.map(f => h(ErrorLine, { key: f.id, text: `${f.name}: ${f.message}` }))),
1096
+ h(ErrorLine, { text: error || state.error })),
1097
+ !pane && h(ErrorLine, { text: error || state.error }));
1098
+ }
1099
+ return function ModelPickerGuard(props) {
1100
+ if (!props.directory || !props.directory.store) return h('span', { className: 'dsc-ui dsc-control-status' }, '模型目录未就绪');
1101
+ return h(ModelPickerBoundary, null, h(ModelPicker, props));
1102
+ };
1103
+ }
1104
+
1105
+ function apply(ctx) {
1106
+ console.warn('[codex-style] apply 开始 v' + PLUGIN_VERSION);
1107
+ const tokenValues = {
1108
+ '--dsw-alias-bg-base': '#181818', '--dsw-alias-bg-layer-1': '#212121', '--dsw-alias-bg-layer-2': '#282828', '--dsw-alias-bg-layer-3': '#2d2d2d', '--dsw-alias-bg-overlay': '#282828',
1109
+ '--dsw-alias-bg-module-platform': '#212121', '--dsw-alias-label-primary': '#fff', '--dsw-alias-label-secondary': 'rgba(255,255,255,.7)', '--dsw-alias-label-tertiary': 'rgba(255,255,255,.5)',
1110
+ '--dsw-alias-label-caption': 'rgba(255,255,255,.5)', '--dsw-alias-label-dimmed': 'rgba(255,255,255,.32)', '--dsw-alias-label-primary-dimmed': 'rgba(255,255,255,.6)', '--dsw-alias-label-primary-bluish': '#eef3ff', '--dsw-alias-label-primary-inverted': '#131313',
1111
+ // 本版始终呈现深色;宿主 preference=system/light 时也要成对覆盖按钮背景与文字。
1112
+ '--dsw-alias-button-elevated-fill': 'var(--cx-active)', '--dsw-alias-button-floating-hover': 'rgba(255,255,255,.12)',
1113
+ '--dsw-alias-border-l1': 'rgba(255,255,255,.08)', '--dsw-alias-border-l2': 'rgba(255,255,255,.08)', '--dsw-alias-border-l3': 'rgba(255,255,255,.12)', '--dsw-alias-border-l4': 'rgba(255,255,255,.16)',
1114
+ '--dsw-alias-brand-primary': '#0285ff', '--dsw-alias-link': '#339cff', '--dsw-alias-interactive-bg-hover': 'rgba(255,255,255,.08)', '--dsw-alias-interactive-bg-hover-solid': '#282828',
1115
+ // 2026-10-23 按用户 Codex 截图采样:顶栏/窗口 chrome #202020、用户气泡 #222222(对话区保持 #181818)。
1116
+ '--dsw-specific-sidebar-fill': '#202020', '--dsw-specific-bubble': '#222222', '--dsw-specific-input-major': 'rgba(55,55,55,.94)', '--dsw-specific-menu': '#2d2d2d', '--dsw-specific-selector': 'transparent',
1117
+ // 设置页左栏与侧栏导航项:light 主题下宿主给浅色底,插件把文字固定为白色,会成白底白字;这里按宿主暗色主题成对补齐。
1118
+ '--dsw-specific-sidebar-nav-item-active': '#43454a', '--dsw-specific-sidebar-nav-item-hover': '#2c2c2e', '--dsw-specific-sidebar-nav-item-active-accent': '#353638',
1119
+ // markdown 代码外观:light 主题下 inline code / 代码块顶栏也是浅底,需一并转成与对话背景有区分的深色。
1120
+ '--dsw-alias-markdown-code-block': '#212121', '--dsw-alias-markdown-inline-code': '#292929', '--dsw-alias-markdown-code-block-banner': '#2c2c2e',
1121
+ '--dsw-alias-markdown-code-segment-selected': '#353638', '--dsw-alias-markdown-code-segment-unselected': '#1b1b1c',
1122
+ '--dsw-alias-state-business-primary': '#339cff', '--dsw-alias-state-business-tertiary': '#0285ff26', '--dsw-alias-state-error-primary': '#ff6764', '--dsw-alias-state-warn-primary': '#ff8549', '--dsw-alias-state-warn-secondary': '#ff8549bb', '--dsw-alias-state-warn-tertiary': '#ff854926',
1123
+ '--dsw-alias-scrollbar-bg-l1': '#ffffff26', '--dsw-alias-scrollbar-bg-l2': '#ffffff32', '--dsw-alias-scrollbar-hover-l2': '#ffffff59',
1124
+ '--dsw-radius-panel': '25px', '--dsw-menu-surface-fill': '#2d2d2d', '--dsw-elevation-soft': '0 0 0 .5px rgba(255,255,255,.16),0 3px 8px #0000000a'
1125
+ };
1126
+ ctx.effect(() => ctx.theme.overrideTokens(PLUGIN_ID, Object.fromEntries(Object.entries(tokenValues).map(([k, v]) => [k, { light: v, dark: v }]))), 'codex-style: theme');
1127
+ // 宿主偏好是 system 且系统为浅色时只会启用 light 色板;组件私有的 light/dark 分支(如变更卡的 --changes-fill)
1128
+ // 不在 token 覆盖范围内。注册并保持一个暗色主题,让 data-ds-dark-theme 生效,再用上面的 token 层覆盖 Codex 配色。
1129
+ const DARK_THEME_ID = 'dsh-codex-dark';
1130
+ ctx.effect(() => {
1131
+ let disposeTheme;
1132
+ try {
1133
+ disposeTheme = ctx.theme.register({ id: DARK_THEME_ID, colorScheme: 'dark', tokens: {} });
1134
+ } catch (error) {
1135
+ // 注册失败时退回纯 token 覆盖,不影响插件其余部分。
1136
+ console.warn('codex-style: 暗色主题注册失败', error);
1137
+ return () => {};
1138
+ }
1139
+ const previous = ctx.theme.getTheme()?.preference;
1140
+ const keep = snapshot => {
1141
+ if (snapshot?.preference === DARK_THEME_ID) return;
1142
+ try { ctx.theme.setTheme(DARK_THEME_ID); } catch (error) { console.warn('codex-style: 无法启用内置暗色主题', error); }
1143
+ };
1144
+ keep(ctx.theme.getTheme());
1145
+ const off = ctx.on('theme/change', keep);
1146
+ return () => {
1147
+ off();
1148
+ try { if (previous !== undefined && previous !== DARK_THEME_ID) ctx.theme.setTheme(previous); } catch (error) { console.warn('codex-style: 恢复原主题失败', error); }
1149
+ disposeTheme();
1150
+ };
1151
+ }, 'codex-style: dark theme');
1152
+ ctx.effect(() => {
1153
+ const previous = document.body.getAttribute('data-dsh-codex-style');
1154
+ document.body.setAttribute('data-dsh-codex-style', PLUGIN_VERSION);
1155
+ const style = document.createElement('style'); style.dataset.dshCodexStyle = PLUGIN_VERSION; style.textContent = CSS_TEXT; document.head.appendChild(style);
1156
+ return () => { style.remove(); if (previous === null) document.body.removeAttribute('data-dsh-codex-style'); else document.body.setAttribute('data-dsh-codex-style', previous); };
1157
+ }, 'codex-style: stylesheet');
1158
+ const register = (scope, name, component, inject) => scope.slots.inject(name, () => {
1159
+ const priorities = scope.slots.entries(name).map(entry => entry.options.priority ?? 0);
1160
+ return scope.slots.register({ name, priority: Math.min(0, ...priorities) - 1, ...(inject ? { inject } : {}) }, component);
1161
+ });
1162
+ const components = {
1163
+ 'sidebar.brand.mark': () => h('img', { className: 'dsc-brand-mark', src: BRAND_MARK, alt: '' }),
1164
+ 'sidebar.brand.name': () => h('span', { className: 'dsc-ui dsc-brand' }, h('strong', null, 'deepseek'), h('small', null, 'HARNESS')),
1165
+ 'settings.trigger': () => h('span', { className: 'dsc-ui dsc-settings-trigger' }, h(Icon, { name: 'settings', size: 18 }), '设置')
1166
+ };
1167
+ for (const [name, component] of Object.entries(components)) register(ctx, name, component);
1168
+ ctx.slots.inject('plugins.detail.actions', () => ctx.slots.register({ name: 'plugins.detail.actions', id: 'codex-style:check-update' }, PluginUpdateAction));
1169
+ // 等各区域自己的服务就绪;权限 RPC 的命名空间不能由 remote 根服务存在来推断。
1170
+ ctx.inject(['sessions', 'uiSession', 'workspaces', 'uiWorkspace', 'layout', 'shortcuts', 'remote', 'locale'], scope => {
1171
+ const sidebar = createSidebarPresentation();
1172
+ scope.effect(() => () => sidebar.dispose(), 'codex-style: sidebar presentation');
1173
+ register(scope, 'sidebar.workspaces', makeSidebarChrome(scope, sidebar));
1174
+ scope.slots.inject('shell.overlay', () => scope.slots.register({ name: 'shell.overlay', id: 'codex-style:shell' }, makeShellChrome(scope, sidebar)));
1175
+ register(scope, 'conversation.header.leading', makeCollapsedNewChat(scope));
1176
+ });
1177
+ ctx.inject(['locale', 'remote', 'remote.account'], scope => register(scope, 'settings.launcher', makeGearLauncher(scope)));
1178
+ ctx.inject(['workspaces'], scope => {
1179
+ register(scope, 'conversation.hero.brand.mark', EmptyHero);
1180
+ register(scope, 'conversation.hero.workspace', makeHeroWorkspace(scope));
1181
+ });
1182
+ // 模型控件:主动探测 modelDirectories 并重试注册,避免因服务就绪通知延迟而静默缺失、回退到宿主原生控件。
1183
+ // directoryFor / select 内部会访问 session Remote;注入列表与宿主模型选择插件保持一致。
1184
+ ctx.inject(['sessions', 'slots', 'locale', 'remote', 'remote.session'], scope => {
1185
+ let mounted = false;
1186
+ const mount = () => {
1187
+ if (mounted) return true;
1188
+ const directories = scope.get('modelDirectories');
1189
+ console.warn('[codex-style] modelDirectories 探测:', Boolean(directories));
1190
+ if (!directories) return false;
1191
+ const Picker = makeModelPicker(scope);
1192
+ scope.slots.inject('conversation.input.model', () => {
1193
+ const tryRegister = priority => scope.slots.register({
1194
+ name: 'conversation.input.model',
1195
+ priority,
1196
+ inject: sessionId => ({ directory: directories.directoryFor(sessionId) })
1197
+ }, Picker);
1198
+ try {
1199
+ // single slot 按最低 priority 渲染:取现有注册的最低值再低 1,才能覆盖宿主原生控件。
1200
+ const registered = scope.slots.entries('conversation.input.model').map(entry => entry.options?.priority ?? 0);
1201
+ let priority = Math.min(0, ...registered) - 1;
1202
+ let dispose;
1203
+ try {
1204
+ dispose = tryRegister(priority);
1205
+ } catch (error) {
1206
+ priority = Math.min(priority, -1000);
1207
+ dispose = tryRegister(priority);
1208
+ }
1209
+ console.warn('[codex-style] 自绘模型控件已注册,priority', priority);
1210
+ return dispose;
1211
+ } catch (error) {
1212
+ console.error('[codex-style] 自绘模型控件注册失败', error);
1213
+ return () => {};
1214
+ }
1215
+ });
1216
+ mounted = true;
1217
+ return true;
1218
+ };
1219
+ if (mount()) return;
1220
+ let attempts = 0;
1221
+ const timer = setInterval(() => { attempts += 1; if (mount() || attempts > 40) clearInterval(timer); }, 250);
1222
+ scope.effect(() => () => clearInterval(timer), 'codex-style: model directory probe');
1223
+ });
1224
+ ctx.inject(['connection', 'sessions', 'remote', 'remote.permissionPresets'], scope => {
1225
+ const permissions = permissionDirectory(scope);
1226
+ scope.effect(() => () => permissions.dispose(), 'codex-style: permission directory');
1227
+ register(scope, 'conversation.input.permission', makePermissionPicker(scope, permissions));
1228
+ });
1229
+ // 对话正文列与重绘后的输入框对齐:直接测量输入框卡片的视觉宽度并同步给正文列,
1230
+ // 避免宿主变量(正文 +32px)与实际渲染的卡宽不一致。
1231
+ ctx.effect(() => {
1232
+ let frame = 0;
1233
+ const sync = () => {
1234
+ frame = 0;
1235
+ const bar = document.querySelector('[data-slot="conversation.composer.bar"]');
1236
+ if (!bar) return;
1237
+ let width = 0;
1238
+ for (const card of bar.querySelectorAll('[class*="_card"]')) width = Math.max(width, card.getBoundingClientRect().width);
1239
+ if (width <= 0) width = bar.getBoundingClientRect().width;
1240
+ if (width > 0) document.documentElement.style.setProperty('--dsc-composer-visual-width', `${Math.round(width)}px`);
1241
+ };
1242
+ const schedule = () => { if (!frame) frame = requestAnimationFrame(sync); };
1243
+ const observer = new ResizeObserver(schedule);
1244
+ observer.observe(document.body);
1245
+ for (const el of document.querySelectorAll('[data-slot="conversation.composer.bar"], [data-conversation-content]')) observer.observe(el);
1246
+ window.addEventListener('resize', schedule);
1247
+ const timer = setInterval(schedule, 1200);
1248
+ schedule();
1249
+ return () => {
1250
+ observer.disconnect();
1251
+ window.removeEventListener('resize', schedule);
1252
+ clearInterval(timer);
1253
+ if (frame) cancelAnimationFrame(frame);
1254
+ document.documentElement.style.removeProperty('--dsc-composer-visual-width');
1255
+ };
1256
+ }, 'codex-style: composer width sync');
1257
+ }
1258
+ exports.name = 'codex-style';
1259
+ exports.inject = ['slots', 'theme'];
1260
+ exports.apply = apply;
1261
+
1262
+ // 手动只读检查;固定 scoped 身份,不能查询 npm 上同名的第三方包。
1263
+ const UPDATE_PACKAGE = '@xinxing1708/dsh-codex-style';
1264
+ const UPDATE_REGISTRIES = [
1265
+ { id: 'npmmirror', label: 'npmmirror 中国镜像', url: 'https://registry.npmmirror.com/' },
1266
+ { id: 'tencent', label: '腾讯云 npm 镜像', url: 'https://mirrors.cloud.tencent.com/npm/' },
1267
+ { id: 'npm', label: 'npm 官方源', url: 'https://registry.npmjs.org/' }
1268
+ ];
1269
+
1270
+ function updateVersionParts(value) {
1271
+ if (typeof value !== 'string') return null;
1272
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
1273
+ if (!match) return null;
1274
+ const pre = match[4]?.split('.');
1275
+ if (pre?.some(part => /^\d+$/.test(part) && part.length > 1 && part[0] === '0')) return null;
1276
+ return { core: match.slice(1, 4).map(BigInt), pre };
1277
+ }
1278
+ function compareUpdateVersions(left, right) {
1279
+ const a = updateVersionParts(left), b = updateVersionParts(right);
1280
+ if (!a || !b) return null;
1281
+ for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i] ? 1 : -1;
1282
+ if (!a.pre || !b.pre) return a.pre ? -1 : b.pre ? 1 : 0;
1283
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
1284
+ const x = a.pre[i], y = b.pre[i];
1285
+ if (x === y) continue;
1286
+ if (x === undefined || y === undefined) return x === undefined ? -1 : 1;
1287
+ const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
1288
+ if (xn && yn) return BigInt(x) > BigInt(y) ? 1 : -1;
1289
+ if (xn !== yn) return xn ? -1 : 1;
1290
+ return x > y ? 1 : -1;
1291
+ }
1292
+ return 0;
1293
+ }
1294
+
1295
+ async function checkPluginUpdate(currentVersion, { signal, fetcher = globalThis.fetch, timeoutMs = 6500 } = {}) {
1296
+ signal?.throwIfAborted();
1297
+ if (!updateVersionParts(currentVersion)) return { state: 'error', message: '无法读取当前插件版本,请重新打开插件详情。' };
1298
+ // 并行读取多个源,取已返回的最高版本,避免先响应的滞后镜像遮住新版。
1299
+ const sources = await Promise.all(UPDATE_REGISTRIES.map(async registry => {
1300
+ const controller = new AbortController();
1301
+ const abort = () => controller.abort();
1302
+ signal?.addEventListener('abort', abort, { once: true });
1303
+ const timer = setTimeout(abort, timeoutMs);
1304
+ try {
1305
+ const response = await fetcher(registry.url + UPDATE_PACKAGE.replace('/', '%2F') + '/latest', {
1306
+ signal: controller.signal, mode: 'cors', credentials: 'omit', cache: 'no-store', redirect: 'error', referrerPolicy: 'no-referrer', headers: { Accept: 'application/json' }
1307
+ });
1308
+ if (response.status === 404) return { ...registry, status: 'missing' };
1309
+ if (!response.ok) throw new Error('HTTP ' + response.status);
1310
+ const metadata = await response.json();
1311
+ if (metadata.name !== UPDATE_PACKAGE || !updateVersionParts(metadata.version)) throw new Error('无效的包元数据');
1312
+ return { ...registry, status: 'ok', version: metadata.version };
1313
+ } catch {
1314
+ return { ...registry, status: 'error' };
1315
+ } finally {
1316
+ clearTimeout(timer);
1317
+ signal?.removeEventListener('abort', abort);
1318
+ }
1319
+ }));
1320
+ signal?.throwIfAborted();
1321
+ const available = sources.filter(source => source.status === 'ok');
1322
+ if (!available.length) {
1323
+ const unpublished = sources.find(source => source.id === 'npm')?.status === 'missing';
1324
+ return { state: unpublished ? 'unpublished' : 'error', sources, message: unpublished ? '该包尚未发布到 npm,或已被撤回,暂时无法查询版本。' : '暂时无法从更新源读取版本,请稍后重试。' };
1325
+ }
1326
+ const latest = available.reduce((best, source) => {
1327
+ const compared = compareUpdateVersions(source.version, best.version);
1328
+ return compared > 0 || (compared === 0 && source.id === 'npm') ? source : best;
1329
+ });
1330
+ const comparison = compareUpdateVersions(latest.version, currentVersion);
1331
+ return { state: comparison > 0 ? 'update' : comparison < 0 ? 'ahead' : 'current', latestVersion: latest.version, source: latest.label, mirror: latest.id !== 'npm', sources };
1332
+ }
1333
+
1334
+ function PluginUpdateAction({ subject }) {
1335
+ const pkg = subject?.pkg;
1336
+ if (subject?.kind !== 'bundle' || !pkg?.installed) return null;
1337
+ const legacy = PLUGIN_PACKAGE === PLUGIN_ID && pkg.name === PLUGIN_ID && pkg.rows?.some(row => row.rowId === 'ui-skin-codex-style' && row.moduleName === PLUGIN_ID);
1338
+ if (pkg.name !== UPDATE_PACKAGE && !legacy) return null;
1339
+ return h(PluginUpdateControl, { key: pkg.name + '@' + pkg.version, currentVersion: pkg.version });
1340
+ }
1341
+ function PluginUpdateControl({ currentVersion }) {
1342
+ const anchor = useRef(null), request = useRef(null);
1343
+ const [open, setOpen] = useState(false);
1344
+ const [result, setResult] = useState({ state: 'idle' });
1345
+ const busy = result.state === 'checking';
1346
+ useEffect(() => () => { request.current?.abort(); }, []);
1347
+ const close = useCallback(() => { setOpen(false); }, []);
1348
+ const check = async () => {
1349
+ if (request.current) return;
1350
+ const controller = new AbortController();
1351
+ request.current = controller;
1352
+ setOpen(true);
1353
+ setResult({ state: 'checking' });
1354
+ try {
1355
+ const next = await checkPluginUpdate(currentVersion, { signal: controller.signal });
1356
+ if (!controller.signal.aborted) setResult(next);
1357
+ } catch (error) {
1358
+ if (!controller.signal.aborted) setResult({ state: 'error', message: '检查失败,请稍后重试。' });
1359
+ } finally {
1360
+ if (request.current === controller) request.current = null;
1361
+ }
1362
+ };
1363
+ const messages = { checking: '正在检查更新…', current: '已是最新版本', ahead: '当前版本较新', update: '发现新版本', unpublished: '暂未查到发布版本', error: '检查失败' };
1364
+ return h('span', { className: 'dsc-update-control', 'data-update-state': result.state },
1365
+ h(Button, { variant: 'outline', size: 'sm', ref: anchor, disabled: busy, icon: h(IconRefreshOutlineRegular, { size: 13, className: busy ? 'dsc-update-spin' : undefined }), onClick: check, 'aria-haspopup': 'dialog', 'aria-expanded': open }, busy ? '检查中…' : '检查更新'),
1366
+ open && h(Popover, { anchor, onClose: close, label: '检查插件更新', role: 'dialog', width: 336, className: 'dsc-update-panel' },
1367
+ h('div', { className: 'dsc-update-heading' }, h('strong', null, '插件更新'), h('button', { type: 'button', className: 'dsc-update-close', onClick: close, 'aria-label': '关闭更新检查' }, h(Icon, { name: 'close' }))),
1368
+ h('p', { className: 'dsc-update-message', role: 'status', 'aria-live': 'polite', 'data-state': result.state }, messages[result.state]),
1369
+ h('dl', { className: 'dsc-update-versions' }, h('dt', null, '当前版本'), h('dd', null, currentVersion ? 'v' + currentVersion : '未知'), result.latestVersion && h(React.Fragment, null, h('dt', null, '最新版本'), h('dd', null, 'v' + result.latestVersion))),
1370
+ result.source && h('p', { className: 'dsc-update-note' }, '来源:' + result.source, result.mirror && h('span', null, '镜像同步可能稍有延迟。')),
1371
+ result.message && h('p', { className: 'dsc-update-note' }, result.message),
1372
+ result.state === 'update' && h('p', { className: 'dsc-update-note' }, '新版本已发布,请按安装说明更新插件。'),
1373
+ !busy && h('button', { type: 'button', className: 'dsc-update-retry', onClick: check }, '重新检查')));
1374
+ }
1375
+
1376
+ return module.exports;
1377
+ }
1378
+ });