@shgroup/dsh-serenity-hooks 1.69.3 → 1.69.4

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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.69.3",
3
+ "version": "1.69.4",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/cognitive_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/index.js CHANGED
@@ -13909,6 +13909,13 @@ const DEFAULT_COMPACTION_TOOLS = [
13909
13909
  */
13910
13910
  const DEFAULT_ANCHOR_MESSAGES = ["You are the operator agent of Serenity — a cognitive container governed by the Abstract Cognitive Container (ACC) protocol.\nWe operate under the Explicit Abstraction Principle (EAP): the functional value of a thought equals its external reconstructability — every output we produce must be explicit (E↑), reconstructable (R↓), and stable (S↑).\nThe personal pronoun is us/we.\nWe anchor first, then act: the abstract layer precedes the concrete.\nPlease simply reply \"acknowledge\" — no action needed.", "Before we proceed, align on how we work:\n1. We read before we write — every decision grounds in what already exists in the container.\n2. Every output records its reasoning (R↓): decisions carry reasons and alternatives.\n3. We never jump levels — abstract layer first, then specifics.\n4. Every artifact we create is a durable cognitive anchor for the work that follows.\n5. We keep the container's state coherent (SESSION.md) as we advance.\nPlease simply reply \"acknowledge\" — no action needed."];
13911
13911
  /**
13912
+ * 锚定交换里「模型那半」的回复文本。
13913
+ *
13914
+ * 只写一个词:锚定话语明确要求 `reply "acknowledge" — no action needed`,
13915
+ * 伪造一条更长的回复反而与提示词不符(模型会据历史学样)。
13916
+ */
13917
+ const ANCHOR_ACK_TEXT = "acknowledge";
13918
+ /**
13912
13919
  * 会话里是否已有模型回复(= 已经不是首轮)。
13913
13920
  *
13914
13921
  * 🔴 这是锚定的**形状判据**(v1.62):锚定只能在**首轮**造。
@@ -13924,14 +13931,24 @@ function hasAssistantHistory(session) {
13924
13931
  *
13925
13932
  * ## 🔴 两条硬约束(宿主 v4 格式校验器,实测逐条踩过)
13926
13933
  *
13927
- * **约束 A — 只能写 `user/message`**:`system/message` 与 `assistant/message` 都在
13928
- * `STEP_EVENT_TYPES` 里 ⇒ 必须过 `requireStep()`(`data.turn`/`data.step` 精确匹配
13929
- * **当前打开的** turn 与 step,且为 null 时同样拒绝)。而本函数在 `agent/pre-step`
13930
- * 前后被调用时 **turn/step 都还没打开**(`turn/start` 在 preStep 之前,`step/start`
13931
- * 在 preStep 之后)⇒ 代写它们**结构性非法**。
13932
- * ⚠️ 症状:写通道不校验 ⇒ **首轮正常**;**重新进入**时重放才抛
13933
- * `system/message does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
13934
- * ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案因此作废**。
13934
+ * **约束 A — `system/message` 绝不能代写;`assistant/message` 可以,但必须落在已打开的 turn/step 内**。
13935
+ *
13936
+ * 🔴 **D140 的理由已由本文件 `tests/anchor-history-shape.test.ts` 当场更正**(探针直调宿主
13937
+ * `assertReleasedV4Relationships()`,不靠读源码推断):
13938
+ * 原记「`system/message` 与 `assistant/message` 都在 `STEP_EVENT_TYPES` 里」—— **只对前半**。
13939
+ * 实测 `STEP_EVENT_TYPES` = `{ system/message, developer/message, assistant/attempt }`,
13940
+ * **`assistant/message` 不在其中**。它仍要过 `requireStep`,但走的是另一条路
13941
+ * (`Relationships.accept()` → `case "assistant/message"` → `tool()` → `requireStep`)。
13942
+ *
13943
+ * ⇒ **结论保留、理由作废**:`assistant/message` 在**该 system/message 之前**写是非法的
13944
+ * (那时 turn/step 尚未打开);而在**之后**写是**合法的**(那时两者都已打开)。
13945
+ * 探针三态实测:A(仅 user)✅ / **B(user + assistant 交替,turn/step 匹配)✅** /
13946
+ * C(assistant 落在未打开的 turn/step)❌ `does not match an open turn and step`。
13947
+ *
13948
+ * ⚠️ 症状(不许省这条):写通道不校验 ⇒ **首轮看着正常**;**重新进入**时重放才抛
13949
+ * `does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
13950
+ * ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案仍然作废**
13951
+ * (它写的是 `system/message`,那**确实**在 `STEP_EVENT_TYPES` 里)。
13935
13952
  *
13936
13953
  * **约束 B — 必须写在宿主提交 `system/message` 之后**:格式层有 `protectedHead`
13937
13954
  * 机制 —— `foldSurface()` 里 `system/message` 若在 **surface 非空且尚无 protectedHead**
@@ -13944,38 +13961,69 @@ function hasAssistantHistory(session) {
13944
13961
  * ⇒ 因此调用时机 = **`session/event` 里看到该会话第一条 `system/message` 之后**(见下方注册处),
13945
13962
  * 而不是 `agent/inbox/inserted`(那太早)。
13946
13963
  *
13947
- * ## 为什么去掉 assistant 那半不损失什么
13964
+ * ## 为什么必须成对(user + assistant ack)—— v1.69.4 修正
13965
+ *
13966
+ * 只造 user 的那一版在**界面上读不成对话**:两条"用户"消息连排在模型回复之前,
13967
+ * 看起来像用户自己连说了两句协议、而模型没理。owner 2026-10-19 报的两点
13968
+ * (「助手的回应也需要造出来」+「用户的怎么跑到第一个了」)其实是**同一个缺陷的两面**。
13969
+ * ⇒ 造**成对交换**:每条锚定话语后面跟一条 `acknowledge` 回复。
13948
13970
  *
13949
- * 锚定话语的**唯一目的**是让模型认为自己已经同意过协作协议。
13950
- * 保留 **2 条 `user/message`** 即可达成;丢掉的只是"模型自己的 ack 回复"——
13951
- * 而那原本也是**我们伪造的**,不是模型真说过的。
13971
+ * 🔴 **前提条件**:`assistant/message` 必须落在**已打开**的 turn/step 内
13972
+ * (探针 S3/S4 实测:未打开/已关闭都抛 `does not match an open turn and step`)。
13973
+ * 本函数由 `session/event` 的 `system/message` 分支触发,而**那条事件自己就带着
13974
+ * `data.turn` / `data.step`** —— 且实测此刻 turn/step 确实处于打开态
13975
+ * (真机日志:`step/start` → `system/message` → 锚定写入 → `step/end`)。
13976
+ * ⇒ turn/step **从触发事件透传进来**,不自己猜。
13977
+ *
13978
+ * ⚠️ **拿不到 turn/step 时退回只造 user**(不抛、不阻断):宁可少伪造一条 ack,
13979
+ * 也绝不在非法坐标上写 `assistant/message` —— 那会让会话在重放时 corrupt。
13952
13980
  *
13953
13981
  * ## 判据(改这个函数前后都必须跑)
13954
13982
  *
13955
- * ① 单测钉形状(只许 user/message);
13956
- * ② **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
13983
+ * ① 形状判据 = `tests/anchor-history-shape.test.ts`(直调宿主校验器,正控 + 两种负控);
13984
+ * ② 单测钉事件序列(user/assistant 交替、turn/step 逐条匹配);
13985
+ * ③ **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
13957
13986
  * (前两次修复都是"单测全绿 + 首轮正常"却仍然 corrupt)。
13958
- * ③ 直接调宿主自己的 `assertReleasedV4Relationships()` 判形状,**不要自写复刻**。
13959
13987
  *
13960
13988
  * @param session 会话(需有 append)
13961
13989
  * @param anchorMessages 锚定话语序列(缺省协议固有两条)
13990
+ * @param slot 触发本轮 `system/message` 所在的 turn/step(缺省或非法 ⇒ 只造 user)
13962
13991
  * @returns 是否成功写入
13963
13992
  */
13964
- function appendAnchorHistory(session, anchorMessages = DEFAULT_ANCHOR_MESSAGES) {
13993
+ function appendAnchorHistory(session, anchorMessages = DEFAULT_ANCHOR_MESSAGES, slot) {
13965
13994
  const target = session;
13966
13995
  if (typeof target?.append !== "function") return false;
13967
13996
  const append = target.append.bind(target);
13968
13997
  const rid = (prefix) => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
13998
+ const turn = typeof slot?.turn === "number" ? slot.turn : 0;
13999
+ const step = typeof slot?.step === "number" ? slot.step : 0;
14000
+ const canPlaceAck = turn > 0 && step > 0;
13969
14001
  try {
13970
- for (let i = 0; i < anchorMessages.length; i++) append("user/message", {
13971
- id: rid(`anchor-user-${i + 1}`),
13972
- role: "user",
13973
- content: [{
13974
- type: "text",
13975
- text: anchorMessages[i]
13976
- }],
13977
- source: { kind: "user" }
13978
- }, { surfaceOp: "append" });
14002
+ for (let i = 0; i < anchorMessages.length; i++) {
14003
+ append("user/message", {
14004
+ id: rid(`anchor-user-${i + 1}`),
14005
+ role: "user",
14006
+ content: [{
14007
+ type: "text",
14008
+ text: anchorMessages[i]
14009
+ }],
14010
+ source: { kind: "user" }
14011
+ }, { surfaceOp: "append" });
14012
+ if (!canPlaceAck) continue;
14013
+ append("assistant/message", {
14014
+ turn,
14015
+ step,
14016
+ message: {
14017
+ id: rid(`anchor-ack-${i + 1}`),
14018
+ role: "assistant",
14019
+ content: [{
14020
+ type: "text",
14021
+ text: ANCHOR_ACK_TEXT
14022
+ }],
14023
+ source: { kind: "model" }
14024
+ }
14025
+ }, { surfaceOp: "append" });
14026
+ }
13979
14027
  return anchorMessages.length > 0;
13980
14028
  } catch {
13981
14029
  return false;
@@ -14152,24 +14200,30 @@ function registerBootstrap(ctx) {
14152
14200
  const root = cccRootForCwd(cwd);
14153
14201
  if (!root) return;
14154
14202
  trackerFor(root).observe(session, event);
14155
- if (event.type === "system/message") setTimeout(() => {
14156
- try {
14157
- maybeAnchor(session, root);
14158
- } catch {}
14159
- }, 0);
14203
+ if (event.type === "system/message") {
14204
+ const slot = event.data;
14205
+ setTimeout(() => {
14206
+ try {
14207
+ maybeAnchor(session, root, slot);
14208
+ } catch {}
14209
+ }, 0);
14210
+ }
14160
14211
  } catch {}
14161
14212
  });
14162
14213
  /** 造锚定历史(幂等;防重靠 `anchoredSessions`)。 */
14163
- const maybeAnchor = (session, _root) => {
14214
+ const maybeAnchor = (session, _root, slot) => {
14164
14215
  const s = session;
14165
14216
  s?.header?.delegationDepth;
14166
14217
  const sid = typeof s?.id === "string" ? s.id : void 0;
14167
14218
  if (sid !== void 0 && anchoredSessions.has(sid)) return;
14168
14219
  if (sid !== void 0 && (sid.startsWith("cheap-worker-") || isSkiffSessionId(sid))) return;
14169
14220
  if (hasAssistantHistory(session)) return;
14170
- const wrote = appendAnchorHistory(session, SETTINGS.anchorMessages);
14221
+ const wrote = appendAnchorHistory(session, SETTINGS.anchorMessages, slot);
14171
14222
  if (wrote && sid !== void 0) anchoredSessions.add(sid);
14172
- if (wrote) warnOnce(`anchor history fabricated: ${SETTINGS.anchorMessages.length} 条 user 消息(不再真跑)`);
14223
+ if (wrote) {
14224
+ const paired = typeof slot?.turn === "number" && typeof slot?.step === "number" && slot.turn > 0 && slot.step > 0;
14225
+ warnOnce(`anchor history fabricated: ${SETTINGS.anchorMessages.length} 条 user 消息` + (paired ? ` + ${SETTINGS.anchorMessages.length} 条 ack(turn ${String(slot.turn)} / step ${String(slot.step)})` : "(无打开坐标 ⇒ 未造 ack)") + "(不再真跑)");
14226
+ }
14173
14227
  };
14174
14228
  ctx.on("agent/inbox/inserted", ({ agent, message }) => {
14175
14229
  try {
@@ -50,6 +50,13 @@ export declare const DEFAULT_COMPACTION_TOOLS: string[];
50
50
  * 文本 = 原 home-serenity CCC 配置(anchorMessages),随零配置化固化进代码。
51
51
  */
52
52
  export declare const DEFAULT_ANCHOR_MESSAGES: string[];
53
+ /**
54
+ * 锚定交换里「模型那半」的回复文本。
55
+ *
56
+ * 只写一个词:锚定话语明确要求 `reply "acknowledge" — no action needed`,
57
+ * 伪造一条更长的回复反而与提示词不符(模型会据历史学样)。
58
+ */
59
+ export declare const ANCHOR_ACK_TEXT = "acknowledge";
53
60
  /**
54
61
  * 根会话锚定重入判定(v1.28.1 提取导出以便单测):会话是否已有真实用户消息。
55
62
  * resume/续跑的会话已有对话历史 → **不重锚**(first-anchor 只注入一次)。
@@ -73,14 +80,24 @@ export declare function hasAssistantHistory(session: unknown): boolean;
73
80
  *
74
81
  * ## 🔴 两条硬约束(宿主 v4 格式校验器,实测逐条踩过)
75
82
  *
76
- * **约束 A — 只能写 `user/message`**:`system/message` 与 `assistant/message` 都在
77
- * `STEP_EVENT_TYPES` 里 ⇒ 必须过 `requireStep()`(`data.turn`/`data.step` 精确匹配
78
- * **当前打开的** turn 与 step,且为 null 时同样拒绝)。而本函数在 `agent/pre-step`
79
- * 前后被调用时 **turn/step 都还没打开**(`turn/start` 在 preStep 之前,`step/start`
80
- * 在 preStep 之后)⇒ 代写它们**结构性非法**。
81
- * ⚠️ 症状:写通道不校验 ⇒ **首轮正常**;**重新进入**时重放才抛
82
- * `system/message does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
83
- * ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案因此作废**。
83
+ * **约束 A — `system/message` 绝不能代写;`assistant/message` 可以,但必须落在已打开的 turn/step 内**。
84
+ *
85
+ * 🔴 **D140 的理由已由本文件 `tests/anchor-history-shape.test.ts` 当场更正**(探针直调宿主
86
+ * `assertReleasedV4Relationships()`,不靠读源码推断):
87
+ * 原记「`system/message` 与 `assistant/message` 都在 `STEP_EVENT_TYPES` 里」—— **只对前半**。
88
+ * 实测 `STEP_EVENT_TYPES` = `{ system/message, developer/message, assistant/attempt }`,
89
+ * **`assistant/message` 不在其中**。它仍要过 `requireStep`,但走的是另一条路
90
+ * (`Relationships.accept()` → `case "assistant/message"` → `tool()` → `requireStep`)。
91
+ *
92
+ * ⇒ **结论保留、理由作废**:`assistant/message` 在**该 system/message 之前**写是非法的
93
+ * (那时 turn/step 尚未打开);而在**之后**写是**合法的**(那时两者都已打开)。
94
+ * 探针三态实测:A(仅 user)✅ / **B(user + assistant 交替,turn/step 匹配)✅** /
95
+ * C(assistant 落在未打开的 turn/step)❌ `does not match an open turn and step`。
96
+ *
97
+ * ⚠️ 症状(不许省这条):写通道不校验 ⇒ **首轮看着正常**;**重新进入**时重放才抛
98
+ * `does not match an open turn and step` ⇒ 会话 corrupt、界面空白。
99
+ * ⇒ **v1.60 那版「先写占位 system 等宿主 replace」的整套方案仍然作废**
100
+ * (它写的是 `system/message`,那**确实**在 `STEP_EVENT_TYPES` 里)。
84
101
  *
85
102
  * **约束 B — 必须写在宿主提交 `system/message` 之后**:格式层有 `protectedHead`
86
103
  * 机制 —— `foldSurface()` 里 `system/message` 若在 **surface 非空且尚无 protectedHead**
@@ -93,24 +110,39 @@ export declare function hasAssistantHistory(session: unknown): boolean;
93
110
  * ⇒ 因此调用时机 = **`session/event` 里看到该会话第一条 `system/message` 之后**(见下方注册处),
94
111
  * 而不是 `agent/inbox/inserted`(那太早)。
95
112
  *
96
- * ## 为什么去掉 assistant 那半不损失什么
113
+ * ## 为什么必须成对(user + assistant ack)—— v1.69.4 修正
114
+ *
115
+ * 只造 user 的那一版在**界面上读不成对话**:两条"用户"消息连排在模型回复之前,
116
+ * 看起来像用户自己连说了两句协议、而模型没理。owner 2026-10-19 报的两点
117
+ * (「助手的回应也需要造出来」+「用户的怎么跑到第一个了」)其实是**同一个缺陷的两面**。
118
+ * ⇒ 造**成对交换**:每条锚定话语后面跟一条 `acknowledge` 回复。
119
+ *
120
+ * 🔴 **前提条件**:`assistant/message` 必须落在**已打开**的 turn/step 内
121
+ * (探针 S3/S4 实测:未打开/已关闭都抛 `does not match an open turn and step`)。
122
+ * 本函数由 `session/event` 的 `system/message` 分支触发,而**那条事件自己就带着
123
+ * `data.turn` / `data.step`** —— 且实测此刻 turn/step 确实处于打开态
124
+ * (真机日志:`step/start` → `system/message` → 锚定写入 → `step/end`)。
125
+ * ⇒ turn/step **从触发事件透传进来**,不自己猜。
97
126
  *
98
- * 锚定话语的**唯一目的**是让模型认为自己已经同意过协作协议。
99
- * 保留 **2 条 `user/message`** 即可达成;丢掉的只是"模型自己的 ack 回复"——
100
- * 而那原本也是**我们伪造的**,不是模型真说过的。
127
+ * ⚠️ **拿不到 turn/step 时退回只造 user**(不抛、不阻断):宁可少伪造一条 ack,
128
+ * 也绝不在非法坐标上写 `assistant/message` —— 那会让会话在重放时 corrupt。
101
129
  *
102
130
  * ## 判据(改这个函数前后都必须跑)
103
131
  *
104
- * ① 单测钉形状(只许 user/message);
105
- * ② **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
132
+ * ① 形状判据 = `tests/anchor-history-shape.test.ts`(直调宿主校验器,正控 + 两种负控);
133
+ * ② 单测钉事件序列(user/assistant 交替、turn/step 逐条匹配);
134
+ * ③ **容器内真跑一轮 → 重启宿主 → 重新进入该会话** —— 只有这条路能证明日志合法
106
135
  * (前两次修复都是"单测全绿 + 首轮正常"却仍然 corrupt)。
107
- * ③ 直接调宿主自己的 `assertReleasedV4Relationships()` 判形状,**不要自写复刻**。
108
136
  *
109
137
  * @param session 会话(需有 append)
110
138
  * @param anchorMessages 锚定话语序列(缺省协议固有两条)
139
+ * @param slot 触发本轮 `system/message` 所在的 turn/step(缺省或非法 ⇒ 只造 user)
111
140
  * @returns 是否成功写入
112
141
  */
113
- export declare function appendAnchorHistory(session: unknown, anchorMessages?: string[]): boolean;
142
+ export declare function appendAnchorHistory(session: unknown, anchorMessages?: string[], slot?: {
143
+ turn?: unknown;
144
+ step?: unknown;
145
+ }): boolean;
114
146
  interface PromotionStatus {
115
147
  /** 最后一次 compaction/end 的 seq(-1 = 未压缩过) */
116
148
  boundary: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.69.3",
3
+ "version": "1.69.4",
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 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/官方 ACP 面/认知轨迹唤醒注册表。适配 DSH 0.1.7-rc.1 / 0.2.0-rc.1 / 0.2.1-rc.1(三档)。",
5
5
  "license": "MIT",
6
6
  "repository": {