@shgroup/dsh-serenity-hooks 1.73.0 → 1.75.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 (48) hide show
  1. package/README.md +18 -12
  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-BVxn97dY.js} +4 -4
  6. package/lib/client.js +116 -1
  7. package/lib/cro/core.d.ts +11 -1
  8. package/lib/cro/log.d.ts +54 -4
  9. package/lib/guard/seam.d.ts +4 -4
  10. package/lib/host/effect.d.ts +1 -1
  11. package/lib/im/bridge.d.ts +3 -3
  12. package/lib/im/endpoint.d.ts +2 -2
  13. package/lib/im/guard.d.ts +1 -1
  14. package/lib/im/hook.d.ts +1 -1
  15. package/lib/im/inbound-event.d.ts +6 -6
  16. package/lib/im/route.d.ts +25 -3
  17. package/lib/index.d.ts +5 -5
  18. package/lib/index.js +680 -295
  19. package/lib/kernel/ccc-roots.d.ts +4 -4
  20. package/lib/kernel/ccc.d.ts +20 -19
  21. package/lib/kernel/ports.d.ts +1 -1
  22. package/lib/{message-source-ChyVfev9.js → message-source-JhN-qyfn.js} +2 -2
  23. package/lib/patches/opencode-provider.d.ts +1 -1
  24. package/lib/{profile-BIQ2widI.js → profile-Cz4oBXlI.js} +197 -65
  25. package/lib/{route-ItCgAsnz.js → route-COOFLTpU.js} +44 -6
  26. package/lib/seams/guards.d.ts +14 -14
  27. package/lib/seams/keeper.d.ts +1 -1
  28. package/lib/seams/lifecycle.d.ts +2 -2
  29. package/lib/{settings-UcwIPJhM.js → settings-CII0Y597.js} +40 -26
  30. package/lib/surface/faces.d.ts +1 -1
  31. package/lib/surface/http.d.ts +1 -1
  32. package/lib/surface/settings.d.ts +7 -7
  33. package/lib/surface/text.d.ts +3 -3
  34. package/lib/tools/config-docs.d.ts +71 -0
  35. package/lib/tools/container-admin.d.ts +24 -12
  36. package/lib/tools/msm/service.d.ts +18 -0
  37. package/lib/tools/trajectory-constraint-admin.d.ts +30 -0
  38. package/lib/trajectory/carrier.d.ts +1 -1
  39. package/lib/trajectory/core.d.ts +11 -1
  40. package/lib/trajectory/policy/files.d.ts +75 -0
  41. package/lib/trajectory/policy/profile.d.ts +41 -36
  42. package/lib/trajectory/policy/rules.d.ts +33 -20
  43. package/lib/trajectory/policy/window.d.ts +13 -13
  44. package/lib/trajectory/prompt.d.ts +12 -12
  45. package/lib/trajectory/rebuild.d.ts +2 -2
  46. package/lib/trajectory/session.d.ts +49 -49
  47. package/package.json +2 -2
  48. package/lib/tools/skiff-admin.d.ts +0 -30
@@ -83,9 +83,9 @@ export interface CccEntry {
83
83
  /** 目录名(展示用) */
84
84
  name: string;
85
85
  /**
86
- * 该 CCC 的 skiff 列表(**轨迹编码**;新模型里编码即身份)。
86
+ * 该 CCC 的 轨迹约束 列表(**轨迹编码**;新模型里编码即身份)。
87
87
  * ⚠️ **仅 `withRoles: true` 时非空**;缺省 `false` ⇒ 恒为 `[]` 且**不扫
88
- * `AGENT_SESSIONS/`**(唤醒调度器每 tick 都要枚举,不该为 skiff 付 IO)。
88
+ * `AGENT_SESSIONS/`**(唤醒调度器每 tick 都要枚举,不该为 轨迹约束 付 IO)。
89
89
  */
90
90
  roles: string[];
91
91
  }
@@ -95,7 +95,7 @@ export interface ListCccsOptions {
95
95
  * 传 `undefined`/`''` 表示没有默认根。
96
96
  */
97
97
  defaultRoot?: string;
98
- /** 是否实时扫各 CCC 的 skiff 轨迹(缺省 false)。 */
98
+ /** 是否实时扫各 CCC 的 轨迹约束轨迹(缺省 false)。 */
99
99
  withRoles?: boolean;
100
100
  }
101
101
  /**
@@ -110,7 +110,7 @@ export interface ListCccsOptions {
110
110
  * **整个列表都不含后者**("名单不全")。并集修正它,且**不改**任何消费方的输入契约。
111
111
  *
112
112
  * @param ctx 插件上下文
113
- * @param opts.defaultRoot 默认根(不在列表 → 置首);opts.withRoles 是否读 `skiff.roles`
113
+ * @param opts.defaultRoot 默认根(不在列表 → 置首);opts.withRoles 是否读 `轨迹约束.roles`
114
114
  * @returns 按上述顺序去重的 CCC 条目(无任何来源 → 空数组 + 可选默认根)
115
115
  */
116
116
  export declare function listCccs(ctx: unknown, opts?: ListCccsOptions): Promise<CccEntry[]>;
@@ -87,14 +87,15 @@ interface SerenityConfig {
87
87
  autoRestoreSession?: boolean;
88
88
  };
