@shgroup/dsh-serenity-hooks 1.73.0 → 1.74.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 (46) hide show
  1. package/README.md +12 -11
  2. package/dsh.plugin.json +2 -2
  3. package/lib/accounts-BHQbakUh.js +33 -0
  4. package/lib/{ccc-CZUOq3NY.js → ccc-CyYJL4F2.js} +9 -9
  5. package/lib/{ccc-roots-BhoA7G-0.js → ccc-roots-B2t_aM8L.js} +4 -4
  6. package/lib/client.js +116 -1
  7. package/lib/cro/core.d.ts +1 -1
  8. package/lib/guard/seam.d.ts +4 -4
  9. package/lib/host/effect.d.ts +1 -1
  10. package/lib/im/bridge.d.ts +3 -3
  11. package/lib/im/endpoint.d.ts +2 -2
  12. package/lib/im/guard.d.ts +1 -1
  13. package/lib/im/hook.d.ts +1 -1
  14. package/lib/im/inbound-event.d.ts +6 -6
  15. package/lib/im/route.d.ts +25 -3
  16. package/lib/index.d.ts +5 -5
  17. package/lib/index.js +360 -265
  18. package/lib/kernel/ccc-roots.d.ts +4 -4
  19. package/lib/kernel/ccc.d.ts +19 -19
  20. package/lib/kernel/ports.d.ts +1 -1
  21. package/lib/{message-source-ChyVfev9.js → message-source-DCpoRHP3.js} +2 -2
  22. package/lib/patches/opencode-provider.d.ts +1 -1
  23. package/lib/{profile-BIQ2widI.js → profile-BVt84mbi.js} +59 -59
  24. package/lib/{route-ItCgAsnz.js → route-COOFLTpU.js} +44 -6
  25. package/lib/seams/guards.d.ts +14 -14
  26. package/lib/seams/keeper.d.ts +1 -1
  27. package/lib/seams/lifecycle.d.ts +2 -2
  28. package/lib/{settings-UcwIPJhM.js → settings-D8hlqxua.js} +40 -26
  29. package/lib/surface/faces.d.ts +1 -1
  30. package/lib/surface/http.d.ts +1 -1
  31. package/lib/surface/settings.d.ts +7 -7
  32. package/lib/surface/text.d.ts +3 -3
  33. package/lib/tools/config-docs.d.ts +53 -0
  34. package/lib/tools/container-admin.d.ts +19 -12
  35. package/lib/tools/msm/service.d.ts +7 -0
  36. package/lib/tools/trajectory-constraint-admin.d.ts +30 -0
  37. package/lib/trajectory/carrier.d.ts +1 -1
  38. package/lib/trajectory/core.d.ts +1 -1
  39. package/lib/trajectory/policy/profile.d.ts +36 -36
  40. package/lib/trajectory/policy/rules.d.ts +33 -20
  41. package/lib/trajectory/policy/window.d.ts +13 -13
  42. package/lib/trajectory/prompt.d.ts +12 -12
  43. package/lib/trajectory/rebuild.d.ts +2 -2
  44. package/lib/trajectory/session.d.ts +49 -49
  45. package/package.json +2 -2
  46. package/lib/tools/skiff-admin.d.ts +0 -30
package/README.md CHANGED
@@ -80,7 +80,7 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
80
80
  | `serenity-skill-creator` | 增强型技能标准(serenity-skill)的创建器:`guide` 编写方法 / `create` 生成骨架(基础链路径由它算)/ `check` 结构检查(三态可分辨:已建模通过 · 普通技能 · 建模了但检查未执行)/ `review` 逐条合规问题清单 / `index` 预览**模型实际会看到**的参考件清单 / `status` 全容器一览 | 新建或改造技能、为技能建本体、需要确认"模型究竟能否看到指定参考件"时 |
81
81
  | `cheap-worker` | 杂工:委派一个低成本模型的助手执行任务,取回其最终文本。一次调用一个串行子代理;`jobs` 可一次扇出多个并行(上限 10)。**不循环、不校验完成码、不写进度文件** | 大批量、重复性的工作(扫描数十个技能、逐用例回归) |
82
82
  | `localstore` | 存储密钥与配置(凭据、偏好两个命名空间) | API key、密码集中放一处,不进 git |
83
- | `container_admin` | 机务舱:管理受限轨迹(skiff)、管理小工具注册表、查看全部配置 | 定义 skiff、注册新小工具、修改配置时 |
83
+ | `container_admin` | 机务舱:`config` 域(说明目录/按章详情 + 轨迹约束的 guide/validate/apply/list + 看当前配置)/`msm` 域(小工具注册表 + 手册) | 定义轨迹约束、注册新小工具、查配置怎么填时 |
84
84
  | `im-bridge` | 向 IM 联系人发消息(目前为微信):发文本、发文件、查已配置联系人、查通道状态。每次成功发送自动写入工作区的消息记录 | 需要 AI 主动给家人/同事发消息时(见 §6.2)。**条件出现**:仅在工作区配置了微信桥时可见 |
85
85
  | `acc-diag` | ACC 运行态诊断:一次调用输出全报告——当前活跃会话数、各自所属工作区、唤醒时钟的武装态与 tick 次数、唤醒登记表的每一条 | **ACC 维护者专用**。**条件出现**:默认对所有工作区隐藏,仅在工作区配置中点名(`exclusiveTools`)时出现 |
86
86
  | `diagram` | 绘图:AI 给出一段**标签式源码**,插件**在进程内**将其绘制为 PNG 并**贴入对话**——不启动浏览器、不截图、正常路径不落文件。坐标 = 画布像素、y 向下;写错的图元逐条报错并跳过,其余照常绘制;自带四项检查(块重叠 / 标签溢出 / 悬空端点 / 线压字),**只告警不拦图** | 需要把关系图/示意图直接放进对话时。**条件出现**:实验性 demo,**默认关**(关闭时该工具**根本不注册**,AI 看不到它、不占上下文);开关在设置面板「工具」组,拨动即时生效。中英文与常用符号均可绘制,因字体随插件内置(约 5 MB) |
@@ -112,10 +112,11 @@ dsh plugin --profile web add link:$(pwd)/hooks/dsh-serenity-hooks
112
112
  | DSH 主界面 | 3080 | 本机自用(插件不碰这个端口,也不起这个监听器) |
113
113
  | **网页登录入口** | 3081 | 外部/手机访问完整界面:登录后反向代理到 3080,可配工作区白名单 |
114
114
  | **微信主动发送入口** | 3082(只绑 127.0.0.1) | 工作区外的脚本/集成用它给微信发消息(公网不可达,所以不需要密钥)。工作区内的 AI 不走这个端口,直接使用 `im-bridge` 工具 |
115
- | **受限轨迹调试页** | 3099(只绑 127.0.0.1) | 调试 skiff 时使用,可切换工作区、查看对话轨迹 |
116
115
  | **微信桥** | 无需端口(出站长轮询) | 家人在微信里直接和 AI 说话 |
