@shgroup/dsh-serenity-hooks 1.103.0 → 1.105.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.
Files changed (33) hide show
  1. package/README.md +1 -2
  2. package/dsh.plugin.json +2 -3
  3. package/lib/{ccc-CyYJL4F2.js → ccc-B-h_A7uc.js} +17 -36
  4. package/lib/{ccc-roots-WfktvbN8.js → ccc-roots-BPicjCsj.js} +19 -11
  5. package/lib/{channels-CuCsUfcY.js → channels-bAiZlY07.js} +2 -2
  6. package/lib/client.js +1 -6
  7. package/lib/cro/guide.d.ts +1 -1
  8. package/lib/guard/seam.d.ts +2 -1
  9. package/lib/host/access.d.ts +37 -39
  10. package/lib/host/contract.d.ts +3 -2
  11. package/lib/index.js +64 -375
  12. package/lib/invariant.js +0 -1
  13. package/lib/kernel/ccc-roots.d.ts +4 -3
  14. package/lib/kernel/ccc.d.ts +12 -30
  15. package/lib/kernel/invariant.d.ts +1 -1
  16. package/lib/{message-source-BAxZk9Dn.js → message-source-DoVtXnoe.js} +3 -3
  17. package/lib/{profile-zxakCNXy.js → profile-Dpg3wYO5.js} +1 -1
  18. package/lib/{route-DxIKWeN9.js → route-CmEoKp9w.js} +2 -2
  19. package/lib/seams/context.d.ts +5 -2
  20. package/lib/{service-DDmHSglV.js → service-Cndtf7q4.js} +6 -6
  21. package/lib/{session-DWmIqApm.js → session-CYqyod3z.js} +73 -140
  22. package/lib/surface/status.d.ts +1 -2
  23. package/lib/tools/localstore/service.d.ts +1 -1
  24. package/lib/tools/msm/service.d.ts +2 -2
  25. package/lib/tools/trajectory-constraint-admin.d.ts +1 -1
  26. package/lib/trajectory/carrier.d.ts +5 -4
  27. package/lib/trajectory/model-runtime.d.ts +5 -3
  28. package/lib/trajectory/model-string.d.ts +26 -0
  29. package/lib/trajectory/policy/profile.d.ts +1 -1
  30. package/package.json +2 -2
  31. package/lib/tools/cheap-worker.d.ts +0 -21
  32. package/lib/trajectory/delegate/core.d.ts +0 -55
  33. package/lib/trajectory/delegate/preset.d.ts +0 -42
package/README.md CHANGED
@@ -60,7 +60,7 @@ my-workspace/
60
60
 
61
61
  ## 3 装完多了什么
62
62
 
63
- ### 3.1 十四个工具(其中三个按条件出现)
63
+ ### 3.1 十三个工具(其中三个按条件出现)
64
64
 
65
65
  > 这份清单**只在这里**(用户视角:能做什么)。**契约语义**(每个动作必须怎样)在标准的 §4,本文不复制。
66
66
 
@@ -74,7 +74,6 @@ my-workspace/
74
74
  | `praxis` | 按需注入四套方法:`eap`(把话说清)/ `neat`(先对齐再动手)/ `cce`(连续性)/ `ontology`(把领域结构显式化) |
75
75
  | `ontology` | 本体工程**引擎**:读 `.ttl`,做结构校验与语义审查、写模板。与 `praxis ontology` 一个说、一个跑 |
76
76
  | `serenity-skill-creator` | 增强型技能的创建器:`guide` / `create` / `check` / `review` / `index` / `status` |
77
- | `cheap-worker` | 杂工:把任务交给低成本模型的子代理并取回最终文本;`jobs` 一次扇出(上限 10) |
78
77
  | `localstore` | 凭据与本地偏好:工作区根的 `localstore.json`,默认拒绝入库 |
79
78
  | `container_admin` | 机务舱:`config`(说明目录 / 轨迹约束 / 当前配置)+ `msm`(注册表管理) |
80
79
  | `im-bridge` ● | 给联系人发消息(微信 / 飞书):文本、文件、联系人、通道状态 |
