@shgroup/dsh-serenity-hooks 1.57.2 → 1.58.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/dsh.plugin.json +1 -1
- package/lib/cro.d.ts +90 -0
- package/lib/index.js +194 -2
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 与 0.2.0-rc.1(双档,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/cro.d.ts
CHANGED
|
@@ -45,6 +45,14 @@
|
|
|
45
45
|
*/
|
|
46
46
|
/** 入口文件名(**全写**,所有者令 2026-09-19;不改缩写) */
|
|
47
47
|
export declare const CRO_FILENAME = "continuous-re-occurrence.ts";
|
|
48
|
+
/**
|
|
49
|
+
* 机器绑定文件名(**全写**,与 `CRO_FILENAME` 同风格)。
|
|
50
|
+
*
|
|
51
|
+
* 🔴 **它随 CCC git 走是设计意图,不是缺陷**:
|
|
52
|
+
* 绑定声明必须跟着**轨迹**走,才能在第二台机器上被读到、并被判为"不是我的"。
|
|
53
|
+
* (这正是机器绑定的整个目的 —— 见 `resolveCroHostBinding` 的头注。)
|
|
54
|
+
*/
|
|
55
|
+
export declare const CRO_HOST_FILENAME = "cro-host-binding";
|
|
48
56
|
/** 硬超时(沿用 `biasProvider` 先例的 60s;设计 §5 情形 2) */
|
|
49
57
|
export declare const CRO_TIMEOUT_MS = 60000;
|
|
50
58
|
/**
|
|
@@ -223,6 +231,88 @@ export type CroRunner = (scriptAbs: string, stdinJson: string, timeoutMs: number
|
|
|
223
231
|
export declare function croScriptPath(root: string, dirName: string): string;
|
|
224
232
|
/** CRO 是否启用(**判据 = 文件在不在**;设计 §2.1:无 enabled 字段、无注册表) */
|
|
225
233
|
export declare function isCroEnabled(root: string, dirName: string): boolean;
|
|
234
|
+
/**
|
|
235
|
+
* 机器绑定判定结果(**永不抛**)。
|
|
236
|
+
*
|
|
237
|
+
* 三态而非布尔:**「没绑」与「绑给别人」必须分得开**(见下"为什么三态")。
|
|
238
|
+
*/
|
|
239
|
+
export interface CroHostVerdict {
|
|
240
|
+
/** 这台机器可以跑吗(唯一决定投递与否的字段) */
|
|
241
|
+
allowed: boolean;
|
|
242
|
+
/** 绑定文件里写的机器名(未绑/读不到 ⇒ `null`) */
|
|
243
|
+
bound: string | null;
|
|
244
|
+
/** 本机机器名 */
|
|
245
|
+
self: string;
|
|
246
|
+
/**
|
|
247
|
+
* 机器可读的判定原因(稳定码,供测试与日志断言)。
|
|
248
|
+
* · `bound-to-self` —— 已绑且是本机 ⇒ 跑
|
|
249
|
+
* · `not-bound` —— **没有绑定文件**(或文件是空的)⇒ **不跑**(fail-closed)
|
|
250
|
+
* · `bound-to-other` —— 绑给了别的机器 ⇒ 不跑
|
|
251
|
+
* · `read-error` —— 文件在但读不了 ⇒ 不跑(fail-closed)
|
|
252
|
+
*/
|
|
253
|
+
code: 'bound-to-self' | 'not-bound' | 'bound-to-other' | 'read-error';
|
|
254
|
+
/** 人读说明(进 tick 日志) */
|
|
255
|
+
detail: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 解析机器绑定 —— 「**这条轨迹由哪台机器负责自唤醒**」(所有者令 2026-10-15)。
|
|
259
|
+
*
|
|
260
|
+
* ## 所有者裁定的语义(逐字,D135)
|
|
261
|
+
*
|
|
262
|
+
* 「**CRO 必须指定绑定机器,否则不运行,唤醒器 检查这个**」⇒ 三条同时成立:
|
|
263
|
+
* ① **只许这一台**(白名单,不是"排除这一台");
|
|
264
|
+
* ② **fail-closed** —— **没绑 = 不跑**(不是"没绑 = 随便跑");
|
|
265
|
+
* ③ **检查者在唤醒器**(本函数由 `runCroPhase` 调用,不是程序自己判)。
|
|
266
|
+
*
|
|
267
|
+
* ## 绑定键 = 机器名(所有者 2026-10-15 裁「用 hostname 吧,比较合理」)
|
|
268
|
+
*
|
|
269
|
+
* ⚠️ **已接受的取舍**:本机机器名是 QEMU 机型派生的默认形
|
|
270
|
+
* (`yh-Standard-PC-Q35-ICH9-2009`)⇒ 同法新建的同名用户 VM 会取到同一个名字。
|
|
271
|
+
* 所有者 2026-10-15 明确「**不改名,家里就这几台机器没必要**」⇒ **风险由所有者接受**。
|
|
272
|
+
*
|
|
273
|
+
* ## 🔴 为什么必须是三态,「没绑」不能并进「绑给别人」
|
|
274
|
+
*
|
|
275
|
+
* 2026-10-15 已发生过**同族事故**:`send-now` 失败时报的是「无绑定会话记录」,
|
|
276
|
+
* 而真因是"那条轨迹不可寻址"—— **文案相同、原因不同**,害得排查方向错了一次
|
|
277
|
+
* (S142 §日志 续 129 / D134)。
|
|
278
|
+
* ⇒ 绑定这一层**不许再造同样的坑**:「你没绑」与「你绑给了别人」是**两件不同的事**,
|
|
279
|
+
* 日志必须分开写 —— 否则将来"为什么这条轨迹不跑了"会又一次指错方向。
|
|
280
|
+
*
|
|
281
|
+
* ## 文件形态(刻意的极简)
|
|
282
|
+
*
|
|
283
|
+
* · 位置 = `<轨迹目录>/cro-host-binding`(与 CRO 程序**同级**)
|
|
284
|
+
* · 内容 = **一行机器名**(首尾空白裁掉;只取第一行有效内容)
|
|
285
|
+
* · 不用 JSON —— 它只有一个值,JSON 只会多一种写错的方式
|
|
286
|
+
* · 支持 `#` 注释行与空行(方便人写、方便留痕)
|
|
287
|
+
*
|
|
288
|
+
* 🔴 **它必须随 git 入库**(不是"运行态"):绑定声明要**跟着轨迹走到第二台机器**,
|
|
289
|
+
* 在那里被判成「绑给了别人」而**正确地不跑** —— 那就是本机制在工作。
|
|
290
|
+
* ⇒ ⛔ **不要把它加进 `.gitignore`**(那会让第二台机器读到"没绑"而同样不跑,
|
|
291
|
+
* 表面上结果一样,但**判据从"明确绑给别人"退化成"什么都没说"**,失去可诊断性)。
|
|
292
|
+
*
|
|
293
|
+
* ## 为什么读文件而不是读快照字段
|
|
294
|
+
*
|
|
295
|
+
* 快照(`CroSnapshot`)是喂给**程序**的;而本判定发生在**程序被 spawn 之前**
|
|
296
|
+
* —— 程序还没跑,没有快照可读。⇒ 只能读文件。(这也是"机制属 ACC"的体现:
|
|
297
|
+
* ACC 在调用用户程序前先判定自己该不该调用。)
|
|
298
|
+
*
|
|
299
|
+
* @param root CCC 根
|
|
300
|
+
* @param dirName 轨迹目录名
|
|
301
|
+
* @param selfHost 本机机器名(**注入** ⇒ 可测;生产调用方传 `hostname()`)
|
|
302
|
+
* @returns 判定结果(**永不抛** —— 旁路容忍铁律)
|
|
303
|
+
*/
|
|
304
|
+
export declare function resolveCroHostBinding(root: string, dirName: string, selfHost: string): CroHostVerdict;
|
|
305
|
+
/**
|
|
306
|
+
* 解析绑定文件正文 ⇒ 机器名(取不到 ⇒ `null`)。
|
|
307
|
+
*
|
|
308
|
+
* 规则(刻意宽松:**只认一行,其余都当注释**):
|
|
309
|
+
* · 逐行扫;跳过空行与 `#` 开头的行;
|
|
310
|
+
* · **第一条有效行** = 机器名(裁首尾空白);
|
|
311
|
+
* · 无有效行 ⇒ `null`。
|
|
312
|
+
* @param raw 文件正文
|
|
313
|
+
* @returns 机器名;无有效内容 ⇒ `null`
|
|
314
|
+
*/
|
|
315
|
+
export declare function parseCroHostBinding(raw: string): string | null;
|
|
226
316
|
/**
|
|
227
317
|
* 列出**启用了 CRO 的轨迹目录名**(供调度器每 tick 扫描,设计 §6.1)。
|
|
228
318
|
*
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
13
13
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
14
14
|
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
16
|
-
import { homedir, platform } from "node:os";
|
|
16
|
+
import { homedir, hostname, platform } from "node:os";
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
18
|
import { platform as platform$1 } from "node:process";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
@@ -9971,10 +9971,20 @@ function listWakes(root, opts = {}) {
|
|
|
9971
9971
|
*/
|
|
9972
9972
|
/** 入口文件名(**全写**,所有者令 2026-09-19;不改缩写) */
|
|
9973
9973
|
const CRO_FILENAME = "continuous-re-occurrence.ts";
|
|
9974
|
+
/**
|
|
9975
|
+
* 机器绑定文件名(**全写**,与 `CRO_FILENAME` 同风格)。
|
|
9976
|
+
*
|
|
9977
|
+
* 🔴 **它随 CCC git 走是设计意图,不是缺陷**:
|
|
9978
|
+
* 绑定声明必须跟着**轨迹**走,才能在第二台机器上被读到、并被判为"不是我的"。
|
|
9979
|
+
* (这正是机器绑定的整个目的 —— 见 `resolveCroHostBinding` 的头注。)
|
|
9980
|
+
*/
|
|
9981
|
+
const CRO_HOST_FILENAME = "cro-host-binding";
|
|
9974
9982
|
/** 硬超时(沿用 `biasProvider` 先例的 60s;设计 §5 情形 2) */
|
|
9975
9983
|
const CRO_TIMEOUT_MS = 6e4;
|
|
9976
9984
|
/** stdout 上限(防一个坏程序把日志撑爆;沿用 hook 截断思路) */
|
|
9977
9985
|
const CRO_STDOUT_MAX = 65536;
|
|
9986
|
+
/** 绑定文件体积上限(它只是个机器名 ⇒ 超过这个数一定是写错了,不读) */
|
|
9987
|
+
const CRO_HOST_FILE_MAX = 1024;
|
|
9978
9988
|
/** SESSION.md 文件名(与 `trajectory-ops.ts` 同值;此处本地常量避免反向依赖) */
|
|
9979
9989
|
const SESSION_MD = "SESSION.md";
|
|
9980
9990
|
/**
|
|
@@ -10109,6 +10119,135 @@ function croScriptPath(root, dirName) {
|
|
|
10109
10119
|
return resolveInside(root, join("AGENT_SESSIONS", dirName, CRO_FILENAME));
|
|
10110
10120
|
}
|
|
10111
10121
|
/**
|
|
10122
|
+
* 解析机器绑定 —— 「**这条轨迹由哪台机器负责自唤醒**」(所有者令 2026-10-15)。
|
|
10123
|
+
*
|
|
10124
|
+
* ## 所有者裁定的语义(逐字,D135)
|
|
10125
|
+
*
|
|
10126
|
+
* 「**CRO 必须指定绑定机器,否则不运行,唤醒器 检查这个**」⇒ 三条同时成立:
|
|
10127
|
+
* ① **只许这一台**(白名单,不是"排除这一台");
|
|
10128
|
+
* ② **fail-closed** —— **没绑 = 不跑**(不是"没绑 = 随便跑");
|
|
10129
|
+
* ③ **检查者在唤醒器**(本函数由 `runCroPhase` 调用,不是程序自己判)。
|
|
10130
|
+
*
|
|
10131
|
+
* ## 绑定键 = 机器名(所有者 2026-10-15 裁「用 hostname 吧,比较合理」)
|
|
10132
|
+
*
|
|
10133
|
+
* ⚠️ **已接受的取舍**:本机机器名是 QEMU 机型派生的默认形
|
|
10134
|
+
* (`yh-Standard-PC-Q35-ICH9-2009`)⇒ 同法新建的同名用户 VM 会取到同一个名字。
|
|
10135
|
+
* 所有者 2026-10-15 明确「**不改名,家里就这几台机器没必要**」⇒ **风险由所有者接受**。
|
|
10136
|
+
*
|
|
10137
|
+
* ## 🔴 为什么必须是三态,「没绑」不能并进「绑给别人」
|
|
10138
|
+
*
|
|
10139
|
+
* 2026-10-15 已发生过**同族事故**:`send-now` 失败时报的是「无绑定会话记录」,
|
|
10140
|
+
* 而真因是"那条轨迹不可寻址"—— **文案相同、原因不同**,害得排查方向错了一次
|
|
10141
|
+
* (S142 §日志 续 129 / D134)。
|
|
10142
|
+
* ⇒ 绑定这一层**不许再造同样的坑**:「你没绑」与「你绑给了别人」是**两件不同的事**,
|
|
10143
|
+
* 日志必须分开写 —— 否则将来"为什么这条轨迹不跑了"会又一次指错方向。
|
|
10144
|
+
*
|
|
10145
|
+
* ## 文件形态(刻意的极简)
|
|
10146
|
+
*
|
|
10147
|
+
* · 位置 = `<轨迹目录>/cro-host-binding`(与 CRO 程序**同级**)
|
|
10148
|
+
* · 内容 = **一行机器名**(首尾空白裁掉;只取第一行有效内容)
|
|
10149
|
+
* · 不用 JSON —— 它只有一个值,JSON 只会多一种写错的方式
|
|
10150
|
+
* · 支持 `#` 注释行与空行(方便人写、方便留痕)
|
|
10151
|
+
*
|
|
10152
|
+
* 🔴 **它必须随 git 入库**(不是"运行态"):绑定声明要**跟着轨迹走到第二台机器**,
|
|
10153
|
+
* 在那里被判成「绑给了别人」而**正确地不跑** —— 那就是本机制在工作。
|
|
10154
|
+
* ⇒ ⛔ **不要把它加进 `.gitignore`**(那会让第二台机器读到"没绑"而同样不跑,
|
|
10155
|
+
* 表面上结果一样,但**判据从"明确绑给别人"退化成"什么都没说"**,失去可诊断性)。
|
|
10156
|
+
*
|
|
10157
|
+
* ## 为什么读文件而不是读快照字段
|
|
10158
|
+
*
|
|
10159
|
+
* 快照(`CroSnapshot`)是喂给**程序**的;而本判定发生在**程序被 spawn 之前**
|
|
10160
|
+
* —— 程序还没跑,没有快照可读。⇒ 只能读文件。(这也是"机制属 ACC"的体现:
|
|
10161
|
+
* ACC 在调用用户程序前先判定自己该不该调用。)
|
|
10162
|
+
*
|
|
10163
|
+
* @param root CCC 根
|
|
10164
|
+
* @param dirName 轨迹目录名
|
|
10165
|
+
* @param selfHost 本机机器名(**注入** ⇒ 可测;生产调用方传 `hostname()`)
|
|
10166
|
+
* @returns 判定结果(**永不抛** —— 旁路容忍铁律)
|
|
10167
|
+
*/
|
|
10168
|
+
function resolveCroHostBinding(root, dirName, selfHost) {
|
|
10169
|
+
const self = String(selfHost ?? "").trim();
|
|
10170
|
+
let fileAbs;
|
|
10171
|
+
try {
|
|
10172
|
+
fileAbs = resolveInside(root, join("AGENT_SESSIONS", dirName, CRO_HOST_FILENAME));
|
|
10173
|
+
} catch (err) {
|
|
10174
|
+
return {
|
|
10175
|
+
allowed: false,
|
|
10176
|
+
bound: null,
|
|
10177
|
+
self,
|
|
10178
|
+
code: "read-error",
|
|
10179
|
+
detail: `绑定文件路径非法(已忽略):${String(err?.message ?? err)}`
|
|
10180
|
+
};
|
|
10181
|
+
}
|
|
10182
|
+
let raw;
|
|
10183
|
+
try {
|
|
10184
|
+
if (!existsSync(fileAbs)) return {
|
|
10185
|
+
allowed: false,
|
|
10186
|
+
bound: null,
|
|
10187
|
+
self,
|
|
10188
|
+
code: "not-bound",
|
|
10189
|
+
detail: `未绑定机器(缺 ${CRO_HOST_FILENAME})⇒ 按"必须指定"不运行`
|
|
10190
|
+
};
|
|
10191
|
+
const st = statSync(fileAbs);
|
|
10192
|
+
if (st.size > CRO_HOST_FILE_MAX) return {
|
|
10193
|
+
allowed: false,
|
|
10194
|
+
bound: null,
|
|
10195
|
+
self,
|
|
10196
|
+
code: "read-error",
|
|
10197
|
+
detail: `绑定文件过大(${st.size} B > ${CRO_HOST_FILE_MAX} B)⇒ 判为写错,不运行`
|
|
10198
|
+
};
|
|
10199
|
+
raw = readFileSync(fileAbs, "utf-8");
|
|
10200
|
+
} catch (err) {
|
|
10201
|
+
return {
|
|
10202
|
+
allowed: false,
|
|
10203
|
+
bound: null,
|
|
10204
|
+
self,
|
|
10205
|
+
code: "read-error",
|
|
10206
|
+
detail: `绑定文件读取失败 ⇒ 不运行:${String(err?.message ?? err)}`
|
|
10207
|
+
};
|
|
10208
|
+
}
|
|
10209
|
+
const bound = parseCroHostBinding(raw);
|
|
10210
|
+
if (bound === null) return {
|
|
10211
|
+
allowed: false,
|
|
10212
|
+
bound: null,
|
|
10213
|
+
self,
|
|
10214
|
+
code: "not-bound",
|
|
10215
|
+
detail: `绑定文件为空(只有注释/空行)⇒ 按"必须指定"不运行`
|
|
10216
|
+
};
|
|
10217
|
+
if (bound === self) return {
|
|
10218
|
+
allowed: true,
|
|
10219
|
+
bound,
|
|
10220
|
+
self,
|
|
10221
|
+
code: "bound-to-self",
|
|
10222
|
+
detail: `已绑定本机(${bound})`
|
|
10223
|
+
};
|
|
10224
|
+
return {
|
|
10225
|
+
allowed: false,
|
|
10226
|
+
bound,
|
|
10227
|
+
self,
|
|
10228
|
+
code: "bound-to-other",
|
|
10229
|
+
detail: `绑定给别的机器(绑定=${bound},本机=${self})⇒ 本机不运行`
|
|
10230
|
+
};
|
|
10231
|
+
}
|
|
10232
|
+
/**
|
|
10233
|
+
* 解析绑定文件正文 ⇒ 机器名(取不到 ⇒ `null`)。
|
|
10234
|
+
*
|
|
10235
|
+
* 规则(刻意宽松:**只认一行,其余都当注释**):
|
|
10236
|
+
* · 逐行扫;跳过空行与 `#` 开头的行;
|
|
10237
|
+
* · **第一条有效行** = 机器名(裁首尾空白);
|
|
10238
|
+
* · 无有效行 ⇒ `null`。
|
|
10239
|
+
* @param raw 文件正文
|
|
10240
|
+
* @returns 机器名;无有效内容 ⇒ `null`
|
|
10241
|
+
*/
|
|
10242
|
+
function parseCroHostBinding(raw) {
|
|
10243
|
+
for (const line of String(raw ?? "").split(/\r?\n/)) {
|
|
10244
|
+
const t = line.trim();
|
|
10245
|
+
if (t === "" || t.startsWith("#")) continue;
|
|
10246
|
+
return t;
|
|
10247
|
+
}
|
|
10248
|
+
return null;
|
|
10249
|
+
}
|
|
10250
|
+
/**
|
|
10112
10251
|
* 列出**启用了 CRO 的轨迹目录名**(供调度器每 tick 扫描,设计 §6.1)。
|
|
10113
10252
|
*
|
|
10114
10253
|
* ## 判据与形态(R↓)
|
|
@@ -11441,10 +11580,18 @@ async function runCroPhase(ctx, root, registry, log) {
|
|
|
11441
11580
|
lastSkipReason: s.lastSkipReason
|
|
11442
11581
|
};
|
|
11443
11582
|
const agents = hostAgents(ctx);
|
|
11583
|
+
const selfHost = safeHostname();
|
|
11584
|
+
let hostSkips = 0;
|
|
11444
11585
|
let wakes = 0;
|
|
11445
11586
|
let quiet = 0;
|
|
11446
11587
|
let skips = 0;
|
|
11447
11588
|
for (const dirName of dirs) try {
|
|
11589
|
+
const verdict = resolveCroHostBinding(root, dirName, selfHost);
|
|
11590
|
+
if (!verdict.allowed) {
|
|
11591
|
+
hostSkips += 1;
|
|
11592
|
+
log.push(`· ${root}: ${dirName}: CRO 机器绑定未通过(${verdict.code}):${verdict.detail}`);
|
|
11593
|
+
continue;
|
|
11594
|
+
}
|
|
11448
11595
|
const bound = listBoundSessionIds(root, dirName);
|
|
11449
11596
|
const running = runningCarriersOf(bound, nowMs);
|
|
11450
11597
|
const outcome = await evaluateCro(root, dirName, readCroSnapshotInput(root, dirName, nowMs, {
|
|
@@ -11481,7 +11628,22 @@ async function runCroPhase(ctx, root, registry, log) {
|
|
|
11481
11628
|
skips += 1;
|
|
11482
11629
|
log.push(`· ${root}: ${dirName}: CRO 内部异常(已忽略,不影响既有链路): ${String(err?.message ?? err)}`);
|
|
11483
11630
|
}
|
|
11484
|
-
log.push(`· ${root}: CRO 评估 ${dirs.length} 条(唤起 ${wakes},不唤起 ${quiet},跳过 ${skips})`);
|
|
11631
|
+
log.push(`· ${root}: CRO 评估 ${dirs.length} 条(唤起 ${wakes},不唤起 ${quiet},跳过 ${skips}${hostSkips > 0 ? `,非本机绑定 ${hostSkips}` : ""})`);
|
|
11632
|
+
}
|
|
11633
|
+
/**
|
|
11634
|
+
* 取本机机器名(**永不抛**)。
|
|
11635
|
+
*
|
|
11636
|
+
* `os.hostname()` 理论上可能抛(极少数环境)⇒ 这里兜底成空串。
|
|
11637
|
+
* ⚠️ **空串的后果是"全部不通过"**(`bound === self` 永不成立)⇒ 即 **fail-closed**,
|
|
11638
|
+
* 与所有者裁定同向(宁可都不跑,也不会"名字取不到就随便跑")。
|
|
11639
|
+
* @returns 本机机器名;取不到 ⇒ `''`
|
|
11640
|
+
*/
|
|
11641
|
+
function safeHostname() {
|
|
11642
|
+
try {
|
|
11643
|
+
return hostname();
|
|
11644
|
+
} catch {
|
|
11645
|
+
return "";
|
|
11646
|
+
}
|
|
11485
11647
|
}
|
|
11486
11648
|
/** 一次调度 tick:遍历已知 CCC → 到期项串行投递 / 超窗项置 missed;返回人读摘要 */
|
|
11487
11649
|
async function runWakeTick(ctx) {
|
|
@@ -11641,6 +11803,36 @@ const CRO_GUIDE = `CRO(Continuous Re-Occurrence · 持续再发生)编写指
|
|
|
11641
11803
|
(tick 日志里会记一行跳过理由 —— 这是为了让你事后能分辨
|
|
11642
11804
|
「**总闸关着没跑**」和「**跑了、程序自己判了不叫**」这两件完全不同的事)。
|
|
11643
11805
|
|
|
11806
|
+
## 2b. 🔴 机器绑定(**必须指定,否则不运行** —— 所有者令 2026-10-15)
|
|
11807
|
+
|
|
11808
|
+
同目录再放一个文件:
|
|
11809
|
+
|
|
11810
|
+
<CCC 根>/AGENT_SESSIONS/<轨迹目录>/cro-host-binding
|
|
11811
|
+
|
|
11812
|
+
内容 = **一行机器名**(就是本机 \`hostname\`)。也支持 \`#\` 注释行,方便留痕:
|
|
11813
|
+
|
|
11814
|
+
# 这条轨迹的自唤醒只在 CCC 宿主机上跑
|
|
11815
|
+
yh-Standard-PC-Q35-ICH9-2009
|
|
11816
|
+
|
|
11817
|
+
**语义(三条,缺一不可)**:
|
|
11818
|
+
· **只许这一台** —— 文件名里写的那台;别处**不跑**;
|
|
11819
|
+
· 🔴 **fail-closed** —— **没写这个文件 ⇒ 不跑**(不是"没写就随便跑");
|
|
11820
|
+
· **检查者是唤醒器**(ACC),**不是**你的程序 —— 你的程序**根本不会被调用**。
|
|
11821
|
+
|
|
11822
|
+
**为什么要有它**:CRO 程序本身**随 git 走**(它在你轨迹目录里,会跟着 CCC 到第二台机器)。
|
|
11823
|
+
没有这道门,**同一段程序会在两台机器各跑一次**(同一轨迹被叫两次)。
|
|
11824
|
+
有了它,第二台机器读到"绑给了别人"就**正确地不跑**。
|
|
11825
|
+
|
|
11826
|
+
⚠️ **两条与它有关的纪律**:
|
|
11827
|
+
1. 🔴 **不要把这个文件加进 \`.gitignore\`** —— 它**必须**跟着仓走。
|
|
11828
|
+
否则第二台机器读到的是"没绑",结果**碰巧一样**(也不跑),
|
|
11829
|
+
但你的判据从「**明确绑给了别人**」退化成「**什么都没说**」⇒ 排查时看不出真因。
|
|
11830
|
+
2. 🔴 **tick 日志会区分两种跳过**:
|
|
11831
|
+
「\`not-bound\` 你没绑」与「\`bound-to-other\` 你绑给了别人」是**两件事**,文案不同。
|
|
11832
|
+
(2026-10-15 有过一次教训:两种不同的原因报同一句话,害排查指错方向。)
|
|
11833
|
+
|
|
11834
|
+
**改绑 / 解除**:直接改这个文件(或删掉 ⇒ 变回"没绑 ⇒ 不跑")。**没有额外命令**。
|
|
11835
|
+
|
|
11644
11836
|
## 3. 我给你什么(输入)
|
|
11645
11837
|
ACC 把这条轨迹的状态做成一个 **JSON 快照**,从 **stdin** 喂给你的程序。
|
|
11646
11838
|
字段分四组:identity(身份)/ time(时间)/ body(轨迹身体)/ binding + scheduling(绑定与调度)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.0",
|
|
4
4
|
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:14 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/ontology/serenity-skill-creator/cheap-worker/localstore/container_admin;另三个 im-bridge/acc-diag/diagram 按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.7-rc.1 与 0.2.0-rc.1(双档)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|