117
116
 
118
- > 3081 / 3099 **默认关**(须自行开启);3082 **默认开**(微信桥启用时才有意义)。默认只监听 127.0.0.1 的入口,如需暴露到公网,由使用者自行决定方式(隧道 / 反代 / 端口映射都行),插件不绑定任何特定做法。
117
+ > 3081 **默认关**(须自行开启);3082 **默认开**(微信桥启用时才有意义)。默认只监听 127.0.0.1 的入口,如需暴露到公网,由使用者自行决定方式(隧道 / 反代 / 端口映射都行),插件不绑定任何特定做法。
118
+ >
119
+ > 原 3099「受限轨迹调试页」已随 v1.73.0 整条退役 —— 轨迹约束的配置现由 `container_admin config` 直接读写。
119
120
 
120
121
  > 🔴 **已退役:原「ACP + 对外问答页」(3100)**。该面是本插件早期自造的一套私有 HTTP+HTML 协议外壳
121
122
  > (无客户端生态),已于 2026-10-15 **整体移除**(连同 `acp-core` / `acp-http` 与相关开关、问答页 key)。
@@ -251,28 +252,28 @@ my-workspace/ ← 工作区根目录(放一个 .serenity
251
252
  `source: "reply-fallback"` 表示"AI 没发、插件兜底发的"。
252
253
  - **排障**:`msm("weixin-doctor", ["status"|"diag"|"verify"|"guide"])`
253
254
 
254
- ### 6.3 受限轨迹(Skiff)
255
+ ### 6.3 轨迹约束(trajectory constraint)
255
256
 
256
- **skiff 就是一条「目录里放了 `skiff.json` 的认知轨迹」** —— 没有别的定义。
257
- 放进去,这条轨迹立刻变成受限形态;删掉它,当场恢复成普通轨迹。
257
+ **轨迹约束就是一条「目录里放了 `trajectory-constraint.json` 的认知轨迹」** —— 没有别的定义。
258
+ 放进去,这条轨迹立刻受约束;删掉它,当场恢复成普通轨迹。
258
259
 
259
260
  ```jsonc
260
261
  // AGENT_SESSIONS/2026-10-19--qa--问答/ ← 轨迹目录(编码 = 目录名固定位置那一段)
261
- // ├─ SESSION.md ← 它自己的身体(工作台就是它,不另建)
262
- // ├─ skiff.json ← 约束面(这个文件的存在,就是"它是 skiff"的判据)
262
+ // ├─ SESSION.md ← 它自己的身体(工作台就是它,不另建)
263
+ // ├─ PROMPT.md ← 它的章程(推荐;所有轨迹都适用,不限于轨迹约束)
264
+ // ├─ trajectory-constraint.json ← 约束面(这个文件的存在,就是"它是轨迹约束"的判据)
263
265
  {
264
266
  "model": "provider/model", // 这条轨迹单独用哪个模型
265
267
  "msms": ["web-search", "vlm-describe"], // 它能调哪些小工具(MSM)
266
268
  "tools": ["read", "grep", "glob"], // 它能用哪些平台工具
267
- "systemPromptFile": "roles/qa.md", // 它的人格与边界(也可以直接内联 systemPrompt)
268
269
  "cognitiveTrajectory": { "session": true, "keeper": true, "rebuild": true } // 缺省即全 true
269
270
  }
270
271
  ```
271
272
 
272
273
  - 两份白名单分开配(小工具 / 平台工具),**没列出来的一律从工具清单里移除**(不是"看得见调不动")
273
274
  - **`cognitiveTrajectory` 缺省全 `true`**:工作台就是它自己的 `SESSION.md`,关掉等于把它的身体摘了
274
- - 调试页(3099)可以切工作区、看轨迹;回答用 markdown 渲染,思考过程折叠
275
- - `container_admin role validate` 校验,`apply` 才真正生效;两者都**实时读盘**,改完下一轮即生效
275
+ - 章程写在轨迹目录的 **`PROMPT.md`** 里(跟着轨迹走,归档时一起搬);旧的 `systemPromptFile` 仍被读取,但 `PROMPT.md` 优先
276
+ - `container_admin config validate` 校验,`apply` 才真正生效;两者都**实时读盘**,改完下一轮即生效
276
277
  - 微信路由也指向它:`routes: [{ "user": "*", "trajectory": "qa" }]`(**user → 轨迹编码**)
277
278
 
278
279
  ### 6.4 对外接入(官方 ACP)
package/dsh.plugin.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.73.0",
3
+ "version": "1.74.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 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/认知轨迹唤醒注册表。skiff = 目录下有 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 工作区」——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-constraint.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
  },
@@ -0,0 +1,33 @@
1
+ import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
2
+ //#region src/im/accounts.ts
3
+ var accounts_exports = /* @__PURE__ */ __exportAll({
4
+ getImAccountImpl: () => getImAccountImpl,
5
+ imAccountChannels: () => imAccountChannels,
6
+ normalizeAccountChannel: () => normalizeAccountChannel,
7
+ registerImAccountImpl: () => registerImAccountImpl
8
+ });
9
+ const impls = /* @__PURE__ */ new Map();
10
+ /** 注册账号实现(`index.ts` 装配时调用;重复 channel 覆盖——测试与热重载友好) */
11
+ function registerImAccountImpl(impl) {
12
+ impls.set(impl.channel, impl);
13
+ }
14
+ /** 取实现(未注册 ⇒ `null`,由调用方**响亮出声**,不要静默跳过) */
15
+ function getImAccountImpl(channel) {
16
+ return impls.get(channel) ?? null;
17
+ }
18
+ /** 已注册的实现名(诊断与错误文案用;顺序 = 注册顺序) */
19
+ function imAccountChannels() {
20
+ return [...impls.keys()];
21
+ }
22
+ /**
23
+ * 账号行的渠道归一(**单一处**)。
24
+ *
25
+ * 缺省 `weixin`:老配置平移到 `im` 段时不必补这个字段——owner 的"统一配置"
26
+ * 要能直接吃旧数据(与 `route.ts` 的 `normalizeImSection` 保持同一口径)。
27
+ */
28
+ function normalizeAccountChannel(channel) {
29
+ const v = (channel ?? "").trim();
30
+ return v === "" ? "weixin" : v;
31
+ }
32
+ //#endregion
33
+ export { registerImAccountImpl as a, normalizeAccountChannel as i, getImAccountImpl as n, imAccountChannels as r, accounts_exports as t };
@@ -77,18 +77,18 @@ function readCccName(root) {
77
77
  }
78
78
  }