package/dsh.plugin.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.103.0",
3
+ "version": "1.105.0",
4
4
  "main": "lib/index.js",
5
- "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
5
+ "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——13 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
7
7
  "dsh": ">=0.1.7-rc.1"
8
8
  },
@@ -14,7 +14,6 @@
14
14
  "container_git",
15
15
  "msm",
16
16
  "praxis",
17
- "cheap-worker",
18
17
  "localstore",
19
18
  "container_admin",
20
19
  "im-bridge",
@@ -91,40 +91,21 @@ function readCccName(root) {
91
91
  * 而新模型里 轨迹约束恒等于一条轨迹 ⇒ 恒有 SESSION.md ⇒ 该区分没有指称对象(静默忽略)。
92
92
  */
93
93
  /**
94
- * 解析 cheap-worker 配置。
95
- *
96
- * 🔴 **键的优先级(2026-10-14,owner 令「配置兼容旧配置 key」)**:
97
- * ① 新键 `cheap-worker`;② **读不到(键缺失或 models 非有效数组)⇒ 回落旧键 `handyman`**。
98
- * 「读不到」的判据是**同一套**(models 必须是至少含一个非空字符串的数组)—— 这样"新键存在但写错了"
99
- * 与"没写新键"行为一致,都不会静默走一个空白名单。
100
- *
101
- * ⚠️ `maxRounds`/`maxParallel` 已随 background 模式退场,**不再被本函数读取**(旧配置里留着无害)。
102
- *
103
- * @returns 白名单 + 缺省模型;两处都取不到 models 时返回 null(工具应报错要求配置)
94
+ * 🔴 **`readCheapWorkerConfig` / `pickModelSection` 已于 2026-10-28 删除**
95
+ * (owner 令「下线 cheap-worker,完全删掉」)。
96
+ *
97
+ * 它们原本有三个消费者,三个都已同步处置:
98
+ * | 消费者 | 处置 |
99
+ * |---|---|
100
+ * | `tools/cheap-worker.ts`(模型白名单校验) | 文件整体删除 |
101
+ * | `surface/status.ts` 的 `cheapWorkerModel`(面板回显) | 字段删除(面板同步) |
102
+ * | `tools/trajectory-constraint-admin.ts` 的 `model ∈ cheap-worker.models` 校验 | **校验整条移除** |
103
+ *
104
+ * ⚠️ 最后一条要说清(R↓):那条校验原本是「轨迹约束声明的 model 必须在 CCC 白名单里」。
105
+ * 白名单随 cheap-worker 一起消失 ⇒ **它失去了判据来源**。而模型合法性本来就有
106
+ * **更权威的判据**(`trajectory/model-runtime.ts`:宿主报非法模型 ⇒ 不切+响亮告警)
107
+ * ⇒ 删掉它**不是丢了一个保护,而是去掉一份与权威判据并行的第二真相源**(D131 同族)。
104
108
  */
105
- function readCheapWorkerConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
106
- const cfg = loadSerenityConfig(root, paths);
107
- const section = pickModelSection(cfg["cheap-worker"]) ?? pickModelSection(cfg.handyman) ?? null;
108
- if (section === null) return null;
109
- const clean = section.models.map((m) => m.trim()).filter(Boolean);
110
- if (clean.length === 0) return null;
111
- const defaultModel = section.defaultModel?.trim();
112
- return {
113
- models: clean,
114
- defaultModel: defaultModel !== void 0 && clean.includes(defaultModel) ? defaultModel : clean[0]
115
- };
116
- }
117
- /**
118
- * 从一段配置里取出可用的模型白名单段;不满足「models = 至少一个非空字符串的数组」⇒ `null`。
119
- * 抽成函数是为了让**新键与旧键走同一套判据**(不同判据会让"回落"产生两类行为差异)。
120
- */
121
- function pickModelSection(section) {
122
- if (section === void 0 || section === null) return null;
123
- const models = section.models;
124
- if (!Array.isArray(models) || models.length === 0) return null;
125
- if (models.every((m) => typeof m !== "string" || m.trim() === "")) return null;
126
- return section;
127
- }
128
109
  /**
129
110
  * ACC 依赖的 CCC 配置路径(S134 修正:兼容历史——历史在 .opencode,规范位置 = .opencode;
130
111
  * .dsh 仅作 dsh 运行时回退,不优先)。
@@ -139,7 +120,7 @@ function readUtf8(path) {
139
120
  }
140
121
  /**
141
122
  * 已告警过的损坏配置路径(进程级去重:同一坏文件不刷屏,但绝不静默)。
142
- * S142 review F-07:此前损坏 → 静默返回 `{}`——安全模式黑名单/cheap-worker 白名单/轨迹约束角色
123
+ * S142 review F-07:此前损坏 → 静默返回 `{}`——安全模式黑名单/轨迹约束角色
143
124
  * 全部**无声失效**(用户以为配了,实际没配)。守卫的输入失败必须响亮。
144
125
  */
