@shgroup/dsh-serenity-hooks 1.51.1 → 1.51.3

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/README.md CHANGED
@@ -80,7 +80,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
80
80
  | `container_admin` | 机务舱:管理子角色、管理小工具注册表、查看全部配置 | 定义"子角色"、注册新小工具、改配置时 |
81
81
  | `im-bridge` | 给 IM 联系人发消息(目前是微信):发文本、发文件、查已配置联系人、查通道状态。每次成功发送自动进工作区的消息记录 | 想让 AI 主动给家人/同事发消息(见 §6.6)。**只在工作区配了微信桥时才出现**,且只能发本工作区的消息 |
82
82
  | `acc-diag` | ACC 运行态诊断:一次调用出全报告——当前有多少会话活着、各自属于哪个工作区、唤醒时钟的武装态与 tick 次数、唤醒登记表的每一条(含状态 / 投递结果 / 补跑窗口) | **ACC 维护者专用**。**默认对所有工作区隐藏**,只有在工作区配置里点名(`exclusiveTools`)才出现 |
83
- | `diagram` | 画图:AI 给一段**标签式源码**(`<diagram>` 根 + `<rect>` / `<arrow>` / `<text>` / `<color>` / `<legend>`),插件**在进程内**把它画成 PNG 并**贴进对话**(可回看、可被后续轮次引用)——**不启浏览器、不截图、正常路径不落文件**。坐标 = **画布像素、y 向下**(`domain="x0,y0,x1,y1"` 可选);写错的图元**逐条报错并跳过**,其余照画。自带四项体检(块重叠 / 标签溢出 / 悬空端点 / 线压字),**只告警不拦图** | 想把关系图/示意图直接放进对话时。**实验性 demo,默认关**:关着的时候这个工具**根本不注册**(AI 看不到它,不占上下文);开关在设置面板「工具」组,**拨动即时生效**(不必重启)。中英文与常用符号(`①` `⇒` `→` `★`)都能画,是因为**三个字型随插件内置**(汉字 + 拉丁 + 符号,合计约 5 MB) |
83
+ | `diagram` | 画图:AI 给一段**标签式源码**(`<diagram>` 根 + `<rect>` / `<arrow>` / `<text>` / `<color>` / `<legend>`),插件**在进程内**把它画成 PNG 并**贴进对话**(**工具卡里内联一份,本轮回答的尾部再显示一份**——工具卡可折叠,尾部那份折不掉;两处用**同一份 durable 附件**,**不写文件、不占内存**。可回看、可被后续轮次引用)——**不启浏览器、不截图、正常路径不落文件**。坐标 = **画布像素、y 向下**(`domain="x0,y0,x1,y1"` 可选);写错的图元**逐条报错并跳过**,其余照画。自带四项体检(块重叠 / 标签溢出 / 悬空端点 / 线压字),**只告警不拦图** | 想把关系图/示意图直接放进对话时。**实验性 demo,默认关**:关着的时候这个工具**根本不注册**(AI 看不到它,不占上下文);开关在设置面板「工具」组,**拨动即时生效**(不必重启)。中英文与常用符号(`①` `⇒` `→` `★`)都能画,是因为**三个字型随插件内置**(汉字 + 拉丁 + 符号,合计约 5 MB) |
84
84
 
85
85
  > **改过名**(旧名已彻底停用,没有兼容别名):下面每组的箭头链是**逐个发布版本**的名字,**末项才是今名**——`cc_fs` → `container_fs` · `cc_git` → `container_git` · `session`+`session_rebuild` → `logbook`(v1.30/1.31)→ `trajectory`(v1.32)→ **`container_trajectory`**(v1.34,今名) · `acc_kit` → `dashboard` · `acc_msm` → `msm`(执行)+ `container_admin`(管理)· `eap`/`neat`/`cce` → `praxis` · `skiff_admin` → `container_admin role` · `autopilot-trajectory` → `trajectory`(v1.32)→ **`container_trajectory`**(v1.34,今名)。老会话里看到旧名,**一律取所在那一组的末项**。