89
89
  /**
90
- * 🔴 **`skiff` 段已于 2026-10-19 退役**(owner 重做令)。
90
+ * 🔴 **`轨迹约束` 段已于 2026-10-19 退役**(owner 重做令)。
91
91
  *
92
- * skiff 不再是 `serenity.json` 里的一组角色定义,而是**带 `skiff.json` 的认知轨迹**
93
- * (约束面住在轨迹目录里)。⇒ 本段**不再被读取**:存量配置里残留的 `skiff.roles` ⇒
94
- * **静默忽略**(schemastery 非 strict,不报错;也不会被误当成"没有 skiff")。
92
+ * 轨迹约束 不再是 `serenity.json` 里的一组角色定义,而是**带 `trajectory-constraint.json` 的认知轨迹**
93
+ * (约束面住在轨迹目录里)。⇒ 本段**不再被读取**:存量配置里残留的 `轨迹约束.roles` ⇒
94
+ * **静默忽略**(schemastery 非 strict,不报错;也不会被误当成"没有 轨迹约束")。
95
95
  *
96
- * 迁移路径 = 把 `skiff.roles.<name>` 的内容原样搬进对应轨迹目录的 `skiff.json`
97
- * (判据与步骤见 `trajectory/policy/profile.ts` 头注释与 `docs/skiff-as-trajectory-redesign.md` §5)。
96
+ * 迁移路径 = 把 `轨迹约束.roles.<name>` 的内容原样搬进对应轨迹目录的声明文件
97
+ * (判据与步骤见 `trajectory/policy/profile.ts` 头注释)。
98
+ * 🔴 2026-10-19:声明文件已收拢为 **`trajectory.json`**(旧名仍在**读取族**里,写只写新名)。
98
99
  */
99
100
  /**
100
101
  * 认知轨迹族配置(D58,S142 2026-09-13;2026-10-18 更名 `cognitiveTrajectory`)。
@@ -109,7 +110,7 @@ interface SerenityConfig {
109
110
  */
110
111
  cognitiveTrajectory?: {
111
112
  /**
112
- * **CCC 级 skill 供给**(v1.44.0;owner 令 2026-09-20:「要求实现我的诉求……skiff 也支持」)。
113
+ * **CCC 级 skill 供给**(v1.44.0;owner 令 2026-09-20:「要求实现我的诉求……轨迹约束也支持」)。
113
114
  *
114
115
  * 语义:**本 CCC 的每条轨迹都带这些 skill**——声明的 skill 全文在该轨迹**被绑定期间**
115
116
  * 动态注入系统提示词(每请求重新求值,不缓存)。与轨迹自己在 `SESSION.md` frontmatter
@@ -124,7 +125,7 @@ interface SerenityConfig {
124
125
  * 配置损坏本身已由 `loadSerenityConfig` 响亮告警(不静默)。
125
126
  *
126
127
  * ⚠️ 与 `create` 的关系:**不触发**——`create` 刻意不夺绑定(U6),注入的门是"存在绑定"。
127
- * ⚠️ 作用面:**含 skiff 角色会话**(它们同样绑定工作台轨迹)。
128
+ * ⚠️ 作用面:**含 轨迹约束角色会话**(它们同样绑定工作台轨迹)。
128
129
  */
129
130
  skills?: string[];
130
131
  };
