@shgroup/dsh-serenity-hooks 1.77.0 → 1.78.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.
@@ -0,0 +1,145 @@
1
+ import { f as hostSettings } from "./ccc-roots-D3ty1C8P.js";
2
+ //#region src/surface/settings.ts
3
+ /**
4
+ * 设置面板命名空间 = **profile 条目 id**(v0.1.7 起;与旧 `settings.yaml` 段名逐字相同)。
5
+ */
6
+ const SERENITY_SETTINGS_NS = "serenity-hooks";
7
+ /** 进程级默认(无 Config 可读时的兜底;与 Config 各字段的缺省一致) */
8
+ function defaultSimpleSettings() {
9
+ return {
10
+ gatewayEnabled: false,
11
+ rebuildThresholdK: 400,
12
+ croEnabled: true,
13
+ diagramEnabled: false,
14
+ humanChannel: defaultHumanChannelSettings()
15
+ };
16
+ }
17
+ /**
18
+ * volatile 字段的**结构化解包**(v1.47.2;本文件读取语义的第一道)。
19
+ *
20
+ * ## 为什么必须有它(两个事实的合成,缺一即错)
21
+ * ① **0.1.7 的面板层字段必须标 `.volatile()`** —— 否则本插件命名空间整个不进宿主的
22
+ * `settings.describe()`(`volatileForm()` 只认 `meta.volatile`)⇒ **设置面板那一节消失**。
23
+ * ② **标了之后,运行期拿到的是引用包装,不是裸值** —— schemastery 的 `Schema.resolve()` 对
24
+ * `meta.volatile` **无条件**包 `createVolatile(value)`,且该分支排在"缺省值回退"那一段
25
+ * **之前** ⇒ **"用户没设过"的字段也是恒真对象**(`.get()` 返回 `undefined`),**不是 `undefined`**。
26
+ *
27
+ * ⇒ 若这里不解包,下面十行的 `??` 链会**短路在这层包装上** ⇒ 网关 / 轨迹约束 会被
28
+ * **无意打开**(比"面板不显示"更坏:静默改变对外暴露面)。
29
+ *
30
+ * ## 为什么用结构判定
31
+ * 可观察形状就是"有 `get()`"(cosmokit 的 `isVolatile` 也这么判);本模块不引它的运行时依赖。
32
+ *
33
+ * @param value - volatile 引用 | 裸值 | undefined
34
+ * @returns 解包后的裸值(引用 ⇒ `.get()`;裸值原样;缺省 ⇒ `undefined`)
35
+ */
36
+ function unwrapVolatile(value) {
37
+ if (value === void 0 || value === null) return void 0;
38
+ if (typeof value.get === "function") return value.get();
39
+ return value;
40
+ }
41
+ /** human-channel 的内建缺省(与 `Config` 的 schema 缺省**同源**:这里是读取面,那里是解析面) */
42
+ function defaultHumanChannelSettings() {
43
+ return {
44
+ accounts: [],
45
+ subscriptions: [],
46
+ relayCcc: null,
47
+ sendingAllow: ["*"]
48
+ };
49
+ }
50
+ /** 取"有内容的字符串"(首尾空白剥掉;空白串 ⇒ `undefined`)—— 面板空文本框会写 `''`,那是"没填"不是"填了个空名" */
51
+ function nonEmptyText(value) {
52
+ if (typeof value !== "string") return void 0;
53
+ const trimmed = value.trim();
54
+ return trimmed === "" ? void 0 : trimmed;
55
+ }
56
+ /**
57
+ * `humanChannel` 片段 → 运行时归一形态。**优先级:显式值 > 内建缺省**(与 `simpleSettingsFromConfig` 同族)。
58
+ *
59
+ * 🔴 三处"缺省 vs 显式"的语义差别,别写反:
60
+ * ① `sending.allow: []` ⇒ **`[]`**("谁都不许"),**不是**回退到 `['*']` —— 故用 `??`
61
+ * (只对 `undefined`/`null` 回退)。⚠️ **别拿 `??`→`||` 当变异探针**:空数组在 JS 里是
62
+ * **真值** ⇒ 两种写法在这个输入上**恰好同效**,那是**非变异**(会得出"测试不承重"的假结论)。
63
+ * ② `relay.ccc` 空白 ⇒ `null`("没填"与"没这一项"同义),**不是**把空白当 CCC 名;
64
+ * ③ 账号 `enabled` 缺省 **true**(老账号表里 `enabled` 不写就是开)。
65
+ *
66
+ * 🔴 **旧 CCC 侧 `weixin.*` 不在这里、也不该在这里**:机器级面**只**认 `humanChannel`
67
+ * (硬切无别名,同 D61 先例)⇒ 传进来的旧键**一律不进结构**(回归钉见
68
+ * `tests/human-channel-config.test.ts`;CCC 侧旧读取面的退役在 P0-2~P0-4)。
69
+ * @param fragment - `Config.humanChannel`(可能整个不存在 —— 那是**合法**初始态)
70
+ * @returns 归一形态(永不 `undefined`)
71
+ */
72
+ function humanChannelSettingsFromConfig(fragment) {
73
+ const d = defaultHumanChannelSettings();
74
+ if (!fragment) return d;
75
+ return {
76
+ accounts: (fragment.accounts ?? []).map((a) => ({
77
+ id: nonEmptyText(a?.id),
78
+ channel: nonEmptyText(a?.channel),
79
+ enabled: a?.enabled !== false,
80
+ token: a?.token,
81
+ userId: a?.userId
82
+ })),
83
+ subscriptions: (fragment.subscriptions ?? []).map((s) => ({
84
+ account: nonEmptyText(s?.account),
85
+ user: nonEmptyText(s?.user),
86
+ ccc: nonEmptyText(s?.ccc),
87
+ role: nonEmptyText(s?.role)
88
+ })),
89
+ relayCcc: nonEmptyText(fragment.relay?.ccc) ?? d.relayCcc,
90
+ sendingAllow: fragment.sending?.allow ?? d.sendingAllow
91
+ };
92
+ }
93
+ /**
94
+ * Config(宿主 Loader 校验后的解析值)→ 扁平简单配置。**本文件唯一的读取语义**。
95
+ *
96
+ * 优先级:**面板层(扁平键)> 部署层(嵌套段)> 内建缺省**。
97
+ * @param config - 插件 Config(`fiber.config` 或 apply 收到的 config)
98
+ * @returns 扁平开关集合
99
+ */
100
+ function simpleSettingsFromConfig(config) {
101
+ const d = defaultSimpleSettings();
102
+ return {
103
+ gatewayEnabled: unwrapVolatile(config.gatewayEnabled) ?? config.gateway?.enabled ?? d.gatewayEnabled,
104
+ rebuildThresholdK: unwrapVolatile(config.rebuildThresholdK) ?? config.rebuild?.thresholdK ?? d.rebuildThresholdK,
105
+ croEnabled: unwrapVolatile(config.croEnabled) ?? d.croEnabled,
106
+ diagramEnabled: unwrapVolatile(config.diagramEnabled) ?? config.diagram?.enabled ?? d.diagramEnabled,
107
+ humanChannel: humanChannelSettingsFromConfig(config.humanChannel)
108
+ };
109
+ }
110
+ /** 运行时源(`registerSettingsSection` 装配:读本插件 fiber 的**当前** Config) */
111
+ let simpleSource = null;
112
+ /**
113
+ * 读取当前简单配置(各功能 gateway/rebuild/轨迹约束/… 的启动与运行时判断都经此)。
114
+ *
115
+ * 源 = 本插件 fiber 的 `config`(宿主在配置变更时走 `fiber.update()` ⇒ `config` 始终最新,
116
+ * **不必**依赖 apply 重跑,也不必缓存)。
117
+ * @returns 扁平开关集合
118
+ */
119
+ function readSimpleSettings() {
120
+ return simpleSource ? simpleSource() : defaultSimpleSettings();
121
+ }
122
+ /**
123
+ * 装配简单配置读取面(v1.21 分层;**v0.1.7 起不再注册任何 section**)。
124
+ *
125
+ * 两件事:
126
+ * 1. 把"本插件自己的 Config"接成简单配置源 —— 读 `ctx.fiber.config`(宿主 `fiber.update()`
127
+ * 保证最新),拿不到 fiber 时退回 apply 收到的 `config`。
128
+ * 2. 关掉宿主按 Config schema **自动生成**的通用页(`settings.configure({ auto: false })`)——
129
+ * 门面是我们的自定义页 `client/SettingsSection.tsx`(同一个命名空间,同一份数据)。
130
+ *
131
+ * ⚠️ 两个失败都不阻断插件装载(面板是附属能力,绝不能成为启动单点)。
132
+ * @param ctx - 插件上下文
133
+ * @param config - apply 收到的 Config(fiber.config 不可用时的兜底)
134
+ */
135
+ function registerSettingsSection(ctx, config) {
136
+ const fiber = ctx.fiber;
137
+ const readConfig = () => fiber?.config ?? config;
138
+ simpleSource = () => simpleSettingsFromConfig(readConfig());
139
+ const settings = hostSettings(ctx);
140
+ try {
141
+ settings?.configure?.({ auto: false }, fiber);
142
+ } catch {}
143
+ }
144
+ //#endregion
145
+ export { readSimpleSettings as n, registerSettingsSection as r, SERENITY_SETTINGS_NS as t };
@@ -13,7 +13,14 @@
13
13
  * 归属(D23)不变:ACC 提供机制(教程 + 校验 + 列表),**约束内容归 CCC**(写进 `trajectory-constraint.json`)。
