@shgroup/dsh-serenity-hooks 1.74.0 → 1.76.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.
@@ -93,8 +93,9 @@ interface SerenityConfig {
93
93
  * (约束面住在轨迹目录里)。⇒ 本段**不再被读取**:存量配置里残留的 `轨迹约束.roles` ⇒
94
94
  * **静默忽略**(schemastery 非 strict,不报错;也不会被误当成"没有 轨迹约束")。
95
95
  *
96
- * 迁移路径 = 把 `轨迹约束.roles.<name>` 的内容原样搬进对应轨迹目录的 `trajectory-constraint.json`
97
- * (判据与步骤见 `trajectory/policy/profile.ts` 头注释与 `docs/trajectory-constraint-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`)。
@@ -1,5 +1,5 @@
1
- import { r as cccRootForCwd } from "./ccc-roots-B2t_aM8L.js";
2
- import { T as parseTrajectoryCode, b as getLastActiveSessionInfo, k as sessionEvents, y as getActiveSessionInfo } from "./profile-BVt84mbi.js";
1
+ import { r as cccRootForCwd } from "./ccc-roots-rwgzt_0F.js";
2
+ import { T as parseTrajectoryCode, b as getLastActiveSessionInfo, k as sessionEvents, y as getActiveSessionInfo } from "./profile-DplLGGqo.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
@@ -50,6 +50,7 @@ function fromDomainRecord(rec) {
50
50
  action: rec.action ?? "activate",
51
51
  at: typeof rec.at === "number" ? rec.at : 0,
52
52
  ...rec.note ? { note: rec.note } : {},
53
+ ...rec.createdAsUser === true ? { createdAsUser: true } : {},
53
54
  ...rec.supersededAt !== void 0 ? { supersededAt: rec.supersededAt } : {}
54
55
  };
55
56
  }
@@ -62,6 +63,7 @@ function toDomainRecord(rec) {
62
63
  action: rec.action,
63
64
  at: rec.at,
64
65
  ...rec.note ? { note: rec.note } : {},
66
+ ...rec.createdAsUser === true ? { createdAsUser: true } : {},
65
67
  ...rec.supersededAt !== void 0 ? { supersededAt: rec.supersededAt } : {}
66
68
  };
67
69
  }
@@ -394,7 +396,8 @@ function appendBound(session, action, rec) {
394
396
  ...rec.sessionId ? { sessionId: rec.sessionId } : {},
395
397
  action,
396
398
  at: Date.now(),
397
- ...rec.note ? { note: rec.note } : {}
399
+ ...rec.note ? { note: rec.note } : {},
400
+ ...rec.createdAsUser === true ? { createdAsUser: true } : {}
398
401
  };
399
402
  if (domainStore?.available) domainStore.put(id, toDomainRecord(record)).catch(() => {});
400
403
  if (!path) return domainStore?.available === true;
@@ -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 依赖,可独立单测)
@@ -244,6 +352,19 @@ function parseTrajectoryCode(dirName) {
244
352
  return code === "" ? null : code;
245
353
  }
246
354
  /**
355
+ * 从目录名取**可读描述**(编码之后那一段;没有就返回空串)。
356
+ *
357
+ * 用途:给"模拟用户开场白"当标签(D171)。判据 = 与 {@link parseTrajectoryCode} **互逆、同源**:
358
+ * 剥日期前缀 → 第一段是编码 → 其后(去掉前导 `--`)全是描述。
359
+ * ⚠️ 描述里**可以**含 `--`(只有编码不许)⇒ 这里必须取"第一段之后整串",不能 split 再取第二段。
360
+ */
361
+ function trajectoryDescOf(dirName) {
362
+ if (!DIR_DATE_PREFIX_RE.test(dirName)) return "";
363
+ const rest = dirName.replace(DIR_DATE_PREFIX_RE, "");
364
+ const sep = rest.indexOf("--");
365
+ return sep === -1 ? "" : rest.slice(sep + 2).trim();
366
+ }
367
+ /**
247
368
  * 展示用会话 ID(`list` 面板 / 绑定记录的 `sessionId` 字段)。
248
369
  *
249
370
  * 🔴 2026-10-19 起**编码即 ID**,不再只认 `S###` —— 旧实现只匹配 `--S(\d{3,})--`,
@@ -309,8 +430,25 @@ function isValidUid(uid) {
309
430
  * 🔴 **形态非法的文件按"没设定"处理,而不是报错**:解析侧出现在**每一次** `findSession` 里
310
431
  * (show/use/archive/send-now/send-later 全走它),在那里抛错会让一个坏文件**炸掉整条轨迹的所有操作**
311
432
  * —— 惩罚远大于问题本身。而写入侧已退役 ⇒ 坏文件只可能来自外部手改,更不该连坐。
433
+ *
434
+ * ## 🆕 2026-10-19 owner 裁 Q2:**声明文件里的 `uid` 优先,独立文件回落**
435
+ *
436
+ * 收拢后这个旧标识住在 `<轨迹目录>/trajectory.json` 的 `uid` 字段里(它属"声明",
437
+ * 与约束面/机器绑定同源)。判据(**两代并读,不硬切**):
438
+ * **声明面里有合法 `uid` ⇒ 用它;否则回落 `TRAJECTORY-UID` 文件**。
439
+ *
440
+ * ⚠️ **为什么字段名是 `uid` 而不是方案书里写的 `code`**:`code` 在本仓已有一个**更窄的含义**
441
+ * —— 目录名里那个固定位置的轨迹编码(`parseTrajectoryCode`)。同一个词在一个 JSON 里表示两件事,
442
+ * 正是本轨反复抓的那类"名字会撒谎"。⇒ 旧标识就叫 `uid`(它本来就是 UID,见 `UID_FILENAME`)。
312
443
  */