86
86
 
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.51.1",
3
+ "version": "1.51.3",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——12 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag/diagram;后三个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -8,11 +8,11 @@ window.__ModuleLoader__.load({
8
8
  let react = require("react");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
11
- const css$3 = "/* SafeModePanel — 会话头部 Serenity 状态胶囊(v1.24.10 胶囊改版,对齐 OcgoDockEntry pill)\n * 胶囊 999px 全圆 → 点击 → 自绘 popover(340px 右上角卡片,外点/Escape 关闭)\n * popover 内:分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应)+ --sp-safe-green 统一绿家族;\n * 前缀 sp- 防冲突。 */\n\n/* ── 统一绿色变量家族(一处改全改;emerald 500~600 区间保证深色对比度) ── */\n\n.sp-root {\n --sp-safe-green: #0ba875; /* emerald-550:SAFE ON 主色 */\n --sp-safe-green-deep: #059669; /* emerald-600:渐变深端 */\n}\n\n/* ── 头部状态胶囊(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 9px; /* 无固定 height:由内容撑高(胶囊) */\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 绿点 = Serenity 在线(恒亮绿,不随 SAFE 变);离线/未激活 → 灰 */\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-cardInactive {\n opacity: 0.85;\n}\n\n.sp-cardInactive .sp-brand {\n color: var(--dsw-alias-label-secondary);\n font-weight: 500;\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* 版本号:monospace + translateY(1px) 下沉对齐(与文字基线齐平) */\n.sp-ver {\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-family: var(--ds-font-family-code, monospace);\n transform: translateY(1px);\n}\n\n/* 1px 深灰分隔线(两侧紧凑) */\n.sp-sep {\n flex: none;\n width: 1px;\n height: 12px;\n margin: 0 2px;\n background: var(--dsw-alias-label-tertiary);\n}\n\n.sp-shield {\n display: inline-flex;\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.sp-shield svg {\n width: 14px;\n height: 14px;\n}\n\n.sp-statusWord {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n white-space: nowrap;\n}\n\n/* SAFE OFF(未保护):灰——低姿态提醒,非错误(用户自己的开关选择) */\n.sp-cardUnprotected .sp-shield {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-cardUnprotected .sp-statusWord {\n color: var(--dsw-alias-label-secondary);\n}\n\n/* SAFE ON(保护中):emerald 绿——安心(盾牌实色 + SAFE 文字渐变质感) */\n.sp-cardProtected .sp-shield {\n color: var(--sp-safe-green);\n}\n\n.sp-cardProtected .sp-statusWord {\n color: var(--sp-safe-green);\n background: linear-gradient(135deg, var(--sp-safe-green) 0%, var(--sp-safe-green-deep) 100%);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n}\n\n/* ── Mac 风格滑块(v1.24.10:轨道 24×13 / thumb 11px 灰白 #f2f4f6 无图标 / cubic-bezier 平滑) ── */\n\n.sp-switch {\n display: inline-block;\n width: 24px;\n height: 13px;\n border-radius: 999px;\n position: relative;\n flex: none;\n cursor: pointer;\n transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff {\n background: var(--dsw-alias-border-l2);\n}\n\n.sp-switchOn {\n background: var(--sp-safe-green);\n}\n\n.sp-switchThumb {\n position: absolute;\n top: 1px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #f2f4f6; /* Mac 灰白 thumb(明暗通用) */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff .sp-switchThumb {\n left: 1px;\n}\n\n.sp-switchOn .sp-switchThumb {\n left: 12px; /* 24 - 11 - 1 */\n}\n\n/* CSS 特异性坑(S013 指南):.sp-switch:hover (0,2,0) 会覆盖 .sp-switchOn (0,1,0)\n * → ON 态 hover 绿底消失。必须同特异性且后定义补回。 */\n.sp-switch:hover {\n filter: brightness(1.08);\n}\n\n.sp-switchOn:hover {\n background: var(--sp-safe-green);\n}\n\n.sp-switchBusy {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n flex: none;\n}\n\n/* ── 自绘 popover(340px 右上角小卡片,替代官方 Modal) ── */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n}\n\n/* ── 自绘 popover(卡片,替代官方 Modal)\n v1.36.1(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):340 → **680px**(双倍)。\n v1.39.1(所有者:「下拉 dialog / 弹窗内容过宽后,会被左侧元素遮挡」):\n 🔴 根因 = **祖先 `overflow:hidden` 裁切**,不是 z-index 也不是宽度不够。\n 真机取证(CDP hit-test,1280/1600/1920 三档一致):\n · 胶囊右端 x=954.2、卡片 680 宽 ⇒ 左边界 274.2\n · 而会话中列 `pI_x6G_centerCol` / `wSkVaW_root` 都 `overflow:hidden` 且左边界 = **280**\n ⇒ 左边缘 **5.8px** 被裁掉(左边框线与圆角消失)。\n 加宽前(340px)左边界 614.2 远在 280 右侧 ⇒ **这正是 v1.36.1 加宽引入的回归**。\n 修法:**改为 `position: fixed`** —— fixed 子元素**不受祖先 overflow 裁切**;\n 坐标由 SafeModePanel 用 `getBoundingClientRect()` 算出并夹取进视口(见 .tsx 的 useLayoutEffect)。\n (本仓 client 侧**无 `react-dom` 依赖** ⇒ `createPortal` 不可用;fixed 是等效且更轻的出路。\n 已在真机验证祖先链**无** transform/filter/contain/will-change ⇒ fixed 不会被降级为相对某祖先。)\n 宽度仍走 `min(680px, 100vw-24px)`(窄视口兜底),窄屏退回单栏见文末 @media。 */\n.sp-pop {\n position: fixed; /* 不受祖先 overflow:hidden 裁切;left/top 由 JS 计算 */\n top: 0; /* ← 声明默认值,JS 会覆写(未落位时组件给 -9999 屏外值,不会闪到右下角) */\n left: 0;\n width: min(680px, calc(100vw - 24px));\n z-index: 2000;\n box-sizing: border-box;\n padding: 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 14px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);\n}\n\n/* ── 航行动画栏:宁静号心智模型(ACC 层资产 /serenity/voyage,S142 §12.44 → v1.36.1 改右栏)\n v1.36.1 改版(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):\n 动画从**背景层**改为**右栏**。这一改动解开了 v1.36.0 的取舍死结——\n 旧布局里动画垫在正文底下,必须靠 0.75 的卡片底色把它压住才读得清字;\n 分段之后两者不再争同一片像素 ⇒ 动画可以亮、正文可以干净,不必再互相妥协。\n ⚠️ 动画本身仍是「卡片模式」(词元压到 22%、铭牌层关闭),本栏只负责**给位置**,不改作品。 */\n.sp-popVoyage {\n overflow: hidden; /* 裁掉 iframe 在 14px 圆角外的溢出 */\n /* 分段后不再需要 0.75 的重压暗:压到 0.9 只压整体观感,不与正文争对比度。\n 若所有者觉得动画太亮/太暗,这仍是主旋钮(次旋钮 = iframe 的 blur)。 */\n background: rgba(6, 11, 14, 0.9);\n display: flex;\n flex-direction: row;\n /* 两栏都从**同一行高**分配(row 轴 cross = 高度):左栏按内容、右栏吃满行高。\n 行高 = 左栏内容高度,且被 max-height 封顶 ⇒ 信息长时内部滚动,动画不把行撑破。 */\n align-items: stretch;\n gap: 12px;\n padding: 12px;\n max-height: calc(100vh - 120px);\n}\n/* 左栏:信息(正文)——固定 340px,与旧卡片同宽,保证原有排印不变 */\n.sp-popVoyage > .sp-popBody {\n position: relative;\n z-index: 1;\n order: 1;\n flex: none;\n width: 340px;\n min-width: 0;\n}\n/* 右栏:动画——吃掉剩余宽度,并吃满左栏高度 */\n.sp-voyage {\n order: 2;\n flex: 1 1 auto;\n min-width: 0;\n position: relative;\n pointer-events: none; /* 纯展示:不挡点击、不抢焦点 */\n overflow: hidden;\n border-radius: 10px;\n min-height: 260px; /* 动画的下限:信息很少时也不塌成一条 */\n}\n.sp-voyage iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n /* ⚠️ 用 iframe 自身的 filter 而**不是** backdrop-filter:iframe 内容不参与本文档的\n backdrop(跨文档不采样)⇒ backdrop-filter 对它无效,只有 filter 才是可靠的杠杆。\n v1.36.1:动画移入独立右栏后不再压在文字后面 ⇒ 磨砂可以收得很轻(1.2 → 0.6px),\n 既保留柔化,又让飞船细节(舷窗/引擎环)比旧版清楚。 */\n filter: blur(0.6px);\n}\n\n/* 窄屏(放不下两栏)⇒ 退回单栏:信息在上、动画在下(DOM 顺序即如此,无需改结构) */\n@media (max-width: 860px) {\n .sp-pop {\n /* 两栏放不下时,卡片回到\"不超过视口\"的旧行为 */\n width: min(420px, calc(100vw - 24px));\n }\n .sp-popVoyage {\n flex-direction: column;\n align-items: stretch;\n }\n .sp-popVoyage > .sp-popBody {\n width: auto; /* 单栏下不再固定 340px,跟随卡片宽度 */\n flex: none; /* 按内容高(仍受自身 max-height 约束) */\n }\n .sp-voyage {\n flex: none;\n height: 200px; /* 单栏时给动画明确高度,避免被 flex 压扁 */\n min-height: 0;\n }\n}\n\n.sp-popBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-height: calc(100vh - 140px);\n overflow-y: auto;\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* handyman 运行状态区块 */\n\n.sp-handymenToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-handymenToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-handymenList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-handymanItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-handymanHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-handymanLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-handymanRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-handymenMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
11
+ const css$5 = "/* SafeModePanel — 会话头部 Serenity 状态胶囊(v1.24.10 胶囊改版,对齐 OcgoDockEntry pill)\n * 胶囊 999px 全圆 → 点击 → 自绘 popover(340px 右上角卡片,外点/Escape 关闭)\n * popover 内:分组(groupTitle 标题 + rowCard 行卡)——看齐官方 settings 设计语言\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应)+ --sp-safe-green 统一绿家族;\n * 前缀 sp- 防冲突。 */\n\n/* ── 统一绿色变量家族(一处改全改;emerald 500~600 区间保证深色对比度) ── */\n\n.sp-root {\n --sp-safe-green: #0ba875; /* emerald-550:SAFE ON 主色 */\n --sp-safe-green-deep: #059669; /* emerald-600:渐变深端 */\n}\n\n/* ── 头部状态胶囊(可点击) ── */\n\n.sp-card {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 9px; /* 无固定 height:由内容撑高(胶囊) */\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease),\n border-color var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-card:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n}\n\n.sp-card:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-card:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 绿点 = Serenity 在线(恒亮绿,不随 SAFE 变);离线/未激活 → 灰 */\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-cardInactive {\n opacity: 0.85;\n}\n\n.sp-cardInactive .sp-brand {\n color: var(--dsw-alias-label-secondary);\n font-weight: 500;\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* 版本号:monospace + translateY(1px) 下沉对齐(与文字基线齐平) */\n.sp-ver {\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n font-family: var(--ds-font-family-code, monospace);\n transform: translateY(1px);\n}\n\n/* 1px 深灰分隔线(两侧紧凑) */\n.sp-sep {\n flex: none;\n width: 1px;\n height: 12px;\n margin: 0 2px;\n background: var(--dsw-alias-label-tertiary);\n}\n\n.sp-shield {\n display: inline-flex;\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.sp-shield svg {\n width: 14px;\n height: 14px;\n}\n\n.sp-statusWord {\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0;\n white-space: nowrap;\n}\n\n/* SAFE OFF(未保护):灰——低姿态提醒,非错误(用户自己的开关选择) */\n.sp-cardUnprotected .sp-shield {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-cardUnprotected .sp-statusWord {\n color: var(--dsw-alias-label-secondary);\n}\n\n/* SAFE ON(保护中):emerald 绿——安心(盾牌实色 + SAFE 文字渐变质感) */\n.sp-cardProtected .sp-shield {\n color: var(--sp-safe-green);\n}\n\n.sp-cardProtected .sp-statusWord {\n color: var(--sp-safe-green);\n background: linear-gradient(135deg, var(--sp-safe-green) 0%, var(--sp-safe-green-deep) 100%);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n}\n\n/* ── Mac 风格滑块(v1.24.10:轨道 24×13 / thumb 11px 灰白 #f2f4f6 无图标 / cubic-bezier 平滑) ── */\n\n.sp-switch {\n display: inline-block;\n width: 24px;\n height: 13px;\n border-radius: 999px;\n position: relative;\n flex: none;\n cursor: pointer;\n transition: background 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff {\n background: var(--dsw-alias-border-l2);\n}\n\n.sp-switchOn {\n background: var(--sp-safe-green);\n}\n\n.sp-switchThumb {\n position: absolute;\n top: 1px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #f2f4f6; /* Mac 灰白 thumb(明暗通用) */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\n transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.sp-switchOff .sp-switchThumb {\n left: 1px;\n}\n\n.sp-switchOn .sp-switchThumb {\n left: 12px; /* 24 - 11 - 1 */\n}\n\n/* CSS 特异性坑(S013 指南):.sp-switch:hover (0,2,0) 会覆盖 .sp-switchOn (0,1,0)\n * → ON 态 hover 绿底消失。必须同特异性且后定义补回。 */\n.sp-switch:hover {\n filter: brightness(1.08);\n}\n\n.sp-switchOn:hover {\n background: var(--sp-safe-green);\n}\n\n.sp-switchBusy {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n}\n\n.sp-chev {\n color: var(--dsw-alias-label-dimmed);\n flex: none;\n}\n\n/* ── 自绘 popover(340px 右上角小卡片,替代官方 Modal) ── */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n}\n\n/* ── 自绘 popover(卡片,替代官方 Modal)\n v1.36.1(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):340 → **680px**(双倍)。\n v1.39.1(所有者:「下拉 dialog / 弹窗内容过宽后,会被左侧元素遮挡」):\n 🔴 根因 = **祖先 `overflow:hidden` 裁切**,不是 z-index 也不是宽度不够。\n 真机取证(CDP hit-test,1280/1600/1920 三档一致):\n · 胶囊右端 x=954.2、卡片 680 宽 ⇒ 左边界 274.2\n · 而会话中列 `pI_x6G_centerCol` / `wSkVaW_root` 都 `overflow:hidden` 且左边界 = **280**\n ⇒ 左边缘 **5.8px** 被裁掉(左边框线与圆角消失)。\n 加宽前(340px)左边界 614.2 远在 280 右侧 ⇒ **这正是 v1.36.1 加宽引入的回归**。\n 修法:**改为 `position: fixed`** —— fixed 子元素**不受祖先 overflow 裁切**;\n 坐标由 SafeModePanel 用 `getBoundingClientRect()` 算出并夹取进视口(见 .tsx 的 useLayoutEffect)。\n (本仓 client 侧**无 `react-dom` 依赖** ⇒ `createPortal` 不可用;fixed 是等效且更轻的出路。\n 已在真机验证祖先链**无** transform/filter/contain/will-change ⇒ fixed 不会被降级为相对某祖先。)\n 宽度仍走 `min(680px, 100vw-24px)`(窄视口兜底),窄屏退回单栏见文末 @media。 */\n.sp-pop {\n position: fixed; /* 不受祖先 overflow:hidden 裁切;left/top 由 JS 计算 */\n top: 0; /* ← 声明默认值,JS 会覆写(未落位时组件给 -9999 屏外值,不会闪到右下角) */\n left: 0;\n width: min(680px, calc(100vw - 24px));\n z-index: 2000;\n box-sizing: border-box;\n padding: 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 14px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);\n}\n\n/* ── 航行动画栏:宁静号心智模型(ACC 层资产 /serenity/voyage,S142 §12.44 → v1.36.1 改右栏)\n v1.36.1 改版(所有者:「卡片做成双倍宽,左侧是信息,右侧是动画」):\n 动画从**背景层**改为**右栏**。这一改动解开了 v1.36.0 的取舍死结——\n 旧布局里动画垫在正文底下,必须靠 0.75 的卡片底色把它压住才读得清字;\n 分段之后两者不再争同一片像素 ⇒ 动画可以亮、正文可以干净,不必再互相妥协。\n ⚠️ 动画本身仍是「卡片模式」(词元压到 22%、铭牌层关闭),本栏只负责**给位置**,不改作品。 */\n.sp-popVoyage {\n overflow: hidden; /* 裁掉 iframe 在 14px 圆角外的溢出 */\n /* 分段后不再需要 0.75 的重压暗:压到 0.9 只压整体观感,不与正文争对比度。\n 若所有者觉得动画太亮/太暗,这仍是主旋钮(次旋钮 = iframe 的 blur)。 */\n background: rgba(6, 11, 14, 0.9);\n display: flex;\n flex-direction: row;\n /* 两栏都从**同一行高**分配(row 轴 cross = 高度):左栏按内容、右栏吃满行高。\n 行高 = 左栏内容高度,且被 max-height 封顶 ⇒ 信息长时内部滚动,动画不把行撑破。 */\n align-items: stretch;\n gap: 12px;\n padding: 12px;\n max-height: calc(100vh - 120px);\n}\n/* 左栏:信息(正文)——固定 340px,与旧卡片同宽,保证原有排印不变 */\n.sp-popVoyage > .sp-popBody {\n position: relative;\n z-index: 1;\n order: 1;\n flex: none;\n width: 340px;\n min-width: 0;\n}\n/* 右栏:动画——吃掉剩余宽度,并吃满左栏高度 */\n.sp-voyage {\n order: 2;\n flex: 1 1 auto;\n min-width: 0;\n position: relative;\n pointer-events: none; /* 纯展示:不挡点击、不抢焦点 */\n overflow: hidden;\n border-radius: 10px;\n min-height: 260px; /* 动画的下限:信息很少时也不塌成一条 */\n}\n.sp-voyage iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n /* ⚠️ 用 iframe 自身的 filter 而**不是** backdrop-filter:iframe 内容不参与本文档的\n backdrop(跨文档不采样)⇒ backdrop-filter 对它无效,只有 filter 才是可靠的杠杆。\n v1.36.1:动画移入独立右栏后不再压在文字后面 ⇒ 磨砂可以收得很轻(1.2 → 0.6px),\n 既保留柔化,又让飞船细节(舷窗/引擎环)比旧版清楚。 */\n filter: blur(0.6px);\n}\n\n/* 窄屏(放不下两栏)⇒ 退回单栏:信息在上、动画在下(DOM 顺序即如此,无需改结构) */\n@media (max-width: 860px) {\n .sp-pop {\n /* 两栏放不下时,卡片回到\"不超过视口\"的旧行为 */\n width: min(420px, calc(100vw - 24px));\n }\n .sp-popVoyage {\n flex-direction: column;\n align-items: stretch;\n }\n .sp-popVoyage > .sp-popBody {\n width: auto; /* 单栏下不再固定 340px,跟随卡片宽度 */\n flex: none; /* 按内容高(仍受自身 max-height 约束) */\n }\n .sp-voyage {\n flex: none;\n height: 200px; /* 单栏时给动画明确高度,避免被 flex 压扁 */\n min-height: 0;\n }\n}\n\n.sp-popBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-height: calc(100vh - 140px);\n overflow-y: auto;\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* 行卡(rowCard:border + radius + padding;值与 label 分离、长内容可换行) */\n.sp-row {\n display: flex;\n align-items: baseline;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-label {\n flex: none;\n width: 76px;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.sp-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n overflow-wrap: anywhere;\n}\n\n/* 安全模式动作区 */\n.sp-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggleLg {\n height: 30px;\n padding: 0 16px;\n font-size: 13px;\n}\n\n.sp-toggleOn {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleDesc {\n flex: 1;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* handyman 运行状态区块 */\n\n.sp-handymenToggle {\n flex: none;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n cursor: pointer;\n}\n\n.sp-handymenToggle:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-handymenList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-top: 2px;\n}\n\n.sp-handymanItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n min-width: 0;\n}\n\n.sp-handymanHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-handymanLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-handymanRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n.sp-handymanResp {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.sp-handymenMore {\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n padding: 4px 12px;\n}\n\n/* 底部提示(配置入口引导) */\n.sp-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 10px;\n}\n";
12
12
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SafeModePanel\"]") === null) {
13
13
  const tag = document.createElement("style");
14
14
  tag.setAttribute("data-sp-css", "SafeModePanel");
15
- tag.textContent = css$3;
15
+ tag.textContent = css$5;
16
16
  document.head.appendChild(tag);
17
17
  }
