@shgroup/dsh-serenity-hooks 1.32.0 → 1.34.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.
Files changed (36) hide show
  1. package/README.md +11 -11
  2. package/dsh.plugin.json +5 -5
  3. package/experiments/autopilot-trajectory/SKILL.md +12 -12
  4. package/experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts +69 -45
  5. package/lib/autopilot-script.d.ts +40 -0
  6. package/lib/autopilot-trajectory.d.ts +29 -0
  7. package/lib/{ccc-DAsSHsub.js → ccc-eu_IPF94.js} +17 -1
  8. package/lib/ccc.d.ts +20 -0
  9. package/lib/client.js +28 -8
  10. package/lib/diag-ops.d.ts +61 -0
  11. package/lib/index.d.ts +1 -1
  12. package/lib/index.js +3192 -3182
  13. package/lib/invariant.d.ts +5 -1
  14. package/lib/invariant.js +7 -3
  15. package/lib/json.d.ts +1 -1
  16. package/lib/msm-ops.d.ts +2 -2
  17. package/lib/output-guard-seam.d.ts +1 -1
  18. package/lib/rebuild.d.ts +4 -3
  19. package/lib/seams/guards.d.ts +27 -0
  20. package/lib/seams/keeper.d.ts +3 -3
  21. package/lib/{settings-section-Bm-xG-NR.js → settings-section-BuPGQv_H.js} +5 -6
  22. package/lib/settings-section.d.ts +15 -8
  23. package/lib/skiff-core.d.ts +1 -1
  24. package/lib/{skiff-debug-K2k1JnGj.js → skiff-debug-D83-Njy7.js} +7 -7
  25. package/lib/{skiff-role-BYvNnwv1.js → skiff-role-DAfPb6YQ.js} +1 -1
  26. package/lib/tools/acc-diag.d.ts +22 -0
  27. package/lib/tools/container-admin.d.ts +1 -1
  28. package/lib/tools/{session.d.ts → trajectory.d.ts} +32 -16
  29. package/lib/{session-bound-NHozeLFc.js → trajectory-bound-C8DUxi6_.js} +21 -276
  30. package/lib/{session-bound.d.ts → trajectory-bound.d.ts} +2 -2
  31. package/lib/{session-ops.d.ts → trajectory-ops.d.ts} +24 -28
  32. package/lib/wake-registry-D0Vj0WXb.js +270 -0
  33. package/lib/wake-scheduler.d.ts +41 -2
  34. package/lib/{weixin-route-wWk4AIwV.js → weixin-route-D1ND823E.js} +1 -1
  35. package/package.json +2 -2
  36. package/lib/tools/autopilot-trajectory.d.ts +0 -25