79
79
  /**
80
- * 🔴 **`SkiffRoleConfig` 已于 2026-10-19 退役**(owner 重做令)。
80
+ * 🔴 **`TrajectoryConstraintConfig` 已于 2026-10-19 退役**(owner 重做令)。
81
81
  *
82
- * 它的语义整体搬进**轨迹目录下的 `skiff.json`**,类型现在是
83
- * `trajectory/policy/profile.ts` 的 `SkiffProfile`(在那里还多了 `code`/`dirName`/`path`
82
+ * 它的语义整体搬进**轨迹目录下的 `trajectory-constraint.json`**,类型现在是
83
+ * `trajectory/policy/profile.ts` 的 `TrajectoryConstraint`(在那里还多了 `code`/`dirName`/`path`
84
84
  * 三个与轨迹身份相关的字段)。
85
85
  *
86
- * 退役理由(一句话):skiff 的约束面是**某一条轨迹的属性**,不是容器级配置表里的一个 entry。
87
- * 放进 `serenity.json` 会让"这条轨迹是不是 skiff"分散在**两个文件**里,而 owner 要的是
88
- * 一条判据(「目录下拥有 `skiff.json` 的认知轨迹自动是一个 skiff」)。
86
+ * 退役理由(一句话):轨迹约束的约束面是**某一条轨迹的属性**,不是容器级配置表里的一个 entry。
87
+ * 放进 `serenity.json` 会让"这条轨迹是不是 轨迹约束"分散在**两个文件**里,而 owner 要的是
88
+ * 一条判据(「目录下拥有 `trajectory-constraint.json` 的认知轨迹自动是一个 轨迹约束」)。
89
89
  *
90
90
  * ⚠️ 旧字段里 `kind: temporary|persistent` **整件无对应物**:它区分"有没有工作台",
91
- * 而新模型里 skiff 恒等于一条轨迹 ⇒ 恒有 SESSION.md ⇒ 该区分没有指称对象(静默忽略)。
91
+ * 而新模型里 轨迹约束恒等于一条轨迹 ⇒ 恒有 SESSION.md ⇒ 该区分没有指称对象(静默忽略)。
92
92
  */
93
93
  /**
94
94
  * 解析 cheap-worker 配置。
@@ -139,7 +139,7 @@ function readUtf8(path) {
139
139
  }
140
140
  /**
141
141
  * 已告警过的损坏配置路径(进程级去重:同一坏文件不刷屏,但绝不静默)。
142
- * S142 review F-07:此前损坏 → 静默返回 `{}`——安全模式黑名单/cheap-worker 白名单/skiff 角色
142
+ * S142 review F-07:此前损坏 → 静默返回 `{}`——安全模式黑名单/cheap-worker 白名单/轨迹约束角色
143
143
  * 全部**无声失效**(用户以为配了,实际没配)。守卫的输入失败必须响亮。
144
144
  */
145
145
  const warnedBrokenConfigs = /* @__PURE__ */ new Set();
@@ -163,7 +163,7 @@ function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
163
163
  } catch (err) {
164
164
  if (!warnedBrokenConfigs.has(p)) {
165
165
  warnedBrokenConfigs.add(p);
166
- console.warn(`[serenity-hooks] ✗ CCC 配置解析失败,已按空配置继续(该 CCC 的安全模式黑名单/cheap-worker 白名单/skiff 角色等全部失效,请修 JSON): ${p} — ${String(err?.message ?? err)}`);
166
+ console.warn(`[serenity-hooks] ✗ CCC 配置解析失败,已按空配置继续(该 CCC 的安全模式黑名单/cheap-worker 白名单/轨迹约束角色等全部失效,请修 JSON): ${p} — ${String(err?.message ?? err)}`);
167
167
  }
168
168
  return {};
169
169
  }
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
2
- import { a as findSerenityRoot } from "./ccc-CZUOq3NY.js";
2
+ import { a as findSerenityRoot } from "./ccc-CyYJL4F2.js";
3
3
  import { basename } from "node:path";
4
4
  //#region src/host/access.ts
5
5
  /**
@@ -215,7 +215,7 @@ function liveSessionPaths(ctx) {
215
215
  * **整个列表都不含后者**("名单不全")。并集修正它,且**不改**任何消费方的输入契约。
216
216
  *
217
217
  * @param ctx 插件上下文
218
- * @param opts.defaultRoot 默认根(不在列表 → 置首);opts.withRoles 是否读 `skiff.roles`
218
+ * @param opts.defaultRoot 默认根(不在列表 → 置首);opts.withRoles 是否读 `轨迹约束.roles`
219
219
  * @returns 按上述顺序去重的 CCC 条目(无任何来源 → 空数组 + 可选默认根)
220
220
  */
221
221
  async function listCccs(ctx, opts = {}) {
@@ -227,10 +227,10 @@ async function listCccs(ctx, opts = {}) {
227
227
  if (typeof defaultRoot === "string" && defaultRoot !== "" && !c.roots.includes(defaultRoot)) c.roots.unshift(defaultRoot);
228
228
  let rolesOf = null;
229
229
  if (opts.withRoles === true) {
230
- const { listSkiffProfiles } = await import("./profile-BIQ2widI.js").then((n) => n.i);
230
+ const { listTrajectoryConstraints } = await import("./profile-BVt84mbi.js").then((n) => n.i);
231
231
  rolesOf = (root) => {
232
232
  try {
233
- return listSkiffProfiles(root).map((p) => p.code);
233
+ return listTrajectoryConstraints(root).map((p) => p.code);
234
234
  } catch {
235
235
  return [];
236
236
  }
package/lib/client.js CHANGED
@@ -3805,6 +3805,11 @@ Instruction-following style:
3805
3805
  const [loginError, setLoginError] = (0, react.useState)(null);
3806
3806
  const [loginNotice, setLoginNotice] = (0, react.useState)(null);
3807
3807
  const [routesDraft, setRoutesDraft] = (0, react.useState)("");
3808
+ const [feishuAppId, setFeishuAppId] = (0, react.useState)("");
3809
+ const [feishuAppSecret, setFeishuAppSecret] = (0, react.useState)("");
3810
+ const [feishuName, setFeishuName] = (0, react.useState)("");
3811
+ const [feishuBusy, setFeishuBusy] = (0, react.useState)(false);
3812
+ const [feishuNotice, setFeishuNotice] = (0, react.useState)(null);
3808
3813
  (0, react.useEffect)(() => {
3809
3814
  let alive = true;
3810
3815
  (async () => {
@@ -4021,6 +4026,54 @@ Instruction-following style:
4021
4026
  if (b.lastPollAt > 0) return `轮询中(${new Date(b.lastPollAt).toLocaleTimeString()})`;
4022
4027
  return "启动中";
4023
4028
  };
4029
+ /**
4030
+ * 添加飞书账号(P4)。
4031
+ *
4032
+ * 🔴 为什么飞书是表单而微信用二维码:**扫码是微信独有形态**。飞书的身份是
4033
+ * 「企业自建应用」的 appId + appSecret ⇒ 只能由人从飞书开放平台后台抄过来。
4034
+ * 在此之前面板没有任何入口 ⇒ owner 只能手写 localstore,那不算"能配"。
4035
+ *
4036
+ * 成功后**清空表单**:这是"可反复添加"的判据(同一份凭据连点两次会建出两个账号)。
4037
+ */
4038
+ const addFeishuAccount = async () => {
4039
+ if (!selectedRoot || feishuBusy) return;
4040
+ setFeishuBusy(true);
4041
+ setFeishuNotice(null);
4042
+ try {
4043
+ const res = await fetch("/serenity/weixin", {
4044
+ method: "POST",
4045
+ headers: {
4046
+ "content-type": "application/json",
4047
+ "x-serenity-ui": "1"
4048
+ },
4049
+ body: JSON.stringify({
4050
+ action: "add-feishu-account",
4051
+ ccc: selectedRoot,
4052
+ appId: feishuAppId.trim(),
4053
+ appSecret: feishuAppSecret.trim(),
4054
+ name: feishuName.trim()
4055
+ })
4056
+ });
4057
+ const body = await res.json().catch(() => null);
4058
+ if (!res.ok) {
4059
+ setFeishuNotice(body?.error ?? `HTTP ${res.status}`);
4060
+ return;
4061
+ }
4062
+ setFeishuNotice(`账号 ${body?.accountId ?? ""} 已添加 ✓(凭据已写入 localstore)`);
4063
+ setFeishuAppId("");
4064
+ setFeishuAppSecret("");
4065
+ setFeishuName("");
4066
+ const sbody = await (await fetch(`/serenity/weixin?ccc=${encodeURIComponent(selectedRoot)}`, { headers: {
4067
+ accept: "application/json",
4068
+ "x-serenity-ui": "1"
4069
+ } })).json().catch(() => null);
4070
+ if (sbody) setStatus(sbody);
4071
+ } catch (err) {
4072
+ setFeishuNotice(err instanceof Error ? err.message : String(err));
4073
+ } finally {
4074
+ setFeishuBusy(false);
4075
+ }
4076
+ };
4024
4077
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
4025
4078
  className: "ss-rows",
4026
4079
  children: [
@@ -4211,6 +4264,68 @@ Instruction-following style:
4211
4264
  })
4212
4265
  })]
4213
4266
  }) }),