145
126
  const warnedBrokenConfigs = /* @__PURE__ */ new Set();
@@ -163,7 +144,7 @@ function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
163
144
  } catch (err) {
164
145
  if (!warnedBrokenConfigs.has(p)) {
165
146
  warnedBrokenConfigs.add(p);
166
- console.warn(`[serenity-hooks] ✗ CCC 配置解析失败,已按空配置继续(该 CCC 的安全模式黑名单/cheap-worker 白名单/轨迹约束角色等全部失效,请修 JSON): ${p} — ${String(err?.message ?? err)}`);
147
+ console.warn(`[serenity-hooks] ✗ CCC 配置解析失败,已按空配置继续(该 CCC 的安全模式黑名单/轨迹约束角色等全部失效,请修 JSON): ${p} — ${String(err?.message ?? err)}`);
167
148
  }
168
149
  return {};
169
150
  }
@@ -270,4 +251,4 @@ function matchBlacklist(relPath, rules) {
270
251
  return null;
271
252
  }
272
253
  //#endregion
273
- export { resolveInside as _, findSerenityRoot as a, matchBlacklist as c, readCccName as d, readCheapWorkerConfig as f, readUtf8 as g, readSafeModeHiddenTools as h, findGitRoot as i, pathInside as l, readExclusiveTools as m, SAFE_MODE_MARKER as n, isSafeModeOn as o, readCognitiveTrajectorySkills as p, classifyPath as r, loadSerenityConfig as s, DEFAULT_SERENITY_CONFIG_PATHS as t, readBlacklist as u };
254
+ export { findSerenityRoot as a, matchBlacklist as c, readCccName as d, readCognitiveTrajectorySkills as f, resolveInside as g, readUtf8 as h, findGitRoot as i, pathInside as l, readSafeModeHiddenTools as m, SAFE_MODE_MARKER as n, isSafeModeOn as o, readExclusiveTools as p, classifyPath as r, loadSerenityConfig as s, DEFAULT_SERENITY_CONFIG_PATHS as t, readBlacklist as u };
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
2
- import { a as findSerenityRoot } from "./ccc-CyYJL4F2.js";
2
+ import { a as findSerenityRoot } from "./ccc-B-h_A7uc.js";
3
3
  import { basename } from "node:path";
4
4
  //#region src/host/access.ts
