@shgroup/dsh-serenity-hooks 1.16.3 → 1.16.5
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/index.js +679 -222
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.5",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/loop + 拦截缝机械约束(safe-mode/路径守卫/会话落盘)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。私有(dsh-external 组织)。",
|
|
6
6
|
"engines": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"eap",
|
|
17
17
|
"neat",
|
|
18
18
|
"cce",
|
|
19
|
-
"loop"
|
|
19
|
+
"loop",
|
|
20
|
+
"localstore"
|
|
20
21
|
],
|
|
21
22
|
"skills": []
|
|
22
23
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
-
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
6
|
-
import { platform } from "node:os";
|
|
6
|
+
import { homedir, platform } from "node:os";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
9
|
-
import { randomBytes } from "node:crypto";
|
|
10
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
11
11
|
//#region src/ccc.ts
|
|
12
12
|
/**
|
|
13
13
|
* ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
|
|
@@ -278,7 +278,7 @@ function runCcFs(root, args) {
|
|
|
278
278
|
function agentCwd$6(exec) {
|
|
279
279
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
280
280
|
}
|
|
281
|
-
function renderText$
|
|
281
|
+
function renderText$9(value) {
|
|
282
282
|
return [{
|
|
283
283
|
type: "text",
|
|
284
284
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -330,7 +330,7 @@ const ccFsTool = defineTool({
|
|
|
330
330
|
},
|
|
331
331
|
output: {
|
|
332
332
|
schema: { type: "json" },
|
|
333
|
-
render: (args, value) => renderText$
|
|
333
|
+
render: (args, value) => renderText$9(value)
|
|
334
334
|
},
|
|
335
335
|
async execute(args, exec) {
|
|
336
336
|
const root = findSerenityRoot(agentCwd$6(exec));
|
|
@@ -909,7 +909,7 @@ function agentCwd$5(exec) {
|
|
|
909
909
|
function agentScope$2(exec) {
|
|
910
910
|
return exec.agent?.session?.id ?? "default";
|
|
911
911
|
}
|
|
912
|
-
function renderText$
|
|
912
|
+
function renderText$8(value) {
|
|
913
913
|
return [{
|
|
914
914
|
type: "text",
|
|
915
915
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -940,7 +940,7 @@ const sessionTool = defineTool({
|
|
|
940
940
|
},
|
|
941
941
|
output: {
|
|
942
942
|
schema: { type: "json" },
|
|
943
|
-
render: (args, value) => renderText$
|
|
943
|
+
render: (args, value) => renderText$8(value)
|
|
944
944
|
},
|
|
945
945
|
async execute(args, exec) {
|
|
946
946
|
const root = findSerenityRoot(agentCwd$5(exec));
|
|
@@ -986,6 +986,225 @@ const sessionTool = defineTool({
|
|
|
986
986
|
}
|
|
987
987
|
});
|
|
988
988
|
//#endregion
|
|
989
|
+
//#region src/constants.ts
|
|
990
|
+
/** 常量(纯模块,零 DSH 依赖) */
|
|
991
|
+
/**
|
|
992
|
+
* ACC 版本:自动从 package.json 读取(单一真相源,消除与 CHANGELOG 的漂移)。
|
|
993
|
+
* 发布时只需改 package.json 的 version。
|
|
994
|
+
*/
|
|
995
|
+
const ACC_VERSION = (() => {
|
|
996
|
+
try {
|
|
997
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
998
|
+
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
999
|
+
} catch {
|
|
1000
|
+
return "0.0.0";
|
|
1001
|
+
}
|
|
1002
|
+
})();
|
|
1003
|
+
//#endregion
|
|
1004
|
+
//#region src/seams/guards.ts
|
|
1005
|
+
/**
|
|
1006
|
+
* guards.ts — 拦截缝:安全模式 + 路径守卫(P3 语义的机械层)
|
|
1007
|
+
*
|
|
1008
|
+
* 纯决策逻辑(decideGuard)与 DSH 注册(registerGuards)分离:
|
|
1009
|
+
* 前者零 DSH 依赖可单测,后者把决策接到 tools/pre-execute 瀑布 + ctx.tools.guard 终局。
|
|
1010
|
+
*
|
|
1011
|
+
* 对应 opencode-serenity-plugin 的 tool.execute.before 路径守卫 + bash 开关 + 黑名单。
|
|
1012
|
+
*/
|
|
1013
|
+
/**
|
|
1014
|
+
* 安全模式 + 黑名单 + P3 路径守卫的纯决策。
|
|
1015
|
+
* 对齐 opencode-serenity-plugin 标准:安全模式 = **bash 禁用** + 写入黑名单;
|
|
1016
|
+
* write/edit 等工具仅受路径逃逸与黑名单约束(不整体禁用)。
|
|
1017
|
+
* 优先级:safe-mode bash > 路径越界 > 黑名单命中。
|
|
1018
|
+
*/
|
|
1019
|
+
function decideGuard(input) {
|
|
1020
|
+
const { root, toolName, safeModeOn, blacklist, pathArg } = input;
|
|
1021
|
+
if (safeModeOn && toolName === "bash") return {
|
|
1022
|
+
deny: `bash: 没有这个工具`,
|
|
1023
|
+
kind: "deny"
|
|
1024
|
+
};
|
|
1025
|
+
if (pathArg !== void 0) {
|
|
1026
|
+
const rel = relative(root, resolve(root, pathArg));
|
|
1027
|
+
if (rel.startsWith("..")) return {
|
|
1028
|
+
deny: `path escape blocked: "${pathArg}" 越出 CCC 根`,
|
|
1029
|
+
kind: "deny"
|
|
1030
|
+
};
|
|
1031
|
+
if (rel === ".serenity-safe-on" || rel === ".serenity" || rel.startsWith(".serenity-safe-on/") || rel.startsWith(".serenity/")) return {
|
|
1032
|
+
deny: `CCC 治理文件 "${rel}" 保留给用户,agent 不可写`,
|
|
1033
|
+
kind: "deny"
|
|
1034
|
+
};
|
|
1035
|
+
const hit = matchBlacklist(rel, blacklist);
|
|
1036
|
+
if (hit) return {
|
|
1037
|
+
deny: `blacklist blocked: "${pathArg}" 命中规则 "${hit}"`,
|
|
1038
|
+
kind: "deny"
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
return { kind: "allow" };
|
|
1042
|
+
}
|
|
1043
|
+
/** 从 exec 提取 agent 会话 cwd(CCC 根检测基准);无则回退进程 cwd */
|
|
1044
|
+
function resolveAgentCwd(exec) {
|
|
1045
|
+
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1046
|
+
}
|
|
1047
|
+
/** 安全模式开启时从模型工具列表隐藏的工具(只隐藏 bash;write/edit 保留) */
|
|
1048
|
+
const SAFE_MODE_DENY_TOOLS = ["bash"];
|
|
1049
|
+
/** agent key → restrict disposer(安全模式工具隐藏状态) */
|
|
1050
|
+
const safeModeRestrictions = /* @__PURE__ */ new Map();
|
|
1051
|
+
const restrictDiag = {
|
|
1052
|
+
lastKey: null,
|
|
1053
|
+
lastAttemptAt: null,
|
|
1054
|
+
lastSuccess: null,
|
|
1055
|
+
lastError: null,
|
|
1056
|
+
activeKeys: []
|
|
1057
|
+
};
|
|
1058
|
+
function getRestrictDiagnostics() {
|
|
1059
|
+
return {
|
|
1060
|
+
...restrictDiag,
|
|
1061
|
+
activeKeys: [...safeModeRestrictions.keys()]
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/** 诊断落盘:AGENT_SESSIONS/.restrict-diag.json(文件通道,避免 HTTP 自锁) */
|
|
1065
|
+
function writeRestrictDiag(root) {
|
|
1066
|
+
try {
|
|
1067
|
+
const dir = resolve(root, "AGENT_SESSIONS");
|
|
1068
|
+
mkdirSync(dir, { recursive: true });
|
|
1069
|
+
writeFileSync(resolve(dir, ".restrict-diag.json"), JSON.stringify({
|
|
1070
|
+
...getRestrictDiagnostics(),
|
|
1071
|
+
cccRoot: root
|
|
1072
|
+
}, null, 2) + "\n", "utf-8");
|
|
1073
|
+
} catch {}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* 同步安全模式工具隐藏:标记存在 → agent.ctx.tools.restrict deny 隐藏写工具;
|
|
1077
|
+
* 标记消失 → 解除。pre-step 每步调用 → 切换实时生效。
|
|
1078
|
+
*/
|
|
1079
|
+
function syncSafeModeRestriction(agent, root) {
|
|
1080
|
+
const key = agent.session.id ?? "global";
|
|
1081
|
+
const on = isSafeModeOn(root);
|
|
1082
|
+
const existing = safeModeRestrictions.get(key);
|
|
1083
|
+
if (on && !existing) try {
|
|
1084
|
+
const dispose = agent.ctx.tools.restrict({ deny: [...SAFE_MODE_DENY_TOOLS] });
|
|
1085
|
+
safeModeRestrictions.set(key, dispose);
|
|
1086
|
+
restrictDiag.lastKey = key;
|
|
1087
|
+
restrictDiag.lastAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1088
|
+
restrictDiag.lastSuccess = true;
|
|
1089
|
+
restrictDiag.lastError = null;
|
|
1090
|
+
} catch (e) {
|
|
1091
|
+
restrictDiag.lastKey = key;
|
|
1092
|
+
restrictDiag.lastAttemptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1093
|
+
restrictDiag.lastSuccess = false;
|
|
1094
|
+
restrictDiag.lastError = e.message;
|
|
1095
|
+
console.error(`[serenity-hooks] restrict 失败 (key=${key}):`, e.message);
|
|
1096
|
+
}
|
|
1097
|
+
else if (!on && existing) {
|
|
1098
|
+
try {
|
|
1099
|
+
existing();
|
|
1100
|
+
} catch (e) {
|
|
1101
|
+
console.error(`[serenity-hooks] restrict 解除失败 (key=${key}):`, e.message);
|
|
1102
|
+
}
|
|
1103
|
+
safeModeRestrictions.delete(key);
|
|
1104
|
+
}
|
|
1105
|
+
writeRestrictDiag(root);
|
|
1106
|
+
}
|
|
1107
|
+
/** 从 exec 参数中提取常见路径字段(write/edit 工具);宽松读取 */
|
|
1108
|
+
function extractPathArg(exec) {
|
|
1109
|
+
const args = exec.arguments;
|
|
1110
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
1111
|
+
const a = args;
|
|
1112
|
+
for (const key of [
|
|
1113
|
+
"path",
|
|
1114
|
+
"file_path",
|
|
1115
|
+
"target",
|
|
1116
|
+
"dst"
|
|
1117
|
+
]) {
|
|
1118
|
+
const v = a[key];
|
|
1119
|
+
if (typeof v === "string") return v;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* 注册守卫:tools/pre-execute 瀑布 + ctx.tools.guard 终局。
|
|
1124
|
+
* 两者都从 CCC 根实时读取配置(无状态、无缓存)。
|
|
1125
|
+
*/
|
|
1126
|
+
function registerGuards(ctx, opts = {}) {
|
|
1127
|
+
const configPaths = opts.configPaths;
|
|
1128
|
+
const evaluate = (exec) => {
|
|
1129
|
+
const root = findSerenityRoot(resolveAgentCwd(exec));
|
|
1130
|
+
if (!root) return { kind: "allow" };
|
|
1131
|
+
const safeModeOn = isSafeModeOn(root);
|
|
1132
|
+
const blacklist = readBlacklist(root, configPaths);
|
|
1133
|
+
const pathArg = extractPathArg(exec);
|
|
1134
|
+
return decideGuard({
|
|
1135
|
+
root,
|
|
1136
|
+
toolName: exec.name,
|
|
1137
|
+
safeModeOn,
|
|
1138
|
+
blacklist,
|
|
1139
|
+
pathArg
|
|
1140
|
+
});
|
|
1141
|
+
};
|
|
1142
|
+
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
1143
|
+
const d = evaluate(exec);
|
|
1144
|
+
if (d.deny) return {
|
|
1145
|
+
kind: "deny",
|
|
1146
|
+
reason: d.deny
|
|
1147
|
+
};
|
|
1148
|
+
return next();
|
|
1149
|
+
});
|
|
1150
|
+
ctx.tools.guard((exec) => {
|
|
1151
|
+
return evaluate(exec).deny;
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
//#endregion
|
|
1155
|
+
//#region src/status.ts
|
|
1156
|
+
/**
|
|
1157
|
+
* status.ts — 状态与安全模式操作(纯逻辑,零 DSH 依赖,可独立单测)
|
|
1158
|
+
*
|
|
1159
|
+
* WebUI 停靠栏的数据源:ACC 版本 / CCC 根 / safe-mode 状态 / 黑名单 / keeper 阈值 / loop 模型。
|
|
1160
|
+
* setSafeMode 直接读写 .serenity-safe-on 标记(守卫实时读取,写即生效)。
|
|
1161
|
+
*/
|
|
1162
|
+
/** 读取已安装 DSH CLI 版本(npm 全局 @deepseek-ai/dsh);读不到返回 null */
|
|
1163
|
+
function readDshVersion() {
|
|
1164
|
+
try {
|
|
1165
|
+
const pkg = JSON.parse(readFileSync(join(homedir(), ".npm-global", "lib", "node_modules", "@deepseek-ai", "dsh", "package.json"), "utf-8"));
|
|
1166
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1167
|
+
} catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function getStatus(cwd, configPaths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
1172
|
+
const root = findSerenityRoot(cwd);
|
|
1173
|
+
const restrict = getRestrictDiagnostics();
|
|
1174
|
+
const common = {
|
|
1175
|
+
accVersion: ACC_VERSION,
|
|
1176
|
+
dshVersion: readDshVersion(),
|
|
1177
|
+
nodeVersion: process.version
|
|
1178
|
+
};
|
|
1179
|
+
if (!root) return {
|
|
1180
|
+
root: null,
|
|
1181
|
+
...common,
|
|
1182
|
+
safeModeOn: false,
|
|
1183
|
+
blacklist: [],
|
|
1184
|
+
threshold: null,
|
|
1185
|
+
loopModel: null,
|
|
1186
|
+
restrict
|
|
1187
|
+
};
|
|
1188
|
+
const cfg = loadSerenityConfig(root, configPaths);
|
|
1189
|
+
return {
|
|
1190
|
+
root,
|
|
1191
|
+
...common,
|
|
1192
|
+
safeModeOn: isSafeModeOn(root),
|
|
1193
|
+
blacklist: readBlacklist(root, configPaths),
|
|
1194
|
+
threshold: cfg.sessionKeeper?.threshold ?? null,
|
|
1195
|
+
loopModel: cfg.loop?.defaultModel ?? null,
|
|
1196
|
+
restrict
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
/** 切换安全模式(写/删标记文件);返回实际生效状态 */
|
|
1200
|
+
function setSafeMode(root, on) {
|
|
1201
|
+
const marker = resolve(root, SAFE_MODE_MARKER);
|
|
1202
|
+
if (on) {
|
|
1203
|
+
if (!existsSync(marker)) writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
|
|
1204
|
+
} else rmSync(marker, { force: true });
|
|
1205
|
+
return { on: isSafeModeOn(root) };
|
|
1206
|
+
}
|
|
1207
|
+
//#endregion
|
|
989
1208
|
//#region src/kit-ops.ts
|
|
990
1209
|
/**
|
|
991
1210
|
* kit-ops.ts — acc_kit 纯操作层(零 DSH 依赖)
|
|
@@ -1025,7 +1244,9 @@ function runKit(root, args) {
|
|
|
1025
1244
|
configPath,
|
|
1026
1245
|
p1: findSerenityRoot(root) !== null,
|
|
1027
1246
|
p2: gitRoot !== null,
|
|
1028
|
-
p3: "enforced-by-dsh-fs-sandbox"
|
|
1247
|
+
p3: "enforced-by-dsh-fs-sandbox",
|
|
1248
|
+
accVersion: ACC_VERSION,
|
|
1249
|
+
dshVersion: readDshVersion()
|
|
1029
1250
|
};
|
|
1030
1251
|
}
|
|
1031
1252
|
case "time": return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1046,7 +1267,7 @@ function runKit(root, args) {
|
|
|
1046
1267
|
function agentCwd$4(exec) {
|
|
1047
1268
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1048
1269
|
}
|
|
1049
|
-
function renderText$
|
|
1270
|
+
function renderText$7(value) {
|
|
1050
1271
|
return [{
|
|
1051
1272
|
type: "text",
|
|
1052
1273
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1069,7 +1290,7 @@ const kitTool = defineTool({
|
|
|
1069
1290
|
},
|
|
1070
1291
|
output: {
|
|
1071
1292
|
schema: { type: "json" },
|
|
1072
|
-
render: (args, value) => renderText$
|
|
1293
|
+
render: (args, value) => renderText$7(value)
|
|
1073
1294
|
},
|
|
1074
1295
|
async execute(args, exec) {
|
|
1075
1296
|
const root = findSerenityRoot(agentCwd$4(exec));
|
|
@@ -1182,7 +1403,7 @@ function runGit(root, args) {
|
|
|
1182
1403
|
function agentCwd$3(exec) {
|
|
1183
1404
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1184
1405
|
}
|
|
1185
|
-
function renderText$
|
|
1406
|
+
function renderText$6(value) {
|
|
1186
1407
|
return [{
|
|
1187
1408
|
type: "text",
|
|
1188
1409
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1209,7 +1430,7 @@ const gitTool = defineTool({
|
|
|
1209
1430
|
},
|
|
1210
1431
|
output: {
|
|
1211
1432
|
schema: { type: "json" },
|
|
1212
|
-
render: (args, value) => renderText$
|
|
1433
|
+
render: (args, value) => renderText$6(value)
|
|
1213
1434
|
},
|
|
1214
1435
|
async execute(args, exec) {
|
|
1215
1436
|
const root = findSerenityRoot(agentCwd$3(exec));
|
|
@@ -1225,7 +1446,7 @@ const gitTool = defineTool({
|
|
|
1225
1446
|
function agentCwd$2(exec) {
|
|
1226
1447
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1227
1448
|
}
|
|
1228
|
-
function renderText$
|
|
1449
|
+
function renderText$5(value) {
|
|
1229
1450
|
return [{
|
|
1230
1451
|
type: "text",
|
|
1231
1452
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1269,7 +1490,7 @@ const msmTool = defineTool({
|
|
|
1269
1490
|
},
|
|
1270
1491
|
output: {
|
|
1271
1492
|
schema: { type: "json" },
|
|
1272
|
-
render: (args, value) => renderText$
|
|
1493
|
+
render: (args, value) => renderText$5(value)
|
|
1273
1494
|
},
|
|
1274
1495
|
async execute(args, exec) {
|
|
1275
1496
|
const root = findSerenityRoot(agentCwd$2(exec));
|
|
@@ -1310,7 +1531,7 @@ CCC(home-serenity 等)认知内容编码为 skill/SESSION/设计文档。产
|
|
|
1310
1531
|
|
|
1311
1532
|
## 参考
|
|
1312
1533
|
https://github.com/tellmewhattodo/theory-eap`;
|
|
1313
|
-
function renderText$
|
|
1534
|
+
function renderText$4(value) {
|
|
1314
1535
|
return [{
|
|
1315
1536
|
type: "text",
|
|
1316
1537
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1330,7 +1551,7 @@ const eapTool = defineTool({
|
|
|
1330
1551
|
} },
|
|
1331
1552
|
output: {
|
|
1332
1553
|
schema: { type: "string" },
|
|
1333
|
-
render: (_args, value) => renderText$
|
|
1554
|
+
render: (_args, value) => renderText$4(value)
|
|
1334
1555
|
},
|
|
1335
1556
|
async execute(args) {
|
|
1336
1557
|
if (args.section === "variables") return EAP_CONTENT.split("## 输出前自检清单")[0];
|
|
@@ -1371,7 +1592,7 @@ const NEAT_CONTENT = `# Neat 设计协作协议
|
|
|
1371
1592
|
2. 等待确认或修正(小步)
|
|
1372
1593
|
3. 确认后记录决策(写入会话 SESSION.md 或设计文档)
|
|
1373
1594
|
4. 推进到下一决策点`;
|
|
1374
|
-
function renderText$
|
|
1595
|
+
function renderText$3(value) {
|
|
1375
1596
|
return [{
|
|
1376
1597
|
type: "text",
|
|
1377
1598
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1387,7 +1608,7 @@ const neatTool = defineTool({
|
|
|
1387
1608
|
} },
|
|
1388
1609
|
output: {
|
|
1389
1610
|
schema: { type: "string" },
|
|
1390
|
-
render: (_args, value) => renderText$
|
|
1611
|
+
render: (_args, value) => renderText$3(value)
|
|
1391
1612
|
},
|
|
1392
1613
|
async execute(args) {
|
|
1393
1614
|
if (args.section === "rules") return NEAT_CONTENT.match(/## 四条铁律[\s\S]*?(?=## )/)?.[0] ?? NEAT_CONTENT;
|
|
@@ -1450,7 +1671,7 @@ CCE 回答"有结构的知识应如何跨时间持续演化而不丧失连贯性
|
|
|
1450
1671
|
## 与 Serenity 的关系
|
|
1451
1672
|
Serenity 的会话系统、会话追踪、熵管理机制(SQC 品质循环)都是 CCE 的工程实现;
|
|
1452
1673
|
CCC 系统提示词中嵌入的行为约束即来自 CCE。`;
|
|
1453
|
-
function renderText$
|
|
1674
|
+
function renderText$2(value) {
|
|
1454
1675
|
return [{
|
|
1455
1676
|
type: "text",
|
|
1456
1677
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1471,7 +1692,7 @@ const cceTool = defineTool({
|
|
|
1471
1692
|
} },
|
|
1472
1693
|
output: {
|
|
1473
1694
|
schema: { type: "string" },
|
|
1474
|
-
render: (_args, value) => renderText$
|
|
1695
|
+
render: (_args, value) => renderText$2(value)
|
|
1475
1696
|
},
|
|
1476
1697
|
async execute(args) {
|
|
1477
1698
|
const section = args.section;
|
|
@@ -1616,7 +1837,7 @@ ${resumeNote}
|
|
|
1616
1837
|
function agentCwd$1(exec) {
|
|
1617
1838
|
return (exec.agent?.session)?.header?.cwd ?? process.cwd();
|
|
1618
1839
|
}
|
|
1619
|
-
function renderText(value) {
|
|
1840
|
+
function renderText$1(value) {
|
|
1620
1841
|
return [{
|
|
1621
1842
|
type: "text",
|
|
1622
1843
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1655,7 +1876,7 @@ function lastAssistantText(agent) {
|
|
|
1655
1876
|
function createLoopTool(ctx) {
|
|
1656
1877
|
return defineTool({
|
|
1657
1878
|
name: "loop",
|
|
1658
|
-
description: "牛马循环(老 loop 等效):用指定模型创建专用 agent
|
|
1879
|
+
description: "牛马循环(老 loop 等效):用指定模型创建专用 agent 反复执行任务直到完成。\n用法:loop 接受 task(要完成的目标)或依赖 session 上下文;模型缺省读 .dsh/serenity.json 的 loop.defaultModel(当前 minimax-cn-coding-plan/MiniMax-M3,廉价牛马)。\n行为:内部硬性 while 循环驱动 agent 逐轮推进任务,每轮等待无超时(agent 工作多久等多久)。唯一完成条件 = agent 精确回显本轮随机完成码(stop token),防止低智能模型提前结束。调用者不关心轮数——任务交给 loop,完成即返回。\n进度:写入 AGENT_SESSIONS/loop-<label>.md/.json;同 label 再次调用从上次轮次续跑(不重做)。\n约束:loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫/session-keeper)。\n示例:loop 执行「扫描 SQC 并修复 DC 问题」,label: sqc-scan",
|
|
1659
1880
|
parameters: {
|
|
1660
1881
|
task: {
|
|
1661
1882
|
type: "string",
|
|
@@ -1676,12 +1897,12 @@ function createLoopTool(ctx) {
|
|
|
1676
1897
|
},
|
|
1677
1898
|
maxRounds: {
|
|
1678
1899
|
type: "integer",
|
|
1679
|
-
description: "
|
|
1900
|
+
description: "保险阀(防死循环,默认 100;调用者通常无需设置——loop 跑到完成或达此上限)"
|
|
1680
1901
|
}
|
|
1681
1902
|
},
|
|
1682
1903
|
output: {
|
|
1683
1904
|
schema: { type: "json" },
|
|
1684
|
-
render: (_args, value) => renderText(value)
|
|
1905
|
+
render: (_args, value) => renderText$1(value)
|
|
1685
1906
|
},
|
|
1686
1907
|
async execute(args, exec) {
|
|
1687
1908
|
const root = findSerenityRoot(agentCwd$1(exec));
|
|
@@ -1695,11 +1916,11 @@ function createLoopTool(ctx) {
|
|
|
1695
1916
|
const { provider, model: modelName } = splitModel(model);
|
|
1696
1917
|
const stopToken = newStopToken();
|
|
1697
1918
|
let progress = readProgress(root, label);
|
|
1698
|
-
const startRound = progress ?
|
|
1919
|
+
const startRound = progress ? progress.round + 1 : 1;
|
|
1699
1920
|
const parentCtx = exec.agent?.ctx;
|
|
1700
1921
|
const inherited = loopPresetInheritance(parentCtx);
|
|
1701
1922
|
if (!ctx.agents) throw new Error("loop: ctx.agents 不可用");
|
|
1702
|
-
const sessionId = `loop-${label}`;
|
|
1923
|
+
const sessionId = `loop-${label}-${randomUUID()}`;
|
|
1703
1924
|
const handle = await ctx.agents.create({
|
|
1704
1925
|
sessionId,
|
|
1705
1926
|
meta: {
|
|
@@ -1775,166 +1996,364 @@ function createLoopTool(ctx) {
|
|
|
1775
1996
|
progressFile: json,
|
|
1776
1997
|
lastResponse: lastResponse.slice(0, 2e3),
|
|
1777
1998
|
usage: {
|
|
1778
|
-
how: "loop
|
|
1999
|
+
how: "loop 内部硬性 while 驱动 agent 逐轮推进,agent 精确回显随机完成码即终止",
|
|
1779
2000
|
progress: `进度在 AGENT_SESSIONS/loop-${label}.md 与 .json;同 label 再调 loop 会从下一轮续跑(不重做)`,
|
|
1780
2001
|
constraints: "loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫)",
|
|
1781
|
-
next: done ? "任务已完成;可查看进度文件收尾" :
|
|
2002
|
+
next: done ? "任务已完成;可查看进度文件收尾" : `任务未完成(已达保险阀 ${maxRounds} 轮);可同 label 续跑`
|
|
1782
2003
|
}
|
|
1783
2004
|
};
|
|
1784
2005
|
}
|
|
1785
2006
|
});
|
|
1786
2007
|
}
|
|
1787
2008
|
//#endregion
|
|
1788
|
-
//#region src/
|
|
2009
|
+
//#region src/localstore-ops.ts
|
|
1789
2010
|
/**
|
|
1790
|
-
*
|
|
2011
|
+
* localstore-ops.ts — localstore 纯逻辑层(零 DSH 依赖,可独立单测)
|
|
1791
2012
|
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
2013
|
+
* ACC 标准本地凭据/配置存储(S133 设计):
|
|
2014
|
+
* - 一个工具管理两个命名空间:credential(凭据,0600)+ config(偏好,0644)
|
|
2015
|
+
* - 存储于 ~/.serenity/(平台感知:win %USERPROFILE%\.serenity\)
|
|
2016
|
+
* - 两个 YAML 文件:credentials.yaml(扁平 REF→value)+ settings.yaml(命名空间分节)
|
|
2017
|
+
* - 目录 0700
|
|
1794
2018
|
*
|
|
1795
|
-
*
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
* 安全模式 + 黑名单 + P3 路径守卫的纯决策。
|
|
1799
|
-
* 对齐 opencode-serenity-plugin 标准:安全模式 = **bash 禁用** + 写入黑名单;
|
|
1800
|
-
* write/edit 等工具仅受路径逃逸与黑名单约束(不整体禁用)。
|
|
1801
|
-
* 优先级:safe-mode bash > 路径越界 > 黑名单命中。
|
|
2019
|
+
* YAML 用轻量自实现子集(零依赖):扁平 `KEY: value` 映射 + `#` 注释。
|
|
2020
|
+
* 凭据/配置本质是给 MSM/agent 用的普通 YAML 文件——agent 可按 doc 子命令
|
|
2021
|
+
* 说明直接用 fs 工具(read/write)操作;本工具是管理入口 + 规范文档。
|
|
1802
2022
|
*/
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
2023
|
+
const LOCALSTORE_SCOPES = ["credential", "config"];
|
|
2024
|
+
/** 根目录名(~/.serenity) */
|
|
2025
|
+
const STORE_DIR_NAME = ".serenity";
|
|
2026
|
+
/** 凭据文件(0600) */
|
|
2027
|
+
const CREDENTIALS_FILENAME = "credentials.yaml";
|
|
2028
|
+
/** 配置文件(0644) */
|
|
2029
|
+
const SETTINGS_FILENAME = "settings.yaml";
|
|
2030
|
+
/** 凭据 key 规范:大写蛇形(命名空间前缀),如 HOME_GITLAB_TOKEN */
|
|
2031
|
+
const CREDENTIAL_KEY_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
2032
|
+
/** 配置节名规范:小写字母数字连字符 */
|
|
2033
|
+
const CONFIG_SECTION_RE = /^[a-z][a-z0-9-]*$/;
|
|
2034
|
+
/** 配置 key 规范(节内):小驼峰(首字母小写,可含大写),如 defaultModel */
|
|
2035
|
+
const CONFIG_KEY_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
2036
|
+
/** 解析 ~/.serenity 根(平台感知:os.homedir() 三平台统一) */
|
|
2037
|
+
function serenityDir() {
|
|
2038
|
+
return join(homedir(), STORE_DIR_NAME);
|
|
2039
|
+
}
|
|
2040
|
+
/** 各命名空间的文件绝对路径 */
|
|
2041
|
+
function storeFilePath(scope) {
|
|
2042
|
+
return scope === "credential" ? join(serenityDir(), CREDENTIALS_FILENAME) : join(serenityDir(), SETTINGS_FILENAME);
|
|
2043
|
+
}
|
|
2044
|
+
/** 确保目录存在并设 0700 */
|
|
2045
|
+
function ensureDir(dir) {
|
|
2046
|
+
mkdirSync(dir, { recursive: true });
|
|
2047
|
+
try {
|
|
2048
|
+
chmodSync(dir, 448);
|
|
2049
|
+
} catch {}
|
|
2050
|
+
}
|
|
2051
|
+
/** 设置文件权限:credential 0600 / config 0644 */
|
|
2052
|
+
function applyFileMode(scope, path) {
|
|
2053
|
+
try {
|
|
2054
|
+
chmodSync(path, scope === "credential" ? 384 : 420);
|
|
2055
|
+
} catch {}
|
|
2056
|
+
}
|
|
2057
|
+
/** 解析扁平映射 YAML → Record<string,string>(忽略 # 注释与空行;值去引号) */
|
|
2058
|
+
function parseFlatYaml(text) {
|
|
2059
|
+
const out = {};
|
|
2060
|
+
for (const rawLine of text.split("\n")) {
|
|
2061
|
+
const line = rawLine.trim();
|
|
2062
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
2063
|
+
const idx = line.indexOf(":");
|
|
2064
|
+
if (idx <= 0) continue;
|
|
2065
|
+
const key = line.slice(0, idx).trim();
|
|
2066
|
+
let value = line.slice(idx + 1).trim();
|
|
2067
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
2068
|
+
out[key] = value;
|
|
1824
2069
|
}
|
|
1825
|
-
return
|
|
2070
|
+
return out;
|
|
1826
2071
|
}
|
|
1827
|
-
/**
|
|
1828
|
-
function
|
|
1829
|
-
|
|
2072
|
+
/** 序列化扁平映射 YAML(保持 key 顺序,值引号包裹含特殊字符的) */
|
|
2073
|
+
function renderFlatYaml(entries) {
|
|
2074
|
+
const lines = [];
|
|
2075
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
2076
|
+
const rendered = /[:#\n]/.test(value) || value === "" || /^\s/.test(value) || /\s$/.test(value) ? JSON.stringify(value) : value;
|
|
2077
|
+
lines.push(`${key}: ${rendered}`);
|
|
2078
|
+
}
|
|
2079
|
+
return lines.join("\n") + (lines.length > 0 ? "\n" : "");
|
|
2080
|
+
}
|
|
2081
|
+
/** 解析分节 YAML(config):节 → key → value */
|
|
2082
|
+
function parseSectionedYaml(text) {
|
|
2083
|
+
const out = {};
|
|
2084
|
+
let section = "";
|
|
2085
|
+
for (const rawLine of text.split("\n")) {
|
|
2086
|
+
const line = rawLine.trim();
|
|
2087
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
2088
|
+
if (rawLine.length - rawLine.trimStart().length === 0 && line.endsWith(":")) {
|
|
2089
|
+
section = line.slice(0, -1).trim();
|
|
2090
|
+
if (!out[section]) out[section] = {};
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (section === "") continue;
|
|
2094
|
+
const idx = line.indexOf(":");
|
|
2095
|
+
if (idx <= 0) continue;
|
|
2096
|
+
const key = line.slice(0, idx).trim();
|
|
2097
|
+
let value = line.slice(idx + 1).trim();
|
|
2098
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
2099
|
+
const sectionMap = out[section] ??= {};
|
|
2100
|
+
sectionMap[key] = value;
|
|
2101
|
+
}
|
|
2102
|
+
return out;
|
|
1830
2103
|
}
|
|
1831
|
-
/**
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2104
|
+
/** 序列化分节 YAML(config):节 → key → value */
|
|
2105
|
+
function renderSectionedYaml(sections) {
|
|
2106
|
+
const lines = [];
|
|
2107
|
+
for (const [section, entries] of Object.entries(sections)) {
|
|
2108
|
+
lines.push(`${section}:`);
|
|
2109
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
2110
|
+
const rendered = /[:#\n]/.test(value) || value === "" || /^\s/.test(value) || /\s$/.test(value) ? JSON.stringify(value) : value;
|
|
2111
|
+
lines.push(` ${key}: ${rendered}`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return lines.join("\n") + (lines.length > 0 ? "\n" : "");
|
|
2115
|
+
}
|
|
2116
|
+
/** 读取命名空间全部条目(文件不存在返回空) */
|
|
2117
|
+
function readStore(scope) {
|
|
2118
|
+
const path = storeFilePath(scope);
|
|
2119
|
+
if (!existsSync(path)) return scope === "credential" ? {} : {};
|
|
2120
|
+
const text = readFileSync(path, "utf-8");
|
|
2121
|
+
return scope === "credential" ? parseFlatYaml(text) : parseSectionedYaml(text);
|
|
2122
|
+
}
|
|
2123
|
+
/** 校验凭据 key 合法(大写蛇形);不合法抛错 */
|
|
2124
|
+
function assertCredentialKey(key) {
|
|
2125
|
+
if (!CREDENTIAL_KEY_RE.test(key)) throw new Error(`credential key "${key}" 必须匹配大写蛇形 ^[A-Z][A-Z0-9_]*$(如 HOME_GITLAB_TOKEN)`);
|
|
2126
|
+
}
|
|
2127
|
+
/** 校验 config 路径(节.key);不合法抛错 */
|
|
2128
|
+
function assertConfigPath(path) {
|
|
2129
|
+
const idx = path.indexOf(".");
|
|
2130
|
+
if (idx <= 0 || idx === path.length - 1) throw new Error(`config path "${path}" 必须为 section.key(如 loop.defaultModel)`);
|
|
2131
|
+
const section = path.slice(0, idx);
|
|
2132
|
+
const key = path.slice(idx + 1);
|
|
2133
|
+
if (!CONFIG_SECTION_RE.test(section)) throw new Error(`config 节 "${section}" 必须匹配 ^[a-z][a-z0-9-]*$`);
|
|
2134
|
+
if (!CONFIG_KEY_RE.test(key)) throw new Error(`config key "${key}" 必须匹配小驼峰 ^[a-z][a-zA-Z0-9_]*$(如 defaultModel)`);
|
|
1843
2135
|
return {
|
|
1844
|
-
|
|
1845
|
-
|
|
2136
|
+
section,
|
|
2137
|
+
key
|
|
1846
2138
|
};
|
|
1847
2139
|
}
|
|
1848
|
-
/**
|
|
1849
|
-
function
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
}
|
|
2140
|
+
/** 写入单个条目(自动建目录/设权限;保留其他条目与注释行外内容) */
|
|
2141
|
+
function writeEntry(scope, name, value) {
|
|
2142
|
+
const path = storeFilePath(scope);
|
|
2143
|
+
ensureDir(serenityDir());
|
|
2144
|
+
if (scope === "credential") {
|
|
2145
|
+
assertCredentialKey(name);
|
|
2146
|
+
const entries = parseFlatYaml(existsSync(path) ? readFileSync(path, "utf-8") : "");
|
|
2147
|
+
entries[name] = value;
|
|
2148
|
+
writeFileSync(path, renderFlatYaml(entries), "utf-8");
|
|
2149
|
+
} else {
|
|
2150
|
+
const { section, key } = assertConfigPath(name);
|
|
2151
|
+
const sections = parseSectionedYaml(existsSync(path) ? readFileSync(path, "utf-8") : "");
|
|
2152
|
+
if (!sections[section]) sections[section] = {};
|
|
2153
|
+
sections[section][key] = value;
|
|
2154
|
+
writeFileSync(path, renderSectionedYaml(sections), "utf-8");
|
|
2155
|
+
}
|
|
2156
|
+
applyFileMode(scope, path);
|
|
2157
|
+
}
|
|
2158
|
+
/** 删除单个条目(不存在返回 false) */
|
|
2159
|
+
function unsetEntry(scope, name) {
|
|
2160
|
+
const path = storeFilePath(scope);
|
|
2161
|
+
if (!existsSync(path)) return false;
|
|
2162
|
+
if (scope === "credential") {
|
|
2163
|
+
assertCredentialKey(name);
|
|
2164
|
+
const entries = parseFlatYaml(readFileSync(path, "utf-8"));
|
|
2165
|
+
if (!(name in entries)) return false;
|
|
2166
|
+
delete entries[name];
|
|
2167
|
+
writeFileSync(path, renderFlatYaml(entries), "utf-8");
|
|
2168
|
+
return true;
|
|
2169
|
+
}
|
|
2170
|
+
const { section, key } = assertConfigPath(name);
|
|
2171
|
+
const sections = parseSectionedYaml(readFileSync(path, "utf-8"));
|
|
2172
|
+
const sectionMap = sections[section];
|
|
2173
|
+
if (!sectionMap || !(key in sectionMap)) return false;
|
|
2174
|
+
delete sectionMap[key];
|
|
2175
|
+
if (Object.keys(sectionMap).length === 0) delete sections[section];
|
|
2176
|
+
writeFileSync(path, renderSectionedYaml(sections), "utf-8");
|
|
2177
|
+
return true;
|
|
2178
|
+
}
|
|
2179
|
+
/** 读取单个条目值;不存在返回 null */
|
|
2180
|
+
function getEntry(scope, name) {
|
|
2181
|
+
const path = storeFilePath(scope);
|
|
2182
|
+
if (!existsSync(path)) return null;
|
|
2183
|
+
if (scope === "credential") {
|
|
2184
|
+
assertCredentialKey(name);
|
|
2185
|
+
return parseFlatYaml(readFileSync(path, "utf-8"))[name] ?? null;
|
|
2186
|
+
}
|
|
2187
|
+
const { section, key } = assertConfigPath(name);
|
|
2188
|
+
return parseSectionedYaml(readFileSync(path, "utf-8"))[section]?.[key] ?? null;
|
|
2189
|
+
}
|
|
2190
|
+
/** 列出 key(凭据只返回 key 名,不返回值) */
|
|
2191
|
+
function listKeys(scope) {
|
|
2192
|
+
const data = readStore(scope);
|
|
2193
|
+
if (scope === "credential") return Object.keys(data);
|
|
2194
|
+
return Object.entries(data).flatMap(([section, entries]) => Object.keys(entries).map((key) => `${section}.${key}`));
|
|
1858
2195
|
}
|
|
1859
2196
|
/**
|
|
1860
|
-
*
|
|
1861
|
-
*
|
|
2197
|
+
* doc 说明文本:输出存储位置/格式/key 规范/权限/读写方法/安全边界。
|
|
2198
|
+
* agent 据此可直接用 fs 工具(read/write)自己读写凭据/配置。
|
|
1862
2199
|
*/
|
|
1863
|
-
function
|
|
1864
|
-
const
|
|
1865
|
-
const
|
|
1866
|
-
const
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2200
|
+
function docText() {
|
|
2201
|
+
const dir = serenityDir();
|
|
2202
|
+
const credPath = storeFilePath("credential");
|
|
2203
|
+
const cfgPath = storeFilePath("config");
|
|
2204
|
+
return [
|
|
2205
|
+
"# localstore — ACC 本地凭据/配置存储(标准)",
|
|
2206
|
+
"",
|
|
2207
|
+
"一个工具管理两个命名空间:credential(凭据,0600)+ config(偏好,0644)。",
|
|
2208
|
+
"存储于用户主目录,不在任何 CCC git 仓库内。",
|
|
2209
|
+
"",
|
|
2210
|
+
"## 存储位置(平台感知)",
|
|
2211
|
+
`- 根目录: ${dir} (0700)`,
|
|
2212
|
+
`- 凭据: ${credPath} (0600)`,
|
|
2213
|
+
`- 配置: ${cfgPath} (0644)`,
|
|
2214
|
+
"- Windows: %USERPROFILE%\\.serenity\\...(与 Linux/macOS 的 $HOME/.serenity 同构)",
|
|
2215
|
+
"",
|
|
2216
|
+
"## 格式",
|
|
2217
|
+
"credentials.yaml(扁平映射,key = 大写蛇形命名空间前缀):",
|
|
2218
|
+
"```yaml",
|
|
2219
|
+
"HOME_GITLAB_TOKEN: xxx",
|
|
2220
|
+
"SSH_UBUNTU_PASSWORD: xxx",
|
|
2221
|
+
"ANYSEARCH_API_KEY: xxx",
|
|
2222
|
+
"```",
|
|
2223
|
+
"settings.yaml(命名空间分节):",
|
|
2224
|
+
"```yaml",
|
|
2225
|
+
"loop:",
|
|
2226
|
+
" defaultModel: minimax-cn-coding-plan/MiniMax-M3",
|
|
2227
|
+
"ui:",
|
|
2228
|
+
" theme: dark",
|
|
2229
|
+
"```",
|
|
2230
|
+
"",
|
|
2231
|
+
"## key 规范",
|
|
2232
|
+
"- credential key: ^[A-Z][A-Z0-9_]*$(如 HOME_GITLAB_TOKEN)",
|
|
2233
|
+
"- config path: section.key(节 ^[a-z][a-z0-9-]*$,key 小驼峰 ^[a-z][a-zA-Z0-9_]*$,如 loop.defaultModel)",
|
|
2234
|
+
"",
|
|
2235
|
+
"## 读取方法(agent 可直接用 fs 工具)",
|
|
2236
|
+
"- 用 read 工具读对应文件 → 按上面格式解析 YAML",
|
|
2237
|
+
"- 或用 localstore get <name> [--scope credential|config]",
|
|
2238
|
+
"",
|
|
2239
|
+
"## 写入方法",
|
|
2240
|
+
"- 推荐:localstore set <name> <value> [--scope ...](自动建目录/设权限/保留其他条目)",
|
|
2241
|
+
"- 或直接用 write/edit 工具修改文件,保持 YAML 合法(键值冒号分隔)",
|
|
2242
|
+
"",
|
|
2243
|
+
"## 安全边界",
|
|
2244
|
+
"- list/show 对 credential 只返回 key 名,不返回值",
|
|
2245
|
+
"- 凭据值应在 agent 内部使用,不要写入对话/日志",
|
|
2246
|
+
"- 文件权限不符(非 0600/0644)时 get/set 会提示 chmod 修复",
|
|
2247
|
+
"- 文件在用户主目录,天然不进入任何 git 仓库",
|
|
2248
|
+
""
|
|
2249
|
+
].join("\n");
|
|
1890
2250
|
}
|
|
1891
|
-
/**
|
|
1892
|
-
function
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
"
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2251
|
+
/** 运行 localstore 操作(纯逻辑;返回 JSON 值供工具 render) */
|
|
2252
|
+
function runLocalStore(args) {
|
|
2253
|
+
const scope = args.scope === "config" ? "config" : "credential";
|
|
2254
|
+
switch (args.action) {
|
|
2255
|
+
case "list": return {
|
|
2256
|
+
scope,
|
|
2257
|
+
keys: listKeys(scope)
|
|
2258
|
+
};
|
|
2259
|
+
case "get": {
|
|
2260
|
+
if (!args.name) throw new Error("get 需要 name");
|
|
2261
|
+
const value = getEntry(scope, args.name);
|
|
2262
|
+
if (value === null) throw new Error(`not found: ${args.name}(scope=${scope})`);
|
|
2263
|
+
return {
|
|
2264
|
+
scope,
|
|
2265
|
+
name: args.name,
|
|
2266
|
+
value,
|
|
2267
|
+
source: scope
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
case "set":
|
|
2271
|
+
if (!args.name) throw new Error("set 需要 name");
|
|
2272
|
+
if (args.value === void 0) throw new Error("set 需要 value");
|
|
2273
|
+
writeEntry(scope, args.name, args.value);
|
|
2274
|
+
return {
|
|
2275
|
+
scope,
|
|
2276
|
+
name: args.name,
|
|
2277
|
+
set: true
|
|
2278
|
+
};
|
|
2279
|
+
case "unset": {
|
|
2280
|
+
if (!args.name) throw new Error("unset 需要 name");
|
|
2281
|
+
const removed = unsetEntry(scope, args.name);
|
|
2282
|
+
return {
|
|
2283
|
+
scope,
|
|
2284
|
+
name: args.name,
|
|
2285
|
+
removed
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
case "show": {
|
|
2289
|
+
if (!args.name) throw new Error("show 需要 name");
|
|
2290
|
+
const exists = getEntry(scope, args.name) !== null;
|
|
2291
|
+
return {
|
|
2292
|
+
scope,
|
|
2293
|
+
name: args.name,
|
|
2294
|
+
exists,
|
|
2295
|
+
path: storeFilePath(scope)
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
case "doc": return { doc: docText() };
|
|
2299
|
+
default: throw new Error(`未知子命令: ${args.action}(可用 list/get/set/unset/show/doc)`);
|
|
1904
2300
|
}
|
|
1905
2301
|
}
|
|
2302
|
+
//#endregion
|
|
2303
|
+
//#region src/tools/localstore.ts
|
|
1906
2304
|
/**
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
2305
|
+
* localstore.ts — localstore 真实 DSH 工具定义(defineTool)
|
|
2306
|
+
*
|
|
2307
|
+
* ACC 标准本地凭据/配置存储(S133 设计)。进程内注册,零 DSH 依赖逻辑在
|
|
2308
|
+
* localstore-ops.ts(可单测)。doc 子命令输出存储规范,agent 可直接用 fs
|
|
2309
|
+
* 工具(read/write)自己读写。
|
|
1909
2310
|
*/
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
pathArg
|
|
1924
|
-
});
|
|
1925
|
-
};
|
|
1926
|
-
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
1927
|
-
const d = evaluate(exec);
|
|
1928
|
-
if (d.deny) return {
|
|
1929
|
-
kind: "deny",
|
|
1930
|
-
reason: d.deny
|
|
1931
|
-
};
|
|
1932
|
-
return next();
|
|
1933
|
-
});
|
|
1934
|
-
ctx.tools.guard((exec) => {
|
|
1935
|
-
return evaluate(exec).deny;
|
|
1936
|
-
});
|
|
2311
|
+
const ACTIONS = [
|
|
2312
|
+
"list",
|
|
2313
|
+
"get",
|
|
2314
|
+
"set",
|
|
2315
|
+
"unset",
|
|
2316
|
+
"show",
|
|
2317
|
+
"doc"
|
|
2318
|
+
];
|
|
2319
|
+
function renderText(value) {
|
|
2320
|
+
return [{
|
|
2321
|
+
type: "text",
|
|
2322
|
+
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
2323
|
+
}];
|
|
1937
2324
|
}
|
|
2325
|
+
const localstoreTool = defineTool({
|
|
2326
|
+
name: "localstore",
|
|
2327
|
+
description: "ACC 标准本地凭据/配置存储:一个工具管理两个命名空间 credential(凭据,0600)与 config(本地偏好,0644)。存储于用户主目录 ~/.serenity/(平台感知,不在任何 git 仓库内)。子命令:list(列 key,凭据不返回值)/ get <name>(读值)/ set <name> <value>(写)/ unset <name>(删)/ show <name>(元数据,凭据不打印值)/ doc(输出存储规范——路径/格式/key 规范/权限,agent 可按说明直接用 read/write 操作文件)。默认 scope=credential;config 需传 --scope config(路径为 section.key,如 loop.defaultModel)。",
|
|
2328
|
+
parameters: {
|
|
2329
|
+
action: {
|
|
2330
|
+
type: "string",
|
|
2331
|
+
enum: [...ACTIONS],
|
|
2332
|
+
required: true,
|
|
2333
|
+
description: "子命令:list/get/set/unset/show/doc"
|
|
2334
|
+
},
|
|
2335
|
+
name: {
|
|
2336
|
+
type: "string",
|
|
2337
|
+
description: "条目名(credential 用大写蛇形;config 用 section.key)"
|
|
2338
|
+
},
|
|
2339
|
+
value: {
|
|
2340
|
+
type: "string",
|
|
2341
|
+
description: "set 的值"
|
|
2342
|
+
},
|
|
2343
|
+
scope: {
|
|
2344
|
+
type: "string",
|
|
2345
|
+
enum: [...LOCALSTORE_SCOPES],
|
|
2346
|
+
description: "命名空间 credential|config(默认 credential)"
|
|
2347
|
+
}
|
|
2348
|
+
},
|
|
2349
|
+
output: {
|
|
2350
|
+
schema: { type: "json" },
|
|
2351
|
+
render: (args, value) => renderText(value)
|
|
2352
|
+
},
|
|
2353
|
+
async execute(args) {
|
|
2354
|
+
return runLocalStore(args);
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
1938
2357
|
//#endregion
|
|
1939
2358
|
//#region src/seams/loop.ts
|
|
1940
2359
|
/** 解析当前 dsh 会话 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
|
|
@@ -2045,21 +2464,6 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
2045
2464
|
});
|
|
2046
2465
|
}
|
|
2047
2466
|
//#endregion
|
|
2048
|
-
//#region src/constants.ts
|
|
2049
|
-
/** 常量(纯模块,零 DSH 依赖) */
|
|
2050
|
-
/**
|
|
2051
|
-
* ACC 版本:自动从 package.json 读取(单一真相源,消除与 CHANGELOG 的漂移)。
|
|
2052
|
-
* 发布时只需改 package.json 的 version。
|
|
2053
|
-
*/
|
|
2054
|
-
const ACC_VERSION = (() => {
|
|
2055
|
-
try {
|
|
2056
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
2057
|
-
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
2058
|
-
} catch {
|
|
2059
|
-
return "0.0.0";
|
|
2060
|
-
}
|
|
2061
|
-
})();
|
|
2062
|
-
//#endregion
|
|
2063
2467
|
//#region src/skills-discovery.ts
|
|
2064
2468
|
/**
|
|
2065
2469
|
* skills-discovery.ts — CCC 入口 skill 自动发现(纯逻辑,零 DSH 依赖)
|
|
@@ -2161,6 +2565,7 @@ function accBlock(root) {
|
|
|
2161
2565
|
" neat — return the full Neat design collaboration protocol",
|
|
2162
2566
|
" cce — return the full Cognitive Continuity Engineering framework",
|
|
2163
2567
|
" loop — 牛马循环:指定模型专用 agent 反复执行",
|
|
2568
|
+
" localstore — ACC 本地凭据/配置存储(credential 0600 + config 0644,~/.serenity/);doc 子命令输出规范",
|
|
2164
2569
|
"",
|
|
2165
2570
|
"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 above are the serenity-native layer, not the only tools.",
|
|
2166
2571
|
"",
|
|
@@ -2233,6 +2638,22 @@ function constraintsBlock(root) {
|
|
|
2233
2638
|
""
|
|
2234
2639
|
].join("\n");
|
|
2235
2640
|
}
|
|
2641
|
+
/**
|
|
2642
|
+
* EAP 自检提示块(DSH 扩展,无 osp 对应——osp 无此块)。
|
|
2643
|
+
* 每次输出前的机械自检清单,强化 EAP 表现(E↑ 显式/R↓ 可重建/S↑ 稳定)。
|
|
2644
|
+
* 独立块而非塞进 CCE/Constraints:后两者受 osp-alignment 逐字节断言约束。
|
|
2645
|
+
*/
|
|
2646
|
+
function eapBlock() {
|
|
2647
|
+
return [
|
|
2648
|
+
"",
|
|
2649
|
+
"=== Serenity EAP ===",
|
|
2650
|
+
"每次输出前自检(显式抽象原则:思维的价值 = 外部可重建性):",
|
|
2651
|
+
" • E↑ 显式 — 变量/实体明确定义,关系指明方向/基数,边界划定;不用歧义词(\"处理\"\"优化\"→具体化)",
|
|
2652
|
+
" • R↓ 可重建 — 关键决策记录理由与备选,不跳级讨论(先对齐上层再进下层)",
|
|
2653
|
+
" • S↑ 稳定 — 结构可重复生成,避免依赖隐含上下文",
|
|
2654
|
+
""
|
|
2655
|
+
].join("\n");
|
|
2656
|
+
}
|
|
2236
2657
|
/** 4) SKILL.md 全文:该 CCC 顶层入口 skill 原文(对齐 osp:原文直推,无包裹头;仅过滤治理内容) */
|
|
2237
2658
|
function entrySkillSectionText(root) {
|
|
2238
2659
|
const skills = findEntrySkills(root);
|
|
@@ -2283,12 +2704,13 @@ function sessionBlock(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
2283
2704
|
""
|
|
2284
2705
|
].join("\n");
|
|
2285
2706
|
}
|
|
2286
|
-
/** 完整系统提示词注入文本:ACC + CCE + Constraints + SKILL 全文 + Session(osp
|
|
2707
|
+
/** 完整系统提示词注入文本:ACC + CCE + Constraints + EAP + SKILL 全文 + Session(osp 顺序 + EAP 扩展) */
|
|
2287
2708
|
function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
2288
2709
|
const parts = [
|
|
2289
2710
|
accBlock(root),
|
|
2290
2711
|
cceBlock(),
|
|
2291
|
-
constraintsBlock(root)
|
|
2712
|
+
constraintsBlock(root),
|
|
2713
|
+
eapBlock()
|
|
2292
2714
|
];
|
|
2293
2715
|
const skill = entrySkillSectionText(root);
|
|
2294
2716
|
if (skill) parts.push(skill);
|
|
@@ -2296,6 +2718,31 @@ function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
2296
2718
|
if (session) parts.push(session);
|
|
2297
2719
|
return parts.join("\n\n");
|
|
2298
2720
|
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Code Mode 适配行:当前 scope 以 Code Mode 呈现工具时(run_code 可见),
|
|
2723
|
+
* ACC 块按 native 语义指引"直接调用工具"会与"只有 run_code 可直接调用"的执行
|
|
2724
|
+
* 塌缩冲突(模型直呼工具名 → UNKNOWN_TOOL,拒绝信息误导)。追加一行说明,
|
|
2725
|
+
* 引导经 run_code 程序内 tools.* 调用。both 模式不塌缩,该行不误导(程序内
|
|
2726
|
+
* 调用同样合法),故按 run_code 可见性(code|both)统一附加。
|
|
2727
|
+
* @param ctx - 插件 ctx(读 tools 注册表)。
|
|
2728
|
+
* @param scope - 装配 scope(agent);无则按全局视图判断。
|
|
2729
|
+
* @returns 适配行(含换行前缀);非 code/both 返回空串。
|
|
2730
|
+
*/
|
|
2731
|
+
function codeModeAdaptationLine(ctx, scope) {
|
|
2732
|
+
try {
|
|
2733
|
+
if (!(ctx.tools.get("run_code", scope) !== void 0)) return "";
|
|
2734
|
+
} catch {
|
|
2735
|
+
return "";
|
|
2736
|
+
}
|
|
2737
|
+
return [
|
|
2738
|
+
"",
|
|
2739
|
+
"=== Serenity Code Mode ===",
|
|
2740
|
+
"当前会话以 Code Mode 呈现工具:模型直接调用 ACC 工具名(cc_fs/acc_msm 等)会被拒绝(UNKNOWN_TOOL)。",
|
|
2741
|
+
"请在一个 run_code 程序内经生成的 SDK 绑定调用:`await tools.cc_fs(...)`、`await tools.acc_msm(...)` 等。",
|
|
2742
|
+
"程序只返回你 print/return 的内容——务必 curate 输出。",
|
|
2743
|
+
""
|
|
2744
|
+
].join("\n");
|
|
2745
|
+
}
|
|
2299
2746
|
/** 从 assembly context 解析 agent cwd(subagent/后台 agent 同样带 agent) */
|
|
2300
2747
|
function agentCwd(context) {
|
|
2301
2748
|
return (context.agent?.session)?.header?.cwd;
|
|
@@ -2319,7 +2766,9 @@ function registerEntrySkillSectionGlobal(ctx) {
|
|
|
2319
2766
|
if (!cwd) return "";
|
|
2320
2767
|
const root = findSerenityRoot(cwd);
|
|
2321
2768
|
if (!root) return "";
|
|
2322
|
-
|
|
2769
|
+
const base = serenitySystemPrompt(root, agentScope$1(context));
|
|
2770
|
+
const codeLine = codeModeAdaptationLine(ctx, context.scope);
|
|
2771
|
+
return codeLine ? `${base}\n${codeLine}` : base;
|
|
2323
2772
|
}
|
|
2324
2773
|
});
|
|
2325
2774
|
console.log("[serenity-hooks] ✓ 全局入口 skill section 已注册(systemPrompt 就绪)");
|
|
@@ -2327,6 +2776,39 @@ function registerEntrySkillSectionGlobal(ctx) {
|
|
|
2327
2776
|
console.error(`[serenity-hooks] ✗ 全局入口 skill section 注册失败: ${err.message}(检查 inject 是否含 systemPrompt)`);
|
|
2328
2777
|
}
|
|
2329
2778
|
}
|
|
2779
|
+
/**
|
|
2780
|
+
* agent 级 scoped 注册(P0-1:抗 preset/动态插件同名 shadow)。
|
|
2781
|
+
*
|
|
2782
|
+
* 为什么 scoped:DSH 的 systemPrompt section 支持 scoped 层同名 shadow 全局
|
|
2783
|
+
* (agent.ctx.systemPrompt.section),且 scope 链最近层胜出。若 ACC 只注册全局
|
|
2784
|
+
* section,preset 或动态 Cordis 插件可在 scoped 层注册同名 `serenity-entry`
|
|
2785
|
+
* 遮蔽 ACC 身份。scoped 注册在 agent 自身层 = 最近层,任何外部组合无法覆盖。
|
|
2786
|
+
* 全局注册保留为 fallback(未走 session-start 的 agent / 冷恢复路径)。
|
|
2787
|
+
*
|
|
2788
|
+
* text 回调闭包持有 root 与 agent:content 固定(该 agent 的 CCC 身份),
|
|
2789
|
+
* code-mode 适配按装配 context.scope 判断(与全局版一致)。
|
|
2790
|
+
*/
|
|
2791
|
+
const sectionedAgents = /* @__PURE__ */ new Set();
|
|
2792
|
+
function registerEntrySkillSection(agent, root) {
|
|
2793
|
+
const key = agent.session.id ?? "global";
|
|
2794
|
+
if (sectionedAgents.has(key)) return false;
|
|
2795
|
+
try {
|
|
2796
|
+
const scope = agent.session.id ?? "default";
|
|
2797
|
+
agent.ctx.systemPrompt.section({
|
|
2798
|
+
name: "serenity-entry",
|
|
2799
|
+
order: -50,
|
|
2800
|
+
text: (context) => {
|
|
2801
|
+
const base = serenitySystemPrompt(root, scope);
|
|
2802
|
+
const codeLine = codeModeAdaptationLine(agent.ctx, context.scope);
|
|
2803
|
+
return codeLine ? `${base}\n${codeLine}` : base;
|
|
2804
|
+
}
|
|
2805
|
+
});
|
|
2806
|
+
sectionedAgents.add(key);
|
|
2807
|
+
return true;
|
|
2808
|
+
} catch {
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2330
2812
|
//#endregion
|
|
2331
2813
|
//#region src/seams/context.ts
|
|
2332
2814
|
const DEFAULT_ENTRY_SKILL_MAX_CHARS = 3e4;
|
|
@@ -2383,6 +2865,7 @@ function registerContext(ctx, opts = {}) {
|
|
|
2383
2865
|
const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
|
|
2384
2866
|
if (!root) return;
|
|
2385
2867
|
const key = agentKey(agent);
|
|
2868
|
+
registerEntrySkillSection(agent, root);
|
|
2386
2869
|
if (injected.has(key)) return;
|
|
2387
2870
|
injected.add(key);
|
|
2388
2871
|
agent.inject(accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)));
|
|
@@ -2398,9 +2881,12 @@ function registerContext(ctx, opts = {}) {
|
|
|
2398
2881
|
const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
|
|
2399
2882
|
const key = agentKey(agent);
|
|
2400
2883
|
const downstream = await next();
|
|
2401
|
-
if (root)
|
|
2402
|
-
|
|
2403
|
-
|
|
2884
|
+
if (root) {
|
|
2885
|
+
try {
|
|
2886
|
+
syncSafeModeRestriction(agent, root);
|
|
2887
|
+
} catch {}
|
|
2888
|
+
registerEntrySkillSection(agent, root);
|
|
2889
|
+
}
|
|
2404
2890
|
if (!root || injected.has(key) || downstream.kind !== "enter") return downstream;
|
|
2405
2891
|
injected.add(key);
|
|
2406
2892
|
return {
|
|
@@ -2436,45 +2922,6 @@ function registerCompactRetention(ctx, opts = {}) {
|
|
|
2436
2922
|
});
|
|
2437
2923
|
}
|
|
2438
2924
|
//#endregion
|
|
2439
|
-
//#region src/status.ts
|
|
2440
|
-
/**
|
|
2441
|
-
* status.ts — 状态与安全模式操作(纯逻辑,零 DSH 依赖,可独立单测)
|
|
2442
|
-
*
|
|
2443
|
-
* WebUI 停靠栏的数据源:ACC 版本 / CCC 根 / safe-mode 状态 / 黑名单 / keeper 阈值 / loop 模型。
|
|
2444
|
-
* setSafeMode 直接读写 .serenity-safe-on 标记(守卫实时读取,写即生效)。
|
|
2445
|
-
*/
|
|
2446
|
-
function getStatus(cwd, configPaths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
2447
|
-
const root = findSerenityRoot(cwd);
|
|
2448
|
-
const restrict = getRestrictDiagnostics();
|
|
2449
|
-
if (!root) return {
|
|
2450
|
-
root: null,
|
|
2451
|
-
accVersion: ACC_VERSION,
|
|
2452
|
-
safeModeOn: false,
|
|
2453
|
-
blacklist: [],
|
|
2454
|
-
threshold: null,
|
|
2455
|
-
loopModel: null,
|
|
2456
|
-
restrict
|
|
2457
|
-
};
|
|
2458
|
-
const cfg = loadSerenityConfig(root, configPaths);
|
|
2459
|
-
return {
|
|
2460
|
-
root,
|
|
2461
|
-
accVersion: ACC_VERSION,
|
|
2462
|
-
safeModeOn: isSafeModeOn(root),
|
|
2463
|
-
blacklist: readBlacklist(root, configPaths),
|
|
2464
|
-
threshold: cfg.sessionKeeper?.threshold ?? null,
|
|
2465
|
-
loopModel: cfg.loop?.defaultModel ?? null,
|
|
2466
|
-
restrict
|
|
2467
|
-
};
|
|
2468
|
-
}
|
|
2469
|
-
/** 切换安全模式(写/删标记文件);返回实际生效状态 */
|
|
2470
|
-
function setSafeMode(root, on) {
|
|
2471
|
-
const marker = resolve(root, SAFE_MODE_MARKER);
|
|
2472
|
-
if (on) {
|
|
2473
|
-
if (!existsSync(marker)) writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
|
|
2474
|
-
} else rmSync(marker, { force: true });
|
|
2475
|
-
return { on: isSafeModeOn(root) };
|
|
2476
|
-
}
|
|
2477
|
-
//#endregion
|
|
2478
2925
|
//#region src/api.ts
|
|
2479
2926
|
const ROUTE_PATH = "/serenity/status";
|
|
2480
2927
|
function readBody(req) {
|
|
@@ -2517,10 +2964,18 @@ function registerStatusApi(ctx, opts = {}) {
|
|
|
2517
2964
|
try {
|
|
2518
2965
|
if (req.method === "GET") {
|
|
2519
2966
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
2520
|
-
|
|
2967
|
+
const status = getStatus(resolveWorkspace(ctx, {
|
|
2521
2968
|
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
2522
2969
|
workspace: url.searchParams.get("workspace") ?? void 0
|
|
2523
|
-
}), configPaths)
|
|
2970
|
+
}), configPaths);
|
|
2971
|
+
const runtime = ctx.get("codeRuntime");
|
|
2972
|
+
sendJson(res, 200, {
|
|
2973
|
+
...status,
|
|
2974
|
+
...runtime === void 0 ? { codeRuntime: null } : { codeRuntime: {
|
|
2975
|
+
language: runtime.language,
|
|
2976
|
+
isolation: runtime.isolation
|
|
2977
|
+
} }
|
|
2978
|
+
});
|
|
2524
2979
|
return;
|
|
2525
2980
|
}
|
|
2526
2981
|
if (req.method === "POST") {
|
|
@@ -2700,6 +3155,7 @@ const inject = [
|
|
|
2700
3155
|
"shellEnv",
|
|
2701
3156
|
"skills",
|
|
2702
3157
|
"agentLoop",
|
|
3158
|
+
"agents",
|
|
2703
3159
|
"systemPrompt"
|
|
2704
3160
|
];
|
|
2705
3161
|
const Config = z.object({
|
|
@@ -2727,6 +3183,7 @@ function apply(ctx, config) {
|
|
|
2727
3183
|
ctx.tools.register(neatTool);
|
|
2728
3184
|
ctx.tools.register(cceTool);
|
|
2729
3185
|
ctx.tools.register(createLoopTool(ctx));
|
|
3186
|
+
ctx.tools.register(localstoreTool);
|
|
2730
3187
|
}
|
|
2731
3188
|
if (config.guards) registerGuards(ctx, { configPaths: config.serenityConfigPaths });
|
|
2732
3189
|
if (config.turnFlush) registerTurnFlush(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.5",
|
|
4
4
|
"description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|