4267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4268
+ className: "ss-rowCard",
4269
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4270
+ className: "ss-rowText",
4271
+ children: [
4272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4273
+ className: "ss-rowName",
4274
+ children: "添加飞书账号"
4275
+ }),
4276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4277
+ className: "ss-rowDesc",
4278
+ children: feishuNotice ?? "填企业自建应用的 App ID 与 App Secret——每提交一次新增一个账号"
4279
+ }),
4280
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4281
+ className: "ss-help",
4282
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4283
+ type: "button",
4284
+ className: "ss-helpMark",
4285
+ "aria-label": "飞书绑定帮助",
4286
+ children: "?"
4287
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4288
+ className: "ss-helpTip",
4289
+ children: "添加飞书账号(企业自建应用)。\n· 飞书开放平台 → 开发者后台 → 你的应用 → 「凭证与基础信息」\n· 复制 App ID 与 App Secret 填在下面\n· 事件订阅选「长连接」并订阅 im.message.receive_v1\n· 每提交一次新增一个账号,各自独立连接\n· 与微信并列:账号、路由、开关互不影响"
4290
+ })]
4291
+ })
4292
+ ]
4293
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4294
+ className: "ss-rowControl",
4295
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4296
+ type: "button",
4297
+ className: "ss-loginBtn",
4298
+ disabled: feishuBusy || feishuAppId.trim() === "" || feishuAppSecret.trim() === "",
4299
+ onClick: () => void addFeishuAccount(),
4300
+ children: feishuBusy ? "添加中…" : "添加飞书账号"
4301
+ })
4302
+ })]
4303
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4304
+ className: "ss-feishuForm",
4305
+ children: [
4306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4307
+ className: "ss-input",
4308
+ type: "text",
4309
+ placeholder: "App ID(cli_xxx)",
4310
+ value: feishuAppId,
4311
+ onChange: (e) => setFeishuAppId(e.target.value)
4312
+ }),
4313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4314
+ className: "ss-input",
4315
+ type: "password",
4316
+ placeholder: "App Secret",
4317
+ value: feishuAppSecret,
4318
+ onChange: (e) => setFeishuAppSecret(e.target.value)
4319
+ }),
4320
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4321
+ className: "ss-input",
4322
+ type: "text",
4323
+ placeholder: "账号名(可选,如「家庭群机器人」)",
4324
+ value: feishuName,
4325
+ onChange: (e) => setFeishuName(e.target.value)
4326
+ })
4327
+ ]
4328
+ })] }),
4214
4329
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4215
4330
  className: "ss-rowCard",
4216
4331
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4271,7 +4386,7 @@ Instruction-following style:
4271
4386
  }
4272
4387
  //#endregion
4273
4388
  //#region \0sp-css:src/client/SettingsSection