@@ -0,0 +1,270 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ //#region src/wake-registry.ts
5
+ /**
6
+ * wake-registry.ts — trajectory 唤醒注册表(D58,S142 2026-09-13)
7
+ *
8
+ * 需求(用户原话):一个 CCC 内允许多个 trajectory 任意形态并行;某个 trajectory 可以
9
+ * **预定未来唤醒自己或别人**,本质上是一次「未来时刻 + 一条 message」的投递;autopilot
10
+ * 是「周期自唤醒」的特例(独立循环,D59)。协作边界:互相可见、**无阻塞无等待**。
11
+ *
12
+ * 本模块只负责**注册表本身**(纯数据层,无宿主依赖,可单测):
13
+ * - 落点:CCC 内 `AGENT_SESSIONS/wake-registry.json`(随 CCC git,人可读可审计)
14
+ * - 状态机:`pending` →(投递成功)`delivered`|(超补跑窗口)`missed`|(`wake rm` 物理移除)
15
+ * - 投递即终结:一次性,不重投(无回执语义——发起方不 await,见设计文档 §2)
16
+ *
17
+ * 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
18
+ */
19
+ var wake_registry_exports = /* @__PURE__ */ __exportAll({
20
+ WAKE_CATCH_UP_MS: () => WAKE_CATCH_UP_MS,
21
+ WAKE_REGISTRY_REL_PATH: () => WAKE_REGISTRY_REL_PATH,
22
+ WAKE_REGISTRY_VERSION: () => 1,
23
+ addWake: () => addWake,
24
+ listWakes: () => listWakes,
25
+ loadWakeRegistry: () => loadWakeRegistry,
26
+ newWakeId: () => newWakeId,
27
+ parseWakeAt: () => parseWakeAt,
28
+ saveWakeRegistry: () => saveWakeRegistry,
29
+ splitDueWakes: () => splitDueWakes,
30
+ updateWake: () => updateWake,
31
+ wakeRegistryPath: () => wakeRegistryPath
32
+ });
33
+ /** 注册表相对 CCC 根的路径(与 `.bindings.json` 同居 AGENT_SESSIONS/) */
34
+ const WAKE_REGISTRY_REL_PATH = "AGENT_SESSIONS/wake-registry.json";
35
+ /**
36
+ * 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
37
+ * 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
38
+ */
39
+ const WAKE_CATCH_UP_MS = 72e5;
40
+ /** 注册表绝对路径 */
41
+ function wakeRegistryPath(root) {
42
+ return join(root, WAKE_REGISTRY_REL_PATH);
43
+ }
44
+ /** 空注册表 */
45
+ function emptyRegistry() {
46
+ return {
47
+ version: 1,
48
+ entries: []
49
+ };
50
+ }
51
+ /** 条目形状校验(容忍手改:字段缺失/类型不符 → 丢弃该条,不阻断整表) */
52
+ function asEntry(value) {
53
+ const e = value;
54
+ if (!e || typeof e.id !== "string" || typeof e.target !== "string") return null;
55
+ if (typeof e.at !== "string" || typeof e.message !== "string") return null;
56
+ const state = e.state === "delivered" || e.state === "missed" || e.state === "cancelled" ? e.state : "pending";
57
+ return {
58
+ id: e.id,
59
+ target: e.target,
60
+ at: e.at,
61
+ message: e.message,
62
+ state,
63
+ createdBy: typeof e.createdBy === "string" ? e.createdBy : "",
64
+ createdAt: typeof e.createdAt === "string" ? e.createdAt : "",
65
+ attempts: typeof e.attempts === "number" && Number.isFinite(e.attempts) ? e.attempts : 0,
66
+ lastResult: typeof e.lastResult === "string" ? e.lastResult : null,
67
+ deliveredAt: typeof e.deliveredAt === "string" ? e.deliveredAt : null
68
+ };
69
+ }
70
+ /**
71
+ * 读注册表。文件缺失 → 空表;损坏 → 空表 + error(不抛错,调用方决定是否告警)。
72
+ * @param root CCC 根
73
+ * @returns 注册表与读取错误(无错为 null)
74
+ */
75
+ function loadWakeRegistry(root) {
76
+ const path = wakeRegistryPath(root);
77
+ if (!existsSync(path)) return {
78
+ registry: emptyRegistry(),
79
+ error: null
80
+ };
81
+ try {
82
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
83
+ const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
84
+ const entries = [];
85
+ for (const item of raw) {
86
+ const e = asEntry(item);
87
+ if (e) entries.push(e);
88
+ }
89
+ return {
90
+ registry: {
91
+ version: 1,
92
+ entries
93
+ },
94
+ error: null
95
+ };
96
+ } catch (err) {
97
+ return {
98
+ registry: emptyRegistry(),
99
+ error: `唤醒注册表解析失败(${String(err?.message ?? err)})`
100
+ };
101
+ }
102
+ }
103
+ /** 原子写(tmp + rename;与 .bindings.json 同款形态) */
104
+ function saveWakeRegistry(root, registry) {
105
+ const path = wakeRegistryPath(root);
106
+ try {
107
+ mkdirSync(dirname(path), { recursive: true });
108
+ const tmp = `${path}.tmp`;
109
+ writeFileSync(tmp, `${JSON.stringify({
110
+ version: 1,
111
+ entries: registry.entries
112
+ }, null, 2)}\n`, "utf-8");
113
+ renameSync(tmp, path);
114
+ return {
115
+ ok: true,
116
+ error: null
117
+ };
118
+ } catch (err) {
119
+ return {
120
+ ok: false,
121
+ error: `唤醒注册表写入失败(${String(err?.message ?? err)})`
122
+ };
123
+ }
124
+ }
125
+ /** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
126
+ function newWakeId(nowMs, rand = Math.random) {
127
+ const iso = new Date(nowMs).toISOString();
128
+ return `w-${`${iso.slice(0, 10).replace(/-/g, "")}-${iso.slice(11, 16).replace(":", "")}`}-${Math.floor(rand() * 65536).toString(16).padStart(4, "0")}`;
129
+ }
130
+ /**
131
+ * 解析唤醒时刻。接受两种写法(设计文档 §7):
132
+ * - 绝对:RFC3339(含时区偏移,如 `2026-09-14T09:00:00+08:00`)
133
+ * - 相对:`+30m` / `+2h`(便于 agent 直接登记"半小时后")
134
+ * @param input 用户/agent 传入的时刻串
135
+ * @param nowMs 当前时刻(相对写法基准)
136
+ * @returns `{ ok, ms }` 或 `{ ok: false, error }`
137
+ */
138
+ function parseWakeAt(input, nowMs) {
139
+ const raw = input.trim();
140
+ if (raw === "") return {
141
+ ok: false,
142
+ error: "时刻为空"
143
+ };
144
+ const rel = /^\+(\d+(?:\.\d+)?)([mhd])$/.exec(raw);
145
+ if (rel) {
146
+ const n = Number(rel[1]);
147
+ const unit = rel[2];
148
+ return {
149
+ ok: true,
150
+ ms: nowMs + n * (unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5)
151
+ };
152
+ }
153
+ const ms = Date.parse(raw);
154
+ if (!Number.isFinite(ms)) return {
155
+ ok: false,
156
+ error: `时刻无法解析(须 RFC3339 或 +30m/+2h): ${raw}`
157
+ };
158
+ return {
159
+ ok: true,
160
+ ms
161
+ };
162
+ }
163
+ /**
164
+ * 登记一条唤醒。
165
+ * @param root CCC 根
166
+ * @param input 目标 trajectory / 时刻串 / message / 发起者 / 当前时刻
167
+ * @returns 成功返回条目,失败返回稳定错误文案(不抛错)
168
+ */
169
+ function addWake(root, input) {
170
+ const target = input.target.trim();
171
+ if (target === "") return {
172
+ ok: false,
173
+ error: "target 为空(须 S### 或 AGENT_SESSIONS 目录名)"
174
+ };
175
+ const message = input.message.trim();
176
+ if (message === "") return {
177
+ ok: false,
178
+ error: "message 为空(唤醒必须携带一条信息)"
179
+ };
180
+ const parsed = parseWakeAt(input.at, input.nowMs);
181
+ if (!parsed.ok) return {
182
+ ok: false,
183
+ error: parsed.error
184
+ };
185
+ if (parsed.ms <= input.nowMs) return {
186
+ ok: false,
187
+ error: "时刻必须在未来(唤醒只允许可预期的未来)"
188
+ };
189
+ const { registry, error } = loadWakeRegistry(root);
190
+ if (error) return {
191
+ ok: false,
192
+ error
193
+ };
194
+ const entry = {
195
+ id: newWakeId(input.nowMs),
196
+ target,
197
+ at: new Date(parsed.ms).toISOString(),
198
+ message,
199
+ state: "pending",
200
+ createdBy: input.createdBy,
201
+ createdAt: new Date(input.nowMs).toISOString(),
202
+ attempts: 0,
203
+ lastResult: null,
204
+ deliveredAt: null
205
+ };
206
+ registry.entries.push(entry);
207
+ const saved = saveWakeRegistry(root, registry);
208
+ if (!saved.ok) return {
209
+ ok: false,
210
+ error: saved.error ?? "写入失败"
211
+ };
212
+ return {
213
+ ok: true,
214
+ entry
215
+ };
216
+ }
217
+ /**
218
+ * 到期切分(纯函数,可单测):
219
+ * - `due`:`state === 'pending'` 且 `now >= at`(在补跑窗口内)→ 应投递
220
+ * - `expired`:`state === 'pending'` 且 `now - at > catchUpMs` → 应置 missed(不投递)
221
+ * @param registry 注册表
222
+ * @param nowMs 当前时刻
223
+ * @param catchUpMs 补跑窗口
224
+ */
225
+ function splitDueWakes(registry, nowMs, catchUpMs = WAKE_CATCH_UP_MS) {
226
+ const due = [];
227
+ const expired = [];
228
+ for (const e of registry.entries) {
229
+ if (e.state !== "pending") continue;
230
+ const at = Date.parse(e.at);
231
+ if (!Number.isFinite(at) || nowMs < at) continue;
232
+ if (nowMs - at > catchUpMs) expired.push(e);
233
+ else due.push(e);
234
+ }
235
+ return {
236
+ due,
237
+ expired
238
+ };
239
+ }
240
+ /**
241
+ * 更新一条(状态推进;调用方给最终状态与结果文案)。
242
+ * @param root CCC 根
243
+ * @param id 条目 id
244
+ * @param patch 要写入的字段
245
+ * @returns 写入结果
246
+ */
247
+ function updateWake(root, id, patch) {
248
+ const { registry, error } = loadWakeRegistry(root);
249
+ if (error) return {
250
+ ok: false,
251
+ error
252
+ };
253
+ const entry = registry.entries.find((e) => e.id === id);
254
+ if (!entry) return {
255
+ ok: false,
256
+ error: `wake_not_found: ${id}`
257
+ };
258
+ Object.assign(entry, patch);
259
+ return saveWakeRegistry(root, registry);
260
+ }
261
+ /** 列条目(默认全量;`pendingOnly` 只列在办项——面板/工具用) */
262
+ function listWakes(root, opts = {}) {
263
+ const { registry, error } = loadWakeRegistry(root);
264
+ return {
265
+ entries: [...opts.pendingOnly ? registry.entries.filter((e) => e.state === "pending") : registry.entries].sort((a, b) => Date.parse(a.at) - Date.parse(b.at)),
266
+ error
267
+ };
268
+ }
269
+ //#endregion
270
+ export { splitDueWakes as a, loadWakeRegistry as i, addWake as n, updateWake as o, listWakes as r, wake_registry_exports as s, WAKE_CATCH_UP_MS as t };
@@ -17,7 +17,8 @@
17
17
  * → `sessionController` 缺席(headless profile)⇒ 仅投递 live 目标,**不静默**
