@shgroup/dsh-serenity-hooks 1.66.0 → 1.68.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 +109 -199
- package/lib/cognitive-trajectory-ops.d.ts +70 -3
- package/lib/index.js +112 -62
- package/lib/{message-source-DM9-2eUT.js → message-source-Catc7q1z.js} +102 -5
- package/lib/status.d.ts +14 -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.68.0",
|
|
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 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 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/* 左栏:信息(正文)——固定 340px,与旧卡片同宽,保证原有排印不变 */\n.sp-popVoyage > .sp-popBody {\n position: relative;\n z-index: 1;\n order: 1;\n flex: none;\n width: 340px;\n min-width: 0;\n}\n/* 右栏:动画——吃掉剩余宽度,并吃满左栏高度 */\n.sp-voyage {\n order: 2;\n flex: 1 1 auto;\n min-width: 0;\n position: relative;\n pointer-events: none; /* 纯展示:不挡点击、不抢焦点 */\n overflow: hidden;\n border-radius: 10px;\n /* 🔴 2026-10-18:下限 260 → **360**。卡片加高后动画也该跟着长起来,\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-popBody {\n width: auto; /* 单栏下不再固定 340px,跟随卡片宽度 */\n flex: none; /* 按内容高(仍受自身 max-height 约束) */\n }\n .sp-voyage {\n flex: none;\n height: 200px; /* 单栏时给动画明确高度,避免被 flex 压扁 */\n min-height: 0;\n }\n}\n\n.sp-popBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-height: calc(100vh - 140px);\n overflow-y: auto;\n}\n\n/* ── 分组(官方 settings 语言:groupTitle + rows) ── */\n\n.sp-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-groupHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.sp-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* ── 活跃轨迹清单(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/* ── 主体: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");
|
|
@@ -24,26 +24,6 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
/** 尚未落位时的屏外坐标(`useLayoutEffect` 在绘制前就会覆写,肉眼看不到) */
|
|
25
25
|
const POP_OFFSCREEN = -9999;
|
|
26
26
|
const API = "/serenity/status";
|
|
27
|
-
/** 盾牌 SVG(v1.24.5 校准:fill=none 纯描边;ON=绿 emerald / OFF=灰 label-secondary 由状态类控制) */
|
|
28
|
-
const SHIELD_SVG = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
29
|
-
viewBox: "0 0 16 16",
|
|
30
|
-
fill: "none",
|
|
31
|
-
width: 16,
|
|
32
|
-
height: 16,
|
|
33
|
-
"aria-hidden": "true",
|
|
34
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
35
|
-
d: "M8 1.5L13.5 3.5V7.2C13.5 10.6 11.2 13.6 8 14.8C4.8 13.6 2.5 10.6 2.5 7.2V3.5L8 1.5Z",
|
|
36
|
-
stroke: "currentColor",
|
|
37
|
-
strokeWidth: 1.3,
|
|
38
|
-
strokeLinejoin: "round"
|
|
39
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
40
|
-
d: "M5.8 8L7.3 9.5L10.2 6.6",
|
|
41
|
-
stroke: "currentColor",
|
|
42
|
-
strokeWidth: 1.2,
|
|
43
|
-
strokeLinecap: "round",
|
|
44
|
-
strokeLinejoin: "round"
|
|
45
|
-
})]
|
|
46
|
-
});
|
|
47
27
|
/** 拼接类名(等价 clsx 最小子集,避免额外依赖) */
|
|
48
28
|
function cx(...parts) {
|
|
49
29
|
return parts.filter(Boolean).join(" ");
|
|
@@ -51,13 +31,16 @@ window.__ModuleLoader__.load({
|
|
|
51
31
|
/** 会话头部:Serenity 状态胶囊(点击 → 自绘 popover,纯状态展示) */
|
|
52
32
|
function SafeModePanel(props) {
|
|
53
33
|
const [status, setStatus] = (0, react.useState)(null);
|
|
54
|
-
const [busy, setBusy] = (0, react.useState)(false);
|
|
55
34
|
const [open, setOpen] = (0, react.useState)(false);
|
|
35
|
+
/** tab 切换(2026-10-18 owner 修正:活跃轨迹 / 技能 用 tab,而不是并排两个 panel)
|
|
36
|
+
* ⚠️ 纯前端显示状态 —— 两份数据后端一次性都给全,切换**不触发任何请求**。 */
|
|
37
|
+
const [tab, setTab] = (0, react.useState)("trajectories");
|
|
56
38
|
const rootRef = (0, react.useRef)(null);
|
|
57
39
|
const popRef = (0, react.useRef)(null);
|
|
58
40
|
const [popPos, setPopPos] = (0, react.useState)(null);
|
|
59
41
|
const sessionId = props.sessionId;
|
|
60
42
|
const trajectories = status?.trajectories ?? [];
|
|
43
|
+
const skills = status?.skills ?? [];
|
|
61
44
|
const refresh = (0, react.useCallback)(async () => {
|
|
62
45
|
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
63
46
|
try {
|
|
@@ -114,25 +97,6 @@ window.__ModuleLoader__.load({
|
|
|
114
97
|
window.removeEventListener("scroll", place, true);
|
|
115
98
|
};
|
|
116
99
|
}, [open]);
|
|
117
|
-
const toggle = async (on) => {
|
|
118
|
-
setBusy(true);
|
|
119
|
-
try {
|
|
120
|
-
await fetch(API, {
|
|
121
|
-
method: "POST",
|
|
122
|
-
headers: {
|
|
123
|
-
"content-type": "application/json",
|
|
124
|
-
"x-serenity-ui": "1"
|
|
125
|
-
},
|
|
126
|
-
body: JSON.stringify({
|
|
127
|
-
sessionId,
|
|
128
|
-
on
|
|
129
|
-
})
|
|
130
|
-
});
|
|
131
|
-
await refresh();
|
|
132
|
-
} finally {
|
|
133
|
-
setBusy(false);
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
100
|
if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
137
101
|
type: "button",
|
|
138
102
|
className: cx("sp-card"),
|
|
@@ -163,29 +127,15 @@ window.__ModuleLoader__.load({
|
|
|
163
127
|
className: cx("sp-ver"),
|
|
164
128
|
children: ["v", status.accVersion]
|
|
165
129
|
}),
|
|
166
|
-
inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}),
|
|
176
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
177
|
-
className: cx("sp-switch", status.safeModeOn ? "sp-switchOn" : "sp-switchOff", busy ? "sp-switchBusy" : void 0),
|
|
178
|
-
role: "switch",
|
|
179
|
-
"aria-checked": status.safeModeOn,
|
|
180
|
-
title: status.safeModeOn ? "safe-mode ON(点击关闭)" : "safe-mode OFF(点击开启)",
|
|
181
|
-
onClick: (e) => {
|
|
182
|
-
e.stopPropagation();
|
|
183
|
-
if (busy) return;
|
|
184
|
-
toggle(!status.safeModeOn);
|
|
185
|
-
},
|
|
186
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-switchThumb") })
|
|
187
|
-
})
|
|
188
|
-
] }),
|
|
130
|
+
inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-sep") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
131
|
+
className: cx("sp-safeBadge", status.safeModeOn ? "sp-safeBadgeOn" : "sp-safeBadgeOff"),
|
|
132
|
+
title: status.safeModeOn ? "safe-mode ON — 写工具已隐藏(状态信息;开关在 设置 → Serenity)" : "safe-mode OFF — 写工具全开(状态信息;开关在 设置 → Serenity)",
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-safeDot") }),
|
|
135
|
+
"SAFE ",
|
|
136
|
+
status.safeModeOn ? "ON" : "OFF"
|
|
137
|
+
]
|
|
138
|
+
})] }),
|
|
189
139
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineMedium, {
|
|
190
140
|
size: 12,
|
|
191
141
|
className: cx("sp-chev")
|
|
@@ -204,149 +154,109 @@ window.__ModuleLoader__.load({
|
|
|
204
154
|
top: POP_OFFSCREEN
|
|
205
155
|
},
|
|
206
156
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
207
|
-
className: cx("sp-
|
|
208
|
-
children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
157
|
+
className: cx("sp-content"),
|
|
158
|
+
children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
159
|
+
className: cx("sp-meta"),
|
|
160
|
+
title: `CCC: ${status.root ?? ""}`,
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
163
|
+
className: cx("sp-metaPath"),
|
|
164
|
+
children: status.root
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
167
|
+
className: cx("sp-metaSep"),
|
|
168
|
+
children: "·"
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.cheapWorkerModel ?? "模型未配置" }),
|
|
171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
172
|
+
className: cx("sp-metaSep"),
|
|
173
|
+
children: "·"
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
176
|
+
"blacklist ",
|
|
177
|
+
status.blacklist.length,
|
|
178
|
+
status.threshold !== null ? ` · keeper ${status.threshold}` : ""
|
|
179
|
+
] })
|
|
180
|
+
]
|
|
181
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
182
|
+
className: cx("sp-tabs"),
|
|
183
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
184
|
+
className: cx("sp-tabBar"),
|
|
185
|
+
role: "tablist",
|
|
186
|
+
"aria-label": "CCC 状态面板",
|
|
187
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
188
|
+
type: "button",
|
|
189
|
+
role: "tab",
|
|
190
|
+
"aria-selected": tab === "trajectories",
|
|
191
|
+
className: cx("sp-tab", tab === "trajectories" && "sp-tabActive"),
|
|
192
|
+
onClick: () => setTab("trajectories"),
|
|
193
|
+
children: ["活跃轨迹", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
194
|
+
className: cx("sp-count"),
|
|
195
|
+
children: trajectories.length
|
|
196
|
+
})]
|
|
197
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
198
|
+
type: "button",
|
|
199
|
+
role: "tab",
|
|
200
|
+
"aria-selected": tab === "skills",
|
|
201
|
+
className: cx("sp-tab", tab === "skills" && "sp-tabActive"),
|
|
202
|
+
onClick: () => setTab("skills"),
|
|
203
|
+
children: ["技能", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
204
|
+
className: cx("sp-count"),
|
|
205
|
+
children: skills.length
|
|
206
|
+
})]
|
|
207
|
+
})]
|
|
208
|
+
}), tab === "trajectories" ? trajectories.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
209
|
+
className: cx("sp-panelEmpty"),
|
|
210
|
+
children: "暂无活跃轨迹"
|
|
211
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
212
|
+
className: cx("sp-list"),
|
|
213
|
+
role: "tabpanel",
|
|
214
|
+
children: trajectories.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
215
|
+
className: cx("sp-item"),
|
|
216
216
|
children: [
|
|
217
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
218
|
-
className: cx("sp-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
})]
|
|
217
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
218
|
+
className: cx("sp-itemId"),
|
|
219
|
+
title: t.uid !== null ? "UID(唯一标识符)" : "编码(未设定 UID)",
|
|
220
|
+
children: t.uid ?? (t.sessionId || "—")
|
|
221
|
+
}),
|
|
222
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
223
|
+
className: cx("sp-itemName"),
|
|
224
|
+
title: t.dirName,
|
|
225
|
+
children: t.dirName
|
|
227
226
|
}),
|
|
228
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
229
|
-
className: cx("sp-
|
|
230
|
-
children: [
|
|
231
|
-
className: cx("sp-label"),
|
|
232
|
-
children: "CheapWorker 模型"
|
|
233
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
234
|
-
className: cx("sp-value"),
|
|
235
|
-
title: status.cheapWorkerModel ? `cheap-worker.models: ${status.cheapWorkerModel}` : void 0,
|
|
236
|
-
children: status.cheapWorkerModel ?? "未配置"
|
|
237
|
-
})]
|
|
227
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
228
|
+
className: cx("sp-itemAge"),
|
|
229
|
+
children: [t.ageDays, "d"]
|
|
238
230
|
}),
|
|
239
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
240
|
-
className: cx("sp-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
children: "守卫"
|
|
244
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
245
|
-
className: cx("sp-value"),
|
|
246
|
-
children: [
|
|
247
|
-
"blacklist ",
|
|
248
|
-
status.blacklist.length,
|
|
249
|
-
" 条",
|
|
250
|
-
status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
|
|
251
|
-
]
|
|
252
|
-
})]
|
|
231
|
+
!t.hasSessionMd && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
232
|
+
className: cx("sp-itemWarn"),
|
|
233
|
+
title: "目录存在但没有 SESSION.md(空壳)",
|
|
234
|
+
children: "空壳"
|
|
253
235
|
})
|
|
254
236
|
]
|
|
255
|
-
})
|
|
256
|
-
}),
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
className: cx("sp-
|
|
264
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
265
|
-
type: "button",
|
|
266
|
-
className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
|
|
267
|
-
disabled: busy,
|
|
268
|
-
onClick: () => void toggle(!status.safeModeOn),
|
|
269
|
-
children: [
|
|
270
|
-
status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineMedium, {
|
|
271
|
-
size: 14,
|
|
272
|
-
className: cx("sp-toggleIcon")
|
|
273
|
-
}),
|
|
274
|
-
"safe-mode ",
|
|
275
|
-
status.safeModeOn ? "ON" : "OFF"
|
|
276
|
-
]
|
|
277
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
278
|
-
className: cx("sp-toggleDesc"),
|
|
279
|
-
children: status.safeModeOn ? "写工具已隐藏,可无人值守运行" : "写工具可用(点击开启)"
|
|
280
|
-
})]
|
|
281
|
-
})]
|
|
282
|
-
}),
|
|
283
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
284
|
-
className: cx("sp-group"),
|
|
285
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
286
|
-
className: cx("sp-groupHead"),
|
|
287
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
288
|
-
className: cx("sp-groupTitle"),
|
|
289
|
-
children: "活跃轨迹"
|
|
290
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
291
|
-
className: cx("sp-count"),
|
|
292
|
-
children: trajectories.length
|
|
293
|
-
})]
|
|
294
|
-
}), trajectories.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
295
|
-
className: cx("sp-hint"),
|
|
296
|
-
children: "(暂无活跃轨迹)"
|
|
297
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
298
|
-
className: cx("sp-trajList"),
|
|
299
|
-
children: trajectories.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
300
|
-
className: cx("sp-trajItem"),
|
|
301
|
-
children: [
|
|
302
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
|
-
className: cx("sp-trajId"),
|
|
304
|
-
children: t.sessionId || "—"
|
|
305
|
-
}),
|
|
306
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
307
|
-
className: cx("sp-trajName"),
|
|
308
|
-
title: t.dirName,
|
|
309
|
-
children: t.dirName
|
|
310
|
-
}),
|
|
311
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
312
|
-
className: cx("sp-trajAge"),
|
|
313
|
-
children: [t.ageDays, "d"]
|
|
314
|
-
}),
|
|
315
|
-
!t.hasSessionMd && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
316
|
-
className: cx("sp-trajWarn"),
|
|
317
|
-
title: "目录存在但没有 SESSION.md(空壳)",
|
|
318
|
-
children: "空壳"
|
|
319
|
-
})
|
|
320
|
-
]
|
|
321
|
-
}, t.dirName))
|
|
322
|
-
})]
|
|
323
|
-
}),
|
|
324
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
325
|
-
className: cx("sp-hint"),
|
|
326
|
-
children: [
|
|
327
|
-
"💡 开关 / 阈值 / 账号 / 白名单在 ",
|
|
328
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "设置 → Serenity" }),
|
|
329
|
-
" 里配。"
|
|
330
|
-
]
|
|
331
|
-
})
|
|
332
|
-
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
333
|
-
className: cx("sp-group"),
|
|
334
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
335
|
-
className: cx("sp-groupTitle"),
|
|
336
|
-
children: "状态"
|
|
337
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
338
|
-
className: cx("sp-rows"),
|
|
339
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
340
|
-
className: cx("sp-row"),
|
|
237
|
+
}, t.dirName))
|
|
238
|
+
}) : skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
239
|
+
className: cx("sp-panelEmpty"),
|
|
240
|
+
children: "暂无技能"
|
|
241
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
242
|
+
className: cx("sp-list"),
|
|
243
|
+
role: "tabpanel",
|
|
244
|
+
children: skills.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
245
|
+
className: cx("sp-item"),
|
|
341
246
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
342
|
-
className: cx("sp-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
247
|
+
className: cx("sp-itemName", "sp-itemNameFull"),
|
|
248
|
+
title: s.name,
|
|
249
|
+
children: s.name
|
|
250
|
+
}), s.modeled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
251
|
+
className: cx("sp-itemTag"),
|
|
252
|
+
title: "已建模(存在 skill.ttl)",
|
|
253
|
+
children: "建模"
|
|
347
254
|
})]
|
|
348
|
-
})
|
|
255
|
+
}, s.name))
|
|
349
256
|
})]
|
|
257
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
258
|
+
className: cx("sp-panelEmpty"),
|
|
259
|
+
children: "未激活(工作区不在 CCC 内)"
|
|
350
260
|
})
|
|
351
261
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
352
262
|
className: cx("sp-voyage"),
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
* 而**管轨迹的人正是用本工具的人**;挂这儿 = 设计 §9-5 的"**既有 guide 位置、不新增域**"。
|
|
61
61
|
* ⚠️ 本动作**纯读**(输出指南正文,不碰 fs、不碰注册表)。
|
|
62
62
|
*/
|
|
63
|
-
type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'send-now' | 'send-later' | 'cro-guide';
|
|
63
|
+
type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'uid' | 'send-now' | 'send-later' | 'cro-guide';
|
|
64
64
|
export declare const COGNITIVE_TRAJECTORY_ACTIONS: readonly CognitiveTrajectoryAction[];
|
|
65
65
|
/**
|
|
66
66
|
* 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
|
|
@@ -92,8 +92,70 @@ interface SessionEntry {
|
|
|
92
92
|
status: SessionStatus;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* UID 的声明文件名(**固定名、住在轨迹目录内**)。
|
|
96
|
+
*
|
|
97
|
+
* 🔴 owner 2026-10-18 需求原话:*"认知轨迹编号和命名问题,命名很可能由于变化而变化,
|
|
98
|
+
* 所以我们需要让认知轨迹支持一种用于交流的唯一标识符,它不同于编码和命名,而是单独设定的
|
|
99
|
+
* (也可以不设定,不设定就还是用编码),所有可以用编码操作的能力,都同样支持唯一标识符,
|
|
100
|
+
* 唯一标识符用文件形式声明在认知轨迹目录中"*。
|
|
101
|
+
*
|
|
102
|
+
* ## 为什么是「一个文件」而不是 frontmatter 里一行
|
|
103
|
+
* | 形 | 问题 |
|
|
104
|
+
* |---|---|
|
|
105
|
+
* | 写进 `SESSION.md` frontmatter | SESSION.md 是**轨迹的身体**(会被重写、被 rebuild 锚点反复读)⇒ 标识符住在会变的东西里 |
|
|
106
|
+
* | 写进目录名 | 改名即变 —— 而这正是要解决的问题(owner:命名会变) |
|
|
107
|
+
* | **独立小文件**(选定) | 与目录名、SESSION.md **都解耦**;改标识符不动正文、不动目录名;删文件即"回到用编码" |
|
|
108
|
+
*
|
|
109
|
+
* ## 语义边界(R↓)
|
|
110
|
+
* - **可选**:文件不存在 ⇒ 用编码(`S###`),**不是错误**。
|
|
111
|
+
* - **只用于"被指认"**:它是 `findSession` 的一个**解析分支**,不参与排序、不参与展示优先级、
|
|
112
|
+
* **不改变**目录名与 SESSION.md 一个字节(与 `archive` 同一条纪律:只换指认方式,不动本体)。
|
|
113
|
+
* - **唯一性**:由 `setTrajectoryUid` 在写入时保证(重复即拒绝)。读取侧不做全表唯一性断言 ——
|
|
114
|
+
* 外部手工造的重复应由写入侧拦,读取侧只能"第一个命中",那是注入侧的职责不是解析侧的。
|
|
115
|
+
*/
|
|
116
|
+
export declare const UID_FILENAME = "TRAJECTORY-UID";
|
|
117
|
+
/**
|
|
118
|
+
* 校验 UID 形态。
|
|
119
|
+
*
|
|
120
|
+
* 为什么保守(R↓):UID 是**给人和 agent 在对话里互相指认用的**(owner:「用于交流」)⇒
|
|
121
|
+
* 它必须能**逐字打出来**。含空格/引号/中文的 UID 会在 shell、JSON、markdown 里各自需要一套转义
|
|
122
|
+
* ⇒ "用于交流"这个目的当场失败。⇒ 限定 `[A-Za-z0-9._-]`,首字符必须字母数字,上限 64。
|
|
123
|
+
*
|
|
124
|
+
* ⚠️ 大小写**敏感**:`S142` 与 `s142` 是两个不同 UID(判据:唯一标识符不是给人近似匹配用的;
|
|
125
|
+
* 要模糊匹配请用目录名/编码那条既有分支)。
|
|
126
|
+
*/
|
|
127
|
+
export declare function isValidUid(uid: string): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* 读一条轨迹的 UID(文件不存在 / 读失败 / 形态非法 ⇒ `null` = "没设定")。
|
|
130
|
+
*
|
|
131
|
+
* 🔴 **形态非法的文件按"没设定"处理,而不是报错**:解析侧出现在**每一次** `findSession` 里
|
|
132
|
+
* (show/use/archive/send-now/send-later 全走它),在那里抛错会让一个坏文件**炸掉整条轨迹的所有操作**
|
|
133
|
+
* —— 惩罚远大于问题本身。坏 UID 由 `setTrajectoryUid` 在**写入侧**拦住(那里才该响亮失败)。
|
|
134
|
+
*/
|
|
135
|
+
export declare function readTrajectoryUid(sessionPath: string): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* 设定 / 清除一条轨迹的 UID。
|
|
138
|
+
*
|
|
139
|
+
* @param sessionsDir AGENT_SESSIONS 绝对路径
|
|
140
|
+
* @param key 定位用(目录名 / S### / 既有 UID / 唯一模糊串)
|
|
141
|
+
* @param uid 新 UID;`null` ⇒ **删除声明文件**(回到"用编码")
|
|
142
|
+
* @throws UID 形态非法 / 已被**别的**轨迹占用 / 目标轨迹不存在
|
|
143
|
+
*/
|
|
144
|
+
export declare function setTrajectoryUid(sessionsDir: string, key: string, uid: string | null): {
|
|
145
|
+
dirName: string;
|
|
146
|
+
uid: string | null;
|
|
147
|
+
path: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* 根据 key 查找轨迹。
|
|
151
|
+
*
|
|
152
|
+
* 解析顺序(**越精确越靠前**,且**前三级都不做模糊**):
|
|
153
|
+
* ① 目录名精确 / ② **UID 精确** / ③ 编码 `S###`(允许 `S31`→`031`)/ ④ 唯一模糊子串(多个则报错)
|
|
154
|
+
*
|
|
155
|
+
* 🔴 **UID 排在编码之前**是因为 owner 定的语义:UID 是"单独设定"的**交流标识**
|
|
156
|
+
* ("不设定就还是用编码"⇒ 设定之后,用 UID 指认就该与编码同等可靠)。
|
|
157
|
+
* 它与目录名都属**精确匹配**、彼此不会混淆(UID 形态不含 `--`,目录名必含)⇒ 顺序对结果无实质影响,
|
|
158
|
+
* 但把这个顺序写死,避免后来者以为"UID 是模糊匹配的一种"。
|
|
97
159
|
*/
|
|
98
160
|
export declare function findSession(sessionsDir: string, key: string): SessionEntry | null;
|
|
99
161
|
/** list 子命令(对齐 osp listSessions 文本格式 + active 标记) */
|
|
@@ -227,6 +289,11 @@ export declare function summarize(root: string): string;
|
|
|
227
289
|
export interface TrajectorySummary {
|
|
228
290
|
dirName: string;
|
|
229
291
|
sessionId: string;
|
|
292
|
+
/**
|
|
293
|
+
* 唯一标识符(未设定 ⇒ `null`)。面板与 `list` 同源显示 ——
|
|
294
|
+
* 它是"用于交流"的标识,人类在面板上看到它才能直接拿去用。
|
|
295
|
+
*/
|
|
296
|
+
uid: string | null;
|
|
230
297
|
ageDays: number;
|
|
231
298
|
hasSessionMd: boolean;
|
|
232
299
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { c as hostInjected, d as hostSessions, f as hostSettings, h as hostWebServer, i as cccRootForExec, l as hostLlm, m as hostWeb, n as agentCwdFor, o as listCccs, p as hostSubagents, r as cccRootForCwd, s as hostAgents, t as NO_CCC_FROM_AGENT_CWD, u as hostService } from "./ccc-roots-RMzGU-CJ.js";
|
|
3
3
|
import { _ as resolveInside, a as findSerenityRoot$1, c as matchBlacklist, d as readCccName$1, f as readCheapWorkerConfig, h as readSafeModeHiddenTools, i as findGitRoot, l as pathInside, m as readExclusiveTools, n as SAFE_MODE_MARKER, o as isSafeModeOn, p as readCognitiveTrajectorySkills, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-C33QWoWZ.js";
|
|
4
|
-
import { A as
|
|
4
|
+
import { A as summarize, C as readTrajectoryUid, D as setActiveSessionInfo, E as sessionsRoot, F as localIdStamp, M as isoLocal, N as localFileStamp, O as setTrajectoryUid, P as localHuman, S as readActiveSessionMd, T as sessionEvents$1, _ as getActiveSessionInfo, a as pruneMissingBindings, b as namingTitleFor, c as setBindingStore, d as UID_FILENAME, f as archiveSession, g as findSession, h as extractSessionMdPathFromText, i as listBoundSessionIds, j as useSession, k as showSession, l as COGNITIVE_TRAJECTORY_ACTIONS, m as createSession, n as PLUGIN_SOURCE, o as readLastBound, p as clearActiveSessionInfo, r as appendBound, s as resolveSessionCognitiveTrajectoryLabel, u as DEFAULT_SESSION_SCOPE, v as listActiveTrajectories, w as resolveSessionByTitle, x as parseSessionContextFromEvents, y as listSessions } from "./message-source-Catc7q1z.js";
|
|
5
5
|
import { a as readGitTrack, c as unsetEntry, i as localstorePath, l as writeEntry, n as checkLocalstoreGitCompliance, o as readStore, r as getEntry, s as runLocalStore, t as LOCALSTORE_SCOPES } from "./localstore-ops-ylQoPz1Q.js";
|
|
6
6
|
import { a as FACE_PORTS, c as MECHANISM_PORTS, d as registerSkiffSession$1, f as skiffRoleFor$1, h as ACC_VERSION, i as ACP_PORT, l as SKIFF_DEBUG_PORT, m as unregisterSkiffSession$1, n as readSimpleSettings, o as GATEWAY_PORT, p as skiffSessionInfo$1, r as registerSettingsSection, s as MAIN_WEB_PORT, u as WEIXIN_SEND_PORT } from "./settings-section-B1YzjFz6.js";
|
|
7
7
|
import { a as isSkiffSessionId, c as resolveSkiffKind, f as systemPromptSource, i as createRolePromptReader, l as roleMsmWhitelist, n as buildSkiffBasePrompt, o as readSkiffRoles, r as cognitiveTrajectorySubset, s as resolveRoleSystemPrompt, u as roleToolWhitelist } from "./skiff-role-Boc4SKct.js";
|
|
@@ -12384,7 +12384,11 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
12384
12384
|
},
|
|
12385
12385
|
name: {
|
|
12386
12386
|
type: "string",
|
|
12387
|
-
description: "show/use
|
|
12387
|
+
description: "show/use/archive/uid identifier — S### / dir name / UID / fuzzy keyword"
|
|
12388
|
+
},
|
|
12389
|
+
uid: {
|
|
12390
|
+
type: "string",
|
|
12391
|
+
description: `uid: the trajectory's unique identifier (a separate, optional handle independent of its code and name — file ${UID_FILENAME} inside the trajectory dir). Omit to READ it; pass a value to SET it; pass "" to CLEAR it (falls back to the code). Allowed [A-Za-z0-9._-], ≤64, case-sensitive, must be unique across trajectories.`
|
|
12388
12392
|
},
|
|
12389
12393
|
note: {
|
|
12390
12394
|
type: "string",
|
|
@@ -12547,6 +12551,29 @@ function createCognitiveTrajectoryTool(ctx) {
|
|
|
12547
12551
|
note: res.dryRun ? "预览:未落盘。" : "已从活跃清单移出(目录与 SESSION.md 逐字节未改)。恢复方式:container_fs mv 移回 AGENT_SESSIONS/。"
|
|
12548
12552
|
};
|
|
12549
12553
|
}
|
|
12554
|
+
case "uid": {
|
|
12555
|
+
if (!args.name) throw new Error("uid requires name (S### / dir name / existing UID)");
|
|
12556
|
+
if (args.uid === void 0) {
|
|
12557
|
+
const entry = findSession(join(root, "AGENT_SESSIONS"), args.name);
|
|
12558
|
+
if (!entry) throw new Error(`Trajectory not found: "${args.name}"`);
|
|
12559
|
+
const cur = readTrajectoryUid(entry.path);
|
|
12560
|
+
return {
|
|
12561
|
+
ok: true,
|
|
12562
|
+
dirName: entry.dirName,
|
|
12563
|
+
uid: cur,
|
|
12564
|
+
note: cur === null ? `未设定 UID ⇒ 用编码指认(${entry.dirName.match(/--(S\d{3,})--/)?.[1] ?? "无编码"})。` : `当前 UID = ${cur}(文件 ${UID_FILENAME})`
|
|
12565
|
+
};
|
|
12566
|
+
}
|
|
12567
|
+
const next = args.uid === "" ? null : String(args.uid);
|
|
12568
|
+
const r = setTrajectoryUid(join(root, "AGENT_SESSIONS"), args.name, next);
|
|
12569
|
+
return {
|
|
12570
|
+
ok: true,
|
|
12571
|
+
dirName: r.dirName,
|
|
12572
|
+
uid: r.uid,
|
|
12573
|
+
message: r.uid === null ? `已清除 UID ⇒ "${r.dirName}" 回到用编码指认` : `已设定 UID = "${r.uid}" ⇒ 所有支持编码的动作(show/use/archive/send-now/send-later/uid)现在也接受它`,
|
|
12574
|
+
path: r.path
|
|
12575
|
+
};
|
|
12576
|
+
}
|
|
12550
12577
|
case "send-later": {
|
|
12551
12578
|
if (!args.target || !args.at || !args.message) throw new Error("send-later requires target (S### or dir name) + at (RFC3339 or +30m/+2h) + message");
|
|
12552
12579
|
const wakeScope = agentScope$1(exec);
|
|
@@ -14759,6 +14786,66 @@ function registerCompactRetention(ctx, opts = {}) {
|
|
|
14759
14786
|
});
|
|
14760
14787
|
}
|
|
14761
14788
|
//#endregion
|
|
14789
|
+
//#region src/skills/opencode-scan.ts
|
|
14790
|
+
/**
|
|
14791
|
+
* opencode-scan.ts — opencode skill 标准扫描(纯逻辑,零 DSH 依赖)
|
|
14792
|
+
*
|
|
14793
|
+
* 兼容 opencode 的 skill 标准:`.opencode/skills/<name>/SKILL.md`
|
|
14794
|
+
* (frontmatter: name/description/whenToUse;结构含 references/、scripts/)。
|
|
14795
|
+
*/
|
|
14796
|
+
/** 解析 --- 分隔的 YAML frontmatter(只需 name/description/whenToUse) */
|
|
14797
|
+
function parseFrontmatter(raw) {
|
|
14798
|
+
const text = raw.replace(/^\uFEFF/, "");
|
|
14799
|
+
const m = /^---\s*\n([\s\S]*?)\n---\s*\n?/.exec(text);
|
|
14800
|
+
if (!m) return {
|
|
14801
|
+
meta: {
|
|
14802
|
+
name: "",
|
|
14803
|
+
description: ""
|
|
14804
|
+
},
|
|
14805
|
+
content: text
|
|
14806
|
+
};
|
|
14807
|
+
const fm = m[1];
|
|
14808
|
+
const body = text.slice(m[0].length);
|
|
14809
|
+
const grab = (key) => {
|
|
14810
|
+
const line = fm.split("\n").find((l) => l.startsWith(`${key}:`));
|
|
14811
|
+
if (!line) return void 0;
|
|
14812
|
+
return line.slice(key.length + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
14813
|
+
};
|
|
14814
|
+
const meta = {
|
|
14815
|
+
name: grab("name") ?? "",
|
|
14816
|
+
description: grab("description") ?? ""
|
|
14817
|
+
};
|
|
14818
|
+
const whenToUse = grab("whenToUse");
|
|
14819
|
+
if (whenToUse) meta.whenToUse = whenToUse;
|
|
14820
|
+
return {
|
|
14821
|
+
meta,
|
|
14822
|
+
content: body
|
|
14823
|
+
};
|
|
14824
|
+
}
|
|
14825
|
+
/** 扫描 CCC 的 .opencode/skills(一级目录 + SKILL.md 存在性) */
|
|
14826
|
+
function listOpencodeSkillDirs(root) {
|
|
14827
|
+
const dir = join(root, ".opencode", "skills");
|
|
14828
|
+
if (!existsSync(dir)) return [];
|
|
14829
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(dir, e.name)).filter((d) => existsSync(join(d, "SKILL.md")));
|
|
14830
|
+
}
|
|
14831
|
+
/** 加载一个 opencode skill(frontmatter 解析 + 正文);读失败(EPERM/占用等)返回 null,单目录容错 */
|
|
14832
|
+
function loadOpencodeSkill(skillDir) {
|
|
14833
|
+
let raw;
|
|
14834
|
+
try {
|
|
14835
|
+
raw = readFileSync(join(skillDir, "SKILL.md"), "utf-8");
|
|
14836
|
+
} catch {
|
|
14837
|
+
return null;
|
|
14838
|
+
}
|
|
14839
|
+
const { meta, content } = parseFrontmatter(raw);
|
|
14840
|
+
return {
|
|
14841
|
+
name: meta.name,
|
|
14842
|
+
dir: skillDir,
|
|
14843
|
+
meta,
|
|
14844
|
+
content,
|
|
14845
|
+
path: join(skillDir, "SKILL.md")
|
|
14846
|
+
};
|
|
14847
|
+
}
|
|
14848
|
+
//#endregion
|
|
14762
14849
|
//#region src/status.ts
|
|
14763
14850
|
/**
|
|
14764
14851
|
* status.ts — 状态与安全模式操作(纯逻辑,零 DSH 依赖,可独立单测)
|
|
@@ -14850,6 +14937,7 @@ function getStatus(cwd, configPaths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
14850
14937
|
threshold: null,
|
|
14851
14938
|
cheapWorkerModel: null,
|
|
14852
14939
|
trajectories: [],
|
|
14940
|
+
skills: [],
|
|
14853
14941
|
restrict
|
|
14854
14942
|
};
|
|
14855
14943
|
const cfg = loadSerenityConfig(root, configPaths);
|
|
@@ -14861,9 +14949,31 @@ function getStatus(cwd, configPaths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
14861
14949
|
threshold: cfg.sessionKeeper?.threshold ?? null,
|
|
14862
14950
|
cheapWorkerModel: readCheapWorkerConfig(root, configPaths)?.defaultModel ?? null,
|
|
14863
14951
|
trajectories: listActiveTrajectories(root),
|
|
14952
|
+
skills: listSkillSummaries(root),
|
|
14864
14953
|
restrict
|
|
14865
14954
|
};
|
|
14866
14955
|
}
|
|
14956
|
+
/**
|
|
14957
|
+
* 技能清单(面板用;owner 2026-10-18 要求 skills 单独占一个 panel)。
|
|
14958
|
+
*
|
|
14959
|
+
* 只取**两个可文件系统验证的事实**:名字(目录名)+ 是否已建模(`skill.ttl` 存在)。
|
|
14960
|
+
* 为什么"是否建模"值得进面板:它是 skill 形态的**唯一二值区别**(有 `skill.ttl` = 增强型,
|
|
14961
|
+
* 无 = 普通 skill),而后者**完全正常** —— 所以面板把它呈现为**信息**,不是缺陷标记。
|
|
14962
|
+
*
|
|
14963
|
+
* ⚠️ 单目录读失败**不抛**(`loadOpencodeSkill` 返回 null 时照旧收入名字):
|
|
14964
|
+
* 面板是只读状态面,一个坏目录不该让整块面板消失。
|
|
14965
|
+
*/
|
|
14966
|
+
function listSkillSummaries(root) {
|
|
14967
|
+
const out = [];
|
|
14968
|
+
for (const dir of listOpencodeSkillDirs(root)) {
|
|
14969
|
+
const name = basename(dir);
|
|
14970
|
+
out.push({
|
|
14971
|
+
name,
|
|
14972
|
+
modeled: existsSync(join(dir, "skill.ttl"))
|
|
14973
|
+
});
|
|
14974
|
+
}
|
|
14975
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
14976
|
+
}
|
|
14867
14977
|
/** 切换安全模式(写/删标记文件);返回实际生效状态 */
|
|
14868
14978
|
function setSafeMode(root, on) {
|
|
14869
14979
|
const marker = resolve(root, SAFE_MODE_MARKER);
|
|
@@ -15629,66 +15739,6 @@ function registerEnv(ctx) {
|
|
|
15629
15739
|
});
|
|
15630
15740
|
}
|
|
15631
15741
|
//#endregion
|
|
15632
|
-
//#region src/skills/opencode-scan.ts
|
|
15633
|
-
/**
|
|
15634
|
-
* opencode-scan.ts — opencode skill 标准扫描(纯逻辑,零 DSH 依赖)
|
|
15635
|
-
*
|
|
15636
|
-
* 兼容 opencode 的 skill 标准:`.opencode/skills/<name>/SKILL.md`
|
|
15637
|
-
* (frontmatter: name/description/whenToUse;结构含 references/、scripts/)。
|
|
15638
|
-
*/
|
|
15639
|
-
/** 解析 --- 分隔的 YAML frontmatter(只需 name/description/whenToUse) */
|
|
15640
|
-
function parseFrontmatter(raw) {
|
|
15641
|
-
const text = raw.replace(/^\uFEFF/, "");
|
|
15642
|
-
const m = /^---\s*\n([\s\S]*?)\n---\s*\n?/.exec(text);
|
|
15643
|
-
if (!m) return {
|
|
15644
|
-
meta: {
|
|
15645
|
-
name: "",
|
|
15646
|
-
description: ""
|
|
15647
|
-
},
|
|
15648
|
-
content: text
|
|
15649
|
-
};
|
|
15650
|
-
const fm = m[1];
|
|
15651
|
-
const body = text.slice(m[0].length);
|
|
15652
|
-
const grab = (key) => {
|
|
15653
|
-
const line = fm.split("\n").find((l) => l.startsWith(`${key}:`));
|
|
15654
|
-
if (!line) return void 0;
|
|
15655
|
-
return line.slice(key.length + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
15656
|
-
};
|
|
15657
|
-
const meta = {
|
|
15658
|
-
name: grab("name") ?? "",
|
|
15659
|
-
description: grab("description") ?? ""
|
|
15660
|
-
};
|
|
15661
|
-
const whenToUse = grab("whenToUse");
|
|
15662
|
-
if (whenToUse) meta.whenToUse = whenToUse;
|
|
15663
|
-
return {
|
|
15664
|
-
meta,
|
|
15665
|
-
content: body
|
|
15666
|
-
};
|
|
15667
|
-
}
|
|
15668
|
-
/** 扫描 CCC 的 .opencode/skills(一级目录 + SKILL.md 存在性) */
|
|
15669
|
-
function listOpencodeSkillDirs(root) {
|
|
15670
|
-
const dir = join(root, ".opencode", "skills");
|
|
15671
|
-
if (!existsSync(dir)) return [];
|
|
15672
|
-
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(dir, e.name)).filter((d) => existsSync(join(d, "SKILL.md")));
|
|
15673
|
-
}
|
|
15674
|
-
/** 加载一个 opencode skill(frontmatter 解析 + 正文);读失败(EPERM/占用等)返回 null,单目录容错 */
|
|
15675
|
-
function loadOpencodeSkill(skillDir) {
|
|
15676
|
-
let raw;
|
|
15677
|
-
try {
|
|
15678
|
-
raw = readFileSync(join(skillDir, "SKILL.md"), "utf-8");
|
|
15679
|
-
} catch {
|
|
15680
|
-
return null;
|
|
15681
|
-
}
|
|
15682
|
-
const { meta, content } = parseFrontmatter(raw);
|
|
15683
|
-
return {
|
|
15684
|
-
name: meta.name,
|
|
15685
|
-
dir: skillDir,
|
|
15686
|
-
meta,
|
|
15687
|
-
content,
|
|
15688
|
-
path: join(skillDir, "SKILL.md")
|
|
15689
|
-
};
|
|
15690
|
-
}
|
|
15691
|
-
//#endregion
|
|
15692
15742
|
//#region src/seams/opencode-skills.ts
|
|
15693
15743
|
const OPENCODE_PROVIDER = "opencode-skills";
|
|
15694
15744
|
function registerOpencodeSkills(ctx) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as cccRootForCwd } from "./ccc-roots-RMzGU-CJ.js";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
//#region src/time.ts
|
|
5
5
|
/**
|
|
6
6
|
* time.ts — ACC **时间呈现的单一真相源**(S142 2026-09-17,所有者令:「统一 ACC 所有使用时间的地方使用当地时区」)
|
|
@@ -131,6 +131,7 @@ const COGNITIVE_TRAJECTORY_ACTIONS = [
|
|
|
131
131
|
"use",
|
|
132
132
|
"rebuild",
|
|
133
133
|
"archive",
|
|
134
|
+
"uid",
|
|
134
135
|
"send-now",
|
|
135
136
|
"send-later",
|
|
136
137
|
"cro-guide"
|
|
@@ -217,13 +218,105 @@ function extractSessionId(dirName) {
|
|
|
217
218
|
return m ? `S${m[1]}` : "";
|
|
218
219
|
}
|
|
219
220
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
221
|
+
* UID 的声明文件名(**固定名、住在轨迹目录内**)。
|
|
222
|
+
*
|
|
223
|
+
* 🔴 owner 2026-10-18 需求原话:*"认知轨迹编号和命名问题,命名很可能由于变化而变化,
|
|
224
|
+
* 所以我们需要让认知轨迹支持一种用于交流的唯一标识符,它不同于编码和命名,而是单独设定的
|
|
225
|
+
* (也可以不设定,不设定就还是用编码),所有可以用编码操作的能力,都同样支持唯一标识符,
|
|
226
|
+
* 唯一标识符用文件形式声明在认知轨迹目录中"*。
|
|
227
|
+
*
|
|
228
|
+
* ## 为什么是「一个文件」而不是 frontmatter 里一行
|
|
229
|
+
* | 形 | 问题 |
|
|
230
|
+
* |---|---|
|
|
231
|
+
* | 写进 `SESSION.md` frontmatter | SESSION.md 是**轨迹的身体**(会被重写、被 rebuild 锚点反复读)⇒ 标识符住在会变的东西里 |
|
|
232
|
+
* | 写进目录名 | 改名即变 —— 而这正是要解决的问题(owner:命名会变) |
|
|
233
|
+
* | **独立小文件**(选定) | 与目录名、SESSION.md **都解耦**;改标识符不动正文、不动目录名;删文件即"回到用编码" |
|
|
234
|
+
*
|
|
235
|
+
* ## 语义边界(R↓)
|
|
236
|
+
* - **可选**:文件不存在 ⇒ 用编码(`S###`),**不是错误**。
|
|
237
|
+
* - **只用于"被指认"**:它是 `findSession` 的一个**解析分支**,不参与排序、不参与展示优先级、
|
|
238
|
+
* **不改变**目录名与 SESSION.md 一个字节(与 `archive` 同一条纪律:只换指认方式,不动本体)。
|
|
239
|
+
* - **唯一性**:由 `setTrajectoryUid` 在写入时保证(重复即拒绝)。读取侧不做全表唯一性断言 ——
|
|
240
|
+
* 外部手工造的重复应由写入侧拦,读取侧只能"第一个命中",那是注入侧的职责不是解析侧的。
|
|
241
|
+
*/
|
|
242
|
+
const UID_FILENAME = "TRAJECTORY-UID";
|
|
243
|
+
/** UID 合法形态:**保守**(只允许交流安全的字符)—— 它会被用在命令行、消息文本、日志里 */
|
|
244
|
+
const UID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
245
|
+
/**
|
|
246
|
+
* 校验 UID 形态。
|
|
247
|
+
*
|
|
248
|
+
* 为什么保守(R↓):UID 是**给人和 agent 在对话里互相指认用的**(owner:「用于交流」)⇒
|
|
249
|
+
* 它必须能**逐字打出来**。含空格/引号/中文的 UID 会在 shell、JSON、markdown 里各自需要一套转义
|
|
250
|
+
* ⇒ "用于交流"这个目的当场失败。⇒ 限定 `[A-Za-z0-9._-]`,首字符必须字母数字,上限 64。
|
|
251
|
+
*
|
|
252
|
+
* ⚠️ 大小写**敏感**:`S142` 与 `s142` 是两个不同 UID(判据:唯一标识符不是给人近似匹配用的;
|
|
253
|
+
* 要模糊匹配请用目录名/编码那条既有分支)。
|
|
254
|
+
*/
|
|
255
|
+
function isValidUid(uid) {
|
|
256
|
+
return UID_PATTERN.test(uid);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 读一条轨迹的 UID(文件不存在 / 读失败 / 形态非法 ⇒ `null` = "没设定")。
|
|
260
|
+
*
|
|
261
|
+
* 🔴 **形态非法的文件按"没设定"处理,而不是报错**:解析侧出现在**每一次** `findSession` 里
|
|
262
|
+
* (show/use/archive/send-now/send-later 全走它),在那里抛错会让一个坏文件**炸掉整条轨迹的所有操作**
|
|
263
|
+
* —— 惩罚远大于问题本身。坏 UID 由 `setTrajectoryUid` 在**写入侧**拦住(那里才该响亮失败)。
|
|
264
|
+
*/
|
|
265
|
+
function readTrajectoryUid(sessionPath) {
|
|
266
|
+
try {
|
|
267
|
+
const raw = readFileSync(join(sessionPath, UID_FILENAME), "utf-8").trim();
|
|
268
|
+
return isValidUid(raw) ? raw : null;
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 设定 / 清除一条轨迹的 UID。
|
|
275
|
+
*
|
|
276
|
+
* @param sessionsDir AGENT_SESSIONS 绝对路径
|
|
277
|
+
* @param key 定位用(目录名 / S### / 既有 UID / 唯一模糊串)
|
|
278
|
+
* @param uid 新 UID;`null` ⇒ **删除声明文件**(回到"用编码")
|
|
279
|
+
* @throws UID 形态非法 / 已被**别的**轨迹占用 / 目标轨迹不存在
|
|
280
|
+
*/
|
|
281
|
+
function setTrajectoryUid(sessionsDir, key, uid) {
|
|
282
|
+
const target = findSession(sessionsDir, key);
|
|
283
|
+
if (!target) throw new Error(`Trajectory not found: "${key}". Use "list" to see available trajectories.`);
|
|
284
|
+
const file = join(target.path, UID_FILENAME);
|
|
285
|
+
if (uid === null) {
|
|
286
|
+
rmSync(file, { force: true });
|
|
287
|
+
return {
|
|
288
|
+
dirName: target.dirName,
|
|
289
|
+
uid: null,
|
|
290
|
+
path: file
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (!isValidUid(uid)) throw new Error(`Invalid trajectory UID: "${uid}". 允许 [A-Za-z0-9._-],首字符须字母或数字,长度 ≤64(它要被逐字打出来用于交流 ⇒ 不容忍需要转义的字符)`);
|
|
294
|
+
const clash = readAllSessions(sessionsDir).find((s) => s.dirName !== target.dirName && readTrajectoryUid(s.path) === uid);
|
|
295
|
+
if (clash) throw new Error(`UID "${uid}" 已被 ${clash.dirName} 占用(唯一标识符必须唯一;先解除它或换一个)`);
|
|
296
|
+
writeFileSync(file, uid + "\n", "utf-8");
|
|
297
|
+
return {
|
|
298
|
+
dirName: target.dirName,
|
|
299
|
+
uid,
|
|
300
|
+
path: file
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 根据 key 查找轨迹。
|
|
305
|
+
*
|
|
306
|
+
* 解析顺序(**越精确越靠前**,且**前三级都不做模糊**):
|
|
307
|
+
* ① 目录名精确 / ② **UID 精确** / ③ 编码 `S###`(允许 `S31`→`031`)/ ④ 唯一模糊子串(多个则报错)
|
|
308
|
+
*
|
|
309
|
+
* 🔴 **UID 排在编码之前**是因为 owner 定的语义:UID 是"单独设定"的**交流标识**
|
|
310
|
+
* ("不设定就还是用编码"⇒ 设定之后,用 UID 指认就该与编码同等可靠)。
|
|
311
|
+
* 它与目录名都属**精确匹配**、彼此不会混淆(UID 形态不含 `--`,目录名必含)⇒ 顺序对结果无实质影响,
|
|
312
|
+
* 但把这个顺序写死,避免后来者以为"UID 是模糊匹配的一种"。
|
|
222
313
|
*/
|
|
223
314
|
function findSession(sessionsDir, key) {
|
|
224
315
|
const all = readAllSessions(sessionsDir);
|
|
225
316
|
const byName = all.find((s) => s.dirName === key);
|
|
226
317
|
if (byName) return byName;
|
|
318
|
+
const byUid = all.find((s) => readTrajectoryUid(s.path) === key);
|
|
319
|
+
if (byUid) return byUid;
|
|
227
320
|
const searchId = key.replace(/^S/, "").padStart(3, "0");
|
|
228
321
|
const byId = all.find((s) => {
|
|
229
322
|
const m = s.dirName.match(/--S(\d{3,})--/);
|
|
@@ -243,7 +336,10 @@ function listSessions(root, activeId) {
|
|
|
243
336
|
const lines = sessions.map((s) => {
|
|
244
337
|
const age = Math.floor((Date.now() - s.mtime.getTime()) / DAY);
|
|
245
338
|
const sessionId = extractSessionId(s.dirName);
|
|
246
|
-
|
|
339
|
+
const status = activeId !== void 0 && sessionId !== "" && sessionId === activeId ? "●" : "○";
|
|
340
|
+
const uid = readTrajectoryUid(s.path);
|
|
341
|
+
const uidTag = uid === null ? "" : ` [${uid}]`;
|
|
342
|
+
return `${status} ${s.dirName} (${age}d ago)${uidTag}`;
|
|
247
343
|
});
|
|
248
344
|
return `AGENT_SESSIONS/ (${sessions.length} active)\n` + lines.join("\n");
|
|
249
345
|
}
|
|
@@ -571,6 +667,7 @@ function listActiveTrajectories(root) {
|
|
|
571
667
|
return readAllSessions(sessionsRoot(root)).map((s) => ({
|
|
572
668
|
dirName: s.dirName,
|
|
573
669
|
sessionId: extractSessionId(s.dirName),
|
|
670
|
+
uid: readTrajectoryUid(s.path),
|
|
574
671
|
ageDays: Math.floor((now - s.mtime.getTime()) / DAY),
|
|
575
672
|
hasSessionMd: s.status.hasSessionMd
|
|
576
673
|
}));
|
|
@@ -1049,4 +1146,4 @@ const ACC_MESSAGE_KIND = "plugin:dsh-serenity-hooks";
|
|
|
1049
1146
|
/** ACC 注入消息的统一来源值(各缝共用;需要 `form` 时按 `ContextFormed` 另加字段)。 */
|
|
1050
1147
|
const PLUGIN_SOURCE = { kind: ACC_MESSAGE_KIND };
|
|
1051
1148
|
//#endregion
|
|
1052
|
-
export {
|
|
1149
|
+
export { summarize as A, readTrajectoryUid as C, setActiveSessionInfo as D, sessionsRoot as E, localIdStamp as F, isoLocal as M, localFileStamp as N, setTrajectoryUid as O, localHuman as P, readActiveSessionMd as S, sessionEvents as T, getActiveSessionInfo as _, pruneMissingBindings as a, namingTitleFor as b, setBindingStore as c, UID_FILENAME as d, archiveSession as f, findSession as g, extractSessionMdPathFromText as h, listBoundSessionIds as i, useSession as j, showSession as k, COGNITIVE_TRAJECTORY_ACTIONS as l, createSession as m, PLUGIN_SOURCE as n, readLastBound as o, clearActiveSessionInfo as p, appendBound as r, resolveSessionCognitiveTrajectoryLabel as s, ACC_MESSAGE_KIND as t, DEFAULT_SESSION_SCOPE as u, listActiveTrajectories as v, resolveSessionByTitle as w, parseSessionContextFromEvents as x, listSessions as y };
|
package/lib/status.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ export declare function dshVersionFromEntry(entry: string | undefined): string |
|
|
|
38
38
|
* 那时"本机装了哪个版本"仍需一个答案,只是证据等级更低。
|
|
39
39
|
*/
|
|
40
40
|
export declare function readDshVersion(): string | null;
|
|
41
|
+
/** 面板用的技能摘要(owner 2026-10-18:skills 单独占一个 panel) */
|
|
42
|
+
interface SkillSummary {
|
|
43
|
+
name: string;
|
|
44
|
+
/** 是否已建模(同目录存在 `skill.ttl`)。**无 skill.ttl 完全正常**,不是缺陷。 */
|
|
45
|
+
modeled: boolean;
|
|
46
|
+
}
|
|
41
47
|
interface SerenityStatus {
|
|
42
48
|
root: string | null;
|
|
43
49
|
accVersion: string;
|
|
@@ -58,6 +64,14 @@ interface SerenityStatus {
|
|
|
58
64
|
* 无 CCC 根时为 `[]`。
|
|
59
65
|
*/
|
|
60
66
|
trajectories: TrajectorySummary[];
|
|
67
|
+
/**
|
|
68
|
+
* 技能清单(owner 2026-10-18:「活跃轨迹、skills 分别占两个 panel」)。
|
|
69
|
+
* 只给**面板要展示的最小事实**(名字 + 是否已建模 `skill.ttl`),**不搬 description**:
|
|
70
|
+
* 面板是**状态信息**,不是技能说明书;描述在 SKILL.md 里,搬一份即第二真相源。
|
|
71
|
+
* ⚠️ 数据源 = `listOpencodeSkillDirs`(全量扫描 `.opencode/skills/` 下每个含 SKILL.md 的目录)。
|
|
72
|
+
* ⛔ **不能用 `findEntrySkills`** —— 那只是入口 skill(`*-serenity`),不是全量。
|
|
73
|
+
*/
|
|
74
|
+
skills: SkillSummary[];
|
|
61
75
|
restrict: {
|
|
62
76
|
lastKey: string | null;
|
|
63
77
|
lastAttemptAt: string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.68.0",
|
|
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 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|