14
14
  */
15
15
  import type { JsonValue } from '../kernel/json.js';
16
- /** 轨迹约束 定义教程:概念 / `trajectory.json` schema / 双白名单 / 轨迹纪律 / 微信路由 */
16
+ /**
17
+ * 轨迹约束 定义教程:**只讲「限制面」**(约束面 / 双白名单 / 纪律参与项 / 路由)。
18
+ *
19
+ * 🔴 2026-10-20 拆分(owner 裁 Q2):本章原先同时承载「描述自己」与「限制自己」。
20
+ * 两者读者与前提不同 —— **描述自己是所有轨迹的常态,限制自己才是特例** ——
21
+ * 故「章程 / 常驻 / 三个载体」已移入 `config docs trajectory-self`。
22
+ * ⛔ **本文件不再重复那份内容**(一个内容一个正文入口,v1.75.0 指针纪律)。
23
+ */
17
24
  export declare const TRAJECTORY_CONSTRAINT_GUIDE: string;
18
25
  /** 校验当前 CCC 的 轨迹约束面:msms 均已注册 / model ∈ cheap-worker.models / 提示词非空 */
19
26
  export declare function validateTrajectoryConstraint(root: string): JsonValue;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * tools/trajectory-self-docs.ts — 「一条认知轨迹如何描述自己」的**手册正文**