@@ -223,7 +224,7 @@ export interface ImRouteConfig {
223
224
  * - 结构/路由/开关在 .opencode/serenity.json(git 管可重建)
224
225
  * - **bot_token 凭据在 CCC localstore**(weixin.accounts.<accountId>.token/.baseUrl/.userId)——
225
226
  * 扫码登录后插件自动写入(面板只显示"已绑定"),永不进 git 明文面
226
- * - 路由 user → role:exact 匹配优先,通配 `*` 兜底;role 必须是该 CCC skiff.roles 之一
227
+ * - 路由 user → role:exact 匹配优先,通配 `*` 兜底;role 必须是该 CCC 轨迹约束.roles 之一
227
228
  * - 多账号:accounts[] 每项 { accountId, name?, enabled }(本地唯一键,如 wechat-1)
228
229
  *
229
230
  * 🔴 2026-10-19:**本段已被统一 `im` 段取代**(见上方 `ImSettings`)。此处保留是因为
@@ -236,7 +237,7 @@ export interface WeixinSettings {
236
237
  botType?: string;
237
238
  /** 账号表(本地键 accountId;凭据在 localstore) */
238
239
  accounts?: WeixinAccountConfig[];
239
- /** 消息路由:微信用户 → 该 CCC 的 skiff role(不绑定具体 role——用户自选) */
240
+ /** 消息路由:微信用户 → 该 CCC 的 轨迹约束 role(不绑定具体 role——用户自选) */
240
241
  routes?: WeixinRouteConfig[];
241
242
  /**
242
243
  * 用户别名表(v1.56.10):`{ "<别名>": "<localstore 凭据键>" }`。
@@ -298,8 +299,8 @@ export interface WeixinAccountConfig {
298
299
  * 微信桥路由配置:微信用户 → 该 CCC 的**认知轨迹编码**
299
300
  *
300
301
  * 🔴 2026-10-19(owner 重做令):「微信桥的路由表修改为 **user → 认知轨迹的映射**」。
301
- * 旧形态指向 `skiff.roles` 里的**角色名**;新形态直接指向**轨迹编码**
302
- * (= 目录名固定位置那一段;该轨迹目录里有 `skiff.json` 时它就是 skiff)。
302
+ * 旧形态指向 `轨迹约束.roles` 里的**角色名**;新形态直接指向**轨迹编码**
303
+ * (= 目录名固定位置那一段;该轨迹目录里有 `trajectory-constraint.json` 时它就是 轨迹约束)。
303
304
  */
304
305
  export interface WeixinRouteConfig {
305
306
  /** 微信用户标识(from_user_id,形如 userA@im.wechat);`*` = 通配兜底 */
@@ -314,18 +315,18 @@ export interface WeixinRouteConfig {
314
315
  role?: string;
315
316
  }
316
317
  /**
317
- * 🔴 **`SkiffRoleConfig` 已于 2026-10-19 退役**(owner 重做令)。
318
+ * 🔴 **`TrajectoryConstraintConfig` 已于 2026-10-19 退役**(owner 重做令)。
318
319
  *
319
- * 它的语义整体搬进**轨迹目录下的 `skiff.json`**,类型现在是
320
- * `trajectory/policy/profile.ts` 的 `SkiffProfile`(在那里还多了 `code`/`dirName`/`path`
320
+ * 它的语义整体搬进**轨迹目录下的 `trajectory-constraint.json`**,类型现在是
321
+ * `trajectory/policy/profile.ts` 的 `TrajectoryConstraint`(在那里还多了 `code`/`dirName`/`path`
321
322
  * 三个与轨迹身份相关的字段)。
322
323
  *
323
- * 退役理由(一句话):skiff 的约束面是**某一条轨迹的属性**,不是容器级配置表里的一个 entry。
324
- * 放进 `serenity.json` 会让"这条轨迹是不是 skiff"分散在**两个文件**里,而 owner 要的是
325
- * 一条判据(「目录下拥有 `skiff.json` 的认知轨迹自动是一个 skiff」)。
324
+ * 退役理由(一句话):轨迹约束的约束面是**某一条轨迹的属性**,不是容器级配置表里的一个 entry。
325
+ * 放进 `serenity.json` 会让"这条轨迹是不是 轨迹约束"分散在**两个文件**里,而 owner 要的是
326
+ * 一条判据(「目录下拥有 `trajectory-constraint.json` 的认知轨迹自动是一个 轨迹约束」)。
326
327
  *
327
328
  * ⚠️ 旧字段里 `kind: temporary|persistent` **整件无对应物**:它区分"有没有工作台",
328
- * 而新模型里 skiff 恒等于一条轨迹 ⇒ 恒有 SESSION.md ⇒ 该区分没有指称对象(静默忽略)。
329
+ * 而新模型里 轨迹约束恒等于一条轨迹 ⇒ 恒有 SESSION.md ⇒ 该区分没有指称对象(静默忽略)。
329
330
  */
330
331
  /**
331
332
  * 解析 cheap-worker 配置。
@@ -36,7 +36,7 @@ export interface PortSpec {
36
36
  /**
37
37
  * 集中端口表。
38
38
  * A=宿主主面(插件不监听)/ B=网关(可配,默认关)/ C=微信发送(默认开,仅 loopback)
39
- * / D=Skiff 调试(默认关)。
39
+ * / D=轨迹约束调试(默认关)。
40
40
  */
41
41
  export declare const FACE_PORTS: Readonly<Record<FaceId, PortSpec>>;
42
42
  /** 宿主主 WebUI 端口(面 A:插件**不监听**,只读取;`readWebPort` 的回退值) */
@@ -1,5 +1,5 @@
1
- import { r as cccRootForCwd } from "./ccc-roots-BhoA7G-0.js";
2
- import { T as parseTrajectoryCode, b as getLastActiveSessionInfo, k as sessionEvents, y as getActiveSessionInfo } from "./profile-BIQ2widI.js";
1
+ import { r as cccRootForCwd } from "./ccc-roots-BVxn97dY.js";
2
+ import { T as parseTrajectoryCode, b as getLastActiveSessionInfo, k as sessionEvents, y as getActiveSessionInfo } from "./profile-Cz4oBXlI.js";
3
3
  import { dirname, join } from "node:path";
4
4
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
5
  //#region src/trajectory/bound.ts
@@ -191,7 +191,7 @@ export declare function applyOpencodeAutoConfigOnce(ctx: Context, env?: Readonly
191
191
  /**
192
192
  * 装配 opencode 路由自动配置。
193
193
  *
194
- * 触发面(三层,与 skiff 同款;R↓:单点触发在同装载竞态下必然漏):
194
+ * 触发面(三层,与 轨迹约束 同款;R↓:单点触发在同装载竞态下必然漏):
195
195
  * ① apply 时立即试一次
196
196
  * ② 命名空间尚未注册 → 退避重试(1s/3s/8s/20s/40s)
197
197
  * ③ `settings/updated` → 再试(用户热改 settings.yaml 加路由时自愈;自身写入触发的复评是幂等空转)
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
2
- import { _ as resolveInside, g as readUtf8 } from "./ccc-CZUOq3NY.js";
2
+ import { _ as resolveInside, g as readUtf8 } from "./ccc-CyYJL4F2.js";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
5
5
  //#region src/kernel/time.ts
@@ -88,6 +88,114 @@ function localIdStamp(value = Date.now()) {
88
88
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
89
89
  }
90
90
  //#endregion
91
+ //#region src/trajectory/policy/files.ts
92
+ /**
93
+ * trajectory/policy/files.ts — **轨迹级文件的文件名族**(owner 2026-10-19 裁 Q2:短名 `trajectory.json`)
94
+ *
95
+ * ## 这一层解决什么(白话)
96
+ *
97
+ * 一条轨迹目录里,ACC 自己产的文件原先有**四个名字各异的文件**:
98
+ * `trajectory-constraint.json`(约束面)/`TRAJECTORY-UID`(只读遗留的旧标识)/
99
+ * `cro-host-binding`(一行机器名)/`cro-wake-log.json`(CRO 流水)。
100
+ * 后两个里,**「声明」性质的那两个现在并进一个 JSON**(流水不并 —— 见 `cro/log.ts` 的理由)。
101
+ *
102
+ * ⇒ 收拢后的形态:
103
+ *
104
+ * ```text
105
+ * <轨迹目录>/
106
+ * SESSION.md ← 轨迹的身体(不动)
107
+ * PROMPT.md ← 章程(不动)
108
+ * continuous-re-occurrence.ts← CRO 程序(不动)
109
+ * trajectory.json ← 🆕 声明面:约束 + 旧标识(code) + cro.host
110
+ * cro-wake-log.log ← CRO 流水(高频追加,**刻意不并入**)
111
+ * ```
112
+ *
113
+ * ## 🔴 为什么不硬切(本容器已两次栽在"上游改名、下游写死旧名")
114
+ *
115
+ * 读取侧认**族**:`trajectory.json`(新)→ `trajectory-constraint.json`(v1.74 名)→
116
+ * `skiff.json`(v1.71 名)。**任一存在即成立**。上游再改名只往族里加一项,不再单点写死。
117
+ *
118
+ * 🔴 **`cro-host-binding` 必须两代并读,这一条是有行为后果的**
119
+ * (理由逐字保留在 `cro/core.ts` 的 `resolveCroHostBinding` 头注):
120
+ * 绑定声明要**跟着轨迹走到第二台机器**,在那里被判成「绑给了别人」而**正确地不跑**。
121
+ * 若硬切改名,第二台机器读到的是**「没绑」** —— **表面结果相同,但判据退化**
122
+ * (失去"明确绑给别人"的可诊断性)⇒ 只能两代并读。
123
+ *
124
+ * ## 归属(D23)
125
+ * 本模块只管**机制**(文件叫什么、按什么顺序找、怎么读);**内容归 CCC**
126
+ * (JSON 里填什么:模型 / 白名单 / 提示词路径 / 机器名)。
127
+ *
128
+ * ## 边界与铁律
129
+ * 本模块**零本地依赖**(只用 `node:fs` / `node:path`)⇒ 谁 import 它都不会成环。
130
+ * 每个读取函数**永不抛**:文件不存在 / 坏 JSON ⇒ `null`,由调用方各自决定降级方式。
131
+ */
132
+ /**
133
+ * 收拢后的**新**文件名(owner 裁 Q2「短名」)。
134
+ *
135
+ * 🔴 **为什么要改名**:它装的不止"约束"了(还有 `code` 与 `cro.host`)
136
+ * ⇒ 留着旧名会让**名字本身就是错名**(D151 同族:界面/文档里的名字会随内容演进而撒谎)。
137
+ */
138
+ const TRAJECTORY_FILENAME = "trajectory.json";
139
+ /**
140
+ * **判据族**:任一存在 ⇒ 这条轨迹有声明面(= 旧模型说的"它是一个轨迹约束")。
141
+ *
142
+ * ⚠️ **顺序即优先级**(第一个找到的胜过后面的):新名优先,两个旧名按代际倒序。
143
+ * ⚠️ **准入面宽于约束面是有意的**:约束文件缺失的轨迹仍需要能被当作轨迹处理。
144
+ */
145
+ const TRAJECTORY_FILENAME_FAMILY = [
146
+ TRAJECTORY_FILENAME,
147
+ "trajectory-constraint.json",
148
+ "skiff.json"
149
+ ];
150
+ /** 旧名的单独导出(调用方要单独判断"这是不是旧档"时用;顺序同上、不含新名) */
151
+ const TRAJECTORY_LEGACY_FILENAMES = TRAJECTORY_FILENAME_FAMILY.slice(1);
152
+ /**
153
+ * 在一条轨迹目录里找**声明文件**(族内第一个存在的)。
154
+ *
155
+ * @param trajectoryDir 轨迹目录绝对路径
156
+ * @returns 命中的文件绝对路径;族内一个都不存在 ⇒ `null`(= 普通轨迹,**常态不是错误**)
157
+ */
158
+ function findTrajectoryFile(trajectoryDir) {
159
+ for (const name of TRAJECTORY_FILENAME_FAMILY) {
160
+ const p = join(trajectoryDir, name);
161
+ try {
162
+ if (existsSync(p)) return p;
163
+ } catch {}
164
+ }
165
+ return null;
166
+ }
167
+ /**
168
+ * 读一条轨迹目录里的声明文件,解析成对象(**永不抛**)。
169
+ *
170
+ * @returns `{ file, data }`:`file` 是命中的文件名(族内哪个),`data` 是解析后的对象;
171
+ * **族内无文件 ⇒ `null`**;**坏 JSON / 顶层非对象 ⇒ `{ file, data: null, error }`**
172
+ * —— 这三种情形调用方的降级方式各不相同(普通轨迹 / 告警后按普通轨迹),故分开给。
173
+ */
174
+ function readTrajectoryFile(trajectoryDir) {
175
+ const file = findTrajectoryFile(trajectoryDir);
176
+ if (file === null) return null;
177
+ let raw;
178
+ try {
179
+ raw = JSON.parse(readFileSync(file, "utf-8"));
180
+ } catch (err) {
181
+ return {
182
+ file,
183
+ data: null,
184
+ error: `JSON 解析失败(${String(err?.message ?? err)})`
185
+ };
186
+ }
187
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {
188
+ file,
189
+ data: null,
190
+ error: "顶层不是对象"
191
+ };
192
+ return {
193
+ file,
194
+ data: raw,
195
+ error: null
196
+ };
197
+ }
198
+ //#endregion
91
199
  //#region src/trajectory/core.ts
92
200
  /**
93
201
  * trajectory/core.ts — 轨迹工具纯操作层(零 DSH 依赖,可独立单测)
@@ -123,7 +231,7 @@ function localIdStamp(value = Date.now()) {
123
231
  *
124
232
  * ⚠️ 由此产生一条**必须成立**的配套:`readAllSessions` **跳过 `_` 前缀目录**。
125
233
  * 否则归档后的轨迹会继续出现在清单里,"归档"就不是归档了(历史 bug:`_weixin-logs` /
126
- * `_skiff-logs` / `_corpus` 曾被当成轨迹列出来)。
234
+ * `_轨迹约束-logs` / `_corpus` 曾被当成轨迹列出来)。
127
235
  */
128
236
  const COGNITIVE_TRAJECTORY_ACTIONS = [
129
237
  "list",
@@ -199,7 +307,7 @@ function readSessionEntry(dirPath) {
199
307
  * 读取 AGENT_SESSIONS 中所有**活跃**轨迹,按 mtime 降序。
200
308
  *
201
309
  * 🔴 **跳过 `_` 前缀目录**(2026-10-18 新增,与归档配套):
202
- * `_archived` 是归档区,`_skiff-logs` / `_weixin-logs` / `_corpus` 是**日志与数据区**——
310
+ * `_archived` 是归档区,`_轨迹约束-logs` / `_weixin-logs` / `_corpus` 是**日志与数据区**——
203
311
  * 它们**都不是轨迹**。旧实现会把它们当轨迹列出来(历史 bug)。
204
312
  * 没有这条,归档后的轨迹仍会出现在清单里 ⇒ **"归档"名不副实**。
205
313
  *
@@ -309,8 +417,25 @@ function isValidUid(uid) {
309
417
  * 🔴 **形态非法的文件按"没设定"处理,而不是报错**:解析侧出现在**每一次** `findSession` 里
310
418
  * (show/use/archive/send-now/send-later 全走它),在那里抛错会让一个坏文件**炸掉整条轨迹的所有操作**
311
419
  * —— 惩罚远大于问题本身。而写入侧已退役 ⇒ 坏文件只可能来自外部手改,更不该连坐。
420
+ *
421
+ * ## 🆕 2026-10-19 owner 裁 Q2:**声明文件里的 `uid` 优先,独立文件回落**
422
+ *
423
+ * 收拢后这个旧标识住在 `<轨迹目录>/trajectory.json` 的 `uid` 字段里(它属"声明",
424
+ * 与约束面/机器绑定同源)。判据(**两代并读,不硬切**):
425
+ * **声明面里有合法 `uid` ⇒ 用它;否则回落 `TRAJECTORY-UID` 文件**。
426
+ *
427
+ * ⚠️ **为什么字段名是 `uid` 而不是方案书里写的 `code`**:`code` 在本仓已有一个**更窄的含义**
428
+ * —— 目录名里那个固定位置的轨迹编码(`parseTrajectoryCode`)。同一个词在一个 JSON 里表示两件事,
429
+ * 正是本轨反复抓的那类"名字会撒谎"。⇒ 旧标识就叫 `uid`(它本来就是 UID,见 `UID_FILENAME`)。
312
430
  */
313
431
  function readTrajectoryUid(sessionPath) {
432
+ try {
433
+ const fromJson = readTrajectoryFile(sessionPath)?.data?.["uid"];
434
+ if (typeof fromJson === "string") {
435
+ const trimmed = fromJson.trim();
436
+ if (isValidUid(trimmed)) return trimmed;
437
+ }
438
+ } catch {}
314
439
  try {
315
440
  const raw = readFileSync(join(sessionPath, UID_FILENAME), "utf-8").trim();
316
441
  return isValidUid(raw) ? raw : null;
@@ -751,74 +876,80 @@ function archiveSession(root, key, dryRun = false) {
751
876
  }
752
877
  //#endregion
753
878
  //#region src/trajectory/policy/window.ts
754
- const skiffSessions = /* @__PURE__ */ new Map();
879
+ /** sessionId → 绑定的轨迹(**进程内**;跨重启的权威来源是 `trajectory/bound.ts` 的持久绑定) */
880
+ const trajectorySessions = /* @__PURE__ */ new Map();
755
881
  /** 登记/更新一个会话 → 轨迹的映射 */
756
- function registerSkiffSession(sessionId, trajectoryDirName, ccc) {
757
- skiffSessions.set(sessionId, {
882
+ function registerTrajectorySession(sessionId, trajectoryDirName, ccc) {
883
+ trajectorySessions.set(sessionId, {
758
884
  trajectoryDirName,
759
885
  ccc
760
886
  });
761
887
  }
762
888
  /** 解绑(会话销毁 / 轨迹失效 ⇒ 换身份) */
763
- function unregisterSkiffSession(sessionId) {
764
- skiffSessions.delete(sessionId);
889
+ function unregisterTrajectorySession(sessionId) {
890
+ trajectorySessions.delete(sessionId);
765
891
  }
766
892
  /** 查会话承载的轨迹目录名(无 → null);`guards`/`system-prompt` 的同步热路径用 */
767
- function skiffTrajectoryDirNameFor(sessionId) {
768
- const b = skiffSessions.get(sessionId);
893
+ function trajectoryDirNameForSession(sessionId) {
894
+ const b = trajectorySessions.get(sessionId);
769
895
  return b === void 0 || b.trajectoryDirName === "" ? null : b.trajectoryDirName;
770
896
  }
771
897
  //#endregion
772
898
  //#region src/trajectory/policy/profile.ts
773
899
  /**
774
- * trajectory/policy/profile.ts — **skiff 的唯一判据**:目录下有 `skiff.json` 的认知轨迹(零 DSH 依赖,可独立单测)
900
+ * trajectory/policy/profile.ts — **轨迹约束的唯一判据**:目录下有 `trajectory-constraint.json` 的认知轨迹(零 DSH 依赖,可独立单测)
775
901
  *
776
902
  * ## 🔴 owner 2026-10-19 重做令(逐字,本文件存在的全部理由)
777
903
  *
778
- * > 「skiff 复杂的约束和提示词体系都干掉,改为这样的实现:
779
- * > **skiff 本身是认知轨迹的一个特殊情况,即目录下拥有 `skiff.json` 的认知轨迹自动是一个 skiff**
780
- * > (json 里配置上个版本就有的各种约束),**会话跑在认知轨迹上且发现 `skiff.json`,则运行时实现各类约束**」
904
+ * > 「轨迹约束 复杂的约束和提示词体系都干掉,改为这样的实现:
905
+ * > **轨迹约束本身是认知轨迹的一个特殊情况,即目录下拥有 `trajectory-constraint.json` 的认知轨迹自动是一个 轨迹约束**
906
+ * > (json 里配置上个版本就有的各种约束),**会话跑在认知轨迹上且发现 `trajectory-constraint.json`,则运行时实现各类约束**」
781
907
  *
782
908
  * ## 旧模型 → 新模型(**一条判据取代四条**)
783
909
  *
784
910
  * | 项 | 旧 | 新 |
785
911
  * |---|---|---|
786
- * | skiff 是什么 | `serenity.json` 的 `skiff.roles.<name>` 一条配置 | **轨迹目录里有 `skiff.json`** |
787
- * | 怎么认出 skiff 会话 | dsh sessionId 前缀 `skiff-` | **它绑定的轨迹有 `skiff.json`** |
788
- * | 工作台从哪来 | `ensureSkiffSession` 另建一个 SESSION | **就是该轨迹自己的 SESSION.md** |
912
+ * | 轨迹约束是什么 | `serenity.json` 的 `轨迹约束.roles.<name>` 一条配置 | **轨迹目录里有 `trajectory-constraint.json`** |
913
+ * | 怎么认出 轨迹约束会话 | dsh sessionId 前缀 `轨迹约束-` | **它绑定的轨迹有 `trajectory-constraint.json`** |
914
+ * | 工作台从哪来 | `ensureTrajectorySession` 另建一个 SESSION | **就是该轨迹自己的 SESSION.md** |
789
915
  * | 身份 | 角色名 | **轨迹编码**(`trajectory/core.ts` 的 `parseTrajectoryCode`) |
790
916
  *
791
917
  * ⇒ 收益(owner:「理解难度大幅下降」的实际内容):四个各自会漂的机制收敛为**一条文件存在性判据**。
792
- * 删掉 `skiff.json` ⇒ 该轨迹**当场**退化为普通轨迹(工具面、提示词、keeper 全部恢复默认)。
918
+ * 删掉 `trajectory-constraint.json` ⇒ 该轨迹**当场**退化为普通轨迹(工具面、提示词、keeper 全部恢复默认)。
793
919
  *
794
920
  * ## 归属(D23)
795
921
  * **机制在 ACC**(文件叫什么、怎么读、怎么判、缺省值、如何施加约束);
796
- * **内容在 CCC**(`skiff.json` 里填什么:模型、白名单、提示词路径)。
797
- * ⇒ ACC 代码里**不出现任何具体角色名**,也不读 `serenity.json` 的 skiff 段(该段已退役)。
922
+ * **内容在 CCC**(`trajectory-constraint.json` 里填什么:模型、白名单、提示词路径)。
923
+ * ⇒ ACC 代码里**不出现任何具体角色名**,也不读 `serenity.json` 的 轨迹约束段(该段已退役)。
798
924
  *
799
- * ## S12(2026-10-19,轨迹中心重构):此前位于 `role/skiff/` 下(该目录已随本重构消失)
925
+ * ## S12(2026-10-19,轨迹中心重构):此前位于 `role/轨迹约束/` 下(该目录已随本重构消失)
800
926
  *
801
927
  * 落点 = `trajectory/policy/`。判据(判读表 §3.1):本文件判的是**某条轨迹的约束面**,
802
928
  * 主语是轨迹 ⇒ 它一直就是轨迹切面,只是旧目录把它挂在"角色"这个**平行概念**下。
803
- * ⚠️ **文件名 `profile` 与 `skiff.json` / `SkiffProfile` 等符号名一律不动**:
929
+ * ⚠️ **文件名 `profile` 与 `trajectory-constraint.json` / `TrajectoryConstraint` 等符号名一律不动**:
804
930
  * 那是有具体指称的东西(一个固定文件名 + 它的解析结果),不是组织单位。
805
931
  */
806
932
  var profile_exports = /* @__PURE__ */ __exportAll({
807
- SKIFF_PROFILE_FILENAME: () => SKIFF_PROFILE_FILENAME,
933
+ TRAJECTORY_CONSTRAINT_FILENAME: () => TRAJECTORY_CONSTRAINT_FILENAME,
808
934
  findDirNameByCode: () => findDirNameByCode,
809
- isSkiffSession: () => isSkiffSession,
810
- listSkiffProfiles: () => listSkiffProfiles,
811
- readSkiffProfileAt: () => readSkiffProfileAt,
812
- readSkiffProfileByCode: () => readSkiffProfileByCode,
813
- skiffProfileForSession: () => skiffProfileForSession
935
+ isTrajectoryConstrained: () => isTrajectoryConstrained,
936
+ listTrajectoryConstraints: () => listTrajectoryConstraints,
937
+ readTrajectoryConstraintAt: () => readTrajectoryConstraintAt,
938
+ readTrajectoryConstraintByCode: () => readTrajectoryConstraintByCode,
939
+ trajectoryConstraintForSession: () => trajectoryConstraintForSession
814
940
  });
815
- /** `skiff.json` 的固定文件名(**判据就是它存在**——不是目录名前缀,也不是配置里的一行) */
816
- const SKIFF_PROFILE_FILENAME = "skiff.json";
817
941
  /**
818
- * 已告警过的坏 `skiff.json`(进程级去重:同一坏文件不刷屏,但**绝不静默**)。
942
+ * 🆕 **声明文件的文件名**(2026-10-19 owner 裁 Q2:短名 `trajectory.json`)。
943
+ *
944
+ * ⚠️ **判据不是"这个名字在不在"**,而是**族内任一存在**(`TRAJECTORY_FILENAME_FAMILY`)。
945
+ * 这个名字只是**写入目标**与**提示文案里该印的那个**。
946
+ */
947
+ const TRAJECTORY_CONSTRAINT_FILENAME = TRAJECTORY_FILENAME;
948
+ /**
949
+ * 已告警过的坏 `trajectory-constraint.json`(进程级去重:同一坏文件不刷屏,但**绝不静默**)。
819
950
  *
820
951
  * 🔴 为什么必须响亮(同源先例 = `loadSerenityConfig` 的 F-07):一份坏 JSON 会让
821
- * **工具白名单静默失效**——而白名单正是 skiff 的权限边界 ⇒ 静默 = 权限面悄悄放开或收死,
952
+ * **工具白名单静默失效**——而白名单正是 轨迹约束的权限边界 ⇒ 静默 = 权限面悄悄放开或收死,
822
953
  * 两种都查不到原因。
823
954
  */
824
955
  const warnedBrokenProfiles = /* @__PURE__ */ new Set();
@@ -831,41 +962,42 @@ function activeTrajectoryDirNames(root) {
831
962
  }
832
963
  }
833
964
  /**
834
- * 读一个目录下的 `skiff.json`(**纯读,不施加约束**)。
965
+ * 读一个目录下的 `trajectory-constraint.json`(**纯读,不施加约束**)。
835
966
  *
836
967
  * @param trajectoryDir 轨迹目录绝对路径
837
968
  * @param root CCC 根(仅用于 `systemPromptFile` 的逃逸拒绝)
838
- * @returns 归一后的约束面;**无该文件 → `null`(= 这不是 skiff,常态,不是错误)**
969
+ * @returns 归一后的约束面;**无该文件 → `null`(= 这不是 轨迹约束,常态,不是错误)**
839
970
  *
840
971
  * ⚠️ **形态容错**:数组元素非字符串 → 丢弃该项(不整份作废);`cognitiveTrajectory`
841
972
  * 缺项 → 各自取 **true**(见 R↓);`model` 空白 → undefined(回退缺省)。
842
- * 坏 JSON ⇒ 告警 + `null`(**按"不是 skiff"处理**:宁可让它退回普通轨迹,也不要带着
973
+ * 坏 JSON ⇒ 告警 + `null`(**按"不是 轨迹约束"处理**:宁可让它退回普通轨迹,也不要带着
843
974
  * 半份读不懂的权限面运行)。
844
975
  */
845
- function readSkiffProfileAt(trajectoryDir, root) {
976
+ function readTrajectoryConstraintAt(trajectoryDir, root) {
846
977
  const dirName = trajectoryDir.split("/").filter(Boolean).pop() ?? "";
847
978
  const code = parseTrajectoryCode(dirName);
848
979
  if (code === null) return null;
849
- const file = join(trajectoryDir, SKIFF_PROFILE_FILENAME);
850
- if (!existsSync(file)) return null;
980
+ const found = findTrajectoryFile(trajectoryDir);
981
+ if (found === null) return null;
851
982
  let raw;
852
983
  try {
853
- raw = JSON.parse(readUtf8(file));
984
+ raw = JSON.parse(readUtf8(found));
854
985
  } catch (err) {
855
- if (!warnedBrokenProfiles.has(file)) {
856
- warnedBrokenProfiles.add(file);
857
- console.warn(`[serenity-hooks] ✗ ${SKIFF_PROFILE_FILENAME} 解析失败 ⇒ 该轨迹**按普通轨迹处理**(工具白名单与提示词约束全部不施加)。请修 JSON: ${file} — ${String(err?.message ?? err)}`);
986
+ if (!warnedBrokenProfiles.has(found)) {
987
+ warnedBrokenProfiles.add(found);
988
+ console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 解析失败 ⇒ 该轨迹**按普通轨迹处理**(工具白名单与提示词约束全部不施加)。请修 JSON: ${found} — ${String(err?.message ?? err)}`);
858
989
  }
859
990
  return null;
860
991
  }
861
992
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
862
- if (!warnedBrokenProfiles.has(file)) {
863
- warnedBrokenProfiles.add(file);
864
- console.warn(`[serenity-hooks] ✗ ${SKIFF_PROFILE_FILENAME} 顶层不是对象 ⇒ 按普通轨迹处理: ${file}`);
993
+ if (!warnedBrokenProfiles.has(found)) {
994
+ warnedBrokenProfiles.add(found);
995
+ console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 顶层不是对象 ⇒ 按普通轨迹处理: ${found}`);
865
996
  }
866
997
  return null;
867
998
  }
868
999
  const obj = raw;
1000
+ const file = found;
869
1001
  const strList = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim()) : [];
870
1002
  /** 布尔缺省 **true**(R↓ 见接口:工作台就是这条轨迹自己的身体) */
871
1003
  const boolOrTrue = (v) => typeof v === "boolean" ? v : true;
@@ -878,7 +1010,7 @@ function readSkiffProfileAt(trajectoryDir, root) {
878
1010
  resolveInside(root, rawFile);
879
1011
  systemPromptFile = rawFile;
880
1012
  } catch (err) {
881
- console.warn(`[serenity-hooks] ✗ ${SKIFF_PROFILE_FILENAME} 的 systemPromptFile 越界,已忽略(其余约束仍生效): ${file} — ${String(err?.message ?? err)}`);
1013
+ console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_CONSTRAINT_FILENAME} 的 systemPromptFile 越界,已忽略(其余约束仍生效): ${file} — ${String(err?.message ?? err)}`);
882
1014
  }
883
1015
  return {
884
1016
  dirName,
@@ -897,20 +1029,20 @@ function readSkiffProfileAt(trajectoryDir, root) {
897
1029
  };
898
1030
  }
899
1031
  /**
900
- * 按**轨迹编码**解析 skiff 约束面(`list`/`admin`/调试页/路由解析用)。
1032
+ * 按**轨迹编码**解析 轨迹约束面(`list`/`admin`/调试页/路由解析用)。
901
1033
  *
902
- * @returns 约束面;轨迹不存在 / 无 `skiff.json` → `null`
1034
+ * @returns 约束面;轨迹不存在 / 无 `trajectory-constraint.json` → `null`
903
1035
  */
904
- function readSkiffProfileByCode(root, code) {
1036
+ function readTrajectoryConstraintByCode(root, code) {
905
1037
  const dirName = findDirNameByCode(root, code);
906
1038
  if (dirName === null) return null;
907
- return readSkiffProfileAt(join(sessionsRoot(root), dirName), root);
1039
+ return readTrajectoryConstraintAt(join(sessionsRoot(root), dirName), root);
908
1040
  }
909
- /** 扫全部活跃轨迹,返回全部 skiff。**顺序按目录名**(确定性,便于对读)。 */
910
- function listSkiffProfiles(root) {
1041
+ /** 扫全部活跃轨迹,返回全部 轨迹约束。**顺序按目录名**(确定性,便于对读)。 */
1042
+ function listTrajectoryConstraints(root) {
911
1043
  const out = [];
912
1044
  for (const name of activeTrajectoryDirNames(root)) {
913
- const p = readSkiffProfileAt(join(sessionsRoot(root), name), root);
1045
+ const p = readTrajectoryConstraintAt(join(sessionsRoot(root), name), root);
914
1046
  if (p) out.push(p);
915
1047
  }
916
1048
  return out;
@@ -927,40 +1059,40 @@ function findDirNameByCode(root, code) {
927
1059
  return null;
928
1060
  }
929
1061
  /**
930
- * **会话级**判据(J2):这个 dsh 会话是不是 skiff 会话 —— 唯一入口。
1062
+ * **会话级**判据(J2):这个 dsh 会话是不是 轨迹约束会话 —— 唯一入口。
931
1063
  *
932
- * 判据 = **它绑定的轨迹有 `skiff.json`**(不是 sessionId 前缀,也不是配置里有没有角色)。
1064
+ * 判据 = **它绑定的轨迹有 `trajectory-constraint.json`**(不是 sessionId 前缀,也不是配置里有没有角色)。
933
1065
  *
934
1066
  * ## 为什么"绑定的轨迹"从两个来源取(R↓,一个失败模式)
935
1067
  * ① **持久绑定**(`readLastBound`,宿主存储域/`.bindings.json`)—— 权威,跨重启成立;
936
1068
  * ② **内存注册表**(`trajectory/policy/window`)—— 覆盖绑定的**窗口期**(轨迹刚定、bound 还没落盘)。
937
- * ⇒ 只取 ① 会在建会话的第一轮判成"不是 skiff"(工具面不收窄,模型看见不该有的工具);
938
- * 只取 ② 会在进程重启后全判成"不是"(skiff 失去约束,**权限面静默放开**)。
1069
+ * ⇒ 只取 ① 会在建会话的第一轮判成"不是 轨迹约束"(工具面不收窄,模型看见不该有的工具);
1070
+ * 只取 ② 会在进程重启后全判成"不是"(轨迹约束 失去约束,**权限面静默放开**)。
939
1071
  * ⇒ **两来源并取**,都不中才 `null`。
940
1072
  *
941
1073
  * @param root CCC 根
942
1074
  * @param sessionId dsh 会话 id
943
1075
  * @param boundDirName 调用方已解析的绑定目录名(可选;给了就省一次读绑定)
944
- * @returns 约束面;不是 skiff 会话 → `null`
1076
+ * @returns 约束面;不是 轨迹约束会话 → `null`
945
1077
  */
946
- function skiffProfileForSession(root, sessionId, boundDirName) {
1078
+ function trajectoryConstraintForSession(root, sessionId, boundDirName) {
947
1079
  const dirName = boundDirName ?? registeredDirName(sessionId);
948
1080
  if (dirName === null || dirName === "") return null;
949
- return readSkiffProfileAt(join(sessionsRoot(root), dirName), root);
1081
+ return readTrajectoryConstraintAt(join(sessionsRoot(root), dirName), root);
950
1082
  }
951
1083
  /** 内存登记的轨迹目录名(注册表单一真相源在 `./registry.ts`;此处只做一次转发) */
952
1084
  function registeredDirName(sessionId) {
953
1085
  if (typeof sessionId !== "string" || sessionId === "") return null;
954
- return skiffTrajectoryDirNameFor(sessionId);
1086
+ return trajectoryDirNameForSession(sessionId);
955
1087
  }
956
1088
  /**
957
- * 该 dsh 会话**是不是** skiff 会话(布尔形态的 `skiffProfileForSession`)。
1089
+ * 该 dsh 会话**是不是** 轨迹约束会话(布尔形态的 `trajectoryConstraintForSession`)。
958
1090
  *
959
1091
  * 存在的理由:调用点很多只要一个"是/不是"(seams 的旁路判定),
960
1092
  * 让它们各自写 `!== null` 会把判据散开;**判据只能有一个入口**。
961
1093
  */
962
- function isSkiffSession(root, sessionId) {
963
- return skiffProfileForSession(root, sessionId) !== null;
1094
+ function isTrajectoryConstrained(root, sessionId) {
1095
+ return trajectoryConstraintForSession(root, sessionId) !== null;
964
1096
  }
965
1097
  //#endregion
966
- export { sessionsRoot as A, namingTitleFor as C, readTrajectoryUid as D, readActiveSessionMd as E, isoLocal as F, localFileStamp as I, localHuman as L, showSession as M, summarize as N, resolveSessionByTitle as O, useSession as P, localIdStamp as R, listSessions as S, parseTrajectoryCode as T, extractSessionMdPathFromText as _, readSkiffProfileAt as a, getLastActiveSessionInfo as b, registerSkiffSession as c, COGNITIVE_TRAJECTORY_ACTIONS as d, DEFAULT_SESSION_SCOPE as f, createSession as g, clearActiveSessionInfo as h, profile_exports as i, setActiveSessionInfo as j, sessionEvents as k, skiffTrajectoryDirNameFor as l, archiveSession as m, isSkiffSession as n, readSkiffProfileByCode as o, UID_FILENAME as p, listSkiffProfiles as r, skiffProfileForSession as s, SKIFF_PROFILE_FILENAME as t, unregisterSkiffSession as u, findSession as v, parseSessionContextFromEvents as w, listActiveTrajectories as x, getActiveSessionInfo as y };
1098
+ export { sessionsRoot as A, localHuman as B, namingTitleFor as C, readTrajectoryUid as D, readActiveSessionMd as E, TRAJECTORY_FILENAME as F, TRAJECTORY_LEGACY_FILENAMES as I, readTrajectoryFile as L, showSession as M, summarize as N, resolveSessionByTitle as O, useSession as P, isoLocal as R, listSessions as S, parseTrajectoryCode as T, localIdStamp as V, extractSessionMdPathFromText as _, readTrajectoryConstraintAt as a, getLastActiveSessionInfo as b, registerTrajectorySession as c, COGNITIVE_TRAJECTORY_ACTIONS as d, DEFAULT_SESSION_SCOPE as f, createSession as g, clearActiveSessionInfo as h, profile_exports as i, setActiveSessionInfo as j, sessionEvents as k, trajectoryDirNameForSession as l, archiveSession as m, isTrajectoryConstrained as n, readTrajectoryConstraintByCode as o, UID_FILENAME as p, listTrajectoryConstraints as r, trajectoryConstraintForSession as s, TRAJECTORY_CONSTRAINT_FILENAME as t, unregisterTrajectorySession as u, findSession as v, parseSessionContextFromEvents as w, listActiveTrajectories as x, getActiveSessionInfo as y, localFileStamp as z };