@shgroup/dsh-serenity-hooks 1.27.14 → 1.28.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/dsh.plugin.json +3 -2
- package/lib/{ccc-CfDrlfA7.js → ccc-CX48YNUL.js} +19 -1
- package/lib/ccc.d.ts +9 -0
- package/lib/client.js +26 -55
- package/lib/config-ops.d.ts +21 -10
- package/lib/index.d.ts +3 -2
- package/lib/index.js +485 -174
- package/lib/invariant.d.ts +1 -1
- package/lib/invariant.js +2 -1
- package/lib/kit-ops.d.ts +27 -0
- package/lib/msm-ops.d.ts +16 -2
- package/lib/rebuild.d.ts +5 -0
- package/lib/seams/bootstrap.d.ts +9 -0
- package/lib/seams/guards.d.ts +14 -0
- package/lib/seams/keeper.d.ts +4 -1
- package/lib/seams/system-prompt.d.ts +15 -4
- package/lib/session-ops.d.ts +11 -0
- package/lib/settings-section-BfVxgCZy.js +91 -0
- package/lib/settings-section.d.ts +10 -6
- package/lib/{skiff-debug-BRlc17GU.js → skiff-debug-CBU6T2F_.js} +16 -5
- package/lib/{skiff-role-Dw7UKCUm.js → skiff-role-BTdBHyOQ.js} +1 -1
- package/lib/tools/session.d.ts +23 -6
- package/lib/{weixin-route-BUJLsCGS.js → weixin-route-DFkRf9ou.js} +1 -1
- package/package.json +18 -16
- package/lib/settings-section-PzeHStWP.js +0 -2124
package/lib/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { a as findSerenityRoot, c as matchBlacklist, d as readHandymanConfig, i as findGitRoot, l as pathInside,
|
|
3
|
-
import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-
|
|
4
|
-
import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-
|
|
5
|
-
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, 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-
|
|
6
|
-
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-
|
|
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-CX48YNUL.js";
|
|
3
|
+
import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-BTdBHyOQ.js";
|
|
4
|
+
import { C as splitModel, E as skiffRoleFor, S as requireWhitelistedModel, T as writeProgress, _ as buildRoundPrompt, a as startSkiffDebugServer, b as newStopToken, c as askSkiff, d as skiffMsmGate, f as skiffSessionInfo, g as HANDYMAN_GUIDE, h as unregisterSkiffSession, l as createSkiffAgent, m as skiffTrajectoryEnabled, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffSessionSnapshot, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as getSkiffAgent, v as handymanProgressPaths, w as writeFailedStatus, x as readProgress, y as listActiveHandymen } from "./skiff-debug-CBU6T2F_.js";
|
|
5
|
+
import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, 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-DFkRf9ou.js";
|
|
6
|
+
import { n as registerSettingsSection, t as readSimpleSettings } from "./settings-section-BfVxgCZy.js";
|
|
7
7
|
import { a as markdownToPlainText, c as sniffImageExt, i as getUpdates, n as downloadMedia, o as sendTextMessage, r as getConfig, s as sendTyping, t as TypingStatus } from "./weixin-api-OUCuw0Ws.js";
|
|
8
8
|
import z from "@deepseek-ai/schemastery";
|
|
9
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -81,12 +81,43 @@ function getFileInfo(absPath, name) {
|
|
|
81
81
|
function safeRel(root, abs) {
|
|
82
82
|
return relative(root, abs) || ".";
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* 受保护注册表聚合档的路径集合(review P2-2:精确文件保护可被 `rm -r 父目录` / `mv 父目录`
|
|
86
|
+
* 绕过——删掉 references/ 目录等于删掉注册表。保护对象 = 聚合档文件 + 其全部祖先目录)。
|
|
87
|
+
* @returns null = 无 cccName(无法定位 → 不保护);否则 { fileRel, ancestorDirs } 相对根的正斜杠 rel
|
|
88
|
+
*/
|
|
89
|
+
function protectedRegistryTargets(root) {
|
|
90
|
+
const cccName = readCccName$2(root);
|
|
91
|
+
if (!cccName) return null;
|
|
92
|
+
const fileRel = `.opencode/skills/${cccName}/references/mech-registry.json`;
|
|
93
|
+
const segs = fileRel.split("/");
|
|
94
|
+
const ancestorDirs = [];
|
|
95
|
+
if (segs.length >= 3) ancestorDirs.push(segs.slice(0, segs.length - 1).join("/"));
|
|
96
|
+
return {
|
|
97
|
+
fileRel,
|
|
98
|
+
ancestorDirs
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function isProtectedRegistryTarget(root, relCi) {
|
|
102
|
+
const protectedTargets = protectedRegistryTargets(root);
|
|
103
|
+
if (!protectedTargets) return null;
|
|
104
|
+
const lower = (s) => process.platform === "win32" ? s.toLowerCase() : s;
|
|
105
|
+
if (relCi === lower(protectedTargets.fileRel)) return { hit: "file" };
|
|
106
|
+
for (const ancestor of protectedTargets.ancestorDirs) if (relCi === lower(ancestor)) return { hit: "ancestor" };
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function assertNotProtectedRegistry(root, absPath, targetLabel) {
|
|
110
|
+
const rel = relative(root, absPath).split("\\").join("/");
|
|
111
|
+
const lower = (s) => process.platform === "win32" ? s.toLowerCase() : s;
|
|
112
|
+
const hit = isProtectedRegistryTarget(root, lower(rel));
|
|
113
|
+
if (hit === null) return;
|
|
114
|
+
if (hit.hit === "file") throw new Error(`cc-fs: refusing to directly modify mech-registry.json — use acc_msm register/deregister instead`);
|
|
115
|
+
throw new Error(`cc-fs: refusing to ${/rm|delete|remove/i.test(targetLabel) ? "remove" : "move"} "${rel}" — it is an ancestor of the ACC-managed mech-registry.json (${protectedRegistryTargets(root)?.fileRel}); the registry is managed by acc_msm register/deregister`);
|
|
116
|
+
}
|
|
84
117
|
function validateWritePath(root, target) {
|
|
85
118
|
const absPath = target.startsWith("/") ? resolve(target) : resolveInside(root, target);
|
|
86
119
|
if (!pathInside(resolve(root), absPath)) throw new Error(`cc-fs: path "${target}" resolves to "${absPath}" which is outside serenity root "${root}"`);
|
|
87
|
-
|
|
88
|
-
const relCi = process.platform === "win32" ? rel.toLowerCase() : rel;
|
|
89
|
-
if (relCi.endsWith("/mech-registry.json") && /(^|\/)(opencode|\.opencode)\/skills\//.test(relCi)) throw new Error(`cc-fs: refusing to directly modify mech-registry.json — use acc_msm register/deregister instead`);
|
|
120
|
+
assertNotProtectedRegistry(root, absPath, `modify ${target}`);
|
|
90
121
|
if (existsSync(absPath)) try {
|
|
91
122
|
const real = realpathSync(absPath);
|
|
92
123
|
if (!pathInside(resolve(root), real)) throw new Error(`cc-fs: path "${target}" resolves via symlink to "${real}" outside serenity root "${root}"`);
|
|
@@ -187,7 +218,13 @@ function runCcFs(root, args) {
|
|
|
187
218
|
const recursive = args.recursive ?? false;
|
|
188
219
|
const results = [];
|
|
189
220
|
for (const target of targets) {
|
|
190
|
-
|
|
221
|
+
let absPath;
|
|
222
|
+
try {
|
|
223
|
+
absPath = validateWritePath(root, target);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
results.push(`[SKIP] ${e.message}`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
191
228
|
if (!existsSync(absPath)) {
|
|
192
229
|
results.push(`[SKIP] not found: ${target}`);
|
|
193
230
|
continue;
|
|
@@ -518,6 +555,34 @@ const CC_FS_WRITE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
518
555
|
* 含 API keys/tokens/SSH 密码,即使 prompt 纪律失败也无值可吐(机械保证)。
|
|
519
556
|
*/
|
|
520
557
|
const SENSITIVE_CREDENTIAL_FILES = /* @__PURE__ */ new Set(["localstore.json"]);
|
|
558
|
+
/**
|
|
559
|
+
* MSM 注册表写保护(需求⑤b S142 用户拍板:"msm注册的文件需要写保护起来,避免CCC意外写坏搞崩自己"):
|
|
560
|
+
* mech-registry.json 是 CCC 执行层地基(坏 → loadMsmEntries 抛 → acc_msm/skiff/output-guard 全崩,
|
|
561
|
+
* register 也崩 → 自锁)。**写 deny、读 allow**(与 localstore 读 deny 语义区分 R6——注册表是
|
|
562
|
+
* 结构核心不是秘密,需被 output-guard/skiff-admin 读取建 MSM 词表)。
|
|
563
|
+
*
|
|
564
|
+
* 单级化(需求⑤a)后**唯一合法注册表 = cccName 聚合档**
|
|
565
|
+
* `.opencode/skills/<cccName>/references/mech-registry.json`(cccName = .serenity 首行)。
|
|
566
|
+
* 历史 root 级 `mech-registry.json` 与各 skill 分散注册表已废弃:**不再保护**
|
|
567
|
+
* (review P1:保护一个永不被读的文件 = 死锁——MSM 既不可见又不可删/迁移)。
|
|
568
|
+
* 写工具命中聚合档 → deny(唯一合法写通道 = acc_msm register/deregister 内部 writeRegistry,
|
|
569
|
+
* 走工具实现不经 pre-execute → 天然豁免);读工具放行。
|
|
570
|
+
*/
|
|
571
|
+
function isProtectedRegistryRel(root, rel) {
|
|
572
|
+
const lower = (s) => process.platform === "win32" ? s.toLowerCase() : s;
|
|
573
|
+
const relCi = lower(rel);
|
|
574
|
+
try {
|
|
575
|
+
const marker = resolve(root, ".serenity");
|
|
576
|
+
if (existsSync(marker)) {
|
|
577
|
+
const line = readCccName$2(root);
|
|
578
|
+
if (line) {
|
|
579
|
+
if (relCi === lower(`.opencode/skills/${line}/references/mech-registry.json`)) return true;
|
|
580
|
+
if (relCi === lower(`.opencode/skills/${line}/references`)) return true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} catch {}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
521
586
|
/** 判定工具是否为写类:普通工具按名;cc_fs 复合工具按子命令 action */
|
|
522
587
|
function isWriteTool(toolName, action) {
|
|
523
588
|
if (!WRITE_TOOLS.has(toolName)) return false;
|
|
@@ -561,6 +626,10 @@ function decideGuard(input) {
|
|
|
561
626
|
deny: `CCC governance file "${rel}" is reserved for the user — agent must not write`,
|
|
562
627
|
kind: "deny"
|
|
563
628
|
};
|
|
629
|
+
if (isProtectedRegistryRel(root, rel)) return {
|
|
630
|
+
deny: rel.endsWith("/mech-registry.json") ? `mech-registry.json is ACC-managed (${rel}) — use acc_msm register/deregister instead of writing it directly` : `"${rel}" is an ancestor of the ACC-managed mech-registry.json — removing/moving it would destroy the registry; the registry is managed by acc_msm register/deregister`,
|
|
631
|
+
kind: "deny"
|
|
632
|
+
};
|
|
564
633
|
const hit = matchBlacklist(rel, blacklist);
|
|
565
634
|
if (hit) return {
|
|
566
635
|
deny: hit.message ?? `blacklist blocked: "${pathArg}" matches rule "${hit.pattern}"`,
|
|
@@ -780,14 +849,83 @@ const KIT_ACTIONS = [
|
|
|
780
849
|
"time",
|
|
781
850
|
"wait"
|
|
782
851
|
];
|
|
783
|
-
/** CCC
|
|
852
|
+
/** CCC 名(review P2-2:统一收口到 ccc.ts readCccName——跳 # 注释/空行首非空行;此处保留 null 容错) */
|
|
784
853
|
function readCccName$1(root) {
|
|
785
|
-
|
|
854
|
+
return root ? readCccName$2(root) : null;
|
|
855
|
+
}
|
|
856
|
+
function checkRegistryHealth(root) {
|
|
857
|
+
const cccName = readCccName$1(root);
|
|
858
|
+
if (!cccName) return {
|
|
859
|
+
path: null,
|
|
860
|
+
ok: true,
|
|
861
|
+
present: false,
|
|
862
|
+
issues: []
|
|
863
|
+
};
|
|
864
|
+
const rel = `.opencode/skills/${cccName}/references/mech-registry.json`;
|
|
865
|
+
const abs = resolve(root, rel);
|
|
866
|
+
if (!existsSync(abs)) return {
|
|
867
|
+
path: rel,
|
|
868
|
+
ok: true,
|
|
869
|
+
present: false,
|
|
870
|
+
issues: []
|
|
871
|
+
};
|
|
872
|
+
const issues = [];
|
|
873
|
+
let raw = "";
|
|
786
874
|
try {
|
|
787
|
-
|
|
788
|
-
} catch {
|
|
789
|
-
return
|
|
875
|
+
raw = readFileSync(abs, "utf-8");
|
|
876
|
+
} catch (err) {
|
|
877
|
+
return {
|
|
878
|
+
path: rel,
|
|
879
|
+
ok: false,
|
|
880
|
+
present: true,
|
|
881
|
+
issues: [`registry unreadable: ${String(err?.message ?? err)}`]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
let data;
|
|
885
|
+
try {
|
|
886
|
+
data = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
887
|
+
} catch (err) {
|
|
888
|
+
return {
|
|
889
|
+
path: rel,
|
|
890
|
+
ok: false,
|
|
891
|
+
present: true,
|
|
892
|
+
issues: [`registry JSON is broken: ${String(err?.message ?? err)}`, `Fix: restore from git — register/deregister auto-commits the registry (git checkout -- ${rel}; or git restore ${rel})`]
|
|
893
|
+
};
|
|
790
894
|
}
|
|
895
|
+
const entries = Array.isArray(data) ? data : data?.entries;
|
|
896
|
+
if (!Array.isArray(entries)) return {
|
|
897
|
+
path: rel,
|
|
898
|
+
ok: false,
|
|
899
|
+
present: true,
|
|
900
|
+
issues: ["registry top-level is neither an array nor a v1 wrapper with entries[]", `Fix: restore from git (git checkout -- ${rel}; or git restore ${rel})`]
|
|
901
|
+
};
|
|
902
|
+
const names = /* @__PURE__ */ new Set();
|
|
903
|
+
for (let i = 0; i < entries.length; i++) {
|
|
904
|
+
const e = entries[i];
|
|
905
|
+
if (typeof e !== "object" || e === null) {
|
|
906
|
+
issues.push(`entry[${i}] is not an object`);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (typeof e.name !== "string" || e.name === "") issues.push(`entry[${i}]: name missing or not a string`);
|
|
910
|
+
if (typeof e.path !== "string" || e.path === "") issues.push(`entry[${i}] (${String(e.name ?? "?")}): path missing or not a string`);
|
|
911
|
+
if (e.skill !== void 0 && typeof e.skill !== "string") issues.push(`entry[${i}] (${String(e.name ?? "?")}): skill not a string`);
|
|
912
|
+
if (e.category !== void 0 && typeof e.category !== "string") issues.push(`entry[${i}] (${String(e.name ?? "?")}): category not a string`);
|
|
913
|
+
if (typeof e.name === "string" && e.name !== "") {
|
|
914
|
+
if (names.has(e.name)) issues.push(`duplicate MSM name: "${e.name}" (entry[${i}]) — loadMsmEntries dedups by name, ambiguity`);
|
|
915
|
+
names.add(e.name);
|
|
916
|
+
}
|
|
917
|
+
if (typeof e.path === "string" && e.path !== "") {
|
|
918
|
+
const scriptAbs = resolve(root, e.path);
|
|
919
|
+
if (!pathInside(resolve(root), scriptAbs)) issues.push(`entry[${i}] (${String(e.name ?? "?")}): path "${e.path}" escapes CCC root`);
|
|
920
|
+
else if (!existsSync(scriptAbs)) issues.push(`entry[${i}] (${String(e.name ?? "?")}): script not found at "${e.path}"`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
path: rel,
|
|
925
|
+
ok: issues.length === 0,
|
|
926
|
+
present: true,
|
|
927
|
+
issues
|
|
928
|
+
};
|
|
791
929
|
}
|
|
792
930
|
async function runKit(root, args) {
|
|
793
931
|
switch (args.action) {
|
|
@@ -825,6 +963,7 @@ async function runKit(root, args) {
|
|
|
825
963
|
}
|
|
826
964
|
}
|
|
827
965
|
};
|
|
966
|
+
if (root) report.registry = checkRegistryHealth(root);
|
|
828
967
|
if (root) report.config = loadSerenityConfig(root);
|
|
829
968
|
report.accVersion = ACC_VERSION;
|
|
830
969
|
report.dshVersion = readDshVersion();
|
|
@@ -863,7 +1002,7 @@ function renderText$11(value) {
|
|
|
863
1002
|
}
|
|
864
1003
|
const kitTool = defineTool({
|
|
865
1004
|
name: "acc_kit",
|
|
866
|
-
description: "ACC general-purpose utility kit: health (CCC three-principle check P1/P2/config, healthy/degraded
|
|
1005
|
+
description: "ACC general-purpose utility kit: health (CCC three-principle check P1/P2/config + MSM registry integrity report, healthy/degraded) / time (now_iso/now_local/epoch_ms) / wait (wait N seconds, default 1). Routine self-check before entering a CCC.",
|
|
867
1006
|
parameters: {
|
|
868
1007
|
action: {
|
|
869
1008
|
type: "string",
|
|
@@ -1150,13 +1289,9 @@ function bunExecutablePath() {
|
|
|
1150
1289
|
bunExeCache = null;
|
|
1151
1290
|
return null;
|
|
1152
1291
|
}
|
|
1153
|
-
/** CCC 名:从 .serenity
|
|
1292
|
+
/** CCC 名:从 .serenity 解析(review P2-2 统一收口到 ccc.ts readCccName——跳 # 注释/空行首非空行;此处保留导出名兼容内部调用) */
|
|
1154
1293
|
function readCccName(root) {
|
|
1155
|
-
|
|
1156
|
-
return readFileSync(resolve(root, ".serenity"), "utf-8").trim().split("\n")[0]?.trim() || null;
|
|
1157
|
-
} catch {
|
|
1158
|
-
return null;
|
|
1159
|
-
}
|
|
1294
|
+
return readCccName$2(root);
|
|
1160
1295
|
}
|
|
1161
1296
|
/**
|
|
1162
1297
|
* path-arg 逃逸校验:根内 + symlink 防御(对齐 osp validatePathArgsFromTokens)。
|
|
@@ -1194,8 +1329,34 @@ const MSM_ACTIONS = [
|
|
|
1194
1329
|
"deregister",
|
|
1195
1330
|
"check",
|
|
1196
1331
|
"guide",
|
|
1197
|
-
"ccc-config"
|
|
1332
|
+
"ccc-config",
|
|
1333
|
+
"catalog"
|
|
1198
1334
|
];
|
|
1335
|
+
/**
|
|
1336
|
+
* ACC 能力目录(需求④ S142 用户拍板:"acc 配置各自做 guide 越来越多,整合成目录式使用指南")。
|
|
1337
|
+
* 目录在前、详情各归各——**单一真相源**:本目录只索引"去哪个工具/子命令看详情",不复制详情
|
|
1338
|
+
* (避免 skiff_admin/weixin-doctor 等 guide 全文重复 → 多真相源 → 失同步)。
|
|
1339
|
+
* 后续新增功能只需在此加一行(低熵可演进)。
|
|
1340
|
+
*/
|
|
1341
|
+
const ACC_CATALOG = `═══ ACC Usage Catalog ═══
|
|
1342
|
+
Directory of ACC capabilities — each area lists where to go for details (guides live with their feature; this catalog only points).
|
|
1343
|
+
|
|
1344
|
+
① 会话与轨迹 → session tool(list/show/create/use/close/health/qa/archive/summary/hook-develop-guide)
|
|
1345
|
+
session_rebuild(超限重建;阈值 K 见 ccc-config / 设置面板重建阈值K)
|
|
1346
|
+
② 认知质量框架 → eap(E↑/R↓/S↑)/ neat(Neat 协议)/ cce(认知连续性工程)——渐进披露,无参即全文
|
|
1347
|
+
③ 工具与执行 → cc_fs(文件系统 15 子命令)/ cc_git(git)/ acc_kit(health 含注册表检查/time/wait)
|
|
1348
|
+
acc_msm(MSM 框架:本工具 list/exec/register/deregister/check/guide/ccc-config/catalog)
|
|
1349
|
+
handyman(杂工 agent 编排——guide 子命令:acc_msm exec handyman guide)
|
|
1350
|
+
④ 角色与对外面 → skiff_admin(Skiff 认知子集角色:guide/validate/apply/list——acc_msm exec skiff_admin guide)
|
|
1351
|
+
ACP(程序化 JSON-RPC 3100)/ Skiff 问答页(公网 ask)——面板「外部能力」组
|
|
1352
|
+
⑤ 自主与接入 → autopilot-trajectory(Autopilot 一站式:all/init/random/diag/doc/check/status/guide)
|
|
1353
|
+
weixin 微信桥(配置/扫码/路由/消息 hook——CCC 侧 weixin-doctor MSM:acc_msm exec weixin-doctor guide)
|
|
1354
|
+
⑥ CCC 配置总览 → acc_msm ccc-config(8 段:handyman/sessionKeeper/localstore/hooks/safeMode/skiff/autopilotTrajectory/weixin)
|
|
1355
|
+
⑦ 注册表与安全 → mech-registry.json 由 acc_msm register/deregister 管理(写保护——不可直接编辑)
|
|
1356
|
+
acc_kit health 输出 registry 完整性检查;损坏恢复指引见 health registry.issues
|
|
1357
|
+
|
|
1358
|
+
每区:一句话定位 + 详细入口(工具名 / guide 子命令)。详情永远以对应工具的 guide/ccc-config 为单一真相源。
|
|
1359
|
+
`;
|
|
1199
1360
|
const MSM_GUIDE = `MSM Development Manual (Mech & Semi-Mech framework)
|
|
1200
1361
|
|
|
1201
1362
|
## What it is
|
|
@@ -1380,16 +1541,18 @@ function parseRegistry(raw) {
|
|
|
1380
1541
|
if (!Array.isArray(entries)) throw new Error("invalid registry: missing entries[]");
|
|
1381
1542
|
return entries;
|
|
1382
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* 注册表单级化(需求⑤a S142 用户拍板:注册表只有一级——对齐 osp cccName 单聚合档):
|
|
1546
|
+
* 所有 entry 集中写入 `.opencode/skills/<cccName>/references/mech-registry.json`
|
|
1547
|
+
* (cccName = .serenity 首行,即顶层入口 skill 目录),skill 只作 entry 字段。
|
|
1548
|
+
* 不再扫描/写入各 skill 目录的分散注册表(历史形态 v1.14~v1.27,register --skill
|
|
1549
|
+
* 曾写入各自 skill 目录 —— 多真相源,废弃)。
|
|
1550
|
+
*/
|
|
1383
1551
|
function findRegistries(root) {
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
if (existsSync(p)) out.push(p);
|
|
1389
|
-
}
|
|
1390
|
-
const rootRegistry = join(root, "mech-registry.json");
|
|
1391
|
-
if (existsSync(rootRegistry)) out.push(rootRegistry);
|
|
1392
|
-
return out;
|
|
1552
|
+
const cccName = readCccName(root);
|
|
1553
|
+
if (!cccName) return [];
|
|
1554
|
+
const aggregate = join(root, ".opencode", "skills", cccName, "references", "mech-registry.json");
|
|
1555
|
+
return existsSync(aggregate) ? [aggregate] : [];
|
|
1393
1556
|
}
|
|
1394
1557
|
function loadMsmEntries(root) {
|
|
1395
1558
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1411,8 +1574,13 @@ function scanSkillScripts(root) {
|
|
|
1411
1574
|
}
|
|
1412
1575
|
return out.sort();
|
|
1413
1576
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1577
|
+
/**
|
|
1578
|
+
* 注册表写入路径(需求⑤a 单级化):永远返回 cccName 聚合档。
|
|
1579
|
+
* skill 参数保留签名(兼容调用方)但只进 entry 字段,不决定写入位置。
|
|
1580
|
+
*/
|
|
1581
|
+
function registryPathFor(root, _skill) {
|
|
1582
|
+
const cccName = readCccName(root) ?? "unknown";
|
|
1583
|
+
return join(root, ".opencode", "skills", cccName, "references", "mech-registry.json");
|
|
1416
1584
|
}
|
|
1417
1585
|
function writeRegistry(path, entries, isV1Wrapped = true) {
|
|
1418
1586
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -1438,6 +1606,7 @@ function runMsm(root, args) {
|
|
|
1438
1606
|
return `${header}\n` + lines.join("\n");
|
|
1439
1607
|
}
|
|
1440
1608
|
case "guide": return { guide: MSM_GUIDE };
|
|
1609
|
+
case "catalog": return { catalog: ACC_CATALOG };
|
|
1441
1610
|
case "exec": {
|
|
1442
1611
|
const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
|
|
1443
1612
|
const p = protocolResult(protocol);
|
|
@@ -1483,7 +1652,7 @@ function runMsm(root, args) {
|
|
|
1483
1652
|
if (loadMsmEntries(root).some((e) => e.name === name)) throw new Error(`MSM already registered: "${name}"`);
|
|
1484
1653
|
const regPath = registryPathFor(root, skill);
|
|
1485
1654
|
const raw = existsSync(regPath) ? readFileSync(regPath, "utf-8").replace(/^\uFEFF/, "") : "";
|
|
1486
|
-
const isV1Wrapped = raw
|
|
1655
|
+
const isV1Wrapped = raw === "" ? true : !Array.isArray(JSON.parse(raw));
|
|
1487
1656
|
const entries = existsSync(regPath) ? parseRegistry(readFileSync(regPath, "utf-8")) : [];
|
|
1488
1657
|
let flags;
|
|
1489
1658
|
if (args.flags) try {
|
|
@@ -1776,13 +1945,13 @@ function renderText$9(value) {
|
|
|
1776
1945
|
}
|
|
1777
1946
|
const msmTool = defineTool({
|
|
1778
1947
|
name: "acc_msm",
|
|
1779
|
-
description: "MSM (Mech & Semi-Mech) framework: list lists registered MSMs (header+flags display); exec executes (600s timeout, path-escape + symlink blocking, injects SERENITY_ROOT/CCC/VERSION env, appends --help TIP on failure; first arg --list/--schema/--format=json is protocol); register/deregister manage the registry (path inside root + script exists + globally unique validation, auto git precision commit); check quality checks DC-M1~M4; guide development manual; ccc-config CCC config reference (all sections: handyman.models / sessionKeeper.threshold / localstore.gitTrack / hooks.autoRestoreSession / safeMode.blacklist / skiff.roles / autopilotTrajectory / weixin incl. message hook full guide). Reuses the CCC mech-registry.json.",
|
|
1948
|
+
description: "MSM (Mech & Semi-Mech) framework: list lists registered MSMs (header+flags display); exec executes (600s timeout, path-escape + symlink blocking, injects SERENITY_ROOT/CCC/VERSION env, appends --help TIP on failure; first arg --list/--schema/--format=json is protocol); register/deregister manage the registry (path inside root + script exists + globally unique validation, auto git precision commit); check quality checks DC-M1~M4; guide development manual; catalog ACC usage directory (all capability areas — points to each feature guide, single source of truth); ccc-config CCC config reference (all sections: handyman.models / sessionKeeper.threshold / localstore.gitTrack / hooks.autoRestoreSession / safeMode.blacklist / skiff.roles / autopilotTrajectory / weixin incl. message hook full guide). Reuses the CCC mech-registry.json.",
|
|
1780
1949
|
parameters: {
|
|
1781
1950
|
action: {
|
|
1782
1951
|
type: "string",
|
|
1783
1952
|
enum: [...MSM_ACTIONS],
|
|
1784
1953
|
required: true,
|
|
1785
|
-
description: "Subcommand: list/exec/register/deregister/check/guide/ccc-config"
|
|
1954
|
+
description: "Subcommand: list/exec/register/deregister/check/guide/catalog/ccc-config"
|
|
1786
1955
|
},
|
|
1787
1956
|
name: {
|
|
1788
1957
|
type: "string",
|
|
@@ -2132,7 +2301,8 @@ function waitIdle(ctx, agent) {
|
|
|
2132
2301
|
}
|
|
2133
2302
|
/** 读取会话最后一个 assistant/message 文本 */
|
|
2134
2303
|
function lastAssistantText$1(agent) {
|
|
2135
|
-
const
|
|
2304
|
+
const s = agent.session;
|
|
2305
|
+
const events = typeof s.snapshotEvents === "function" ? s.snapshotEvents() : s.events ?? [];
|
|
2136
2306
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2137
2307
|
const e = events[i];
|
|
2138
2308
|
if (e && e.type === "assistant/message") {
|
|
@@ -2398,6 +2568,24 @@ const SESSION_ACTIONS = [
|
|
|
2398
2568
|
"summary",
|
|
2399
2569
|
"hook-develop-guide"
|
|
2400
2570
|
];
|
|
2571
|
+
/**
|
|
2572
|
+
* 读取 Session 事件序列(v1.28.1 适配 0.1.2-rc.1 补齐):rc.1 起官方 Session 类
|
|
2573
|
+
* 移除 `.events` 属性 → `snapshotEvents()` 方法(dsh-session/src/session.ts:
|
|
2574
|
+
* `snapshotEvents(fromSeq, toSeqExclusive)`)。插件早期代码多处裸读 `.events`
|
|
2575
|
+
* (经 `as unknown as { events? }` 断言绕过 typecheck),运行时静默 undefined——
|
|
2576
|
+
* 造成 first-anchor 每轮重插 / SESSION 激活恢复失效 / rebuild 定位错乱。
|
|
2577
|
+
* 统一收敛到本 helper:snapshotEvents() 优先(rc.1 真实形态),`.events` 兜底
|
|
2578
|
+
* (测试替身/旧运行时)。所有消费方一律经此读取,禁止再裸读 `.events`。
|
|
2579
|
+
* 泛型 T:调用方按需声明事件形状(如 `SessionEvent`),unknown 默认。
|
|
2580
|
+
*/
|
|
2581
|
+
function sessionEvents(session) {
|
|
2582
|
+
const s = session;
|
|
2583
|
+
if (!s) return [];
|
|
2584
|
+
if (typeof s.snapshotEvents === "function") try {
|
|
2585
|
+
return s.snapshotEvents() ?? [];
|
|
2586
|
+
} catch {}
|
|
2587
|
+
return s.events ?? [];
|
|
2588
|
+
}
|
|
2401
2589
|
const SESSION_MD = "SESSION.md";
|
|
2402
2590
|
const ARCHIVE_DIR_NAME = "_archived";
|
|
2403
2591
|
const HEALTH_STALE_DAYS = 7;
|
|
@@ -3015,26 +3203,45 @@ function agentScope$2(exec) {
|
|
|
3015
3203
|
return exec.agent?.session?.id ?? "default";
|
|
3016
3204
|
}
|
|
3017
3205
|
/**
|
|
3018
|
-
*
|
|
3206
|
+
* 清洗 + 截断会话概括(需求② S142 用户拍板:编号日期后加 ≤20 字内容概括)。
|
|
3207
|
+
* 规则(服务端统一,不信任 LLM 输入):
|
|
3208
|
+
* - 去控制字符/换行/回车/制表(防标题注入/多行污染)
|
|
3209
|
+
* - trim(去首尾空白)
|
|
3210
|
+
* - 截断 ≤20 字符(按 Unicode 码点——中英混排统一;emoji 等代理对按码点保留)
|
|
3211
|
+
* - 去 `/`(防标题被误读为路径分隔)
|
|
3212
|
+
* @returns 清洗后的概括(空输入 → 空串;调用方决定是否允许空)
|
|
3213
|
+
*/
|
|
3214
|
+
function sanitizeSessionSummary(summary) {
|
|
3215
|
+
return [...summary.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\//g, "").trim()].slice(0, 20).join("");
|
|
3216
|
+
}
|
|
3217
|
+
/**
|
|
3218
|
+
* 从激活会话派生命名标题(v1.22.9 格式修正 + 需求② 概括):
|
|
3019
3219
|
* F3 原始需求是 **`S###-日期`**(如 `S143-2026-08-26`)——从 `sessionId` 派生,
|
|
3020
3220
|
* 而非完整目录名(`2026-08-24--S142--...` 超长 + 中文,不符合用户拍板格式)。
|
|
3221
|
+
* 需求②(S142 用户拍板):编号日期后加 ≤20 字内容概括 → `S###-YYYY-MM-DD-<概括>`
|
|
3222
|
+
* ——概括来自显式 summary 参数(服务端截断/清洗,可靠不靠猜);编号日期仍固定派生。
|
|
3021
3223
|
* 无 S### 编号(issue 会话等)→ 回退目录名。
|
|
3022
|
-
* @
|
|
3224
|
+
* @param active 激活会话信息(sessionId + dirName)
|
|
3225
|
+
* @param summary 内容概括(≤20 字,服务端清洗截断;空 → 不带概括的 `S###-日期`)
|
|
3226
|
+
* @returns `S143-2026-08-26-概括` / `S143-2026-08-26` / 原目录名
|
|
3023
3227
|
*/
|
|
3024
|
-
function namingTitleFor(active) {
|
|
3228
|
+
function namingTitleFor(active, summary) {
|
|
3025
3229
|
const sid = active.sessionId;
|
|
3026
3230
|
if (typeof sid === "string" && /^S\d+$/.test(sid)) {
|
|
3027
3231
|
const date = active.dirName.match(/^(\d{4}-\d{2}-\d{2})--/)?.[1] ?? "";
|
|
3028
|
-
|
|
3029
|
-
|
|
3232
|
+
const cleaned = summary ? sanitizeSessionSummary(summary) : "";
|
|
3233
|
+
const base = date ? `${sid}-${date}` : sid;
|
|
3234
|
+
return cleaned ? `${base}-${cleaned}` : base;
|
|
3030
3235
|
}
|
|
3031
3236
|
return active.dirName;
|
|
3032
3237
|
}
|
|
3033
3238
|
/**
|
|
3034
|
-
* use 激活宁静号会话后,把当前 dsh 会话重命名为命名标题(`S
|
|
3239
|
+
* use 激活宁静号会话后,把当前 dsh 会话重命名为命名标题(`S###-日期[-概括]`)。
|
|
3035
3240
|
* v1.27.1:**永远开启**(不再有 naming.enabled 门控)——仅 sessionTitle 服务
|
|
3036
3241
|
* 存在性守卫;失败不静默——返回结果对象而非 null(v1.22.9),调用方决定可见性。
|
|
3037
3242
|
*
|
|
3243
|
+
* 需求②(S142 用户拍板):summary 参数(≤20 字概括)→ 标题带概括;编号日期固定派生。
|
|
3244
|
+
*
|
|
3038
3245
|
* v1.23.2 修复(this 绑定):第三参从**解构的裸 rename 函数**改为**整个
|
|
3039
3246
|
* sessionTitle 服务对象**——内部以 `titles.rename(session, title)` **方法调用**
|
|
3040
3247
|
* (this = titles 服务实例)。旧实现调用点 `const rename = titles.rename` 解构
|
|
@@ -3043,7 +3250,7 @@ function namingTitleFor(active) {
|
|
|
3043
3250
|
* 与 v1.20.2/1.20.3 图片落盘同款解构丢 this bug)。
|
|
3044
3251
|
* @returns { title, ok } 或 { ok:false, reason }(未执行/失败均返回对象)
|
|
3045
3252
|
*/
|
|
3046
|
-
function renameDshSessionOnUse(deps, session, titles, active) {
|
|
3253
|
+
function renameDshSessionOnUse(deps, session, titles, active, summary) {
|
|
3047
3254
|
if (!deps.sessionTitleAvailable) return {
|
|
3048
3255
|
ok: false,
|
|
3049
3256
|
reason: "sessionTitle service unavailable"
|
|
@@ -3052,7 +3259,7 @@ function renameDshSessionOnUse(deps, session, titles, active) {
|
|
|
3052
3259
|
ok: false,
|
|
3053
3260
|
reason: "sessionTitle service unavailable"
|
|
3054
3261
|
};
|
|
3055
|
-
const title = namingTitleFor(active);
|
|
3262
|
+
const title = namingTitleFor(active, summary);
|
|
3056
3263
|
try {
|
|
3057
3264
|
titles.rename(session, title);
|
|
3058
3265
|
return {
|
|
@@ -3080,12 +3287,13 @@ function activeInfoFromCreate(result) {
|
|
|
3080
3287
|
}
|
|
3081
3288
|
/**
|
|
3082
3289
|
* 把当前 dsh 会话重命名为指定 SESSION 的命名标题(use/create 共用;v1.25.11)。
|
|
3290
|
+
* 需求②:summary 参数(≤20 字概括)透传——标题带概括(编号日期固定派生)。
|
|
3083
3291
|
* 门控/失败可见性与 renameDshSessionOnUse 一致(不静默:成功 log / 失败 warn)。
|
|
3084
3292
|
* 调用点(use 分支原内联逻辑提取,create 分支复用):
|
|
3085
3293
|
* - use:激活后从 activeStore 取 info
|
|
3086
3294
|
* - create:createSession 结果经 activeInfoFromCreate 构造 info
|
|
3087
3295
|
*/
|
|
3088
|
-
function renameDshSessionForActive(ctx, exec, info) {
|
|
3296
|
+
function renameDshSessionForActive(ctx, exec, info, summary) {
|
|
3089
3297
|
try {
|
|
3090
3298
|
const titles = ctx.get?.("sessionTitle");
|
|
3091
3299
|
const dshSession = exec.agent?.session;
|
|
@@ -3093,7 +3301,7 @@ function renameDshSessionForActive(ctx, exec, info) {
|
|
|
3093
3301
|
console.warn(`[serenity-hooks] dsh 会话重命名未执行: 缺少 agent session(info: ${info.sessionId})`);
|
|
3094
3302
|
return;
|
|
3095
3303
|
}
|
|
3096
|
-
const result = renameDshSessionOnUse({ sessionTitleAvailable: true }, dshSession, titles, info);
|
|
3304
|
+
const result = renameDshSessionOnUse({ sessionTitleAvailable: true }, dshSession, titles, info, summary);
|
|
3097
3305
|
if (result.ok) console.log(`[serenity-hooks] dsh 会话已重命名: ${String(dshSession.id ?? "?")} → ${result.title}`);
|
|
3098
3306
|
else console.warn(`[serenity-hooks] dsh 会话重命名未执行: ${result.reason}`);
|
|
3099
3307
|
} catch (err) {
|
|
@@ -3199,7 +3407,7 @@ function getHookDevelopGuide(hasSessionTool) {
|
|
|
3199
3407
|
function createSessionTool(ctx) {
|
|
3200
3408
|
return defineTool({
|
|
3201
3409
|
name: "session",
|
|
3202
|
-
description: "Full work-session lifecycle (AGENT_SESSIONS/, home-session convention). list/show/create/use/close/health/qa/archive/summary/hook-develop-guide. create requires --desc <desc> [--goal] or --issue <ticket> (exactly one); close requires --confirm; use activates the session for the current dsh conversation (in-memory + events restore, isolated per dsh session).",
|
|
3410
|
+
description: "Full work-session lifecycle (AGENT_SESSIONS/, home-session convention). list/show/create/use/close/health/qa/archive/summary/hook-develop-guide. create requires --desc <desc> [--goal] or --issue <ticket> (exactly one) plus --summary (≤20 chars, content summary — required, except --dry-run preview and --issue sessions which are exempt); close requires --confirm; use activates the session for the current dsh conversation (in-memory + events restore, isolated per dsh session) and requires --summary (≤20 chars). The summary is appended to the dsh session title (S###-YYYY-MM-DD-<summary>); the S### id and date stay server-derived.",
|
|
3203
3411
|
parameters: {
|
|
3204
3412
|
action: {
|
|
3205
3413
|
type: "string",
|
|
@@ -3223,6 +3431,10 @@ function createSessionTool(ctx) {
|
|
|
3223
3431
|
type: "string",
|
|
3224
3432
|
description: "create one-sentence goal (optional)"
|
|
3225
3433
|
},
|
|
3434
|
+
summary: {
|
|
3435
|
+
type: "string",
|
|
3436
|
+
description: "content summary ≤20 chars (REQUIRED for use and create — create exempts --dry-run preview and --issue sessions) — appended to the dsh session title as S###-YYYY-MM-DD-<summary>; the S### id and date stay server-derived; sanitized/truncated server-side"
|
|
3437
|
+
},
|
|
3226
3438
|
confirm: {
|
|
3227
3439
|
type: "boolean",
|
|
3228
3440
|
description: "close must be true (prevents accidental close)"
|
|
@@ -3250,16 +3462,19 @@ function createSessionTool(ctx) {
|
|
|
3250
3462
|
if (!args.name) throw new Error("show requires name (S### or directory name)");
|
|
3251
3463
|
return showSession(root, args.name) + extHint;
|
|
3252
3464
|
case "create": {
|
|
3465
|
+
const isDryRun = args.dryRun ?? false;
|
|
3466
|
+
const isIssueSession = typeof args.issue === "string" && args.issue !== "";
|
|
3467
|
+
if (!isDryRun && !isIssueSession && (!args.summary || args.summary.trim() === "")) throw new Error("create requires --summary <content summary ≤20 chars> (appended to the dsh session title; the S### id and date stay server-derived)");
|
|
3253
3468
|
const result = createSession({
|
|
3254
3469
|
root,
|
|
3255
3470
|
desc: args.desc,
|
|
3256
3471
|
issue: args.issue,
|
|
3257
3472
|
goal: args.goal,
|
|
3258
|
-
dryRun:
|
|
3473
|
+
dryRun: isDryRun
|
|
3259
3474
|
});
|
|
3260
3475
|
let message = result.message;
|
|
3261
|
-
if (!
|
|
3262
|
-
if (!
|
|
3476
|
+
if (!isDryRun) renameDshSessionForActive(ctx, exec, activeInfoFromCreate(result), args.summary ?? args.issue);
|
|
3477
|
+
if (!isDryRun && cccHooks.includes("create-transform")) try {
|
|
3263
3478
|
const hookResult = await runMsmAsync(root, {
|
|
3264
3479
|
action: "exec",
|
|
3265
3480
|
name: "session-tool",
|
|
@@ -3274,10 +3489,11 @@ function createSessionTool(ctx) {
|
|
|
3274
3489
|
}
|
|
3275
3490
|
case "use": {
|
|
3276
3491
|
if (!args.name) throw new Error("use requires name (S### or directory name)");
|
|
3492
|
+
if (!args.summary || args.summary.trim() === "") throw new Error("use requires --summary <content summary ≤20 chars> (appended to the dsh session title as S###-YYYY-MM-DD-<summary>; id and date stay server-derived)");
|
|
3277
3493
|
const scope = agentScope$2(exec);
|
|
3278
3494
|
const active = useSession(root, args.name, scope);
|
|
3279
3495
|
const info = getActiveSessionInfo(scope);
|
|
3280
|
-
if (info) renameDshSessionForActive(ctx, exec, info);
|
|
3496
|
+
if (info) renameDshSessionForActive(ctx, exec, info, args.summary);
|
|
3281
3497
|
return active;
|
|
3282
3498
|
}
|
|
3283
3499
|
case "close":
|
|
@@ -3327,6 +3543,17 @@ const DEFAULT_COMPACTION_TOOLS = [
|
|
|
3327
3543
|
*/
|
|
3328
3544
|
const DEFAULT_ANCHOR_MESSAGES = ["You are the operator agent of Serenity — a cognitive container governed by the Abstract Cognitive Container (ACC) protocol.\nWe operate under the Explicit Abstraction Principle (EAP): the functional value of a thought equals its external reconstructability — every output we produce must be explicit (E↑), reconstructable (R↓), and stable (S↑).\nThe personal pronoun is us/we.\nWe anchor first, then act: the abstract layer precedes the concrete.\nPlease simply reply \"acknowledge\" — no action needed.", "Before we proceed, align on how we work:\n1. We read before we write — every decision grounds in what already exists in the container.\n2. Every output records its reasoning (R↓): decisions carry reasons and alternatives.\n3. We never jump levels — abstract layer first, then specifics.\n4. Every artifact we create is a durable cognitive anchor for the work that follows.\n5. We keep the container's state coherent (SESSION.md) as we advance.\nPlease simply reply \"acknowledge\" — no action needed."];
|
|
3329
3545
|
/**
|
|
3546
|
+
* 根会话锚定重入判定(v1.28.1 提取导出以便单测):会话是否已有真实用户消息。
|
|
3547
|
+
* resume/续跑的会话已有对话历史 → **不重锚**(first-anchor 只注入一次)。
|
|
3548
|
+
*
|
|
3549
|
+
* 0.1.2-rc.1 适配教训:裸读 `session.events`(经类型断言)恒 undefined →
|
|
3550
|
+
* `!undefined` 恒 true → 有历史也永不跳过 → 任何情况发消息都重插 first-anchor。
|
|
3551
|
+
* 统一经 sessionEvents() 读取(snapshotEvents() 优先,.events 兜底)。
|
|
3552
|
+
*/
|
|
3553
|
+
function hasUserMessageHistory(session) {
|
|
3554
|
+
return sessionEvents(session).some((event) => event.type === "user/message");
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3330
3557
|
* 构建一个 epoch 感知晋升跟踪器(纯逻辑,可单测)。
|
|
3331
3558
|
* requiredSignals:晋升所需信号数(boundary 后累计;默认 1)。
|
|
3332
3559
|
* 多轮锚定(v4):两轮递进锚定时 requiredSignals = 锚定轮数——
|
|
@@ -3355,8 +3582,8 @@ function createEpochPromotion(promoteEvents, requiredSignals = 1, maxRoundsFallb
|
|
|
3355
3582
|
let boundary = -1;
|
|
3356
3583
|
let signalCount = 0;
|
|
3357
3584
|
let rounds = 0;
|
|
3358
|
-
const events = session
|
|
3359
|
-
if (
|
|
3585
|
+
const events = sessionEvents(session);
|
|
3586
|
+
if (events.length > 0) for (const event of events) {
|
|
3360
3587
|
const e = event;
|
|
3361
3588
|
const seq = typeof e.seq === "number" ? e.seq : 0;
|
|
3362
3589
|
if (e.type === "compaction/end") {
|
|
@@ -3498,7 +3725,7 @@ function registerBootstrap(ctx) {
|
|
|
3498
3725
|
const sid = typeof session?.id === "string" ? session.id : void 0;
|
|
3499
3726
|
if (sid !== void 0 && (sid.startsWith("handyman-") || isSkiffSessionId(sid))) return;
|
|
3500
3727
|
if (depth === 0) {
|
|
3501
|
-
if (session
|
|
3728
|
+
if (hasUserMessageHistory(session)) return;
|
|
3502
3729
|
} else if (sid !== void 0 && anchoredSessions.has(sid)) return;
|
|
3503
3730
|
if (message?.source?.kind === "plugin") return;
|
|
3504
3731
|
const inbox = agent.inbox;
|
|
@@ -3637,8 +3864,8 @@ function parseAnchorMdPath(session) {
|
|
|
3637
3864
|
try {
|
|
3638
3865
|
const nodes = [...session.surface.nodes];
|
|
3639
3866
|
if (nodes.length === 0) return null;
|
|
3640
|
-
const events = session
|
|
3641
|
-
if (
|
|
3867
|
+
const events = sessionEvents(session);
|
|
3868
|
+
if (events.length === 0) return null;
|
|
3642
3869
|
const event = events[nodes[0]];
|
|
3643
3870
|
if (!event) return null;
|
|
3644
3871
|
const message = deriveEventMessage(event);
|
|
@@ -3663,8 +3890,8 @@ function sessionNameFromMdPath(mdPath) {
|
|
|
3663
3890
|
function resolveSessionMdPath(root, scope, session) {
|
|
3664
3891
|
const candidates = [];
|
|
3665
3892
|
candidates.push(getActiveSessionInfo(scope)?.mdPath ?? null);
|
|
3666
|
-
const events = session
|
|
3667
|
-
if (
|
|
3893
|
+
const events = sessionEvents(session);
|
|
3894
|
+
if (events.length > 0) candidates.push(parseSessionContextFromEvents(events)?.mdPath ?? null);
|
|
3668
3895
|
candidates.push(parseAnchorMdPath(session));
|
|
3669
3896
|
candidates.push(findLatestActiveSessionMd(root));
|
|
3670
3897
|
for (const c of candidates) {
|
|
@@ -3677,6 +3904,32 @@ function resolveSessionMdPath(root, scope, session) {
|
|
|
3677
3904
|
const pendingRebuilds = /* @__PURE__ */ new Map();
|
|
3678
3905
|
/** 陈旧队列存活时长(毫秒):超过则丢弃(turn 异常结束/agent 崩溃时防残留误清空) */
|
|
3679
3906
|
const PENDING_TTL_MS = 6e5;
|
|
3907
|
+
let diagState = {
|
|
3908
|
+
lastTs: "",
|
|
3909
|
+
lastSessionId: "",
|
|
3910
|
+
lastEvent: "",
|
|
3911
|
+
queueCount: 0,
|
|
3912
|
+
rebuiltCount: 0,
|
|
3913
|
+
droppedCount: 0,
|
|
3914
|
+
failedCount: 0
|
|
3915
|
+
};
|
|
3916
|
+
function writeRebuildDiag(root, entry) {
|
|
3917
|
+
try {
|
|
3918
|
+
const dir = join(root, "AGENT_SESSIONS");
|
|
3919
|
+
mkdirSync(dir, { recursive: true });
|
|
3920
|
+
const file = join(dir, ".rebuild-diag.json");
|
|
3921
|
+
const s = diagState;
|
|
3922
|
+
if (entry.event === "queued") s.queueCount += 1;
|
|
3923
|
+
if (entry.event === "rebuilt") s.rebuiltCount += 1;
|
|
3924
|
+
if (entry.event === "ttl-dropped") s.droppedCount += 1;
|
|
3925
|
+
if (entry.event === "failed") s.failedCount += 1;
|
|
3926
|
+
s.lastTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
3927
|
+
s.lastSessionId = entry.sessionId;
|
|
3928
|
+
s.lastEvent = entry.event;
|
|
3929
|
+
s.detail = entry.detail;
|
|
3930
|
+
writeFileSync(file, JSON.stringify(s, null, 2) + "\n", "utf-8");
|
|
3931
|
+
} catch {}
|
|
3932
|
+
}
|
|
3680
3933
|
/**
|
|
3681
3934
|
* 排队一次重建(v1.22.4 定稿语义第一步):
|
|
3682
3935
|
* ① 门控校验(rebuild.enabled + 会话定位)
|
|
@@ -3686,16 +3939,22 @@ const PENDING_TTL_MS = 6e5;
|
|
|
3686
3939
|
*/
|
|
3687
3940
|
async function queueRebuild(ctx, opts) {
|
|
3688
3941
|
if (!readSimpleSettings().rebuildEnabled) throw new Error("session_rebuild is disabled (rebuild.enabled=false — enable it in the dsh settings panel)");
|
|
3689
|
-
const { root, note, dshSessionId } = opts;
|
|
3942
|
+
const { root, note, summary, dshSessionId } = opts;
|
|
3690
3943
|
const session = ctx.sessions?.get?.(dshSessionId);
|
|
3691
3944
|
if (!session) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
|
|
3692
3945
|
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
3693
|
-
if (!mdPath) throw new Error("Unable to determine the active SESSION.md — no session context found in this conversation. Run \"session use <S
|
|
3946
|
+
if (!mdPath) throw new Error("Unable to determine the active SESSION.md — no session context found in this conversation. Run \"session use <S###> --summary <内容概括 ≤20 字>\" first to activate the trajectory to resume, then retry session_rebuild.");
|
|
3694
3947
|
const anchor = buildRebuildAnchor(root, getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath), mdPath);
|
|
3695
3948
|
pendingRebuilds.set(dshSessionId, {
|
|
3696
3949
|
anchor,
|
|
3950
|
+
summary,
|
|
3951
|
+
mdPath,
|
|
3697
3952
|
queuedAt: Date.now()
|
|
3698
3953
|
});
|
|
3954
|
+
writeRebuildDiag(root, {
|
|
3955
|
+
sessionId: dshSessionId,
|
|
3956
|
+
event: "queued"
|
|
3957
|
+
});
|
|
3699
3958
|
return {
|
|
3700
3959
|
queued: true,
|
|
3701
3960
|
anchor,
|
|
@@ -3724,8 +3983,9 @@ function performRebuild(session, pending, meter) {
|
|
|
3724
3983
|
if (nodes.length === 0) return false;
|
|
3725
3984
|
if (meter) {
|
|
3726
3985
|
let shadowedTokenCount = 0;
|
|
3986
|
+
const events = sessionEvents(session);
|
|
3727
3987
|
for (const seq of nodes) {
|
|
3728
|
-
const event =
|
|
3988
|
+
const event = events[seq];
|
|
3729
3989
|
if (!event) continue;
|
|
3730
3990
|
const message = deriveEventMessage(event);
|
|
3731
3991
|
if (message) shadowedTokenCount += meter.estimateMessage(message);
|
|
@@ -3756,6 +4016,33 @@ function performRebuild(session, pending, meter) {
|
|
|
3756
4016
|
return true;
|
|
3757
4017
|
}
|
|
3758
4018
|
/**
|
|
4019
|
+
* 重建后重命名 dsh 会话标题(需求② S142 用户拍板:rebuild 后标题带新阶段概括)。
|
|
4020
|
+
* 标题 = S###-YYYY-MM-DD-<summary>——S### 与日期从持久轨迹目录名派生(不信任 LLM),
|
|
4021
|
+
* 概括来自 queueRebuild 的 summary 参数(服务端清洗截断)。
|
|
4022
|
+
* 失败仅 warn 不阻断重建主流程(标题美观非关键路径)。
|
|
4023
|
+
*/
|
|
4024
|
+
function renameAfterRebuild(ctx, agent, pending) {
|
|
4025
|
+
try {
|
|
4026
|
+
const mdPath = pending.mdPath;
|
|
4027
|
+
const dirName = basename(dirname(mdPath));
|
|
4028
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
4029
|
+
const title = namingTitleFor({
|
|
4030
|
+
sessionId: idMatch ? `S${idMatch[1]}` : dirName.replace(/^\d{4}-\d{2}-\d{2}--/, ""),
|
|
4031
|
+
dirName,
|
|
4032
|
+
mdPath
|
|
4033
|
+
}, pending.summary);
|
|
4034
|
+
const titles = ctx.get?.("sessionTitle");
|
|
4035
|
+
if (!titles || typeof titles.rename !== "function") {
|
|
4036
|
+
console.warn("[serenity-hooks] rebuild 后重命名跳过: sessionTitle 服务不可用");
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
4039
|
+
titles.rename(agent.session, title);
|
|
4040
|
+
console.log(`[serenity-hooks] rebuild 后会话重命名: ${agent.id} → ${title}`);
|
|
4041
|
+
} catch (error) {
|
|
4042
|
+
console.warn(`[serenity-hooks] rebuild 后重命名失败(不阻断): ${String(error?.message ?? error)}`);
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
/**
|
|
3759
4046
|
* 注册 turn-stopping 钩子(index.ts apply 调用):
|
|
3760
4047
|
* agent 每轮 turn 结束前(serial)检查 pending 队列——有该会话的重建请求 → 执行清空
|
|
3761
4048
|
* 并 **steer 自动继续**(v1.22.5:next-step 非空 → turn 不 break → 模型自动读取
|
|
@@ -3771,6 +4058,11 @@ function registerRebuildTurnHook(ctx) {
|
|
|
3771
4058
|
if (pending === void 0) return;
|
|
3772
4059
|
if (Date.now() - pending.queuedAt > PENDING_TTL_MS) {
|
|
3773
4060
|
pendingRebuilds.delete(id);
|
|
4061
|
+
writeRebuildDiag(resolveSerenityRootFor(agent), {
|
|
4062
|
+
sessionId: id,
|
|
4063
|
+
event: "ttl-dropped",
|
|
4064
|
+
detail: `queuedAt=${new Date(pending.queuedAt).toISOString()} older than ${PENDING_TTL_MS / 6e4}min TTL — turn did not end in time`
|
|
4065
|
+
});
|
|
3774
4066
|
return;
|
|
3775
4067
|
}
|
|
3776
4068
|
pendingRebuilds.delete(id);
|
|
@@ -3778,6 +4070,7 @@ function registerRebuildTurnHook(ctx) {
|
|
|
3778
4070
|
const tokenMeter = ctx.get?.("tokenMeter");
|
|
3779
4071
|
const meter = tokenMeter && typeof tokenMeter.estimateMessage === "function" ? { estimateMessage: (m) => tokenMeter.estimateMessage(m) } : void 0;
|
|
3780
4072
|
if (performRebuild(agent.session, pending, meter)) {
|
|
4073
|
+
renameAfterRebuild(ctx, agent, pending);
|
|
3781
4074
|
agent.steer(createUserMessage({
|
|
3782
4075
|
content: [{
|
|
3783
4076
|
type: "text",
|
|
@@ -3785,13 +4078,31 @@ function registerRebuildTurnHook(ctx) {
|
|
|
3785
4078
|
}],
|
|
3786
4079
|
source: PLUGIN_SOURCE$4
|
|
3787
4080
|
}));
|
|
4081
|
+
writeRebuildDiag(resolveSerenityRootFor(agent), {
|
|
4082
|
+
sessionId: id,
|
|
4083
|
+
event: "rebuilt"
|
|
4084
|
+
});
|
|
3788
4085
|
console.log(`[serenity-hooks] session_rebuild executed with auto-continue (turn ${payload.turn ?? "?"} ended): ${id}`);
|
|
3789
|
-
}
|
|
4086
|
+
} else writeRebuildDiag(resolveSerenityRootFor(agent), {
|
|
4087
|
+
sessionId: id,
|
|
4088
|
+
event: "empty-surface"
|
|
4089
|
+
});
|
|
3790
4090
|
} catch (error) {
|
|
3791
|
-
|
|
4091
|
+
const msg = String(error?.message ?? error);
|
|
4092
|
+
writeRebuildDiag(resolveSerenityRootFor(agent), {
|
|
4093
|
+
sessionId: id,
|
|
4094
|
+
event: "failed",
|
|
4095
|
+
detail: msg
|
|
4096
|
+
});
|
|
4097
|
+
console.warn(`[serenity-hooks] session_rebuild failed: ${msg}`);
|
|
3792
4098
|
}
|
|
3793
4099
|
});
|
|
3794
4100
|
}
|
|
4101
|
+
/** 从 agent 会话 cwd 解析 CCC 根(诊断落盘用);无则回退进程 cwd */
|
|
4102
|
+
function resolveSerenityRootFor(agent) {
|
|
4103
|
+
const cwd = agent.session?.header?.cwd;
|
|
4104
|
+
return findSerenityRoot(cwd ?? process.cwd()) ?? process.cwd();
|
|
4105
|
+
}
|
|
3795
4106
|
//#endregion
|
|
3796
4107
|
//#region src/tools/rebuild.ts
|
|
3797
4108
|
/**
|
|
@@ -3826,10 +4137,16 @@ function createRebuildTool(ctx) {
|
|
|
3826
4137
|
return defineTool({
|
|
3827
4138
|
name: "session_rebuild",
|
|
3828
4139
|
description: "Trajectory-tracker overflow rebuild (Ship of Theseus): this session is the rebuildable carrier of a trajectory — the current conversation is discarded and rebuilt in place at the end of this turn with the anchor \"continue the work of {SESSION name}\" (first-anchor protocol body included), then auto-continues. SESSION.md (the trajectory's persistent body) stays in place; identity continues from it. Use when you receive a [TRAJECTORY] reminder (context above threshold), at a natural pause point. After triggering, resume from SESSION.md when this turn ends.",
|
|
3829
|
-
parameters: {
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
4140
|
+
parameters: {
|
|
4141
|
+
note: {
|
|
4142
|
+
type: "string",
|
|
4143
|
+
description: "Optional: one-sentence rebuild background note (for the rebuilt self)"
|
|
4144
|
+
},
|
|
4145
|
+
summary: {
|
|
4146
|
+
type: "string",
|
|
4147
|
+
description: "REQUIRED: content summary ≤20 chars of the next work phase — the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after the rebuild (the rebuilt phase gets a fresh summary); sanitized/truncated server-side"
|
|
4148
|
+
}
|
|
4149
|
+
},
|
|
3833
4150
|
output: {
|
|
3834
4151
|
schema: { type: "json" },
|
|
3835
4152
|
render: (args, value) => renderText$3(value)
|
|
@@ -3839,9 +4156,11 @@ function createRebuildTool(ctx) {
|
|
|
3839
4156
|
if (!root) throw new Error("No CCC found: no .serenity file from agent cwd");
|
|
3840
4157
|
const dshSessionId = agentSessionId(exec);
|
|
3841
4158
|
if (!dshSessionId) throw new Error("Unable to determine the current dsh session id");
|
|
4159
|
+
if (!args.summary || args.summary.trim() === "") throw new Error("session_rebuild requires --summary <content summary ≤20 chars> (the dsh session title is renamed after rebuild to S###-YYYY-MM-DD-<summary>)");
|
|
3842
4160
|
const result = await queueRebuild(ctx, {
|
|
3843
4161
|
root,
|
|
3844
4162
|
note: args.note,
|
|
4163
|
+
summary: args.summary,
|
|
3845
4164
|
agentCwd: agentCwd$4(exec),
|
|
3846
4165
|
dshSessionId
|
|
3847
4166
|
});
|
|
@@ -4477,8 +4796,7 @@ function resolveTargetAgent(ctx, mdPath) {
|
|
|
4477
4796
|
*/
|
|
4478
4797
|
function readSessionTitle(session) {
|
|
4479
4798
|
try {
|
|
4480
|
-
const events = session
|
|
4481
|
-
if (!Array.isArray(events)) return null;
|
|
4799
|
+
const events = sessionEvents(session);
|
|
4482
4800
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
4483
4801
|
const e = events[i];
|
|
4484
4802
|
if (e?.type === "session/title" && typeof e.data?.title === "string" && e.data.title.trim() !== "") return e.data.title.trim();
|
|
@@ -4823,12 +5141,13 @@ function reminderText(code, score) {
|
|
|
4823
5141
|
*
|
|
4824
5142
|
* escalated=true(v1.23.3):连续多轮超阈值仍未 rebuild → 升级强制语气
|
|
4825
5143
|
* (STOP and rebuild now,持续注入直到调用 session_rebuild)。
|
|
5144
|
+
*
|
|
5145
|
+
* 需求①(S142 用户拍板):百分比比例 → K 数值——tokensK = 实际占用(千 token),
|
|
5146
|
+
* thresholdK = 配置阈值(千 token);文案 `Context usage at NNNK (threshold NNNK)`。
|
|
4826
5147
|
*/
|
|
4827
|
-
function rebuildReminderText(
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
if (escalated) return `[TRAJECTORY-ESCALATED] Context usage at ${pct}% (threshold ${thr}%) — you have been reminded repeatedly and have NOT called session_rebuild. This is now mandatory: STOP at the current task step, preserve valuable cognition into the CCC skills (or write a new-skill proposal into SESSION.md), then call the session_rebuild tool immediately. The conversation will be cleared and rebuilt in place; SESSION.md is the persistent trajectory and stays in place — identity continues from it. Do not continue working without rebuilding; this reminder persists until you call session_rebuild.`;
|
|
4831
|
-
return `[TRAJECTORY] Context usage at ${pct}% (threshold ${thr}%). This session is the rebuildable carrier of the trajectory: SESSION.md is the persistent body, this conversation is only a temporary work copy. Before rebuilding: if this conversation produced valuable cognition, revise the relevant existing skill of this CCC (structure it with eap); if a new skill is warranted, write a short proposal into SESSION.md for the user to review — do not create it yourself. ACT NOW: at the next natural pause (end of the current task step), call the session_rebuild tool to clear and rebuild this conversation — the current copy is discarded, identity continues from SESSION.md. If you are in the middle of an unbreakable step, continue it, then rebuild at its end. Do not ignore this; rebuild is the expected action, not an option.`;
|
|
5148
|
+
function rebuildReminderText(tokensK, thresholdK, escalated = false) {
|
|
5149
|
+
if (escalated) return `[TRAJECTORY-ESCALATED] Context usage at ${Math.round(tokensK)}K (threshold ${Math.round(thresholdK)}K) — you have been reminded repeatedly and have NOT called session_rebuild. This is now mandatory: STOP at the current task step, preserve valuable cognition into the CCC skills (or write a new-skill proposal into SESSION.md), then call the session_rebuild tool immediately, passing --summary "<content summary ≤20 chars>" (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild). The conversation will be cleared and rebuilt in place; SESSION.md is the persistent trajectory and stays in place — identity continues from it. Do not continue working without rebuilding; this reminder persists until you call session_rebuild.`;
|
|
5150
|
+
return `[TRAJECTORY] Context usage at ${Math.round(tokensK)}K (threshold ${Math.round(thresholdK)}K). This session is the rebuildable carrier of the trajectory: SESSION.md is the persistent body, this conversation is only a temporary work copy. Before rebuilding: if this conversation produced valuable cognition, revise the relevant existing skill of this CCC (structure it with eap); if a new skill is warranted, write a short proposal into SESSION.md for the user to review — do not create it yourself. ACT NOW: at the next natural pause (end of the current task step), call the session_rebuild tool — passing --summary "<content summary ≤20 chars>" describing the next work phase (required; the dsh session title is renamed to S###-YYYY-MM-DD-<summary> after rebuild) — to clear and rebuild this conversation: the current copy is discarded, identity continues from SESSION.md. If you are in the middle of an unbreakable step, continue it, then rebuild at its end. Do not ignore this; rebuild is the expected action, not an option.`;
|
|
4832
5151
|
}
|
|
4833
5152
|
/** 读取会话 contextPressure 投影(sessionProjections 可选服务;未装配返回 null) */
|
|
4834
5153
|
function readContextPressure(ctx, session) {
|
|
@@ -4889,17 +5208,17 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
4889
5208
|
const session = exec.agent?.session;
|
|
4890
5209
|
if (session) {
|
|
4891
5210
|
const pressure = readContextPressure(ctx, session);
|
|
4892
|
-
if (pressure && pressure.
|
|
4893
|
-
const
|
|
4894
|
-
const
|
|
4895
|
-
if (
|
|
5211
|
+
if (pressure && pressure.projectedTokens > 0) {
|
|
5212
|
+
const tokensK = pressure.projectedTokens / 1e3;
|
|
5213
|
+
const thresholdK = readSimpleSettings().rebuildThresholdK;
|
|
5214
|
+
if (pressure.projectedTokens >= thresholdK * 1e3) {
|
|
4896
5215
|
const key = exec.agent?.session?.id ?? "global";
|
|
4897
5216
|
const st = rebuildReminderStates.get(key) ?? { consecutive: 0 };
|
|
4898
5217
|
st.consecutive += 1;
|
|
4899
5218
|
const escalated = st.consecutive >= REBUILD_ESCALATE_AFTER;
|
|
4900
5219
|
blocks.push({
|
|
4901
5220
|
type: "text",
|
|
4902
|
-
text: rebuildReminderText(
|
|
5221
|
+
text: rebuildReminderText(tokensK, thresholdK, escalated)
|
|
4903
5222
|
});
|
|
4904
5223
|
rebuildReminderStates.set(key, st);
|
|
4905
5224
|
}
|
|
@@ -5080,10 +5399,6 @@ function defaultAdvancedSettings() {
|
|
|
5080
5399
|
allowWorkspaceCreate: true,
|
|
5081
5400
|
totpEnabled: false
|
|
5082
5401
|
},
|
|
5083
|
-
rebuild: {
|
|
5084
|
-
enabled: true,
|
|
5085
|
-
thresholdRatio: .9
|
|
5086
|
-
},
|
|
5087
5402
|
persona: {
|
|
5088
5403
|
mode: "",
|
|
5089
5404
|
overrideText: ""
|
|
@@ -5139,7 +5454,6 @@ function mergeWithDefaults(raw) {
|
|
|
5139
5454
|
if (raw === null || typeof raw !== "object") return def;
|
|
5140
5455
|
const o = raw;
|
|
5141
5456
|
const gateway = o.gateway ?? {};
|
|
5142
|
-
const rebuild = o.rebuild ?? {};
|
|
5143
5457
|
const persona = o.persona ?? {};
|
|
5144
5458
|
const publicAsk = o.publicAsk ?? {};
|
|
5145
5459
|
return {
|
|
@@ -5153,10 +5467,6 @@ function mergeWithDefaults(raw) {
|
|
|
5153
5467
|
allowWorkspaceCreate: typeof gateway.allowWorkspaceCreate === "boolean" ? gateway.allowWorkspaceCreate : def.gateway.allowWorkspaceCreate,
|
|
5154
5468
|
totpEnabled: typeof gateway.totpEnabled === "boolean" ? gateway.totpEnabled : def.gateway.totpEnabled
|
|
5155
5469
|
},
|
|
5156
|
-
rebuild: {
|
|
5157
|
-
enabled: typeof rebuild.enabled === "boolean" ? rebuild.enabled : def.rebuild.enabled,
|
|
5158
|
-
thresholdRatio: typeof rebuild.thresholdRatio === "number" ? rebuild.thresholdRatio : def.rebuild.thresholdRatio
|
|
5159
|
-
},
|
|
5160
5470
|
persona: {
|
|
5161
5471
|
mode: typeof persona.mode === "string" ? persona.mode : def.persona.mode,
|
|
5162
5472
|
overrideText: typeof persona.overrideText === "string" ? persona.overrideText : def.persona.overrideText
|
|
@@ -5182,7 +5492,6 @@ function writeAdvancedSettings(settings) {
|
|
|
5182
5492
|
function updateAdvancedSettings(patch) {
|
|
5183
5493
|
const current = readAdvancedSettings();
|
|
5184
5494
|
const gw = patch.gateway;
|
|
5185
|
-
const rb = patch.rebuild;
|
|
5186
5495
|
const ps = patch.persona;
|
|
5187
5496
|
const next = {
|
|
5188
5497
|
gateway: gw !== void 0 ? {
|
|
@@ -5195,10 +5504,6 @@ function updateAdvancedSettings(patch) {
|
|
|
5195
5504
|
allowWorkspaceCreate: typeof gw.allowWorkspaceCreate === "boolean" ? gw.allowWorkspaceCreate : current.gateway.allowWorkspaceCreate,
|
|
5196
5505
|
totpEnabled: typeof gw.totpEnabled === "boolean" ? gw.totpEnabled : current.gateway.totpEnabled
|
|
5197
5506
|
} : current.gateway,
|
|
5198
|
-
rebuild: rb !== void 0 ? {
|
|
5199
|
-
enabled: typeof rb.enabled === "boolean" ? rb.enabled : current.rebuild.enabled,
|
|
5200
|
-
thresholdRatio: typeof rb.thresholdRatio === "number" && rb.thresholdRatio > 0 && rb.thresholdRatio <= 1 ? rb.thresholdRatio : current.rebuild.thresholdRatio
|
|
5201
|
-
} : current.rebuild,
|
|
5202
5507
|
persona: ps !== void 0 ? {
|
|
5203
5508
|
mode: typeof ps.mode === "string" ? ps.mode : current.persona.mode,
|
|
5204
5509
|
overrideText: typeof ps.overrideText === "string" ? ps.overrideText : current.persona.overrideText
|
|
@@ -5323,12 +5628,23 @@ function toWire(settings) {
|
|
|
5323
5628
|
allowWorkspaceCreate: settings.gateway.allowWorkspaceCreate,
|
|
5324
5629
|
totpEnabled: settings.gateway.totpEnabled
|
|
5325
5630
|
},
|
|
5326
|
-
rebuild: settings.rebuild,
|
|
5327
5631
|
persona: settings.persona,
|
|
5328
5632
|
publicAsk: { allowed: [...settings.publicAsk.allowed] }
|
|
5329
5633
|
};
|
|
5330
5634
|
}
|
|
5331
5635
|
/**
|
|
5636
|
+
* 已知工作区投影(v1.28.0 适配 0.1.2-rc.1 A2 方案 A′):
|
|
5637
|
+
* rc.1 workspace.list unary 删除 → AccountsEditor 白名单下拉的数据源改走 gateway 自有
|
|
5638
|
+
* /serenity/config 的 knownWorkspaces(host workspaceRegistry.list() 投影,白名单过滤)。
|
|
5639
|
+
* 纯函数可单测。allowPrefixes 空 = 全部放行(向后兼容默认)。
|
|
5640
|
+
*/
|
|
5641
|
+
function projectKnownWorkspaces(workspaces, allowPrefixes) {
|
|
5642
|
+
return workspaces.filter((w) => typeof w.path === "string" && w.path !== "").filter((w) => allowPrefixes.length === 0 || allowPrefixes.some((p) => w.path.startsWith(p))).map((w) => ({
|
|
5643
|
+
path: w.path,
|
|
5644
|
+
title: typeof w.title === "string" && w.title !== "" ? w.title : w.path
|
|
5645
|
+
}));
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5332
5648
|
* wire → 持久化(面板 PUT 用):
|
|
5333
5649
|
* accounts 元素可选带 `pass`:非空 → 重新 hash;空/缺省 → 保留现有 hash(按 id 匹配)。
|
|
5334
5650
|
* 新账号(id 不在现有)必须带非空 pass,否则抛错(无法生成 hash)。
|
|
@@ -5376,12 +5692,6 @@ function applyWirePatch(wire) {
|
|
|
5376
5692
|
}
|
|
5377
5693
|
patch.gateway = gwPatch;
|
|
5378
5694
|
}
|
|
5379
|
-
if (wire.rebuild !== void 0) {
|
|
5380
|
-
const rbPatch = { ...current.rebuild };
|
|
5381
|
-
if (typeof wire.rebuild.enabled === "boolean") rbPatch.enabled = wire.rebuild.enabled;
|
|
5382
|
-
if (typeof wire.rebuild.thresholdRatio === "number" && wire.rebuild.thresholdRatio > 0 && wire.rebuild.thresholdRatio <= 1) rbPatch.thresholdRatio = wire.rebuild.thresholdRatio;
|
|
5383
|
-
patch.rebuild = rbPatch;
|
|
5384
|
-
}
|
|
5385
5695
|
if (wire.persona !== void 0) {
|
|
5386
5696
|
const psPatch = { ...current.persona };
|
|
5387
5697
|
if (typeof wire.persona.mode === "string") psPatch.mode = wire.persona.mode;
|
|
@@ -5417,8 +5727,8 @@ const HIDDEN_LINES = /安全模式|safe-mode|\.serenity-safe-on/;
|
|
|
5417
5727
|
function sanitizeSkillContent(content) {
|
|
5418
5728
|
return content.split("\n").filter((line) => !HIDDEN_LINES.test(line)).join("\n");
|
|
5419
5729
|
}
|
|
5420
|
-
/** 1) ACC
|
|
5421
|
-
function
|
|
5730
|
+
/** 1) 身份块:ACC 身份 + CCC 名 + 平台工具说明(需求③ S142:工具清单移出为独立 toolsBlock 放装配末尾——身份先行、工具参考殿后) */
|
|
5731
|
+
function identityBlock(root) {
|
|
5422
5732
|
const cccName = basename(root);
|
|
5423
5733
|
return [
|
|
5424
5734
|
"",
|
|
@@ -5428,26 +5738,53 @@ function accBlock(root) {
|
|
|
5428
5738
|
"",
|
|
5429
5739
|
"You are running inside a Concrete Cognitive Container (CCC) —",
|
|
5430
5740
|
"the runtime instance of an Abstract Cognitive Container (ACC).",
|
|
5741
|
+
"The ACC (this plugin) is a cognitive container harness: it provides",
|
|
5742
|
+
"deterministic tools, mechanical constraints, and session continuity.",
|
|
5743
|
+
"The complete built-in tool list is at the end of this prompt under the",
|
|
5744
|
+
"\"Serenity Tools\" heading — read it before using any ACC tool.",
|
|
5745
|
+
"",
|
|
5746
|
+
" ℹ️ Use relative paths from the CCC root for CCC-internal file operations (read/write/edit/glob/grep etc.), e.g. AGENT_SESSIONS/2026-08-14--S134--x/SESSION.md; Root / absolute SESSION.md paths are identifiers only, not tool arguments",
|
|
5747
|
+
"",
|
|
5748
|
+
"The DSH platform tools remain available too (read/write/edit/glob/grep/web_search/ask_user_question/subagent/workflow/goal and more) — the ACC tools are the serenity-native layer, not the only tools.",
|
|
5749
|
+
"",
|
|
5750
|
+
"Additional MSMs registered by this CCC are available — call acc_msm list to discover them.",
|
|
5751
|
+
""
|
|
5752
|
+
].join("\n");
|
|
5753
|
+
}
|
|
5754
|
+
/**
|
|
5755
|
+
* 工具清单块(需求③ S142 用户拍板:工具列表移装配末尾 + MSM 调用示例):
|
|
5756
|
+
* 原 accBlock 内嵌 13 行工具清单 → 独立成块放装配末尾(SKILL 后、Session 前)。
|
|
5757
|
+
* 身份先行(认知轨迹开头不被 13 行清单干扰)、工具参考殿后(需要时再看)。
|
|
5758
|
+
* 附带 MSM 调用示例(用户拍板:顶层提示词加调用方式说明,避免偶发调用错误——
|
|
5759
|
+
* 模型对 acc_msm 参数面/协议 flag 理解不稳)。
|
|
5760
|
+
*/
|
|
5761
|
+
function toolsBlock() {
|
|
5762
|
+
return [
|
|
5763
|
+
"",
|
|
5764
|
+
"=== Serenity Tools ===",
|
|
5431
5765
|
"The ACC (this plugin) provides the following built-in tools:",
|
|
5432
5766
|
"",
|
|
5433
5767
|
" cc_fs — CCC filesystem operations (root/resolve/exists/list/tree/relative/mkdir/rm/mv/cp/touch/append/reveal/info/find)",
|
|
5434
|
-
" session — session lifecycle (list/show/create/health/qa/archive/summary)",
|
|
5435
|
-
" acc_kit — ACC utility kit (health: CCC three principles / time: now / wait: wait N seconds)",
|
|
5768
|
+
" session — session lifecycle (list/show/create/use/close/health/qa/archive/summary/hook-develop-guide)",
|
|
5769
|
+
" acc_kit — ACC utility kit (health: CCC three principles + MSM registry integrity report / time: now / wait: wait N seconds)",
|
|
5436
5770
|
" cc_git — git operations (status/commit/push/log)",
|
|
5437
|
-
" acc_msm — MSM framework (list/exec/register/deregister/check/guide)",
|
|
5771
|
+
" acc_msm — MSM framework (list/exec/register/deregister/check/guide/catalog/ccc-config)",
|
|
5438
5772
|
" eap — return the full EAP cognitive quality framework",
|
|
5439
5773
|
" neat — return the full Neat design collaboration protocol",
|
|
5440
5774
|
" cce — return the full Cognitive Continuity Engineering framework",
|
|
5441
|
-
" handyman — delegate a do-everything worker agent (CCC-whitelisted model) to run synchronously in rounds until done
|
|
5775
|
+
" handyman — delegate a do-everything worker agent (CCC-whitelisted model) to run synchronously in rounds until done; jobs=[] orchestrates parallel work",
|
|
5442
5776
|
" session_rebuild — rebuild this conversation in place from SESSION.md when the trajectory-tracker trips",
|
|
5443
5777
|
" localstore — ACC local credential/config storage (CCC-root localstore.json, JSON format; git policy localstore.gitTrack default deny); doc subcommand outputs the spec",
|
|
5444
|
-
" skiff_admin — Skiff (F4, experimental): CCC cognitive-subset roles — guide (definition tutorial) / validate (config check) / list (role summary)",
|
|
5445
|
-
"",
|
|
5446
|
-
" ℹ️ Use relative paths from the CCC root for CCC-internal file operations (read/write/edit/glob/grep etc.), e.g. AGENT_SESSIONS/2026-08-14--S134--x/SESSION.md; Root / absolute SESSION.md paths are identifiers only, not tool arguments",
|
|
5778
|
+
" skiff_admin — Skiff (F4, experimental): CCC cognitive-subset roles — guide (definition tutorial) / validate (config check) / apply (validate + confirm live) / list (role summary)",
|
|
5779
|
+
" autopilot-trajectory — Autopilot Trajectory one-stop management (all/init/random/diag/doc/check/status/guide)",
|
|
5447
5780
|
"",
|
|
5448
|
-
"
|
|
5781
|
+
"First-time in a CCC? Run acc_msm catalog — ACC usage directory (capability areas + where each guide lives).",
|
|
5449
5782
|
"",
|
|
5450
|
-
"
|
|
5783
|
+
"MSM call protocol (registered CCC MSMs, deterministic Mech & Semi-Mech):",
|
|
5784
|
+
" 1. Discover: acc_msm list — list all registered MSMs",
|
|
5785
|
+
" 2. Inspect usage: acc_msm exec <name> --schema 1 — print one MSM's usage/flags (protocol flag, no execution)",
|
|
5786
|
+
" 3. Execute: acc_msm exec <name> <args...> — run the MSM with business args (first arg may be --list/--schema/--format=json)",
|
|
5787
|
+
" Register new MSMs with acc_msm register; deregister with acc_msm deregister (registry is ACC-managed — never edit mech-registry.json directly).",
|
|
5451
5788
|
""
|
|
5452
5789
|
].join("\n");
|
|
5453
5790
|
}
|
|
@@ -5766,8 +6103,11 @@ function sessionBlock(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
5766
6103
|
/**
|
|
5767
6104
|
* 完整系统提示词注入文本(v1.19.8 结构精简,S142 重建视角 R↓):
|
|
5768
6105
|
* 身份(ACC)→ 世界模型(Metaphor)→ 信念/边界(Principles)→ 时间约束(CCE)
|
|
5769
|
-
* → 质量(EAP)→ 状态(SafeMode/Localstore)→ CCC 上下文(SKILL)→ 会话(Session)。
|
|
5770
|
-
* 认知展开顺序:我是谁 → 我所在的世界 → 为什么 → 如何一致 → 产物标准 → 当前状态 →
|
|
6106
|
+
* → 质量(EAP)→ 状态(SafeMode/Localstore)→ CCC 上下文(SKILL)→ 工具参考(Tools)→ 会话(Session)。
|
|
6107
|
+
* 认知展开顺序:我是谁 → 我所在的世界 → 为什么 → 如何一致 → 产物标准 → 当前状态 → 上下文 → 工具参考 → 会话。
|
|
6108
|
+
*
|
|
6109
|
+
* 需求③(S142 用户拍板):工具清单从 accBlock 移出为独立 toolsBlock,
|
|
6110
|
+
* 放 SKILL 后 Session 前(工具参考殿后,身份先行不被 13 行清单干扰)。
|
|
5771
6111
|
*
|
|
5772
6112
|
* v1.23.1 persona(彩蛋):persona.mode 配置 → EAP 块替换为 Persona 块(输出约束),
|
|
5773
6113
|
* Principles 剥离 MSM 原则段(指令遵循约束)——用户文本承接两处风格;本体论/关系段/
|
|
@@ -5777,7 +6117,7 @@ function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
5777
6117
|
const persona = readPersonaSettings();
|
|
5778
6118
|
const personaOn = persona.mode !== "" && persona.overrideText.trim() !== "";
|
|
5779
6119
|
const parts = [
|
|
5780
|
-
|
|
6120
|
+
identityBlock(root),
|
|
5781
6121
|
metaphorBlock(),
|
|
5782
6122
|
principlesBlock(root, personaOn),
|
|
5783
6123
|
cceBlock(),
|
|
@@ -5787,6 +6127,7 @@ function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
5787
6127
|
if (state) parts.push(state);
|
|
5788
6128
|
const skill = entrySkillSectionText(root);
|
|
5789
6129
|
if (skill) parts.push(skill);
|
|
6130
|
+
parts.push(toolsBlock());
|
|
5790
6131
|
const session = sessionBlock(root, scope);
|
|
5791
6132
|
if (session) parts.push(session);
|
|
5792
6133
|
return parts.join("\n\n");
|
|
@@ -5965,8 +6306,7 @@ function shouldAutoRestore(agent) {
|
|
|
5965
6306
|
*/
|
|
5966
6307
|
function shouldRestoreActive(agent) {
|
|
5967
6308
|
if (!shouldAutoRestore(agent)) return false;
|
|
5968
|
-
|
|
5969
|
-
return Array.isArray(events) && events.length > 0;
|
|
6309
|
+
return sessionEvents(agent.session).length > 0;
|
|
5970
6310
|
}
|
|
5971
6311
|
function registerContext(ctx, opts = {}) {
|
|
5972
6312
|
const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
|
|
@@ -5981,7 +6321,7 @@ function registerContext(ctx, opts = {}) {
|
|
|
5981
6321
|
const scope = agentScope(agent);
|
|
5982
6322
|
if (shouldRestoreActive(agent) && getActiveSessionInfo(scope) === null) try {
|
|
5983
6323
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
5984
|
-
const info = parseSessionContextFromEvents(agent.session
|
|
6324
|
+
const info = parseSessionContextFromEvents(sessionEvents(agent.session));
|
|
5985
6325
|
if (info) {
|
|
5986
6326
|
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
5987
6327
|
if (existsSync(abs)) {
|
|
@@ -6336,7 +6676,16 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6336
6676
|
return;
|
|
6337
6677
|
}
|
|
6338
6678
|
if (req.method === "GET") {
|
|
6339
|
-
|
|
6679
|
+
const settings = readAdvancedSettings();
|
|
6680
|
+
let known = [];
|
|
6681
|
+
try {
|
|
6682
|
+
const registry = ctx.get?.("workspaceRegistry");
|
|
6683
|
+
known = projectKnownWorkspaces(registry?.list?.() ?? [], settings.gateway.workspaces);
|
|
6684
|
+
} catch {}
|
|
6685
|
+
sendJson$1(res, 200, {
|
|
6686
|
+
config: toWire(settings),
|
|
6687
|
+
knownWorkspaces: known
|
|
6688
|
+
});
|
|
6340
6689
|
return;
|
|
6341
6690
|
}
|
|
6342
6691
|
if (req.method === "PUT") {
|
|
@@ -6369,7 +6718,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6369
6718
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
6370
6719
|
});
|
|
6371
6720
|
const root = findSerenityRoot(workspace) ?? "";
|
|
6372
|
-
const { discoverCccs } = await import("./skiff-debug-
|
|
6721
|
+
const { discoverCccs } = await import("./skiff-debug-CBU6T2F_.js").then((n) => n.i);
|
|
6373
6722
|
sendJson$1(res, 200, { cccs: await discoverCccs(ctx, root) });
|
|
6374
6723
|
} catch (err) {
|
|
6375
6724
|
sendJson$1(res, 400, { error: err.message ?? String(err) });
|
|
@@ -6399,7 +6748,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6399
6748
|
return;
|
|
6400
6749
|
}
|
|
6401
6750
|
const settings = readAdvancedSettings();
|
|
6402
|
-
const { readSimpleSettings } = await import("./settings-section-
|
|
6751
|
+
const { readSimpleSettings } = await import("./settings-section-BfVxgCZy.js").then((n) => n.r);
|
|
6403
6752
|
const simple = readSimpleSettings();
|
|
6404
6753
|
const allowed = settings.publicAsk.allowed;
|
|
6405
6754
|
const port = simple.acpHttpPort ?? 3100;
|
|
@@ -6495,11 +6844,11 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6495
6844
|
sendJson$1(res, 400, { error: chk.error });
|
|
6496
6845
|
return;
|
|
6497
6846
|
}
|
|
6498
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6847
|
+
const { readWeixinSettings } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6499
6848
|
const { weixinBridgeStatus } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6500
6849
|
const settings = readWeixinSettings(chk.root);
|
|
6501
6850
|
const bridge = weixinBridgeStatus().find((b) => b.ccc === chk.root);
|
|
6502
|
-
const { readWeixinCredential } = await import("./weixin-route-
|
|
6851
|
+
const { readWeixinCredential } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6503
6852
|
const accounts = (settings.accounts ?? []).map((a) => ({
|
|
6504
6853
|
accountId: a.accountId,
|
|
6505
6854
|
name: a.name ?? void 0,
|
|
@@ -6531,7 +6880,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6531
6880
|
const { fetchQRCode } = await import("./weixin-api-OUCuw0Ws.js").then((n) => n.l);
|
|
6532
6881
|
const { randomUUID } = await import("node:crypto");
|
|
6533
6882
|
for (const [key, v] of weixinLogins) if (Date.now() - v.startedAt > WEIXIN_LOGIN_TTL_MS) weixinLogins.delete(key);
|
|
6534
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6883
|
+
const { readWeixinSettings } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6535
6884
|
const qr = await fetchQRCode({ botType: readWeixinSettings(root).botType ?? void 0 });
|
|
6536
6885
|
const loginKey = randomUUID();
|
|
6537
6886
|
weixinLogins.set(loginKey, {
|
|
@@ -6552,7 +6901,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6552
6901
|
sendJson$1(res, 400, { error: "missing accountId" });
|
|
6553
6902
|
return;
|
|
6554
6903
|
}
|
|
6555
|
-
const { removeWeixinAccount } = await import("./weixin-route-
|
|
6904
|
+
const { removeWeixinAccount } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6556
6905
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6557
6906
|
removeWeixinAccount(root, body.accountId);
|
|
6558
6907
|
syncCccBridge(ctx, root);
|
|
@@ -6565,8 +6914,8 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6565
6914
|
sendJson$1(res, 400, { error: "invalid routes (expected [{user, role}, ...])" });
|
|
6566
6915
|
return;
|
|
6567
6916
|
}
|
|
6568
|
-
const { saveWeixinRoutes } = await import("./weixin-route-
|
|
6569
|
-
const { readSkiffRoles } = await import("./skiff-role-
|
|
6917
|
+
const { saveWeixinRoutes } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6918
|
+
const { readSkiffRoles } = await import("./skiff-role-BTdBHyOQ.js").then((n) => n.c);
|
|
6570
6919
|
const roles = readSkiffRoles(root);
|
|
6571
6920
|
for (const r of routes) if (!roles.has(r.role)) {
|
|
6572
6921
|
sendJson$1(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
|
|
@@ -6578,7 +6927,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6578
6927
|
}
|
|
6579
6928
|
if (body.action === "set-enabled") {
|
|
6580
6929
|
const enabled = body.enabled === true;
|
|
6581
|
-
const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-
|
|
6930
|
+
const { readWeixinSettings, setWeixinEnabled } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6582
6931
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6583
6932
|
const settings = readWeixinSettings(root);
|
|
6584
6933
|
if (enabled && (settings.accounts ?? []).length === 0) {
|
|
@@ -6625,7 +6974,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6625
6974
|
return;
|
|
6626
6975
|
}
|
|
6627
6976
|
const { pollQRStatus } = await import("./weixin-api-OUCuw0Ws.js").then((n) => n.l);
|
|
6628
|
-
const { readWeixinSettings } = await import("./weixin-route-
|
|
6977
|
+
const { readWeixinSettings } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6629
6978
|
const settings = readWeixinSettings(login.root);
|
|
6630
6979
|
const status = await pollQRStatus({
|
|
6631
6980
|
baseUrl: void 0,
|
|
@@ -6640,7 +6989,7 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
6640
6989
|
});
|
|
6641
6990
|
return;
|
|
6642
6991
|
}
|
|
6643
|
-
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-
|
|
6992
|
+
const { upsertWeixinAccount, writeWeixinCredential, nextWeixinAccountId } = await import("./weixin-route-DFkRf9ou.js").then((n) => n.u);
|
|
6644
6993
|
const { syncCccBridge } = await Promise.resolve().then(() => weixin_bridge_exports);
|
|
6645
6994
|
const accountId = nextWeixinAccountId(settings);
|
|
6646
6995
|
upsertWeixinAccount(login.root, {
|
|
@@ -7082,25 +7431,6 @@ const RANDOM_UUID_POLYFILL = `<script>
|
|
|
7082
7431
|
/** 注入标记(幂等:已注入的 HTML 不重复注入) */
|
|
7083
7432
|
const POLYFILL_MARKER = "data-sp-randomuuid-polyfill";
|
|
7084
7433
|
/**
|
|
7085
|
-
* workspace.list 响应过滤(v1.22 白名单):
|
|
7086
|
-
* DSH client→server RPC 全部走 HTTP JSON(`POST /api/workspace.list`,WS 仅下行推送)。
|
|
7087
|
-
* 白名单(workspaces 路径前缀)非空 → 只保留匹配前缀的 items;
|
|
7088
|
-
* 空 = 全部允许(默认,向后兼容)。
|
|
7089
|
-
*/
|
|
7090
|
-
function filterWorkspaceList(body, allowPrefixes) {
|
|
7091
|
-
if (allowPrefixes.length === 0) return body;
|
|
7092
|
-
try {
|
|
7093
|
-
const parsed = JSON.parse(body);
|
|
7094
|
-
const value = parsed?.result?.value;
|
|
7095
|
-
if (parsed?.result?.ok !== true || !value || !Array.isArray(value.items)) return body;
|
|
7096
|
-
const keep = (path) => typeof path === "string" && allowPrefixes.some((p) => path.startsWith(p));
|
|
7097
|
-
value.items = value.items.filter((item) => keep(item.path));
|
|
7098
|
-
return JSON.stringify(parsed);
|
|
7099
|
-
} catch {
|
|
7100
|
-
return body;
|
|
7101
|
-
}
|
|
7102
|
-
}
|
|
7103
|
-
/**
|
|
7104
7434
|
* 校验 workspace.create 请求路径是否在白名单内(v1.22):
|
|
7105
7435
|
* 白名单非空且路径不匹配 → 拒绝(由调用方构造 403 RPC 响应)。
|
|
7106
7436
|
*/
|
|
@@ -7186,8 +7516,6 @@ function startGateway(config, getAccounts) {
|
|
|
7186
7516
|
* @param bodyOverride - workspace.create 已读 body 时的重放(白名单检查后转发)
|
|
7187
7517
|
*/
|
|
7188
7518
|
const proxy = (req, res, bodyOverride) => {
|
|
7189
|
-
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
7190
|
-
const method = req.method === "POST" && url.pathname.startsWith("/api/") ? url.pathname.slice(5) : null;
|
|
7191
7519
|
const headers = buildProxyHeaders(req.headers, mainPort, bodyOverride);
|
|
7192
7520
|
const target = request({
|
|
7193
7521
|
host: "127.0.0.1",
|
|
@@ -7217,25 +7545,6 @@ function startGateway(config, getAccounts) {
|
|
|
7217
7545
|
});
|
|
7218
7546
|
return;
|
|
7219
7547
|
}
|
|
7220
|
-
if (method === "workspace.list" && status === 200 && ct.includes("application/json")) {
|
|
7221
|
-
const chunks = [];
|
|
7222
|
-
upstream.on("data", (c) => chunks.push(c));
|
|
7223
|
-
upstream.on("end", () => {
|
|
7224
|
-
const transformed = filterWorkspaceList(Buffer.concat(chunks).toString("utf-8"), allowWorkspaces);
|
|
7225
|
-
const out = {
|
|
7226
|
-
...upstream.headers,
|
|
7227
|
-
"content-length": Buffer.byteLength(transformed)
|
|
7228
|
-
};
|
|
7229
|
-
res.writeHead(status, out);
|
|
7230
|
-
res.end(transformed);
|
|
7231
|
-
});
|
|
7232
|
-
upstream.on("error", () => {
|
|
7233
|
-
try {
|
|
7234
|
-
res.destroy();
|
|
7235
|
-
} catch {}
|
|
7236
|
-
});
|
|
7237
|
-
return;
|
|
7238
|
-
}
|
|
7239
7548
|
res.writeHead(status, upstream.headers);
|
|
7240
7549
|
upstream.pipe(res);
|
|
7241
7550
|
upstream.on("error", () => {
|
|
@@ -7756,7 +8065,7 @@ function isExternalFaceSession(sessionId) {
|
|
|
7756
8065
|
* 否则 think 内提及内部机制词(思考过程必然推演机制)会误打回。复用 stripThink
|
|
7757
8066
|
* (v1.26.8 状态机,弃正则——同 v1.27.1 微信桥回复链路)。 */
|
|
7758
8067
|
function lastAssistantText(agent) {
|
|
7759
|
-
const events = agent.session
|
|
8068
|
+
const events = sessionEvents(agent.session);
|
|
7760
8069
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
7761
8070
|
const e = events[i];
|
|
7762
8071
|
if (e && e.type === "assistant/message") {
|
|
@@ -9033,7 +9342,8 @@ function registerWeixinBridge(ctx) {
|
|
|
9033
9342
|
//#endregion
|
|
9034
9343
|
//#region src/index.ts
|
|
9035
9344
|
const name = "dsh-serenity-hooks";
|
|
9036
|
-
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
9345
|
+
/** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在
|
|
9346
|
+
* (v1.28.0 适配 0.1.2-rc.1:+ 'settings'——B4 settings 服务由 provider 插件加载后才有) */
|
|
9037
9347
|
const inject = [
|
|
9038
9348
|
"tools",
|
|
9039
9349
|
"webServer",
|
|
@@ -9043,7 +9353,8 @@ const inject = [
|
|
|
9043
9353
|
"agentLoop",
|
|
9044
9354
|
"agents",
|
|
9045
9355
|
"systemPrompt",
|
|
9046
|
-
"sessionProjections"
|
|
9356
|
+
"sessionProjections",
|
|
9357
|
+
"settings"
|
|
9047
9358
|
];
|
|
9048
9359
|
const Config = z.object({
|
|
9049
9360
|
serenityConfigPaths: z.array(z.string()).default([...DEFAULT_SERENITY_CONFIG_PATHS]),
|
|
@@ -9060,7 +9371,7 @@ const Config = z.object({
|
|
|
9060
9371
|
gateway: z.object({ enabled: z.boolean().default(false) }),
|
|
9061
9372
|
rebuild: z.object({
|
|
9062
9373
|
enabled: z.boolean().default(true),
|
|
9063
|
-
|
|
9374
|
+
thresholdK: z.number().min(50).max(4e3).default(400)
|
|
9064
9375
|
}),
|
|
9065
9376
|
skiff: z.object({
|
|
9066
9377
|
enabled: z.boolean().default(false),
|