18
18
  * 4. 投递:`agent.followup(...)`("Queue an ordinary follow-up turn and wake the driver")
19
19
  *
20
- * 守卫(I-4 默认):全局 `trajectoryEnabled`(旧键 `autopilotEnabled` 回退)|
20
+ * 守卫(I-4 默认 + v1.34 S-1 解耦):全局 `wakeSchedulerEnabled`(**缺省开**;**不再**回退旧键
21
+ * `autopilotEnabled`——关周期自唤醒不得连带关一次性唤醒)|
21
22
  * 补跑窗口 2h(超窗置 missed 留痕)|同 tick 串行|唤起后维持 live(人类可介入)。
22
23
  */
23
24
  import type { Context } from 'cordis';
@@ -28,6 +29,33 @@ export interface WakeDeliveryResult {
28
29
  ok: boolean;
29
30
  detail: string;
30
31
  }
32
+ /**
33
+ * 调度器**进程态**(诊断用,模块级 = 进程级,正是诊断对象)。
34
+ *
35
+ * 为什么需要它(R↓,2026-09-14 F 段缺陷):调度器此前**没有任何可观测面**——
36
+ * "条目为何一直是 pending" 只能靠反推。有了这四个字段,`acc-diag` 一次调用即可回答
37
+ * "时钟是否武装 / 上次 tick 何时 / 为何跳过"。
38
+ */
39
+ interface WakeSchedulerRuntime {
40
+ /** 定时器是否在跑(startTimer 成功 → true;disposer 拆卸 → false) */
41
+ armed: boolean;
42
+ /** 武装时刻(ms;null = 从未) */
43
+ armedAt: number | null;
44
+ /** 上次真正执行 tick 的时刻(ms;null = 从未——被跳过的 tick 不刷新它) */
45
+ lastTickAt: number | null;
46
+ /** 真正执行过多少次 tick */
47
+ ticks: number;
48
+ /** 上次 tick 的逐行摘要(投递 / missed 留痕) */
49
+ lastTickLog: string[];
50
+ /** 上次被跳过 / 异常的原因(null = 上次正常执行) */
51
+ lastSkipReason: string | null;
52
+ }
53
+ /** 调度器进程态快照(只读;`enabled` = 全局闸**当前**值,便于区分"未武装"与"闸关") */
54
+ export declare function wakeSchedulerState(): WakeSchedulerRuntime & {
55
+ enabled: boolean;
56
+ };
57
+ /** 测试用:复位进程态(避免用例间串味) */
58
+ export declare function __resetWakeSchedulerStateForTest(): void;
31
59
  /** 目标定位:目录名 + SESSION.md 绝对路径 */