4274
- const css$2 = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* IM 桥:账号行的**渠道标签**(2026-10-19 泛化后新增)。\n 为什么是标签而不是\"把渠道拼进 id\":渠道是账号的属性,不是名字的一部分;\n 拼进去会让「同一个账号换了渠道」看起来像另一个账号。\n 🔴 不得定宽(v1.72.0 那次 `sp-label` 写死 76px 压住数值的教训)⇒ inline-block + 自适应。 */\n.ss-channelTag {\n display: inline-block;\n padding: 0 6px;\n margin-right: 6px;\n border-radius: 4px;\n font-size: 11px;\n line-height: 18px;\n font-weight: 600;\n vertical-align: 1px;\n background: rgba(11, 168, 117, .12);\n color: #0ba875;\n white-space: nowrap;\n}\n\n@media (prefers-color-scheme: dark) {\n .ss-channelTag { color: #3ddc9a; }\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-border-l3);\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 错误提示(会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── 开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n\n/* ── 窄屏(面板被塞进窄栏或手机浏览器)────────────────────────────────────\n * 排版问题只有一个根因:`.ss-rowCard` 是「左文本 + 右控件」的两列 flex,\n * 而控件里有端口输入(96px)/开关(40px)/端口+开关(150px)三档。\n * 窄到放不下时,flex 会先压文字(`min-width: 0` 已开)⇒ 标题被挤成一列竖排字。\n * 处理:窄屏把控件落到第二行(左对齐),文字保住整行宽。 */\n@media (max-width: 560px) {\n .ss-rowCard {\n flex-wrap: wrap;\n gap: 10px;\n }\n\n .ss-rowControl {\n flex-basis: 100%;\n justify-content: flex-start;\n }\n\n /* 行内说明在窄屏不必截断:整行宽足够,截断反而藏信息 */\n .ss-rowDesc {\n -webkit-line-clamp: unset;\n display: block;\n }\n\n /* 详情区与折叠头在窄屏收紧内边距(横向像素更值钱) */\n .ss-rowDetail,\n .ss-collapseBody {\n padding-left: 0;\n padding-right: 0;\n }\n}\n";
4389
+ const css$2 = "/* SettingsSection — dsh 原生设置面板 serenity-hooks 简单配置页\n v1.21.1 看齐 dsh 自身:官方 settings 设计语言——\n section > title + intro + group(groupTitle) + rowCard 行卡;\n 全部 --dsw-alias-* token(与 ui-settings-models 同词汇表)。 */\n\n/* 页面容器(官方 .section:flex column gap 12) */\n.ss-section {\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面标题(官方 .title:16px/24 500) */\n.ss-title {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* 页面说明(官方 .intro:14px/22 tertiary) */\n.ss-intro {\n margin: 0;\n font-size: 14px;\n line-height: 22px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n\n/* 分组(多级标题:分组标题 + 行卡列表) */\n.ss-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 分组标题(二级:12px/18 500 secondary——官方 fieldLabel 风格) */\n.ss-groupTitle {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 行卡列表(官方 .rows:无 marker,gap 8) */\n.ss-rows {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* 行卡(官方 .rowCard:border-l2 r12 padding 12/14) */\n.ss-rowCard {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n padding: 12px 14px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n/* ── v1.29 需求②:可展开行(整行点击 → 行内详设)── */\n.ss-expandable {\n flex-wrap: wrap; /* detail 换行占满整行 */\n cursor: default;\n}\n.ss-expandable .ss-rowText {\n cursor: pointer;\n}\n.ss-expandable .ss-rowText:hover .ss-rowName {\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expanded {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n.ss-expandMark {\n display: inline-block;\n margin-left: 6px;\n font-size: 10px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n.ss-expanded .ss-expandMark {\n transform: rotate(90deg);\n}\n.ss-rowDetail {\n flex-basis: 100%;\n width: 100%;\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 10px;\n padding-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n.ss-detailStack {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.ss-detailIntro {\n margin: 0;\n font-size: 12px;\n line-height: 19px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-line;\n word-break: break-word;\n overflow-wrap: anywhere;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.06));\n}\n\n/* 行卡文本(标题 + 说明 左列) */\n.ss-rowText {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1;\n}\n\n/* 行卡标题(官方 .rowName:14px/22 500) */\n.ss-rowName {\n font-size: 14px;\n line-height: 22px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n/* IM 桥:账号行的**渠道标签**(2026-10-19 泛化后新增)。\n 为什么是标签而不是\"把渠道拼进 id\":渠道是账号的属性,不是名字的一部分;\n 拼进去会让「同一个账号换了渠道」看起来像另一个账号。\n 🔴 不得定宽(v1.72.0 那次 `sp-label` 写死 76px 压住数值的教训)⇒ inline-block + 自适应。 */\n.ss-channelTag {\n display: inline-block;\n padding: 0 6px;\n margin-right: 6px;\n border-radius: 4px;\n font-size: 11px;\n line-height: 18px;\n font-weight: 600;\n vertical-align: 1px;\n background: rgba(11, 168, 117, .12);\n color: #0ba875;\n white-space: nowrap;\n}\n\n@media (prefers-color-scheme: dark) {\n .ss-channelTag { color: #3ddc9a; }\n}\n\n/* 行卡说明(12px/18 tertiary;v1.27.5 紧凑化:默认单行截断,hover title 看全文) */\n.ss-rowDesc {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-word;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* 行卡控件(右置) */\n.ss-rowControl {\n flex: none;\n display: flex;\n align-items: center;\n}\n\n/* 开关(官方 token 的自绘 toggle:track 22px,thumb 16px) */\n.ss-switch {\n position: relative;\n flex: none;\n width: 40px;\n height: 22px;\n cursor: pointer;\n}\n\n.ss-switch input {\n position: absolute;\n opacity: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n cursor: pointer;\n}\n\n.ss-switchTrack {\n position: absolute;\n inset: 0;\n border-radius: 11px;\n background: var(--dsw-alias-border-l3);\n transition: background var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switchTrack::after {\n content: '';\n position: absolute;\n top: 3px;\n left: 3px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform var(--ds-transition-duration-fast, 0.15s) var(--ds-ease-in-out, ease);\n}\n\n.ss-switch input:checked + .ss-switchTrack {\n background: var(--dsw-alias-brand-primary);\n}\n\n.ss-switch input:checked + .ss-switchTrack::after {\n transform: translateX(18px);\n}\n\n.ss-switch input:disabled + .ss-switchTrack {\n opacity: 0.4;\n cursor: default;\n}\n\n/* Skiff 调试端口输入(number;关闭时跟随 disabled 半透明) */\n.ss-portInput {\n width: 96px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.ss-portInput:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 自主轨迹「立即唤起」按钮(v1.26.14 调试语义;官方 button 语言:primary 弱化) */\n.ss-wakeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-wakeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-wakeBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 错误提示(会话清理等) */\n.ss-error {\n margin: 0;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-state-error-primary, #ef4444);\n background: rgba(239, 68, 68, 0.08);\n border-radius: 8px;\n padding: 8px 10px;\n white-space: pre-line;\n word-break: break-word;\n}\n\n/* ── v1.27.5 紧凑化:折叠组(details/summary)── */\n.ss-collapse {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n overflow: hidden;\n}\n\n.ss-collapseHead {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n list-style: none;\n user-select: none;\n}\n\n.ss-collapseHead::-webkit-details-marker {\n display: none;\n}\n\n.ss-collapseHead::before {\n content: '▸';\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n transition: transform 0.15s ease;\n}\n\n.ss-collapse[open] > .ss-collapseHead::before {\n transform: rotate(90deg);\n}\n\n.ss-collapseHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.06));\n}\n\n.ss-collapseTitle {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n}\n\n.ss-collapseDesc {\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.ss-collapseBody {\n padding: 10px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n}\n\n/* ── 开关 + 端口内联行 ── */\n.ss-switchRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n/* ── v1.27.6 「?」帮助浮层(hover/焦点显示三行以上严谨说明)── */\n.ss-help {\n position: relative;\n display: inline-flex;\n align-items: center;\n margin-top: 2px;\n}\n\n.ss-helpMark {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 50%;\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n line-height: 1;\n font-weight: 600;\n cursor: help;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: border-color 0.15s ease, color 0.15s ease;\n}\n\n.ss-helpMark:hover,\n.ss-helpMark:focus-visible {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n outline: none;\n}\n\n.ss-helpTip {\n position: absolute;\n left: 0;\n top: calc(100% + 6px);\n z-index: 30;\n width: 300px;\n max-width: 60vw;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-overlay);\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);\n font-size: 12px;\n line-height: 19px;\n font-weight: 400;\n color: var(--dsw-alias-label-primary);\n white-space: pre-line;\n word-break: break-word;\n opacity: 0;\n pointer-events: none;\n transform: translateY(-4px);\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ss-help:hover .ss-helpTip,\n.ss-helpMark:focus-visible + .ss-helpTip {\n opacity: 1;\n pointer-events: auto;\n transform: translateY(0);\n}\n\n/* 折叠组内帮助浮层定位修正(左侧行卡不会溢出面板) */\n.ss-rowCard .ss-helpTip {\n left: 0;\n}\n\n/* ── 微信桥(F4c-3,v1.27.0 实验性)── */\n\n/* CCC 选择器(下拉) */\n.ss-select {\n max-width: 280px;\n padding: 6px 8px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* 扫码绑定按钮(复用 wakeBtn 语言) */\n.ss-loginBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-loginBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-success-primary, #22c55e);\n color: var(--dsw-alias-state-success-primary, #22c55e);\n}\n\n.ss-loginBtn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 移除账号按钮(危险语义:红边框) */\n.ss-removeBtn {\n flex: none;\n padding: 5px 12px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-interactive-bg-active, rgba(128, 128, 128, 0.12));\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: opacity var(--ds-transition-duration-fast, 0.15s) ease;\n}\n\n.ss-removeBtn:hover:not(:disabled) {\n border-color: var(--dsw-alias-state-error-primary, #ef4444);\n color: var(--dsw-alias-state-error-primary, #ef4444);\n}\n\n/* 二维码展示行 */\n.ss-qrRow {\n align-items: center;\n}\n\n.ss-qrSvg {\n flex: none;\n width: 120px; /* v1.27.5 紧凑化:160 → 120 */\n height: 120px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: #fff; /* 二维码需要白底才能被微信扫出 */\n padding: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.ss-qrSvg svg {\n width: 100%;\n height: 100%;\n}\n\n/* 飞书凭据表单(P4):三格竖排——appId / appSecret / 账号名。\n * 🔴 不做成一行三格:App Secret 通常 32 位,窄屏下会被压到看不见自己输了什么\n * (本文件 D152 记过同族:定宽 + 无断点会把值压到标签上)。竖排在小屏也保持可读。 */\n.ss-feishuForm {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 6px;\n}\n\n.ss-feishuForm .ss-input {\n width: 100%;\n box-sizing: border-box;\n padding: 7px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.ss-feishuForm .ss-input::placeholder {\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 路由表 JSON 编辑框 */\n.ss-routesEditor {\n width: 100%;\n box-sizing: border-box;\n margin-top: 6px;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n font-family: var(--ds-font-family-code);\n resize: vertical;\n}\n\n/* ── 窄屏(面板被塞进窄栏或手机浏览器)────────────────────────────────────\n * 排版问题只有一个根因:`.ss-rowCard` 是「左文本 + 右控件」的两列 flex,\n * 而控件里有端口输入(96px)/开关(40px)/端口+开关(150px)三档。\n * 窄到放不下时,flex 会先压文字(`min-width: 0` 已开)⇒ 标题被挤成一列竖排字。\n * 处理:窄屏把控件落到第二行(左对齐),文字保住整行宽。 */\n@media (max-width: 560px) {\n .ss-rowCard {\n flex-wrap: wrap;\n gap: 10px;\n }\n\n .ss-rowControl {\n flex-basis: 100%;\n justify-content: flex-start;\n }\n\n /* 行内说明在窄屏不必截断:整行宽足够,截断反而藏信息 */\n .ss-rowDesc {\n -webkit-line-clamp: unset;\n display: block;\n }\n\n /* 详情区与折叠头在窄屏收紧内边距(横向像素更值钱) */\n .ss-rowDetail,\n .ss-collapseBody {\n padding-left: 0;\n padding-right: 0;\n }\n}\n";
4275
4390
  if (typeof document !== "undefined" && document.querySelector("style[data-sp-css=\"SettingsSection\"]") === null) {
4276
4391
  const tag = document.createElement("style");
4277
4392
  tag.setAttribute("data-sp-css", "SettingsSection");
package/lib/cro/core.d.ts CHANGED
@@ -321,7 +321,7 @@ export declare function parseCroHostBinding(raw: string): string | null;
321
321
  * `AGENT_SESSIONS/` 下每个目录查一次 `<目录>/continuous-re-occurrence.ts`。
322
322
  * · **一次 readdir + 每个目录一次 existsSync**:N 条轨迹的代价是 O(N) 次 `stat`,
323
323
  * 每 5min 一次 —— 与既有 `listSessions` 同量级,可接受。
324
- * · 跳过 **`_` 前缀**(`_archived` / `_skiff-logs` / `_weixin-logs` = 系统与日志目录)
324
+ * · 跳过 **`_` 前缀**(`_archived` / `_轨迹约束-logs` / `_weixin-logs` = 系统与日志目录)
325
325
  * 与 **`.` 前缀**(隐藏)——它们**不是轨迹**,且扫它们纯属浪费。
326
326
  * · 🔴 **本函数绝不抛错**(返回 `[]`)——它跑在调度器 tick 内,任何异常都可能影响既有链路
327
327
  * (设计 §5 铁律)。目录读不到(不存在 / 权限)⇒ `[]` = "没有轨迹启用 CRO",语义正确。
@@ -5,21 +5,21 @@
5
5
  * 命中 → `agent.steer(buildRebuke(hits))` 打回重生成(turn 不关闭,模型同轮重答)。
6
6
  * 连续命中达 REBUKE_MAX_ROUNDS → 放弃打回(防死循环),输出保留 + 审计日志。
7
7
  *
8
- * 作用范围(v1.26.3 用户拍板):**仅外部面**——skiff 等对外输出会话
9
- * (session id `skiff-`/`rebuild-` 前缀;v1.22.4 rebuild 新建会话同面)。
8
+ * 作用范围(v1.26.3 用户拍板):**仅外部面**——轨迹约束 等对外输出会话
9
+ * (session id `轨迹约束-`/`rebuild-` 前缀;v1.22.4 rebuild 新建会话同面)。
10
10
  * **本地维护会话(普通 dsh 会话)不检测**——维护会话必然提及机制词
11
11
  * (dsh-serenity-hooks/cognitive_trajectory rebuild/mech-registry),打回会瘫痪自身工作。
12
12
  *
13
13
  * 🔴 **2026-10-15 补注(面 E 退役)**:`acp-` 前缀分支**已成孤儿,但本批不动**。
14
14
  * 判据(两条,实测):① **该前缀从来没有生产者** —— 历史 `acp-core.ts` 建会话走的是
15
- * `createSkiffAgent`,其 id = `` `skiff-${role}-${uuid}` ``(`trajectory/session.ts`)⇒ `acp-` **从不出现**;
15
+ * `createTrajectoryAgent`,其 id = `` `轨迹约束-${role}-${uuid}` ``(`trajectory/session.ts`)⇒ `acp-` **从不出现**;
16
16
  * ② 面 E 退役后连"本可能产生它的模块"也不在了。⇒ 该分支是**恒 false 的死判据**。
17
17
  * ⚠️ **仍不擅自删**:这是**运行时字符串比较**,删它属**行为变更**(不是纯清理),
18
18
  * 依本轨既有先例(`cheap-worker-` 四处置孤儿,同族、同判据)⇒ **登记待裁**(见 S142 §当前状态「观察」)。
19
19
  */
20
20
  import type { Context } from 'cordis';
21
21
  import type { Agent } from '@deepseek-ai/dsh-agent';
22
- /** 读会话最后一个 assistant/message 文本(skiff-core 同款)
22
+ /** 读会话最后一个 assistant/message 文本(trajectory-session 同款)
23
23
  * **v1.63.3 起唯一的导出理由消失**:原导出是给 `unattended-seam.ts` 复用的(D77),
24
24
  * 该文件已随无人值守功能整条退役 ⇒ 现只有本模块自己用(`isLeak` 的调用点)。
25
25
  * ⚠️ 保持 `export` 无害(它有测试覆盖,且将来"同一读法只留一份"的纪律仍成立),
@@ -10,7 +10,7 @@
10
10
  * - `ctx.effect` 是宿主成员,但**不可假定存在**(测试替身、极旧宿主、HMR 早期阶段)。
11
11
  * 直接裸调会让 `apply` 抛错 = 整个 dsh 启动失败(app-boot "plugin(s) failed to load"),
12
12
  * 这比"资源没拆卸"严重得多——所以缺失时**响亮降级**(记录 + 返回 false)。
13
- * - 零依赖叶模块:gateway 等模块只引它,不会因此被拖进 skiff/rebuild 的依赖链。
13
+ * - 零依赖叶模块:gateway 等模块只引它,不会因此被拖进 轨迹约束/rebuild 的依赖链。
14
14
  */
15
15
  /** 测试辅助:重置"未装配"告警去重(生产零调用) */
16
16
  export declare function __resetDisposerWarningsForTest(): void;
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * **CCC 级**:dsh 一个进程含多个 CCC,每个 CCC 独立对接微信桥(S142 用户拍板)——
5
5
  * 扫描 live CCC → 每 CCC 读 weixin 配置(enabled + accounts + routes)→
6
- * 启动该 CCC 该账号的 iLink 轮询循环。消息 → weixin-route 路由 → skiff 核心直调
6
+ * 启动该 CCC 该账号的 iLink 轮询循环。消息 → weixin-route 路由 → 轨迹约束核心直调
7
7
  * (session/new + session/prompt,同进程不经网络)。
8
8
  *
9
9
  * 🔴 **一处陈旧注释的更正(2026-10-15,随面 E 退役一并核实)**:本行原写「→ **AcpServer 直调**
10
10
  * (…——acp-core 传输无关设计)」。**该描述从来不是事实** —— 实测 grep 全仓:本文件**从不**
11
- * `import acp-core`(它 import 的是 `skiff-core` / `skiff-role` / `weixin-*`),
11
+ * `import acp-core`(它 import 的是 `trajectory-session` / `trajectory-constraint` / `weixin-*`),
12
12
  * 而 `acp-core` 的消费者**自始至终只有两个**(`acp-http.ts` + 其测试)。
13
13
  * ⇒ 当年那句是**设计意图的误记**,不是依赖关系;面 E 退役后它更是彻底失效。
14
14
  * 🔵 判据复述:**注释里的"设计意图"不能当依赖判据**,必须 grep import。
@@ -21,7 +21,7 @@
21
21
  * `im/inbound.ts`(机器级机制;本文件只接线)。这是"微信桥上移 ACC 层"的第一步。
22
22
  *
23
23
  * 外部面纯净(D9/D11 延续):微信桥会话 = 外部面——session/prompt 走
24
- * includeCognitiveTrajectory:false(对外不返回轨迹);skiff 角色白名单即授权(G9)。
24
+ * includeCognitiveTrajectory:false(对外不返回轨迹);轨迹约束角色白名单即授权(G9)。
25
25
  */
26
26
  import type { Context } from 'cordis';
27
27
  import { type WeixinAccountCredential } from './route.js';
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 为什么单独一个模块(R↓):地址的读者是 `msm-ops.buildMsmEnv`(把地址注入 MSM 子进程 env),
5
5
  * 写者才是 `weixin-send-api`(监听器启停)。若 msm-ops 直接 import weixin-send-api,
6
- * 静态依赖链会拖进整条微信桥栈(weixin-bridge → skiff-core → `@deepseek-ai/dsh-llm`)——
7
- * 后果实测:`acc-extras` / `ops` / `skiff-admin` 三个测试套件因解析不到宿主包而**整文件加载失败**
6
+ * 静态依赖链会拖进整条微信桥栈(weixin-bridge → trajectory-session → `@deepseek-ai/dsh-llm`)——
7
+ * 后果实测:`acc-extras` / `ops` / `trajectory-constraint-admin` 三个测试套件因解析不到宿主包而**整文件加载失败**
8
8
  * (它们只用到 MSM 注册表逻辑)。叶模块把「读」与「写」解耦:读者零依赖,写者自己承担重量。
9
9
  *
10
10
  * 同款先例:`host/effect.ts`(拆卸登记)、`trajectory/carrier.ts`(等待结算)。
package/lib/im/guard.d.ts CHANGED
@@ -58,7 +58,7 @@ export declare function buildManualOutputRebuke(session: ManualOutputSession): s
58
58
  export declare function __resetWeixinOutputGuardForTest(): void;
59
59
  /**
60
60
  * 装配闸门(index.ts apply 调用)。
61
- * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff 会话)零干预。
61
+ * 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 轨迹约束会话)零干预。
62
62
  */
63
63
  export declare function registerWeixinOutputGuard(ctx: Context): void;
64
64
  export {};
package/lib/im/hook.d.ts CHANGED
@@ -33,7 +33,7 @@ interface WeixinHookEventBase {
33
33
  userId: string;
34
34
  /** 固定会话 id(同用户长期同一:skiff-weixin-xxx) */
35
35
  sessionId: string;
36
- /** 路由命中的 skiff role */
36
+ /** 路由命中的 轨迹约束 role */
37
37
  role: string;
38
38
  }
39
39
  /** incoming 事件(用户 → bot):文本(含语音转写)+ 媒体(已落盘 relPath) */
@@ -29,7 +29,7 @@
29
29
  * - ✅ 只负责:「一条已到达的消息,该投到哪条认知轨迹、怎么答、怎么记账」。
30
30
  */
31
31
  import type { Context } from 'cordis';
32
- import { type SkiffProfile } from '../trajectory/policy/profile.js';
32
+ import { type TrajectoryConstraint } from '../trajectory/policy/profile.js';
33
33
  /**
34
34
  * 一条**已到达**的入站消息(归一后)。
35
35
  *
@@ -91,7 +91,7 @@ export interface ImAccountIo {
91
91
  }): Promise<void>;
92
92
  }
93
93
  /**
94
- * 路由解析结果:目标根 + **轨迹编码** + **该轨迹的 skiff 约束面**。
94
+ * 路由解析结果:目标根 + **轨迹编码** + **该轨迹的 轨迹约束面**。
95
95
  * `null` 从本函数出去 ⇒ **不投递**(未命中已留痕 / 目标不存在已响亮出声)。
96
96
  */
97
97
  export interface ResolvedInboundTarget {
@@ -99,11 +99,11 @@ export interface ResolvedInboundTarget {
99
99
  /** 目标认知轨迹编码(= 新模型里的"身份";旧模型的角色名已退役) */
100
100
  trajectory: string;
101
101
  /**
102
- * 该轨迹的 skiff 约束面。
103
- * ⚠️ **可能为 `null`**:轨迹存在但没有 `skiff.json` ⇒ 它是一条**普通轨迹**,
104
- * IM 桥照样可以往里投,只是不施加 skiff 约束。
102
+ * 该轨迹的 轨迹约束面。
103
+ * ⚠️ **可能为 `null`**:轨迹存在但没有 `trajectory-constraint.json` ⇒ 它是一条**普通轨迹**,
104
+ * IM 桥照样可以往里投,只是不施加 轨迹约束 约束。
105
105
  */
106
- profile: SkiffProfile | null;
106
+ profile: TrajectoryConstraint | null;
107
107
  }
108
108
  /**
109
109
  * **入站路由的单一解析处**(P0-3):机器级订阅表优先 → CCC 级回退;三档语义都在这里落地。
package/lib/im/route.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * 职责(零 DSH 依赖,可独立单测):
5
5
  * - 读取 CCC 的 weixin 配置(.opencode/serenity.json weixin 段——结构/路由/开关)
6
6
  * - 读取/写入 CCC localstore 的账号凭据(weixin.accounts.<accountId>.token/.baseUrl/.userId)
7
- * - 微信用户 → skiff 会话 id 映射(固定可重建:skiff-weixin-<sha256(userid)>.slice(1, 17);
7
+ * - 微信用户 → 轨迹约束会话 id 映射(固定可重建:skiff-weixin-<sha256(userid)>.slice(1, 17);
8
8
  * v1.27.3 错位一位避开损坏 log)
9
9
  * - 路由匹配(exact user → 通配 * 兜底)
10
10
  *
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { type WeixinSettings, type WeixinRouteConfig, type WeixinAccountConfig, type ImSettings } from '../kernel/ccc.js';
15
15
  import type { WeixinMediaRef, WeixinMessageItem } from './ilink.js';
16
- /** 判定 sessionId 是否为微信桥会话(**命名形态**判定;权力面判据见 `isSkiffSession`) */
16
+ /** 判定 sessionId 是否为微信桥会话(**命名形态**判定;权力面判据见 `isTrajectoryConstrained`) */
17
17
  export declare function isWeixinSessionId(sessionId: string | undefined): boolean;
18
18
  /**
19
19
  * **轨迹编码 → 固定的 dsh 会话 id**(2026-10-19 重做:身份挂在轨迹上,不挂在用户上)。
@@ -25,7 +25,7 @@ export declare function isWeixinSessionId(sessionId: string | undefined): boolea
25
25
  * 对不上旧会话的历史工具清单)。⇒ 那不是"轨迹是一等公民",那是"用户是一等公民"。
26
26
  *
27
27
  * 新派生 = **由轨迹编码决定**:同一条轨迹 ⇒ 恒同一条 dsh 会话(无论谁在跟它说话);
28
- * 换轨迹 ⇒ 新会话。这正是"skiff 是认知轨迹的一种"。
28
+ * 换轨迹 ⇒ 新会话。这正是"轨迹约束是认知轨迹的一种"。
29
29
  *
30
30
  * ## 连带删掉的东西
31
31
  *
@@ -91,6 +91,20 @@ export declare function readFeishuCredential(root: string, accountId: string): F
91
91
  export declare function writeFeishuCredential(root: string, accountId: string, cred: FeishuAccountCredential): void;
92
92
  /** 清除飞书账号凭据(移除账号时) */
93
93
  export declare function clearFeishuCredential(root: string, accountId: string): void;
94
+ /**
95
+ * **按渠道判「该账号是否已绑定凭据」**(面板读数用;单一处)。
96
+ *
97
+ * 🔴 为什么需要它(2026-10-19 P2 面板实测缺陷):面板原先对所有账号都调
98
+ * `readWeixinCredential`(只看 `WEIXIN_<A>_TOKEN`)。一个**飞书**账号的凭据在
99
+ * `FEISHU_<A>_APPID|SECRET`,走那条读法**必然读到 null** ⇒ 面板把一个配好的飞书账号
100
+ * 显示成「未绑定凭据」,用户据此以为没配上。
101
+ *
102
+ * 判据与装配处**同源**(`im/bridge.ts` 的 `readImCredentialByChannel`):飞书要 appId **且**
103
+ * appSecret,微信只要有 token。两处若各写一份判据,就会重演"两个读数器给出不同答案"。
104
+ *
105
+ * ⚠️ 只回**布尔**,不回任何凭据值 —— 这是 wire 面。
106
+ */
107
+ export declare function imCredentialBound(root: string, accountId: string, channel: string | undefined): boolean;
94
108
  /**
95
109
  * 路由匹配:exact user 优先,通配 `*` 兜底。
96
110
  * @returns 命中的**轨迹编码**;未命中 → null(无路由 → 消息不回复/忽略)
@@ -145,5 +159,13 @@ export declare function saveWeixinRoutes(root: string, routes: WeixinRouteConfig
145
159
  * 移除中间账号后不冲突(wechat-2 被删 → 新账号复用 wechat-2 而非跳到 4)。
146
160
  */
147
161
  export declare function nextWeixinAccountId(settings: WeixinSettings): string;
162
+ /**
163
+ * 生成下一个可用飞书账号 id(`feishu-N`,N 取**最小未占用**)。
164
+ *
165
+ * 与 {@link nextWeixinAccountId} 分开而不是加参数:两者的**前缀不同**,
166
+ * 而前缀是给人看的(面板上一眼分清哪些账号来自哪个渠道)。
167
+ * 判据相同(最小未占用,删中间不跳号)。
168
+ */
169
+ export declare function nextFeishuAccountId(settings: WeixinSettings): string;
148
170
  /** 切换总开关 */
149
171
  export declare function setWeixinEnabled(root: string, enabled: boolean): WeixinSettings;