18
18
  //#endregion
@@ -3313,11 +3313,11 @@ window.__ModuleLoader__.load({
3313
3313
  }
3314
3314
  //#endregion
3315
3315
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/AccountsEditor
3316
- const css$2 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n/* ── v1.27.5 紧凑化:折叠块(details/summary)── */\n.ae-collapse {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n overflow: hidden;\n}\n\n.ae-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ae-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ae-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n transition: transform 0.15s ease;\n}\n\n.ae-collapse[open] > .ae-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ae-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ae-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ae-collapseBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.ae-desc {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n/* v1.24.6:TOTP 绑定二维码(扫码即录入;scalable SVG,白底保证扫码识别) */\n.ae-totpQr {\n flex-basis: 100%;\n display: inline-flex;\n justify-content: center;\n padding: 8px;\n background: #fff;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n}\n\n.ae-totpQr svg {\n width: 132px;\n height: 132px;\n display: block;\n}\n\n.ae-totpSecret {\n font-family: var(--ds-font-family-code);\n font-size: 11px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-button-info-fill);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n/* v1.24.7:绑定提示(去确认码后——扫码或手动录入,保存即绑定) */\n.ae-totpHint {\n flex-basis: 100%;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warn-label);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-button-info-fill);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* ── v1.26.2 建议问答页配置(PublicAskEditor:开放容器白名单 chips + key/地址展示)── */\n\n.ae-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 8px 0 4px;\n}\n\n.ae-chip {\n padding: 4px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.ae-chip:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-chipOn {\n background: var(--dsw-alias-button-primary-fill);\n border-color: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.ae-saved {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.ae-err {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-key {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n word-break: break-all;\n user-select: all;\n}\n\n.ae-copy {\n flex: none;\n margin-left: 8px;\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-copy:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n color: var(--dsw-alias-label-primary);\n}\n\n/* v1.26.5 轮换 key(警示色:旧 key 立即失效) */\n.ae-rotate {\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 35%, transparent);\n}\n\n.ae-rotate:hover {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-rotate:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n\n.ae-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 8px;\n}\n\n.ae-labelDim {\n opacity: 0.65;\n min-width: 96px;\n}\n";
3316
+ const css$4 = "/* AccountsEditor — DSH 设置面板「外部访问」区块(v1.22)\n * 与 SettingsSection ss-* 同设计语言:--dsw-alias-* 语义 token,明暗自适应。 */\n\n.ae-root {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n font-size: 13px;\n}\n\n/* ── v1.27.5 紧凑化:折叠块(details/summary)── */\n.ae-collapse {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n overflow: hidden;\n}\n\n.ae-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ae-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ae-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-dimmed);\n transition: transform 0.15s ease;\n}\n\n.ae-collapse[open] > .ae-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ae-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ae-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ae-collapseBody {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px 12px 10px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-warn {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n}\n\n.ae-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ae-title {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.ae-desc {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n.ae-note {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* ── 监听设置 / 白名单输入行 ── */\n\n.ae-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ae-label {\n flex: none;\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n}\n\n.ae-input {\n flex: 1;\n min-width: 0;\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n outline: none;\n}\n\n.ae-input:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-input::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.ae-input.ae-port {\n flex: none;\n width: 76px;\n}\n\n/* ── 账号列表 ── */\n\n.ae-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ae-head {\n display: flex;\n gap: 8px;\n font-size: 11px;\n font-weight: 500;\n color: var(--dsw-alias-label-dimmed);\n padding: 0 2px;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n}\n\n.ae-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px 2px;\n}\n\n.ae-colUser { flex: 1.2; }\n.ae-colPass { flex: 1.6; }\n.ae-colTotp { flex: 2.2; min-width: 0; }\n.ae-colOp { flex: none; width: 48px; }\n\n/* ── v1.22.4 TOTP 第二因素 ── */\n\n.ae-totpBtn {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.ae-totpBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-totpPending {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-width: 0;\n}\n\n/* v1.24.6:TOTP 绑定二维码(扫码即录入;scalable SVG,白底保证扫码识别) */\n.ae-totpQr {\n flex-basis: 100%;\n display: inline-flex;\n justify-content: center;\n padding: 8px;\n background: #fff;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n}\n\n.ae-totpQr svg {\n width: 132px;\n height: 132px;\n display: block;\n}\n\n.ae-totpSecret {\n font-family: var(--ds-font-family-code);\n font-size: 11px;\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-all;\n}\n\n.ae-totpUri {\n font-size: 11px;\n color: var(--dsw-alias-button-info-fill);\n text-decoration: none;\n}\n\n.ae-totpUri:hover {\n text-decoration: underline;\n}\n\n/* v1.24.7:绑定提示(去确认码后——扫码或手动录入,保存即绑定) */\n.ae-totpHint {\n flex-basis: 100%;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ae-totpBadge {\n font-size: 12px;\n color: var(--dsw-alias-state-warn-label);\n}\n\n.ae-totpClear {\n display: flex;\n gap: 6px;\n align-items: center;\n}\n\n.ae-check {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-top: 10px;\n cursor: pointer;\n}\n\n.ae-check input {\n accent-color: var(--dsw-alias-button-info-fill);\n}\n\n.ae-del {\n flex: none;\n padding: 4px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-del:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n}\n\n.ae-add {\n align-self: flex-start;\n padding: 5px 12px;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-add:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ae-wsSelect {\n margin-top: 8px;\n max-width: 100%;\n cursor: pointer;\n}\n\n/* ── 工作区白名单 chips ── */\n\n.ae-wsList {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.ae-wsChip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.14));\n}\n\n.ae-wsPath {\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n font-family: var(--ds-font-family-code, monospace);\n word-break: break-all;\n}\n\n.ae-wsDel {\n flex: none;\n width: 18px;\n height: 18px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--dsw-alias-label-dimmed);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n}\n\n.ae-wsDel:hover {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* ── 错误 / 操作区 ── */\n\n.ae-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n word-break: break-word;\n}\n\n.ae-actions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n.ae-save {\n padding: 7px 24px;\n border: 0;\n border-radius: 8px;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.ae-save:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.ae-save:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* ── v1.26.2 建议问答页配置(PublicAskEditor:开放容器白名单 chips + key/地址展示)── */\n\n.ae-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin: 8px 0 4px;\n}\n\n.ae-chip {\n padding: 4px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n\n.ae-chip:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.ae-chipOn {\n background: var(--dsw-alias-button-primary-fill);\n border-color: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.ae-saved {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.ae-err {\n margin-left: 10px;\n font-size: 12px;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-key {\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n word-break: break-all;\n user-select: all;\n}\n\n.ae-copy {\n flex: none;\n margin-left: 8px;\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.ae-copy:hover {\n border-color: var(--dsw-alias-button-ghost-active-border);\n color: var(--dsw-alias-label-primary);\n}\n\n/* v1.26.5 轮换 key(警示色:旧 key 立即失效) */\n.ae-rotate {\n color: var(--dsw-alias-state-error-primary);\n border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 35%, transparent);\n}\n\n.ae-rotate:hover {\n border-color: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.ae-rotate:disabled {\n opacity: 0.6;\n cursor: wait;\n}\n\n.ae-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 8px;\n}\n\n.ae-labelDim {\n opacity: 0.65;\n min-width: 96px;\n}\n";
3317
3317
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"AccountsEditor\"]") === null) {
3318
3318
  const tag = document.createElement("style");
3319
3319
  tag.setAttribute("data-sp-css", "AccountsEditor");
3320
- tag.textContent = css$2;
3320
+ tag.textContent = css$4;
3321
3321
  document.head.appendChild(tag);
3322
3322
  }
