@shgroup/dsh-serenity-hooks 1.43.0 → 1.44.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 +1 -1
- package/dsh.plugin.json +1 -1
- package/lib/{ccc-NlLr_sxy.js → ccc-DG4Oc7I1.js} +22 -1
- package/lib/{ccc-roots-Bd_SjEs7.js → ccc-roots-FxWXuZPw.js} +2 -2
- package/lib/ccc.d.ts +36 -0
- package/lib/cro-log.d.ts +135 -0
- package/lib/index.js +4169 -4127
- package/lib/{skiff-registry-FTjoWQTQ.js → skiff-registry-BjIX_KD1.js} +19 -1
- package/lib/{skiff-role-CEHL5cek.js → skiff-role-KqQA6pkr.js} +1 -1
- package/lib/{trajectory-bound-kxvMuCui.js → trajectory-bound-BzdzsDPI.js} +1 -1
- package/lib/trajectory-skills.d.ts +22 -5
- package/lib/{wake-scheduler-DC4-imVO.js → wake-scheduler-C2GwdriD.js} +219 -5
- package/lib/{weixin-route-B2ajFypI.js → weixin-route-CWMRa36W.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -278,7 +278,7 @@ DSH 一个进程可以同时带多个工作区,每个工作区各自对接自
|
|
|
278
278
|
|
|
279
279
|
- **目标没打开也能唤醒**:会话不在内存里时先把它载入再投递(**冷唤醒**);载入不了则条目留在登记表里并记下原因,**不静默丢弃**
|
|
280
280
|
- **"周期"归工作区自己**:要一轮接一轮,就由**收到唤醒的那条轨迹**在每轮结束时再排一次下一轮——ACC 只提供"到点投递一条消息"这个原语,不替工作区决定节拍(焦点、节拍、偏见都由工作区自定;任意条轨迹并行、互不干扰)
|
|
281
|
-
-
|
|
281
|
+
- **轨迹可以带上 skill**(两种写法,**取并集**):① **一处声明、全容器生效**——在 `.opencode/serenity.json` 写 `trajectory.skills: [名字, …]`,**本 CCC 的每条轨迹**被绑定期间都注入这些 skill 的全文(**连 skiff 角色会话也算**);② **只给这一条**——在它的 `SESSION.md` 顶部 frontmatter 写 `skills: [名字, …]`。两者是并集:容器级在前(底座)、轨迹级追加(这条额外的)。⚠️ 是"**绑定期间一直供着**"而非"`use` 时灌一次"——改了配置或 frontmatter**立即生效**,也不随对话压缩消失;而 `create` 只新建、**不夺走当前绑定** ⇒ 新轨迹要**显式 `use`** 才挂上。skill 名来自工作区数据,**先过安全校验才会用于拼路径**,找不到会在提示里明说"缺失"而**不静默跳过**;⚠️ **重写 `SESSION.md` 时务必原样保留顶部 frontmatter**(抹掉 = 静默撤销该轨迹的声明)
|
|
282
282
|
|
|
283
283
|
#### CRO —— 让轨迹**自己判断什么时候该被叫醒**
|
|
284
284
|
|
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——11 个工具(container_fs/container_trajectory/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/im-bridge/acc-diag;后两个按配置条件出现)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 轨迹日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2(deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
|
@@ -155,6 +155,27 @@ function readExclusiveTools(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
|
155
155
|
if (!Array.isArray(list)) return [];
|
|
156
156
|
return list.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim());
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* 读取 CCC 声明的「轨迹默认 skill」清单(`trajectory.skills`,v1.44.0)。
|
|
160
|
+
*
|
|
161
|
+
* 语义与失败方向(R↓):
|
|
162
|
+
* · 有声明 ⇒ 该 CCC **每条被绑定的轨迹**都注入这些 skill(与轨迹自己的声明**并集**);
|
|
163
|
+
* · 未配置 / 字段缺失 / 配置损坏 ⇒ **空数组** = 无 CCC 级供给(**轨迹级声明不受影响**)。
|
|
164
|
+
* 这里不必像 `exclusiveTools` 那样强调 fail-closed:本键的默认态本就是"无供给"
|
|
165
|
+
* (空数组即该默认态),且配置损坏已有 `loadSerenityConfig` 的响亮告警兜住。
|
|
166
|
+
*
|
|
167
|
+
* 🟢 **名字**在此**不做**安全过滤——那是装配层的职责(`trajectory-skills.ts` 走 `isSafeSkillName`
|
|
168
|
+
* 后才拼路径)。本函数只做"取字符串、去空白、丢非字符串",**保持书写顺序**(注入顺序 = 书写顺序)。
|
|
169
|
+
*
|
|
170
|
+
* @param root CCC 根
|
|
171
|
+
* @param paths 候选配置相对路径
|
|
172
|
+
* @returns 去空白后的 skill 名列表(非字符串/空白项被丢弃)
|
|
173
|
+
*/
|
|
174
|
+
function readTrajectorySkills(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
175
|
+
const list = loadSerenityConfig(root, paths).trajectory?.skills;
|
|
176
|
+
if (!Array.isArray(list)) return [];
|
|
177
|
+
return list.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim());
|
|
178
|
+
}
|
|
158
179
|
const SAFE_MODE_MARKER = ".serenity-safe-on";
|
|
159
180
|
function isSafeModeOn(root) {
|
|
160
181
|
return existsSync(resolve(root, SAFE_MODE_MARKER));
|
|
@@ -193,4 +214,4 @@ function matchBlacklist(relPath, rules) {
|
|
|
193
214
|
return null;
|
|
194
215
|
}
|
|
195
216
|
//#endregion
|
|
196
|
-
export { findSerenityRoot as a, matchBlacklist as c, readCccName as d, readExclusiveTools as f, resolveInside as h, findGitRoot as i, pathInside as l,
|
|
217
|
+
export { findSerenityRoot as a, matchBlacklist as c, readCccName as d, readExclusiveTools as f, resolveInside as g, readUtf8 as h, findGitRoot as i, pathInside as l, readTrajectorySkills as m, SAFE_MODE_MARKER as n, isSafeModeOn as o, readHandymanConfig as p, classifyPath as r, loadSerenityConfig as s, DEFAULT_SERENITY_CONFIG_PATHS as t, readBlacklist as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { i as hostSessions, r as hostService } from "./access-fiehjxV6.js";
|
|
3
|
-
import { a as findSerenityRoot } from "./ccc-
|
|
3
|
+
import { a as findSerenityRoot } from "./ccc-DG4Oc7I1.js";
|
|
4
4
|
import { basename } from "node:path";
|
|
5
5
|
//#region src/ccc-roots.ts
|
|
6
6
|
/**
|
|
@@ -139,7 +139,7 @@ async function listCccs(ctx, opts = {}) {
|
|
|
139
139
|
if (typeof defaultRoot === "string" && defaultRoot !== "" && !c.roots.includes(defaultRoot)) c.roots.unshift(defaultRoot);
|
|
140
140
|
let rolesOf = null;
|
|
141
141
|
if (opts.withRoles === true) {
|
|
142
|
-
const { readSkiffRoles } = await import("./skiff-role-
|
|
142
|
+
const { readSkiffRoles } = await import("./skiff-role-KqQA6pkr.js").then((n) => n.u);
|
|
143
143
|
rolesOf = (root) => [...readSkiffRoles(root).keys()];
|
|
144
144
|
}
|
|
145
145
|
return c.roots.map((root) => ({
|
package/lib/ccc.d.ts
CHANGED
|
@@ -93,6 +93,25 @@ interface SerenityConfig {
|
|
|
93
93
|
trajectory?: {
|
|
94
94
|
/** autopilot(周期自唤醒的特例;每 CCC 单例,D59) */
|
|
95
95
|
autopilot?: AutopilotTrajectorySettings;
|
|
96
|
+
/**
|
|
97
|
+
* **CCC 级 skill 供给**(v1.44.0;owner 令 2026-09-20:「要求实现我的诉求……skiff 也支持」)。
|
|
98
|
+
*
|
|
99
|
+
* 语义:**本 CCC 的每条轨迹都带这些 skill**——声明的 skill 全文在该轨迹**被绑定期间**
|
|
100
|
+
* 动态注入系统提示词(每请求重新求值,不缓存)。与轨迹自己在 `SESSION.md` frontmatter
|
|
101
|
+
* 里的 `skills:` 是**并集**(CCC 级在前、轨迹级追加、同名去重)⇒ 容器给底座、
|
|
102
|
+
* 单条轨迹仍可加自己的额外项(这正是 C1 设计当初否决"纯 CCC 全局"的理由所在:
|
|
103
|
+
* 全局化会**丢掉 per-trajectory**;并集把它保住)。
|
|
104
|
+
*
|
|
105
|
+
* 归属(D23):**机制在 ACC**(读取 + 合并 + 注入 + 守卫),**声明在 CCC**(本键)——
|
|
106
|
+
* ACC 代码里不出现任何具体 CCC 或具体 skill 名。
|
|
107
|
+
*
|
|
108
|
+
* 失败语义:未配置 / 字段缺失 / 配置损坏 ⇒ **空数组**(= 没有 CCC 级供给;轨迹级声明照旧生效)。
|
|
109
|
+
* 配置损坏本身已由 `loadSerenityConfig` 响亮告警(不静默)。
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ 与 `create` 的关系:**不触发**——`create` 刻意不夺绑定(U6),注入的门是"存在绑定"。
|
|
112
|
+
* ⚠️ 作用面:**含 skiff 角色会话**(它们同样绑定工作台轨迹)。
|
|
113
|
+
*/
|
|
114
|
+
skills?: string[];
|
|
96
115
|
};
|
|
97
116
|
/**
|
|
98
117
|
* 微信桥(F4c-3,v1.27.0 实验性):**CCC 级**配置——dsh 一个进程含多个 CCC,
|
|
@@ -292,6 +311,23 @@ export declare function loadSerenityConfig(root: string, paths?: string[]): Sere
|
|
|
292
311
|
* @returns 去空白后的工具名列表(非字符串项被丢弃)
|
|
293
312
|
*/
|
|
294
313
|
export declare function readExclusiveTools(root: string, paths?: string[]): string[];
|
|
314
|
+
/**
|
|
315
|
+
* 读取 CCC 声明的「轨迹默认 skill」清单(`trajectory.skills`,v1.44.0)。
|
|
316
|
+
*
|
|
317
|
+
* 语义与失败方向(R↓):
|
|
318
|
+
* · 有声明 ⇒ 该 CCC **每条被绑定的轨迹**都注入这些 skill(与轨迹自己的声明**并集**);
|
|
319
|
+
* · 未配置 / 字段缺失 / 配置损坏 ⇒ **空数组** = 无 CCC 级供给(**轨迹级声明不受影响**)。
|
|
320
|
+
* 这里不必像 `exclusiveTools` 那样强调 fail-closed:本键的默认态本就是"无供给"
|
|
321
|
+
* (空数组即该默认态),且配置损坏已有 `loadSerenityConfig` 的响亮告警兜住。
|
|
322
|
+
*
|
|
323
|
+
* 🟢 **名字**在此**不做**安全过滤——那是装配层的职责(`trajectory-skills.ts` 走 `isSafeSkillName`
|
|
324
|
+
* 后才拼路径)。本函数只做"取字符串、去空白、丢非字符串",**保持书写顺序**(注入顺序 = 书写顺序)。
|
|
325
|
+
*
|
|
326
|
+
* @param root CCC 根
|
|
327
|
+
* @param paths 候选配置相对路径
|
|
328
|
+
* @returns 去空白后的 skill 名列表(非字符串/空白项被丢弃)
|
|
329
|
+
*/
|
|
330
|
+
export declare function readTrajectorySkills(root: string, paths?: string[]): string[];
|
|
295
331
|
export declare const SAFE_MODE_MARKER = ".serenity-safe-on";
|
|
296
332
|
export declare function isSafeModeOn(root: string): boolean;
|
|
297
333
|
/** 黑名单条目(对齐 osp BlacklistEntry):pattern + 可选自定义拦截提示 message */
|
package/lib/cro-log.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cro-log.ts — CRO 唤起日志(**ACC 侧**,每轨迹一份固定名文件,2026-09-20 S142 §7.16)
|
|
3
|
+
*
|
|
4
|
+
* ## 所有者令(本模块的存在理由)
|
|
5
|
+
*
|
|
6
|
+
* 「S151 和 S185 的验证都说明,trajectory 倾向于自己做个结构化记录来记录自身 CRO 的执行情况,
|
|
7
|
+
* 这是个结构化流水账;**我们 ACC 有必要进行 CRO 唤醒的日志记录**,当 CRO 存在时,每次**成功唤醒**
|
|
8
|
+
* 写相关信息,但是保留数量要少,**只保留仅两日**的记录即可,**这个时间是程序化的**」
|
|
9
|
+
* +「**这个日志写结构化文件到 trajectory 目录,文件名固定即可**」
|
|
10
|
+
* +「**失败成功都记录**」。
|
|
11
|
+
*
|
|
12
|
+
* ## 它解决什么麻烦(白话)
|
|
13
|
+
*
|
|
14
|
+
* 在此之前「ACC 到底叫过谁、叫了几次、哪次**没叫成**」**ACC 自己一个字都不留** ——
|
|
15
|
+
* 要查只能去读**被叫的那个程序**写的记录;程序没写 / 写坏 / 被换掉 ⇒ **这段历史就是空白**。
|
|
16
|
+
* ⇒ 本模块让 ACC **自己那一侧**留一份只记两天的流水,使「唤起历史」**不再依赖被观测者是否老实**。
|
|
17
|
+
*
|
|
18
|
+
* ## 🔴 为什么**不是**第二真相源(本容器铁律,勿删此论证)
|
|
19
|
+
*
|
|
20
|
+
* 轨迹目录里因此**并排两个文件、主语各一**:
|
|
21
|
+
*
|
|
22
|
+
* | 文件 | 谁写 | 主语 | 回答 |
|
|
23
|
+
* |---|---|---|---|
|
|
24
|
+
* | `<轨迹目录>/cro-state.json`(CCC 侧程序自建,名字自定) | **CCC 的 CRO 程序** | 程序 | 「**我判了什么**」(含"没叫"的判定、心跳阈值、环境谓词) |
|
|
25
|
+
* | `<轨迹目录>/cro-wake-log.json`(**本模块**) | **ACC** | ACC | 「**我把什么送出去了、送成没有**」 |
|
|
26
|
+
*
|
|
27
|
+
* **内容不重叠**:本模块**不复制**判定细节(不存 `lastDecision` / 阈值 / 谓词);
|
|
28
|
+
* 而程序**不可能**知道投递结果(CRO 投递是 fire-and-forget)。
|
|
29
|
+
* ⇒ 两者**互补**:完整审计 = 两者合看(本模块 = 投递面|程序状态 = 判定面)。
|
|
30
|
+
*
|
|
31
|
+
* ## 🔴 轮转(所有者明示「程序化的」)
|
|
32
|
+
*
|
|
33
|
+
* **写入时按窗裁剪**:每次落盘只保留 `at >= now - CRO_LOG_RETENTION_MS` 的条目,
|
|
34
|
+
* 随后**原子写**(`tmp + rename`,与 `wake-registry.ts` 同款形态)。
|
|
35
|
+
* ⇒ 清理与写入**是同一笔操作** ⇒ **结构上不存在"忘了清"的可能**(比"另设一个每 tick 的清理步"更硬:
|
|
36
|
+
* 那多一个"可能忘了跑"的面)。窗宽是**常量**,**无配置键、无人工清理动作**。
|
|
37
|
+
*
|
|
38
|
+
* ## 🔴 铁律(与 `cro.ts` 同族)
|
|
39
|
+
*
|
|
40
|
+
* **本模块的每个导出函数都不抛错**:写盘 / 裁剪 / 解析失败一律返回结构化失败,
|
|
41
|
+
* 由调用方只记一行 tick 日志 ⇒ **CRO 流水的问题绝不影响投递与既有链路**(设计 §5)。
|
|
42
|
+
*
|
|
43
|
+
* ## 保留量级(实测,非估计)
|
|
44
|
+
*
|
|
45
|
+
* 实测最高 = S185 的 40min 心跳(≈36 条/天);两天窗 ⇒ **≤ ~80 条、< 20 KB**。
|
|
46
|
+
* 最坏(某程序每 5min 都叫)⇒ 288 条/天 ⇒ 两天 **≤ ~600 条、< 100 KB** —— 轮转**自带上限**。
|
|
47
|
+
*/
|
|
48
|
+
/** 固定文件名(所有者令:「文件名固定即可」)——与程序自建的 `cro-state.json` 并排、主语分明 */
|
|
49
|
+
export declare const CRO_LOG_FILENAME = "cro-wake-log.json";
|
|
50
|
+
/** 保留窗 = 2 日(所有者令「只保留仅两日」);**常量** ⇒ 「这个时间是程序化的」 */
|
|
51
|
+
export declare const CRO_LOG_RETENTION_MS: number;
|
|
52
|
+
/** 提示词摘要取前多少字(**只存摘要,不搬第二份全文**——全文属程序那侧的产物) */
|
|
53
|
+
export declare const CRO_PROMPT_HEAD_MAX = 80;
|
|
54
|
+
/** 一条唤起**尝试**(成功与失败**都记**,所有者令 2026-09-20) */
|
|
55
|
+
export interface CroWakeLogEntry {
|
|
56
|
+
/** 时刻(当地 RFC3339 带偏移,遵 D67) */
|
|
57
|
+
at: string;
|
|
58
|
+
/** 投递是否成功 */
|
|
59
|
+
ok: boolean;
|
|
60
|
+
/** 投递方式(`已投递(live)` / `已投递(冷载入)`)或失败原因 */
|
|
61
|
+
detail: string;
|
|
62
|
+
/** 调度器 tick 计数(便于与 `acc-diag` ①b 对齐) */
|
|
63
|
+
tick: number;
|
|
64
|
+
/** 程序自报理由;**取不到写 `null`,不留空白**(同 §4.5.4 纪律:留空白等于把缺口也丢了) */
|
|
65
|
+
reason: string | null;
|
|
66
|
+
/** 唤起提示词**摘要**(长度 + 前 N 字) */
|
|
67
|
+
promptDigest: {
|
|
68
|
+
length: number;
|
|
69
|
+
head: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** 文件形态(与 `wake-registry.json` 同款:`{version, entries}`) */
|
|
73
|
+
export interface CroWakeLog {
|
|
74
|
+
version: number;
|
|
75
|
+
entries: CroWakeLogEntry[];
|
|
76
|
+
}
|
|
77
|
+
/** 追加所需的输入(`at` 由本模块按当地时区生成,不劳调用方) */
|
|
78
|
+
export interface CroWakeLogInput {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
detail: string;
|
|
81
|
+
tick: number;
|
|
82
|
+
reason: string | null;
|
|
83
|
+
/** 唤起提示词原文(本模块只留摘要) */
|
|
84
|
+
prompt?: string;
|
|
85
|
+
}
|
|
86
|
+
/** 日志绝对路径 */
|
|
87
|
+
export declare function croWakeLogPath(root: string, dirName: string): string;
|
|
88
|
+
/** 提示词摘要(长度 + 前 N 字;超长不截断标记——长度字段本身就是信号) */
|
|
89
|
+
export declare function summarizeCroPrompt(prompt: string | undefined): {
|
|
90
|
+
length: number;
|
|
91
|
+
head: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* 读取日志(**永不抛**;读坏 ⇒ 当作空档 + 返回 error 文本)。
|
|
95
|
+
* @param root CCC 根
|
|
96
|
+
* @param dirName 轨迹目录名
|
|
97
|
+
*/
|
|
98
|
+
export declare function loadCroWakeLog(root: string, dirName: string): {
|
|
99
|
+
log: CroWakeLog;
|
|
100
|
+
error: string | null;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* 按窗裁剪(**纯函数** ⇒ 可穷举测试)。
|
|
104
|
+
*
|
|
105
|
+
* 判据:`Date.parse(at) >= nowMs - windowMs` 保留;反之丢弃。
|
|
106
|
+
* 🔴 **`at` 不可解析者一律丢弃**(本模块只写 `isoLocal()` 产物 ⇒ 不可解析 = 被手改或损坏),
|
|
107
|
+
* **并在返回值里报数**(`dropped`)——**不静默吞掉**(本容器反复栽的"安静失败"纪律)。
|
|
108
|
+
*/
|
|
109
|
+
export declare function pruneCroLogEntries(entries: CroWakeLogEntry[], nowMs: number, windowMs?: number): {
|
|
110
|
+
kept: CroWakeLogEntry[];
|
|
111
|
+
pruned: number;
|
|
112
|
+
dropped: number;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* 追加一条唤起记录并按窗裁剪(**本模块主入口;永不抛**)。
|
|
116
|
+
*
|
|
117
|
+
* 顺序 = 读 → 追加 → 裁剪 → **原子写**。任一步失败 ⇒ `{ok:false, error}`,
|
|
118
|
+
* **不抛**(调用方只记一行 tick 日志;**绝不影响投递**)。
|
|
119
|
+
*
|
|
120
|
+
* 无 CRO 的轨迹**不会**因本函数产生文件:只有真的发生过一次唤起尝试才会建/写该档
|
|
121
|
+
* (所有者令「**当 CRO 存在时**」)。
|
|
122
|
+
*
|
|
123
|
+
* @param root CCC 根
|
|
124
|
+
* @param dirName 轨迹目录名
|
|
125
|
+
* @param input 本次唤起尝试(成败都记)
|
|
126
|
+
* @param nowMs 当前毫秒(可注入 ⇒ 便于测裁剪边界)
|
|
127
|
+
* @returns 写入结果与裁剪计数
|
|
128
|
+
*/
|
|
129
|
+
export declare function appendCroWakeLog(root: string, dirName: string, input: CroWakeLogInput, nowMs?: number): {
|
|
130
|
+
ok: boolean;
|
|
131
|
+
error: string | null;
|
|
132
|
+
kept: number;
|
|
133
|
+
pruned: number;
|
|
134
|
+
dropped: number;
|
|
135
|
+
};
|