@shgroup/dsh-serenity-hooks 1.87.2 → 1.87.3

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.87.2",
3
+ "version": "1.87.3",
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 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 认知轨迹日志与原地重建 + 网页登录入口/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(三档,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
@@ -20,6 +20,7 @@ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:
20
20
  var ilink_exports = /* @__PURE__ */ __exportAll({
21
21
  TypingStatus: () => TypingStatus,
22
22
  aes128EcbDecrypt: () => aes128EcbDecrypt,
23
+ buildBaseInfo: () => buildBaseInfo,
23
24
  buildClientVersion: () => buildClientVersion,
24
25
  buildMediaDownloadUrl: () => buildMediaDownloadUrl,
25
26
  downloadMedia: () => downloadMedia,
@@ -65,6 +66,30 @@ function buildCommonHeaders() {
65
66
  "iLink-App-ClientVersion": String(CLIENT_VERSION)
66
67
  };
67
68
  }
69
+ /**
70
+ * `base_info` —— **每一个业务 POST 都必须带**(2026-10-22 补齐,完全照官方实现)。
71
+ *
72
+ * ## 🔴 我们漏了这个字段,漏了很久
73
+ *
74
+ * 腾讯官方 `@tencent-weixin/openclaw-weixin` 的 `src/api/api.ts` 里,**九处**业务 POST
75
+ * 全部无条件附加它(`body: JSON.stringify({ ...body, base_info: buildBaseInfo() })`),
76
+ * 而 `buildBaseInfo()` 恒为 `{ channel_version: pkg.version }`。
77
+ *
78
+ * 我们这一侧长期只发 `{ msg: {...} }` —— **`base_info` 完全没有**。
79
+ *
80
+ * ## 症状为什么是「平时能发,隔久了发不出」
81
+ *
82
+ * 缺字段本身不会每次崩:入站带来的**新** `context_token` 给服务端提供了足够上下文,
83
+ * 请求照样成立。等票变旧、服务端进入 `prepare`(准备会话上下文)阶段时,
84
+ * **没有 `channel_version` 可依据** ⇒ 只能回 `ret=-2: prepare failed`。
85
+ * ⇒ 那个报错读作「准备阶段缺参数」,**不是**「会话过期」(后者是 `-14`)。
86
+ *
87
+ * ⚠️ 判据边界:本字段**不修复**「票真的过期」那种失败(那是 `-14`),
88
+ * 它修复的是**票在半旧窗口内、本该能发却被拒**的那一档。
89
+ */
90
+ function buildBaseInfo() {
91
+ return { channel_version: CHANNEL_VERSION };
92
+ }
68
93
  function buildHeaders(token) {
69
94
  return {
70
95
  "Content-Type": "application/json",
@@ -95,6 +120,30 @@ async function apiGet(params) {
95
120
  throw err;
96
121
  }
97
122
  }
123
+ /**
124
+ * 给 POST body 统一附加 `base_info`(**对齐官方做法**)。
125
+ *
126
+ * 官方的九处业务 POST 各自写 `{ ...body, base_info: buildBaseInfo() }`;
127
+ * 我们把它收进 `apiPost` 一处 —— 语义完全相同(每个 POST 都带上),
128
+ * 但**新增调用点不可能漏**(这正是我们当初漏掉它的形态:靠每处手写就会漏)。
129
+ *
130
+ * ⚠️ 只在 body 是 JSON 对象时附加;解析失败就原样发(不为了加字段把请求搞坏)。
131
+ * ⚠️ 已经带 `base_info` 的 body **不覆盖**(以调用方给的为准)。
132
+ */
133
+ function withBaseInfo(body) {
134
+ try {
135
+ const parsed = JSON.parse(body);
136
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return body;
137
+ const obj = parsed;
138
+ if (obj.base_info !== void 0) return body;
139
+ return JSON.stringify({
140
+ ...obj,
141
+ base_info: buildBaseInfo()
142
+ });
143
+ } catch {
144
+ return body;
145
+ }
146
+ }
98
147
  async function apiPost(params) {
99
148
  const base = ensureTrailingSlash(params.baseUrl);
100
149
  const url = new URL(params.endpoint, base);
@@ -104,7 +153,7 @@ async function apiPost(params) {
104
153
  const res = await currentFetch(url.toString(), {
105
154
  method: "POST",
106
155
  headers: buildHeaders(params.token),
107
- body: params.body,
156
+ body: withBaseInfo(params.body),
108
157
  signal: controller.signal
109
158
  });
110
159
  clearTimeout(timer);
@@ -214,7 +263,8 @@ async function sendTextMessage(params) {
214
263
  type: MessageItemType.TEXT,
215
264
  text_item: { text: markdownToPlainText(params.text) }
216
265
  }],
217
- ...params.contextToken ? { context_token: params.contextToken } : {}
266
+ ...params.contextToken ? { context_token: params.contextToken } : {},
267
+ ...params.runId ? { run_id: params.runId } : {}
218
268
  } }),
219
269
  token: params.token,
220
270
  timeoutMs: params.timeoutMs ?? 15e3
package/lib/im/ilink.d.ts CHANGED
@@ -16,6 +16,30 @@
16
16
  */
17
17
  /** 版本号 → ClientVersion 整数:(major<<16)|(minor<<8)|patch */
18
18
  export declare function buildClientVersion(version: string): number;
19
+ /**
20
+ * `base_info` —— **每一个业务 POST 都必须带**(2026-10-22 补齐,完全照官方实现)。
21
+ *
22
+ * ## 🔴 我们漏了这个字段,漏了很久
23
+ *
24
+ * 腾讯官方 `@tencent-weixin/openclaw-weixin` 的 `src/api/api.ts` 里,**九处**业务 POST
25
+ * 全部无条件附加它(`body: JSON.stringify({ ...body, base_info: buildBaseInfo() })`),
26
+ * 而 `buildBaseInfo()` 恒为 `{ channel_version: pkg.version }`。
27
+ *
28
+ * 我们这一侧长期只发 `{ msg: {...} }` —— **`base_info` 完全没有**。
29
+ *
30
+ * ## 症状为什么是「平时能发,隔久了发不出」
31
+ *
32
+ * 缺字段本身不会每次崩:入站带来的**新** `context_token` 给服务端提供了足够上下文,
33
+ * 请求照样成立。等票变旧、服务端进入 `prepare`(准备会话上下文)阶段时,
34
+ * **没有 `channel_version` 可依据** ⇒ 只能回 `ret=-2: prepare failed`。
35
+ * ⇒ 那个报错读作「准备阶段缺参数」,**不是**「会话过期」(后者是 `-14`)。
36
+ *
37
+ * ⚠️ 判据边界:本字段**不修复**「票真的过期」那种失败(那是 `-14`),
38
+ * 它修复的是**票在半旧窗口内、本该能发却被拒**的那一档。
39
+ */
40
+ export declare function buildBaseInfo(): {
41
+ channel_version: string;
42
+ };
19
43
  /** fetch 注入点(测试替换;生产 = 全局 fetch) */
20
44
  type FetchLike = typeof fetch;
21
45
  /** 测试注入:替换全局 fetch 实现(生产零调用) */
@@ -126,6 +150,8 @@ export declare function sendTextMessage(params: {
126
150
  text: string;
127
151
  contextToken?: string;
128
152
  clientId?: string;
153
+ /** 本轮次的运行 id(官方每条出站都带;同一次回复的多条消息共享它) */
154
+ runId?: string;
129
155
  timeoutMs?: number;
130
156
  }): Promise<void>;
131
157
  /**
package/lib/index.js CHANGED
@@ -9,7 +9,7 @@ import { n as hasSessionLogById, r as sessionsRootDir } from "./cleanup-C-92qnDs
9
9
  import { n as readSimpleSettings, r as registerSettingsSection } from "./settings-H7TVDayZ.js";
10
10
  import { a as readFeishuCredential, c as readWeixinSettings, d as weixinInboundDir, f as weixinSessionIdForTrajectory, i as matchWeixinRoute, n as extractWeixinText, o as readImSettings, r as hasVoiceItem, s as readWeixinCredential, t as extractWeixinMedia, u as sanitizeFileName$1 } from "./route-3OzOAjec.js";
11
11
  import { REGISTERED_TOOLS } from "./invariant.js";
12
- import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-BMk2w2KY.js";
12
+ import { c as sendTextMessage, i as getUpdates, l as sendTyping, n as downloadMedia, o as markdownToPlainText, r as getConfig, s as sendFileMessage, t as TypingStatus, u as sniffImageExt } from "./ilink-CsZ-f64q.js";
13
13
  import { a as registerImAccountImpl, i as normalizeAccountChannel, n as getImAccountImpl, r as imAccountChannels } from "./accounts-BHQbakUh.js";
14
14
  import { createRequire } from "node:module";
15
15
  import z from "@deepseek-ai/schemastery";
@@ -14344,7 +14344,7 @@ function registerStatusApi(ctx, opts = {}) {
14344
14344
  }
14345
14345
  const root = chk.root;
14346
14346
  if (body.action === "login-start") {
14347
- const { fetchQRCode } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
14347
+ const { fetchQRCode } = await import("./ilink-CsZ-f64q.js").then((n) => n.a);
14348
14348
  const { randomUUID } = await import("node:crypto");
14349
14349
  for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
14350
14350
  const { readWeixinSettings } = await import("./route-3OzOAjec.js").then((n) => n.l);
@@ -14478,7 +14478,7 @@ function registerStatusApi(ctx, opts = {}) {
14478
14478
  sendJson$2(res, 200, { status: "expired" });
14479
14479
  return;
14480
14480
  }
14481
- const { pollQRStatus } = await import("./ilink-BMk2w2KY.js").then((n) => n.a);
14481
+ const { pollQRStatus } = await import("./ilink-CsZ-f64q.js").then((n) => n.a);
14482
14482
  const { readWeixinSettings } = await import("./route-3OzOAjec.js").then((n) => n.l);
14483
14483
  const settings = readWeixinSettings(login.root);
14484
14484
  const status = await pollQRStatus({
@@ -20190,7 +20190,8 @@ async function handleIncoming(ctx, root, accountId, cred, msg, preset) {
20190
20190
  token: cred.token,
20191
20191
  toUserId,
20192
20192
  text,
20193
- contextToken: replyRef
20193
+ contextToken: replyRef,
20194
+ runId: `dsp-run-${randomBytes(6).toString("hex")}`
20194
20195
  });
20195
20196
  },
20196
20197
  typingStart: ({ toUserId, replyRef }) => sendTypingStart(cred, accountId, toUserId, replyRef),
@@ -20533,7 +20534,8 @@ async function sendProactiveText(input) {
20533
20534
  token: cred.token,
20534
20535
  toUserId,
20535
20536
  text: input.text,
20536
- ...contextToken === void 0 ? {} : { contextToken }
20537
+ ...contextToken === void 0 ? {} : { contextToken },
20538
+ runId: `dsp-run-${randomBytes(6).toString("hex")}`
20537
20539
  });
20538
20540
  } catch (err) {
20539
20541
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.87.2",
3
+ "version": "1.87.3",
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": {