3323
3323
  //#endregion
@@ -3730,11 +3730,11 @@ window.__ModuleLoader__.load({
3730
3730
  }
3731
3731
  //#endregion
3732
3732
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/PersonaEditor
3733
- const css$1 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard 内分组)\n * token 合规(v1.27.14 修复):全部使用 design-platform.css 官方 --dsw-alias-* 词汇;\n * 弃用自造名(text-primary/accent/border/state-success 等)——未定义时深色 fallback\n * 在浅色主题下显示为黑框黑字(用户反馈白色主题通性问题)。 */\n\n.pe-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n padding: 2px 0 6px;\n}\n\n.pe-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pe-label {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.pe-input,\n.pe-textarea {\n width: 100%;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font-size: 13px;\n padding: 8px 10px;\n outline: none;\n}\n\n.pe-input:focus,\n.pe-textarea:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.pe-textarea {\n resize: vertical;\n min-height: 72px; /* v1.27.5 紧凑化:默认 4 行(原 120px 过高),可拖拽扩大 */\n line-height: 1.5;\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n}\n\n.pe-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.pe-btn {\n border-radius: 6px;\n padding: 5px 12px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n transition: opacity 0.15s;\n}\n\n.pe-btn:hover:not(:disabled) {\n opacity: 0.85;\n}\n\n.pe-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.pe-btnGhost {\n border-color: var(--dsw-alias-border-l2);\n}\n\n.pe-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n border-color: transparent;\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.pe-hint {\n font-size: 12px;\n margin: 0;\n}\n\n.pe-hintOk {\n color: var(--dsw-alias-state-success-primary);\n}\n\n.pe-hintErr {\n color: var(--dsw-alias-state-error-primary);\n}\n";
3733
+ const css$3 = "/* PersonaEditor.css — 彩蛋模式区块(v1.23.1,官方 settings 设计语言:rowCard 内分组)\n * token 合规(v1.27.14 修复):全部使用 design-platform.css 官方 --dsw-alias-* 词汇;\n * 弃用自造名(text-primary/accent/border/state-success 等)——未定义时深色 fallback\n * 在浅色主题下显示为黑框黑字(用户反馈白色主题通性问题)。 */\n\n.pe-wrap {\n display: flex;\n flex-direction: column;\n gap: 10px;\n width: 100%;\n padding: 2px 0 6px;\n}\n\n.pe-row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pe-label {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.pe-input,\n.pe-textarea {\n width: 100%;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n font-size: 13px;\n padding: 8px 10px;\n outline: none;\n}\n\n.pe-input:focus,\n.pe-textarea:focus {\n border-color: var(--dsw-alias-button-ghost-active-border);\n}\n\n.pe-textarea {\n resize: vertical;\n min-height: 72px; /* v1.27.5 紧凑化:默认 4 行(原 120px 过高),可拖拽扩大 */\n line-height: 1.5;\n font-family: var(--ds-font-family-code);\n font-size: 12px;\n}\n\n.pe-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n}\n\n.pe-btn {\n border-radius: 6px;\n padding: 5px 12px;\n font-size: 12px;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n transition: opacity 0.15s;\n}\n\n.pe-btn:hover:not(:disabled) {\n opacity: 0.85;\n}\n\n.pe-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.pe-btnGhost {\n border-color: var(--dsw-alias-border-l2);\n}\n\n.pe-btnPrimary {\n background: var(--dsw-alias-button-primary-fill);\n border-color: transparent;\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.pe-hint {\n font-size: 12px;\n margin: 0;\n}\n\n.pe-hintOk {\n color: var(--dsw-alias-state-success-primary);\n}\n\n.pe-hintErr {\n color: var(--dsw-alias-state-error-primary);\n}\n";
3734
3734
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"PersonaEditor\"]") === null) {
3735
3735
  const tag = document.createElement("style");
3736
3736
  tag.setAttribute("data-sp-css", "PersonaEditor");
3737
- tag.textContent = css$1;
3737
+ tag.textContent = css$3;
3738
3738
  document.head.appendChild(tag);
3739
3739
  }
