@shgroup/dsh-serenity-hooks 1.87.1 → 1.87.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.87.1",
3
+ "version": "1.87.2",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
8
8
  let react = require("react");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region \0sp-css:src/client/SafeModePanel
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 /* 🔴 2026-10-18(owner:「可以到 1000px 宽,长度更长些,允许存在滚动条」):\n 680 → **1000px**。这是 CCC 状态面板的**容器**,未来更多 CCC 内容都装在这里,\n ⇒ 宽度按\"要承载什么\"给,不按\"当前装了多少\"给。\n 窄视口仍走 `calc(100vw - 24px)` 兜底(不因追求宽度而在小屏上溢出)。 */\n width: min(1000px, 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 /* 🔴 2026-10-18(owner:「长度更长些,允许存在滚动条」):120 → **80**。\n 上限贴着视口走,把能给的垂直空间尽量给出去;真正超长时由**内部两栏各自滚动**消化\n (左栏 `.sp-popBody` 有 overflow-y:auto,右栏动画吃满行高)——卡片本身仍不溢出视口。 */\n max-height: calc(100vh - 80px);\n}\n/* 左栏:内容区(**主体**)—— owner 2026-10-18:「内容区作为主体,用多 panel 模式」\n 旧形态:固定 340px 的信息栏 + 吃掉剩余宽度的动画栏(动画占了大半张卡片)。\n 现在:内容区 `flex: 1` 吃掉主体,动画降为**定宽窄栏**(见 .sp-voyage)。\n ⚠️ 顺序仍由 `order` 决定(内容在左、动画在右),与 DOM 顺序解耦。 */\n/* 左栏:内容区——**定宽一列**(tab 只显示一个列表,不需要铺满)\n 🔴 2026-10-18 owner 修正后的分工:内容区占**固定列宽**,动画吃剩余空间。\n 为什么定宽而不是 flex:1:tab 里只有一个列表,铺得越宽每行留白越多、反而更难读;\n 定宽 420px 让\"标识 + 名字 + 天数\"三列都有合适密度,剩下的横向空间整体让给动画。 */\n.sp-popVoyage > .sp-content {\n order: 1;\n position: relative;\n z-index: 1;\n flex: none;\n width: 420px;\n min-width: 0;\n}\n/* 右栏:动画——**恢复为可伸缩**(owner 2026-10-18 修正:「剩下的位置还是给动画」)\n 🔵 为什么这次可以给它 flex:1 而上一版不行:上一版两 panel 并排把宽度吃光了;\n 改成 tab 后内容区只占一列 ⇒ 右侧空出来的位置本来就该是动画的。\n ⚠️ 仍然 `min-width: 0`:动画 iframe 不该把内容区挤窄(内容区是 flex:none 的一列)。 */\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 /* 卡片加高后动画也随之变长,避免\"加高只换来留白\" */\n min-height: 360px;\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(520px, calc(100vw - 24px));\n }\n .sp-popVoyage {\n flex-direction: column;\n align-items: stretch;\n }\n .sp-popVoyage > .sp-content {\n /* 单栏下不再定宽 420px:跟随卡片宽度 */\n flex: 1 1 auto;\n width: auto;\n }\n .sp-voyage {\n flex: none;\n width: auto;\n height: 180px;\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/* ── safe-mode 状态徽标(owner 2026-10-18:「显示小标志即可,是个状态信息」)──────────\n 旧形态 = 盾牌 + SAFE 字样 + Mac 滑块(看起来像控件、实际可点)。\n 现在 = **不可交互**的小徽标:一个状态点 + 两三个字母。\n ⚠️ 刻意**不做 hover 反馈、不做 cursor:pointer** —— 它不是按钮。\n 这类\"长得像控件却只是信息\"的形态,正是旧版让人误以为面板能改 safe-mode 的成因。 */\n.sp-safeBadge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex: none;\n padding: 1px 7px;\n border-radius: 99px;\n font-size: 10.5px;\n line-height: 15px;\n font-weight: 600;\n letter-spacing: 0.03em;\n border: 1px solid transparent;\n white-space: nowrap;\n}\n.sp-safeBadgeOn {\n color: #6ee7b7;\n background: rgba(16, 185, 129, 0.12);\n border-color: rgba(16, 185, 129, 0.35);\n}\n.sp-safeBadgeOff {\n color: var(--dsw-alias-label-secondary);\n background: rgba(127, 127, 127, 0.1);\n border-color: var(--dsw-alias-border-l1);\n}\n.sp-safeDot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n flex: none;\n background: currentColor;\n}\n\n/* ── 运行环境:一行元信息(owner:「比较丑,简易一些」)──────────────\n 旧形态 = 三个带边框的 row 卡片。它们是**背景事实**(路径/模型/守卫),\n 逐行加框等于把\"不值得对焦的信息\"提到与主体同等的视觉权重。\n 现在 = 一行等宽小字,溢出省略(path 很长时靠 title 给全文)。 */\n.sp-meta {\n flex: none;\n display: flex;\n align-items: baseline;\n gap: 6px;\n min-width: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n white-space: nowrap;\n overflow: hidden;\n}\n.sp-metaPath {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sp-metaSep {\n flex: none;\n opacity: 0.45;\n}\n\n/* ── 主体:tab 容器(owner 2026-10-18 修正:「活动轨迹和 skills 使用 tab 来实现,\n 剩下的位置还是给动画」)─────────────────────────────────────────────\n 这条修正**推翻了上一版\"两 panel 并排\"的布局**,理由值得写下来(R↓):\n 并排会把内容区横着吃掉一倍宽度,动画栏只能被压成 240px 定宽窄条。\n 改成 tab 后——**同一块面积轮换显示两个列表**——内容区宽度减半,\n 省下的横向空间**正好还给动画**(见 .sp-voyage 恢复 flex:1)。\n ⇒ \"tab\" 在本面板里不只是交互选择,它**同时是布局手段**。 */\n.sp-content {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n overflow: hidden;\n}\n.sp-tabs {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n overflow: hidden;\n}\n/* tab 条:贴近官方 settings 的分段控件语域(低调、无边框盒) */\n.sp-tabBar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 4px;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.sp-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border: 0;\n border-radius: 8px 8px 0 0;\n background: transparent;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n /* 选中态**不只依赖颜色**(色盲不友好):底部 2px 高亮条 + 文字色提亮 */\n border-bottom: 2px solid transparent;\n margin-bottom: -7px;\n}\n.sp-tab:hover {\n color: var(--dsw-alias-label-primary);\n}\n.sp-tabActive {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: #10b981;\n}\n.sp-panelEmpty {\n margin: 0;\n padding: 8px 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n/* tab 内容列表:**自身滚动**(不把容器顶长) */\n.sp-list {\n margin: 0;\n padding: 0;\n list-style: none;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.sp-item {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n padding: 4px 6px;\n border-radius: 6px;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 11px;\n line-height: 16px;\n}\n.sp-item:hover {\n background: var(--dsw-alias-bg-overlay);\n}\n/* 标识列:UID 或编码。定宽让多行对齐,与目录名分离 */\n.sp-itemId {\n flex: none;\n min-width: 56px;\n max-width: 120px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.sp-itemName {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sp-itemNameFull {\n flex: 1 1 auto;\n}\n.sp-itemAge {\n flex: none;\n min-width: 32px;\n text-align: right;\n color: var(--dsw-alias-label-secondary);\n}\n/* 空壳标记:目录在但没有 SESSION.md ⇒ **真异常**(激活会失败),不是阶段 */\n.sp-itemWarn {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: #fff;\n background: #c2410c;\n}\n/* 已建模标记:🟢 **信息**不是缺陷(无 skill.ttl 完全正常)⇒ 中性灰,不用告警色 */\n.sp-itemTag {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: var(--dsw-alias-label-secondary);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── 活跃轨迹清单(owner 2026-10-18 需求二)─────────────────────────\n 形态:每行 = [S### 定宽] [目录名 伸缩] [天数 定宽右对齐] [空壳标记 条件]\n · 用 mono 字体:目录名与 S### 是**标识符**,等宽才扫得快\n · 名字用 `text-overflow: ellipsis` 单行截断 + `title` 全文 —— 目录名很长时\n 宁可行内截断,也不让一行折成三四行把整个清单撑得读不下去\n · 整个列表**有上限高度 + 自身滚动**:轨迹会越攒越多,而它的容器(左栏)不该跟着无限长 */\n.sp-count {\n font-size: 11px;\n line-height: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 99px;\n}\n\n.sp-trajList {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n /* 清单自身封顶:活跃轨迹会越攒越多,超出后本列表滚动,不把左栏顶长 */\n max-height: 240px;\n overflow-y: auto;\n}\n\n.sp-trajItem {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n padding: 5px 8px;\n border-radius: 8px;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 11px;\n line-height: 16px;\n}\n.sp-trajItem:hover {\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-trajId {\n flex: none;\n min-width: 40px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-trajName {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sp-trajAge {\n flex: none;\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n min-width: 32px;\n}\n\n/* 空壳标记:目录在但没有 SESSION.md —— 这是**真异常**(激活会失败),不是阶段。\n ⚠️ 因此用告警色,而不是任何\"状态\"色。 */\n.sp-trajWarn {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: #fff;\n background: #c2410c;\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 /* 🔴 曾写死 `width: 76px` ⇒ 「CheapWorker 模型」这类长标签会**溢出到数值上**\n (nowrap + 定宽 ⇒ 文字直接压过去,不换行也不报错)。\n 改 `min-width`:短标签仍在同一列对齐,长标签自己撑开,绝不压数值。 */\n flex: none;\n min-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/* 🔴 2026-10-14:`.sp-cheapWorkers*` / `.sp-cheapWorker*` 样式区块已随 background 模式删除\n —— 那是「运行中 cheap-worker」列表(进度文件驱动)的样式,而进度文件不再有生产者。 */\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 /* 🔴 2026-10-18(owner:「可以到 1000px 宽,长度更长些,允许存在滚动条」):\n 680 → **1000px**。这是 CCC 状态面板的**容器**,未来更多 CCC 内容都装在这里,\n ⇒ 宽度按\"要承载什么\"给,不按\"当前装了多少\"给。\n 窄视口仍走 `calc(100vw - 24px)` 兜底(不因追求宽度而在小屏上溢出)。 */\n width: min(1000px, 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 /* 🔴 2026-10-18(owner:「长度更长些,允许存在滚动条」):120 → **80**。\n 上限贴着视口走,把能给的垂直空间尽量给出去;真正超长时由**内部两栏各自滚动**消化\n (左栏 `.sp-popBody` 有 overflow-y:auto,右栏动画吃满行高)——卡片本身仍不溢出视口。 */\n max-height: calc(100vh - 80px);\n}\n/* 左栏:内容区(**主体**)—— owner 2026-10-18:「内容区作为主体,用多 panel 模式」\n 旧形态:固定 340px 的信息栏 + 吃掉剩余宽度的动画栏(动画占了大半张卡片)。\n 现在:内容区 `flex: 1` 吃掉主体,动画降为**定宽窄栏**(见 .sp-voyage)。\n ⚠️ 顺序仍由 `order` 决定(内容在左、动画在右),与 DOM 顺序解耦。 */\n/* 左栏:内容区——**定宽一列**(tab 只显示一个列表,不需要铺满)\n 🔴 2026-10-18 owner 修正后的分工:内容区占**固定列宽**,动画吃剩余空间。\n 为什么定宽而不是 flex:1:tab 里只有一个列表,铺得越宽每行留白越多、反而更难读;\n 定宽 420px 让\"标识 + 名字 + 天数\"三列都有合适密度,剩下的横向空间整体让给动画。 */\n.sp-popVoyage > .sp-content {\n order: 1;\n position: relative;\n z-index: 1;\n flex: none;\n width: 420px;\n min-width: 0;\n}\n/* 右栏:动画——**恢复为可伸缩**(owner 2026-10-18 修正:「剩下的位置还是给动画」)\n 🔵 为什么这次可以给它 flex:1 而上一版不行:上一版两 panel 并排把宽度吃光了;\n 改成 tab 后内容区只占一列 ⇒ 右侧空出来的位置本来就该是动画的。\n ⚠️ 仍然 `min-width: 0`:动画 iframe 不该把内容区挤窄(内容区是 flex:none 的一列)。 */\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 /* 卡片加高后动画也随之变长,避免\"加高只换来留白\" */\n min-height: 360px;\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(520px, calc(100vw - 24px));\n }\n .sp-popVoyage {\n flex-direction: column;\n align-items: stretch;\n }\n .sp-popVoyage > .sp-content {\n /* 单栏下不再定宽 420px:跟随卡片宽度 */\n flex: 1 1 auto;\n width: auto;\n }\n .sp-voyage {\n flex: none;\n width: auto;\n height: 180px;\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/* ── safe-mode 状态徽标(owner 2026-10-18:「显示小标志即可,是个状态信息」)──────────\n 旧形态 = 盾牌 + SAFE 字样 + Mac 滑块(看起来像控件、实际可点)。\n 现在 = **不可交互**的小徽标:一个状态点 + 两三个字母。\n ⚠️ 刻意**不做 hover 反馈、不做 cursor:pointer** —— 它不是按钮。\n 这类\"长得像控件却只是信息\"的形态,正是旧版让人误以为面板能改 safe-mode 的成因。 */\n.sp-safeBadge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex: none;\n padding: 1px 7px;\n border-radius: 99px;\n font-size: 10.5px;\n line-height: 15px;\n font-weight: 600;\n letter-spacing: 0.03em;\n border: 1px solid transparent;\n white-space: nowrap;\n}\n.sp-safeBadgeOn {\n color: #6ee7b7;\n background: rgba(16, 185, 129, 0.12);\n border-color: rgba(16, 185, 129, 0.35);\n}\n.sp-safeBadgeOff {\n color: var(--dsw-alias-label-secondary);\n background: rgba(127, 127, 127, 0.1);\n border-color: var(--dsw-alias-border-l1);\n}\n.sp-safeDot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n flex: none;\n background: currentColor;\n}\n\n/* ── 运行环境:一行元信息(owner:「比较丑,简易一些」)──────────────\n 旧形态 = 三个带边框的 row 卡片。它们是**背景事实**(路径/模型/守卫),\n 逐行加框等于把\"不值得对焦的信息\"提到与主体同等的视觉权重。\n 现在 = 一行等宽小字,溢出省略(path 很长时靠 title 给全文)。 */\n.sp-meta {\n flex: none;\n display: flex;\n align-items: baseline;\n gap: 6px;\n min-width: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n white-space: nowrap;\n overflow: hidden;\n}\n.sp-metaPath {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sp-metaSep {\n flex: none;\n opacity: 0.45;\n}\n\n/* ── safe-mode 快捷开关(owner 2026-10-22:「放到 CCC 面板里吧,方便些」)──\n 形态沿用胶囊里那枚 Mac 风格滑块(24×13),只缩到 32×16 以贴合 11px 小字行高。\n 🔴 `align-self: center` + `flex: none`:`.sp-meta` 是 `align-items: baseline`,\n 滑块按基线对齐会**沉到文字下沿**(视觉上像掉出这一行)。 */\n.sp-quickSwitch {\n flex: none;\n align-self: center;\n position: relative;\n width: 32px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 999px;\n cursor: pointer;\n transition: background-color 120ms ease;\n}\n/* OFF 态底色取官方 `border-l3`(中性灰,浅/深主题都成立)。\n ⚠️ 不用 `--dsw-alias-fill-*` 家族:那份词汇表里**没有** fill-* 系列\n (`client-css-tokens.test.ts` 的官方白名单是唯一真相源,自造 token 会在浅色主题下变黑块)。 */\n.sp-quickSwitchOff {\n background: var(--dsw-alias-border-l3);\n}\n.sp-quickSwitchOn {\n background: #10b981; /* emerald-500,与胶囊 SAFE ON 的绿一致 */\n}\n.sp-quickSwitchBusy {\n opacity: 0.55;\n cursor: default;\n}\n.sp-quickSwitchThumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n transition: transform 120ms ease;\n}\n.sp-quickSwitchOn .sp-quickSwitchThumb {\n transform: translateX(16px);\n}\n/* SAFE ON/OFF 字样:紧挨滑块,是**状态文字**不是控件(滑块才是控件) */\n.sp-metaSafeWord {\n flex: none;\n font-variant-numeric: tabular-nums;\n}\n\n/* ── 主体:tab 容器(owner 2026-10-18 修正:「活动轨迹和 skills 使用 tab 来实现,\n 剩下的位置还是给动画」)─────────────────────────────────────────────\n 这条修正**推翻了上一版\"两 panel 并排\"的布局**,理由值得写下来(R↓):\n 并排会把内容区横着吃掉一倍宽度,动画栏只能被压成 240px 定宽窄条。\n 改成 tab 后——**同一块面积轮换显示两个列表**——内容区宽度减半,\n 省下的横向空间**正好还给动画**(见 .sp-voyage 恢复 flex:1)。\n ⇒ \"tab\" 在本面板里不只是交互选择,它**同时是布局手段**。 */\n.sp-content {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n overflow: hidden;\n}\n.sp-tabs {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n overflow: hidden;\n}\n/* tab 条:贴近官方 settings 的分段控件语域(低调、无边框盒) */\n.sp-tabBar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 4px;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n}\n.sp-tab {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border: 0;\n border-radius: 8px 8px 0 0;\n background: transparent;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n /* 选中态**不只依赖颜色**(色盲不友好):底部 2px 高亮条 + 文字色提亮 */\n border-bottom: 2px solid transparent;\n margin-bottom: -7px;\n}\n.sp-tab:hover {\n color: var(--dsw-alias-label-primary);\n}\n.sp-tabActive {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: #10b981;\n}\n.sp-panelEmpty {\n margin: 0;\n padding: 8px 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n}\n/* tab 内容列表:**自身滚动**(不把容器顶长) */\n.sp-list {\n margin: 0;\n padding: 0;\n list-style: none;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.sp-item {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n padding: 4px 6px;\n border-radius: 6px;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 11px;\n line-height: 16px;\n}\n.sp-item:hover {\n background: var(--dsw-alias-bg-overlay);\n}\n/* 标识列:UID 或编码。定宽让多行对齐,与目录名分离 */\n.sp-itemId {\n flex: none;\n min-width: 56px;\n max-width: 120px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.sp-itemName {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sp-itemNameFull {\n flex: 1 1 auto;\n}\n.sp-itemAge {\n flex: none;\n min-width: 32px;\n text-align: right;\n color: var(--dsw-alias-label-secondary);\n}\n/* 空壳标记:目录在但没有 SESSION.md ⇒ **真异常**(激活会失败),不是阶段 */\n.sp-itemWarn {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: #fff;\n background: #c2410c;\n}\n/* 已建模标记:🟢 **信息**不是缺陷(无 skill.ttl 完全正常)⇒ 中性灰,不用告警色 */\n.sp-itemTag {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: var(--dsw-alias-label-secondary);\n border: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── 活跃轨迹清单(owner 2026-10-18 需求二)─────────────────────────\n 形态:每行 = [S### 定宽] [目录名 伸缩] [天数 定宽右对齐] [空壳标记 条件]\n · 用 mono 字体:目录名与 S### 是**标识符**,等宽才扫得快\n · 名字用 `text-overflow: ellipsis` 单行截断 + `title` 全文 —— 目录名很长时\n 宁可行内截断,也不让一行折成三四行把整个清单撑得读不下去\n · 整个列表**有上限高度 + 自身滚动**:轨迹会越攒越多,而它的容器(左栏)不该跟着无限长 */\n.sp-count {\n font-size: 11px;\n line-height: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n padding: 0 6px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 99px;\n}\n\n.sp-trajList {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 2px;\n /* 清单自身封顶:活跃轨迹会越攒越多,超出后本列表滚动,不把左栏顶长 */\n max-height: 240px;\n overflow-y: auto;\n}\n\n.sp-trajItem {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n padding: 5px 8px;\n border-radius: 8px;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 11px;\n line-height: 16px;\n}\n.sp-trajItem:hover {\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sp-trajId {\n flex: none;\n min-width: 40px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-trajName {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sp-trajAge {\n flex: none;\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n min-width: 32px;\n}\n\n/* 空壳标记:目录在但没有 SESSION.md —— 这是**真异常**(激活会失败),不是阶段。\n ⚠️ 因此用告警色,而不是任何\"状态\"色。 */\n.sp-trajWarn {\n flex: none;\n padding: 0 5px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 14px;\n color: #fff;\n background: #c2410c;\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 /* 🔴 曾写死 `width: 76px` ⇒ 「CheapWorker 模型」这类长标签会**溢出到数值上**\n (nowrap + 定宽 ⇒ 文字直接压过去,不换行也不报错)。\n 改 `min-width`:短标签仍在同一列对齐,长标签自己撑开,绝不压数值。 */\n flex: none;\n min-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/* 🔴 2026-10-14:`.sp-cheapWorkers*` / `.sp-cheapWorker*` 样式区块已随 background 模式删除\n —— 那是「运行中 cheap-worker」列表(进度文件驱动)的样式,而进度文件不再有生产者。 */\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");
@@ -35,6 +35,8 @@ window.__ModuleLoader__.load({
35
35
  /** tab 切换(2026-10-18 owner 修正:活跃轨迹 / 技能 用 tab,而不是并排两个 panel)
36
36
  * ⚠️ 纯前端显示状态 —— 两份数据后端一次性都给全,切换**不触发任何请求**。 */
37
37
  const [tab, setTab] = (0, react.useState)("trajectories");
38
+ /** 切换在途(防重复点击;同时给按钮一个禁用态) */
39
+ const [busy, setBusy] = (0, react.useState)(false);
38
40
  const rootRef = (0, react.useRef)(null);
39
41
  const popRef = (0, react.useRef)(null);
40
42
  const [popPos, setPopPos] = (0, react.useState)(null);
@@ -53,6 +55,41 @@ window.__ModuleLoader__.load({
53
55
  (0, react.useEffect)(() => {
54
56
  refresh();
55
57
  }, [refresh]);
58
+ /**
59
+ * 切换安全模式(owner 2026-10-22:「放到 CCC 面板里吧,方便些」⇒ 快捷开关放回面板)。
60
+ *
61
+ * 🔴 写通道 = 同源 `POST /serenity/status`,服务端要求 **WebUI 专属头 `x-serenity-ui`**
62
+ * (`requireWebUi`:agent 工具 / curl 不带该头 ⇒ 403,safe-mode 仍是**用户能力**)。
63
+ * 🔴 成功后**以服务端回包为准**(`{...getStatus(), ...setSafeMode()}` 里带 `safeModeOn`),
64
+ * 不做乐观刷新 —— 标记文件写没写成,只有回包知道(D144:断言入参 ≠ 断言效果)。
65
+ */
66
+ const toggleSafeMode = (0, react.useCallback)(async (on) => {
67
+ if (busy) return;
68
+ setBusy(true);
69
+ try {
70
+ const r = await fetch(API, {
71
+ method: "POST",
72
+ headers: {
73
+ "content-type": "application/json",
74
+ "x-serenity-ui": "1"
75
+ },
76
+ body: JSON.stringify({
77
+ sessionId,
78
+ on
79
+ })
80
+ });
81
+ if (r.ok) setStatus(await r.json());
82
+ else await refresh();
83
+ } catch {
84
+ await refresh();
85
+ } finally {
86
+ setBusy(false);
87
+ }
88
+ }, [
89
+ busy,
90
+ sessionId,
91
+ refresh
92
+ ]);
56
93
  (0, react.useEffect)(() => {
57
94
  if (!open) return;
58
95
  const onDocMouseDown = (e) => {
@@ -159,6 +196,25 @@ window.__ModuleLoader__.load({
159
196
  className: cx("sp-meta"),
160
197
  title: `CCC: ${status.root ?? ""}`,
161
198
  children: [
199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
200
+ type: "button",
201
+ role: "switch",
202
+ "aria-checked": status.safeModeOn,
203
+ "aria-label": "safe-mode",
204
+ disabled: busy,
205
+ className: cx("sp-quickSwitch", status.safeModeOn ? "sp-quickSwitchOn" : "sp-quickSwitchOff", busy ? "sp-quickSwitchBusy" : void 0),
206
+ title: status.safeModeOn ? "safe-mode ON —— 写工具已隐藏(点击关闭)" : "safe-mode OFF —— 写工具全开(点击开启)",
207
+ onClick: () => void toggleSafeMode(!status.safeModeOn),
208
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-quickSwitchThumb") })
209
+ }),
210
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
211
+ className: cx("sp-metaSafeWord"),
212
+ children: ["SAFE ", status.safeModeOn ? "ON" : "OFF"]
213
+ }),
214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
215
+ className: cx("sp-metaSep"),
216
+ children: "·"
217
+ }),
162
218
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
163
219
  className: cx("sp-metaPath"),
164
220
  children: status.root
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.87.1",
3
+ "version": "1.87.2",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {