@shgroup/dsh-serenity-hooks 1.32.0 → 1.34.1
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 +12 -11
- package/dsh.plugin.json +5 -5
- package/lib/acp-core.d.ts +2 -22
- package/lib/acp-http.d.ts +8 -0
- package/lib/api.d.ts +12 -4
- package/lib/autopilot-chain.d.ts +76 -0
- package/lib/autopilot-core-DCRkiWEe.js +229 -0
- package/lib/autopilot-core.d.ts +132 -0
- package/lib/autopilot-ops.d.ts +48 -0
- package/lib/autopilot-trajectory-D9Znxo75.js +782 -0
- package/lib/autopilot-trajectory.d.ts +79 -83
- package/lib/{ccc-DAsSHsub.js → ccc-NlLr_sxy.js} +18 -2
- package/lib/ccc-roots-Bd_SjEs7.js +152 -0
- package/lib/ccc-roots.d.ts +105 -0
- package/lib/ccc.d.ts +23 -6
- package/lib/client.js +30 -10
- package/lib/clock-runtime.d.ts +197 -0
- package/lib/config-ops.d.ts +9 -14
- package/lib/constants.d.ts +0 -2
- package/lib/container-status.d.ts +183 -0
- package/lib/diag-ops.d.ts +76 -0
- package/lib/face-host.d.ts +110 -0
- package/lib/fs-ops.d.ts +3 -2
- package/lib/gateway-auth.d.ts +2 -5
- package/lib/gateway.d.ts +0 -34
- package/lib/git-ops.d.ts +3 -2
- package/lib/handyman-ops.d.ts +3 -2
- package/lib/handyman-preset-inherit.d.ts +2 -1
- package/lib/host/access.d.ts +8 -7
- package/lib/host/contract.d.ts +17 -6
- package/lib/host/type-contract.d.ts +1 -2
- package/lib/im-bridge.d.ts +6 -5
- package/lib/index.d.ts +1 -1
- package/lib/index.js +11053 -8305
- package/lib/invariant.d.ts +5 -16
- package/lib/invariant.js +8 -4
- package/lib/json.d.ts +1 -1
- package/lib/kit-ops.d.ts +11 -31
- package/lib/localstore-ops.d.ts +3 -17
- package/lib/msm-ops.d.ts +34 -52
- package/lib/opencode-provider.d.ts +4 -5
- package/lib/output-guard-seam.d.ts +1 -4
- package/lib/output-guard.d.ts +12 -3
- package/lib/ports.d.ts +59 -0
- package/lib/rebuild.d.ts +5 -4
- package/lib/seams/bootstrap.d.ts +4 -3
- package/lib/seams/compact.d.ts +2 -1
- package/lib/seams/context.d.ts +2 -4
- package/lib/seams/env.d.ts +2 -1
- package/lib/seams/guards.d.ts +22 -34
- package/lib/seams/keeper.d.ts +4 -4
- package/lib/seams/system-prompt.d.ts +1 -5
- package/lib/session-cleanup.d.ts +3 -2
- package/lib/{settings-section-Bm-xG-NR.js → settings-section-DTYZjwUs.js} +70 -15
- package/lib/settings-section.d.ts +14 -35
- package/lib/skiff-core.d.ts +5 -10
- package/lib/skiff-debug.d.ts +15 -21
- package/lib/skiff-registry-FTjoWQTQ.js +25 -0
- package/lib/skiff-registry.d.ts +2 -1
- package/lib/{skiff-role-BYvNnwv1.js → skiff-role-CEHL5cek.js} +1 -1
- package/lib/skiff-role.d.ts +2 -2
- package/lib/skills/opencode-scan.d.ts +3 -2
- package/lib/skills-discovery.d.ts +15 -2
- package/lib/status.d.ts +2 -1
- package/lib/tools/acc-diag.d.ts +22 -0
- package/lib/tools/cce.d.ts +1 -2
- package/lib/tools/container-admin.d.ts +7 -3
- package/lib/tools/eap.d.ts +2 -3
- package/lib/tools/handyman.d.ts +0 -29
- package/lib/tools/msm.d.ts +0 -4
- package/lib/tools/neat.d.ts +1 -2
- package/lib/tools/praxis.d.ts +0 -1
- package/lib/tools/skiff-admin.d.ts +2 -2
- package/lib/tools/{session.d.ts → trajectory.d.ts} +25 -17
- package/lib/totp.d.ts +0 -4
- package/lib/trajectory-assistant.d.ts +2 -1
- package/lib/trajectory-bound-Cax1p9ut.js +190 -0
- package/lib/{session-bound.d.ts → trajectory-bound.d.ts} +5 -6
- package/lib/trajectory-ops-mrXK0H0I.js +410 -0
- package/lib/{session-ops.d.ts → trajectory-ops.d.ts} +27 -36
- package/lib/trajectory-skills.d.ts +71 -0
- package/lib/wake-registry-CYxIb484.js +257 -0
- package/lib/wake-registry.d.ts +3 -11
- package/lib/wake-scheduler.d.ts +23 -20
- package/lib/web-fetch-provider.d.ts +0 -21
- package/lib/{weixin-api-PjEhmjlZ.js → weixin-api-CtFYm53S.js} +3 -9
- package/lib/weixin-api.d.ts +6 -37
- package/lib/weixin-bridge.d.ts +5 -24
- package/lib/weixin-hook.d.ts +6 -12
- package/lib/weixin-output-guard.d.ts +2 -1
- package/lib/{weixin-route-wWk4AIwV.js → weixin-route-B2ajFypI.js} +4 -5
- package/lib/weixin-route.d.ts +0 -2
- package/lib/weixin-send-api.d.ts +15 -6
- package/package.json +3 -5
- package/experiments/autopilot-trajectory/SKILL.md +0 -115
- package/experiments/autopilot-trajectory/scripts/autopilot-trajectory.ts +0 -522
- package/lib/session-bound-NHozeLFc.js +0 -822
- package/lib/skiff-debug-K2k1JnGj.js +0 -2867
- package/lib/tools/autopilot-trajectory.d.ts +0 -25
- package/lib/{session-cleanup-CMT4mHWt.js → session-cleanup-B6IHZgww.js} +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
//#region src/wake-registry.ts
|
|
4
|
+
/**
|
|
5
|
+
* wake-registry.ts — trajectory 唤醒注册表(D58,S142 2026-09-13)
|
|
6
|
+
*
|
|
7
|
+
* 需求(用户原话):一个 CCC 内允许多个 trajectory 任意形态并行;某个 trajectory 可以
|
|
8
|
+
* **预定未来唤醒自己或别人**,本质上是一次「未来时刻 + 一条 message」的投递;autopilot
|
|
9
|
+
* 是「周期自唤醒」的特例(独立循环,D59)。协作边界:互相可见、**无阻塞无等待**。
|
|
10
|
+
*
|
|
11
|
+
* 本模块只负责**注册表本身**(纯数据层,无宿主依赖,可单测):
|
|
12
|
+
* - 落点:CCC 内 `AGENT_SESSIONS/wake-registry.json`(随 CCC git,人可读可审计)
|
|
13
|
+
* - 状态机:`pending` →(投递成功)`delivered`|(超补跑窗口)`missed`|(`wake rm` 物理移除)
|
|
14
|
+
* - 投递即终结:一次性,不重投(无回执语义——发起方不 await,见设计文档 §2)
|
|
15
|
+
*
|
|
16
|
+
* 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
|
|
17
|
+
*/
|
|
18
|
+
/** 注册表相对 CCC 根的路径(与 `.bindings.json` 同居 AGENT_SESSIONS/) */
|
|
19
|
+
const WAKE_REGISTRY_REL_PATH = "AGENT_SESSIONS/wake-registry.json";
|
|
20
|
+
/** 注册表格式版本(结构变更时 +1;读取端只接受本版本) */
|
|
21
|
+
const WAKE_REGISTRY_VERSION = 1;
|
|
22
|
+
/**
|
|
23
|
+
* 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
|
|
24
|
+
* 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
|
|
25
|
+
*/
|
|
26
|
+
const WAKE_CATCH_UP_MS = 72e5;
|
|
27
|
+
/** 注册表绝对路径 */
|
|
28
|
+
function wakeRegistryPath(root) {
|
|
29
|
+
return join(root, WAKE_REGISTRY_REL_PATH);
|
|
30
|
+
}
|
|
31
|
+
/** 空注册表 */
|
|
32
|
+
function emptyRegistry() {
|
|
33
|
+
return {
|
|
34
|
+
version: WAKE_REGISTRY_VERSION,
|
|
35
|
+
entries: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** 条目形状校验(容忍手改:字段缺失/类型不符 → 丢弃该条,不阻断整表) */
|
|
39
|
+
function asEntry(value) {
|
|
40
|
+
const e = value;
|
|
41
|
+
if (!e || typeof e.id !== "string" || typeof e.target !== "string") return null;
|
|
42
|
+
if (typeof e.at !== "string" || typeof e.message !== "string") return null;
|
|
43
|
+
const state = e.state === "delivered" || e.state === "missed" || e.state === "cancelled" ? e.state : "pending";
|
|
44
|
+
return {
|
|
45
|
+
id: e.id,
|
|
46
|
+
target: e.target,
|
|
47
|
+
at: e.at,
|
|
48
|
+
message: e.message,
|
|
49
|
+
state,
|
|
50
|
+
createdBy: typeof e.createdBy === "string" ? e.createdBy : "",
|
|
51
|
+
createdAt: typeof e.createdAt === "string" ? e.createdAt : "",
|
|
52
|
+
attempts: typeof e.attempts === "number" && Number.isFinite(e.attempts) ? e.attempts : 0,
|
|
53
|
+
lastResult: typeof e.lastResult === "string" ? e.lastResult : null,
|
|
54
|
+
deliveredAt: typeof e.deliveredAt === "string" ? e.deliveredAt : null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 读注册表。文件缺失 → 空表;损坏 → 空表 + error(不抛错,调用方决定是否告警)。
|
|
59
|
+
* @param root CCC 根
|
|
60
|
+
* @returns 注册表与读取错误(无错为 null)
|
|
61
|
+
*/
|
|
62
|
+
function loadWakeRegistry(root) {
|
|
63
|
+
const path = wakeRegistryPath(root);
|
|
64
|
+
if (!existsSync(path)) return {
|
|
65
|
+
registry: emptyRegistry(),
|
|
66
|
+
error: null
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
70
|
+
const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const item of raw) {
|
|
73
|
+
const e = asEntry(item);
|
|
74
|
+
if (e) entries.push(e);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
registry: {
|
|
78
|
+
version: WAKE_REGISTRY_VERSION,
|
|
79
|
+
entries
|
|
80
|
+
},
|
|
81
|
+
error: null
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
registry: emptyRegistry(),
|
|
86
|
+
error: `唤醒注册表解析失败(${String(err?.message ?? err)})`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** 原子写(tmp + rename;与 .bindings.json 同款形态) */
|
|
91
|
+
function saveWakeRegistry(root, registry) {
|
|
92
|
+
const path = wakeRegistryPath(root);
|
|
93
|
+
try {
|
|
94
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
95
|
+
const tmp = `${path}.tmp`;
|
|
96
|
+
writeFileSync(tmp, `${JSON.stringify({
|
|
97
|
+
version: WAKE_REGISTRY_VERSION,
|
|
98
|
+
entries: registry.entries
|
|
99
|
+
}, null, 2)}\n`, "utf-8");
|
|
100
|
+
renameSync(tmp, path);
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
error: null
|
|
104
|
+
};
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: `唤醒注册表写入失败(${String(err?.message ?? err)})`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
|
|
113
|
+
function newWakeId(nowMs, rand = Math.random) {
|
|
114
|
+
const iso = new Date(nowMs).toISOString();
|
|
115
|
+
return `w-${`${iso.slice(0, 10).replace(/-/g, "")}-${iso.slice(11, 16).replace(":", "")}`}-${Math.floor(rand() * 65536).toString(16).padStart(4, "0")}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 解析唤醒时刻。接受两种写法(设计文档 §7):
|
|
119
|
+
* - 绝对:RFC3339(含时区偏移,如 `2026-09-14T09:00:00+08:00`)
|
|
120
|
+
* - 相对:`+30m` / `+2h`(便于 agent 直接登记"半小时后")
|
|
121
|
+
* @param input 用户/agent 传入的时刻串
|
|
122
|
+
* @param nowMs 当前时刻(相对写法基准)
|
|
123
|
+
* @returns `{ ok, ms }` 或 `{ ok: false, error }`
|
|
124
|
+
*/
|
|
125
|
+
function parseWakeAt(input, nowMs) {
|
|
126
|
+
const raw = input.trim();
|
|
127
|
+
if (raw === "") return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: "时刻为空"
|
|
130
|
+
};
|
|
131
|
+
const rel = /^\+(\d+(?:\.\d+)?)([mhd])$/.exec(raw);
|
|
132
|
+
if (rel) {
|
|
133
|
+
const n = Number(rel[1]);
|
|
134
|
+
const unit = rel[2];
|
|
135
|
+
return {
|
|
136
|
+
ok: true,
|
|
137
|
+
ms: nowMs + n * (unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const ms = Date.parse(raw);
|
|
141
|
+
if (!Number.isFinite(ms)) return {
|
|
142
|
+
ok: false,
|
|
143
|
+
error: `时刻无法解析(须 RFC3339 或 +30m/+2h): ${raw}`
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
ms
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 登记一条唤醒。
|
|
152
|
+
* @param root CCC 根
|
|
153
|
+
* @param input 目标 trajectory / 时刻串 / message / 发起者 / 当前时刻
|
|
154
|
+
* @returns 成功返回条目,失败返回稳定错误文案(不抛错)
|
|
155
|
+
*/
|
|
156
|
+
function addWake(root, input) {
|
|
157
|
+
const target = input.target.trim();
|
|
158
|
+
if (target === "") return {
|
|
159
|
+
ok: false,
|
|
160
|
+
error: "target 为空(须 S### 或 AGENT_SESSIONS 目录名)"
|
|
161
|
+
};
|
|
162
|
+
const message = input.message.trim();
|
|
163
|
+
if (message === "") return {
|
|
164
|
+
ok: false,
|
|
165
|
+
error: "message 为空(唤醒必须携带一条信息)"
|
|
166
|
+
};
|
|
167
|
+
const parsed = parseWakeAt(input.at, input.nowMs);
|
|
168
|
+
if (!parsed.ok) return {
|
|
169
|
+
ok: false,
|
|
170
|
+
error: parsed.error
|
|
171
|
+
};
|
|
172
|
+
if (parsed.ms <= input.nowMs) return {
|
|
173
|
+
ok: false,
|
|
174
|
+
error: "时刻必须在未来(唤醒只允许可预期的未来)"
|
|
175
|
+
};
|
|
176
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
177
|
+
if (error) return {
|
|
178
|
+
ok: false,
|
|
179
|
+
error
|
|
180
|
+
};
|
|
181
|
+
const entry = {
|
|
182
|
+
id: newWakeId(input.nowMs),
|
|
183
|
+
target,
|
|
184
|
+
at: new Date(parsed.ms).toISOString(),
|
|
185
|
+
message,
|
|
186
|
+
state: "pending",
|
|
187
|
+
createdBy: input.createdBy,
|
|
188
|
+
createdAt: new Date(input.nowMs).toISOString(),
|
|
189
|
+
attempts: 0,
|
|
190
|
+
lastResult: null,
|
|
191
|
+
deliveredAt: null
|
|
192
|
+
};
|
|
193
|
+
registry.entries.push(entry);
|
|
194
|
+
const saved = saveWakeRegistry(root, registry);
|
|
195
|
+
if (!saved.ok) return {
|
|
196
|
+
ok: false,
|
|
197
|
+
error: saved.error ?? "写入失败"
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
ok: true,
|
|
201
|
+
entry
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 到期切分(纯函数,可单测):
|
|
206
|
+
* - `due`:`state === 'pending'` 且 `now >= at`(在补跑窗口内)→ 应投递
|
|
207
|
+
* - `expired`:`state === 'pending'` 且 `now - at > catchUpMs` → 应置 missed(不投递)
|
|
208
|
+
* @param registry 注册表
|
|
209
|
+
* @param nowMs 当前时刻
|
|
210
|
+
* @param catchUpMs 补跑窗口
|
|
211
|
+
*/
|
|
212
|
+
function splitDueWakes(registry, nowMs, catchUpMs = WAKE_CATCH_UP_MS) {
|
|
213
|
+
const due = [];
|
|
214
|
+
const expired = [];
|
|
215
|
+
for (const e of registry.entries) {
|
|
216
|
+
if (e.state !== "pending") continue;
|
|
217
|
+
const at = Date.parse(e.at);
|
|
218
|
+
if (!Number.isFinite(at) || nowMs < at) continue;
|
|
219
|
+
if (nowMs - at > catchUpMs) expired.push(e);
|
|
220
|
+
else due.push(e);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
due,
|
|
224
|
+
expired
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 更新一条(状态推进;调用方给最终状态与结果文案)。
|
|
229
|
+
* @param root CCC 根
|
|
230
|
+
* @param id 条目 id
|
|
231
|
+
* @param patch 要写入的字段
|
|
232
|
+
* @returns 写入结果
|
|
233
|
+
*/
|
|
234
|
+
function updateWake(root, id, patch) {
|
|
235
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
236
|
+
if (error) return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error
|
|
239
|
+
};
|
|
240
|
+
const entry = registry.entries.find((e) => e.id === id);
|
|
241
|
+
if (!entry) return {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: `wake_not_found: ${id}`
|
|
244
|
+
};
|
|
245
|
+
Object.assign(entry, patch);
|
|
246
|
+
return saveWakeRegistry(root, registry);
|
|
247
|
+
}
|
|
248
|
+
/** 列条目(默认全量;`pendingOnly` 只列在办项——面板/工具用) */
|
|
249
|
+
function listWakes(root, opts = {}) {
|
|
250
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
251
|
+
return {
|
|
252
|
+
entries: [...opts.pendingOnly ? registry.entries.filter((e) => e.state === "pending") : registry.entries].sort((a, b) => Date.parse(a.at) - Date.parse(b.at)),
|
|
253
|
+
error
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
export { splitDueWakes as a, loadWakeRegistry as i, addWake as n, updateWake as o, listWakes as r, WAKE_CATCH_UP_MS as t };
|
package/lib/wake-registry.d.ts
CHANGED
|
@@ -12,17 +12,13 @@
|
|
|
12
12
|
*
|
|
13
13
|
* 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
|
|
14
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
15
|
/**
|
|
20
16
|
* 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
|
|
21
17
|
* 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
|
|
22
18
|
*/
|
|
23
19
|
export declare const WAKE_CATCH_UP_MS: number;
|
|
24
20
|
/** 条目状态:一次性唤醒只有这四种归宿 */
|
|
25
|
-
|
|
21
|
+
type WakeState = 'pending' | 'delivered' | 'missed' | 'cancelled';
|
|
26
22
|
/** 一条唤醒条目 */
|
|
27
23
|
export interface WakeEntry {
|
|
28
24
|
/** 稳定 id(不复用;`w-<UTC 到分钟>-<4 hex>`) */
|
|
@@ -47,7 +43,7 @@ export interface WakeEntry {
|
|
|
47
43
|
deliveredAt: string | null;
|
|
48
44
|
}
|
|
49
45
|
/** 注册表文件形态 */
|
|
50
|
-
|
|
46
|
+
interface WakeRegistry {
|
|
51
47
|
version: number;
|
|
52
48
|
entries: WakeEntry[];
|
|
53
49
|
}
|
|
@@ -62,11 +58,6 @@ export declare function loadWakeRegistry(root: string): {
|
|
|
62
58
|
registry: WakeRegistry;
|
|
63
59
|
error: string | null;
|
|
64
60
|
};
|
|
65
|
-
/** 原子写(tmp + rename;与 .bindings.json 同款形态) */
|
|
66
|
-
export declare function saveWakeRegistry(root: string, registry: WakeRegistry): {
|
|
67
|
-
ok: boolean;
|
|
68
|
-
error: string | null;
|
|
69
|
-
};
|
|
70
61
|
/** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
|
|
71
62
|
export declare function newWakeId(nowMs: number, rand?: () => number): string;
|
|
72
63
|
/**
|
|
@@ -144,3 +135,4 @@ export declare function listWakes(root: string, opts?: {
|
|
|
144
135
|
entries: WakeEntry[];
|
|
145
136
|
error: string | null;
|
|
146
137
|
};
|
|
138
|
+
export {};
|
package/lib/wake-scheduler.d.ts
CHANGED
|
@@ -17,17 +17,31 @@
|
|
|
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
|
|
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';
|
|
24
|
-
import type
|
|
25
|
+
import { type ClockRuntime } from './clock-runtime.js';
|
|
25
26
|
import { type WakeEntry } from './wake-registry.js';
|
|
26
27
|
/** 投递结果(写回条目的 lastResult;人读) */
|
|
27
|
-
|
|
28
|
+
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
|
+
* C6b(P2):字段由 {@link ClockRuntime} 提供(**工厂那份**,字段名与语义一个字未改);
|
|
40
|
+
* 此处只把 `lastTickLog` 收为**必需**(本钟独有,autopilot 没有它——现状稿 §2.4)。
|
|
41
|
+
*/
|
|
42
|
+
export type WakeSchedulerRuntime = ClockRuntime & {
|
|
43
|
+
lastTickLog: string[];
|
|
44
|
+
};
|
|
31
45
|
/** 目标定位:目录名 + SESSION.md 绝对路径 */
|
|
32
46
|
interface WakeTarget {
|
|
33
47
|
dirName: string;
|
|
@@ -40,21 +54,12 @@ interface WakeTarget {
|
|
|
40
54
|
* @returns 命中返回目录信息,未命中 null
|
|
41
55
|
*/
|
|
42
56
|
export declare function resolveWakeTarget(root: string, target: string): WakeTarget | null;
|
|
43
|
-
/**
|
|
44
|
-
export declare function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* @param dirName 目标 trajectory 目录名
|
|
50
|
-
* @returns 命中返回 agent 与取得方式(how 进 lastResult,便于诊断);未命中返回原因
|
|
51
|
-
*/
|
|
52
|
-
export declare function acquireWakeAgent(ctx: Context, root: string, dirName: string): Promise<{
|
|
53
|
-
agent: Agent;
|
|
54
|
-
how: string;
|
|
55
|
-
} | {
|
|
56
|
-
error: string;
|
|
57
|
-
}>;
|
|
57
|
+
/** 调度器进程态快照(只读;`enabled` = 全局闸**当前**值,便于区分"未武装"与"闸关") */
|
|
58
|
+
export declare function wakeSchedulerState(): WakeSchedulerRuntime & {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
};
|
|
61
|
+
/** 测试用:复位进程态(避免用例间串味) */
|
|
62
|
+
export declare function __resetWakeSchedulerStateForTest(): void;
|
|
58
63
|
/** 唤醒消息(唤起即投递正文:身份锚定 + 唤醒信息 + 任务) */
|
|
59
64
|
export declare function buildWakeText(entry: WakeEntry, target: WakeTarget): string;
|
|
60
65
|
/**
|
|
@@ -65,8 +70,6 @@ export declare function buildWakeText(entry: WakeEntry, target: WakeTarget): str
|
|
|
65
70
|
* @returns 投递结果(写回 lastResult)
|
|
66
71
|
*/
|
|
67
72
|
export declare function deliverWake(ctx: Context, root: string, entry: WakeEntry): Promise<WakeDeliveryResult>;
|
|
68
|
-
/** 一次调度 tick:遍历 live CCC → 到期项串行投递 / 超窗项置 missed;返回人读摘要 */
|
|
69
|
-
export declare function runWakeTick(ctx: Context): Promise<string[]>;
|
|
70
73
|
/**
|
|
71
74
|
* 装配调度器(index.ts apply 调用):5min tick + 生命周期 disposer +
|
|
72
75
|
* 会话出现/settings 变化时热启动(与 autopilot 同款触发面,互不干扰)。
|
|
@@ -24,27 +24,6 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { Context } from 'cordis';
|
|
26
26
|
import type { HttpFetchResolver } from '@deepseek-ai/dsh-web-fetch-http';
|
|
27
|
-
/**
|
|
28
|
-
* 与宿主 `WebError` 同形的最小错误(带机器可路由的 `code`)。
|
|
29
|
-
*
|
|
30
|
-
* 为什么不 import 宿主的 WebError:宿主 peer 包在测试环境不可解析(现有测试全部
|
|
31
|
-
* `vi.mock('@deepseek-ai/dsh-tools')` 同因),而 `dsh-tool-web` 只渲染 `error.message`、
|
|
32
|
-
* 不判 `instanceof`——因此本地错误对象足以满足契约,且让本模块零运行时宿主依赖。
|
|
33
|
-
*/
|
|
34
|
-
export declare function fetchError(message: string, code: string): Error;
|
|
35
|
-
/**
|
|
36
|
-
* 传输与配额上限。
|
|
37
|
-
*
|
|
38
|
-
* 值镜像 `@deepseek-ai/dsh-web-fetch-http` 的 Config 默认值(该包 README 的字段表;
|
|
39
|
-
* 它不导出解析后的默认值,`Config` 是 schemastery schema 而非结果)。`userAgent`
|
|
40
|
-
* 运行时取该包导出的 `DEFAULT_USER_AGENT`(单一真相源,不复制字符串)。
|
|
41
|
-
*/
|
|
42
|
-
export declare const FETCH_LIMIT_DEFAULTS: {
|
|
43
|
-
readonly maxResponseBytes: 5000000;
|
|
44
|
-
readonly maxBodyChars: 100000;
|
|
45
|
-
readonly timeoutMs: 30000;
|
|
46
|
-
readonly maxRedirects: 5;
|
|
47
|
-
};
|
|
48
27
|
/** 公网单播 IPv4(拒绝全部保留/私网段——与宿主判定口径一致,只额外放行 fake-ip) */
|
|
49
28
|
export declare function isPublicV4(address: string): boolean;
|
|
50
29
|
/** fake-ip 段判定(198.18.0.0/15) */
|
|
@@ -18,14 +18,8 @@ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:
|
|
|
18
18
|
* - sendmessage 必须回带 context_token 关联对话
|
|
19
19
|
*/
|
|
20
20
|
var weixin_api_exports = /* @__PURE__ */ __exportAll({
|
|
21
|
-
ILINK_CDN_BASE_URL: () => ILINK_CDN_BASE_URL,
|
|
22
|
-
ILINK_DEFAULT_BASE_URL: () => ILINK_DEFAULT_BASE_URL,
|
|
23
|
-
MessageItemType: () => MessageItemType,
|
|
24
|
-
MessageState: () => MessageState,
|
|
25
|
-
MessageType: () => MessageType,
|
|
26
21
|
TypingStatus: () => TypingStatus,
|
|
27
22
|
aes128EcbDecrypt: () => aes128EcbDecrypt,
|
|
28
|
-
assertIlinkOk: () => assertIlinkOk,
|
|
29
23
|
buildClientVersion: () => buildClientVersion,
|
|
30
24
|
buildMediaDownloadUrl: () => buildMediaDownloadUrl,
|
|
31
25
|
downloadMedia: () => downloadMedia,
|
|
@@ -146,7 +140,7 @@ function assertIlinkOk(endpoint, rawText) {
|
|
|
146
140
|
async function fetchQRCode(params) {
|
|
147
141
|
const botType = params.botType ?? "3";
|
|
148
142
|
const rawText = await apiGet({
|
|
149
|
-
baseUrl: params.baseUrl ??
|
|
143
|
+
baseUrl: params.baseUrl ?? ILINK_DEFAULT_BASE_URL,
|
|
150
144
|
endpoint: `ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(botType)}`,
|
|
151
145
|
timeoutMs: params.timeoutMs ?? 5e3
|
|
152
146
|
});
|
|
@@ -155,7 +149,7 @@ async function fetchQRCode(params) {
|
|
|
155
149
|
async function pollQRStatus(params) {
|
|
156
150
|
try {
|
|
157
151
|
const rawText = await apiGet({
|
|
158
|
-
baseUrl: params.baseUrl ??
|
|
152
|
+
baseUrl: params.baseUrl ?? ILINK_DEFAULT_BASE_URL,
|
|
159
153
|
endpoint: `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(params.qrcode)}`,
|
|
160
154
|
timeoutMs: params.timeoutMs ?? 35e3
|
|
161
155
|
});
|
|
@@ -272,7 +266,7 @@ async function sendFileMessage(params) {
|
|
|
272
266
|
}
|
|
273
267
|
assertIlinkOk("ilink/bot/getuploadurl", uplRaw);
|
|
274
268
|
if (!upl.upload_full_url && !upl.upload_param) throw new Error(`getuploadurl 响应缺 upload_full_url/upload_param: ${uplRaw.slice(0, 300)}`);
|
|
275
|
-
const uploadUrl = upl.upload_full_url ??
|
|
269
|
+
const uploadUrl = upl.upload_full_url ?? `${ILINK_CDN_BASE_URL}/upload?encrypted_query_param=${encodeURIComponent(upl.upload_param ?? "")}&filekey=${encodeURIComponent(upl.filekey ?? fileKey)}`;
|
|
276
270
|
const cipher = createCipheriv("aes-128-ecb", aesKey, null);
|
|
277
271
|
const encBody = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
278
272
|
const uploadRes = await currentFetch(uploadUrl, {
|
package/lib/weixin-api.d.ts
CHANGED
|
@@ -14,26 +14,12 @@
|
|
|
14
14
|
* - getupdates 长轮询 35s hold;游标 get_updates_buf 从响应原样带回续传
|
|
15
15
|
* - sendmessage 必须回带 context_token 关联对话
|
|
16
16
|
*/
|
|
17
|
-
/** iLink API Base URL(腾讯官方) */
|
|
18
|
-
export declare const ILINK_DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
19
|
-
/** CDN Base(媒体上传/下载;P3 媒体期用) */
|
|
20
|
-
export declare const ILINK_CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
|
|
21
17
|
/** 版本号 → ClientVersion 整数:(major<<16)|(minor<<8)|patch */
|
|
22
18
|
export declare function buildClientVersion(version: string): number;
|
|
23
19
|
/** fetch 注入点(测试替换;生产 = 全局 fetch) */
|
|
24
|
-
|
|
20
|
+
type FetchLike = typeof fetch;
|
|
25
21
|
/** 测试注入:替换全局 fetch 实现(生产零调用) */
|
|
26
22
|
export declare function __setWeixinFetchForTest(fn: FetchLike | null): void;
|
|
27
|
-
/**
|
|
28
|
-
* iLink 业务层返回码校验(v1.30.9 修复):`apiPost` 只判 HTTP 状态,而 iLink 在 **HTTP 200**
|
|
29
|
-
* 的响应体里用 `ret` / `errcode` 表达失败(如 `{ret:1,errmsg:"denied"}`)——此前被当作成功,
|
|
30
|
-
* 于是"发送成功"的判定是假的:回复路径会给一条**从未送达**的消息触发 outgoing hook 记录
|
|
31
|
-
* (与 weixin-bridge 注释"发送失败不记录"矛盾)。发送类调用一律经此校验。
|
|
32
|
-
* 非 JSON 的 200 响应保持既有宽容语义(视为成功)。
|
|
33
|
-
* @param endpoint 端点名(错误信息定位用)
|
|
34
|
-
* @param rawText 响应体原文
|
|
35
|
-
*/
|
|
36
|
-
export declare function assertIlinkOk(endpoint: string, rawText: string): void;
|
|
37
23
|
/** 取登录二维码(无需凭据;实证裸调可用) */
|
|
38
24
|
export declare function fetchQRCode(params: {
|
|
39
25
|
baseUrl?: string;
|
|
@@ -44,7 +30,7 @@ export declare function fetchQRCode(params: {
|
|
|
44
30
|
qrcode_img_content: string;
|
|
45
31
|
}>;
|
|
46
32
|
/** 扫码状态(轮询;1s 间隔建议,5min 有效) */
|
|
47
|
-
|
|
33
|
+
interface QrStatusResult {
|
|
48
34
|
status: string;
|
|
49
35
|
bot_token?: string;
|
|
50
36
|
ilink_bot_id?: string;
|
|
@@ -58,7 +44,7 @@ export declare function pollQRStatus(params: {
|
|
|
58
44
|
timeoutMs?: number;
|
|
59
45
|
}): Promise<QrStatusResult>;
|
|
60
46
|
/** CDN 媒体元数据(下载侧;协议见 docs/weixin-bot-api.md §6) */
|
|
61
|
-
|
|
47
|
+
interface CDNMedia {
|
|
62
48
|
encrypt_query_param?: string;
|
|
63
49
|
aes_key?: string;
|
|
64
50
|
encrypt_type?: number;
|
|
@@ -116,7 +102,7 @@ export interface WeixinMessage {
|
|
|
116
102
|
item_list?: WeixinMessageItem[];
|
|
117
103
|
context_token?: string;
|
|
118
104
|
}
|
|
119
|
-
|
|
105
|
+
interface GetUpdatesResp {
|
|
120
106
|
ret?: number;
|
|
121
107
|
errcode?: number;
|
|
122
108
|
errmsg?: string;
|
|
@@ -131,24 +117,6 @@ export declare function getUpdates(params: {
|
|
|
131
117
|
getUpdatesBuf?: string;
|
|
132
118
|
timeoutMs?: number;
|
|
133
119
|
}): Promise<GetUpdatesResp>;
|
|
134
|
-
export declare const MessageType: {
|
|
135
|
-
readonly NONE: 0;
|
|
136
|
-
readonly USER: 1;
|
|
137
|
-
readonly BOT: 2;
|
|
138
|
-
};
|
|
139
|
-
export declare const MessageState: {
|
|
140
|
-
readonly NEW: 0;
|
|
141
|
-
readonly GENERATING: 1;
|
|
142
|
-
readonly FINISH: 2;
|
|
143
|
-
};
|
|
144
|
-
export declare const MessageItemType: {
|
|
145
|
-
readonly NONE: 0;
|
|
146
|
-
readonly TEXT: 1;
|
|
147
|
-
readonly IMAGE: 2;
|
|
148
|
-
readonly VOICE: 3;
|
|
149
|
-
readonly FILE: 4;
|
|
150
|
-
readonly VIDEO: 5;
|
|
151
|
-
};
|
|
152
120
|
/** 发文本消息(主动发起时可不带 context_token;md→plain 内置——微信不支持 Markdown)。
|
|
153
121
|
* v1.30.9:HTTP 200 但 `ret/errcode != 0` → 抛错(此前静默当成功,导致"记录已送达"的假象)。 */
|
|
154
122
|
export declare function sendTextMessage(params: {
|
|
@@ -188,7 +156,7 @@ export declare const TypingStatus: {
|
|
|
188
156
|
readonly TYPING: 1;
|
|
189
157
|
readonly CANCEL: 0;
|
|
190
158
|
};
|
|
191
|
-
|
|
159
|
+
interface GetConfigResp {
|
|
192
160
|
ret?: number;
|
|
193
161
|
errmsg?: string;
|
|
194
162
|
typing_ticket?: string;
|
|
@@ -236,3 +204,4 @@ export declare function downloadMedia(params: {
|
|
|
236
204
|
} | null>;
|
|
237
205
|
/** Markdown → 纯文本(代码块/图片/链接/表格/标题/粗斜体/删除线 7 类) */
|
|
238
206
|
export declare function markdownToPlainText(text: string): string;
|
|
207
|
+
export {};
|
package/lib/weixin-bridge.d.ts
CHANGED
|
@@ -17,26 +17,6 @@ import { type WeixinAccountCredential } from './weixin-route.js';
|
|
|
17
17
|
import { type WeixinMessage } from './weixin-api.js';
|
|
18
18
|
/** 测试辅助:清空 typing_ticket 缓存(生产零调用) */
|
|
19
19
|
export declare function resetWeixinTypingCache(): void;
|
|
20
|
-
/**
|
|
21
|
-
* 手动输出模式的**机制标记**(v1.30.10 引入;v1.30.16 按 S142 用户"这个词不能让 ACC 定义,
|
|
22
|
-
* 要让 CCC 定义"重构)。
|
|
23
|
-
*
|
|
24
|
-
* 边界(R↓):ACC 只提供**机制与数据**——"桥不转发最终文本"这个事实 + 三个已填好的参数值
|
|
25
|
-
* (CCC 根 / 账号 / 用户 id)。**纪律措辞归 CCC**:怎么写、必须怎么做、后果是什么,全部写在
|
|
26
|
-
* CCC 的角色提示词文件里(如 `.opencode/skiff/zhaocai.md` 的「输出通道」节),CCC 可随时改,
|
|
27
|
-
* 且 v1.30.15 起角色提示词热重载——改完立即生效,无需 ACC 发版。
|
|
28
|
-
*
|
|
29
|
-
* 为什么不用 ACC 写措辞(历史教训):v1.30.10~v1.30.15 由 ACC 内嵌一段纪律文案,用户实测
|
|
30
|
-
* "约束不够,LLM 不听"——且措辞迭代要动插件代码 + 发版 + 重启;把措辞放 CCC 才能快速迭代,
|
|
31
|
-
* 也符合"ACC 管机制、CCC 管内容"的归属二分。
|
|
32
|
-
*
|
|
33
|
-
* 标记格式(稳定,供 CCC 提示词引用;v1.31.0 起为 ACC 工具形态):
|
|
34
|
-
* ```
|
|
35
|
-
* [serenity:weixin-manual-output]
|
|
36
|
-
* im-bridge({channel:"weixin", action:"send", account:"<account>", user:"<user>", text:"<回复>"})
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare function weixinManualOutputMarker(root: string, accountId: string, userId: string): string;
|
|
40
20
|
/**
|
|
41
21
|
* 手动输出模式的**兜底判定**(纯函数,v1.30.17——把决策矩阵从装配里抽出来,可独立测试)。
|
|
42
22
|
*
|
|
@@ -83,8 +63,8 @@ export declare function stopCccBridge(root: string): void;
|
|
|
83
63
|
*/
|
|
84
64
|
export declare function stopAllBridges(): void;
|
|
85
65
|
/** 主动发送失败原因(稳定 code——入口层翻译成 HTTP 状态 + 可行动提示) */
|
|
86
|
-
|
|
87
|
-
|
|
66
|
+
type ProactiveSendErrorCode = 'BRIDGE_DISABLED' | 'NO_ACCOUNT' | 'ACCOUNT_NOT_FOUND' | 'ACCOUNT_NOT_BOUND' | 'SEND_FAILED';
|
|
67
|
+
interface ProactiveSendInput {
|
|
88
68
|
/** CCC 根(绝对路径;由入口层解析名称/路径后传入) */
|
|
89
69
|
root: string;
|
|
90
70
|
/** 目标用户(iLink from_user_id,形如 xxx@im.wechat;别名解析归调用方) */
|
|
@@ -94,7 +74,7 @@ export interface ProactiveSendInput {
|
|
|
94
74
|
/** 发送账号(缺省 = 该 CCC 第一个启用且已绑定的账号) */
|
|
95
75
|
accountId?: string;
|
|
96
76
|
}
|
|
97
|
-
|
|
77
|
+
type ProactiveSendResult = {
|
|
98
78
|
ok: true;
|
|
99
79
|
accountId: string;
|
|
100
80
|
userId: string;
|
|
@@ -122,7 +102,7 @@ export type ProactiveSendResult = {
|
|
|
122
102
|
* @returns 成功含 accountId/userId/sessionId/role;失败含稳定 code 与可行动提示(不抛错)
|
|
123
103
|
*/
|
|
124
104
|
/** 账号解析结果(文本 / 文件发送共用) */
|
|
125
|
-
|
|
105
|
+
type WeixinAccountResolution = {
|
|
126
106
|
ok: true;
|
|
127
107
|
accountId: string;
|
|
128
108
|
cred: WeixinAccountCredential;
|
|
@@ -154,3 +134,4 @@ export declare function weixinBridgeStatus(): Array<{
|
|
|
154
134
|
* 事件驱动(对齐 autotrajectory v1.26.15):live 会话出现 → 同步该 CCC 桥。
|
|
155
135
|
*/
|
|
156
136
|
export declare function registerWeixinBridge(ctx: Context): void;
|
|
137
|
+
export {};
|
package/lib/weixin-hook.d.ts
CHANGED
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 定位:Mech 纯逻辑(事件构造纯函数 + 执行器确定性),可独立单测。
|
|
15
15
|
*/
|
|
16
|
-
/** hook 执行超时(旁路记录足够;超时 kill 防挂死脚本) */
|
|
17
|
-
export declare const WEIXIN_HOOK_TIMEOUT_MS = 15000;
|
|
18
16
|
/** 媒体引用(hook 事件载荷:kind + 相对 CCC 根路径——CCC 可自行决定是否持久保存媒体) */
|
|
19
17
|
export interface WeixinHookMediaRef {
|
|
20
18
|
kind: 'image' | 'file';
|
|
@@ -54,7 +52,7 @@ export interface WeixinHookIncomingEvent extends WeixinHookEventBase {
|
|
|
54
52
|
* - `proactive` = 桥被调用主动发给指定用户(CCC MSM 经本机入口发起)
|
|
55
53
|
* - `reply-fallback`(v1.30.17)= 手动输出模式下 agent 本轮一次都没发送成功 → 桥兜底转发其最终文本
|
|
56
54
|
*/
|
|
57
|
-
|
|
55
|
+
type WeixinOutgoingSource = 'reply' | 'proactive' | 'reply-fallback';
|
|
58
56
|
/** outgoing 事件(bot → 用户):回复文本(已剥离 think) */
|
|
59
57
|
export interface WeixinHookOutgoingEvent extends WeixinHookEventBase {
|
|
60
58
|
event: 'outgoing';
|
|
@@ -69,11 +67,9 @@ export interface WeixinHookOutgoingEvent extends WeixinHookEventBase {
|
|
|
69
67
|
};
|
|
70
68
|
}
|
|
71
69
|
/** 全部 hook 事件(判别联合) */
|
|
72
|
-
|
|
73
|
-
/** 事件载荷(JSON 序列化:不含任何会话凭据——H5) */
|
|
74
|
-
export type WeixinHookEventJson = WeixinHookEvent;
|
|
70
|
+
type WeixinHookEvent = WeixinHookIncomingEvent | WeixinHookOutgoingEvent;
|
|
75
71
|
/** incoming 事件构造输入(bridge 提供实参) */
|
|
76
|
-
|
|
72
|
+
interface IncomingHookInput {
|
|
77
73
|
cccRoot: string;
|
|
78
74
|
accountId: string;
|
|
79
75
|
userId: string;
|
|
@@ -83,7 +79,7 @@ export interface IncomingHookInput {
|
|
|
83
79
|
media: WeixinHookMediaRef[];
|
|
84
80
|
}
|
|
85
81
|
/** outgoing 事件构造输入(bridge 提供实参) */
|
|
86
|
-
|
|
82
|
+
interface OutgoingHookInput {
|
|
87
83
|
cccRoot: string;
|
|
88
84
|
accountId: string;
|
|
89
85
|
userId: string;
|
|
@@ -104,7 +100,7 @@ export declare function buildIncomingHookEvent(input: IncomingHookInput): Weixin
|
|
|
104
100
|
/** 构造 outgoing 事件对象(纯函数,可测) */
|
|
105
101
|
export declare function buildOutgoingHookEvent(input: OutgoingHookInput): WeixinHookOutgoingEvent;
|
|
106
102
|
/** hook 执行结果(测试/日志用;调用方只关心无异常) */
|
|
107
|
-
|
|
103
|
+
interface WeixinHookRunResult {
|
|
108
104
|
ok: boolean;
|
|
109
105
|
detail?: string;
|
|
110
106
|
}
|
|
@@ -115,10 +111,8 @@ export interface WeixinHookRunResult {
|
|
|
115
111
|
* 脚本缺失 → {ok:false}(配置了但未实现——日志提示;不视为异常)。
|
|
116
112
|
*/
|
|
117
113
|
export declare function runWeixinHook(root: string, hookRel: string, event: WeixinHookEvent, timeoutMs?: number): Promise<WeixinHookRunResult>;
|
|
118
|
-
/** 测试辅助:把 WeixinHookEvent 转单行 JSON(脚本侧读取约定) */
|
|
119
|
-
export declare function hookEventToStdinLine(event: WeixinHookEvent): string;
|
|
120
114
|
/** hook runner 签名(可注入——bridge 集成测试捕获事件防真实 spawn flake) */
|
|
121
|
-
|
|
115
|
+
type WeixinHookRunner = (root: string, hookRel: string, event: WeixinHookEvent) => Promise<WeixinHookRunResult>;
|
|
122
116
|
/** 测试辅助:替换 runner(null → 还原真实 spawn 执行);生产零调用 */
|
|
123
117
|
export declare function setWeixinHookRunnerForTest(runner: WeixinHookRunner | null): void;
|
|
124
118
|
/** bridge 调用入口(转发 activeRunner——默认真实 spawn;测试可注入捕获) */
|
|
@@ -23,7 +23,7 @@ import type { Context } from 'cordis';
|
|
|
23
23
|
/** 打回上限(超过则放弃并告警——避免与模型无限拉扯) */
|
|
24
24
|
export declare const WEIXIN_OUTPUT_REBUKE_MAX = 2;
|
|
25
25
|
/** 手动模式会话上下文(桥登记) */
|
|
26
|
-
|
|
26
|
+
interface ManualOutputSession {
|
|
27
27
|
root: string;
|
|
28
28
|
accountId: string;
|
|
29
29
|
userId: string;
|
|
@@ -61,3 +61,4 @@ export declare function __resetWeixinOutputGuardForTest(): void;
|
|
|
61
61
|
* 只对**已登记的手动模式会话**生效——其他会话(主舱/其他 skiff/ACP 临时会话)零干预。
|
|
62
62
|
*/
|
|
63
63
|
export declare function registerWeixinOutputGuard(ctx: Context): void;
|
|
64
|
+
export {};
|