3
+ *
4
+ * ## 为什么这一章单独存在(owner 2026-10-20 原话)
5
+ *
6
+ * > 「例如某个认知轨迹想设定自己是**常驻**的,想给自己做**轨迹约束**,
7
+ * > 这种**说明性的内容**有吗,在哪里,**这是很重要的内容**,
8
+ * > 是认知轨迹感知理解这些**边界的重要手册**」
9
+ *
10
+ * ⇒ 此前「描述自己」与「限制自己」被混成一章(`trajectory-constraint`),
11
+ * 而那两件事的**读者与前提完全不同**:
12
+ *
13
+ * | 面 | 谁需要 | 性质 |
14
+ * |---|---|---|
15
+ * | **描述自己**(我是谁、我擅长什么、我是否欢迎来麻烦我) | **所有轨迹** | 常态 |
16
+ * | **限制自己**(工具白名单、模型、MSM 白名单) | 只有需要限权的轨迹 | 特例 |
17
+ *
18
+ * ⇒ 把「描述」独立成章。**普通轨迹不必为了写一段自述而变成受约束的轨迹** ——
19
+ * 这是本章存在的**唯一理由**,也是 D173「常驻 = 自我声明」的落点。
20
+ *
21
+ * ## 归属(D23)
22
+ * 机制在 ACC(有哪些载体文件、字段叫什么、怎么被读、何时生效);
23
+ * **内容在 CCC**(这条轨迹具体写什么 —— 它的章程与自述是它自己的知识)。
24
+ *
25
+ * ## 与 `trajectory-constraint` 章的分工(一个内容一个正文入口)
26
+ * 本章 = **声明面(我是什么)**;那一章 = **约束面(我被限成什么)**。
27
+ * 两章互指,但**不复制**对方正文(本仓 v1.75.0 的指针纪律)。
28
+ */
29
+ /**
30
+ * 一章的全文。
31
+ *
32
+ * ⚠️ 本函数是**纯常量拼接**(无 IO、无 ctx):手册正文不随容器状态变化,
33
+ * 唯一随状态变的东西("我现在是不是轨迹约束")由 `config list` 回答,不写进正文。
34
+ */
35
+ export declare const TRAJECTORY_SELF_GUIDE: string;
@@ -59,8 +59,11 @@
59
59
  * (`<轨迹目录>/continuous-re-occurrence.ts`)⇒ 「怎么写它」是**逐轨迹**的知识,