313
444
  function readTrajectoryUid(sessionPath) {
445
+ try {
446
+ const fromJson = readTrajectoryFile(sessionPath)?.data?.["uid"];
447
+ if (typeof fromJson === "string") {
448
+ const trimmed = fromJson.trim();
449
+ if (isValidUid(trimmed)) return trimmed;
450
+ }
451
+ } catch {}
314
452
  try {
315
453
  const raw = readFileSync(join(sessionPath, UID_FILENAME), "utf-8").trim();
316
454
  return isValidUid(raw) ? raw : null;
@@ -751,21 +889,22 @@ function archiveSession(root, key, dryRun = false) {
751
889
  }
752
890
  //#endregion
753
891
  //#region src/trajectory/policy/window.ts
754
- const 轨迹约束Sessions = /* @__PURE__ */ new Map();
892
+ /** sessionId → 绑定的轨迹(**进程内**;跨重启的权威来源是 `trajectory/bound.ts` 的持久绑定) */
893
+ const trajectorySessions = /* @__PURE__ */ new Map();
755
894
  /** 登记/更新一个会话 → 轨迹的映射 */
756
895
  function registerTrajectorySession(sessionId, trajectoryDirName, ccc) {
757
- 轨迹约束Sessions.set(sessionId, {
896
+ trajectorySessions.set(sessionId, {
758
897
  trajectoryDirName,
759
898
  ccc
760
899
  });
761
900
  }
762
901
  /** 解绑(会话销毁 / 轨迹失效 ⇒ 换身份) */
763
902
  function unregisterTrajectorySession(sessionId) {
764
- 轨迹约束Sessions.delete(sessionId);
903
+ trajectorySessions.delete(sessionId);
765
904
  }
766
905
  /** 查会话承载的轨迹目录名(无 → null);`guards`/`system-prompt` 的同步热路径用 */
767
906
  function trajectoryDirNameForSession(sessionId) {
768
- const b = 轨迹约束Sessions.get(sessionId);
907
+ const b = trajectorySessions.get(sessionId);
769
908
  return b === void 0 || b.trajectoryDirName === "" ? null : b.trajectoryDirName;
770
909
  }
771
910
  //#endregion
@@ -812,8 +951,13 @@ var profile_exports = /* @__PURE__ */ __exportAll({
812
951
  readTrajectoryConstraintByCode: () => readTrajectoryConstraintByCode,
813
952
  trajectoryConstraintForSession: () => trajectoryConstraintForSession
814
953
  });
815
- /** `trajectory-constraint.json` 的固定文件名(**判据就是它存在**——不是目录名前缀,也不是配置里的一行) */
816
- const TRAJECTORY_CONSTRAINT_FILENAME = "trajectory-constraint.json";
954
+ /**
955
+ * 🆕 **声明文件的文件名**(2026-10-19 owner 裁 Q2:短名 `trajectory.json`)。
956
+ *
957
+ * ⚠️ **判据不是"这个名字在不在"**,而是**族内任一存在**(`TRAJECTORY_FILENAME_FAMILY`)。
958
+ * 这个名字只是**写入目标**与**提示文案里该印的那个**。
959
+ */
960
+ const TRAJECTORY_CONSTRAINT_FILENAME = TRAJECTORY_FILENAME;
817
961
  /**
818
962
  * 已告警过的坏 `trajectory-constraint.json`(进程级去重:同一坏文件不刷屏,但**绝不静默**)。
819
963
  *
@@ -846,26 +990,27 @@ function readTrajectoryConstraintAt(trajectoryDir, root) {
846
990
  const dirName = trajectoryDir.split("/").filter(Boolean).pop() ?? "";
847
991
  const code = parseTrajectoryCode(dirName);
848
992
  if (code === null) return null;
849
- const file = join(trajectoryDir, TRAJECTORY_CONSTRAINT_FILENAME);
850
- if (!existsSync(file)) return null;
993
+ const found = findTrajectoryFile(trajectoryDir);
994
+ if (found === null) return null;
851
995
  let raw;
852
996
  try {
853
- raw = JSON.parse(readUtf8(file));
997
+ raw = JSON.parse(readUtf8(found));
854
998
  } catch (err) {
855
- if (!warnedBrokenProfiles.has(file)) {
856
- warnedBrokenProfiles.add(file);
857
- console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_CONSTRAINT_FILENAME} 解析失败 ⇒ 该轨迹**按普通轨迹处理**(工具白名单与提示词约束全部不施加)。请修 JSON: ${file} — ${String(err?.message ?? err)}`);
999
+ if (!warnedBrokenProfiles.has(found)) {
1000
+ warnedBrokenProfiles.add(found);
1001
+ console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 解析失败 ⇒ 该轨迹**按普通轨迹处理**(工具白名单与提示词约束全部不施加)。请修 JSON: ${found} — ${String(err?.message ?? err)}`);
858
1002
  }
859
1003
  return null;
860
1004
  }
861
1005
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
862
- if (!warnedBrokenProfiles.has(file)) {
863
- warnedBrokenProfiles.add(file);
864
- console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_CONSTRAINT_FILENAME} 顶层不是对象 ⇒ 按普通轨迹处理: ${file}`);
1006
+ if (!warnedBrokenProfiles.has(found)) {
1007
+ warnedBrokenProfiles.add(found);
1008
+ console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 顶层不是对象 ⇒ 按普通轨迹处理: ${found}`);
865
1009
  }
866
1010
  return null;
867
1011
  }
868
1012
  const obj = raw;
1013
+ const file = found;
869
1014
  const strList = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim()) : [];
870
1015
  /** 布尔缺省 **true**(R↓ 见接口:工作台就是这条轨迹自己的身体) */
871
1016
  const boolOrTrue = (v) => typeof v === "boolean" ? v : true;
@@ -963,4 +1108,4 @@ function isTrajectoryConstrained(root, sessionId) {
963
1108
  return trajectoryConstraintForSession(root, sessionId) !== null;
964
1109
  }
965
1110
  //#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 _, 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 };
1111
+ export { sessionsRoot as A, localFileStamp as B, namingTitleFor as C, readTrajectoryUid as D, readActiveSessionMd as E, useSession as F, localIdStamp as H, TRAJECTORY_FILENAME as I, TRAJECTORY_LEGACY_FILENAMES as L, showSession as M, summarize as N, resolveSessionByTitle as O, trajectoryDescOf as P, readTrajectoryFile as R, listSessions as S, parseTrajectoryCode as T, localHuman 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, isoLocal as z };
@@ -1,4 +1,4 @@
1
- import { f as hostSettings } from "./ccc-roots-B2t_aM8L.js";
1
+ import { f as hostSettings } from "./ccc-roots-rwgzt_0F.js";
2
2
  import { _ as resolveInside, g as readUtf8 } from "./ccc-CyYJL4F2.js";
3
3
  import { dirname, join } from "node:path";
4
4
  import { existsSync, readFileSync, statSync } from "node:fs";
@@ -51,3 +51,21 @@ export declare function renderDocsIndex(chapters: readonly DocsChapter[]): strin
51
51
  * "不知道这个章节" 与 "这一章是空的" 必须可区分(否则用户以为功能没有)。
52
52
  */
53
53
  export declare function findDocsChapter(chapters: readonly DocsChapter[], id: string): DocsChapter | null;
54
+ /**
55
+ * **指针文案**(v1.75.0,owner 裁 Q3「指针」)——被降级的旧入口共用这一份。
56
+ *
57
+ * ## 为什么要有"指针"这个形态(而不是直接删掉旧入口)
58
+ *
59
+ * 旧入口的名字(`msm catalog` / `msm ccc-config` / `config guide`)**已经写在 CCC 侧的
60
+ * 文档与提示词里**,硬删会让它们全部变成"未知动作",而且**没有任何机械信号**告诉我们
61
+ * 哪份文档已经说谎了(本仓 D151 同族)。
62
+ *
63
+ * ⇒ 保留名字、只换返回内容:调用方能**自己顺着指针找到正文**,而正文仍然只有一份。
64
+ *
65
+ * 🔴 **必须只有一处实现**:`container-admin.ts`(config 域)与 `msm/service.ts`(msm 域)
66
+ * 都要用它。两处各写一份文案 ⇒ 措辞漂移(本仓反复踩过的形态)。
67
+ *
68
+ * @param chapter 正文所在的章 id
69
+ * @param what 这个入口原来叫什么(进标题,让调用方一眼确认自己找对了)
70
+ */
71
+ export declare function docsPointer(chapter: string, what: string): string;
@@ -25,4 +25,9 @@
25
25
  * container = CCC(具体认知容器);admin = 管理操作。
26
26
  */
27
27
  import type { Context } from 'cordis';
28
+ /**
29
+ * 指针与章目的关系(v1.75.0):
30
+ * 被降级的旧入口(`msm catalog` / `msm ccc-config` / `config guide`)统一走
31
+ * `config-docs.ts` 的 `docsPointer()` —— **只有一处实现**,避免两域各写一份文案而漂移。
32
+ */
28
33
  export declare function createContainerAdminTool(ctx: Context): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -7,6 +7,17 @@
7
7
  import type { JsonValue } from '../../kernel/json.js';
8
8
  type MsmAction = 'list' | 'exec' | 'register' | 'deregister' | 'check' | 'guide' | 'ccc-config' | 'catalog';
9
9
  export declare const MSM_ACTIONS: readonly MsmAction[];
10
+ /**
11
+ * ACC 能力目录(需求④ S142 用户拍板:"acc 配置各自做 guide 越来越多,整合成目录式使用指南")。
12
+ * 目录在前、详情各归各——**单一真相源**:本目录只索引"去哪个工具/子命令看详情",不复制详情
13
+ * (避免 container_admin/weixin-doctor 等 guide 全文重复 → 多真相源 → 失同步)。
14
+ * 后续新增功能只需在此加一行(低熵可演进)。
15
+ *
16
+ * 🔴 **2026-10-19 v1.75.0 起导出**:它是 `container_admin config docs catalog` 那一章的正文。
17
+ * `msm catalog` 动作**降为指针**(只指路,不再返回本常量)—— 否则同一份正文会有两个入口,
18
+ * 而"两个入口返回同一份内容"正是本目录自己反对的形态。
19
+ */
20
+ export declare const ACC_CATALOG = "\u2550\u2550\u2550 ACC Usage Catalog \u2550\u2550\u2550\nDirectory of ACC capabilities \u2014 each area lists where to go for details (guides live with their feature; this catalog only points).\n\n \u2460 \u8F68\u8FF9\u672C\u4F53 \u2192 cognitive_trajectory\uFF08list/show/create/use + rebuild \u8D85\u9650\u91CD\u5EFA + send-now \u5373\u65F6\u6295\u9012 + send-later \u672A\u6765\u4E00\u6B21\u6027\u5524\u9192\n + cro-guide CRO \u7F16\u5199\u6307\u5357\uFF09\n \u539F logbook \u5DF2\u5E76\u5165\uFF08v1.33 \u7528\u6237\u88C1\u51B3\uFF1A\u5E9F\u9664 logbook \u8BCD\uFF09\uFF1Bclose/archive \u5220\uFF08\u5F52\u6863\u8D70 container_fs mv\uFF09\uFF0C\n health/qa/summary \u5206\u522B\u5E76\u5165 use / list\uFF0Chook-develop-guide \u5E76\u8FDB container_admin msm guide\n \u2461 \u8BA4\u77E5\u8D28\u91CF\u6846\u67B6 \u2192 praxis\uFF08eap/neat/cce/ontology \u56DB\u5408\u4E00\u2014\u2014\u53EF\u5B9E\u8DF5\u7406\u8BBA\u6CE8\u5165\uFF0Csection \u6E10\u8FDB\u62AB\u9732\uFF0C\u65E0\u53C2\u5373\u76EE\u5F55\uFF09\n ontology\uFF08v1.52 D121\uFF1A\u672C\u4F53\u5DE5\u7A0B**\u5F15\u64CE**\u2014\u2014init/check/explain/verify/review/compliance\uFF1B\n \u4E0E praxis ontology \u7684\u5206\u5DE5 = \u90A3\u4E2A\u662F**\u65B9\u6CD5\u6587\u672C**\u3001\u8FD9\u4E2A\u662F**\u80FD\u8DD1\u7684\u90A3\u4E2A**\uFF09\n \u2462 \u5DE5\u5177\u4E0E\u6267\u884C \u2192 container_fs\uFF08\u5BB9\u5668\u6587\u4EF6\u7CFB\u7EDF 15 \u5B50\u547D\u4EE4\uFF09/ container_git\uFF08git\uFF09/ dashboard\uFF08health \u542B\u6CE8\u518C\u8868\u68C0\u67E5/time/wait\uFF09\n acc_msm \u5DF2\u5E76\u5165 msm\uFF08\u6267\u884C+\u53D1\u73B0\uFF09\u4E0E container_admin\uFF08\u7BA1\u7406\uFF09\u2014\u2014msm(\"<name>\", [\"<args>\"])\uFF1Binspect=true \u67E5\u7528\u6CD5\n cheap-worker\uFF08\u6742\u5DE5 agent \u7F16\u6392\u2014\u2014guide \u5B50\u547D\u4EE4\uFF1Acheap-worker guide\uFF09\n \u4E13\u5C5E\u5DE5\u5177\uFF08**\u9ED8\u8BA4\u5BF9\u6240\u6709 CCC \u9690\u85CF**\uFF0C\u53EA\u6709\u5728\u81EA\u5DF1 exclusiveTools \u91CC\u70B9\u540D\u7684 CCC \u53EF\u89C1\uFF09\u2192 acc-diag\uFF08\u8FD0\u884C\u6001\u8BCA\u65AD\uFF09\n \u2463 \u8F68\u8FF9\u7EA6\u675F\u4E0E\u5BF9\u5916\u9762 \u2192 container_admin config\uFF08\u8F68\u8FF9\u7EA6\u675F\u56DB\u52A8\u4F5C guide/validate/apply/list\u2014\u2014**\u539F role \u57DF\u5DF2\u5E76\u5165 config**\uFF0C\u65E7\u540D\u4F5C\u8FC7\u6E21\u522B\u540D\u4ECD\u53EF\u7528\uFF1B\u8BF4\u660E\u7C7B\u914D\u7F6E\u8D70**\u4E24\u5C42\u83DC\u5355** config docs \u76EE\u5F55 \uFF0F config docs <\u7AE0\u8282> \u8BE6\u60C5\uFF09\n \u5916\u90E8\u7A0B\u5E8F\u5316\u63A5\u5165\u7528\u5B98\u65B9 dsh --profile acp\n \uFF0B \u73B0\u6210 ACP \u5BA2\u6237\u7AEF\uFF1B\u672C\u63D2\u4EF6\u4E0D\u63D0\u4F9B ACP \u9762\n \u2464 \u81EA\u4E3B\u4E0E\u63A5\u5165 \u2192 cognitive_trajectory send-now\uFF08\u8DE8\u8F68\u8FF9\u7684**\u5373\u65F6**\u9012\u9001\uFF0C**\u4E0D\u6392\u961F**\uFF1Alive \u76EE\u6807\u5728\u8DD1\u5C31\u5F53\u573A\u6CE8\u5165\u5F53\u524D\u8F6E\u3001\u7A7A\u95F2\u5219\u7ACB\u5373\u8D77\u8F6E\uFF1B\u975E live \u51B7\u8F7D\u5165\u2014\u2014\u7B49\u6548\u4E8E\u76F4\u63A5 wake\uFF1B**\u6709\u540C\u6B65\u56DE\u6267**\uFF09\n cognitive_trajectory send-later\uFF08\u8DE8\u8F68\u8FF9\u7684\"\u672A\u6765\u65F6\u523B + \u4E00\u6761 message\"\uFF1Bfire-and-forget\uFF0C\u65E0\u56DE\u6267\u65E0\u56DE\u6536\uFF09\n weixin \u5FAE\u4FE1\u6865\uFF08\u914D\u7F6E/\u626B\u7801/\u8DEF\u7531/\u6D88\u606F hook\u2014\u2014CCC \u4FA7 weixin-doctor MSM\uFF1Amsm(\"weixin-doctor\", [\"guide\"])\uFF09\n \u2465 CCC \u914D\u7F6E\u603B\u89C8 \u2192 container_admin config docs config\uFF08\u6309\u7AE0\u8BE6\u60C5\u2014\u20149 \u6BB5\u5168\u8868\uFF09\n \u6216 container_admin msm ccc-config\uFF08\u540C\u4E00\u4EFD\u6B63\u6587\u7684\u65E7\u5165\u53E3\uFF0C\u4FDD\u7559\uFF09\n \u2466 \u6CE8\u518C\u8868\u4E0E\u5B89\u5168 \u2192 mech-registry.json \u7531 container_admin msm register/deregister \u7BA1\u7406\uFF08\u5199\u4FDD\u62A4\u2014\u2014\u4E0D\u53EF\u76F4\u63A5\u7F16\u8F91\uFF09\n dashboard health \u8F93\u51FA registry \u5B8C\u6574\u6027\u68C0\u67E5\uFF1B\u635F\u574F\u6062\u590D\u6307\u5F15\u89C1 health registry.issues\n CCC \u7EA7\u5F00\u5173\uFF1AexclusiveTools\uFF08\u58F0\u660E\u4E13\u5C5E\u5DE5\u5177\uFF09/ safeMode.blacklist\uFF08\u5199\u8DEF\u5F84\u9ED1\u540D\u5355\uFF09\n / safeMode.hiddenTools\uFF08**\u5B89\u5168\u6A21\u5F0F\u5F00\u542F\u671F\u95F4**\u989D\u5916\u9690\u85CF\u7684\u5DE5\u5177\uFF0Cv1.51.4\uFF09\n CCC \u7EA7 skill \u4F9B\u7ED9\uFF1AcognitiveTrajectory.skills\uFF08v1.44.0\u2014\u2014**\u672C CCC \u6BCF\u6761\u8F68\u8FF9\u90FD\u5E26**\uFF0C\u542B \u8F68\u8FF9\u7EA6\u675F\u4F1A\u8BDD\uFF1B\u4E0E\u8F68\u8FF9\u81EA\u5DF1\u7684 frontmatter \u58F0\u660E\u53D6\u5E76\u96C6\uFF09\n\n\u6BCF\u533A\uFF1A\u4E00\u53E5\u8BDD\u5B9A\u4F4D + \u8BE6\u7EC6\u5165\u53E3\uFF08\u5DE5\u5177\u540D / guide \u5B50\u547D\u4EE4\uFF09\u3002\u8BE6\u60C5\u6C38\u8FDC\u4EE5\u5BF9\u5E94\u5DE5\u5177\u7684 guide/ccc-config \u4E3A\u5355\u4E00\u771F\u76F8\u6E90\u3002\n";
10
21
  export declare const MSM_GUIDE = "MSM Development Manual (Mech & Semi-Mech framework)\n\n## What it is\nMSM = the executable-unit layer. Mech is pure TS with zero LLM reasoning; Semi-Mech is a TS framework + LLM decision points.\nMSM is ACC's deterministic executable-unit layer \u2014 all shell/exec operations go through MSM and cannot be bypassed.\n\n## Registering a new MSM (container_admin msm register)\n1. Write the script under <skill>/scripts/ (runnable via tsx; must include a main() CLI guard with an import.meta.url check)\n2. container_admin msm register --domain msm --action register <name> --skill <s> --path <script path relative to root> --category <mech|semi-mech> --description <desc>\n3. Auto-writes mech-registry.json (preserving the original format) + git commit (commits only the registry file)\n4. Validation: path must be inside root, script must exist, name must be globally unique\n\n## flag schema (v1)\nflags is a new-style object array for parameter validation and path-escape guarding:\n [{\"name\":\"output\",\"type\":\"string\",\"description\":\"output path\"},\n {\"name\":\"target\",\"type\":\"path\",\"description\":\"operation target (type:path enables in-root validation + symlink defense)\"},\n {\"name\":\"force\",\"type\":\"boolean\",\"description\":\"force mode\",\"default\":false}]\n- {name, type} format \u2014 new style, type:\"path\" enables the path-escape guard\n- {flag, description} format \u2014 old style, CLI flag description string\n- On registration, flags are passed via container_admin msm register --flags '<json>'\n\n## Script conventions\n- Top-of-file documentation: purpose / usage / exit codes\n- Exit codes: 0 success / 1 user / 2 system / 3 operator (per ACC protocol classification)\n- main() CLI guard (DC-M2): the script must start with\n if (import.meta.url === `file://${process.argv[1]}`) { main() }\n or an equivalent isMain / require.main === check \u2014 so vitest imports never trigger top-level code\n- A paired .test.ts or .spec.ts (DC-M1, vitest)\n- Business subprocess environment: SERENITY_ROOT / SERENITY_CCC / SERENITY_VERSION injected\n\n## Interaction & confirmation conventions (no blocking confirmation)\n- MSMs run in **user-less subprocesses** (spawn/execFile, 600s timeout) \u2014 **never** use\n readline / prompt / process.stdin etc. to block waiting for user input (it would hang until killed by the timeout)\n- When a second confirmation is needed: **directly return the confirmation request** (list the operation to perform and its impact + how to retry with the confirmation flag),\n returning a non-zero exit code (or an explicit hint)\n- After the agent confirms, **re-invoke the MSM with the confirmation flag** (e.g. --confirm / --yes / --force) to retry\n- Standard mode (two-phase):\n 1. First call: destructive/confirmation-requiring operation detected and no confirmation flag present \u2192 output the confirmation request (list operation/impact/rollback),\n exit non-zero (e.g. 1 user), **perform no changes**\n 2. After the agent evaluates, re-invoke: with the confirmation flag \u2192 execute and output the result\n- Applies to: delete/overwrite/push/batch operations and other irreversible or wide-impact operations\n- Anti-example (forbidden): readline waiting for user input, process.stdin.on('data') blocking waits\n (MSMs have no stdin interaction channel \u2014 they hang until the 600s timeout)\n\n## Quality checks (container_admin msm check, DC-M1~M4)\nDC-M1 has .test.ts/.spec.ts; DC-M2 has a main() guard (function main( / isMain / require.main === / import.meta.url);\nDC-M3 bidirectional: scripts unregistered + registry references missing scripts; DC-M4 path-type flags marked type:\"path\"\n\n## Self-description (protocol flags, first argument only)\nmsm(\"<name>\", [\"--list\"]) \u2014 list all MSMs\nmsm(\"<name>\", [\"--schema\", \"<n>\"]) \u2014 view a specific MSM's parameter schema\nmsm(\"<name>\", [\"--format=json\"]) \u2014 JSON output mode (remaining args passed through losslessly)\n";
11
22
  /**
12
23
  * CCC 配置参考(对齐 osp ccc-config action)。
@@ -13,8 +13,8 @@
13
13
  * 归属(D23)不变:ACC 提供机制(教程 + 校验 + 列表),**约束内容归 CCC**(写进 `trajectory-constraint.json`)。
14
14
  */
15
15
  import type { JsonValue } from '../kernel/json.js';
16
- /** 轨迹约束 定义教程:概念 / `trajectory-constraint.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
17
- export declare const TRAJECTORY_CONSTRAINT_GUIDE = "\u2550\u2550\u2550 \u8F68\u8FF9\u7EA6\u675F Guide (2026-10-19 redesigned) \u2550\u2550\u2550\n\n**A \u8F68\u8FF9\u7EA6\u675F IS a cognitive trajectory \u2014 the one whose directory contains `trajectory-constraint.json`.**\nThat is the whole rule. No role table, no name registry: put the file there and the trajectory\nbecomes a \u8F68\u8FF9\u7EA6\u675F; delete it and it goes back to being an ordinary trajectory (takes effect at once).\n\n AGENT_SESSIONS/<YYYY-MM-DD--<code>--<desc>>/\n \u251C\u2500 SESSION.md \u2190 its persistent body (this IS the workspace \u2014 nothing is auto-created)\n \u251C\u2500 trajectory-constraint.json \u2190 its constraints (this file is what makes it a \u8F68\u8FF9\u7EA6\u675F)\n \u2514\u2500 TRAJECTORY-UID \u2190 legacy, read-only (superseded by the code)\n\n\u2500\u2500 trajectory-constraint.json schema \u2500\u2500\n {\n \"model\": \"provider/model\", // optional; falls back to cheap-worker.defaultModel\n \"tools\": [\"read\",\"grep\",\"msm\",...], // non-MSM toolbar whitelist \u2014 anything not listed is REMOVED from the schema\n \"msms\": [\"msm-a\",\"msm-b\"], // MSM whitelist \u2014 msm exec may only run these; register/deregister always refused\n \"cognitiveTrajectory\": { \"session\": true, \"keeper\": true, \"rebuild\": true }, // defaults TRUE (see below)\n \"systemPromptFile\": \".opencode/skiff/<role>.md\", // recommended: relative to CCC root, hot-reloads on save\n \"systemPrompt\": \"...\" // inline fallback; systemPromptFile wins when both are set\n }\n\n\u2500\u2500 Two whitelists, both default-deny \u2500\u2500\n - `tools: []` + non-empty `msms` = pure-MSM \u8F68\u8FF9\u7EA6\u675F (the usual knowledge-QA shape)\n - non-empty `msms` \u21D2 the `msm` tool is added automatically (the only entry point for MSMs)\n - anything outside both lists is invisible in the schema, not merely rejected at call time\n\n\u2500\u2500 cognitiveTrajectory: why the defaults are TRUE \u2500\u2500\n The workspace of a \u8F68\u8FF9\u7EA6\u675F is its own SESSION.md. Setting these to false would cut it off from\n its own body, so `session`/`keeper`/`rebuild` all default to true. Set false explicitly only\n when you deliberately want that participation off.\n\n\u2500\u2500 WeChat routing: user \u2192 trajectory code \u2500\u2500\n `weixin.routes`: `[{ \"user\": \"<wx id or *>\", \"trajectory\": \"<code>\" }]`\n A route pointing at a code with no such trajectory is a hard error (the message is NOT delivered).\n A trajectory WITHOUT `trajectory-constraint.json` is still a legal target \u2014 it is just an ordinary trajectory.\n\n\u2500\u2500 Operating it \u2500\u2500\n Everything is read live: saving `trajectory-constraint.json` takes effect on the next turn, no restart.\n (The prompt FILE hot-reloads by mtime; the JSON itself is re-read every turn.)\n After editing, run container_admin config apply for an explicit validation + confirmation pass.\n Skill loading is always available to \u8F68\u8FF9\u7EA6\u675F sessions (skills are not whitelisted \u2014 they are read-only knowledge).";
16
+ /** 轨迹约束 定义教程:概念 / `trajectory.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
17
+ export declare const TRAJECTORY_CONSTRAINT_GUIDE: string;
18
18
  /** 校验当前 CCC 的 轨迹约束面:msms 均已注册 / model ∈ cheap-worker.models / 提示词非空 */
19
19
  export declare function validateTrajectoryConstraint(root: string): JsonValue;
20
20
  /** 列出当前 CCC 的轨迹约束(编码 / 模型 / msms / tools / 纪律摘要 / 提示词来源) */
@@ -32,6 +32,8 @@ interface SessionBoundRecord {
32
32
  action: SessionBoundAction;
33
33
  at: number;
34
34
  note?: string;
35
+ /** 🆕 ACC 以"用户开场白"形态建的载体(判决 D171)。**布尔事实**,不从 `note` 文案推。 */
36
+ createdAsUser?: boolean;
35
37
  /**
36
38
  * **(b) 已被取代标记**(S142 §0y,所有者 2026-09-18 裁「b 做」)。
37
39
  *
@@ -166,5 +168,6 @@ export declare function appendBound(session: unknown, action: SessionBoundAction
166
168
  mdPath: string;
167
169
  sessionId?: string;
168
170
  note?: string;
171
+ createdAsUser?: boolean;
169
172
  }): boolean;
170
173
  export {};
@@ -109,6 +109,14 @@ export declare function isValidTrajectoryCode(code: string): boolean;
109
109
  * @returns 编码;目录名不带日期前缀(非本工具建的 / 损坏)→ `null`
110
110
  */
111
111
  export declare function parseTrajectoryCode(dirName: string): string | null;
112
+ /**
113
+ * 从目录名取**可读描述**(编码之后那一段;没有就返回空串)。
114
+ *
115
+ * 用途:给"模拟用户开场白"当标签(D171)。判据 = 与 {@link parseTrajectoryCode} **互逆、同源**:
116
+ * 剥日期前缀 → 第一段是编码 → 其后(去掉前导 `--`)全是描述。
117
+ * ⚠️ 描述里**可以**含 `--`(只有编码不许)⇒ 这里必须取"第一段之后整串",不能 split 再取第二段。
118
+ */
119
+ export declare function trajectoryDescOf(dirName: string): string;
112
120
  /**
113
121
  * UID 的声明文件名(**固定名、住在轨迹目录内**)。
114
122
  *
@@ -162,6 +170,16 @@ export declare function isValidUid(uid: string): boolean;
162
170
  * 🔴 **形态非法的文件按"没设定"处理,而不是报错**:解析侧出现在**每一次** `findSession` 里
163
171
  * (show/use/archive/send-now/send-later 全走它),在那里抛错会让一个坏文件**炸掉整条轨迹的所有操作**
164
172
  * —— 惩罚远大于问题本身。而写入侧已退役 ⇒ 坏文件只可能来自外部手改,更不该连坐。
173
+ *
174
+ * ## 🆕 2026-10-19 owner 裁 Q2:**声明文件里的 `uid` 优先,独立文件回落**
175
+ *
176
+ * 收拢后这个旧标识住在 `<轨迹目录>/trajectory.json` 的 `uid` 字段里(它属"声明",
177
+ * 与约束面/机器绑定同源)。判据(**两代并读,不硬切**):
178
+ * **声明面里有合法 `uid` ⇒ 用它;否则回落 `TRAJECTORY-UID` 文件**。
179
+ *
180
+ * ⚠️ **为什么字段名是 `uid` 而不是方案书里写的 `code`**:`code` 在本仓已有一个**更窄的含义**
181
+ * —— 目录名里那个固定位置的轨迹编码(`parseTrajectoryCode`)。同一个词在一个 JSON 里表示两件事,
182
+ * 正是本轨反复抓的那类"名字会撒谎"。⇒ 旧标识就叫 `uid`(它本来就是 UID,见 `UID_FILENAME`)。
165
183
  */
166
184
  export declare function readTrajectoryUid(sessionPath: string): string | null;
167
185
  /**
@@ -0,0 +1,75 @@
1
+ /**
2
+ * trajectory/policy/files.ts — **轨迹级文件的文件名族**(owner 2026-10-19 裁 Q2:短名 `trajectory.json`)
3
+ *
4
+ * ## 这一层解决什么(白话)
5
+ *
6
+ * 一条轨迹目录里,ACC 自己产的文件原先有**四个名字各异的文件**:
7
+ * `trajectory-constraint.json`(约束面)/`TRAJECTORY-UID`(只读遗留的旧标识)/
8
+ * `cro-host-binding`(一行机器名)/`cro-wake-log.json`(CRO 流水)。
9
+ * 后两个里,**「声明」性质的那两个现在并进一个 JSON**(流水不并 —— 见 `cro/log.ts` 的理由)。
10
+ *
11
+ * ⇒ 收拢后的形态:
12
+ *
13
+ * ```text
14
+ * <轨迹目录>/
15
+ * SESSION.md ← 轨迹的身体(不动)
16
+ * PROMPT.md ← 章程(不动)
17
+ * continuous-re-occurrence.ts← CRO 程序(不动)
18
+ * trajectory.json ← 🆕 声明面:约束 + 旧标识(code) + cro.host
19
+ * cro-wake-log.log ← CRO 流水(高频追加,**刻意不并入**)
20
+ * ```
21
+ *
22
+ * ## 🔴 为什么不硬切(本容器已两次栽在"上游改名、下游写死旧名")
23
+ *
24
+ * 读取侧认**族**:`trajectory.json`(新)→ `trajectory-constraint.json`(v1.74 名)→
25
+ * `skiff.json`(v1.71 名)。**任一存在即成立**。上游再改名只往族里加一项,不再单点写死。
26
+ *
27
+ * 🔴 **`cro-host-binding` 必须两代并读,这一条是有行为后果的**
28
+ * (理由逐字保留在 `cro/core.ts` 的 `resolveCroHostBinding` 头注):
29
+ * 绑定声明要**跟着轨迹走到第二台机器**,在那里被判成「绑给了别人」而**正确地不跑**。
30
+ * 若硬切改名,第二台机器读到的是**「没绑」** —— **表面结果相同,但判据退化**
31
+ * (失去"明确绑给别人"的可诊断性)⇒ 只能两代并读。
32
+ *
33
+ * ## 归属(D23)
34
+ * 本模块只管**机制**(文件叫什么、按什么顺序找、怎么读);**内容归 CCC**
35
+ * (JSON 里填什么:模型 / 白名单 / 提示词路径 / 机器名)。
36
+ *
37
+ * ## 边界与铁律
38
+ * 本模块**零本地依赖**(只用 `node:fs` / `node:path`)⇒ 谁 import 它都不会成环。
39
+ * 每个读取函数**永不抛**:文件不存在 / 坏 JSON ⇒ `null`,由调用方各自决定降级方式。
40
+ */
41
+ /**
42
+ * 收拢后的**新**文件名(owner 裁 Q2「短名」)。
43
+ *
44
+ * 🔴 **为什么要改名**:它装的不止"约束"了(还有 `code` 与 `cro.host`)
45
+ * ⇒ 留着旧名会让**名字本身就是错名**(D151 同族:界面/文档里的名字会随内容演进而撒谎)。
46
+ */
47
+ export declare const TRAJECTORY_FILENAME = "trajectory.json";
48
+ /**
49
+ * **判据族**:任一存在 ⇒ 这条轨迹有声明面(= 旧模型说的"它是一个轨迹约束")。
50
+ *
51
+ * ⚠️ **顺序即优先级**(第一个找到的胜过后面的):新名优先,两个旧名按代际倒序。
52
+ * ⚠️ **准入面宽于约束面是有意的**:约束文件缺失的轨迹仍需要能被当作轨迹处理。
53
+ */
54
+ export declare const TRAJECTORY_FILENAME_FAMILY: readonly ["trajectory.json", "trajectory-constraint.json", "skiff.json"];
55
+ /** 旧名的单独导出(调用方要单独判断"这是不是旧档"时用;顺序同上、不含新名) */
56
+ export declare const TRAJECTORY_LEGACY_FILENAMES: ("trajectory.json" | "trajectory-constraint.json" | "skiff.json")[];
57
+ /**
58
+ * 在一条轨迹目录里找**声明文件**(族内第一个存在的)。
59
+ *
60
+ * @param trajectoryDir 轨迹目录绝对路径
61
+ * @returns 命中的文件绝对路径;族内一个都不存在 ⇒ `null`(= 普通轨迹,**常态不是错误**)
62
+ */
63
+ export declare function findTrajectoryFile(trajectoryDir: string): string | null;
64
+ /**
65
+ * 读一条轨迹目录里的声明文件,解析成对象(**永不抛**)。
66
+ *
67
+ * @returns `{ file, data }`:`file` 是命中的文件名(族内哪个),`data` 是解析后的对象;
68
+ * **族内无文件 ⇒ `null`**;**坏 JSON / 顶层非对象 ⇒ `{ file, data: null, error }`**
69
+ * —— 这三种情形调用方的降级方式各不相同(普通轨迹 / 告警后按普通轨迹),故分开给。
70
+ */
71
+ export declare function readTrajectoryFile(trajectoryDir: string): {
72
+ file: string;
73
+ data: Record<string, unknown> | null;
74
+ error: string | null;
75
+ } | null;
@@ -31,8 +31,13 @@
31
31
  * ⚠️ **文件名 `profile` 与 `trajectory-constraint.json` / `TrajectoryConstraint` 等符号名一律不动**:
32
32
  * 那是有具体指称的东西(一个固定文件名 + 它的解析结果),不是组织单位。
33
33
  */
34
- /** `trajectory-constraint.json` 的固定文件名(**判据就是它存在**——不是目录名前缀,也不是配置里的一行) */
35
- export declare const TRAJECTORY_CONSTRAINT_FILENAME = "trajectory-constraint.json";
34
+ /**
35
+ * 🆕 **声明文件的文件名**(2026-10-19 owner 裁 Q2:短名 `trajectory.json`)。
36
+ *
37
+ * ⚠️ **判据不是"这个名字在不在"**,而是**族内任一存在**(`TRAJECTORY_FILENAME_FAMILY`)。
38
+ * 这个名字只是**写入目标**与**提示文案里该印的那个**。
39
+ */
40
+ export declare const TRAJECTORY_CONSTRAINT_FILENAME = "trajectory.json";
36
41
  /**
37
42
  * 一个 轨迹约束轨迹的**完整约束面**(= 旧 `轨迹约束.roles.<name>` 那套,整体搬进轨迹目录)。
38
43
  *
@@ -26,8 +26,8 @@
26
26
  * (此时持久绑定已落盘 ⇒ 由它接管,**不会因为内存空而失去约束**)。
27
27
  */
28
28
  /** 注册表值:该 dsh 会话承载哪条轨迹(`trajectoryDirName` 为空串 = 已登记但还没定轨迹) */
29
- interface 轨迹约束SessionBinding {
30
- /** 轨迹目录名(`YYYY-MM-DD--<code>[--<desc>]`)——**轨迹约束 身份的唯一载体** */
29
+ interface TrajectorySessionBinding {
30
+ /** 轨迹目录名(`YYYY-MM-DD--<code>[--<desc>]`)——**轨迹身份的唯一载体** */
31
31
  trajectoryDirName: string;
32
32
  /** CCC 根(多 CCC 时防止串味:同 sessionId 复用必须同 (轨迹, CCC)) */
33
33
  ccc: string;
@@ -39,9 +39,9 @@ export declare function unregisterTrajectorySession(sessionId: string): void;
39
39
  /** 查会话承载的轨迹目录名(无 → null);`guards`/`system-prompt` 的同步热路径用 */
40
40
  export declare function trajectoryDirNameForSession(sessionId: string): string | null;
41
41
  /** 查会话的完整绑定(轨迹 + CCC;无 → null)——追问延续时校验 (轨迹, CCC) 未串味 */
42
- export declare function trajectorySessionInfo(sessionId: string): 轨迹约束SessionBinding | null;
42
+ export declare function trajectorySessionInfo(sessionId: string): TrajectorySessionBinding | null;
43
43
  /** 测试/诊断:注册表快照 */
44
- export declare function trajectorySessionSnapshot(): ReadonlyMap<string, 轨迹约束SessionBinding>;
44
+ export declare function trajectorySessionSnapshot(): ReadonlyMap<string, TrajectorySessionBinding>;
45
45
  /** 测试:清空(进程级状态,用例间必须复位) */
46
46
  export declare function resetTrajectorySessions(): void;
47
47
  export {};
@@ -83,7 +83,7 @@ export declare function workspaceCognitiveTrajectoryLine(mdPath: string): string
83
83
  * @returns true = 段已在位;false = 该 agent 挂不上(调用方降级为 question 前缀注入)
84
84
  */
85
85
  export declare function ensureWorkspacePromptSection(agent: Agent, scope: string, root: string): boolean;
86
- interface 轨迹约束AgentRef {
86
+ interface TrajectoryAgentRef {
87
87
  handle: AgentHandle;
88
88
  agent: Agent;
89
89
  sessionId: string;
@@ -100,8 +100,8 @@ interface 轨迹约束AgentRef {
100
100
  * @param profile 该 轨迹约束的约束面(来自 `trajectory-constraint.json`)
101
101
  * @param sessionId 指定会话 id(可选;微信桥等外部面用固定 id 实现"轨迹 ↔ 会话"长期映射)
102
102
  */
103
- export declare function createTrajectoryAgent(ctx: Context, root: string, profile: TrajectoryConstraint, defaultModel?: string, sessionId?: string): Promise<轨迹约束AgentRef>;
104
- interface 轨迹约束AskResult {
103
+ export declare function createTrajectoryAgent(ctx: Context, root: string, profile: TrajectoryConstraint, defaultModel?: string, sessionId?: string): Promise<TrajectoryAgentRef>;
104
+ interface TrajectoryAskResult {
105
105
  answer: string;
106
106
  sessionId: string;
107
107
  }
@@ -132,7 +132,7 @@ export declare function waitForTurnAssistant(ctx: unknown, agent: Agent, beforeA
132
132
  */
133
133
  export declare function askTrajectory(ctx: Context, agent: Agent, question: string, options?: {
134
134
  timeoutMs?: number;
135
- }): Promise<轨迹约束AskResult>;
135
+ }): Promise<TrajectoryAskResult>;
136
136
  /**
137
137
  * 轨迹约束会话的轨迹纪律参与判定:**不是 轨迹约束恒 true**(正常参与);
138
138
  * 轨迹约束 按 `trajectory-constraint.json` 的 `cognitiveTrajectory` 子集决定。
@@ -142,7 +142,7 @@ export declare function askTrajectory(ctx: Context, agent: Agent, question: stri
142
142
  * 纪律参与项的失败不该让会话失去 keeper/rebuild(那是能力倒退,比多跑一次更糟)。
143
143
  */
144
144
  export declare function trajectoryConstraintParticipates(root: string, sessionId: string | undefined, key: 'session' | 'keeper' | 'rebuild'): boolean;
145
- interface 轨迹约束MsmGate {
145
+ interface TrajectoryConstraintMsmGate {
146
146
  /** 拒绝原因(有则拒绝执行) */
147
147
  reject?: string;
148
148
  /** list 显示白名单(msms);仅 action==='list' 时有值 */
@@ -153,12 +153,12 @@ interface 轨迹约束MsmGate {
153
153
  * exec 非白名单 MSM 拒绝(**不列名单**)、register/deregister 必拒、
154
154
  * list 白名单过滤、check/guide/ccc-config 只读放行。
155
155
  */
156
- export declare function trajectoryConstraintMsmGate(root: string, sessionId: string | undefined, action: string, name?: string): 轨迹约束MsmGate;
157
- /** 该 轨迹约束会话的工具白名单(guards 的 schema 收窄用) */
158
- export declare function 轨迹约束ToolWhitelist(root: string, sessionId: string | undefined): Set<string> | null;
156
+ export declare function trajectoryConstraintMsmGate(root: string, sessionId: string | undefined, action: string, name?: string): TrajectoryConstraintMsmGate;
157
+ /** 该轨迹约束会话的工具白名单(guards 的 schema 收窄用) */
158
+ export declare function trajectoryConstraintToolWhitelist(root: string, sessionId: string | undefined): Set<string> | null;
159
159
  /** 按编码解析 轨迹约束(路由/调试页/审计用) */
160
160
  export declare function trajectoryConstraintByCode(root: string, code: string): TrajectoryConstraint | null;
161
161
  /** 编码 → 轨迹目录名(路由用;只做精确匹配) */
162
- export declare function 轨迹约束DirNameByCode(root: string, code: string): string | null;
162
+ export declare function trajectoryConstraintDirNameByCode(root: string, code: string): string | null;
163
163
  /** 一次性解析提示词全文(admin/validate 用;热路径不要用) */
164
- export declare function 轨迹约束SystemPrompt(root: string, profile: TrajectoryConstraint | null | undefined): string;
164
+ export declare function trajectoryConstraintSystemPrompt(root: string, profile: TrajectoryConstraint | null | undefined): string;