3740
3740
  //#endregion
@@ -4663,11 +4663,11 @@ Instruction-following style:
4663
4663
  }
4664
4664
  //#endregion
4665
4665
  //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SettingsSection
4666
- const css = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-border-l3);\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 错误提示(v1.29 会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n";
4666
+ const css$2 = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-border-l3);\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 阈值行(滑块 + 值) */\n.ss-threshold {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.ss-threshold input[type='range'] {\n width: 160px;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.ss-value {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n text-align: right;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 底部帮助行 */\n.ss-hint {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 降级提示 */\n.ss-degrade {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-warn-label);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 错误提示(v1.29 会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── v1.27.5 紧凑化:只读定义列表(dl/dt/dd 2 列网格)── */\n.ss-defList {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px 20px;\n margin: 0;\n width: 100%;\n min-width: 0;\n}\n\n.ss-defItem {\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.ss-defTerm {\n font-size: 11px;\n line-height: 16px;\n font-weight: 500;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.ss-defValue {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ── v1.27.5 紧凑化:开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n";
4667
4667
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
4668
4668
  const tag = document.createElement("style");
4669
4669
  tag.setAttribute("data-sp-css", "SettingsSection");
4670
- tag.textContent = css;
4670
+ tag.textContent = css$2;
4671
4671
  document.head.appendChild(tag);
4672
4672
  }
4673
4673
  //#endregion
@@ -5168,12 +5168,446 @@ Instruction-following style:
5168
5168
  });
5169
5169
  }
5170
5170
  //#endregion
5171
+ //#region src/client/diagram-result.ts
5172
+ /**
5173
+ * 附件路径接受的栅格格式 —— **宿主 `ImageMediaType` 的逐字副本**
5174
+ * (`dsh-attachment/types.d.ts`)。校验用;同时挡住 SVG 之类非栅格
5175
+ * (本工具只产出 PNG,见 `src/tools/diagram.ts`)。
5176
+ */
5177
+ const DIAGRAM_IMAGE_MEDIA_TYPES = [
5178
+ "image/png",
5179
+ "image/jpeg",
5180
+ "image/webp",
5181
+ "image/gif"
5182
+ ];
5183
+ /**
5184
+ * 本插件认领的 wire Tool 名(= 客户端两处的唯一真相源,v1.51.3 起):
5185
+ * 工具卡行(`tool.call.toolview` 的 key)与轮尾行(从 Chat 快照里挑图)都读它,**不再各写一份字面量**。
5186
+ */
5187
+ const DIAGRAM_TOOL_NAME = "diagram";
5188
+ /** 非空对象视图(`null` = 不是对象)。 */
5189
+ function recordOf(value) {
5190
+ return typeof value === "object" && value !== null ? value : null;
5191
+ }
5192
+ /** 非空字符串(去空白后仍非空),否则 `null`。 */
5193
+ function nonEmptyString(value) {
5194
+ return typeof value === "string" && value.trim() !== "" ? value : null;
5195
+ }
5196
+ /** 正有限数(尺寸/字节数必须为正),否则 `null`。 */
5197
+ function positiveNumber(value) {
5198
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
5199
+ }
5200
+ /**
5201
+ * 该 attachment 是否能安全当作栅格图渲染。
5202
+ *
5203
+ * 宿主已声明其类型,但**声明不是运行期保证**(快照/日志可被截断或改写)⇒ 仍逐字段校验;
5204
+ * 校验只看结构化视图,**通过则原样返回宿主那个对象**(不重建、不 cast,保住品牌 id)。
5205
+ * @param attachment - 候选引用。
5206
+ * @returns 同一个引用,或 `null`(形状不合法 ⇒ 当作"这次没图")。
5207
+ */
5208
+ function renderableImage(attachment) {
5209
+ const rec = recordOf(attachment);
5210
+ if (rec === null) return null;
5211
+ if (nonEmptyString(rec.attachmentId) === null) return null;
5212
+ if (!DIAGRAM_IMAGE_MEDIA_TYPES.some((known) => known === rec.mediaType)) return null;
5213
+ if (positiveNumber(rec.bytes) === null) return null;
5214
+ if (positiveNumber(rec.width) === null || positiveNumber(rec.height) === null) return null;
5215
+ return attachment;
5216
+ }
5217
+ /**
5218
+ * 从一份已结算的工具结果派生展示材料。
5219
+ *
5220
+ * 取数规则(顺序固定):
5221
+ * - `text`:收集**全部** `type === 'text'` 块的文字,按出现顺序以 `\n` 相连;
5222
+ * - `image`:取**第一个**通过 {@link renderableImage} 的 `type === 'image'` 块
5223
+ * (宿主按结果顺序只放一张);
5224
+ * - 其余块(`reasoning` / `file` / 未知扩展块)**忽略**,不做任何 flatten。
5225
+ *
5226
+ * @param block - 已结算的工具结果节点(`phase === 'result'` 的 `block`)。
5227
+ * @returns 展示材料;**任何输入都返回一个视图**(最坏情况是空文字 + 无图),不抛。
5228
+ */
5229
+ function diagramResultModel(block) {
5230
+ const raw = block.content;
5231
+ const content = Array.isArray(raw) ? raw : [];
5232
+ const texts = [];
5233
+ let image = null;
5234
+ for (const entry of content) {
5235
+ if (recordOf(entry) === null) continue;
5236
+ if (entry.type === "text") {
5237
+ texts.push(entry.text);
5238
+ continue;
5239
+ }
5240
+ if (entry.type === "image" && image === null) image = renderableImage(entry.attachment);
5241
+ }
5242
+ let text = texts.join("\n");
5243
+ if (text === "" && block.isError === true) {
5244
+ const error = recordOf(block.error);
5245
+ if (error !== null) {
5246
+ const name = nonEmptyString(error.name);
5247
+ const code = nonEmptyString(error.code);
5248
+ if (name !== null && code !== null) text = `${name}: ${code}`;
5249
+ }
5250
+ }
5251
+ const newline = text.indexOf("\n");
5252
+ return {
5253
+ title: newline === -1 ? text : text.slice(0, newline),
5254
+ text,
5255
+ image,
5256
+ isError: block.isError === true
5257
+ };
5258
+ }
5259
+ //#endregion
5260
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/DiagramToolView
5261
+ const css$1 = "/*\n * DiagramToolView.css — `diagram` 工具行(Chat 视图)\n *\n * 🔴 只用 design-platform 的**官方** token(`--dsw-alias-*`)。\n * 自造 token(如 --sp-color / --text-primary)在浅色主题下 var() 永不解析 ⇒ 永远走深色 fallback\n * ⇒ 黑块 / 黑字(2026-09-04 S142 用户反馈的成因)。`tests/client-css-tokens.test.ts` 机械守卫此约束。\n * 本文件用到的 5 个 token 均在官方词汇表内:bg-layer-1 / border-l1 /\n * label-primary / label-secondary / state-error-primary。\n */\n\n.sp-dgRow {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.sp-dgTitle {\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n word-break: break-word;\n}\n\n.sp-dgHint {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 图自身有像素尺寸;这里只兜住容器宽度(窄屏不溢出)与圆角/描边 */\n.sp-dgImage {\n display: block;\n max-width: 100%;\n height: auto;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n/* 信封文字:结果原文里的换行是有意排版的(一行一个图元/lint 项)⇒ 保留换行 */\n.sp-dgText {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.sp-dgError {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary);\n}\n";
5262
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"DiagramToolView\"]") === null) {
5263
+ const tag = document.createElement("style");
5264
+ tag.setAttribute("data-sp-css", "DiagramToolView");
5265
+ tag.textContent = css$1;
5266
+ document.head.appendChild(tag);
5267
+ }
5268
+ //#endregion
5269
+ //#region src/client/DiagramToolView.tsx
5270
+ /**
5271
+ * 用会话授权的 loader 把 durable 引用换成浏览器 URL。
5272
+ *
5273
+ * 顺序:先问同步缓存(`loadImage.peek`,命中则**不发起读取**),未命中才 `await loadImage(ref)`。
5274
+ * 组件卸载后不再 setState(`live` 旗),加载失败落到 `failed`(**不抛上 React 树**)。
5275
+ * @param loadImage - owner 交来的会话授权加载器。
5276
+ * @param image - 已校验的引用;`null` = 本次没图(降级)。
5277
+ * @returns 当前 URL 与失败标志。
5278
+ */
5279
+ function useDiagramImage(loadImage, image) {
5280
+ const [url, setUrl] = (0, react.useState)(null);
5281
+ const [failed, setFailed] = (0, react.useState)(false);
5282
+ (0, react.useEffect)(() => {
5283
+ if (image === null) {
5284
+ setUrl(null);
5285
+ setFailed(false);
5286
+ return;
5287
+ }
5288
+ setFailed(false);
5289
+ const cached = loadImage.peek?.(image);
5290
+ if (cached !== void 0) {
5291
+ setUrl(cached);
5292
+ return;
5293
+ }
5294
+ let live = true;
5295
+ loadImage(image).then((resolved) => {
5296
+ if (live) setUrl(resolved);
5297
+ }, () => {
5298
+ if (live) {
5299
+ setUrl(null);
5300
+ setFailed(true);
5301
+ }
5302
+ });
5303
+ return () => {
5304
+ live = false;
5305
+ };
5306
+ }, [image, loadImage]);
5307
+ return {
5308
+ url,
5309
+ failed
5310
+ };
5311
+ }
5312
+ /**
5313
+ * 未结算阶段的行:只有工具名与一句状态,**不猜参数**(`preparing` 阶段没有已派发的实参)。
5314
+ * @param phase - `preparing` 或 `start`。
5315
+ */
5316
+ function DiagramRunningRow({ phase }) {
5317
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5318
+ className: "sp-dgRow",
5319
+ "data-state": "running",
5320
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5321
+ className: "sp-dgTitle",
5322
+ children: DIAGRAM_TOOL_NAME
5323
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5324
+ className: "sp-dgHint",
5325
+ children: phase === "preparing" ? "准备中…" : "绘制中…"
5326
+ })]
5327
+ });
5328
+ }
5329
+ /**
5330
+ * 已结算阶段的行:图(有则贴)+ 信封文字(**只来自结果自己的 text 块**)。
5331
+ *
5332
+ * 🔴 绝不把 `block.content` 整体 flatten:图块被 `JSON.stringify` 后会在图片下面
5333
+ * 印出原始 attachment 对象 —— 这正是本行要消除的症状(宿主 `imageCardModel` 文档同款理由)。
5334
+ * @param props - 已结算结果与加载器。
5335
+ */
5336
+ function DiagramResultRow({ block, loadImage }) {
5337
+ const model = (0, react.useMemo)(() => diagramResultModel(block), [block]);
5338
+ const { url, failed } = useDiagramImage(loadImage, model.image);
5339
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5340
+ className: "sp-dgRow",
5341
+ "data-state": model.isError ? "error" : "ok",
5342
+ children: [
5343
+ model.title === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5344
+ className: "sp-dgTitle",
5345
+ children: model.title
5346
+ }),
5347
+ model.image === null ? null : failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5348
+ className: "sp-dgError",
5349
+ children: "图片加载失败(attachment 读取被拒或已过期)"
5350
+ }) : url === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5351
+ className: "sp-dgHint",
5352
+ children: "图片加载中…"
5353
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5354
+ className: "sp-dgImage",
5355
+ src: url,
5356
+ alt: model.image.name ?? "diagram",
5357
+ width: model.image.width,
5358
+ height: model.image.height
5359
+ }),
5360
+ model.text === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5361
+ className: "sp-dgText",
5362
+ children: model.text
5363
+ })
5364
+ ]
5365
+ });
5366
+ }
5367
+ /**
5368
+ * `diagram` 的原子工具视图(注册进 keyed 槽 `tool.call.toolview`,key = `diagram`)。
5369
+ *
5370
+ * 三阶段分流:`result` → {@link DiagramResultRow}(带图);其余 → {@link DiagramRunningRow}。
5371
+ * 本组件自身不调 Hook ⇒ 条件返回安全。
5372
+ * @param props - 框架合成的槽 props。
5373
+ */
5374
+ function DiagramToolView(props) {
5375
+ if (props.phase !== "result") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagramRunningRow, { phase: props.phase });
5376
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagramResultRow, {
5377
+ block: props.block,
5378
+ loadImage: props.loadImage
5379
+ });
5380
+ }
5381
+ //#endregion
5382
+ //#region src/client/diagram-tail.ts
5383
+ /**
5384
+ * 递归上限:宿主自己的 `MAX_TOOL_CALL_TREE_DEPTH` 是 256,这里只需要"别被畸形/超深树拖住",
5385
+ * 取一个小得多的值 —— 超过就**停止下探**(已经拿到的照常返回,不抛)。
5386
+ */
5387
+ const MAX_DEPTH = 32;
5388
+ /** 单轮里最多看多少个 tool-call 根(防一次超长轮把渲染拖住)。 */
5389
+ const MAX_ROOTS = 512;
5390
+ /**
5391
+ * 该块是不是**已结算**的工具结果。
5392
+ *
5393
+ * `'kind' in block` 先把联合类型收窄到**唯一带 `kind` 的成员**(运行中的两个只有 `phase`),
5394
+ * 再比字面量值 —— 全程无 cast(理由见文件头)。
5395
+ * @param block - 候选块。
5396
+ * @returns 是否为已结算结果(`true` 时类型收窄为 {@link ToolResultNode})。
5397
+ */
5398
+ function isSettled(block) {
5399
+ return "kind" in block && block.kind === "tool-result";
5400
+ }
5401
+ /**
5402
+ * 深度优先收集一个工具调用子树里的 diagram 图(**发现顺序** = 该轮的调用顺序)。
5403
+ * @param block - 工具调用块(只有**已结算且工具名匹配**的才可能带图;运行中有子调用也照常下探)。
5404
+ * @param out - 收集器。
5405
+ * @param depth - 当前深度(超过 {@link MAX_DEPTH} 即停)。
5406
+ */
5407
+ function collect(block, out, depth) {
5408
+ if (depth > MAX_DEPTH || block === null || typeof block !== "object") return;
5409
+ if (isSettled(block) && block.call?.name === "diagram") {
5410
+ const image = diagramResultModel(block).image;
5411
+ if (image !== null) out.push({
5412
+ callId: block.callId,
5413
+ seq: block.seq,
5414
+ image
5415
+ });
5416
+ }
5417
+ const children = Array.isArray(block.subCalls) ? block.subCalls : [];
5418
+ for (const child of children) collect(child, out, depth + 1);
5419
+ }
5420
+ /**
5421
+ * 挑出**某一轮**里 `diagram` 产出的图。
5422
+ *
5423
+ * 输入 = 该 Turn 的 tool-call **根**列表(调用方用宿主的
5424
+ * `chat.nodes.turnDataSource(turn, 'tool-call')` 取,**含被隐藏的节点** ⇒ 折叠与否都不影响结果)。
5425
+ * @param roots - 本轮的工具调用根(顺序 = 锚点顺序)。
5426
+ * @returns 按发现顺序排列的图(无图 ⇒ 空数组;任何畸形输入都不抛)。
5427
+ */
5428
+ function diagramImagesOfRoots(roots) {
5429
+ if (!Array.isArray(roots)) return [];
5430
+ const out = [];
5431
+ let seen = 0;
5432
+ for (const root of roots) {
5433
+ if (seen >= MAX_ROOTS) break;
5434
+ seen += 1;
5435
+ collect(root, out, 0);
5436
+ }
5437
+ return out;
5438
+ }
5439
+ //#endregion
5440
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/DiagramImageTail
5441
+ const css = "/**\n * DiagramImageTail.css — 轮尾图行的样式(v1.51.3)\n *\n * 与 `DiagramToolView.css` 同规矩:**只用官方 `--dsw-alias-*` 词汇表里的令牌**,\n * 圆角/尺寸用字面量(`--dsw-radius-*` **不在**官方 alias 词汇表里,`client-css-tokens.test.ts`\n * 会把它当自造 token 拦下 —— 浅色主题下会变成黑块)。\n */\n.sp-dtRoot {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 12px;\n}\n\n.sp-dtImage {\n max-width: 100%;\n height: auto;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n/* 字节还在读(或读数被拒)时的占位 —— 与图的左边缘对齐,避免行高跳动 */\n.sp-dtPending {\n width: 160px;\n height: 100px;\n border-radius: 6px;\n background: var(--dsw-alias-interactive-bg-hover);\n}\n";
5442
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"DiagramImageTail\"]") === null) {
5443
+ const tag = document.createElement("style");
5444
+ tag.setAttribute("data-sp-css", "DiagramImageTail");
5445
+ tag.textContent = css;
5446
+ document.head.appendChild(tag);
5447
+ }
5448
+ //#endregion
5449
+ //#region src/client/DiagramImageTail.tsx
5450
+ /**
5451
+ * client/DiagramImageTail.tsx — **本轮回答尾部**的 `diagram` 图(v1.51.3)
5452
+ *
5453
+ * ## 它修的是什么
5454
+ *
5455
+ * v1.51.2 让图出现在**工具卡**里 —— 但工具卡是**可折叠**的宿主外壳,折起来图就没了。
5456
+ * owner 的诉求:**图要出现在「回答」区域,且仍然不落盘**。
5457
+ *
5458
+ * ## 为什么是"槽"而不是"markdown 里的地址"(两条路都量过,2026-09-30)
5459
+ *
5460
+ * - **markdown 内联图**:宿主契约逐字写着 *"Images additionally require absolute HTTP(S)"*,
5461
+ * 且渲染器的 DOM 被 fixtures **逐字钉死**(`md-render.d.ts` 首行)—— 那是宿主包,我们**零改 DSH**;
5462
+ * 那条"把自定义目的地解成可显示 URL"的缝(`pathImages`)在宿主组件里用 `useMemo` 写死成
5463
+ * 本地文件路径解析、**不查任何服务** ⇒ 插件塞不进去。⇒ **"包装地址"这条路不成**。
5464
+ * - **`conversation.chat.turnTail`**:宿主声明的 **list 槽**,专用于"在一轮完成后的尾部追加功能行"
5465
+ * (官方 `ui-deliverables` 就是这么挂交付物的)⇒ 图的渲染**归我们**,于是既不需要地址、
5466
+ * 也不需要新的 HTTP 路由。
5467
+ *
5468
+ * ## 数据与 URL 从哪来(**都不新增存储**)
5469
+ *
5470
+ * - **图**:宿主自己的 Chat 快照 —— `ctx.uiConversation.binding(sessionId).target('chat')`,
5471
+ * 再用 `chat.nodes.turnDataSource(turn, 'tool-call')` 取**本轮**的工具调用根
5472
+ * (契约原话:*"Observe one Turn's data for a single Node kind, **including hidden Nodes**, in anchor order"*
5473
+ * ⇒ 工具卡折叠与否都不影响取数)→ 交给纯模块 `diagram-tail.ts` 挑图。
5474
+ * - **URL**:宿主自己的 per-session 授权 URL 缓存 `ctx.uiConversation.imageUrl(sessionId, ref)`
5475
+ * (先问 `peekImageUrl`,命中则**不发起读取**)。字节仍在**宿主的 durable 附件库**里
5476
+ * (`src/tools/diagram.ts` 的 `attachments.saveImage` 产出的那个引用)
5477
+ * ⇒ **不落工作区文件、不常驻内存**。🔴 因此**不做**任何"自己存一份图"的设计
5478
+ * (owner 2026-09-30 明确否掉过 loop/内存常驻:那会长期占内存)。
5479
+ *
5480
+ * ## 两个"被契约纠正"的实现细节(实测 0.1.7-rc.1)
5481
+ *
5482
+ * 1. 🔴 **本槽需要 `inject` 面**(与线上 0.2.0-rc.1 的 `TurnTailOwnerProps` 契约不同)——
5483
+ * tsc 直接报 *"Property 'inject' is missing … but required"*。故按**官方同款**走 `inject`,
5484
+ * 组件用 `InjectFace<DiagramTailInjected>` 声明自己的面(`ui-deliverables` 亦如此)。
5485
+ * 2. 🔴 **`sessionId` 是品牌类型 `SessionId`,不是 `string`**(`string` 赋不进去)。
5486
+ * 这里不引入 `@deepseek-ai/dsh-session/types` 新依赖,直接从本行 props 上取类型。
5487
+ */
5488
+ /** 没有可用目标快照时的稳定空订阅(`useSyncExternalStore` 要求引用稳定)。 */
5489
+ const NO_SUBSCRIBE = () => () => {};
5490
+ /** 没有可用目标快照时的稳定空读数。 */
5491
+ const NO_SNAPSHOT = () => void 0;
5492
+ /**
5493
+ * 取该会话的 Chat 目标快照源。
5494
+ *
5495
+ * 宿主契约写明 `binding()` 在**会话未知或绑定已换代**时**抛错** ⇒ 这里按"本轮不显示"处理
5496
+ * (不是错误态:换会话/竞态都会走到这里)。
5497
+ * @param scope - 客户端 ctx(含 `uiConversation`)。
5498
+ * @param sessionId - 槽的 session 作用域。
5499
+ * @returns 目标快照源,或 `null`(拿不到 ⇒ 不渲染)。
5500
+ */
5501
+ function chatTargetOf(scope, sessionId) {
5502
+ try {
5503
+ return scope.uiConversation.binding(sessionId).target("chat");
5504
+ } catch {
5505
+ return null;
5506
+ }
5507
+ }
5508
+ /**
5509
+ * 造一行的注入面(在 `index.ts` 注册时由 `scope` 闭包)。
5510
+ * @param scope - 客户端 ctx。
5511
+ * @returns 该槽的注入面。
5512
+ */
5513
+ function diagramTailInjected(scope) {
5514
+ return {
5515
+ chatTarget: (sessionId) => chatTargetOf(scope, sessionId),
5516
+ peekImageUrl: (sessionId, ref) => {
5517
+ try {
5518
+ return scope.uiConversation.peekImageUrl(sessionId, ref) ?? null;
5519
+ } catch {
5520
+ return null;
5521
+ }
5522
+ },
5523
+ loadImageUrl: (sessionId, ref) => {
5524
+ try {
5525
+ return scope.uiConversation.imageUrl(sessionId, ref).catch(() => null);
5526
+ } catch {
5527
+ return Promise.resolve(null);
5528
+ }
5529
+ }
5530
+ };
5531
+ }
5532
+ /**
5533
+ * 把每张图的 durable 引用解成**浏览器可直接用的 URL**(先查缓存,未命中才发起读取)。
5534
+ * @param injected - 本行的注入面。
5535
+ * @param sessionId - 授权作用域。
5536
+ * @param images - 本轮挑出的图(顺序即渲染顺序)。
5537
+ * @returns 与 `images` 等长的 URL 数组;未就绪或读取失败的位置为 `null`。
5538
+ */
5539
+ function useTailUrls(injected, sessionId, images) {
5540
+ const [urls, setUrls] = (0, react.useState)([]);
5541
+ (0, react.useEffect)(() => {
5542
+ let live = true;
5543
+ const initial = images.map((entry) => injected.peekImageUrl(sessionId, entry.image));
5544
+ setUrls(initial);
5545
+ images.forEach((entry, index) => {
5546
+ if (initial[index] !== null) return;
5547
+ injected.loadImageUrl(sessionId, entry.image).then((url) => {
5548
+ if (!live || url === null) return;
5549
+ setUrls((prev) => prev.map((value, at) => at === index ? url : value));
5550
+ });
5551
+ });
5552
+ return () => {
5553
+ live = false;
5554
+ };
5555
+ }, [
5556
+ injected,
5557
+ sessionId,
5558
+ images
5559
+ ]);
5560
+ return urls;
5561
+ }
5562
+ /**
5563
+ * `diagram` 的**轮尾图行**:注册进 `conversation.chat.turnTail`(`kind: 'list'`,入口 id `serenity-diagram-tail`)。
5564
+ *
5565
+ * 本轮没有图 ⇒ 返回 `null`("entries without content return null",不占位)。
5566
+ * @param props - 框架合成的槽 props。
5567
+ */
5568
+ function DiagramImageTail(props) {
5569
+ const { sessionId, chatTarget, peekImageUrl, loadImageUrl } = props;
5570
+ const injected = (0, react.useMemo)(() => ({
5571
+ chatTarget,
5572
+ peekImageUrl,
5573
+ loadImageUrl
5574
+ }), [
5575
+ chatTarget,
5576
+ peekImageUrl,
5577
+ loadImageUrl
5578
+ ]);
5579
+ const turn = props.turn?.turn;
5580
+ const source = (0, react.useMemo)(() => injected.chatTarget(sessionId), [injected, sessionId]);
5581
+ const chat = (0, react.useSyncExternalStore)((0, react.useMemo)(() => source === null ? NO_SUBSCRIBE : (notify) => source.subscribe(notify), [source]), (0, react.useMemo)(() => source === null ? NO_SNAPSHOT : () => source.getSnapshot(), [source]));
5582
+ const images = (0, react.useMemo)(() => {
5583
+ if (chat === void 0 || turn === void 0) return [];
5584
+ return diagramImagesOfRoots(chat.nodes.turnDataSource(turn, "tool-call").getSnapshot().map((data) => data.root));
5585
+ }, [chat, turn]);
5586
+ const urls = useTailUrls(injected, sessionId, images);
5587
+ if (images.length === 0) return null;
5588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5589
+ className: "sp-dtRoot",
5590
+ "data-count": images.length,
5591
+ children: images.map((entry, index) => {
5592
+ const url = urls[index] ?? null;
5593
+ return url === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "sp-dtPending" }, entry.callId) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5594
+ className: "sp-dtImage",
5595
+ src: url,
5596
+ alt: entry.image.name ?? "diagram",
5597
+ width: entry.image.width,
5598
+ height: entry.image.height
5599
+ }, entry.callId);
5600
+ })
5601
+ });
5602
+ }
5603
+ //#endregion
5171
5604
  //#region src/client/index.ts