60
60
  * 而**管轨迹的人正是用本工具的人**;挂这儿 = 设计 §9-5 的"**既有 guide 位置、不新增域**"。
61
61
  * ⚠️ 本动作**纯读**(输出指南正文,不碰 fs、不碰注册表)。
62
+ * 2026-10-20(D173 兑现,owner 令):「常驻」= 轨迹的**自我声明**,落地为 **`resident`** 只读动作。
63
+ * 判据:它管的是**被找到**,不是**不被销毁** ⇒ 纯读、不碰生命周期、不进系统提示词。
64
+ * ⚠️ 这也修正了 v1.77.1 的一处说明面失真(手册曾描述一个**不存在**的动作,D179(a))。
62
65
  */
63
- type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'uid' | 'send-now' | 'send-later' | 'cro-guide';
66
+ type CognitiveTrajectoryAction = 'list' | 'show' | 'create' | 'use' | 'rebuild' | 'archive' | 'uid' | 'send-now' | 'send-later' | 'cro-guide' | 'resident';
64
67
  export declare const COGNITIVE_TRAJECTORY_ACTIONS: readonly CognitiveTrajectoryAction[];
65
68
  /**
66
69
  * 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
@@ -0,0 +1,97 @@
1
+ /**
2
+ * trajectory/model-runtime.ts — **`trajectory.json` 的 `model` 字段:每轮求值 + 运行时热切换**
3
+ *
4
+ * ## 这个模块存在的唯一理由(owner 2026-10-20 令,逐字)
5
+ *
6
+ * > 「我认为认知轨迹作为统一概念 **设定不应根据入口不同而有变化 所以应当直接生效 运行时热加载**」
7
+ *
8
+ * ## 旧实现错在哪(D182 的事实底数)
9
+ *
10
+ * `model` 的**唯一消费点**曾是 `createTrajectoryAgent` 建 agent 时,而它的**唯一调用方**是
11
+ * IM 入站路由 ⇒ 对**已经存在的会话**(web 会话、`send-now` 冷启的载体……)**完全不生效**。
12
+ * 而 `container_admin config list` 把它印成 `'(cheap-worker default)'`,看上去像个统一设定。
13
+ * ⇒ **同一个字段在 A 入口生效、在 B 入口不生效** —— 正是 owner 要禁的形态。
14
+ *
15
+ * ## 修法:接宿主**自带**的原语,不复刻语义
16
+ *
17
+ * `@deepseek-ai/dsh-agent` 导出 `installModelSelection(agentCtx, selection)`,它挂三条缝:
18
+ *
19
+ * | 缝 | 作用 |
20
+ * |---|---|
21
+ * | `system-prompt/assemble` | 把选中路由写进提示词变量快照(保证"这一步用哪个模型"一致) |
22
+ * | **`agent/request`** | **把冻结的调用配置替换成 `selection.current`** ⇒ 这就是"下一轮生效" |
23
+ * | `agent/pre-step` | 路由变化时追加 `[model changed: …]` 持久提示 |
24
+ *
25
+ * 🔴 **为什么必须直接调它而不是自己挂 `agent/request`**(本仓既有纪律):
26
+ * 自己复刻就要重做第三条的三态判断(reject/空步/aborted)与 `requestHeader` 比对,
27
+ * **复刻错了会得到与事实相反的结论**。宿主已实现 ⇒ 直接调用。
28
+ *
29
+ * 🔴 **跨宿主档位**:`_tmp/host-*` 全部 **10 个**快照(`0.1.5-rc.1` … `0.2.1-rc.2`)
30
+ * 逐个核对,该导出**全部存在且签名一致**(唯一差异在宿主内部的提示 `source` 形状,
31
+ * **不由我们构造**)⇒ 无需宿主升级、无需分档分支。
32
+ *
33
+ * ## 三条硬边界(少一条就会重演 S196 的坑)
34
+ *
35
+ * 1. **未写 `model` ⇒ 不装这条缝**(`undefined` 原样返回)。⛔ **绝不回退 `cheap-worker.defaultModel`**
36
+ * —— 那个回退把"我想声明自述"变成"我被降级到杂工模型",而用户完全不会预期到。
37
+ * 2. **切换会留下宿主的 `[model changed: …]` 提示**。这是**特性不是噪声**:
38
+ * 否则模型不知道自己换了,它会照旧解释上方那些由**另一个模型**生成的回合。
39
+ * 3. **模型名非法 ⇒ 不切 + 响亮告警**,绝不静默停在旧模型上("改了没生效"是最难查的一类)。
40
+ *
41
+ * ## 归属(D23)
42
+ * **机制在 ACC**(怎么读、怎么装、何时刷新、非法怎么办);**内容在 CCC**(`trajectory.json` 写哪个模型)。
43
+ */
44
+ /**
45
+ * 从轨迹声明里取模型选择(**每轮调用**,故不缓存)。
46
+ *
47
+ * @param root CCC 根
48
+ * @param dirName 轨迹目录名
49
+ * @returns `{ provider?, model }`;**未写 `model` ⇒ `undefined`(= 不动当前模型)**
50
+ *
51
+ * ⚠️ 读盘失败/坏 JSON ⇒ `undefined` 并**不抛**:一次读失败不该让整轮失败
52
+ * (`readTrajectoryConstraintAt` 内部已对坏 JSON 告警)。
53
+ */
54
+ export declare function readModelSelection(root: string, dirName: string): {
55
+ provider?: string;
56
+ model: string;
57
+ } | undefined;
58
+ /**
59
+ * 该轨迹**是否声明了**模型(= 要不要装这条缝)。
60
+ *
61
+ * 🔴 与 `readModelSelection` 分开的理由:装上缝之后 `selection.current = undefined`
62
+ * 会**覆盖**宿主已有的选择(把它变成"没选"),所以**没写 `model` 的轨迹根本不该装缝**。
63
+ */
64
+ export declare function hasModelDeclared(root: string, dirName: string): boolean;
65
+ /** 进程态快照(`acc-diag` 级别的可观测性;**只暴露"装了没有",不暴露模型值**) */
66
+ export declare function modelRuntimeSnapshot(): {
67
+ keys: string[];
68
+ };
69
+ /** 会话销毁时清理 */
70
+ export declare function forgetModelRuntime(sessionId: string): void;
71
+ /**
72
+ * 给这个 agent 装**模型运行时热切换**(每轮重读 `trajectory.json`)。
73
+ *
74
+ * 🔴 **只在轨迹声明了 `model` 时装**(D182 边界 ①):
75
+ * 装了缝而 `current` 为 `undefined` 会**覆盖**宿主已有的模型选择 ⇒ 等于把会话的模型清空。
76
+ * 故"没写" **必须** 表现为"这条缝不存在",而不是"缝在、值为空"。
77
+ *
78
+ * 🔴 **每轮刷新**:`agent/pre-step` 每步都调它,求值器**重读磁盘**
79
+ * (`readTrajectoryConstraintAt` 无缓存)⇒ 改 `trajectory.json` 下一轮即生效,
80
+ * **与"谁创建了这个会话"无关**(这就是 owner 的令)。
81
+ *
82
+ * @param agent 目标 agent
83
+ * @param root CCC 根
84
+ * @param dirName 该 agent 绑定的轨迹目录名
85
+ * @returns 是否装了缝(`false` = 该轨迹没声明 model,未改动任何东西)
86
+ */
87
+ export declare function syncTrajectoryModel(agent: AgentLike, root: string, dirName: string): boolean;
88
+ /** 测试用:清掉宿主解析缓存(真实进程内不需要) */
89
+ export declare function resetModelRuntimeForTests(): void;
90
+ /** 最小 agent 形状(只用到 `session.id` 与 `ctx`;避免把整个 Agent 类型拖进来) */
91
+ interface AgentLike {
92
+ session?: {
93
+ id?: string;
94
+ };
95
+ ctx?: unknown;
96
+ }
97
+ export {};
@@ -54,12 +54,18 @@ export interface TrajectoryConstraint {
54
54
  code: string;
55
55
  /** `trajectory-constraint.json` 的绝对路径 */
56
56
  path: string;
57
- /** 模型(`provider/model`);缺省回退 `cheap-worker.defaultModel` */
57
+ /** 模型(`provider/model`);缺省 ⇒ 不改变会话当前模型(**不**回退 cheap-worker),见 D181 */
58
58
  model?: string;
59
- /** 非 MSM 工具白名单(白名单外**从 schema 移除**,不是"看得见调不动") */
60
- tools: string[];
61
- /** MSM 白名单(`msm()` 单入口内再过滤) */
62
- msms: string[];
59
+ /**
60
+ * 非 MSM 工具白名单。
61
+ *
62
+ * 🔴 **D181(owner 2026-10-20 裁「不写当然不收窄」):
63
+ * `undefined` = 字段缺省 ⇒ **不收窄**;`[]` = 显式空数组 ⇒ **全隐藏**(除恒可用的 `skill`)。**
64
+ * 两者是**不同的意图**,故类型必须是 `string[] | undefined`,不能归一成 `[]`。
65
+ */
66
+ tools?: string[];
67
+ /** MSM 白名单;缺省/空数组语义同 `tools`(D181) */
68
+ msms?: string[];
63
69
  /** 轨迹纪律参与项(**缺省全 true**,见上方 R↓) */
64
70
  cognitiveTrajectory: {
65
71
  session: boolean;
@@ -70,6 +76,16 @@ export interface TrajectoryConstraint {
70
76
  systemPrompt?: string;
71
77
  /** 提示词文件(相对 CCC 根;**优先于** `systemPrompt`) */
72
78
  systemPromptFile?: string;
79
+ /**
80
+ * 常驻自述(D173):我对**自己用途**的公开声明。
81
+ *
82
+ * 🔴 它**不是**资源状态(不是"永不休眠"),**不碰**载体生命周期,
83
+ * **不进系统提示词**(不占每轮 token)—— 只被 `cognitive_trajectory` 的
84
+ * `resident` 只读动作按需查出。两句判据:管的是**被找到**,不是**不被销毁**。
85
+ */
86
+ resident?: {
87
+ serves?: string;
88
+ };
73
89
  }
74
90
  /**
75
91
  * 读一个目录下的 `trajectory-constraint.json`(**纯读,不施加约束**)。
@@ -123,5 +139,34 @@ export declare function trajectoryConstraintForSession(root: string, sessionId:
123
139
  *
124
140
  * 存在的理由:调用点很多只要一个"是/不是"(seams 的旁路判定),
125
141
  * 让它们各自写 `!== null` 会把判据散开;**判据只能有一个入口**。
142
+ *
143
+ * 🔴 **D183(owner 2026-10-20):这里的 `true` 只表示"有声明",不再等于"该旁路默认行为"。**
144
+ * 要判断后者请用 `replacesDefaultIdentity()` —— 两者**刻意分开**,因为:
145
+ * 「我声明过」与「我声明了什么」是两件事,而旁路 ACC 身份只需要后者成立。
126
146
  */
127
147
  export declare function isTrajectoryConstrained(root: string, sessionId: string | undefined): boolean;
148
+ /**
149
+ * 这条声明**是否真的取代了该轨迹的默认身份**(= 该不该旁路 ACC 身份/首轮/恢复等默认行为)。
150
+ *
151
+ * ## 为什么需要它(D183,owner 2026-10-20 逐字)
152
+ *
153
+ * > 「这个 `trajectory.json` 中**所有 key 都是可选的;不写就是不设定 不应影响默认行为**」
154
+ *
155
+ * 旧判据是**文件存在** ⇒ 只写一句 `resident`(对外用途自述)就会连带丢掉
156
+ * ACC 身份播种、首轮锚定、自动恢复、compact 重注入 —— **完全超出用户预期**。
157
+ *
158
+ * ## 判据(哪些 key 真的改"这条轨迹是谁")
159
+ *
160
+ * | key | 取代默认身份? | 理由 |
161
+ * |---|---|---|
162
+ * | `tools` / `msms` | **否** | 它们收窄**能力**,但"我是谁、我按什么章程工作"没变 ⇒ 仍该拿 ACC 身份 |
163
+ * | `model` | **否** | 换的只是**执行的模型**,不是身份 |
164
+ * | `resident` | **否** | 纯对外自述,D173 明说"不碰任何默认行为" |
165
+ * | `cro.host` | **否** | 管"哪台机器跑 CRO 程序",与身份无关 |
166
+ * | `cognitiveTrajectory` | **否** | 管"参与哪些纪律",纪律是叠加不是取代 |
167
+ * | `systemPromptFile` / `systemPrompt` | **是** | 它自带**章程** ⇒ 用户的提示词整段取代 ACC 的 |
168
+ * | `PROMPT.md`(目录里) | **否** | 它**追加**在 ACC 身份之外(v1.72.0 起的机制),不取代 |
169
+ *
170
+ * @returns `true` ⇒ 该轨迹自带章程,应旁路 ACC 身份类默认行为;否则 `false`
171
+ */
172
+ export declare function replacesDefaultIdentity(profile: TrajectoryConstraint | null | undefined): boolean;
@@ -23,14 +23,27 @@ import type { TrajectoryConstraint } from './profile.js';
23
23
  /** 提示词来源提示串(`apply` 回执与面板共用;避免两处措辞漂) */
24
24
  export declare const TRAJECTORY_CONSTRAINT_HINT = "systemPromptFile hot-reloads by mtime; the rest of trajectory-constraint.json is re-read every turn";
25
25
  /**
26
- * 该 轨迹约束会话**可见**的工具面(tools ∪ `msm`)。
26
+ * 该轨迹会话**可见**的工具面(tools ∪ `msm`)。
27
27
  *
28
- * 🔴 `msm` 为什么自动加入:`msms` 非空意味着这个 轨迹约束 **有 MSM 通道**,
28
+ * 🔴 **返回值 `null` 有专门含义(D181,owner 2026-10-20 裁「不写当然不收窄」)**:
29
+ *
30
+ * | `tools` | `msms` | 返回 | 语义 |
31
+ * |---|---|---|---|
32
+ * | 缺省 | 缺省 | **`null`** | **不收窄任何工具面**(这条轨迹要的是别的设定,如只写 `resident`) |
33
+ * | `[]` | 缺省 | `{'msm'?}` 空/仅 msm | **全隐藏**(除恒可用的 `skill`) |
34
+ * | `['a']` | … | `{'a', …}` | 只给这些 |
35
+ *
36
+ * ⛔ **调用方不许把 `null` 当空集处理** —— 那正是 S196 被锁死的形态(D179(b))。
37
+ * 判据是**字段在不在**,不是数组空不空。
38
+ *
39
+ * 🔴 `msm` 为什么自动加入:`msms` 非空意味着这个轨迹 **有 MSM 通道**,
29
40
  * 而 MSM 的唯一合法调用入口是 `msm` 工具(单入口纪律)——不给它 `msm` 就等于
30
41
  * 声明了一堆调不动的能力。⚠️ 这是**并集**,不是覆盖:`tools` 里显式写的照旧生效。
42
+ *
43
+ * @returns 白名单集合;**`null` = 不收窄**(调用方须显式判 `null`)
31
44
  */
32
- export declare function profileToolWhitelist(profile: TrajectoryConstraint | null | undefined): Set<string>;
33
- /** 该 轨迹约束会话可 exec 的 MSM 白名单(`msm` 工具内部再过滤;独立于工具白名单) */
45
+ export declare function profileToolWhitelist(profile: TrajectoryConstraint | null | undefined): Set<string> | null;
46
+ /** 该轨迹会话可 exec 的 MSM 白名单(`msm` 工具内部再过滤;独立于工具白名单) */
34
47
  export declare function profileMsmWhitelist(profile: TrajectoryConstraint | null | undefined): Set<string>;
35
48
  /** 轨迹纪律参与项(`profile` 缺省已在 `readTrajectoryConstraintAt` 归一为全 true) */
36
49
  export declare function constraintCognitiveTrajectory(profile: TrajectoryConstraint | null | undefined): {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * trajectory/resident.ts — **常驻名单**(D173 的兑现口,`cognitive_trajectory resident` 的实现)
3
+ *
4
+ * ## 这条能力的判据(owner 2026-10-20 原话,逐字)
5
+ *
6
+ * > 「常驻的意思就是**自己声明为自己欢迎别人来麻烦自己**,和永不休眠没啥关系,
7
+ * > **trajectory never ends 悖论的一种实现**」
8
+ *
9
+ * ⇒ **常驻不是资源状态**。它不是"永不休眠"、不是"常驻内存"、不是"不许销毁我的会话"。
10
+ * 一条轨迹**永远存在**(没有生命周期),常驻与否都不改变这一点。
11
+ *
12
+ * ⇒ **它管的是「被找到」,不是「不被销毁」。**
13
+ * 两件事混为一谈,就会去动调度器的载体生命周期 —— 那正是本轨反复踩的"名字撒谎"。
14
+ *
15
+ * ## 因此本模块的三条硬边界
16
+ *
17
+ * 1. **纯读**:不写任何文件、不碰注册表、不碰 `wake/scheduler.ts`。
18
+ * 2. **不进系统提示词**(owner 裁决 Q2:说明类信息按需取,不占每轮 token)。
19
+ * 3. **只报写了 `serves` 的轨迹** —— 只写半个(有 `resident` 没 `serves`)等于
20
+ * "把自己挂上去却不说能干什么",按手册的判据那比不写更坏,所以**不列出来**,
21
+ * 但要**单独数出**来,让写的人知道自己的写法没生效。
22
+ *
23
+ * ## 归属(D23)
24
+ * 机制在 ACC(怎么找、按什么顺序、列什么字段);内容在 CCC(`serves` 那句话写什么)。
25
+ */
26
+ /** 名单里的一行 */
27
+ export interface ResidentEntry {
28
+ /** 轨迹编码(身份) */
29
+ code: string;
30
+ /** 轨迹目录名 */
31
+ dirName: string;
32
+ /** 它自己说的"我擅长什么、能帮上什么忙" */
33
+ serves: string;
34
+ /** 该轨迹是否另外声明了模型(方便找人时一并知道) */
35
+ model?: string;
36
+ }
37
+ /** 名单结果 */
38
+ export interface ResidentRoster {
39
+ /** 可被找到的轨迹(写了 `resident.serves`) */
40
+ entries: ResidentEntry[];
41
+ /** ⚠️ 写了 `resident` 但 **没写 `serves`** 的编码(写半个 = 别人会来问错人) */
42
+ incomplete: string[];
43
+ }
44
+ /**
45
+ * 列出全部**可被找到**的轨迹(按编码排序,确定性)。
46
+ *
47
+ * ⚠️ 读盘失败 / 坏 JSON 一律**跳过该条**(不抛):一份坏的声明不该让整张名单拿不到。
48
+ *
49
+ * @param root CCC 根
50
+ */
51
+ export declare function collectResident(root: string): ResidentRoster;
52
+ /**
53
+ * 渲染成给模型读的文本(`cognitive_trajectory resident` 的返回值)。
54
+ *
55
+ * 🔴 **空名单也是有效结果**,且必须说清"这意味着什么" —— 否则读者会以为工具坏了。
56
+ */
57
+ export declare function listResident(root: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.77.0",
3
+ "version": "1.78.0",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/IM 桥/认知轨迹唤醒注册表。轨迹声明 = 轨迹目录下的 trajectory.json(约束面 + 旧标识 uid + CRO 机器绑定 cro.host;旧名 trajectory-constraint.json / skiff.json 仍在读取族里);轨迹提示词 = 轨迹目录下的 PROMPT.md(一条轨迹「是谁、按什么章程工作」,所有轨迹通用,追加在容器层与质量层之后,不替换它们)。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {