@shgroup/dsh-serenity-hooks 1.31.13 → 1.32.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 +8 -5
- package/dsh.plugin.json +3 -3
- package/experiments/autopilot-trajectory/SKILL.md +23 -21
- package/lib/autopilot-trajectory.d.ts +15 -5
- package/lib/ccc.d.ts +10 -0
- package/lib/client.js +16 -4
- package/lib/index.js +635 -49
- package/lib/invariant.d.ts +1 -1
- package/lib/invariant.js +1 -1
- package/lib/msm-ops.d.ts +2 -2
- package/lib/{session-bound-BpTayaIS.js → session-bound-NHozeLFc.js} +57 -1
- package/lib/session-bound.d.ts +30 -0
- package/lib/{settings-section-01rTVq6v.js → settings-section-Bm-xG-NR.js} +6 -3
- package/lib/settings-section.d.ts +5 -0
- package/lib/{skiff-debug-w2c3UqQk.js → skiff-debug-K2k1JnGj.js} +1 -1
- package/lib/tools/autopilot-trajectory.d.ts +2 -2
- package/lib/wake-registry.d.ts +146 -0
- package/lib/wake-scheduler.d.ts +76 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$2, f as readHandymanConfig, i as findGitRoot, l as pathInside, m as resolveInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DAsSHsub.js";
|
|
3
3
|
import { a as readSkiffRoles, d as systemPromptSource, i as isSkiffSessionId, l as roleToolWhitelist, o as resolveRoleSystemPrompt } from "./skiff-role-BYvNnwv1.js";
|
|
4
|
-
import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-
|
|
4
|
+
import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-K2k1JnGj.js";
|
|
5
5
|
import { a as hostSettings, c as hostWebServer, i as hostSessions, n as hostInjected, o as hostSubagents, r as hostService, s as hostWeb, t as hostAgents } from "./access-fiehjxV6.js";
|
|
6
6
|
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, g as runLocalStore, h as readStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-wWk4AIwV.js";
|
|
7
|
-
import { C as
|
|
8
|
-
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-
|
|
7
|
+
import { C as showSession, S as setActiveSessionInfo, T as useSession, _ as qaCheck, a as DEFAULT_SESSION_SCOPE, b as sessionEvents, c as clearActiveSessionInfo, d as extractSessionMdPathFromText, f as findSession, g as parseSessionContextFromEvents, h as listSessions, i as resolveSessionTrajectoryLabel, l as closeSession, m as healthCheck, n as listBoundSessionIds, o as SESSION_ACTIONS, p as getActiveSessionInfo, r as readLastBound, s as archiveSessions, t as appendBound, u as createSession, v as readActiveSessionMd, w as summarize, x as sessionsRoot, y as resolveSessionByTitle } from "./session-bound-NHozeLFc.js";
|
|
8
|
+
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-Bm-xG-NR.js";
|
|
9
9
|
import { a as markdownToPlainText, c as sendTyping, i as getUpdates, l as sniffImageExt, n as downloadMedia, o as sendFileMessage, r as getConfig, s as sendTextMessage, t as TypingStatus } from "./weixin-api-PjEhmjlZ.js";
|
|
10
10
|
import z from "@deepseek-ai/schemastery";
|
|
11
11
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -1286,6 +1286,17 @@ const HOST_SERVICES = [
|
|
|
1286
1286
|
impact: "CCC 自动发现回落失效(诊断面受限)",
|
|
1287
1287
|
required: false
|
|
1288
1288
|
},
|
|
1289
|
+
{
|
|
1290
|
+
id: "sessionController",
|
|
1291
|
+
name: "sessionController",
|
|
1292
|
+
access: "lazy",
|
|
1293
|
+
members: [{
|
|
1294
|
+
name: "resolveAgent",
|
|
1295
|
+
kind: "function"
|
|
1296
|
+
}],
|
|
1297
|
+
impact: "trajectory 冷会话唤醒不可用(只能唤醒已加载的 live 会话)——唤醒注册表投递降级",
|
|
1298
|
+
required: false
|
|
1299
|
+
},
|
|
1289
1300
|
{
|
|
1290
1301
|
id: "connection",
|
|
1291
1302
|
name: "connection",
|
|
@@ -1980,9 +1991,9 @@ Directory of ACC capabilities — each area lists where to go for details (guide
|
|
|
1980
1991
|
handyman(杂工 agent 编排——guide 子命令:handyman guide)
|
|
1981
1992
|
④ 角色与对外面 → container_admin role(Skiff 认知子集角色:guide/validate/apply/list——skiff_admin 已并入)
|
|
1982
1993
|
ACP(程序化 JSON-RPC 3100)/ Skiff 问答页(公网 ask)——面板「外部能力」组
|
|
1983
|
-
⑤ 自主与接入 →
|
|
1994
|
+
⑤ 自主与接入 → trajectory(轨迹一站式:all/init/random/diag/doc/check/status/guide + 唤醒注册表 wake-add/wake-list/wake-rm)
|
|
1984
1995
|
weixin 微信桥(配置/扫码/路由/消息 hook——CCC 侧 weixin-doctor MSM:msm("weixin-doctor", ["guide"]))
|
|
1985
|
-
⑥ CCC 配置总览 → container_admin msm ccc-config(8 段:handyman/sessionKeeper/localstore/hooks/safeMode/skiff/
|
|
1996
|
+
⑥ CCC 配置总览 → container_admin msm ccc-config(8 段:handyman/sessionKeeper/localstore/hooks/safeMode/skiff/trajectory/weixin)
|
|
1986
1997
|
⑦ 注册表与安全 → mech-registry.json 由 container_admin msm register/deregister 管理(写保护——不可直接编辑)
|
|
1987
1998
|
dashboard health 输出 registry 完整性检查;损坏恢复指引见 health registry.issues
|
|
1988
1999
|
|
|
@@ -2120,22 +2131,26 @@ validate 校验 / apply 生效 / list 查看:container_admin role <guide|valid
|
|
|
2120
2131
|
"systemPromptFile": ".opencode/skiff/qa.md"
|
|
2121
2132
|
} } } }
|
|
2122
2133
|
|
|
2123
|
-
── 7.
|
|
2134
|
+
── 7. trajectory(轨迹:autopilot 自主巡航 + 唤醒注册表,D58)──
|
|
2124
2135
|
CCC 定义的一条自主 trajectory——时钟到点自动唤起(前台注入,用户可见可介入)。
|
|
2136
|
+
**autopilot 是 trajectory 的子集**:周期自唤醒的特例;trajectory 本身是一等概念(一个 CCC 内可多条并行)。
|
|
2125
2137
|
未配置或 enabled=false → 完全不启动(零资源占用)。多 CCC 独立:每 CCC 自己的配置。
|
|
2126
2138
|
唤起消息四段式:轨迹焦点 topPrompt(最先注入,稳定锚)→ 身份锚定 → 先验偏见
|
|
2127
2139
|
(CCC 根脚本 biasProvider 输出)→ 任务。目标会话 session 必填(目录须带 --auto 后缀)。
|
|
2128
|
-
诊断/状态/立即唤起:
|
|
2140
|
+
诊断/状态/立即唤起:trajectory <all|check|status|diag>(trajectory({action:"all"}))。
|
|
2141
|
+
唤醒注册表(wake-add/wake-list/wake-rm):唤醒 = 未来时刻 + 一条 message,可唤醒自己或别的 trajectory;
|
|
2142
|
+
落点 AGENT_SESSIONS/wake-registry.json,中心调度器到点投递(fire-and-forget)。
|
|
2129
2143
|
|
|
2130
2144
|
Config:
|
|
2131
|
-
{ "
|
|
2132
|
-
"
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2145
|
+
{ "trajectory": {
|
|
2146
|
+
"autopilot": { // 原 autopilotTrajectory 段,字段不变(旧键仍回退读)
|
|
2147
|
+
"enabled": true,
|
|
2148
|
+
"intervalHours": 2,
|
|
2149
|
+
"session": "S151",
|
|
2150
|
+
"biasProvider": "autopilot-bias.ts",
|
|
2151
|
+
"topPrompt": "本轨迹核心目标/纪律/质量要求(CCC 自填,防漂移)",
|
|
2152
|
+
"avoidWakeHours": { "start": 8, "end": 18 }
|
|
2153
|
+
} } }
|
|
2139
2154
|
|
|
2140
2155
|
── 8. weixin(微信桥 F4c-3,含消息记录 hook)──
|
|
2141
2156
|
CCC 级微信个人号接入(iLink 协议):dsh 一进程多 CCC,每 CCC 独立对接微信桥。
|
|
@@ -4137,6 +4152,7 @@ var autopilot_trajectory_exports = /* @__PURE__ */ __exportAll({
|
|
|
4137
4152
|
beijingHour: () => beijingHour,
|
|
4138
4153
|
buildWakeMessage: () => buildWakeMessage,
|
|
4139
4154
|
collectAutopilotCccs: () => collectAutopilotCccs,
|
|
4155
|
+
collectLiveCccs: () => collectLiveCccs,
|
|
4140
4156
|
diagLive: () => diagLive,
|
|
4141
4157
|
fetchBiasContent: () => fetchBiasContent,
|
|
4142
4158
|
getAutopilotStatus: () => getAutopilotStatus,
|
|
@@ -4152,7 +4168,7 @@ var autopilot_trajectory_exports = /* @__PURE__ */ __exportAll({
|
|
|
4152
4168
|
shouldWake: () => shouldWake,
|
|
4153
4169
|
wakeHistoryFor: () => wakeHistoryFor
|
|
4154
4170
|
});
|
|
4155
|
-
const PLUGIN_SOURCE$
|
|
4171
|
+
const PLUGIN_SOURCE$6 = {
|
|
4156
4172
|
kind: "plugin",
|
|
4157
4173
|
plugin: "dsh-serenity-hooks"
|
|
4158
4174
|
};
|
|
@@ -4177,10 +4193,10 @@ const DEFAULT_AVOID_HOURS = {
|
|
|
4177
4193
|
const BIAS_OUTPUT_MAX = 8192;
|
|
4178
4194
|
/** 偏见脚本执行超时(正式版沙箱;原 600s 过长阻塞 tick) */
|
|
4179
4195
|
const BIAS_RUN_TIMEOUT_MS = 6e4;
|
|
4180
|
-
/**
|
|
4196
|
+
/** 读配置:`trajectory.autopilot`(D58 新键)优先 → 旧键 autopilotTrajectory → 旧键 autotrajectory */
|
|
4181
4197
|
function readAutopilotSettings(root) {
|
|
4182
4198
|
const cfg = loadSerenityConfig(root);
|
|
4183
|
-
return cfg.autopilotTrajectory ?? cfg.autotrajectory ?? null;
|
|
4199
|
+
return cfg.trajectory?.autopilot ?? cfg.autopilotTrajectory ?? cfg.autotrajectory ?? null;
|
|
4184
4200
|
}
|
|
4185
4201
|
/**
|
|
4186
4202
|
* 北京时间(UTC+8)当前小时——不依赖服务器时区(服务器可能 UTC/本地任意)。
|
|
@@ -4266,7 +4282,7 @@ async function fetchBiasContent(root, providerRel) {
|
|
|
4266
4282
|
}
|
|
4267
4283
|
if (!existsSync(scriptAbs)) return {
|
|
4268
4284
|
text: null,
|
|
4269
|
-
error: `请在 CCC 根目录实现偏见内容提供者脚本: ${providerRel}(或旧默认 ${LEGACY_BIAS_PROVIDER};stdout 输出偏见内容一行;
|
|
4285
|
+
error: `请在 CCC 根目录实现偏见内容提供者脚本: ${providerRel}(或旧默认 ${LEGACY_BIAS_PROVIDER};stdout 输出偏见内容一行;trajectory init 可生成模板)`
|
|
4270
4286
|
};
|
|
4271
4287
|
const runs = [["bun", [scriptAbs]], [process.execPath, [scriptAbs]]];
|
|
4272
4288
|
for (const [cmd, args] of runs) try {
|
|
@@ -4390,7 +4406,7 @@ async function performAutopilotWake(ctx, root, settings, opts = {}) {
|
|
|
4390
4406
|
type: "text",
|
|
4391
4407
|
text: message
|
|
4392
4408
|
}],
|
|
4393
|
-
source: PLUGIN_SOURCE$
|
|
4409
|
+
source: PLUGIN_SOURCE$6
|
|
4394
4410
|
}));
|
|
4395
4411
|
return {
|
|
4396
4412
|
ok: true,
|
|
@@ -4674,6 +4690,20 @@ function collectAutopilotCccs(ctx, opts = {}) {
|
|
|
4674
4690
|
}
|
|
4675
4691
|
return opts.includeDisabled ? [...enabled, ...configured] : enabled;
|
|
4676
4692
|
}
|
|
4693
|
+
/**
|
|
4694
|
+
* 所有 live 会话所属的 CCC 根(去重,保持出现顺序)。
|
|
4695
|
+
*
|
|
4696
|
+
* 与 {@link collectAutopilotCccs} 的区别:**不要求该 CCC 配置 trajectory.autopilot**——
|
|
4697
|
+
* 唤醒注册表(wake-scheduler)的条目可以指向任何 CCC 的任何 trajectory。
|
|
4698
|
+
*
|
|
4699
|
+
* @param ctx 插件上下文
|
|
4700
|
+
* @returns CCC 根路径列表(无 live 会话 → `[]`)
|
|
4701
|
+
*/
|
|
4702
|
+
function collectLiveCccs(ctx) {
|
|
4703
|
+
const roots = [];
|
|
4704
|
+
for (const s of listLiveSessions(ctx)) if (s.cccRoot && !roots.includes(s.cccRoot)) roots.push(s.cccRoot);
|
|
4705
|
+
return roots;
|
|
4706
|
+
}
|
|
4677
4707
|
function diagLive(ctx) {
|
|
4678
4708
|
const liveSessions = listLiveSessions(ctx);
|
|
4679
4709
|
const processCwd = process.cwd();
|
|
@@ -4708,6 +4738,305 @@ function diagLive(ctx) {
|
|
|
4708
4738
|
};
|
|
4709
4739
|
}
|
|
4710
4740
|
//#endregion
|
|
4741
|
+
//#region src/wake-registry.ts
|
|
4742
|
+
/**
|
|
4743
|
+
* wake-registry.ts — trajectory 唤醒注册表(D58,S142 2026-09-13)
|
|
4744
|
+
*
|
|
4745
|
+
* 需求(用户原话):一个 CCC 内允许多个 trajectory 任意形态并行;某个 trajectory 可以
|
|
4746
|
+
* **预定未来唤醒自己或别人**,本质上是一次「未来时刻 + 一条 message」的投递;autopilot
|
|
4747
|
+
* 是「周期自唤醒」的特例(独立循环,D59)。协作边界:互相可见、**无阻塞无等待**。
|
|
4748
|
+
*
|
|
4749
|
+
* 本模块只负责**注册表本身**(纯数据层,无宿主依赖,可单测):
|
|
4750
|
+
* - 落点:CCC 内 `AGENT_SESSIONS/wake-registry.json`(随 CCC git,人可读可审计)
|
|
4751
|
+
* - 状态机:`pending` →(投递成功)`delivered`|(超补跑窗口)`missed`|(`wake rm` 物理移除)
|
|
4752
|
+
* - 投递即终结:一次性,不重投(无回执语义——发起方不 await,见设计文档 §2)
|
|
4753
|
+
*
|
|
4754
|
+
* 调度与投递在 `wake-scheduler.ts`(需要 ctx),本模块保持纯净。
|
|
4755
|
+
*/
|
|
4756
|
+
var wake_registry_exports = /* @__PURE__ */ __exportAll({
|
|
4757
|
+
WAKE_CATCH_UP_MS: () => WAKE_CATCH_UP_MS,
|
|
4758
|
+
WAKE_REGISTRY_REL_PATH: () => WAKE_REGISTRY_REL_PATH,
|
|
4759
|
+
WAKE_REGISTRY_VERSION: () => 1,
|
|
4760
|
+
addWake: () => addWake,
|
|
4761
|
+
listWakes: () => listWakes,
|
|
4762
|
+
loadWakeRegistry: () => loadWakeRegistry,
|
|
4763
|
+
newWakeId: () => newWakeId,
|
|
4764
|
+
parseWakeAt: () => parseWakeAt,
|
|
4765
|
+
removeWake: () => removeWake,
|
|
4766
|
+
saveWakeRegistry: () => saveWakeRegistry,
|
|
4767
|
+
splitDueWakes: () => splitDueWakes,
|
|
4768
|
+
updateWake: () => updateWake,
|
|
4769
|
+
wakeRegistryPath: () => wakeRegistryPath
|
|
4770
|
+
});
|
|
4771
|
+
/** 注册表相对 CCC 根的路径(与 `.bindings.json` 同居 AGENT_SESSIONS/) */
|
|
4772
|
+
const WAKE_REGISTRY_REL_PATH = "AGENT_SESSIONS/wake-registry.json";
|
|
4773
|
+
/**
|
|
4774
|
+
* 补跑窗口(缺省 2h):dsh 停机期间到期的条目,若迟到不超过本窗口则补投,
|
|
4775
|
+
* 超过则置 `missed` 留痕(不补投——避免"半夜该做的事早上突然连环跑")。
|
|
4776
|
+
*/
|
|
4777
|
+
const WAKE_CATCH_UP_MS = 72e5;
|
|
4778
|
+
/** 注册表绝对路径 */
|
|
4779
|
+
function wakeRegistryPath(root) {
|
|
4780
|
+
return join(root, WAKE_REGISTRY_REL_PATH);
|
|
4781
|
+
}
|
|
4782
|
+
/** 空注册表 */
|
|
4783
|
+
function emptyRegistry() {
|
|
4784
|
+
return {
|
|
4785
|
+
version: 1,
|
|
4786
|
+
entries: []
|
|
4787
|
+
};
|
|
4788
|
+
}
|
|
4789
|
+
/** 条目形状校验(容忍手改:字段缺失/类型不符 → 丢弃该条,不阻断整表) */
|
|
4790
|
+
function asEntry(value) {
|
|
4791
|
+
const e = value;
|
|
4792
|
+
if (!e || typeof e.id !== "string" || typeof e.target !== "string") return null;
|
|
4793
|
+
if (typeof e.at !== "string" || typeof e.message !== "string") return null;
|
|
4794
|
+
const state = e.state === "delivered" || e.state === "missed" || e.state === "cancelled" ? e.state : "pending";
|
|
4795
|
+
return {
|
|
4796
|
+
id: e.id,
|
|
4797
|
+
target: e.target,
|
|
4798
|
+
at: e.at,
|
|
4799
|
+
message: e.message,
|
|
4800
|
+
state,
|
|
4801
|
+
createdBy: typeof e.createdBy === "string" ? e.createdBy : "",
|
|
4802
|
+
createdAt: typeof e.createdAt === "string" ? e.createdAt : "",
|
|
4803
|
+
attempts: typeof e.attempts === "number" && Number.isFinite(e.attempts) ? e.attempts : 0,
|
|
4804
|
+
lastResult: typeof e.lastResult === "string" ? e.lastResult : null,
|
|
4805
|
+
deliveredAt: typeof e.deliveredAt === "string" ? e.deliveredAt : null
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4808
|
+
/**
|
|
4809
|
+
* 读注册表。文件缺失 → 空表;损坏 → 空表 + error(不抛错,调用方决定是否告警)。
|
|
4810
|
+
* @param root CCC 根
|
|
4811
|
+
* @returns 注册表与读取错误(无错为 null)
|
|
4812
|
+
*/
|
|
4813
|
+
function loadWakeRegistry(root) {
|
|
4814
|
+
const path = wakeRegistryPath(root);
|
|
4815
|
+
if (!existsSync(path)) return {
|
|
4816
|
+
registry: emptyRegistry(),
|
|
4817
|
+
error: null
|
|
4818
|
+
};
|
|
4819
|
+
try {
|
|
4820
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
4821
|
+
const raw = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
4822
|
+
const entries = [];
|
|
4823
|
+
for (const item of raw) {
|
|
4824
|
+
const e = asEntry(item);
|
|
4825
|
+
if (e) entries.push(e);
|
|
4826
|
+
}
|
|
4827
|
+
return {
|
|
4828
|
+
registry: {
|
|
4829
|
+
version: 1,
|
|
4830
|
+
entries
|
|
4831
|
+
},
|
|
4832
|
+
error: null
|
|
4833
|
+
};
|
|
4834
|
+
} catch (err) {
|
|
4835
|
+
return {
|
|
4836
|
+
registry: emptyRegistry(),
|
|
4837
|
+
error: `唤醒注册表解析失败(${String(err?.message ?? err)})`
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
/** 原子写(tmp + rename;与 .bindings.json 同款形态) */
|
|
4842
|
+
function saveWakeRegistry(root, registry) {
|
|
4843
|
+
const path = wakeRegistryPath(root);
|
|
4844
|
+
try {
|
|
4845
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
4846
|
+
const tmp = `${path}.tmp`;
|
|
4847
|
+
writeFileSync(tmp, `${JSON.stringify({
|
|
4848
|
+
version: 1,
|
|
4849
|
+
entries: registry.entries
|
|
4850
|
+
}, null, 2)}\n`, "utf-8");
|
|
4851
|
+
renameSync(tmp, path);
|
|
4852
|
+
return {
|
|
4853
|
+
ok: true,
|
|
4854
|
+
error: null
|
|
4855
|
+
};
|
|
4856
|
+
} catch (err) {
|
|
4857
|
+
return {
|
|
4858
|
+
ok: false,
|
|
4859
|
+
error: `唤醒注册表写入失败(${String(err?.message ?? err)})`
|
|
4860
|
+
};
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
/** 条目 id:`w-<UTC 到分钟>-<4 hex>`(可读 + 低碰撞;不复用由"物理移除"保证) */
|
|
4864
|
+
function newWakeId(nowMs, rand = Math.random) {
|
|
4865
|
+
const iso = new Date(nowMs).toISOString();
|
|
4866
|
+
return `w-${`${iso.slice(0, 10).replace(/-/g, "")}-${iso.slice(11, 16).replace(":", "")}`}-${Math.floor(rand() * 65536).toString(16).padStart(4, "0")}`;
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* 解析唤醒时刻。接受两种写法(设计文档 §7):
|
|
4870
|
+
* - 绝对:RFC3339(含时区偏移,如 `2026-09-14T09:00:00+08:00`)
|
|
4871
|
+
* - 相对:`+30m` / `+2h`(便于 agent 直接登记"半小时后")
|
|
4872
|
+
* @param input 用户/agent 传入的时刻串
|
|
4873
|
+
* @param nowMs 当前时刻(相对写法基准)
|
|
4874
|
+
* @returns `{ ok, ms }` 或 `{ ok: false, error }`
|
|
4875
|
+
*/
|
|
4876
|
+
function parseWakeAt(input, nowMs) {
|
|
4877
|
+
const raw = input.trim();
|
|
4878
|
+
if (raw === "") return {
|
|
4879
|
+
ok: false,
|
|
4880
|
+
error: "时刻为空"
|
|
4881
|
+
};
|
|
4882
|
+
const rel = /^\+(\d+(?:\.\d+)?)([mhd])$/.exec(raw);
|
|
4883
|
+
if (rel) {
|
|
4884
|
+
const n = Number(rel[1]);
|
|
4885
|
+
const unit = rel[2];
|
|
4886
|
+
return {
|
|
4887
|
+
ok: true,
|
|
4888
|
+
ms: nowMs + n * (unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5)
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
const ms = Date.parse(raw);
|
|
4892
|
+
if (!Number.isFinite(ms)) return {
|
|
4893
|
+
ok: false,
|
|
4894
|
+
error: `时刻无法解析(须 RFC3339 或 +30m/+2h): ${raw}`
|
|
4895
|
+
};
|
|
4896
|
+
return {
|
|
4897
|
+
ok: true,
|
|
4898
|
+
ms
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
/**
|
|
4902
|
+
* 登记一条唤醒。
|
|
4903
|
+
* @param root CCC 根
|
|
4904
|
+
* @param input 目标 trajectory / 时刻串 / message / 发起者 / 当前时刻
|
|
4905
|
+
* @returns 成功返回条目,失败返回稳定错误文案(不抛错)
|
|
4906
|
+
*/
|
|
4907
|
+
function addWake(root, input) {
|
|
4908
|
+
const target = input.target.trim();
|
|
4909
|
+
if (target === "") return {
|
|
4910
|
+
ok: false,
|
|
4911
|
+
error: "target 为空(须 S### 或 AGENT_SESSIONS 目录名)"
|
|
4912
|
+
};
|
|
4913
|
+
const message = input.message.trim();
|
|
4914
|
+
if (message === "") return {
|
|
4915
|
+
ok: false,
|
|
4916
|
+
error: "message 为空(唤醒必须携带一条信息)"
|
|
4917
|
+
};
|
|
4918
|
+
const parsed = parseWakeAt(input.at, input.nowMs);
|
|
4919
|
+
if (!parsed.ok) return {
|
|
4920
|
+
ok: false,
|
|
4921
|
+
error: parsed.error
|
|
4922
|
+
};
|
|
4923
|
+
if (parsed.ms <= input.nowMs) return {
|
|
4924
|
+
ok: false,
|
|
4925
|
+
error: "时刻必须在未来(唤醒只允许可预期的未来)"
|
|
4926
|
+
};
|
|
4927
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
4928
|
+
if (error) return {
|
|
4929
|
+
ok: false,
|
|
4930
|
+
error
|
|
4931
|
+
};
|
|
4932
|
+
const entry = {
|
|
4933
|
+
id: newWakeId(input.nowMs),
|
|
4934
|
+
target,
|
|
4935
|
+
at: new Date(parsed.ms).toISOString(),
|
|
4936
|
+
message,
|
|
4937
|
+
state: "pending",
|
|
4938
|
+
createdBy: input.createdBy,
|
|
4939
|
+
createdAt: new Date(input.nowMs).toISOString(),
|
|
4940
|
+
attempts: 0,
|
|
4941
|
+
lastResult: null,
|
|
4942
|
+
deliveredAt: null
|
|
4943
|
+
};
|
|
4944
|
+
registry.entries.push(entry);
|
|
4945
|
+
const saved = saveWakeRegistry(root, registry);
|
|
4946
|
+
if (!saved.ok) return {
|
|
4947
|
+
ok: false,
|
|
4948
|
+
error: saved.error ?? "写入失败"
|
|
4949
|
+
};
|
|
4950
|
+
return {
|
|
4951
|
+
ok: true,
|
|
4952
|
+
entry
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
/**
|
|
4956
|
+
* 物理移除一条(`wake rm`)。审计由 SESSION.md 与工具输出承担——注册表只保留在办项。
|
|
4957
|
+
* @param root CCC 根
|
|
4958
|
+
* @param id 条目 id
|
|
4959
|
+
* @returns 移除结果(不存在 → 稳定错误码文案)
|
|
4960
|
+
*/
|
|
4961
|
+
function removeWake(root, id) {
|
|
4962
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
4963
|
+
if (error) return {
|
|
4964
|
+
ok: false,
|
|
4965
|
+
error,
|
|
4966
|
+
removed: null
|
|
4967
|
+
};
|
|
4968
|
+
const idx = registry.entries.findIndex((e) => e.id === id);
|
|
4969
|
+
if (idx < 0) return {
|
|
4970
|
+
ok: false,
|
|
4971
|
+
error: `wake_not_found: ${id}`,
|
|
4972
|
+
removed: null
|
|
4973
|
+
};
|
|
4974
|
+
const [removed] = registry.entries.splice(idx, 1);
|
|
4975
|
+
const saved = saveWakeRegistry(root, registry);
|
|
4976
|
+
if (!saved.ok) return {
|
|
4977
|
+
ok: false,
|
|
4978
|
+
error: saved.error,
|
|
4979
|
+
removed: null
|
|
4980
|
+
};
|
|
4981
|
+
return {
|
|
4982
|
+
ok: true,
|
|
4983
|
+
error: null,
|
|
4984
|
+
removed: removed ?? null
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
/**
|
|
4988
|
+
* 到期切分(纯函数,可单测):
|
|
4989
|
+
* - `due`:`state === 'pending'` 且 `now >= at`(在补跑窗口内)→ 应投递
|
|
4990
|
+
* - `expired`:`state === 'pending'` 且 `now - at > catchUpMs` → 应置 missed(不投递)
|
|
4991
|
+
* @param registry 注册表
|
|
4992
|
+
* @param nowMs 当前时刻
|
|
4993
|
+
* @param catchUpMs 补跑窗口
|
|
4994
|
+
*/
|
|
4995
|
+
function splitDueWakes(registry, nowMs, catchUpMs = WAKE_CATCH_UP_MS) {
|
|
4996
|
+
const due = [];
|
|
4997
|
+
const expired = [];
|
|
4998
|
+
for (const e of registry.entries) {
|
|
4999
|
+
if (e.state !== "pending") continue;
|
|
5000
|
+
const at = Date.parse(e.at);
|
|
5001
|
+
if (!Number.isFinite(at) || nowMs < at) continue;
|
|
5002
|
+
if (nowMs - at > catchUpMs) expired.push(e);
|
|
5003
|
+
else due.push(e);
|
|
5004
|
+
}
|
|
5005
|
+
return {
|
|
5006
|
+
due,
|
|
5007
|
+
expired
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
/**
|
|
5011
|
+
* 更新一条(状态推进;调用方给最终状态与结果文案)。
|
|
5012
|
+
* @param root CCC 根
|
|
5013
|
+
* @param id 条目 id
|
|
5014
|
+
* @param patch 要写入的字段
|
|
5015
|
+
* @returns 写入结果
|
|
5016
|
+
*/
|
|
5017
|
+
function updateWake(root, id, patch) {
|
|
5018
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
5019
|
+
if (error) return {
|
|
5020
|
+
ok: false,
|
|
5021
|
+
error
|
|
5022
|
+
};
|
|
5023
|
+
const entry = registry.entries.find((e) => e.id === id);
|
|
5024
|
+
if (!entry) return {
|
|
5025
|
+
ok: false,
|
|
5026
|
+
error: `wake_not_found: ${id}`
|
|
5027
|
+
};
|
|
5028
|
+
Object.assign(entry, patch);
|
|
5029
|
+
return saveWakeRegistry(root, registry);
|
|
5030
|
+
}
|
|
5031
|
+
/** 列条目(默认全量;`pendingOnly` 只列在办项——面板/工具用) */
|
|
5032
|
+
function listWakes(root, opts = {}) {
|
|
5033
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
5034
|
+
return {
|
|
5035
|
+
entries: [...opts.pendingOnly ? registry.entries.filter((e) => e.state === "pending") : registry.entries].sort((a, b) => Date.parse(a.at) - Date.parse(b.at)),
|
|
5036
|
+
error
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
//#endregion
|
|
4711
5040
|
//#region src/tools/autopilot-trajectory.ts
|
|
4712
5041
|
/**
|
|
4713
5042
|
* autopilot-trajectory.ts — Autopilot Trajectory 一站式管理工具(v1.26.12 实验
|
|
@@ -4749,8 +5078,31 @@ const AUTOPILOT_ACTIONS = [
|
|
|
4749
5078
|
"check",
|
|
4750
5079
|
"status",
|
|
4751
5080
|
"guide",
|
|
4752
|
-
"diag-live"
|
|
5081
|
+
"diag-live",
|
|
5082
|
+
"wake-add",
|
|
5083
|
+
"wake-list",
|
|
5084
|
+
"wake-rm"
|
|
4753
5085
|
];
|
|
5086
|
+
/** 进程内处理的动作(不 exec 包内脚本):diag-live 诊断 + 唤醒注册表三动作 */
|
|
5087
|
+
const NATIVE_ACTIONS = /* @__PURE__ */ new Set([
|
|
5088
|
+
"diag-live",
|
|
5089
|
+
"wake-add",
|
|
5090
|
+
"wake-list",
|
|
5091
|
+
"wake-rm"
|
|
5092
|
+
]);
|
|
5093
|
+
/** 渲染唤醒条目(工具输出/面板共用形态;按 at 升序) */
|
|
5094
|
+
function renderWakeList(entries, error) {
|
|
5095
|
+
const lines = [`═══ trajectory 唤醒注册表(${entries.length} 条)═══`];
|
|
5096
|
+
if (error) lines.push(`⚠ ${error}`);
|
|
5097
|
+
if (entries.length === 0) lines.push(" (无条目——用 wake-add 登记:唤醒 = 未来时刻 + 一条 message)");
|
|
5098
|
+
for (const e of entries) {
|
|
5099
|
+
lines.push(` · ${e.id} [${e.state}] at=${e.at} target=${e.target} by=${e.createdBy || "?"}`);
|
|
5100
|
+
lines.push(` message: ${e.message.length > 120 ? `${e.message.slice(0, 120)}…` : e.message}`);
|
|
5101
|
+
if (e.lastResult) lines.push(` last: ${e.lastResult}(attempts=${e.attempts})`);
|
|
5102
|
+
}
|
|
5103
|
+
lines.push("", `补跑窗口 ${WAKE_CATCH_UP_MS / 36e5}h:停机期间到期且迟到未超窗 → 补投;超窗 → missed 留痕。`);
|
|
5104
|
+
return lines.join("\n");
|
|
5105
|
+
}
|
|
4754
5106
|
function agentCwd$2(exec) {
|
|
4755
5107
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
4756
5108
|
}
|
|
@@ -4780,32 +5132,71 @@ function renderDiagLive(r) {
|
|
|
4780
5132
|
}
|
|
4781
5133
|
return lines.join("\n");
|
|
4782
5134
|
}
|
|
4783
|
-
/** 创建
|
|
5135
|
+
/** 创建 trajectory 工具(原 autopilot-trajectory;D58 更名 + 唤醒注册表三动作) */
|
|
4784
5136
|
function createAutopilotTool(ctx) {
|
|
4785
5137
|
return defineTool({
|
|
4786
|
-
name: "
|
|
4787
|
-
description: "
|
|
4788
|
-
parameters: {
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
5138
|
+
name: "trajectory",
|
|
5139
|
+
description: "trajectory 一站式管理(D58:trajectory 是一等概念,**autopilot 是其子集**——周期自唤醒的特例)。无参/action=all:全报告(背景摘要 + 就绪检查 + 状态 + 下一步)——CCC agent 看一次即完整理解并知道怎么开始;init:初始化辅助(写配置 + 生成偏见提供者脚本模板);random:运行偏见提供者脚本输出当前偏见内容;diag:唤起条件链诊断(--ccc <path> 指定 CCC,无参递归扫描 /home/yh 两层;逐条件输出 + 阻断点 + 修复建议);diag-live:**进程内诊断**(live 会话清单/标题/agent 定位/面板解析目标);doc:定义全文;check/status/guide:单项。**唤醒注册表(wake-add/wake-list/wake-rm)**:唤醒 = **未来时刻 + 一条 message**——可对**自己**预定未来唤醒,也可唤醒**别人**(任一 trajectory);落点 CCC 内 `AGENT_SESSIONS/wake-registry.json`;到点由中心调度器投递(冷会话自动载入),**fire-and-forget:无回执、无阻塞、不等待**。wake-add 参数:target(S### 或目录名)/ at(RFC3339 或 `+30m`/`+2h`)/ message;wake-rm 参数:id。**topPrompt(轨迹焦点)**:CCC 定义时自己填写本轨迹核心焦点(顶层提示词),每次唤起最先注入——稳定焦点锚定防漂移。多 CCC 独立:每个 live CCC 各自评估。机制是 CCC 的自选动作——dsp 只提供工具与知识,不自动安装任何东西。",
|
|
5140
|
+
parameters: {
|
|
5141
|
+
action: {
|
|
5142
|
+
type: "string",
|
|
5143
|
+
enum: [...AUTOPILOT_ACTIONS],
|
|
5144
|
+
required: true,
|
|
5145
|
+
description: "Subcommand"
|
|
5146
|
+
},
|
|
5147
|
+
target: {
|
|
5148
|
+
type: "string",
|
|
5149
|
+
description: "wake-add:目标 trajectory(S### 或 AGENT_SESSIONS 目录名)"
|
|
5150
|
+
},
|
|
5151
|
+
at: {
|
|
5152
|
+
type: "string",
|
|
5153
|
+
description: "wake-add:未来时刻(RFC3339 含时区,或 +30m / +2h 相对写法)"
|
|
5154
|
+
},
|
|
5155
|
+
message: {
|
|
5156
|
+
type: "string",
|
|
5157
|
+
description: "wake-add:唤醒 message(投递给目标 trajectory 的正文)"
|
|
5158
|
+
},
|
|
5159
|
+
id: {
|
|
5160
|
+
type: "string",
|
|
5161
|
+
description: "wake-rm:条目 id(wake-list 可见)"
|
|
5162
|
+
}
|
|
5163
|
+
},
|
|
4794
5164
|
output: {
|
|
4795
5165
|
schema: { type: "json" },
|
|
4796
5166
|
render: (_args, value) => renderText$1(value)
|
|
4797
5167
|
},
|
|
4798
5168
|
async execute(args, exec) {
|
|
4799
|
-
if (args.action === "diag-live") return { output: renderDiagLive(diagLive(ctx)) };
|
|
4800
5169
|
const root = findSerenityRoot(agentCwd$2(exec));
|
|
5170
|
+
if (args.action === "diag-live") return { output: renderDiagLive(diagLive(ctx)) };
|
|
5171
|
+
if (NATIVE_ACTIONS.has(args.action ?? "")) {
|
|
5172
|
+
if (!root) return { output: "⚠ 未定位到 CCC 根(.serenity)——唤醒注册表按 CCC 存放,请在 CCC 内调用" };
|
|
5173
|
+
if (args.action === "wake-list") {
|
|
5174
|
+
const { entries, error } = listWakes(root);
|
|
5175
|
+
return { output: renderWakeList(entries, error) };
|
|
5176
|
+
}
|
|
5177
|
+
if (args.action === "wake-add") {
|
|
5178
|
+
const scope = (exec.agent?.session)?.header?.id ?? "";
|
|
5179
|
+
const res = addWake(root, {
|
|
5180
|
+
target: args.target ?? "",
|
|
5181
|
+
at: args.at ?? "",
|
|
5182
|
+
message: args.message ?? "",
|
|
5183
|
+
createdBy: resolveSessionTrajectoryLabel(exec.agent?.session, scope),
|
|
5184
|
+
nowMs: Date.now()
|
|
5185
|
+
});
|
|
5186
|
+
if (!res.ok) return { output: `✗ 登记失败:${res.error}` };
|
|
5187
|
+
return { output: `✓ 已登记唤醒\n${renderWakeList([res.entry], null)}` };
|
|
5188
|
+
}
|
|
5189
|
+
const removed = removeWake(root, args.id ?? "");
|
|
5190
|
+
return { output: removed.ok ? `✓ 已移除唤醒条目 ${args.id}` : `✗ 移除失败:${removed.error}` };
|
|
5191
|
+
}
|
|
4801
5192
|
const result = {};
|
|
4802
5193
|
if (!EXP_SCRIPT) {
|
|
4803
|
-
result.error = "
|
|
5194
|
+
result.error = "trajectory 脚本未随安装分发(npm 包缺 experiments/autopilot-trajectory/)——请检查包完整性";
|
|
4804
5195
|
return result;
|
|
4805
5196
|
}
|
|
4806
5197
|
const script = EXP_SCRIPT;
|
|
4807
5198
|
if (!existsSync(script)) {
|
|
4808
|
-
result.error = `
|
|
5199
|
+
result.error = `trajectory 脚本缺失(${script})——包未随安装分发,请检查 npm 包完整性`;
|
|
4809
5200
|
return result;
|
|
4810
5201
|
}
|
|
4811
5202
|
const env = { ...process.env };
|
|
@@ -4825,7 +5216,7 @@ function createAutopilotTool(ctx) {
|
|
|
4825
5216
|
return result;
|
|
4826
5217
|
}
|
|
4827
5218
|
if (r.error?.code === "ENOENT") {
|
|
4828
|
-
result.error = "
|
|
5219
|
+
result.error = "trajectory 需要 bun 运行时(bun not found in PATH)";
|
|
4829
5220
|
return result;
|
|
4830
5221
|
}
|
|
4831
5222
|
result.error = r.stderr?.trim() || r.stdout?.trim() || `exit ${r.status ?? "?"}`;
|
|
@@ -5378,7 +5769,7 @@ function logbookCompactionReminderText(input) {
|
|
|
5378
5769
|
if (escalated) return `${eventToken("compaction")} ${facts} — you have been reminded repeatedly and have NOT compacted the logbook. This is now mandatory: STOP the current task step, load eap (praxis eap), rewrite SESSION.md, then resume. ${principles} This reminder persists until the file is under the limit.`;
|
|
5379
5770
|
return `${eventToken("compaction")} ${facts}. Pause the current work and compact the trajectory logbook before continuing — the logbook is the persistent body of this trajectory, and its size is operational entropy (H_op) the container pays on every read. ${principles} After rewriting, resume the paused work; this reminder stops once the file is under the limit.`;
|
|
5380
5771
|
}
|
|
5381
|
-
const PLUGIN_SOURCE$
|
|
5772
|
+
const PLUGIN_SOURCE$5 = {
|
|
5382
5773
|
kind: "plugin",
|
|
5383
5774
|
plugin: "dsh-serenity-hooks"
|
|
5384
5775
|
};
|
|
@@ -5465,7 +5856,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
5465
5856
|
if (blocks.length === 0) return downstream;
|
|
5466
5857
|
const reminderAll = createUserMessage({
|
|
5467
5858
|
content: blocks,
|
|
5468
|
-
source: PLUGIN_SOURCE$
|
|
5859
|
+
source: PLUGIN_SOURCE$5
|
|
5469
5860
|
});
|
|
5470
5861
|
if (downstream.kind === "block") return {
|
|
5471
5862
|
kind: "block",
|
|
@@ -6023,7 +6414,7 @@ function toolsBlock() {
|
|
|
6023
6414
|
" handyman — delegate work to a worker agent on a CCC-whitelisted model; mode=\"foreground\" (default) = one serial child returns its final text; mode=\"background\" = loop-validated worker (completion code + round cap + restart + progress file), jobs=[] orchestrates parallel work",
|
|
6024
6415
|
" localstore — ACC local credential/config storage (CCC-root localstore.json; git policy localstore.gitTrack default deny)",
|
|
6025
6416
|
" container_admin — container administration (the maintenance bay): role (Skiff roles: guide/validate/apply/list) / msm (register/deregister/check/guide/catalog/ccc-config) / config",
|
|
6026
|
-
"
|
|
6417
|
+
" trajectory — trajectory one-stop management (all/init/random/diag/doc/check/status/guide/diag-live) + wake registry (wake-add/wake-list/wake-rm: wake = a future instant + one message, delivered to any trajectory; fire-and-forget)",
|
|
6027
6418
|
"",
|
|
6028
6419
|
"MSM call (registered CCC MSMs, deterministic Mech & Semi-Mech):",
|
|
6029
6420
|
" Execute: msm(\"<name>\", [\"<arg1>\", \"<arg2>\"]) — run a registered MSM directly (partial name returns matching candidates)",
|
|
@@ -6513,7 +6904,7 @@ function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entr
|
|
|
6513
6904
|
}
|
|
6514
6905
|
return lines.join("\n");
|
|
6515
6906
|
}
|
|
6516
|
-
const PLUGIN_SOURCE$
|
|
6907
|
+
const PLUGIN_SOURCE$4 = {
|
|
6517
6908
|
kind: "plugin",
|
|
6518
6909
|
plugin: "dsh-serenity-hooks"
|
|
6519
6910
|
};
|
|
@@ -6530,7 +6921,7 @@ function accMessage(root, configPaths, entrySkillMaxChars) {
|
|
|
6530
6921
|
}];
|
|
6531
6922
|
return createUserMessage({
|
|
6532
6923
|
content,
|
|
6533
|
-
source: PLUGIN_SOURCE$
|
|
6924
|
+
source: PLUGIN_SOURCE$4
|
|
6534
6925
|
});
|
|
6535
6926
|
}
|
|
6536
6927
|
/** 每 agent 是否已注入过(进程内存态) */
|
|
@@ -6705,7 +7096,7 @@ const FILE_UPLOAD_PATH = "/serenity/file-upload";
|
|
|
6705
7096
|
const CONFIG_PATH = "/serenity/config";
|
|
6706
7097
|
const CCCS_PATH = "/serenity/cccs";
|
|
6707
7098
|
const PUBLIC_ASK_PATH = "/serenity/public-ask";
|
|
6708
|
-
const
|
|
7099
|
+
const TRAJECTORY_PATH = "/serenity/trajectory";
|
|
6709
7100
|
const WEIXIN_PATH = "/serenity/weixin";
|
|
6710
7101
|
/** 图片落盘目录(CCC 根相对;S142 图片自动识别基础设施——粘贴图片落盘供 agent 经 CCC vlm MSM 自主处理) */
|
|
6711
7102
|
const IMAGE_UPLOAD_DIR = "_tmp/images_from_user";
|
|
@@ -7021,7 +7412,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7021
7412
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
7022
7413
|
});
|
|
7023
7414
|
const root = findSerenityRoot(workspace) ?? "";
|
|
7024
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
7415
|
+
const { discoverCccs } = await import("./skiff-debug-K2k1JnGj.js").then((n) => n.i);
|
|
7025
7416
|
sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
7026
7417
|
} catch (err) {
|
|
7027
7418
|
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
@@ -7051,7 +7442,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7051
7442
|
return;
|
|
7052
7443
|
}
|
|
7053
7444
|
const settings = readAdvancedSettings();
|
|
7054
|
-
const { readSimpleSettings } = await import("./settings-section-
|
|
7445
|
+
const { readSimpleSettings } = await import("./settings-section-Bm-xG-NR.js").then((n) => n.r);
|
|
7055
7446
|
const simple = readSimpleSettings();
|
|
7056
7447
|
const allowed = settings.publicAsk.allowed;
|
|
7057
7448
|
const port = simple.acpHttpPort ?? 3100;
|
|
@@ -7075,7 +7466,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7075
7466
|
});
|
|
7076
7467
|
ctx.webServer.register({
|
|
7077
7468
|
kind: "exact",
|
|
7078
|
-
path:
|
|
7469
|
+
path: TRAJECTORY_PATH,
|
|
7079
7470
|
handler: async (req, res) => {
|
|
7080
7471
|
try {
|
|
7081
7472
|
if (req.method === "POST") {
|
|
@@ -7125,7 +7516,11 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
7125
7516
|
sendJson$2(res, 200, { status: null });
|
|
7126
7517
|
return;
|
|
7127
7518
|
}
|
|
7128
|
-
|
|
7519
|
+
const { listWakes } = await Promise.resolve().then(() => wake_registry_exports);
|
|
7520
|
+
sendJson$2(res, 200, {
|
|
7521
|
+
status: getAutopilotStatus(effectiveRoot),
|
|
7522
|
+
wakes: listWakes(effectiveRoot).entries
|
|
7523
|
+
});
|
|
7129
7524
|
} catch (err) {
|
|
7130
7525
|
sendJson$2(res, 400, { error: err.message ?? String(err) });
|
|
7131
7526
|
}
|
|
@@ -8427,7 +8822,7 @@ var rebuild_exports = /* @__PURE__ */ __exportAll({
|
|
|
8427
8822
|
sanitizeFocusLine: () => sanitizeFocusLine,
|
|
8428
8823
|
stripAckSuffix: () => stripAckSuffix
|
|
8429
8824
|
});
|
|
8430
|
-
const PLUGIN_SOURCE$
|
|
8825
|
+
const PLUGIN_SOURCE$3 = {
|
|
8431
8826
|
kind: "plugin",
|
|
8432
8827
|
plugin: "dsh-serenity-hooks"
|
|
8433
8828
|
};
|
|
@@ -8746,7 +9141,7 @@ function registerRebuildTurnHook(ctx) {
|
|
|
8746
9141
|
type: "text",
|
|
8747
9142
|
text: `${eventToken("rebuild")} The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent trajectory (SESSION.md) and continue the work automatically from the last checkpoint.`
|
|
8748
9143
|
}],
|
|
8749
|
-
source: PLUGIN_SOURCE$
|
|
9144
|
+
source: PLUGIN_SOURCE$3
|
|
8750
9145
|
}));
|
|
8751
9146
|
writeRebuildDiag(resolveSerenityRootFor(agent), {
|
|
8752
9147
|
sessionId: id,
|
|
@@ -8920,7 +9315,7 @@ function buildRebuke(hits) {
|
|
|
8920
9315
|
const rebukeStates = /* @__PURE__ */ new Map();
|
|
8921
9316
|
//#endregion
|
|
8922
9317
|
//#region src/output-guard-seam.ts
|
|
8923
|
-
const PLUGIN_SOURCE$
|
|
9318
|
+
const PLUGIN_SOURCE$2 = {
|
|
8924
9319
|
kind: "plugin",
|
|
8925
9320
|
plugin: "dsh-serenity-hooks"
|
|
8926
9321
|
};
|
|
@@ -8981,7 +9376,7 @@ function registerOutputGuardHook(ctx) {
|
|
|
8981
9376
|
type: "text",
|
|
8982
9377
|
text: buildRebuke(hits)
|
|
8983
9378
|
}],
|
|
8984
|
-
source: PLUGIN_SOURCE$
|
|
9379
|
+
source: PLUGIN_SOURCE$2
|
|
8985
9380
|
}));
|
|
8986
9381
|
} catch (error) {
|
|
8987
9382
|
console.warn(`[serenity-hooks] output-guard steer failed: ${String(error?.message ?? error)}`);
|
|
@@ -9747,6 +10142,196 @@ qInput.focus()
|
|
|
9747
10142
|
</html>`;
|
|
9748
10143
|
}
|
|
9749
10144
|
//#endregion
|
|
10145
|
+
//#region src/wake-scheduler.ts
|
|
10146
|
+
const PLUGIN_SOURCE$1 = {
|
|
10147
|
+
kind: "plugin",
|
|
10148
|
+
plugin: "dsh-serenity-hooks"
|
|
10149
|
+
};
|
|
10150
|
+
/**
|
|
10151
|
+
* 解析条目的 target(`S###` 或完整目录名)→ 目录名 + SESSION.md 路径。
|
|
10152
|
+
* @param root CCC 根
|
|
10153
|
+
* @param target 条目里的目标标识
|
|
10154
|
+
* @returns 命中返回目录信息,未命中 null
|
|
10155
|
+
*/
|
|
10156
|
+
function resolveWakeTarget(root, target) {
|
|
10157
|
+
const found = findSession(sessionsRoot(root), target);
|
|
10158
|
+
if (!found) return null;
|
|
10159
|
+
const mdPath = join(found.path, "SESSION.md");
|
|
10160
|
+
if (!existsSync(mdPath)) return null;
|
|
10161
|
+
return {
|
|
10162
|
+
dirName: basename(found.path),
|
|
10163
|
+
mdPath
|
|
10164
|
+
};
|
|
10165
|
+
}
|
|
10166
|
+
/** 全局闸:`trajectoryEnabled` 优先,旧键 `autopilotEnabled` 回退(设置服务不可用 → 关) */
|
|
10167
|
+
function wakeSchedulerEnabled() {
|
|
10168
|
+
try {
|
|
10169
|
+
const s = readSimpleSettings();
|
|
10170
|
+
return s.trajectoryEnabled === true || s.autopilotEnabled === true;
|
|
10171
|
+
} catch {
|
|
10172
|
+
return false;
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
/**
|
|
10176
|
+
* 取目标 agent:live 优先 → 冷会话走 sessionController.resolveAgent → 标题回退。
|
|
10177
|
+
* @param ctx 插件上下文
|
|
10178
|
+
* @param root CCC 根
|
|
10179
|
+
* @param dirName 目标 trajectory 目录名
|
|
10180
|
+
* @returns 命中返回 agent 与取得方式(how 进 lastResult,便于诊断);未命中返回原因
|
|
10181
|
+
*/
|
|
10182
|
+
async function acquireWakeAgent(ctx, root, dirName) {
|
|
10183
|
+
const agents = hostAgents(ctx);
|
|
10184
|
+
const ids = listBoundSessionIds(root, dirName);
|
|
10185
|
+
for (const id of ids) {
|
|
10186
|
+
const live = agents?.get?.(id);
|
|
10187
|
+
if (live) return {
|
|
10188
|
+
agent: live,
|
|
10189
|
+
how: `live(bound ${id})`
|
|
10190
|
+
};
|
|
10191
|
+
}
|
|
10192
|
+
if (ids.length > 0) {
|
|
10193
|
+
const sc = hostService(ctx, "sessionController");
|
|
10194
|
+
const resolved = sc?.resolveAgent;
|
|
10195
|
+
if (typeof resolved !== "function") return { error: `目标会话未加载且 sessionController 不可用(headless profile?)——绑定 id: ${ids[0]}` };
|
|
10196
|
+
const id = ids[0];
|
|
10197
|
+
try {
|
|
10198
|
+
const out = await resolved.call(sc, id);
|
|
10199
|
+
if (out?.agent) return {
|
|
10200
|
+
agent: out.agent,
|
|
10201
|
+
how: `cold-resume(${id})`
|
|
10202
|
+
};
|
|
10203
|
+
return { error: `sessionController.resolveAgent 未返回 agent(${id}): ${String(out?.error ?? "未知原因")}` };
|
|
10204
|
+
} catch (err) {
|
|
10205
|
+
return { error: `冷会话载入失败(${id}): ${String(err?.message ?? err)}` };
|
|
10206
|
+
}
|
|
10207
|
+
}
|
|
10208
|
+
return { error: `无绑定会话记录(AGENT_SESSIONS/.bindings.json 中无 ${dirName})——目标轨迹需先被 logbook use 激活过` };
|
|
10209
|
+
}
|
|
10210
|
+
/** 唤醒消息(唤起即投递正文:身份锚定 + 唤醒信息 + 任务) */
|
|
10211
|
+
function buildWakeText(entry, target) {
|
|
10212
|
+
return [
|
|
10213
|
+
`[trajectory 唤醒] 到点(登记于 ${entry.createdAt},发起者 ${entry.createdBy || "未知"},id ${entry.id})。`,
|
|
10214
|
+
"",
|
|
10215
|
+
`身份锚定:继续 ${target.dirName} 的 trajectory(SESSION.md: ${target.mdPath})。`,
|
|
10216
|
+
"",
|
|
10217
|
+
"唤醒信息:",
|
|
10218
|
+
entry.message,
|
|
10219
|
+
"",
|
|
10220
|
+
"任务:按上述唤醒信息继续本轨迹的工作;完成后把进展写入 SESSION.md。"
|
|
10221
|
+
].join("\n");
|
|
10222
|
+
}
|
|
10223
|
+
/**
|
|
10224
|
+
* 投递一条唤醒(fire-and-forget:`followup` 入队即返回,不等模型结果)。
|
|
10225
|
+
* @param ctx 插件上下文
|
|
10226
|
+
* @param root CCC 根
|
|
10227
|
+
* @param entry 唤醒条目
|
|
10228
|
+
* @returns 投递结果(写回 lastResult)
|
|
10229
|
+
*/
|
|
10230
|
+
async function deliverWake(ctx, root, entry) {
|
|
10231
|
+
const target = resolveWakeTarget(root, entry.target);
|
|
10232
|
+
if (!target) return {
|
|
10233
|
+
ok: false,
|
|
10234
|
+
detail: `目标 trajectory 未命中(${entry.target})`
|
|
10235
|
+
};
|
|
10236
|
+
const acquired = await acquireWakeAgent(ctx, root, target.dirName);
|
|
10237
|
+
if ("error" in acquired) return {
|
|
10238
|
+
ok: false,
|
|
10239
|
+
detail: acquired.error
|
|
10240
|
+
};
|
|
10241
|
+
try {
|
|
10242
|
+
acquired.agent.followup(createUserMessage({
|
|
10243
|
+
content: [{
|
|
10244
|
+
type: "text",
|
|
10245
|
+
text: buildWakeText(entry, target)
|
|
10246
|
+
}],
|
|
10247
|
+
source: PLUGIN_SOURCE$1
|
|
10248
|
+
}));
|
|
10249
|
+
} catch (err) {
|
|
10250
|
+
return {
|
|
10251
|
+
ok: false,
|
|
10252
|
+
detail: `投递失败(${acquired.how}): ${String(err?.message ?? err)}`
|
|
10253
|
+
};
|
|
10254
|
+
}
|
|
10255
|
+
return {
|
|
10256
|
+
ok: true,
|
|
10257
|
+
detail: `已投递 ${target.dirName}(${acquired.how})`
|
|
10258
|
+
};
|
|
10259
|
+
}
|
|
10260
|
+
/** 一次调度 tick:遍历 live CCC → 到期项串行投递 / 超窗项置 missed;返回人读摘要 */
|
|
10261
|
+
async function runWakeTick(ctx) {
|
|
10262
|
+
const log = [];
|
|
10263
|
+
for (const root of collectLiveCccs(ctx)) {
|
|
10264
|
+
const { registry, error } = loadWakeRegistry(root);
|
|
10265
|
+
if (error) {
|
|
10266
|
+
log.push(`✗ ${root}: ${error}`);
|
|
10267
|
+
continue;
|
|
10268
|
+
}
|
|
10269
|
+
const { due, expired } = splitDueWakes(registry, Date.now(), WAKE_CATCH_UP_MS);
|
|
10270
|
+
for (const e of expired) {
|
|
10271
|
+
updateWake(root, e.id, {
|
|
10272
|
+
state: "missed",
|
|
10273
|
+
lastResult: `超过补跑窗口(${WAKE_CATCH_UP_MS / 36e5}h)未投递`
|
|
10274
|
+
});
|
|
10275
|
+
log.push(`· ${root}: ${e.id} → missed(超补跑窗口)`);
|
|
10276
|
+
}
|
|
10277
|
+
for (const e of due) {
|
|
10278
|
+
const res = await deliverWake(ctx, root, e);
|
|
10279
|
+
updateWake(root, e.id, {
|
|
10280
|
+
state: res.ok ? "delivered" : "pending",
|
|
10281
|
+
attempts: e.attempts + 1,
|
|
10282
|
+
lastResult: res.detail,
|
|
10283
|
+
deliveredAt: res.ok ? (/* @__PURE__ */ new Date()).toISOString() : null
|
|
10284
|
+
});
|
|
10285
|
+
log.push(`· ${root}: ${e.id} → ${res.ok ? "delivered" : `重试(${res.detail})`}`);
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
return log;
|
|
10289
|
+
}
|
|
10290
|
+
/**
|
|
10291
|
+
* 装配调度器(index.ts apply 调用):5min tick + 生命周期 disposer +
|
|
10292
|
+
* 会话出现/settings 变化时热启动(与 autopilot 同款触发面,互不干扰)。
|
|
10293
|
+
* @param ctx 插件上下文
|
|
10294
|
+
*/
|
|
10295
|
+
function registerWakeScheduler(ctx) {
|
|
10296
|
+
let timer = null;
|
|
10297
|
+
/** 全局串行链(同一时刻只跑一次 tick;投递本身按条目串行) */
|
|
10298
|
+
let chain = Promise.resolve();
|
|
10299
|
+
const tick = () => {
|
|
10300
|
+
if (!wakeSchedulerEnabled()) return;
|
|
10301
|
+
if (collectLiveCccs(ctx).length === 0) return;
|
|
10302
|
+
chain = chain.then(async () => {
|
|
10303
|
+
try {
|
|
10304
|
+
const log = await runWakeTick(ctx);
|
|
10305
|
+
for (const line of log) console.log(`[serenity-hooks] trajectory 唤醒 ${line}`);
|
|
10306
|
+
} catch (err) {
|
|
10307
|
+
console.warn(`[serenity-hooks] ✗ trajectory 唤醒 tick 异常: ${String(err?.message ?? err)}`);
|
|
10308
|
+
}
|
|
10309
|
+
});
|
|
10310
|
+
};
|
|
10311
|
+
const startTimer = () => {
|
|
10312
|
+
if (timer) return;
|
|
10313
|
+
if (!wakeSchedulerEnabled()) return;
|
|
10314
|
+
if (collectLiveCccs(ctx).length === 0) return;
|
|
10315
|
+
timer = setInterval(tick, TICK_MS);
|
|
10316
|
+
timer.unref();
|
|
10317
|
+
console.log("[serenity-hooks] ✓ trajectory 唤醒调度器启动(5min tick)");
|
|
10318
|
+
tick();
|
|
10319
|
+
};
|
|
10320
|
+
startTimer();
|
|
10321
|
+
try {
|
|
10322
|
+
ctx.on("session/created", () => startTimer());
|
|
10323
|
+
} catch {}
|
|
10324
|
+
try {
|
|
10325
|
+
ctx.on("serenity/settings-changed", () => startTimer());
|
|
10326
|
+
} catch {}
|
|
10327
|
+
registerDisposer(ctx, "trajectory 唤醒调度器", () => {
|
|
10328
|
+
if (timer) {
|
|
10329
|
+
clearInterval(timer);
|
|
10330
|
+
timer = null;
|
|
10331
|
+
}
|
|
10332
|
+
});
|
|
10333
|
+
}
|
|
10334
|
+
//#endregion
|
|
9750
10335
|
//#region src/weixin-output-guard.ts
|
|
9751
10336
|
const PLUGIN_SOURCE = {
|
|
9752
10337
|
kind: "plugin",
|
|
@@ -10654,7 +11239,7 @@ function matchCcc(input, candidates) {
|
|
|
10654
11239
|
}
|
|
10655
11240
|
/** 组装候选列表(discoverCccs 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
|
|
10656
11241
|
async function collectCandidates(ctx) {
|
|
10657
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
11242
|
+
const { discoverCccs } = await import("./skiff-debug-K2k1JnGj.js").then((n) => n.i);
|
|
10658
11243
|
const entries = await discoverCccs(ctx, process.cwd());
|
|
10659
11244
|
const seen = /* @__PURE__ */ new Set();
|
|
10660
11245
|
const out = [];
|
|
@@ -11717,6 +12302,7 @@ function apply(ctx, config) {
|
|
|
11717
12302
|
registerSkiff(ctx);
|
|
11718
12303
|
registerAcp(ctx);
|
|
11719
12304
|
registerAutopilot(ctx);
|
|
12305
|
+
registerWakeScheduler(ctx);
|
|
11720
12306
|
registerWeixinBridge(ctx);
|
|
11721
12307
|
registerWeixinSendApi(ctx);
|
|
11722
12308
|
registerWeixinOutputGuard(ctx);
|