@shgroup/dsh-serenity-hooks 1.104.0 → 1.107.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dsh.plugin.json +2 -3
- package/lib/{profile-Dpg3wYO5.js → bind-file-B0TuOP89.js} +1 -333
- package/lib/{ccc-B-h_A7uc.js → ccc-BmpE3cQz.js} +64 -12
- package/lib/{ccc-roots-BIdg0EyM.js → ccc-roots-DPMwBywc.js} +15 -3
- package/lib/{channels-BItMJUO8.js → channels-Dty_NIl_.js} +2 -146
- package/lib/contract-BiNzzd77.js +2197 -0
- package/lib/host/access.d.ts +30 -0
- package/lib/index.js +176 -6355
- package/lib/invariant.js +1 -2
- package/lib/kernel/ccc.d.ts +60 -13
- package/lib/kernel/invariant.d.ts +2 -2
- package/lib/message-source-BTIe2cVO.js +254 -0
- package/lib/observation-BWpkA865.js +210 -0
- package/lib/profile-BjBn_-be.js +336 -0
- package/lib/{route-CmEoKp9w.js → route-reyv8qcB.js} +2 -2
- package/lib/rules-BLkYTPUy.js +261 -0
- package/lib/scheduler-B0pB9mxI.js +3603 -0
- package/lib/seams/guards.d.ts +0 -4
- package/lib/{service-Cndtf7q4.js → service-Z3aSR1cn.js} +1 -1
- package/lib/service-xvjwsX3p.js +249 -0
- package/lib/{session-CaNqSHVB.js → session-BlgUdKH4.js} +55 -6
- package/lib/settings-DY4_DYYY.js +145 -0
- package/lib/subscriptions-CyrfNpcG.js +273 -0
- package/lib/tools/diag/service.d.ts +74 -24
- package/lib/tools/kit/service.d.ts +1 -1
- package/lib/tools/msm/service.d.ts +2 -2
- package/lib/trajectory/subscriptions.d.ts +170 -0
- package/package.json +2 -2
- package/lib/message-source-CKJPJCzc.js +0 -513
- package/lib/tools/diag/index.d.ts +0 -25
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
|
+
import { g as resolveInside, h as readUtf8 } from "./ccc-BmpE3cQz.js";
|
|
3
|
+
import { N as findTrajectoryFile, T as sessionsRoot, b as parseTrajectoryCode, i as listBindDeclarations, j as TRAJECTORY_FILENAME } from "./bind-file-B0TuOP89.js";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { readdirSync } from "node:fs";
|
|
6
|
+
//#region src/trajectory/policy/window.ts
|
|
7
|
+
/** sessionId → 绑定的轨迹(**进程内**;跨重启的权威来源是 `trajectory/bound.ts` 的持久绑定) */
|
|
8
|
+
const trajectorySessions = /* @__PURE__ */ new Map();
|
|
9
|
+
/** 登记/更新一个会话 → 轨迹的映射 */
|
|
10
|
+
function registerTrajectorySession(sessionId, trajectoryDirName, ccc) {
|
|
11
|
+
trajectorySessions.set(sessionId, {
|
|
12
|
+
trajectoryDirName,
|
|
13
|
+
ccc
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** 解绑(会话销毁 / 轨迹失效 ⇒ 换身份) */
|
|
17
|
+
function unregisterTrajectorySession(sessionId) {
|
|
18
|
+
trajectorySessions.delete(sessionId);
|
|
19
|
+
}
|
|
20
|
+
/** 查会话承载的轨迹目录名(无 → null);`guards`/`system-prompt` 的同步热路径用 */
|
|
21
|
+
function trajectoryDirNameForSession(sessionId) {
|
|
22
|
+
const b = trajectorySessions.get(sessionId);
|
|
23
|
+
return b === void 0 || b.trajectoryDirName === "" ? null : b.trajectoryDirName;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/trajectory/policy/profile.ts
|
|
27
|
+
/**
|
|
28
|
+
* trajectory/policy/profile.ts — **轨迹约束的唯一判据**:目录下有 `trajectory-constraint.json` 的认知轨迹(零 DSH 依赖,可独立单测)
|
|
29
|
+
*
|
|
30
|
+
* ## 🔴 owner 2026-10-19 重做令(逐字,本文件存在的全部理由)
|
|
31
|
+
*
|
|
32
|
+
* > 「轨迹约束 复杂的约束和提示词体系都干掉,改为这样的实现:
|
|
33
|
+
* > **轨迹约束本身是认知轨迹的一个特殊情况,即目录下拥有 `trajectory-constraint.json` 的认知轨迹自动是一个 轨迹约束**
|
|
34
|
+
* > (json 里配置上个版本就有的各种约束),**会话跑在认知轨迹上且发现 `trajectory-constraint.json`,则运行时实现各类约束**」
|
|
35
|
+
*
|
|
36
|
+
* ## 旧模型 → 新模型(**一条判据取代四条**)
|
|
37
|
+
*
|
|
38
|
+
* | 项 | 旧 | 新 |
|
|
39
|
+
* |---|---|---|
|
|
40
|
+
* | 轨迹约束是什么 | `serenity.json` 的 `轨迹约束.roles.<name>` 一条配置 | **轨迹目录里有 `trajectory-constraint.json`** |
|
|
41
|
+
* | 怎么认出 轨迹约束会话 | dsh sessionId 前缀 `轨迹约束-` | **它绑定的轨迹有 `trajectory-constraint.json`** |
|
|
42
|
+
* | 工作台从哪来 | `ensureTrajectorySession` 另建一个 SESSION | **就是该轨迹自己的 SESSION.md** |
|
|
43
|
+
* | 身份 | 角色名 | **轨迹编码**(`trajectory/core.ts` 的 `parseTrajectoryCode`) |
|
|
44
|
+
*
|
|
45
|
+
* ⇒ 收益(owner:「理解难度大幅下降」的实际内容):四个各自会漂的机制收敛为**一条文件存在性判据**。
|
|
46
|
+
* 删掉 `trajectory-constraint.json` ⇒ 该轨迹**当场**退化为普通轨迹(工具面、提示词、keeper 全部恢复默认)。
|
|
47
|
+
*
|
|
48
|
+
* ## 归属(D23)
|
|
49
|
+
* **机制在 ACC**(文件叫什么、怎么读、怎么判、缺省值、如何施加约束);
|
|
50
|
+
* **内容在 CCC**(`trajectory-constraint.json` 里填什么:模型、白名单、提示词路径)。
|
|
51
|
+
* ⇒ ACC 代码里**不出现任何具体角色名**,也不读 `serenity.json` 的 轨迹约束段(该段已退役)。
|
|
52
|
+
*
|
|
53
|
+
* ## S12(2026-10-19,轨迹中心重构):此前位于 `role/轨迹约束/` 下(该目录已随本重构消失)
|
|
54
|
+
*
|
|
55
|
+
* 落点 = `trajectory/policy/`。判据(判读表 §3.1):本文件判的是**某条轨迹的约束面**,
|
|
56
|
+
* 主语是轨迹 ⇒ 它一直就是轨迹切面,只是旧目录把它挂在"角色"这个**平行概念**下。
|
|
57
|
+
* ⚠️ **文件名 `profile` 与 `trajectory-constraint.json` / `TrajectoryConstraint` 等符号名一律不动**:
|
|
58
|
+
* 那是有具体指称的东西(一个固定文件名 + 它的解析结果),不是组织单位。
|
|
59
|
+
*/
|
|
60
|
+
var profile_exports = /* @__PURE__ */ __exportAll({
|
|
61
|
+
TRAJECTORY_CONSTRAINT_FILENAME: () => TRAJECTORY_CONSTRAINT_FILENAME,
|
|
62
|
+
findDirNameByCode: () => findDirNameByCode,
|
|
63
|
+
isTrajectoryConstrained: () => isTrajectoryConstrained,
|
|
64
|
+
listTrajectoryConstraints: () => listTrajectoryConstraints,
|
|
65
|
+
readTrajectoryConstraintAt: () => readTrajectoryConstraintAt,
|
|
66
|
+
readTrajectoryConstraintByCode: () => readTrajectoryConstraintByCode,
|
|
67
|
+
replacesDefaultIdentity: () => replacesDefaultIdentity,
|
|
68
|
+
trajectoryCodeForSession: () => trajectoryCodeForSession,
|
|
69
|
+
trajectoryConstraintForSession: () => trajectoryConstraintForSession
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* 🆕 **声明文件的文件名**(2026-10-19 owner 裁 Q2:短名 `trajectory.json`)。
|
|
73
|
+
*
|
|
74
|
+
* ⚠️ **判据不是"这个名字在不在"**,而是**族内任一存在**(`TRAJECTORY_FILENAME_FAMILY`)。
|
|
75
|
+
* 这个名字只是**写入目标**与**提示文案里该印的那个**。
|
|
76
|
+
*/
|
|
77
|
+
const TRAJECTORY_CONSTRAINT_FILENAME = TRAJECTORY_FILENAME;
|
|
78
|
+
/**
|
|
79
|
+
* 已告警过的坏 `trajectory-constraint.json`(进程级去重:同一坏文件不刷屏,但**绝不静默**)。
|
|
80
|
+
*
|
|
81
|
+
* 🔴 为什么必须响亮(同源先例 = `loadSerenityConfig` 的 F-07):一份坏 JSON 会让
|
|
82
|
+
* **工具白名单静默失效**——而白名单正是 轨迹约束的权限边界 ⇒ 静默 = 权限面悄悄放开或收死,
|
|
83
|
+
* 两种都查不到原因。
|
|
84
|
+
*/
|
|
85
|
+
const warnedBrokenProfiles = /* @__PURE__ */ new Set();
|
|
86
|
+
/** 活跃轨迹目录名(跳过 `_`/`.` 前缀 —— 归档区与日志区不是轨迹;判据同 `readAllSessions`) */
|
|
87
|
+
function activeTrajectoryDirNames(root) {
|
|
88
|
+
try {
|
|
89
|
+
return readdirSync(sessionsRoot(root), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
90
|
+
} catch {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 读一个目录下的 `trajectory-constraint.json`(**纯读,不施加约束**)。
|
|
96
|
+
*
|
|
97
|
+
* @param trajectoryDir 轨迹目录绝对路径
|
|
98
|
+
* @param root CCC 根(仅用于 `systemPromptFile` 的逃逸拒绝)
|
|
99
|
+
* @returns 归一后的约束面;**无该文件 → `null`(= 这不是 轨迹约束,常态,不是错误)**
|
|
100
|
+
*
|
|
101
|
+
* ⚠️ **形态容错**:数组元素非字符串 → 丢弃该项(不整份作废);`cognitiveTrajectory`
|
|
102
|
+
* 缺项 → 各自取 **true**(见 R↓);`model` 空白 → undefined(回退缺省)。
|
|
103
|
+
* 坏 JSON ⇒ 告警 + `null`(**按"不是 轨迹约束"处理**:宁可让它退回普通轨迹,也不要带着
|
|
104
|
+
* 半份读不懂的权限面运行)。
|
|
105
|
+
*/
|
|
106
|
+
function readTrajectoryConstraintAt(trajectoryDir, root) {
|
|
107
|
+
const dirName = trajectoryDir.split("/").filter(Boolean).pop() ?? "";
|
|
108
|
+
const code = parseTrajectoryCode(dirName);
|
|
109
|
+
if (code === null) return null;
|
|
110
|
+
const found = findTrajectoryFile(trajectoryDir);
|
|
111
|
+
if (found === null) return null;
|
|
112
|
+
let raw;
|
|
113
|
+
try {
|
|
114
|
+
raw = JSON.parse(readUtf8(found));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (!warnedBrokenProfiles.has(found)) {
|
|
117
|
+
warnedBrokenProfiles.add(found);
|
|
118
|
+
console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 解析失败 ⇒ 该轨迹**按普通轨迹处理**(工具白名单与提示词约束全部不施加)。请修 JSON: ${found} — ${String(err?.message ?? err)}`);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
123
|
+
if (!warnedBrokenProfiles.has(found)) {
|
|
124
|
+
warnedBrokenProfiles.add(found);
|
|
125
|
+
console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_FILENAME} 顶层不是对象 ⇒ 按普通轨迹处理: ${found}`);
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const obj = raw;
|
|
130
|
+
const file = found;
|
|
131
|
+
/**
|
|
132
|
+
* 白名单字段的**两态**读取(D181,owner 2026-10-20 裁「不写当然不收窄」):
|
|
133
|
+
*
|
|
134
|
+
* | 文件里 | 返回 | 语义 |
|
|
135
|
+
* |---|---|---|
|
|
136
|
+
* | 字段**不存在** | `undefined` | **不收窄**(这条轨迹要的是别的设定,比如只写 `resident`) |
|
|
137
|
+
* | `[]`(显式空数组) | `[]` | **全隐藏**(除恒可用的 `skill`)—— 用户明说了"我一个都不给" |
|
|
138
|
+
* | `["a","b"]` | `["a","b"]` | 只给这些 |
|
|
139
|
+
*
|
|
140
|
+
* 🔴 **为什么不能归一成 `[]`**:那会把"我没提这件事"读成"我要禁止一切",
|
|
141
|
+
* 于是「只想声明常驻」的人会被静默锁死(S196 实测踩到,见 D179(b))。
|
|
142
|
+
* 判据是**字段在不在**,不是数组空不空。
|
|
143
|
+
*/
|
|
144
|
+
const strListMaybe = (v) => v === void 0 ? void 0 : Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim()) : void 0;
|
|
145
|
+
/** 布尔缺省 **true**(R↓ 见接口:工作台就是这条轨迹自己的身体) */
|
|
146
|
+
const boolOrTrue = (v) => typeof v === "boolean" ? v : true;
|
|
147
|
+
const ct = obj.cognitiveTrajectory && typeof obj.cognitiveTrajectory === "object" ? obj.cognitiveTrajectory : {};
|
|
148
|
+
const model = typeof obj.model === "string" && obj.model.trim() !== "" ? obj.model.trim() : void 0;
|
|
149
|
+
const systemPrompt = typeof obj.systemPrompt === "string" ? obj.systemPrompt : void 0;
|
|
150
|
+
const rawFile = typeof obj.systemPromptFile === "string" ? obj.systemPromptFile.trim() : "";
|
|
151
|
+
let systemPromptFile;
|
|
152
|
+
if (rawFile !== "") try {
|
|
153
|
+
resolveInside(root, rawFile);
|
|
154
|
+
systemPromptFile = rawFile;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.warn(`[serenity-hooks] ✗ ${TRAJECTORY_CONSTRAINT_FILENAME} 的 systemPromptFile 越界,已忽略(其余约束仍生效): ${file} — ${String(err?.message ?? err)}`);
|
|
157
|
+
}
|
|
158
|
+
const residentRaw = obj.resident;
|
|
159
|
+
let resident;
|
|
160
|
+
if (residentRaw && typeof residentRaw === "object" && !Array.isArray(residentRaw)) {
|
|
161
|
+
const serves = residentRaw.serves;
|
|
162
|
+
resident = typeof serves === "string" && serves.trim() !== "" ? { serves: serves.trim() } : {};
|
|
163
|
+
}
|
|
164
|
+
const tools = strListMaybe(obj.tools);
|
|
165
|
+
const msms = strListMaybe(obj.msms);
|
|
166
|
+
const writes = strListMaybe(obj.writes);
|
|
167
|
+
/**
|
|
168
|
+
* 提示词注入模式(owner 2026-10-20):只搬运,不判决。
|
|
169
|
+
*
|
|
170
|
+
* 🔴 判决在 `planPromptInjection()` —— 那里有唯一的合法性表与降级规则。
|
|
171
|
+
* 这里若也判一次(比如把未知值归一成 append),就会有两处真相:
|
|
172
|
+
* 一处静默改值、一处告警,读数是矛盾的(D151 同族)。
|
|
173
|
+
*/
|
|
174
|
+
const promptRaw = obj.prompt;
|
|
175
|
+
const prompt = promptRaw !== null && typeof promptRaw === "object" && !Array.isArray(promptRaw) ? {
|
|
176
|
+
...typeof promptRaw.injection === "string" ? { injection: promptRaw.injection } : {},
|
|
177
|
+
...strListMaybe(promptRaw.replaces) !== void 0 ? { replaces: strListMaybe(promptRaw.replaces) } : {}
|
|
178
|
+
} : void 0;
|
|
179
|
+
return {
|
|
180
|
+
dirName,
|
|
181
|
+
code,
|
|
182
|
+
path: file,
|
|
183
|
+
...model !== void 0 ? { model } : {},
|
|
184
|
+
...tools !== void 0 ? { tools } : {},
|
|
185
|
+
...msms !== void 0 ? { msms } : {},
|
|
186
|
+
...writes !== void 0 ? { writes } : {},
|
|
187
|
+
...resident !== void 0 ? { resident } : {},
|
|
188
|
+
...prompt !== void 0 ? { prompt } : {},
|
|
189
|
+
cognitiveTrajectory: {
|
|
190
|
+
session: boolOrTrue(ct.session),
|
|
191
|
+
keeper: boolOrTrue(ct.keeper),
|
|
192
|
+
rebuild: boolOrTrue(ct.rebuild)
|
|
193
|
+
},
|
|
194
|
+
...systemPrompt !== void 0 ? { systemPrompt } : {},
|
|
195
|
+
...systemPromptFile !== void 0 ? { systemPromptFile } : {}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 按**轨迹编码**解析 轨迹约束面(`list`/`admin`/调试页/路由解析用)。
|
|
200
|
+
*
|
|
201
|
+
* @returns 约束面;轨迹不存在 / 无 `trajectory-constraint.json` → `null`
|
|
202
|
+
*/
|
|
203
|
+
function readTrajectoryConstraintByCode(root, code) {
|
|
204
|
+
const dirName = findDirNameByCode(root, code);
|
|
205
|
+
if (dirName === null) return null;
|
|
206
|
+
return readTrajectoryConstraintAt(join(sessionsRoot(root), dirName), root);
|
|
207
|
+
}
|
|
208
|
+
/** 扫全部活跃轨迹,返回全部 轨迹约束。**顺序按目录名**(确定性,便于对读)。 */
|
|
209
|
+
function listTrajectoryConstraints(root) {
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const name of activeTrajectoryDirNames(root)) {
|
|
212
|
+
const p = readTrajectoryConstraintAt(join(sessionsRoot(root), name), root);
|
|
213
|
+
if (p) out.push(p);
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 编码 → 轨迹目录名(活跃轨迹内)。找不到 → `null`。
|
|
219
|
+
*
|
|
220
|
+
* ⚠️ 与 `findSession` 的分工:这里**只按编码精确**匹配(不做模糊),
|
|
221
|
+
* 因为它的调用方(路由 / 约束施加)要的是**确定性**,模糊匹配会把"手抖打错的名字"
|
|
222
|
+
* 静默投到一条相近的轨迹上——那正是最难查的一类串味。
|
|
223
|
+
*/
|
|
224
|
+
function findDirNameByCode(root, code) {
|
|
225
|
+
const hits = [];
|
|
226
|
+
for (const name of activeTrajectoryDirNames(root)) if (parseTrajectoryCode(name) === code) hits.push(name);
|
|
227
|
+
if (hits.length > 1) throw new Error(`编码「${code}」在本 CCC 有 ${hits.length} 条轨迹同时占用,无法确定要施加到哪一条:\n` + hits.map((n) => ` · ${n}`).join("\n") + "\n编码是终身身份(AXIOM 3)⇒ 不替你任选其一。改用**完整目录名**指定。");
|
|
228
|
+
return hits[0] ?? null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* **会话级**判据(J2):这个 dsh 会话是不是 轨迹约束会话 —— 唯一入口。
|
|
232
|
+
*
|
|
233
|
+
* 判据 = **它绑定的轨迹有 `trajectory-constraint.json`**(不是 sessionId 前缀,也不是配置里有没有角色)。
|
|
234
|
+
*
|
|
235
|
+
* ## 为什么"绑定的轨迹"从两个来源取(R↓,一个失败模式)
|
|
236
|
+
* ① **持久绑定**(`readLastBound`,宿主存储域/`.bindings.json`)—— 权威,跨重启成立;
|
|
237
|
+
* ② **内存注册表**(`trajectory/policy/window`)—— 覆盖绑定的**窗口期**(轨迹刚定、bound 还没落盘)。
|
|
238
|
+
* ⇒ 只取 ① 会在建会话的第一轮判成"不是 轨迹约束"(工具面不收窄,模型看见不该有的工具);
|
|
239
|
+
* 只取 ② 会在进程重启后全判成"不是"(轨迹约束 失去约束,**权限面静默放开**)。
|
|
240
|
+
* ⇒ **两来源并取**,都不中才 `null`。
|
|
241
|
+
*
|
|
242
|
+
* @param root CCC 根
|
|
243
|
+
* @param sessionId dsh 会话 id
|
|
244
|
+
* @param boundDirName 调用方已解析的绑定目录名(可选;给了就省一次读绑定)
|
|
245
|
+
* @returns 约束面;不是 轨迹约束会话 → `null`
|
|
246
|
+
*/
|
|
247
|
+
function trajectoryConstraintForSession(root, sessionId, boundDirName) {
|
|
248
|
+
const dirName = boundDirName ?? registeredDirName(sessionId);
|
|
249
|
+
if (dirName === null || dirName === "") return null;
|
|
250
|
+
return readTrajectoryConstraintAt(join(sessionsRoot(root), dirName), root);
|
|
251
|
+
}
|
|
252
|
+
/** 内存登记的轨迹目录名(注册表单一真相源在 `./registry.ts`;此处只做一次转发) */
|
|
253
|
+
function registeredDirName(sessionId) {
|
|
254
|
+
if (typeof sessionId !== "string" || sessionId === "") return null;
|
|
255
|
+
return trajectoryDirNameForSession(sessionId);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* **会话 → 认知轨迹编码**(供工具可见性这类"只有 sessionId、没有 agent 对象"的调用方用)。
|
|
259
|
+
*
|
|
260
|
+
* ## 为什么需要它(而不是复用 `trajectoryConstraintForSession`)
|
|
261
|
+
*
|
|
262
|
+
* 那条路的判据是"**绑定的轨迹有 `trajectory-constraint.json`**"——它回答的是
|
|
263
|
+
* 「这条会话是不是一条**约束会话**」。而工具可见性问的是另一个问题:
|
|
264
|
+
* 「这条会话属于**哪条**轨迹」。前者对**普通轨迹**恒返回 `null`(它们没有约束文件),
|
|
265
|
+
* ⇒ 拿它去判 IM 接线,会把所有普通轨迹一律判成"无归属"。
|
|
266
|
+
*
|
|
267
|
+
* ## 两个来源并取(与 `trajectoryConstraintForSession` 同一条纪律)
|
|
268
|
+
*
|
|
269
|
+
* ① **内存注册表**(`window.ts`)——覆盖"刚 `use`、绑定还没落盘"的窗口期;
|
|
270
|
+
* ② **持久绑定**(`listBindDeclarations` 扫 `current.dsh_session_bind`)——跨重启成立。
|
|
271
|
+
*
|
|
272
|
+
* ⇒ 只取 ① 会在重启后全判成"没归属"(工具**静默消失**);
|
|
273
|
+
* 只取 ② 会在建会话的第一轮判成"没归属"(同样是静默)。
|
|
274
|
+
* 🔴 **这条纪律不是新发明的**:`trajectoryConstraintForSession` 的注释里已经付过一次代价。
|
|
275
|
+
*
|
|
276
|
+
* @param root CCC 根
|
|
277
|
+
* @param sessionId dsh 会话 id
|
|
278
|
+
* @returns 轨迹编码(`S###` 或自定义码);解析不到 ⇒ `null`
|
|
279
|
+
*/
|
|
280
|
+
function trajectoryCodeForSession(root, sessionId) {
|
|
281
|
+
if (typeof sessionId !== "string" || sessionId === "") return null;
|
|
282
|
+
const fromMemory = registeredDirName(sessionId);
|
|
283
|
+
if (fromMemory !== null && fromMemory !== "") return parseTrajectoryCode(fromMemory) ?? fromMemory;
|
|
284
|
+
try {
|
|
285
|
+
for (const { dirName, decl } of listBindDeclarations(sessionsRoot(root))) {
|
|
286
|
+
if (decl.sessionId !== sessionId) continue;
|
|
287
|
+
return parseTrajectoryCode(dirName) ?? dirName;
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 该 dsh 会话**是不是** 轨迹约束会话(布尔形态的 `trajectoryConstraintForSession`)。
|
|
294
|
+
*
|
|
295
|
+
* 存在的理由:调用点很多只要一个"是/不是"(seams 的旁路判定),
|
|
296
|
+
* 让它们各自写 `!== null` 会把判据散开;**判据只能有一个入口**。
|
|
297
|
+
*
|
|
298
|
+
* 🔴 **D183(owner 2026-10-20):这里的 `true` 只表示"有声明",不再等于"该旁路默认行为"。**
|
|
299
|
+
* 要判断后者请用 `replacesDefaultIdentity()` —— 两者**刻意分开**,因为:
|
|
300
|
+
* 「我声明过」与「我声明了什么」是两件事,而旁路 ACC 身份只需要后者成立。
|
|
301
|
+
*/
|
|
302
|
+
function isTrajectoryConstrained(root, sessionId) {
|
|
303
|
+
return trajectoryConstraintForSession(root, sessionId) !== null;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 这条声明**是否真的取代了该轨迹的默认身份**(= 该不该旁路 ACC 身份/首轮/恢复等默认行为)。
|
|
307
|
+
*
|
|
308
|
+
* ## 为什么需要它(D183,owner 2026-10-20 逐字)
|
|
309
|
+
*
|
|
310
|
+
* > 「这个 `trajectory.json` 中**所有 key 都是可选的;不写就是不设定 不应影响默认行为**」
|
|
311
|
+
*
|
|
312
|
+
* 旧判据是**文件存在** ⇒ 只写一句 `resident`(对外用途自述)就会连带丢掉
|
|
313
|
+
* ACC 身份播种、首轮锚定、自动恢复、compact 重注入 —— **完全超出用户预期**。
|
|
314
|
+
*
|
|
315
|
+
* ## 判据(哪些 key 真的改"这条轨迹是谁")
|
|
316
|
+
*
|
|
317
|
+
* | key | 取代默认身份? | 理由 |
|
|
318
|
+
* |---|---|---|
|
|
319
|
+
* | `tools` / `msms` | **否** | 它们收窄**能力**,但"我是谁、我按什么章程工作"没变 ⇒ 仍该拿 ACC 身份 |
|
|
320
|
+
* | `model` | **否** | 换的只是**执行的模型**,不是身份 |
|
|
321
|
+
* | `resident` | **否** | 纯对外自述,D173 明说"不碰任何默认行为" |
|
|
322
|
+
* | `cro.host` | **否** | 管"哪台机器跑 CRO 程序",与身份无关 |
|
|
323
|
+
* | `cognitiveTrajectory` | **否** | 管"参与哪些纪律",纪律是叠加不是取代 |
|
|
324
|
+
* | `systemPromptFile` / `systemPrompt` | **是** | 它自带**章程** ⇒ 用户的提示词整段取代 ACC 的 |
|
|
325
|
+
* | `PROMPT.md`(目录里) | **否** | 它**追加**在 ACC 身份之外(v1.72.0 起的机制),不取代 |
|
|
326
|
+
*
|
|
327
|
+
* @returns `true` ⇒ 该轨迹自带章程,应旁路 ACC 身份类默认行为;否则 `false`
|
|
328
|
+
*/
|
|
329
|
+
function replacesDefaultIdentity(profile) {
|
|
330
|
+
if (!profile) return false;
|
|
331
|
+
const hasFile = (profile.systemPromptFile ?? "").trim() !== "";
|
|
332
|
+
const hasInline = (profile.systemPrompt ?? "").trim() !== "";
|
|
333
|
+
return hasFile || hasInline;
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
export { profile_exports as a, replacesDefaultIdentity as c, registerTrajectorySession as d, trajectoryDirNameForSession as f, listTrajectoryConstraints as i, trajectoryCodeForSession as l, findDirNameByCode as n, readTrajectoryConstraintAt as o, unregisterTrajectorySession as p, isTrajectoryConstrained as r, readTrajectoryConstraintByCode as s, TRAJECTORY_CONSTRAINT_FILENAME as t, trajectoryConstraintForSession as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.js";
|
|
2
|
-
import { s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-
|
|
3
|
-
import { c as unsetEntry, l as writeEntry, r as getEntry } from "./service-
|
|
2
|
+
import { s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-BmpE3cQz.js";
|
|
3
|
+
import { c as unsetEntry, l as writeEntry, r as getEntry } from "./service-Z3aSR1cn.js";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { g as resolveInside, h as readUtf8 } from "./ccc-BmpE3cQz.js";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/kernel/constants.ts
|
|
6
|
+
/** 常量(纯模块,零 DSH 依赖) */
|
|
7
|
+
/** 本包名 —— 上溯定位包根时的**身份核对**(同名目录家的邻居不会误命中) */
|
|
8
|
+
const PACKAGE_NAME = "@shgroup/dsh-serenity-hooks";
|
|
9
|
+
/** 上溯层数上限(源码态最深 `src/<域>/<子域>/` / 发布态 `lib/` ⇒ 8 层足够,且**保证有界**) */
|
|
10
|
+
const MAX_ROOT_ASCENT = 8;
|
|
11
|
+
/**
|
|
12
|
+
* 包根 = 含**本包** `package.json` 的目录。
|
|
13
|
+
*
|
|
14
|
+
* 🔴 为什么不写 `join(here, '..')`:本模块自身的位置**随目录重构变化**
|
|
15
|
+
* (曾 `src/`,现 `src/kernel/`),而发布态是从打包入口 `lib/index.js` 反推 ——
|
|
16
|
+
* 两种布局的深度不同,写死层数**必然在其中一个布局下静默错位**。
|
|
17
|
+
* 实测代价:`src/` → `src/kernel/` 之后,`join(here,'..')` 指到 `src/`(无 package.json)
|
|
18
|
+
* ⇒ `ACC_VERSION` 落进 catch 分支变 `0.0.0`,**没有任何门禁报警**。
|
|
19
|
+
*
|
|
20
|
+
* ⇒ 判据改成**内容**(找带本包名的 package.json)而不是**层数**。
|
|
21
|
+
*
|
|
22
|
+
* @param start 起点目录(缺省 = 本模块所在目录;测试可注入)
|
|
23
|
+
*/
|
|
24
|
+
function findPackageRoot(start) {
|
|
25
|
+
let dir = start ?? dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
for (let i = 0; i < MAX_ROOT_ASCENT; i += 1) {
|
|
27
|
+
try {
|
|
28
|
+
if (JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")).name === PACKAGE_NAME) return dir;
|
|
29
|
+
} catch {}
|
|
30
|
+
const parent = dirname(dir);
|
|
31
|
+
if (parent === dir) return null;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ACC 版本:自动从 package.json 读取(单一真相源,消除与 CHANGELOG 的漂移)。
|
|
38
|
+
* 发布时只需改 package.json 的 version。
|
|
39
|
+
*/
|
|
40
|
+
const ACC_VERSION = (() => {
|
|
41
|
+
try {
|
|
42
|
+
const root = findPackageRoot();
|
|
43
|
+
if (root === null) return "0.0.0";
|
|
44
|
+
return JSON.parse(readFileSync(join(root, "package.json"), "utf-8")).version ?? "0.0.0";
|
|
45
|
+
} catch {
|
|
46
|
+
return "0.0.0";
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/trajectory/policy/rules.ts
|
|
51
|
+
/**
|
|
52
|
+
* trajectory/policy/rules.ts — 轨迹约束面的**纯逻辑助手**(零 DSH 依赖,可独立单测)
|
|
53
|
+
*
|
|
54
|
+
* ## 2026-10-19 重做后的职责边界(R↓,别再混起来)
|
|
55
|
+
*
|
|
56
|
+
* | 模块 | 管什么 |
|
|
57
|
+
* |---|---|
|
|
58
|
+
* | `./profile.ts` | **判据 + 读盘**("这是不是 轨迹约束"、"约束面是什么") |
|
|
59
|
+
* | `./rules.ts`(本文件) | **从约束面派生出的纯函数**(白名单集合、提示词读取器、基础提示词文本) |
|
|
60
|
+
* | `../session-create.ts` | 会话创建/复用、提问、守门查询 |
|
|
61
|
+
*
|
|
62
|
+
* ⇒ 本文件**不再读 `serenity.json`**:`轨迹约束.roles` 整段已退役(owner 2026-10-19 重做)。
|
|
63
|
+
* 它只接收一个已解析好的 `TrajectoryConstraint`,因此**不涉及任何 IO**(除提示词文件,那是刻意懒读的)。
|
|
64
|
+
*
|
|
65
|
+
* ## S12(2026-10-19,轨迹中心重构)
|
|
66
|
+
*
|
|
67
|
+
* 原名 `role/轨迹约束/config.ts`。改名理由(框架 §6.3「角色不是一个概念,是一段历史」):
|
|
68
|
+
* 它派生的是**某条轨迹的约束面**,与"配置读取"无关(本文件零配置读取,见上一段)
|
|
69
|
+
* ⇒ `config` 这个名字描述的是**它曾经的样子**,`rules` 描述它**现在做的事**。
|
|
70
|
+
*/
|
|
71
|
+
/** 白名单推导 ────────────────────────────────────────────────────────────── */
|
|
72
|
+
/** 提示词来源提示串(`apply` 回执与面板共用;避免两处措辞漂) */
|
|
73
|
+
const TRAJECTORY_CONSTRAINT_HINT = "systemPromptFile hot-reloads by mtime; the rest of trajectory-constraint.json is re-read every turn";
|
|
74
|
+
/**
|
|
75
|
+
* 该轨迹会话**可见**的工具面(tools ∪ `msm`)。
|
|
76
|
+
*
|
|
77
|
+
* 🔴 **返回值 `null` 有专门含义(D181,owner 2026-10-20 裁「不写当然不收窄」)**:
|
|
78
|
+
*
|
|
79
|
+
* | `tools` | `msms` | 返回 | 语义 |
|
|
80
|
+
* |---|---|---|---|
|
|
81
|
+
* | 缺省 | 缺省 | **`null`** | **不收窄任何工具面**(这条轨迹要的是别的设定,如只写 `resident`) |
|
|
82
|
+
* | `[]` | 缺省 | `{'msm'?}` 空/仅 msm | **全隐藏**(除恒可用的 `skill`) |
|
|
83
|
+
* | `['a']` | … | `{'a', …}` | 只给这些 |
|
|
84
|
+
*
|
|
85
|
+
* ⛔ **调用方不许把 `null` 当空集处理** —— 那正是 S196 被锁死的形态(D179(b))。
|
|
86
|
+
* 判据是**字段在不在**,不是数组空不空。
|
|
87
|
+
*
|
|
88
|
+
* 🔴 `msm` 为什么自动加入:`msms` 非空意味着这个轨迹 **有 MSM 通道**,
|
|
89
|
+
* 而 MSM 的唯一合法调用入口是 `msm` 工具(单入口纪律)——不给它 `msm` 就等于
|
|
90
|
+
* 声明了一堆调不动的能力。⚠️ 这是**并集**,不是覆盖:`tools` 里显式写的照旧生效。
|
|
91
|
+
*
|
|
92
|
+
* @returns 白名单集合;**`null` = 不收窄**(调用方须显式判 `null`)
|
|
93
|
+
*/
|
|
94
|
+
function profileToolWhitelist(profile) {
|
|
95
|
+
if (!profile) return null;
|
|
96
|
+
if (profile.tools === void 0 && profile.msms === void 0) return null;
|
|
97
|
+
const out = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const t of profile.tools ?? []) out.add(t);
|
|
99
|
+
if ((profile.msms?.length ?? 0) > 0) out.add("msm");
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
/** 该轨迹会话可 exec 的 MSM 白名单(`msm` 工具内部再过滤;独立于工具白名单) */
|
|
103
|
+
function profileMsmWhitelist(profile) {
|
|
104
|
+
return new Set(profile?.msms ?? []);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 该轨迹可**写**的路径前缀(owner 2026-10-20 令:「轨迹设定自己的写白名单」)。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 与 `profileToolWhitelist` 共用同一条判据(D181/D183):**字段在不在,不是数组空不空**。
|
|
110
|
+
*
|
|
111
|
+
* | `writes` | 返回 | 语义 |
|
|
112
|
+
* |---|---|---|
|
|
113
|
+
* | 缺省 | **`null`** | 不收窄(写权限 = 路径守卫那几条,与今天一致) |
|
|
114
|
+
* | `[]` | `[]`(空数组) | **一个写都落不下**(显式意图) |
|
|
115
|
+
* | `['a/']` | `['a/']` | 写目标必须落在 `a/` 之内 |
|
|
116
|
+
*
|
|
117
|
+
* ⛔ **调用方不许把 `null` 当 `[]`** —— 那会把"我没想到这件事"读成"我不许写任何东西",
|
|
118
|
+
* 正是 S196 被静默锁死的形态(D179(b))。
|
|
119
|
+
*
|
|
120
|
+
* ⚠️ 返回的是**原始条目**(相对 CCC 根或绝对路径都可),解析成绝对路径由调用方按自己的 root 做 ——
|
|
121
|
+
* 本模块不持有 root(判据在 `seams/guards.ts`)。
|
|
122
|
+
*/
|
|
123
|
+
function profileWriteWhitelist(profile) {
|
|
124
|
+
if (!profile) return null;
|
|
125
|
+
if (profile.writes === void 0) return null;
|
|
126
|
+
return profile.writes;
|
|
127
|
+
}
|
|
128
|
+
/** 轨迹纪律参与项(`profile` 缺省已在 `readTrajectoryConstraintAt` 归一为全 true) */
|
|
129
|
+
function constraintCognitiveTrajectory(profile) {
|
|
130
|
+
return {
|
|
131
|
+
session: profile?.cognitiveTrajectory.session === true,
|
|
132
|
+
keeper: profile?.cognitiveTrajectory.keeper === true,
|
|
133
|
+
rebuild: profile?.cognitiveTrajectory.rebuild === true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 解析约束面声明的**系统提示词全文**(一次性读取;`create` / `validate` / `list` 用)。
|
|
138
|
+
*
|
|
139
|
+
* 顺序:`systemPromptFile`(**推荐**:超长提示词在 JSON 里内嵌不可读)→ 内嵌 `systemPrompt` → 空串。
|
|
140
|
+
* 文件缺失/逃逸 ⇒ **抛错**(调用方 catch 降级;`validate` 据此报 issue)。
|
|
141
|
+
*
|
|
142
|
+
* ⚠️ 热路径**不用**它(那会用每次工具调用去读盘)——热路径用 `createProfilePromptReader`。
|
|
143
|
+
*/
|
|
144
|
+
function resolveConstraintSystemPrompt(root, profile) {
|
|
145
|
+
if (!profile) return "";
|
|
146
|
+
const file = profile.systemPromptFile?.trim();
|
|
147
|
+
if (file) {
|
|
148
|
+
const abs = resolveInside(root, file);
|
|
149
|
+
if (!existsSync(abs)) throw new Error(`轨迹约束 "${profile.code}": systemPromptFile "${file}" not found (resolved: ${abs})`);
|
|
150
|
+
return readUtf8(abs).trim();
|
|
151
|
+
}
|
|
152
|
+
return profile.systemPrompt ?? "";
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 提示词来源(`list`/`validate` 展示用)。
|
|
156
|
+
*
|
|
157
|
+
* 🔴 **2026-10-19 修(T4 的漏网面)**:v1.72.0 起提示词有两代来源——**新形态**
|
|
158
|
+
* 是轨迹目录里的 `PROMPT.md`(`trajectory/prompt.ts`,**优先**),旧形态才是
|
|
159
|
+
* `systemPromptFile` / `systemPrompt`。本函数原先只认旧形态 ⇒ 两条只写了 `PROMPT.md`
|
|
160
|
+
* 的轨迹在 `container_admin config list` 里恒显示 `promptSource: 'none'`。
|
|
161
|
+
*
|
|
162
|
+
* 那是**读数器过时**,不是配置缺失:`validate` 侧早已按两条来源判(见
|
|
163
|
+
* `trajectory-constraint-admin.ts` 的 `hasTrajectoryPromptOrLegacy`),而 `list` 侧漏了。
|
|
164
|
+
* ⇒ 返 **四种**值,把"有章程但来源是新形态"这一格单独说出来。
|
|
165
|
+
*
|
|
166
|
+
* @param hasPromptFile 该轨迹目录下**是否真有** `PROMPT.md`(调用方查,本函数不做 IO)
|
|
167
|
+
*/
|
|
168
|
+
function constraintSystemPromptSource(profile, hasPromptFile = false) {
|
|
169
|
+
if (hasPromptFile) return "trajectory-file";
|
|
170
|
+
if (profile?.systemPromptFile?.trim()) return "file";
|
|
171
|
+
if (profile?.systemPrompt?.trim()) return "inline";
|
|
172
|
+
return "none";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 提示词**读取器**(长期热更新;改文件即生效,无需重启)。
|
|
176
|
+
*
|
|
177
|
+
* 为什么返回函数而不是字符串(R↓,实证代价):旧实现在创建时把提示词**快照**进系统提示词段闭包
|
|
178
|
+
* ⇒ CCC 改了提示词文件必须重启 dsh web 才生效(实测:改完 18KB 提示词,live agent 仍用旧文本)。
|
|
179
|
+
* 现形态由系统提示词段的 `text()` **每轮调用**本读取器:
|
|
180
|
+
* - `systemPromptFile`:按 **mtime + size** 缓存重读(命中缓存零 IO;未变不解析)
|
|
181
|
+
* - 内嵌 `systemPrompt`:静态(改 JSON 仍需重载配置,这与文件热更的差别是**必然**的 —— 文件有 mtime,JSON 没有)
|
|
182
|
+
* - 读取失败:返回**上次成功内容**(⛔ 绝不返回空——空提示词会让角色失去人格与纪律),并按标签去重告警一次
|
|
183
|
+
*
|
|
184
|
+
* @param root CCC 根
|
|
185
|
+
* @param profile 约束面
|
|
186
|
+
*/
|
|
187
|
+
function createProfilePromptReader(root, profile) {
|
|
188
|
+
const file = profile?.systemPromptFile?.trim();
|
|
189
|
+
if (!file) {
|
|
190
|
+
const inline = profile?.systemPrompt ?? "";
|
|
191
|
+
return () => inline;
|
|
192
|
+
}
|
|
193
|
+
let abs = null;
|
|
194
|
+
try {
|
|
195
|
+
abs = resolveInside(root, file);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.warn(`[serenity-hooks] ✗ 轨迹约束提示词路径非法(${file}): ${String(err?.message ?? err)}`);
|
|
198
|
+
}
|
|
199
|
+
if (abs === null) return () => "";
|
|
200
|
+
const absPath = abs;
|
|
201
|
+
let cachedMtime = -1;
|
|
202
|
+
let cachedSize = -1;
|
|
203
|
+
let cachedText = "";
|
|
204
|
+
let warned = false;
|
|
205
|
+
return () => {
|
|
206
|
+
try {
|
|
207
|
+
const st = statSync(absPath);
|
|
208
|
+
if (st.mtimeMs === cachedMtime && st.size === cachedSize) return cachedText;
|
|
209
|
+
const text = readUtf8(absPath).trim();
|
|
210
|
+
const isReload = cachedMtime !== -1;
|
|
211
|
+
cachedMtime = st.mtimeMs;
|
|
212
|
+
cachedSize = st.size;
|
|
213
|
+
cachedText = text;
|
|
214
|
+
warned = false;
|
|
215
|
+
if (isReload) console.info(`[serenity-hooks] ↻ 轨迹约束提示词已热重载(${file},${text.length} 字符)`);
|
|
216
|
+
return text;
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (!warned) {
|
|
219
|
+
warned = true;
|
|
220
|
+
console.warn(`[serenity-hooks] ✗ 轨迹约束提示词读取失败(${file})——沿用上次成功内容: ${String(err?.message ?? err)}`);
|
|
221
|
+
}
|
|
222
|
+
return cachedText;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 轨迹约束 基础提示词(dsp 只给这部分;CCC 的 `systemPrompt` 段由调用方拼接)。
|
|
228
|
+
*
|
|
229
|
+
* ## 🔴 为什么**不再枚举**清单(owner 2026-10-19 令)
|
|
230
|
+
*
|
|
231
|
+
* 旧版在这里抄了两份清单(`MSMs: a, b, c` / `Tools: read, grep, …`)。那是**第二真相源**,
|
|
232
|
+
* 而真相源本来就在模型自己手里:
|
|
233
|
+
* · **工具清单** = 这一轮实际注册给它的 tool schema —— 模型已经看得见,抄一遍只会漂
|
|
234
|
+
* · **MSM 清单** = `msm()` 无参调用返回的目录 —— 已按白名单过滤,且**每次实时读配置**
|
|
235
|
+
*
|
|
236
|
+
* ⚠️ **漂移不是理论风险,是已发生的缺陷**(2026-10-19):角色配置早就长到 8 个工具,
|
|
237
|
+
* 而这个函数是从**创建时冻结的快照**求值的 ⇒ 提示词里一直写着旧的 4 个
|
|
238
|
+
* ⇒ 模型照着提示词办事,**它自己以为没有的能力就不去调** ⇒ 症状是"工具用不了",
|
|
239
|
+
* 而守卫那边(每调用实时读配置)其实是放行的。两个真相源打架,输的总是提示词。
|
|
240
|
+
*
|
|
241
|
+
* ⇒ 现在只声明**边界与发现路径**,不抄内容:清单在哪,怎么取,越界会怎样。
|
|
242
|
+
*
|
|
243
|
+
* @param code 轨迹编码(轨迹约束的身份 —— 不是"角色名",那个概念已退役)
|
|
244
|
+
*/
|
|
245
|
+
function buildTrajectoryBasePrompt(code) {
|
|
246
|
+
return [
|
|
247
|
+
"=== Serenity 轨迹约束 ===",
|
|
248
|
+
`Cognitive trajectory: ${code} (this is the trajectory you ARE, not a role you play)`,
|
|
249
|
+
"Your capability boundary is enforced mechanically by this CCC — anything outside it is rejected at the call site, not by your judgment.",
|
|
250
|
+
"Discover your surface, do not memorize it (both are generated from the live config on every turn):",
|
|
251
|
+
" · Your tools are exactly the tool schemas you see in this session — if a tool is not in your schema, it is not available.",
|
|
252
|
+
" · Your MSMs are listed by calling msm() with no arguments — that directory is already filtered to this trajectory.",
|
|
253
|
+
" · Usage for any MSM: msm(\"<name>\", [], inspect=true) or pass \"--help\" as the first argument.",
|
|
254
|
+
"Do not infer unavailability from anything you remember or from an earlier list: this surface changes when the CCC configuration changes, and it takes effect without restarting.",
|
|
255
|
+
"",
|
|
256
|
+
"---",
|
|
257
|
+
""
|
|
258
|
+
].join("\n");
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { createProfilePromptReader as a, profileWriteWhitelist as c, findPackageRoot as d, constraintSystemPromptSource as i, resolveConstraintSystemPrompt as l, buildTrajectoryBasePrompt as n, profileMsmWhitelist as o, constraintCognitiveTrajectory as r, profileToolWhitelist as s, TRAJECTORY_CONSTRAINT_HINT as t, ACC_VERSION as u };
|