@shgroup/dsh-serenity-hooks 1.31.13 → 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.
- package/README.md +14 -11
- package/dsh.plugin.json +5 -5
- package/experiments/autopilot-trajectory/SKILL.md +23 -21
- package/experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts +69 -45
- package/lib/autopilot-script.d.ts +40 -0
- package/lib/autopilot-trajectory.d.ts +44 -5
- package/lib/{ccc-DAsSHsub.js → ccc-eu_IPF94.js} +17 -1
- package/lib/ccc.d.ts +30 -0
- package/lib/client.js +43 -11
- package/lib/diag-ops.d.ts +61 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +4802 -4206
- package/lib/invariant.d.ts +5 -1
- package/lib/invariant.js +7 -3
- package/lib/json.d.ts +1 -1
- package/lib/msm-ops.d.ts +2 -2
- package/lib/output-guard-seam.d.ts +1 -1
- package/lib/rebuild.d.ts +4 -3
- package/lib/seams/guards.d.ts +27 -0
- package/lib/seams/keeper.d.ts +3 -3
- package/lib/{settings-section-01rTVq6v.js → settings-section-BuPGQv_H.js} +5 -3
- package/lib/settings-section.d.ts +15 -3
- package/lib/skiff-core.d.ts +1 -1
- package/lib/{skiff-debug-w2c3UqQk.js → skiff-debug-D83-Njy7.js} +7 -7
- package/lib/{skiff-role-BYvNnwv1.js → skiff-role-DAfPb6YQ.js} +1 -1
- package/lib/tools/acc-diag.d.ts +22 -0
- package/lib/tools/container-admin.d.ts +1 -1
- package/lib/tools/{session.d.ts → trajectory.d.ts} +32 -16
- package/lib/{session-bound-BpTayaIS.js → trajectory-bound-C8DUxi6_.js} +77 -276
- package/lib/{session-bound.d.ts → trajectory-bound.d.ts} +32 -2
- package/lib/{session-ops.d.ts → trajectory-ops.d.ts} +24 -28
- package/lib/wake-registry-D0Vj0WXb.js +270 -0
- package/lib/wake-registry.d.ts +146 -0
- package/lib/wake-scheduler.d.ts +115 -0
- package/lib/{weixin-route-wWk4AIwV.js → weixin-route-D1ND823E.js} +1 -1
- package/package.json +2 -2
- 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 };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wake-registry.ts — trajectory 唤醒注册表(D58,S142 2026-09-13)
|
|
3
|
+
*
|
|
4
|
+
* 需求(用户原话):一个 CCC 内允许多个 trajectory 任意形态并行;某个 trajectory 可以
|
|
5
|
+
* **预定未来唤醒自己或别人**,本质上是一次「未来时刻 + 一条 message」的投递;autopilot
|
|
6
|
+
* 是「周期自唤醒」的特例(独立循环,D59)。协作边界:互相可见、**无阻塞无等待**。
|
|
7
|
+
*
|
|
8
|
+
* 本模块只负责**注册表本身**(纯数据层,无宿主依赖,可单测):
|
|
9
|
+
* - 落点:CCC 内 `AGENT_SESSIONS/wake-registry.json`(随 CCC git,人可读可审计)
|
|
10
|
+
* - 状态机:`pending` →(投递成功)`delivered`|(超补跑窗口)`missed`|(`wake rm` 物理移除)
|
|
11
|
+
* - 投递即终结:一次性,不重投(无回执语义——发起方不 await,见设计文档 §2)
|
|
12
|
+
*
|
|
13
|
+
* 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
|
|
14
|
+
*/
|
|
15
|
+
/** 注册表相对 CCC 根的路径(与 `.bindings.json` 同居 AGENT_SESSIONS/) */
|
|
16
|
+
export declare const WAKE_REGISTRY_REL_PATH = "AGENT_SESSIONS/wake-registry.json";
|
|
17
|
+
/** 注册表格式版本(结构变更时 +1;读取端只接受本版本) */
|
|
18
|
+
export declare const WAKE_REGISTRY_VERSION = 1;
|
|
19
|
+
/**
|
|
20
|
+
* 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
|
|
21
|
+
* 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
|
|
22
|
+
*/
|
|
23
|
+
export declare const WAKE_CATCH_UP_MS: number;
|
|
24
|
+
/** 条目状态:一次性唤醒只有这四种归宿 */
|
|
25
|
+
export type WakeState = 'pending' | 'delivered' | 'missed' | 'cancelled';
|
|
26
|
+
/** 一条唤醒条目 */
|
|
27
|
+
export interface WakeEntry {
|
|
28
|
+
/** 稳定 id(不复用;`w-<UTC 到分钟>-<4 hex>`) */
|
|
29
|
+
id: string;
|
|
30
|
+
/** 目标 trajectory:`S###` 或完整 AGENT_SESSIONS 目录名(编码无关,与绑定锚一致) */
|
|
31
|
+
target: string;
|
|
32
|
+
/** 绝对时刻(UTC RFC3339,如 `2026-09-14T01:00:00.000Z`) */
|
|
33
|
+
at: string;
|
|
34
|
+
/** 唤醒 message(投递给目标 trajectory 的正文) */
|
|
35
|
+
message: string;
|
|
36
|
+
/** 状态 */
|
|
37
|
+
state: WakeState;
|
|
38
|
+
/** 发起者(轨迹标识,如 `S142`;可见性/审计用) */
|
|
39
|
+
createdBy: string;
|
|
40
|
+
/** 登记时刻(UTC RFC3339) */
|
|
41
|
+
createdAt: string;
|
|
42
|
+
/** 已尝试投递次数 */
|
|
43
|
+
attempts: number;
|
|
44
|
+
/** 最近一次投递结果(人读;失败必须留痕——禁止静默) */
|
|
45
|
+
lastResult: string | null;
|
|
46
|
+
/** 投递成功时刻(UTC RFC3339) */
|
|
47
|
+
deliveredAt: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** 注册表文件形态 */
|
|
50
|
+
export interface WakeRegistry {
|
|
51
|
+
version: number;
|
|
52
|
+
entries: WakeEntry[];
|
|
53
|
+
}
|
|
54
|
+
/** 注册表绝对路径 */
|
|
55
|
+
export declare function wakeRegistryPath(root: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* 读注册表。文件缺失 → 空表;损坏 → 空表 + error(不抛错,调用方决定是否告警)。
|
|
58
|
+
* @param root CCC 根
|
|
59
|
+
* @returns 注册表与读取错误(无错为 null)
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadWakeRegistry(root: string): {
|
|
62
|
+
registry: WakeRegistry;
|
|
63
|
+
error: string | null;
|
|
64
|
+
};
|
|
65
|
+
/** 原子写(tmp + rename;与 .bindings.json 同款形态) */
|
|
66
|
+
export declare function saveWakeRegistry(root: string, registry: WakeRegistry): {
|
|
67
|
+
ok: boolean;
|
|
68
|
+
error: string | null;
|
|
69
|
+
};
|
|
70
|
+
/** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
|
|
71
|
+
export declare function newWakeId(nowMs: number, rand?: () => number): string;
|
|
72
|
+
/**
|
|
73
|
+
* 解析唤醒时刻。接受两种写法(设计文档 §7):
|
|
74
|
+
* - 绝对:RFC3339(含时区偏移,如 `2026-09-14T09:00:00+08:00`)
|
|
75
|
+
* - 相对:`+30m` / `+2h`(便于 agent 直接登记"半小时后")
|
|
76
|
+
* @param input 用户/agent 传入的时刻串
|
|
77
|
+
* @param nowMs 当前时刻(相对写法基准)
|
|
78
|
+
* @returns `{ ok, ms }` 或 `{ ok: false, error }`
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseWakeAt(input: string, nowMs: number): {
|
|
81
|
+
ok: true;
|
|
82
|
+
ms: number;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
error: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 登记一条唤醒。
|
|
89
|
+
* @param root CCC 根
|
|
90
|
+
* @param input 目标 trajectory / 时刻串 / message / 发起者 / 当前时刻
|
|
91
|
+
* @returns 成功返回条目,失败返回稳定错误文案(不抛错)
|
|
92
|
+
*/
|
|
93
|
+
export declare function addWake(root: string, input: {
|
|
94
|
+
target: string;
|
|
95
|
+
at: string;
|
|
96
|
+
message: string;
|
|
97
|
+
createdBy: string;
|
|
98
|
+
nowMs: number;
|
|
99
|
+
}): {
|
|
100
|
+
ok: true;
|
|
101
|
+
entry: WakeEntry;
|
|
102
|
+
} | {
|
|
103
|
+
ok: false;
|
|
104
|
+
error: string;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* 物理移除一条(`wake rm`)。审计由 SESSION.md 与工具输出承担——注册表只保留在办项。
|
|
108
|
+
* @param root CCC 根
|
|
109
|
+
* @param id 条目 id
|
|
110
|
+
* @returns 移除结果(不存在 → 稳定错误码文案)
|
|
111
|
+
*/
|
|
112
|
+
export declare function removeWake(root: string, id: string): {
|
|
113
|
+
ok: boolean;
|
|
114
|
+
error: string | null;
|
|
115
|
+
removed: WakeEntry | null;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* 到期切分(纯函数,可单测):
|
|
119
|
+
* - `due`:`state === 'pending'` 且 `now >= at`(在补跑窗口内)→ 应投递
|
|
120
|
+
* - `expired`:`state === 'pending'` 且 `now - at > catchUpMs` → 应置 missed(不投递)
|
|
121
|
+
* @param registry 注册表
|
|
122
|
+
* @param nowMs 当前时刻
|
|
123
|
+
* @param catchUpMs 补跑窗口
|
|
124
|
+
*/
|
|
125
|
+
export declare function splitDueWakes(registry: WakeRegistry, nowMs: number, catchUpMs?: number): {
|
|
126
|
+
due: WakeEntry[];
|
|
127
|
+
expired: WakeEntry[];
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* 更新一条(状态推进;调用方给最终状态与结果文案)。
|
|
131
|
+
* @param root CCC 根
|
|
132
|
+
* @param id 条目 id
|
|
133
|
+
* @param patch 要写入的字段
|
|
134
|
+
* @returns 写入结果
|
|
135
|
+
*/
|
|
136
|
+
export declare function updateWake(root: string, id: string, patch: Partial<Pick<WakeEntry, 'state' | 'attempts' | 'lastResult' | 'deliveredAt'>>): {
|
|
137
|
+
ok: boolean;
|
|
138
|
+
error: string | null;
|
|
139
|
+
};
|
|
140
|
+
/** 列条目(默认全量;`pendingOnly` 只列在办项——面板/工具用) */
|
|
141
|
+
export declare function listWakes(root: string, opts?: {
|
|
142
|
+
pendingOnly?: boolean;
|
|
143
|
+
}): {
|
|
144
|
+
entries: WakeEntry[];
|
|
145
|
+
error: string | null;
|
|
146
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wake-scheduler.ts — trajectory 唤醒调度器(D58/D59/D60;S142 2026-09-13)
|
|
3
|
+
*
|
|
4
|
+
* 一个**中心调度器**(5min tick,复用 autopilot 的节律常量),处理 CCC 唤醒注册表里
|
|
5
|
+
* 到期的条目:把「未来时刻 + 一条 message」投递给目标 trajectory,**fire-and-forget**
|
|
6
|
+
* (无阻塞、无等待、无回执——发起方不 await,调度器不替模型结果负责)。
|
|
7
|
+
*
|
|
8
|
+
* 与 autopilot 的关系(D59):**两个 tick 各自独立**。autopilot 是每 CCC 单例的
|
|
9
|
+
* 周期自唤醒,逻辑一行不碰;本模块只处理注册表的一次性唤醒条目。
|
|
10
|
+
*
|
|
11
|
+
* 投递路径(P-1 证据链见 SESSION.md §30.6):
|
|
12
|
+
* 1. target(S### / 目录名)→ 目录名 + SESSION.md
|
|
13
|
+
* 2. 目标 dsh 会话 id:`.bindings.json` 里绑定该目录名的会话(按绑定时间倒序,权威)
|
|
14
|
+
* → 回退:live 会话标题匹配
|
|
15
|
+
* 3. agent:**live 优先**(`ctx.agents.get`)→ 冷会话走 `ctx.sessionController.resolveAgent`
|
|
16
|
+
* (宿主实现:live 优先 + resume 去重 + 由会话元数据恢复 preset)
|
|
17
|
+
* → `sessionController` 缺席(headless profile)⇒ 仅投递 live 目标,**不静默**
|
|
18
|
+
* 4. 投递:`agent.followup(...)`("Queue an ordinary follow-up turn and wake the driver")
|
|
19
|
+
*
|
|
20
|
+
* 守卫(I-4 默认 + v1.34 S-1 解耦):全局 `wakeSchedulerEnabled`(**缺省开**;**不再**回退旧键
|
|
21
|
+
* `autopilotEnabled`——关周期自唤醒不得连带关一次性唤醒)|
|
|
22
|
+
* 补跑窗口 2h(超窗置 missed 留痕)|同 tick 串行|唤起后维持 live(人类可介入)。
|
|
23
|
+
*/
|
|
24
|
+
import type { Context } from 'cordis';
|
|
25
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
26
|
+
import { type WakeEntry } from './wake-registry.js';
|
|
27
|
+
/** 投递结果(写回条目的 lastResult;人读) */
|
|
28
|
+
export interface WakeDeliveryResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
detail: string;
|
|
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;
|
|
59
|
+
/** 目标定位:目录名 + SESSION.md 绝对路径 */
|
|
60
|
+
interface WakeTarget {
|
|
61
|
+
dirName: string;
|
|
62
|
+
mdPath: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 解析条目的 target(`S###` 或完整目录名)→ 目录名 + SESSION.md 路径。
|
|
66
|
+
* @param root CCC 根
|
|
67
|
+
* @param target 条目里的目标标识
|
|
68
|
+
* @returns 命中返回目录信息,未命中 null
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveWakeTarget(root: string, target: string): WakeTarget | null;
|
|
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
|
+
*/
|
|
83
|
+
export declare function wakeSchedulerEnabled(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* 取目标 agent:live 优先 → 冷会话走 sessionController.resolveAgent → 标题回退。
|
|
86
|
+
* @param ctx 插件上下文
|
|
87
|
+
* @param root CCC 根
|
|
88
|
+
* @param dirName 目标 trajectory 目录名
|
|
89
|
+
* @returns 命中返回 agent 与取得方式(how 进 lastResult,便于诊断);未命中返回原因
|
|
90
|
+
*/
|
|
91
|
+
export declare function acquireWakeAgent(ctx: Context, root: string, dirName: string): Promise<{
|
|
92
|
+
agent: Agent;
|
|
93
|
+
how: string;
|
|
94
|
+
} | {
|
|
95
|
+
error: string;
|
|
96
|
+
}>;
|
|
97
|
+
/** 唤醒消息(唤起即投递正文:身份锚定 + 唤醒信息 + 任务) */
|
|
98
|
+
export declare function buildWakeText(entry: WakeEntry, target: WakeTarget): string;
|
|
99
|
+
/**
|
|
100
|
+
* 投递一条唤醒(fire-and-forget:`followup` 入队即返回,不等模型结果)。
|
|
101
|
+
* @param ctx 插件上下文
|
|
102
|
+
* @param root CCC 根
|
|
103
|
+
* @param entry 唤醒条目
|
|
104
|
+
* @returns 投递结果(写回 lastResult)
|
|
105
|
+
*/
|
|
106
|
+
export declare function deliverWake(ctx: Context, root: string, entry: WakeEntry): Promise<WakeDeliveryResult>;
|
|
107
|
+
/** 一次调度 tick:遍历 live CCC → 到期项串行投递 / 超窗项置 missed;返回人读摘要 */
|
|
108
|
+
export declare function runWakeTick(ctx: Context): Promise<string[]>;
|
|
109
|
+
/**
|
|
110
|
+
* 装配调度器(index.ts apply 调用):5min tick + 生命周期 disposer +
|
|
111
|
+
* 会话出现/settings 变化时热启动(与 autopilot 同款触发面,互不干扰)。
|
|
112
|
+
* @param ctx 插件上下文
|
|
113
|
+
*/
|
|
114
|
+
export declare function registerWakeScheduler(ctx: Context): void;
|
|
115
|
+
export {};
|
|
@@ -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-
|
|
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.
|
|
4
|
-
"description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:11 个工具(container_fs/
|
|
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"];
|
|
24
|
-
/** 创建 autopilot-trajectory 工具(闭包捕获 ctx → diag-live 进程内诊断;v1.26.14 + v1.27.4 改名) */
|
|
25
|
-
export declare function createAutopilotTool(ctx: Context): ReturnType<typeof defineTool>;
|