@shgroup/dsh-serenity-hooks 1.109.0 → 1.111.0
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 +1 -1
- package/lib/client.js +276 -175
- package/lib/index.js +25 -2
- package/lib/surface/http.d.ts +16 -0
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.111.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/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/* ── 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 🔴 2026-10-28:工具本体也整条下线 ⇒ 这些类名**永远不会再出现**。 */\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 🔴 2026-10-28:工具本体也整条下线 ⇒ 这些类名**永远不会再出现**。 */\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\n/* ── 订阅 tab(v1.110.0:从 DSH 设置面板搬到 CCC 面板)─────────────────\n 🔴 为什么这一段样式在**这个**文件里:面板是 420px 定宽的一栏,\n 而设置面板是宽页 ⇒ 原来用 `style={{...}}` 内联的写法搬过来会挤爆窄栏。\n 这里全部走类名,并让**内容区自身滚动**(`.sp-subsBody`),\n 与 `.sp-list` 同一套做法:清单越多不该把卡片顶长。 */\n.sp-subs {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n overflow: hidden;\n}\n\n.sp-subsBar {\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-subsBtn {\n flex: none;\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n font-size: 11px;\n line-height: 16px;\n cursor: pointer;\n}\n.sp-subsBtn:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n/* 说明文字:**单行截断**(窄栏里换行会把按钮行撑高两倍),全文走 title */\n.sp-subsHint {\n flex: 1;\n min-width: 0;\n font-size: 10px;\n line-height: 14px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* 内容区:**自身滚动**(订阅/投递/机制三段加起来通常比一屏高) */\n.sp-subsBody {\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\n.sp-subsSectionTitle {\n flex: none;\n margin-top: 6px;\n font-size: 11px;\n line-height: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n}\n.sp-subsSectionTitle:first-child {\n margin-top: 0;\n}\n\n/* 机制计数:等宽(数字要能对齐扫读),可换行(它是一句完整的话) */\n.sp-subsMeter {\n margin: 0;\n padding: 4px 6px;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 10px;\n line-height: 15px;\n color: var(--dsw-alias-label-secondary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 告警:配置坏项 / 拉取失败 / 投递失败。**用告警色**(它是真异常,不是状态) */\n.sp-subsError {\n margin: 0;\n padding: 4px 6px;\n border-radius: 6px;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* ── v1.111.0:把「谁盯谁」从一句话改成卡片(owner:「太丑陋了 这怎么让人看得懂呢」)──\n 旧形态 = 一整行 `2026-10-23--butler--家庭容器治理管家 盯 2026-10-20--logistics--…、…`,\n 130+ 字符塞进 420px 栏 ⇒ 截断位置恰好落在**后半批成员**上,\n 于是\"它到底管哪几条\"这件事在界面上**根本读不到**(不是不好看,是信息丢失)。\n 新形态 = 卡头一行(观察者 → N 个)+ 成员各占一行 + note 灰字,都是短名。 */\n.sp-subsCard {\n flex: none;\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n\n.sp-subsCardHead {\n display: flex;\n align-items: baseline;\n gap: 6px;\n min-width: 0;\n}\n\n/* 观察者名:**加粗且不截断主体**(它是这条卡片的标题) */\n.sp-subsWho {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n line-height: 17px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sp-subsArrow {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* 「7 个」计数:用小徽章形状,让\"它管几条\"一眼可数(不必逐行数) */\n.sp-subsCount {\n flex: none;\n padding: 0 6px;\n border-radius: 99px;\n border: 1px solid var(--dsw-alias-border-l1);\n font-family: var(--ds-font-family-code, monospace);\n font-size: 10px;\n line-height: 15px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 成员清单:每个成员一行,前面一个淡淡的圆点(读起来是\"名单\",不是段落) */\n.sp-subsMembers {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.sp-subsMember {\n position: relative;\n padding-left: 12px;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sp-subsMember::before {\n content: '';\n position: absolute;\n left: 3px;\n top: 50%;\n width: 3px;\n height: 3px;\n margin-top: -1.5px;\n border-radius: 50%;\n background: var(--dsw-alias-label-tertiary);\n}\n\n/* note:多行截断(3 行封顶)。它是**说明**不是数据 ⇒ 允许被截,全文走 title。\n 不给无限高度是因为 note 可以很长(管家那条 100+ 字),会把卡片撑成一整屏。 */\n.sp-subsNote {\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n font-size: 10px;\n line-height: 15px;\n color: var(--dsw-alias-label-tertiary);\n border-top: 1px dashed var(--dsw-alias-border-l1);\n padding-top: 4px;\n margin-top: 1px;\n}\n\n/* 投递一行:左=谁(成员短名) 右=成功/失败徽章。\n 🔴 键是 `watcher←member`,旧形态把两端原始目录名拼在一起显示 ⇒ 更读不懂;\n 这里只显示**成员**(\"谁跑完了\"),watcher 已在上方卡片里,重复写只会挤爆。 */\n.sp-subsDelivery {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n padding: 2px 4px;\n font-size: 11px;\n line-height: 16px;\n}\n\n.sp-subsDlvWho {\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-subsDlvStat {\n flex: none;\n font-family: var(--ds-font-family-code, monospace);\n font-size: 10px;\n padding: 0 5px;\n border-radius: 4px;\n}\n.sp-subsDlvOk {\n color: #10b981;\n background: color-mix(in srgb, #10b981 14%, transparent);\n}\n.sp-subsDlvBad {\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\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");
|
|
@@ -24,6 +24,53 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
/** 尚未落位时的屏外坐标(`useLayoutEffect` 在绘制前就会覆写,肉眼看不到) */
|
|
25
25
|
const POP_OFFSCREEN = -9999;
|
|
26
26
|
const API = "/serenity/status";
|
|
27
|
+
/**
|
|
28
|
+
* 轨迹名 → 展示名(v1.111.0)。
|
|
29
|
+
*
|
|
30
|
+
* 🔴 **为什么需要它**(owner 原话:「功能正常,但太丑陋了 这怎么让人看得懂呢」):
|
|
31
|
+
* 原始目录名 `2026-10-23--butler--家庭容器治理管家` 在 420px 窄栏里被截断成
|
|
32
|
+
* `2026-10-23--butler--家庭容器…` —— 用户看到的是一串**日期**,而"谁"被挤没了。
|
|
33
|
+
* 日期前缀是**排序/归档信息,不是身份** ⇒ 展示时让位。
|
|
34
|
+
*
|
|
35
|
+
* 规则与 `surface/http.ts` 的 `trajectoryLabel` **同源**(剥 `YYYY-MM-DD--`,`--` → ` · `)。
|
|
36
|
+
* ⚠️ 这里是**纯字符串**副本,刻意不在 client bundle 里引入 node 侧的 `trajectory/core.ts`;
|
|
37
|
+
* 若展示规则哪天变复杂(要查盘/要本地化)⇒ **必须改为服务端下发**,别让两份规则漂。
|
|
38
|
+
*/
|
|
39
|
+
function labelOf(name) {
|
|
40
|
+
const m = /^\d{4}-\d{2}-\d{2}--(.*)$/.exec(name);
|
|
41
|
+
if (!m) return name;
|
|
42
|
+
return m[1].replace(/--/g, " · ");
|
|
43
|
+
}
|
|
44
|
+
/** 投递键 `watcher←member` → 两端展示名 */
|
|
45
|
+
function splitDeliveryKey(key) {
|
|
46
|
+
const at = key.indexOf("←");
|
|
47
|
+
if (at === -1) return {
|
|
48
|
+
watcher: labelOf(key),
|
|
49
|
+
member: ""
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
watcher: labelOf(key.slice(0, at)),
|
|
53
|
+
member: labelOf(key.slice(at + 1))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const SUBSCRIPTIONS_API = "/serenity/subscriptions";
|
|
57
|
+
/**
|
|
58
|
+
* 拉取订阅读数(只读、无副作用)。
|
|
59
|
+
*
|
|
60
|
+
* @param workspace 本面板正在显示的那个 CCC(= `status.root`)。
|
|
61
|
+
* 🔴 **必须传**:服务端在"没有归属信息且多 CCC 共存"时**故意不猜**
|
|
62
|
+
* (`surface/http.ts` 的 `resolveWorkspaceCore`)⇒ 不传就会拿到 `root:""` + 空表。
|
|
63
|
+
* ⚠️ 空表**不报错**,所以漏传这件事在界面上看起来像"没配订阅"——这正是要避免的形态。
|
|
64
|
+
*/
|
|
65
|
+
async function fetchSubscriptions(workspace) {
|
|
66
|
+
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
67
|
+
const res = await fetch(`${SUBSCRIPTIONS_API}${qs}`, { headers: { accept: "application/json" } });
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const body = await res.json().catch(() => null);
|
|
70
|
+
throw new Error(body?.error ?? `HTTP ${res.status}`);
|
|
71
|
+
}
|
|
72
|
+
return await res.json();
|
|
73
|
+
}
|
|
27
74
|
/** 拼接类名(等价 clsx 最小子集,避免额外依赖) */
|
|
28
75
|
function cx(...parts) {
|
|
29
76
|
return parts.filter(Boolean).join(" ");
|
|
@@ -33,7 +80,9 @@ window.__ModuleLoader__.load({
|
|
|
33
80
|
const [status, setStatus] = (0, react.useState)(null);
|
|
34
81
|
const [open, setOpen] = (0, react.useState)(false);
|
|
35
82
|
/** tab 切换(2026-10-18 owner 修正:活跃轨迹 / 技能 用 tab,而不是并排两个 panel)
|
|
36
|
-
* ⚠️
|
|
83
|
+
* ⚠️ 前两个 tab 是纯前端显示状态 —— 那两份数据后端一次性都给全,切换**不触发请求**。
|
|
84
|
+
* 🔴 2026-11-01 新增第三个 tab「订阅」:它的数据**不在** `/serenity/status` 里
|
|
85
|
+
* (投递计数只存在于插件进程内,见 `SubscriptionsWire` 上方说明)⇒ 首次选中时才拉一次。 */
|
|
37
86
|
const [tab, setTab] = (0, react.useState)("trajectories");
|
|
38
87
|
/** 切换在途(防重复点击;同时给按钮一个禁用态) */
|
|
39
88
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
@@ -43,6 +92,10 @@ window.__ModuleLoader__.load({
|
|
|
43
92
|
const sessionId = props.sessionId;
|
|
44
93
|
const trajectories = status?.trajectories ?? [];
|
|
45
94
|
const skills = status?.skills ?? [];
|
|
95
|
+
const [subs, setSubs] = (0, react.useState)(null);
|
|
96
|
+
const [subsBusy, setSubsBusy] = (0, react.useState)(false);
|
|
97
|
+
const [subsError, setSubsError] = (0, react.useState)(null);
|
|
98
|
+
const subsRoot = status?.root ?? null;
|
|
46
99
|
const refresh = (0, react.useCallback)(async () => {
|
|
47
100
|
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
48
101
|
try {
|
|
@@ -56,6 +109,33 @@ window.__ModuleLoader__.load({
|
|
|
56
109
|
refresh();
|
|
57
110
|
}, [refresh]);
|
|
58
111
|
/**
|
|
112
|
+
* 拉订阅读数。**只在切到「订阅」tab 时拉**(不是每次开卡片都拉):
|
|
113
|
+
* 它读的是进程内计数,多拉一次只多一次噪声;而"进面板就要有数"的诉求由 tab 切换满足。
|
|
114
|
+
* 🔴 依赖 `subsRoot`:`status` 首次加载完才有 root,root 变了必须重拉
|
|
115
|
+
* (否则从 CCC 切到另一个 CCC 时会显示上一个 CCC 的订阅表)。
|
|
116
|
+
*/
|
|
117
|
+
const loadSubs = (0, react.useCallback)(async () => {
|
|
118
|
+
if (subsRoot === null) return;
|
|
119
|
+
setSubsBusy(true);
|
|
120
|
+
setSubsError(null);
|
|
121
|
+
try {
|
|
122
|
+
setSubs(await fetchSubscriptions(subsRoot));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
setSubsError(err instanceof Error ? err.message : String(err));
|
|
125
|
+
} finally {
|
|
126
|
+
setSubsBusy(false);
|
|
127
|
+
}
|
|
128
|
+
}, [subsRoot]);
|
|
129
|
+
(0, react.useEffect)(() => {
|
|
130
|
+
if (open && tab === "subscriptions" && subs === null && !subsBusy) loadSubs();
|
|
131
|
+
}, [
|
|
132
|
+
open,
|
|
133
|
+
tab,
|
|
134
|
+
subs,
|
|
135
|
+
subsBusy,
|
|
136
|
+
loadSubs
|
|
137
|
+
]);
|
|
138
|
+
/**
|
|
59
139
|
* 切换安全模式(owner 2026-10-22:「放到 CCC 面板里吧,方便些」⇒ 快捷开关放回面板)。
|
|
60
140
|
*
|
|
61
141
|
* 🔴 写通道 = 同源 `POST /serenity/status`,服务端要求 **WebUI 专属头 `x-serenity-ui`**
|
|
@@ -235,27 +315,41 @@ window.__ModuleLoader__.load({
|
|
|
235
315
|
className: cx("sp-tabBar"),
|
|
236
316
|
role: "tablist",
|
|
237
317
|
"aria-label": "CCC 状态面板",
|
|
238
|
-
children: [
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
children:
|
|
257
|
-
|
|
258
|
-
|
|
318
|
+
children: [
|
|
319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
320
|
+
type: "button",
|
|
321
|
+
role: "tab",
|
|
322
|
+
"aria-selected": tab === "trajectories",
|
|
323
|
+
className: cx("sp-tab", tab === "trajectories" && "sp-tabActive"),
|
|
324
|
+
onClick: () => setTab("trajectories"),
|
|
325
|
+
children: ["活跃轨迹", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
326
|
+
className: cx("sp-count"),
|
|
327
|
+
children: trajectories.length
|
|
328
|
+
})]
|
|
329
|
+
}),
|
|
330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
331
|
+
type: "button",
|
|
332
|
+
role: "tab",
|
|
333
|
+
"aria-selected": tab === "skills",
|
|
334
|
+
className: cx("sp-tab", tab === "skills" && "sp-tabActive"),
|
|
335
|
+
onClick: () => setTab("skills"),
|
|
336
|
+
children: ["技能", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
337
|
+
className: cx("sp-count"),
|
|
338
|
+
children: skills.length
|
|
339
|
+
})]
|
|
340
|
+
}),
|
|
341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
342
|
+
type: "button",
|
|
343
|
+
role: "tab",
|
|
344
|
+
"aria-selected": tab === "subscriptions",
|
|
345
|
+
className: cx("sp-tab", tab === "subscriptions" && "sp-tabActive"),
|
|
346
|
+
onClick: () => setTab("subscriptions"),
|
|
347
|
+
children: ["订阅", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
348
|
+
className: cx("sp-count"),
|
|
349
|
+
children: subs?.subscriptions.length ?? 0
|
|
350
|
+
})]
|
|
351
|
+
})
|
|
352
|
+
]
|
|
259
353
|
}), tab === "trajectories" ? trajectories.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
260
354
|
className: cx("sp-panelEmpty"),
|
|
261
355
|
children: "暂无活跃轨迹"
|
|
@@ -286,7 +380,7 @@ window.__ModuleLoader__.load({
|
|
|
286
380
|
})
|
|
287
381
|
]
|
|
288
382
|
}, t.dirName))
|
|
289
|
-
}) : skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
383
|
+
}) : tab === "skills" ? skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
290
384
|
className: cx("sp-panelEmpty"),
|
|
291
385
|
children: "暂无技能"
|
|
292
386
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
@@ -304,6 +398,11 @@ window.__ModuleLoader__.load({
|
|
|
304
398
|
children: "建模"
|
|
305
399
|
})]
|
|
306
400
|
}, s.name))
|
|
401
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsView, {
|
|
402
|
+
data: subs,
|
|
403
|
+
busy: subsBusy,
|
|
404
|
+
error: subsError,
|
|
405
|
+
onRefresh: () => void loadSubs()
|
|
307
406
|
})]
|
|
308
407
|
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
309
408
|
className: cx("sp-panelEmpty"),
|
|
@@ -322,6 +421,159 @@ window.__ModuleLoader__.load({
|
|
|
322
421
|
})]
|
|
323
422
|
});
|
|
324
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* 「订阅」tab 内容(v1.110.0)。
|
|
426
|
+
*
|
|
427
|
+
* 🔴 **刻意分三段显示,不合并不省略**:
|
|
428
|
+
* ① **意图**(配置里写了谁盯谁)② **实际**(真的投递过没有)③ **机制**(收到过事件吗)。
|
|
429
|
+
* 三者回答三个不同问题,任一为真都不能推出另一个:
|
|
430
|
+
* · 有意图、无实际 ⇒ 还没触发过(⛔ 不是"在工作")
|
|
431
|
+
* · 有实际、`failed>0` ⇒ 触发了但送不到(这才是要修的)
|
|
432
|
+
* · `statusEvents=0` ⇒ 要么没人跑轮次,要么订阅没装配 —— **两者在读数上同形**
|
|
433
|
+
* ⇒ 把三段合成一句话,人就分不出来了(这是本轨迹反复踩的同族形态)。
|
|
434
|
+
*
|
|
435
|
+
* ⚠️ 计数是**进程内**的(宿主重启清零)——UI 上必须写明,
|
|
436
|
+
* 否则 `0` 会被读成"从来没配过"。
|
|
437
|
+
*/
|
|
438
|
+
function SubscriptionsView(props) {
|
|
439
|
+
const { data, busy, error, onRefresh } = props;
|
|
440
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
441
|
+
className: cx("sp-subs"),
|
|
442
|
+
children: [
|
|
443
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
444
|
+
className: cx("sp-subsBar"),
|
|
445
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
446
|
+
type: "button",
|
|
447
|
+
className: cx("sp-subsBtn"),
|
|
448
|
+
disabled: busy,
|
|
449
|
+
onClick: onRefresh,
|
|
450
|
+
children: busy ? "读取中…" : "刷新"
|
|
451
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
452
|
+
className: cx("sp-subsHint"),
|
|
453
|
+
title: "投递计数只存在于宿主进程内存,重启即清零",
|
|
454
|
+
children: "计数重启清零"
|
|
455
|
+
})]
|
|
456
|
+
}),
|
|
457
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
458
|
+
className: cx("sp-subsError"),
|
|
459
|
+
children: ["⚠ ", error]
|
|
460
|
+
}),
|
|
461
|
+
data === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
462
|
+
className: cx("sp-panelEmpty"),
|
|
463
|
+
children: busy ? "读取中…" : "尚未读取"
|
|
464
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
465
|
+
className: cx("sp-subsBody"),
|
|
466
|
+
children: [
|
|
467
|
+
data.problems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
468
|
+
className: cx("sp-subsError"),
|
|
469
|
+
children: [
|
|
470
|
+
"⚠ 配置坏项 ",
|
|
471
|
+
data.problems.length,
|
|
472
|
+
" 条(配了但不生效):",
|
|
473
|
+
data.problems.join(";")
|
|
474
|
+
]
|
|
475
|
+
}),
|
|
476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
477
|
+
className: cx("sp-subsSectionTitle"),
|
|
478
|
+
children: "谁盯谁(配置)"
|
|
479
|
+
}),
|
|
480
|
+
data.subscriptions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
481
|
+
className: cx("sp-panelEmpty"),
|
|
482
|
+
children: "未配置 —— 在 .opencode/serenity.json 的 trajectorySubscriptions 里写"
|
|
483
|
+
}) : data.subscriptions.map((s, i) => {
|
|
484
|
+
const wLabel = s.watcherLabel ?? labelOf(s.watcher);
|
|
485
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
486
|
+
className: cx("sp-subsCard"),
|
|
487
|
+
children: [
|
|
488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
489
|
+
className: cx("sp-subsCardHead"),
|
|
490
|
+
title: `观察者:${s.watcher}`,
|
|
491
|
+
children: [
|
|
492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
493
|
+
className: cx("sp-subsWho"),
|
|
494
|
+
children: wLabel
|
|
495
|
+
}),
|
|
496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
497
|
+
className: cx("sp-subsArrow"),
|
|
498
|
+
children: "→"
|
|
499
|
+
}),
|
|
500
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
501
|
+
className: cx("sp-subsCount"),
|
|
502
|
+
children: [s.members.length, " 个"]
|
|
503
|
+
})
|
|
504
|
+
]
|
|
505
|
+
}),
|
|
506
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
507
|
+
className: cx("sp-subsMembers"),
|
|
508
|
+
children: s.members.map((m, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
509
|
+
className: cx("sp-subsMember"),
|
|
510
|
+
title: m,
|
|
511
|
+
children: s.memberLabels?.[j] ?? labelOf(m)
|
|
512
|
+
}, m))
|
|
513
|
+
}),
|
|
514
|
+
s.note !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
515
|
+
className: cx("sp-subsNote"),
|
|
516
|
+
title: s.note,
|
|
517
|
+
children: s.note
|
|
518
|
+
})
|
|
519
|
+
]
|
|
520
|
+
}, `${s.watcher}-${i}`);
|
|
521
|
+
}),
|
|
522
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
523
|
+
className: cx("sp-subsSectionTitle"),
|
|
524
|
+
children: "投递(实际发生过)"
|
|
525
|
+
}),
|
|
526
|
+
data.deliveries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
527
|
+
className: cx("sp-panelEmpty"),
|
|
528
|
+
children: "还没投递过 —— 配了但没出现=还没触发,不等于在工作"
|
|
529
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
530
|
+
className: cx("sp-subsMembers"),
|
|
531
|
+
children: data.deliveries.map((d) => {
|
|
532
|
+
const { watcher, member } = splitDeliveryKey(d.key);
|
|
533
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
534
|
+
className: cx("sp-subsDelivery"),
|
|
535
|
+
title: d.lastError ?? d.key,
|
|
536
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
537
|
+
className: cx("sp-subsDlvWho"),
|
|
538
|
+
children: member || watcher
|
|
539
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
540
|
+
className: cx("sp-subsDlvStat", d.failed > 0 ? "sp-subsDlvBad" : "sp-subsDlvOk"),
|
|
541
|
+
children: d.failed > 0 ? `失败 ${d.failed}` : `成功 ${d.ok}`
|
|
542
|
+
})]
|
|
543
|
+
}, d.key);
|
|
544
|
+
})
|
|
545
|
+
}),
|
|
546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
547
|
+
className: cx("sp-subsSectionTitle"),
|
|
548
|
+
children: "机制(在跑吗)"
|
|
549
|
+
}),
|
|
550
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
551
|
+
className: cx("sp-subsMeter"),
|
|
552
|
+
children: [
|
|
553
|
+
"收到状态事件 ",
|
|
554
|
+
data.eval.statusEvents,
|
|
555
|
+
" 次 | 认出在跑 ",
|
|
556
|
+
data.eval.runningSeen,
|
|
557
|
+
" / 空闲",
|
|
558
|
+
" ",
|
|
559
|
+
data.eval.idleSeen,
|
|
560
|
+
" | 判成一轮结束 ",
|
|
561
|
+
data.eval.turnFinished
|
|
562
|
+
]
|
|
563
|
+
}),
|
|
564
|
+
Object.keys(data.eval.skips).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
565
|
+
className: cx("sp-subsMeter"),
|
|
566
|
+
children: ["跳过:", Object.entries(data.eval.skips).map(([k, v]) => `${k}×${v}`).join("、")]
|
|
567
|
+
}),
|
|
568
|
+
data.eval.statusEvents === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
569
|
+
className: cx("sp-panelEmpty"),
|
|
570
|
+
children: "⚠ 一次状态事件都没收到 —— 此刻没人跑轮次,或订阅未装配;两者读数同形。"
|
|
571
|
+
})
|
|
572
|
+
]
|
|
573
|
+
})
|
|
574
|
+
]
|
|
575
|
+
});
|
|
576
|
+
}
|
|
325
577
|
//#endregion
|
|
326
578
|
//#region src/client/image-fallback-api.ts
|
|
327
579
|
/** 图片落盘接口路径(node half surface/http.ts,client 专属 x-serenity-ui 头) */
|
|
@@ -4754,161 +5006,10 @@ Instruction-following style:
|
|
|
4754
5006
|
title: "会话清理",
|
|
4755
5007
|
desc: "删除 N 天前无活动的 DSH 会话(手动,不可恢复)",
|
|
4756
5008
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionCleanupBlock, {})
|
|
4757
|
-
}),
|
|
4758
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapse, {
|
|
4759
|
-
title: "轨迹订阅",
|
|
4760
|
-
desc: "观察者盯一组成员,任一轮结束即通知(只读;配置在 serenity.json)",
|
|
4761
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsBlock, {})
|
|
4762
5009
|
})
|
|
4763
5010
|
]
|
|
4764
5011
|
});
|
|
4765
5012
|
}
|
|
4766
|
-
/**
|
|
4767
|
-
* 「轨迹订阅」块(v1.108.0,owner 2026-11-01 P3)。
|
|
4768
|
-
*
|
|
4769
|
-
* 🔴 **只读**,且刻意分两栏显示「意图」与「实际」:
|
|
4770
|
-
* · **意图栏** = 配置里写了谁盯谁;**实际栏** = 真的发生过投递的键。
|
|
4771
|
-
* · 两者**必须分开看**:一个配了却不出现在实际栏的订阅 = **还没触发过**(不是"在工作");
|
|
4772
|
-
* 出现在实际栏且 `failed>0` = **触发了但送不到**(那才是要修的)。
|
|
4773
|
-
* 把这两件事合成一句话,人就分不出来了(这正是 diag ⑤ 段同一个理由)。
|
|
4774
|
-
*
|
|
4775
|
-
* ⚠️ 计数是**进程内**的(重启清零)—— UI 上必须写明,否则"0"会被读成"从来没配过"。
|
|
4776
|
-
*/
|
|
4777
|
-
function SubscriptionsBlock() {
|
|
4778
|
-
const [data, setData] = (0, react.useState)(null);
|
|
4779
|
-
const [busy, setBusy] = (0, react.useState)(false);
|
|
4780
|
-
const [error, setError] = (0, react.useState)(null);
|
|
4781
|
-
const load = async () => {
|
|
4782
|
-
setBusy(true);
|
|
4783
|
-
setError(null);
|
|
4784
|
-
try {
|
|
4785
|
-
const res = await fetch("/serenity/subscriptions", { headers: {
|
|
4786
|
-
accept: "application/json",
|
|
4787
|
-
"x-serenity-ui": "1"
|
|
4788
|
-
} });
|
|
4789
|
-
if (!res.ok) {
|
|
4790
|
-
const body = await res.json().catch(() => null);
|
|
4791
|
-
setError(body?.error ?? `HTTP ${res.status}`);
|
|
4792
|
-
return;
|
|
4793
|
-
}
|
|
4794
|
-
setData(await res.json());
|
|
4795
|
-
} catch (err) {
|
|
4796
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
4797
|
-
} finally {
|
|
4798
|
-
setBusy(false);
|
|
4799
|
-
}
|
|
4800
|
-
};
|
|
4801
|
-
(0, react.useEffect)(() => {
|
|
4802
|
-
load();
|
|
4803
|
-
}, []);
|
|
4804
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4805
|
-
className: "ss-field",
|
|
4806
|
-
children: [
|
|
4807
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4808
|
-
className: "ss-row",
|
|
4809
|
-
style: {
|
|
4810
|
-
display: "flex",
|
|
4811
|
-
alignItems: "center",
|
|
4812
|
-
gap: 8
|
|
4813
|
-
},
|
|
4814
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4815
|
-
type: "button",
|
|
4816
|
-
className: "ss-btn",
|
|
4817
|
-
disabled: busy,
|
|
4818
|
-
onClick: () => void load(),
|
|
4819
|
-
children: busy ? "读取中…" : "刷新读数"
|
|
4820
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4821
|
-
style: {
|
|
4822
|
-
opacity: .7,
|
|
4823
|
-
fontSize: 12
|
|
4824
|
-
},
|
|
4825
|
-
children: "投递计数只在宿主进程内有效(重启清零);配置全表在 .opencode/serenity.json"
|
|
4826
|
-
})]
|
|
4827
|
-
}),
|
|
4828
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4829
|
-
className: "ss-error",
|
|
4830
|
-
children: ["⚠ ", error]
|
|
4831
|
-
}),
|
|
4832
|
-
data !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4833
|
-
data.problems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4834
|
-
className: "ss-error",
|
|
4835
|
-
children: [
|
|
4836
|
-
"⚠ 配置坏项 ",
|
|
4837
|
-
data.problems.length,
|
|
4838
|
-
" 条(配了但不生效):",
|
|
4839
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4840
|
-
style: { margin: "4px 0 0 16px" },
|
|
4841
|
-
children: data.problems.map((p, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: p }, i))
|
|
4842
|
-
})
|
|
4843
|
-
]
|
|
4844
|
-
}),
|
|
4845
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
4846
|
-
style: { margin: "12px 0 4px" },
|
|
4847
|
-
children: "配置(意图:谁盯谁)"
|
|
4848
|
-
}),
|
|
4849
|
-
data.subscriptions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4850
|
-
style: { opacity: .7 },
|
|
4851
|
-
children: "(未配置任何订阅 —— 在 .opencode/serenity.json 里写 trajectorySubscriptions)"
|
|
4852
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4853
|
-
style: { margin: "0 0 0 16px" },
|
|
4854
|
-
children: data.subscriptions.map((s, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
4855
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: s.watcher }),
|
|
4856
|
-
" 盯 ",
|
|
4857
|
-
s.members.join("、"),
|
|
4858
|
-
s.note !== null ? `(${s.note})` : ""
|
|
4859
|
-
] }, i))
|
|
4860
|
-
}),
|
|
4861
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
4862
|
-
style: { margin: "12px 0 4px" },
|
|
4863
|
-
children: "投递(实际:发生了没有)"
|
|
4864
|
-
}),
|
|
4865
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4866
|
-
style: {
|
|
4867
|
-
opacity: .75,
|
|
4868
|
-
fontSize: 12,
|
|
4869
|
-
marginBottom: 4
|
|
4870
|
-
},
|
|
4871
|
-
children: [
|
|
4872
|
-
"机制计数:收到 agent/status ",
|
|
4873
|
-
data.eval.statusEvents,
|
|
4874
|
-
" 次 | running ",
|
|
4875
|
-
data.eval.runningSeen,
|
|
4876
|
-
" / idle",
|
|
4877
|
-
" ",
|
|
4878
|
-
data.eval.idleSeen,
|
|
4879
|
-
" | 判成一轮结束 ",
|
|
4880
|
-
data.eval.turnFinished,
|
|
4881
|
-
Object.keys(data.eval.skips).length > 0 && ` | 跳过 ${Object.entries(data.eval.skips).map(([k, v]) => `${k}=${v}`).join("、")}`
|
|
4882
|
-
]
|
|
4883
|
-
}),
|
|
4884
|
-
data.eval.statusEvents === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4885
|
-
style: {
|
|
4886
|
-
opacity: .7,
|
|
4887
|
-
fontSize: 12
|
|
4888
|
-
},
|
|
4889
|
-
children: "⚠ 一次 agent/status 都没收到 —— 可能此刻没人跑轮次,也可能订阅未装配;两者在读数上同形。"
|
|
4890
|
-
}),
|
|
4891
|
-
data.deliveries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4892
|
-
style: { opacity: .7 },
|
|
4893
|
-
children: "(本进程内还没有任何投递发生 —— 配了但没出现 = 还没触发过,不等于在工作)"
|
|
4894
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
4895
|
-
style: { margin: "0 0 0 16px" },
|
|
4896
|
-
children: data.deliveries.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
4897
|
-
d.key,
|
|
4898
|
-
":成功 ",
|
|
4899
|
-
d.ok,
|
|
4900
|
-
" / 失败 ",
|
|
4901
|
-
d.failed,
|
|
4902
|
-
d.failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4903
|
-
className: "ss-error",
|
|
4904
|
-
children: [" ⚠ ", d.lastError ?? "(无说明)"]
|
|
4905
|
-
})
|
|
4906
|
-
] }, i))
|
|
4907
|
-
})
|
|
4908
|
-
] })
|
|
4909
|
-
]
|
|
4910
|
-
});
|
|
4911
|
-
}
|
|
4912
5013
|
/** 「会话清理」块(v1.29 需求③,S142 用户拍板:lastActive + 手动按钮 + 直接物理删):
|
|
4913
5014
|
* 天数输入 → GET 预览(dryRun 列表)→ 确认 → POST 执行。live 会话由服务端保护跳过。 */
|
|
4914
5015
|
function SessionCleanupBlock() {
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
2
|
import { c as hostAgents, d as hostSessions, f as hostSettings, i as cccRootForExec, m as hostWebServer, n as agentCwdFor, o as listCccs, p as hostWeb, r as cccRootForCwd, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-DPMwBywc.js";
|
|
3
3
|
import { a as findSerenityRoot$1, d as readCccName, g as resolveInside, l as pathInside, m as readTrajectorySubscriptions, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-BmpE3cQz.js";
|
|
4
|
-
import { A as useSession, C as resolveSessionByTitle, D as showSession, E as setActiveSessionInfo, F as isoLocal, I as localFileStamp, M as TRAJECTORY_LEGACY_FILENAMES, O as summarize, S as readTrajectoryUid, T as sessionsRoot, _ as listSessions, b as parseTrajectoryCode, c as UID_FILENAME, d as createSession, f as extractSessionMdPathFromText, j as TRAJECTORY_FILENAME, l as archiveSession, m as getActiveSessionInfo, o as COGNITIVE_TRAJECTORY_ACTIONS, p as findSession, u as clearActiveSessionInfo, v as namingTitleFor, w as sessionEvents, y as parseSessionContextFromEvents } from "./bind-file-B0TuOP89.js";
|
|
4
|
+
import { A as useSession, C as resolveSessionByTitle, D as showSession, E as setActiveSessionInfo, F as isoLocal, I as localFileStamp, M as TRAJECTORY_LEGACY_FILENAMES, O as summarize, S as readTrajectoryUid, T as sessionsRoot, _ as listSessions, b as parseTrajectoryCode, c as UID_FILENAME, d as createSession, f as extractSessionMdPathFromText, j as TRAJECTORY_FILENAME, k as trajectoryDescOf, l as archiveSession, m as getActiveSessionInfo, o as COGNITIVE_TRAJECTORY_ACTIONS, p as findSession, u as clearActiveSessionInfo, v as namingTitleFor, w as sessionEvents, y as parseSessionContextFromEvents } from "./bind-file-B0TuOP89.js";
|
|
5
5
|
import { n as checkLocalstoreGitCompliance, o as readStore, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./service-Z3aSR1cn.js";
|
|
6
6
|
import { d as findPackageRoot, i as constraintSystemPromptSource, l as resolveConstraintSystemPrompt, r as constraintCognitiveTrajectory, t as TRAJECTORY_CONSTRAINT_HINT, u as ACC_VERSION } from "./rules-BLkYTPUy.js";
|
|
7
7
|
import { C as docsPointer, E as setWeixinSendEndpoint, S as runMsmAsync, T as renderDocsIndex, _ as CCC_CONFIG_REFERENCE, a as setSafeMode, b as loadMsmEntries, d as forgetNonCccToolVisibility, g as ACC_CATALOG, h as forgetToolVisibility, i as readDshVersion, m as syncToolVisibility, n as summarizeHostContract, o as listOpencodeSkillDirs, p as registerGuards, r as getStatus, s as loadOpencodeSkill, t as hostContractReport, w as findDocsChapter, x as runMsm, y as findEntry } from "./contract-BiNzzd77.js";
|
|
@@ -9290,6 +9290,27 @@ function sendJson$2(res, code, body) {
|
|
|
9290
9290
|
res.end(payload);
|
|
9291
9291
|
}
|
|
9292
9292
|
/**
|
|
9293
|
+
* 轨迹的**展示名**(v1.111.0):`2026-10-23--butler--家庭容器治理管家` → `butler · 家庭容器治理管家`。
|
|
9294
|
+
*
|
|
9295
|
+
* 🔴 为什么要它(owner 原话:「功能正常,但太丑陋了 这怎么让人看得懂呢」):
|
|
9296
|
+
* 面板是 420px 窄栏,而原始目录名 20+ 字符 ⇒ 被截断成 `2026-10-23--butler--家庭容器…`,
|
|
9297
|
+
* 用户看到的是**一串日期**,而身份信息(谁)被挤到看不见的地方。
|
|
9298
|
+
* 日期前缀是**排序/归档信息,不是身份** ⇒ 展示时让位。
|
|
9299
|
+
*
|
|
9300
|
+
* 判据:解析走 {@link parseTrajectoryCode} / {@link trajectoryDescOf} 两个**既有**函数
|
|
9301
|
+
* —— 它们与 `create --id` 的写入侧同源(互逆)。⛔ 不在这里另写剥日期的正则:
|
|
9302
|
+
* 那会长出第二份会漂的实现(同 `resolveWorkspace` 那条纪律)。
|
|
9303
|
+
*
|
|
9304
|
+
* @param name 轨迹编码(`S142` / `butler`)**或** AGENT_SESSIONS 目录名(配置里两种都允许)
|
|
9305
|
+
* @returns 展示名;解析不出(不是目录名形态)⇒ **原样返回**(不吞信息,也不假装认识它)
|
|
9306
|
+
*/
|
|
9307
|
+
function trajectoryLabel(name) {
|
|
9308
|
+
const code = parseTrajectoryCode(name);
|
|
9309
|
+
if (code === null) return name;
|
|
9310
|
+
const desc = trajectoryDescOf(name);
|
|
9311
|
+
return desc === "" ? code : `${code} · ${desc}`;
|
|
9312
|
+
}
|
|
9313
|
+
/**
|
|
9293
9314
|
* WebUI 专属动作的来源判别(C4 块 C:此前 **9 处内联** `req.headers['x-serenity-ui'] !== '1'`)。
|
|
9294
9315
|
*
|
|
9295
9316
|
* ⚠️ 语义边界(勿误读为认证):这是**客户端判别头**——任何本机能发 HTTP 的进程都可自带它
|
|
@@ -9544,7 +9565,9 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
9544
9565
|
subscriptions: subs.map((s) => ({
|
|
9545
9566
|
watcher: s.watcher,
|
|
9546
9567
|
members: s.members,
|
|
9547
|
-
note: s.note ?? null
|
|
9568
|
+
note: s.note ?? null,
|
|
9569
|
+
watcherLabel: trajectoryLabel(s.watcher),
|
|
9570
|
+
memberLabels: s.members.map((m) => trajectoryLabel(m))
|
|
9548
9571
|
})),
|
|
9549
9572
|
problems,
|
|
9550
9573
|
deliveries: subscriptionStats(),
|
package/lib/surface/http.d.ts
CHANGED
|
@@ -30,6 +30,22 @@ export declare function sanitizeFileName(name: string): string;
|
|
|
30
30
|
* 校验失败抛 Error(handler 转 400)。
|
|
31
31
|
*/
|
|
32
32
|
export declare function saveFileToTmp(root: string, fileName: string, data: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* 轨迹的**展示名**(v1.111.0):`2026-10-23--butler--家庭容器治理管家` → `butler · 家庭容器治理管家`。
|
|
35
|
+
*
|
|
36
|
+
* 🔴 为什么要它(owner 原话:「功能正常,但太丑陋了 这怎么让人看得懂呢」):
|
|
37
|
+
* 面板是 420px 窄栏,而原始目录名 20+ 字符 ⇒ 被截断成 `2026-10-23--butler--家庭容器…`,
|
|
38
|
+
* 用户看到的是**一串日期**,而身份信息(谁)被挤到看不见的地方。
|
|
39
|
+
* 日期前缀是**排序/归档信息,不是身份** ⇒ 展示时让位。
|
|
40
|
+
*
|
|
41
|
+
* 判据:解析走 {@link parseTrajectoryCode} / {@link trajectoryDescOf} 两个**既有**函数
|
|
42
|
+
* —— 它们与 `create --id` 的写入侧同源(互逆)。⛔ 不在这里另写剥日期的正则:
|
|
43
|
+
* 那会长出第二份会漂的实现(同 `resolveWorkspace` 那条纪律)。
|
|
44
|
+
*
|
|
45
|
+
* @param name 轨迹编码(`S142` / `butler`)**或** AGENT_SESSIONS 目录名(配置里两种都允许)
|
|
46
|
+
* @returns 展示名;解析不出(不是目录名形态)⇒ **原样返回**(不吞信息,也不假装认识它)
|
|
47
|
+
*/
|
|
48
|
+
export declare function trajectoryLabel(name: string): string;
|
|
33
49
|
interface StatusApiRegistration {
|
|
34
50
|
configPaths?: string[];
|
|
35
51
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.111.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:12 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另两个 im-bridge/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": {
|