5
5
  /**
@@ -38,6 +38,7 @@ function hostService(ctx, name) {
38
38
  * 于是"先直接属性读、失败回落 ctx.get"的写法在**真实 cordis** 下永远走不到回落分支——
39
39
  * 异常直接逃逸(`hostService` 的 try/catch 被绕过)。v1.31.3 的 `hostSubagents`
40
40
  * 正是这样炸的:`subagents` 是 lazy 服务(不在插件 `inject` 列表内)。
41
+ * (该函数已于 2026-10-28 随 cheap-worker 删除;这段教训仍是本模块的**存在理由**。)
41
42
  * 现有 fake ctx 单测(普通对象无 getter)复现不了 → 真 cordis 用例见
42
43
  * `tests/host/cordis-access.test.ts`。
43
44
  *
@@ -72,11 +73,17 @@ function hostSettings(ctx) {
72
73
  function hostWeb(ctx) {
73
74
  return hostInjected(ctx, "web");
74
75
  }
75
- function hostSubagents(ctx) {
76
- return hostInjected(ctx, "subagents");
77
- }
78
- function hostLlm(ctx) {
79
- return hostInjected(ctx, "llm");
76
+ /**
77
+ * 宿主默认模型选择(**没显式指定模型时**该用的那个)。
78
+ *
79
+ * 🔴 为什么需要这个读取器(v1.105.0,P0 回归修复):
80
+ * `agents.create` **不传** `agentOptions` ⇒ `agent.options.model === undefined`
81
+ * ⇒ 宿主的 persona 模板 `powered by the {{model}} model` **严格插值抛错** ⇒ 整轮失败。
82
+ * ⇒ 「不传」不等于「安全」:**下游有依赖方要求这个值必须存在**。
83
+ * 详见 `trajectory/session.ts` 的 `resolveAgentModel`。
84
+ */
85
+ function hostAgentDefaultModel(ctx) {
86
+ return hostInjected(ctx, "agentDefaultModel");
80
87
  }
81
88
  //#endregion
82
89
  //#region src/kernel/ccc-roots.ts