32
60
  interface WakeTarget {
33
61
  dirName: string;
@@ -40,7 +68,18 @@ interface WakeTarget {
40
68
  * @returns 命中返回目录信息,未命中 null
41
69
  */
42
70
  export declare function resolveWakeTarget(root: string, target: string): WakeTarget | null;
43
- /** 全局闸:`trajectoryEnabled` 优先,旧键 `autopilotEnabled` 回退(设置服务不可用 → 关) */
71
+ /**
72
+ * 全局闸:**只看 `wakeSchedulerEnabled`**(缺省**开**;显式 `false` 才关)。
73
+ *
74
+ * ⚠️ 2026-09-15 用户裁决(S-1 **解耦**):「auto-trajectory 的开关**只关闭 auto-trajectory 唤醒**」。
75
+ * ⇒ 本函数**不得**再把 `autopilotEnabled`(周期自唤醒闸)作为回退键 —— 旧实现是
76
+ * `trajectoryEnabled === true || autopilotEnabled === true`,后果实测:所有者关掉 auto-trajectory 后,
77
+ * **一次性唤醒 `wake-later` 被连带关掉**,注册表条目静默滞留(当日 6.6h 零 tick)。
78
+ * 缺省由 false 改 true 的理由:唤醒注册表已是一等机制(D58),条目**全部由人类/agent 显式登记**
79
+ * 未来时刻(无环境自主性),不需要"默认关"的实验保护;代价 = 一个 unref 的 5min 空检查。
80
+ *
81
+ * 设置服务不可用 → 关(保守;异常由日志与 `dashboard health` 响亮暴露,不静默)。
82
+ */
44
83
  export declare function wakeSchedulerEnabled(): boolean;
45
84
  /**
46
85
  * 取目标 agent:live 优先 → 冷会话走 sessionController.resolveAgent → 标题回退。
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-DAsSHsub.js";
2
+ import { s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-eu_IPF94.js";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
  import { createHash } from "node:crypto";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.32.0",
4
- "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/trajectory/im-bridge)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/trajectory 唤醒注册表。适配 DSH 0.1.5-rc.2。",
3
+ "version": "1.34.0",
4
+ "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「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。",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -1,25 +0,0 @@
1
- /**
2
- * autopilot-trajectory.ts — Autopilot Trajectory 一站式管理工具(v1.26.12 实验
3
- * autotrajectory-exp → v1.27.4 正式化改名)
4
- *
5
- * 定位:dsp **只提供工具与知识**,不向 CCC 自动安装任何东西(机制是 CCC 的自选动作)——
6
- * agent 调本工具(doc/全报告)即懂机制,init/random/check 辅助,
7
- * 实际执行(写配置/写偏见脚本/标记会话)由 CCC 自己决定、自己用现有工具完成。
8
- *
9
- * 实现:薄封装——exec 包内静态脚本(npm files 含 experiments/),脚本是单一真相源。
10
- * 环境注入 SERENITY_ROOT(当前 CCC 根)供脚本定位;bun 优先(可直跑 TS)。
11
- * diag-live:**进程内诊断**(v1.26.14 用户"排查访问不到")——闭包捕获 ctx,
12
- * 直接读 live 会话/标题/agent(脚本 diag 看不到运行时),输出实例级诊断报告。
13
- */
14
- import { defineTool } from '@deepseek-ai/dsh-tools';
15
- import type { Context } from 'cordis';
16
- /**
17
- * 定位包内脚本(npm files 分发 experiments/autopilot-trajectory/)。
18
- * 布局差异:tsdown bundle 后 import.meta.url 指向 lib/index.js(lib → 包根 1 层);
19
- * vitest 源码直跑时指向 src/tools/x.ts(src/tools → 包根 2 层)——逐级上溯查找,
20
- * 两种布局都稳(找到 experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts 即止)。
21
- */
22
- export declare function findExpScript(startDir: string): string | null;
23
- export declare const AUTOPILOT_ACTIONS: readonly ["all", "init", "random", "diag", "doc", "check", "status", "guide", "diag-live", "wake-add", "wake-list", "wake-rm"];
24
- /** 创建 trajectory 工具(原 autopilot-trajectory;D58 更名 + 唤醒注册表三动作) */
25
- export declare function createAutopilotTool(ctx: Context): ReturnType<typeof defineTool>;