5172
5605
  const inject = [
5173
5606
  "slots",
5174
5607
  "conversation",
5175
5608
  "sessions",
5176
- "configForms"
5609
+ "configForms",
5610
+ "uiConversation"
5177
5611
  ];
5178
5612
  /** 设置页命名空间 = profile 条目 id(与 host 侧 `SERENITY_SETTINGS_NS` 同值;单一真相源在 host 侧常量,此处只作字面量引用) */
5179
5613
  const SERENITY_SETTINGS_NS = "serenity-hooks";
@@ -5182,7 +5616,8 @@ Instruction-following style:
5182
5616
  "slots",
5183
5617
  "conversation",
5184
5618
  "sessions",
5185
- "configForms"
5619
+ "configForms",
5620
+ "uiConversation"
5186
5621
  ], (scope) => {
5187
5622
  scope.effect(() => scope.slots.register({
5188
5623
  name: "conversation.session.header.actions",
@@ -5218,6 +5653,15 @@ Instruction-following style:
5218
5653
  }
5219
5654
  })
5220
5655
  }, SettingsSection))), "serenity: simple settings section");
5656
+ scope.effect(() => scope.slots.inject("tool.call.toolview", () => scope.slots.register({
5657
+ name: "tool.call.toolview",
5658
+ key: "diagram"
5659
+ }, DiagramToolView)), "serenity: diagram tool view");
5660
+ scope.effect(() => scope.slots.inject("conversation.chat.turnTail", () => scope.slots.register({
5661
+ name: "conversation.chat.turnTail",
5662
+ id: "serenity-diagram-tail",
5663
+ inject: () => diagramTailInjected(scope)
5664
+ }, DiagramImageTail)), "serenity: diagram image tail");
5221
5665
  });
5222
5666
  }
5223
5667
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.51.1",
3
+ "version": "1.51.3",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:12 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag/diagram)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -111,14 +111,18 @@
111
111
  "@deepseek-ai/dsh-agent-loop": "0.1.7-rc.1",
112
112
  "@deepseek-ai/dsh-agent-preset-registry": "0.1.7-rc.1",
113
113
  "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.1",
114
+ "@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
115
+ "@deepseek-ai/dsh-brand": "0.1.7-rc.1",
114
116
  "@deepseek-ai/dsh-client-connection": "0.1.7-rc.1",
115
117
  "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
118
+ "@deepseek-ai/dsh-client-ui-chat": "0.1.7-rc.1",
116
119
  "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
117
120
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
118
121
  "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
119
122
  "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.1",
120
123
  "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
121
124
  "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
125
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.7-rc.1",
122
126
  "@deepseek-ai/dsh-compaction": "0.1.7-rc.1",
123
127
  "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
124
128
  "@deepseek-ai/dsh-llm": "0.1.7-rc.1",