@@ -123,9 +130,10 @@ var ccc_roots_exports = /* @__PURE__ */ __exportAll({
123
130
  * `exec.agent?.session?.header?.cwd ?? process.cwd()`。
124
131
  *
125
132
  * ⚠️ 无 `exec.agent` 时回落进程 cwd —— "用服务启动目录冒充会话目录"是**既有语义**,
126
- * 不是本模块的选择(改它会动到 9 个工具的根解析行为)。已知副作用与规避见
127
- * `tools/cheap-worker.ts` 的「调用形态检查前移」补偿(`?? process.cwd()` 会把真因
128
- * "exec.agent 不存在"盖成 `No CCC found`)。
133
+ * 不是本模块的选择(改它会动到所有工具的根解析行为)。已知副作用:`?? process.cwd()`
134
+ * 会把真因"exec.agent 不存在"盖成 `No CCC found` —— 工具侧须把**调用形态检查前移**再判根
135
+ * (原实现范例 `tools/cheap-worker.ts`,该文件已于 2026-10-28 随工具下线删除;
136
+ * 现行的正例见 `tools/fs/service.ts` 的入参校验顺序)。
129
137
  */
130
138
  function agentCwdFor(execOrAgent) {
131
139
  return execOrAgent.agent?.session?.header?.cwd ?? execOrAgent.session?.header?.cwd ?? process.cwd();
@@ -227,7 +235,7 @@ async function listCccs(ctx, opts = {}) {
227
235
  if (typeof defaultRoot === "string" && defaultRoot !== "" && !c.roots.includes(defaultRoot)) c.roots.unshift(defaultRoot);
228
236
  let rolesOf = null;
229
237
  if (opts.withRoles === true) {
230
- const { listTrajectoryConstraints } = await import("./profile-zxakCNXy.js").then((n) => n.a);
238
+ const { listTrajectoryConstraints } = await import("./profile-Dpg3wYO5.js").then((n) => n.a);
231
239
  rolesOf = (root) => {
232
240
  try {
233
241
  return listTrajectoryConstraints(root).map((p) => p.code);
@@ -243,4 +251,4 @@ async function listCccs(ctx, opts = {}) {
243
251
  }));
244
252
  }
245
253
  //#endregion
246
- export { ccc_roots_exports as a, hostInjected as c, hostSessions as d, hostSettings as f, hostWebServer as h, cccRootForExec as i, hostLlm as l, hostWeb as m, agentCwdFor as n, listCccs as o, hostSubagents as p, cccRootForCwd as r, hostAgents as s, NO_CCC_FROM_AGENT_CWD as t, hostService as u };
254
+ export { ccc_roots_exports as a, hostAgents as c, hostSessions as d, hostSettings as f, cccRootForExec as i, hostInjected as l, hostWebServer as m, agentCwdFor as n, listCccs as o, hostWeb as p, cccRootForCwd as r, hostAgentDefaultModel as s, NO_CCC_FROM_AGENT_CWD as t, hostService as u };
@@ -1,5 +1,5 @@
1
- import { f as hostSettings } from "./ccc-roots-WfktvbN8.js";
2
- import { c as readWeixinSettings, o as readImSettings } from "./route-DxIKWeN9.js";
1
+ import { f as hostSettings } from "./ccc-roots-BPicjCsj.js";
2
+ import { c as readWeixinSettings, o as readImSettings } from "./route-CmEoKp9w.js";
3
3
  import { isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
5
5
  //#region src/surface/settings.ts
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\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";
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");
@@ -223,11 +223,6 @@ window.__ModuleLoader__.load({
223
223
  className: cx("sp-metaSep"),
224
224
  children: "·"
225
225
  }),
226
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.cheapWorkerModel ?? "模型未配置" }),
227
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
228
- className: cx("sp-metaSep"),
229
- children: "·"
230
- }),
231
226
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
232
227
  "blacklist ",
233
228
  status.blacklist.length,
@@ -40,7 +40,7 @@ export { CRO_FILENAME, CRO_TIMEOUT_MS } from './core.js';
40
40
  /**
41
41
  * CRO 编写指南正文(ACC 内置手册;由工具的 guide 动作输出)。
42
42
  *
43
- * 结构照既有 guide 惯例(`MSM_GUIDE` / `cheap-worker(guide=true)`):
43
+ * 结构照既有 guide 惯例(`MSM_GUIDE` / `container_admin config guide`):
44
44
  * 用途 → 位置 → 我给什么 → 你给我什么 → 骨架 → 常见模式 → 坑 → **自测**。
45
45
  */
46
46
  export declare const CRO_GUIDE: string;
@@ -15,7 +15,8 @@
15
15
  * `createTrajectoryAgent`,其 id = `` `轨迹约束-${role}-${uuid}` ``(`trajectory/session.ts`)⇒ `acp-` **从不出现**;
16
16
  * ② 面 E 退役后连"本可能产生它的模块"也不在了。⇒ 该分支是**恒 false 的死判据**。
17
17
  * ⚠️ **仍不擅自删**:这是**运行时字符串比较**,删它属**行为变更**(不是纯清理),
18
- * 依本轨既有先例(`cheap-worker-` 四处置孤儿,同族、同判据)⇒ **登记待裁**(见 S142 §当前状态「观察」)。
18
+ * 依本轨既有先例(`cheap-worker-` 前缀孤儿 —— 生产者文件已于 2026-10-28 整条删除,
19
+ * 但四处判断保留作兼容,同族、同判据)⇒ **登记待裁**(见 S142 §当前状态「观察」)。
19
20
  */
20
21
  import type { Context } from 'cordis';
21
22
  import type { Agent } from '@deepseek-ai/dsh-agent';
@@ -26,6 +26,7 @@ export declare function hostService<T = unknown>(ctx: unknown, name: string): T
26
26
  * 于是"先直接属性读、失败回落 ctx.get"的写法在**真实 cordis** 下永远走不到回落分支——
27
27
  * 异常直接逃逸(`hostService` 的 try/catch 被绕过)。v1.31.3 的 `hostSubagents`
28
28
  * 正是这样炸的:`subagents` 是 lazy 服务(不在插件 `inject` 列表内)。
29
+ * (该函数已于 2026-10-28 随 cheap-worker 删除;这段教训仍是本模块的**存在理由**。)
29
30
  * 现有 fake ctx 单测(普通对象无 getter)复现不了 → 真 cordis 用例见
30
31
  * `tests/host/cordis-access.test.ts`。
31
32
  *
@@ -84,52 +85,49 @@ export declare function hostSettings(ctx: unknown): HostSettings | undefined;
84
85
  /** `ctx.web`(injected;v1.30.12 web_fetch provider 注册通道) */
85
86
  export declare function hostWeb(ctx: unknown): HostWeb | undefined;
86
87
  /**
87
- * `ctx.subagents`(lazy;v1.31.3:cheap-worker foreground 模式的委派正门)。
88
+ * 🔴 **`hostSubagents` / `HostSubagents` 已于 2026-10-28 删除**
89
+ * (owner 令「下线 cheap-worker,完全删掉」)。
88
90
  *
89
- * 🔴 **形状对照宿主 `0.2.1-rc.1`(v1.59.3 适配)**:旧成员 `start(name, request)` **已消失**,
90
- * 改由 `startActivation(spec)` 承担(实名由 `spec.provider` 指定,我们仍传 `'spawn'`)。
91
- * - 旧:`start(name: string, request: SubagentStartRequest): Promise<SubagentRun>`
92
- * - 新:`startActivation(spec: SubagentActivationSpec): Promise<SubagentActivation>`(两个重载)
93
- * 实测依据(**类型面与运行时都核过**,不是只看一边):
94
- * `_tmp/host-0.2.1-rc.1/…/dsh-subagent/lib/types/index.d.ts:135,145` +
95
- * 同包 `lib/index.js` 里**只有** `async startActivation(spec)`。
96
- * ⚠️ **两个方法都保留为可选**:本仓仍声明支持 `0.1.7-rc.1` 双档(`VERIFIED_HOST_BANDS`),
97
- * 而 0.1.7 那代同样只有 `start` —— **删掉 `start` 会让旧档在类型面说谎**。
98
- * 调用方按"先新后旧"选用,见 `cheap-worker.ts` 的 `resolveSubagentStarter()`。
91
+ * 它是 cheap-worker 前台委派的**专属**取证面 —— 全仓 grep 确认:cheap-worker 删除后
92
+ * `hostSubagents` **零消费者**(只剩定义与注释)。留着就是"接线断了却看不出来"的形态
93
+ * (本仓同族先例:`cheapWorkerPresetInheritance` 零调用点)。
99
94
  *
100
- * 本模块只做形状收口;语义与错误处理归调用方。
101
- *
102
- * 注(v1.31.4):本服务**不在** dsp 的 `inject` 列表内,因此走 `hostInjected` 的
103
- * 回落分支(`ctx.get`)——见 `hostInjected` 的 v1.31.4 说明与真实 cordis 用例。
95
+ * ⚠️ **但 `start` / `startActivation` 的双档形状知识不能丢**:它记录的是**宿主契约演进**
96
+ * (0.2.1-rc.1 起 `start` 消失、改由 `startActivation` 承担)。将来若再有委派需求,
97
+ * 这份对照仍是唯一说明书 ⇒ **知识移到 `host/contract.ts` 的契约条目里,代码不留。**
104
98
  */
105
- interface HostSubagents {
106
- /** 宿主 `0.1.7-rc.*` / `0.2.0-rc.*`(旧形态;0.2.1-rc.1 起已移除) */
107
- start?: (name: string, request: unknown) => Promise<unknown>;
108
- /** 宿主 `0.2.1-rc.1+`(新形态;`spec.provider` 即旧 `name`) */
109
- startActivation?: (spec: unknown) => Promise<unknown>;
110
- }
111
- export declare function hostSubagents(ctx: unknown): HostSubagents | undefined;
112
99
  /**
113
- * `ctx.llm`(`LlmRuntime`;v1.48:cheap-worker 的 **provider 预检**取证面)。
100
+ * `ctx.agentDefaultModel`(**刻意不进 `inject`**,v1.105.0)。
114
101
  *
115
- * 形状对照宿主 rc.2(`@deepseek-ai/dsh-llm`):
116
- * · `listProviders()` → `{ id, name }[]` —— **有 adapter** 的路由("本机真能用")
117
- * · `listConfigurableProviders()` → `{ provider, displayName, settingsNs, … }[]` ——
118
- * **已声明但未必激活**的路由("可以激活")
102
+ * 契约原文(`dsh-agent-default-model/lib/types/index.d.ts` 逐字):
103
+ * *"Default model selection for Agents created **without an explicit model**."*
104
+ * ⇒ **这正是「没指定时该用什么」的权威出处**,不该由我们另造一份真相源。
119
105
  *
120
- * 🔴 为什么走 `hostInjected` 而不是直接属性读:`llm` **不在** dsp 的 `inject` 列表内
121
- * (见 `src/index.ts` 的 `inject`)⇒ 真实 cordis 下直接属性读对未声明服务**会抛**
122
- * (`cannot get property "llm" without inject`),必须回落 `ctx.get`。
123
- *
124
- * 🔴 为什么**刻意不把 `llm` 加进 `inject`**:预检是**尽力而为**的增强,不是运行前提。
125
- * 加进 `inject` 会让"没有 llm 服务的 profile"**整块不激活插件** —— 为了一条诊断信息
126
- * 换来插件不上线,代价与收益倒挂。缺失一律 `undefined`,调用方**不得**因此抛错。
106
+ * 为什么不进 `inject`:它是**可选**服务 —— 进了 inject 就把"可选"变"必需",
107
+ * 服务缺失会让整个 fiber 挂起(同 ACP 面 `resolveAcpSelection` 的既有裁决)。
108
+ * 本仓先例:`host/access.ts` 的教训 —— cordis 对未声明 inject 的服务名
109
+ * **直接属性读会抛错**,故必须走 `hostInjected`(先属性读、抛错回落 `ctx.get`)。
127
110
  */
128
- interface HostLlm {
129
- listProviders?: () => unknown;
130
- listConfigurableProviders?: () => unknown;
111
+ interface HostAgentDefaultModel {
112
+ /** 返回 `{ provider, model, reasoningEffort? }`(detached) */
113
+ currentSelection?: () => {
114
+ provider?: string;
115
+ model?: string;
116
+ reasoningEffort?: string;
117
+ } | undefined;
131
118
  }
132
- export declare function hostLlm(ctx: unknown): HostLlm | undefined;
133
- /** 会话 cwd 列表(live 会话;形状不符时返回空数组而非抛错) */
119
+ /**
120
+ * 宿主默认模型选择(**没显式指定模型时**该用的那个)。
121
+ *
122
+ * 🔴 为什么需要这个读取器(v1.105.0,P0 回归修复):
123
+ * `agents.create` **不传** `agentOptions` ⇒ `agent.options.model === undefined`
124
+ * ⇒ 宿主的 persona 模板 `powered by the {{model}} model` **严格插值抛错** ⇒ 整轮失败。
125
+ * ⇒ 「不传」不等于「安全」:**下游有依赖方要求这个值必须存在**。
126
+ * 详见 `trajectory/session.ts` 的 `resolveAgentModel`。
127
+ */
128
+ export declare function hostAgentDefaultModel(ctx: unknown): HostAgentDefaultModel | undefined;
129
+ /**
130
+ * 会话 cwd 列表(live 会话;形状不符时返回空数组而非抛错)
131
+ */
134
132
  export declare function hostSessionCwds(ctx: unknown): string[];
135
133
  export {};
@@ -28,8 +28,9 @@ interface HostMember {
28
28
  *
29
29
  * 🔴 为什么需要(v1.61,S151 2026-10-17 报告):
30
30
  * 宿主会**给同一个能力改名**并保留旧名作回退。`subagents` 就是实例:
31
- * `0.2.1-rc.*+` 的正门叫 `startActivation`,`start` 仅作旧宿主回退
32
- * (`tools/cheap-worker.ts` 的 `resolveSubagentStarter()` 是**双路**的)。
31
+ * `0.2.1-rc.*+` 的正门叫 `startActivation`,`start` 仅作旧宿主回退。
32
+ * (原双路消费者 `tools/cheap-worker.ts` 的 `resolveSubagentStarter()` 已随
33
+ * cheap-worker 下线删除 —— **知识留在这里,代码不留**;将来再有委派需求照此双路写。)
33
34
  * 若契约把某一个具体名字钉成"必须有",宿主改名后 ⇒ **探针报假故障,而功能是好的**。
34
35
  * ⇒ 判据必须是「**这一族里至少有一个能用**」,不是「这个名字必须在」。
35
36
  */