@shgroup/dsh-serenity-hooks 1.17.3 → 1.17.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 +1 -1
- package/lib/index.js +110 -37
- 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.17.
|
|
3
|
+
"version": "1.17.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": {
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
6
6
|
import { homedir, platform } from "node:os";
|
|
@@ -71,12 +71,19 @@ function resolveInside(root, p) {
|
|
|
71
71
|
* .dsh 仅作 dsh 运行时回退,不优先)。
|
|
72
72
|
*/
|
|
73
73
|
const DEFAULT_SERENITY_CONFIG_PATHS = [".opencode/serenity.json", ".dsh/serenity.json"];
|
|
74
|
+
/**
|
|
75
|
+
* 读取 UTF-8 文件并剥离 BOM(Windows 审计问题 16):PowerShell/Windows 编辑器
|
|
76
|
+
* 写出的 BOM(\uFEFF)会让 JSON.parse 抛错(配置静默变空)或 frontmatter 检测失败(技能被丢弃)。
|
|
77
|
+
*/
|
|
78
|
+
function readUtf8(path) {
|
|
79
|
+
return readFileSync(path, "utf-8").replace(/^\uFEFF/, "");
|
|
80
|
+
}
|
|
74
81
|
function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
75
82
|
for (const candidate of paths) {
|
|
76
83
|
const p = resolve(root, candidate);
|
|
77
84
|
if (!existsSync(p)) continue;
|
|
78
85
|
try {
|
|
79
|
-
return JSON.parse(
|
|
86
|
+
return JSON.parse(readUtf8(p));
|
|
80
87
|
} catch {
|
|
81
88
|
return {};
|
|
82
89
|
}
|
|
@@ -87,16 +94,34 @@ const SAFE_MODE_MARKER = ".serenity-safe-on";
|
|
|
87
94
|
function isSafeModeOn(root) {
|
|
88
95
|
return existsSync(resolve(root, SAFE_MODE_MARKER));
|
|
89
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* 读取黑名单(对齐 osp readBlacklist):支持两种条目格式——
|
|
99
|
+
* string:".secrets/" 或 "regex:^..."(前缀 / 正则)
|
|
100
|
+
* object:{ "pattern": ".secrets/", "message": "自定义提示" }
|
|
101
|
+
* dsp v1.17.4 前只支持 string(对象会被 String() 成 "[object Object]" → 规则失效,不拦截)。
|
|
102
|
+
*/
|
|
90
103
|
function readBlacklist(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
91
104
|
const rules = loadSerenityConfig(root, paths).safeMode?.blacklist;
|
|
92
|
-
|
|
105
|
+
if (!Array.isArray(rules)) return [];
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const item of rules) if (typeof item === "string") {
|
|
108
|
+
if (item) out.push({ pattern: item });
|
|
109
|
+
} else if (item && typeof item === "object" && typeof item.pattern === "string") {
|
|
110
|
+
const obj = item;
|
|
111
|
+
const p = obj.pattern;
|
|
112
|
+
if (p) out.push({
|
|
113
|
+
pattern: p,
|
|
114
|
+
message: typeof obj.message === "string" ? obj.message : void 0
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
93
118
|
}
|
|
94
|
-
/**
|
|
119
|
+
/** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp) */
|
|
95
120
|
function matchBlacklist(relPath, rules) {
|
|
96
|
-
for (const rule of rules) if (rule.startsWith("regex:")) try {
|
|
97
|
-
if (new RegExp(rule.slice(6)).test(relPath)) return rule;
|
|
121
|
+
for (const rule of rules) if (rule.pattern.startsWith("regex:")) try {
|
|
122
|
+
if (new RegExp(rule.pattern.slice(6)).test(relPath)) return rule;
|
|
98
123
|
} catch {}
|
|
99
|
-
else if (relPath.startsWith(rule)) return rule;
|
|
124
|
+
else if (relPath.startsWith(rule.pattern)) return rule;
|
|
100
125
|
return null;
|
|
101
126
|
}
|
|
102
127
|
//#endregion
|
|
@@ -165,13 +190,22 @@ function safeRel(root, abs) {
|
|
|
165
190
|
}
|
|
166
191
|
function validateWritePath(root, target) {
|
|
167
192
|
const absPath = target.startsWith("/") ? resolve(target) : resolveInside(root, target);
|
|
168
|
-
if (
|
|
169
|
-
|
|
193
|
+
if (!pathInside(resolve(root), absPath)) throw new Error(`cc-fs: path "${target}" resolves to "${absPath}" which is outside serenity root "${root}"`);
|
|
194
|
+
const rel = relative(root, absPath).split("\\").join("/");
|
|
195
|
+
if (rel.endsWith("/mech-registry.json") && rel.includes("/.opencode/skills/")) throw new Error(`cc-fs: refusing to directly modify mech-registry.json — use acc_msm register/deregister instead`);
|
|
196
|
+
if (existsSync(absPath)) try {
|
|
197
|
+
const real = realpathSync(absPath);
|
|
198
|
+
if (!pathInside(resolve(root), real)) throw new Error(`cc-fs: path "${target}" resolves via symlink to "${real}" outside serenity root "${root}"`);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
if (e instanceof Error && e.message.includes("symlink")) throw e;
|
|
201
|
+
}
|
|
170
202
|
return absPath;
|
|
171
203
|
}
|
|
172
204
|
function assertNotProtected(root, absPath, targetLabel) {
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
const ci = process.platform === "win32";
|
|
206
|
+
const eq = (a, b) => ci ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
207
|
+
if (eq(absPath, resolve(root, ".serenity"))) throw new Error(`cc-fs: refusing to delete protected path: ${targetLabel} (.serenity is the CCC marker)`);
|
|
208
|
+
if (eq(absPath, root)) throw new Error(`cc-fs: refusing to delete the CCC root directory: ${targetLabel}`);
|
|
175
209
|
}
|
|
176
210
|
function runCcFs(root, args) {
|
|
177
211
|
const a = args.action;
|
|
@@ -288,7 +322,12 @@ function runCcFs(root, args) {
|
|
|
288
322
|
recursive: true,
|
|
289
323
|
force: false
|
|
290
324
|
});
|
|
291
|
-
else
|
|
325
|
+
else {
|
|
326
|
+
if (process.platform === "win32") try {
|
|
327
|
+
chmodSync(absPath, 438);
|
|
328
|
+
} catch {}
|
|
329
|
+
unlinkSync(absPath);
|
|
330
|
+
}
|
|
292
331
|
results.push(`[OK] deleted: ${relLabel}`);
|
|
293
332
|
}
|
|
294
333
|
return results.join("\n");
|
|
@@ -349,7 +388,7 @@ function runCcFs(root, args) {
|
|
|
349
388
|
const revealPath = statSync(absPath).isDirectory() ? absPath : dirname(absPath);
|
|
350
389
|
execFileSync("xdg-open", [revealPath], { timeout: 1e4 });
|
|
351
390
|
} else if (os === "win32") {
|
|
352
|
-
const winArgs = statSync(absPath).isDirectory() ? [absPath] : [
|
|
391
|
+
const winArgs = statSync(absPath).isDirectory() ? [absPath] : [`/select,${absPath}`];
|
|
353
392
|
const child = spawn("explorer", winArgs, {
|
|
354
393
|
detached: true,
|
|
355
394
|
stdio: "ignore",
|
|
@@ -694,7 +733,7 @@ safe-mode 由 WebUI 开关控制(写 .serenity-safe-on 标记);黑名单
|
|
|
694
733
|
{ "safeMode": { "blacklist": [".secrets/"] } }
|
|
695
734
|
`;
|
|
696
735
|
function parseRegistry(raw) {
|
|
697
|
-
const data = JSON.parse(raw);
|
|
736
|
+
const data = JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
698
737
|
if (Array.isArray(data)) return data;
|
|
699
738
|
const entries = data.entries;
|
|
700
739
|
if (!Array.isArray(entries)) throw new Error("invalid registry: missing entries[]");
|
|
@@ -773,7 +812,7 @@ function runMsm(root, args) {
|
|
|
773
812
|
],
|
|
774
813
|
env: buildMsmEnv(root)
|
|
775
814
|
});
|
|
776
|
-
if (r.error && r.error
|
|
815
|
+
if (r.error && isBunMissing(r.error)) r = spawnSync(NPX_BIN, [
|
|
777
816
|
"tsx",
|
|
778
817
|
entry.path,
|
|
779
818
|
...businessArgs
|
|
@@ -1019,6 +1058,15 @@ function msmExecResult(name, status, stdout, stderr, fmtJson, hasHelp = false) {
|
|
|
1019
1058
|
* 用 execFile + promisify + timeout(超时自动 kill),**不阻塞 Node 事件循环**。
|
|
1020
1059
|
* (同步 spawnSync 版会阻塞 web 事件循环 → MSM 脚本自请求 3080 时死锁,见 postmortem。)
|
|
1021
1060
|
*/
|
|
1061
|
+
/** bun 缺失的错误码集(Windows 兼容:无 bun 时 execFile('bun') 抛 EINVAL 而非 ENOENT,见 Windows 审计问题 5) */
|
|
1062
|
+
const BUN_MISSING_CODES = /* @__PURE__ */ new Set([
|
|
1063
|
+
"ENOENT",
|
|
1064
|
+
"EINVAL",
|
|
1065
|
+
"EPERM"
|
|
1066
|
+
]);
|
|
1067
|
+
function isBunMissing(err) {
|
|
1068
|
+
return typeof err.code === "string" && BUN_MISSING_CODES.has(err.code);
|
|
1069
|
+
}
|
|
1022
1070
|
async function runMsmAsync(root, args) {
|
|
1023
1071
|
if (args.action !== "exec") return runMsm(root, args);
|
|
1024
1072
|
const { entry, businessArgs, fmtJson, hasHelp, protocol } = prepareExec(root, args);
|
|
@@ -1035,7 +1083,7 @@ async function runMsmAsync(root, args) {
|
|
|
1035
1083
|
return msmExecResult(entry.name, 0, r.stdout, r.stderr, fmtJson, hasHelp);
|
|
1036
1084
|
} catch (e) {
|
|
1037
1085
|
const err = e;
|
|
1038
|
-
if (err
|
|
1086
|
+
if (isBunMissing(err)) try {
|
|
1039
1087
|
const r = await execFileAsync(NPX_BIN, [
|
|
1040
1088
|
"tsx",
|
|
1041
1089
|
entry.path,
|
|
@@ -1203,6 +1251,12 @@ function sessionMdTemplate(title, id, goal, now) {
|
|
|
1203
1251
|
return `# SESSION: ${title}\n- ID: ${id}\n\n## 目标\n${goal ?? "(待补充)"}\n\n## 状态\n- [ ] 进行中\n\n## 关键决策\n| # | 决策 | 理由 |\n|---|------|------|\n| 1 | | |\n\n## 进度记录\n- ${ts} — 创建\n\n## 产出物\n- \n\n## 未解决的问题\n- \n`;
|
|
1204
1252
|
}
|
|
1205
1253
|
/** create 子命令(对齐 osp createSession:--desc/--issue 二选一 + dry-run + 长度限制) */
|
|
1254
|
+
/** 目录名脱敏(Windows 审计问题 10):非法字符 → '-', 去尾点/空格, 保留名(CON/NUL 等)加前缀 */
|
|
1255
|
+
function sanitizeDirName(s) {
|
|
1256
|
+
const cleaned = s.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "-").replace(/[ .]+$/g, "");
|
|
1257
|
+
if (/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(cleaned)) return `_${cleaned}`;
|
|
1258
|
+
return cleaned;
|
|
1259
|
+
}
|
|
1206
1260
|
function createSession(opts) {
|
|
1207
1261
|
const { root, desc, issue, goal, dryRun } = opts;
|
|
1208
1262
|
const sessionsDir = sessionsRoot(root);
|
|
@@ -1212,7 +1266,7 @@ function createSession(opts) {
|
|
|
1212
1266
|
if (desc && issue) throw new Error("--desc and --issue are mutually exclusive");
|
|
1213
1267
|
if (issue) {
|
|
1214
1268
|
if (issue.length > 100) throw new Error(`issue too long: ${issue.length} chars (max 100)`);
|
|
1215
|
-
const dirName = `${datePrefix}--${issue}`;
|
|
1269
|
+
const dirName = `${datePrefix}--${sanitizeDirName(issue)}`;
|
|
1216
1270
|
const sessionPath = join(sessionsDir, dirName);
|
|
1217
1271
|
if (!dryRun && existsSync(sessionPath)) throw new Error(`Session directory already exists: "${dirName}"`);
|
|
1218
1272
|
if (dryRun) return {
|
|
@@ -1242,7 +1296,7 @@ function createSession(opts) {
|
|
|
1242
1296
|
}
|
|
1243
1297
|
}
|
|
1244
1298
|
const nextId = String(maxId + 1).padStart(3, "0");
|
|
1245
|
-
const dirName = `${datePrefix}--S${nextId}--${desc}`;
|
|
1299
|
+
const dirName = `${datePrefix}--S${nextId}--${sanitizeDirName(desc)}`;
|
|
1246
1300
|
const sessionPath = join(sessionsDir, dirName);
|
|
1247
1301
|
if (!dryRun && existsSync(sessionPath)) throw new Error(`Session directory already exists: "${dirName}"`);
|
|
1248
1302
|
if (dryRun) return {
|
|
@@ -1342,6 +1396,7 @@ function closeSession(root, key, confirm, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
1342
1396
|
const mdPath = join(session.path, SESSION_MD);
|
|
1343
1397
|
if (!existsSync(mdPath)) throw new Error(`Session "${session.dirName}" has no SESSION.md — nothing to close.`);
|
|
1344
1398
|
let content = readFileSync(mdPath, "utf-8");
|
|
1399
|
+
content = content.replace(/\r\n/g, "\n");
|
|
1345
1400
|
content = content.replace(/## 状态\n\n?- \[ \] 进行中/, "## 状态\n- [x] 已完成\n- [x] 已关闭");
|
|
1346
1401
|
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", " ");
|
|
1347
1402
|
if (!content.includes("-- 关闭")) content = content.replace(/(## 进度记录\n)/, `$1- ${now} — 关闭\n`);
|
|
@@ -1864,18 +1919,19 @@ function decideGuard(input) {
|
|
|
1864
1919
|
kind: "deny"
|
|
1865
1920
|
};
|
|
1866
1921
|
if (pathArg !== void 0) {
|
|
1867
|
-
const
|
|
1868
|
-
if (
|
|
1922
|
+
const abs = resolve(root, pathArg);
|
|
1923
|
+
if (!pathInside(resolve(root), abs)) return {
|
|
1869
1924
|
deny: `path escape blocked: "${pathArg}" 越出 CCC 根`,
|
|
1870
1925
|
kind: "deny"
|
|
1871
1926
|
};
|
|
1927
|
+
const rel = relative(root, abs).split("\\").join("/");
|
|
1872
1928
|
if (rel === ".serenity-safe-on" || rel === ".serenity" || rel.startsWith(".serenity-safe-on/") || rel.startsWith(".serenity/")) return {
|
|
1873
1929
|
deny: `CCC 治理文件 "${rel}" 保留给用户,agent 不可写`,
|
|
1874
1930
|
kind: "deny"
|
|
1875
1931
|
};
|
|
1876
1932
|
const hit = matchBlacklist(rel, blacklist);
|
|
1877
1933
|
if (hit) return {
|
|
1878
|
-
deny: `blacklist blocked: "${pathArg}" 命中规则 "${hit}"`,
|
|
1934
|
+
deny: hit.message ?? `blacklist blocked: "${pathArg}" 命中规则 "${hit.pattern}"`,
|
|
1879
1935
|
kind: "deny"
|
|
1880
1936
|
};
|
|
1881
1937
|
}
|
|
@@ -2000,14 +2056,21 @@ function registerGuards(ctx, opts = {}) {
|
|
|
2000
2056
|
* WebUI 停靠栏的数据源:ACC 版本 / CCC 根 / safe-mode 状态 / 黑名单 / keeper 阈值 / loop 模型。
|
|
2001
2057
|
* setSafeMode 直接读写 .serenity-safe-on 标记(守卫实时读取,写即生效)。
|
|
2002
2058
|
*/
|
|
2003
|
-
/**
|
|
2059
|
+
/**
|
|
2060
|
+
* 读取已安装 DSH CLI 版本;读不到返回 null。
|
|
2061
|
+
* 跨平台(Windows 审计问题 13):Windows npm 全局装在 %APPDATA%\npm(非 ~/.npm-global)——
|
|
2062
|
+
* 依次尝试 npm_config_prefix / APPDATA\npm / ~/.npm-global。
|
|
2063
|
+
*/
|
|
2004
2064
|
function readDshVersion() {
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2065
|
+
const candidates = [];
|
|
2066
|
+
if (process.env.npm_config_prefix) candidates.push(join(process.env.npm_config_prefix, "lib", "node_modules", "@deepseek-ai", "dsh"));
|
|
2067
|
+
if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", "node_modules", "@deepseek-ai", "dsh"));
|
|
2068
|
+
candidates.push(join(homedir(), ".npm-global", "lib", "node_modules", "@deepseek-ai", "dsh"));
|
|
2069
|
+
for (const p of candidates) try {
|
|
2070
|
+
const pkg = JSON.parse(readFileSync(join(p, "package.json"), "utf-8"));
|
|
2071
|
+
if (typeof pkg.version === "string") return pkg.version;
|
|
2072
|
+
} catch {}
|
|
2073
|
+
return null;
|
|
2011
2074
|
}
|
|
2012
2075
|
function getStatus(cwd, configPaths = DEFAULT_SERENITY_CONFIG_PATHS) {
|
|
2013
2076
|
const root = findSerenityRoot(cwd);
|
|
@@ -2249,7 +2312,7 @@ function readAll(root) {
|
|
|
2249
2312
|
const p = localstorePath(root);
|
|
2250
2313
|
if (!existsSync(p)) return {};
|
|
2251
2314
|
try {
|
|
2252
|
-
const v = JSON.parse(readFileSync(p, "utf-8"));
|
|
2315
|
+
const v = JSON.parse(readFileSync(p, "utf-8").replace(/^\uFEFF/, ""));
|
|
2253
2316
|
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
2254
2317
|
return {};
|
|
2255
2318
|
} catch {
|
|
@@ -2463,6 +2526,9 @@ const GIT_ACTIONS = [
|
|
|
2463
2526
|
"pull",
|
|
2464
2527
|
"diff"
|
|
2465
2528
|
];
|
|
2529
|
+
/** git 操作超时(ms)——网络路径(push/pull/fetch)可能挂起(GCM 弹认证框等),
|
|
2530
|
+
* 无 timeout 会冻结 Node 事件循环 / DSH 3080 server(Windows 审计问题 12) */
|
|
2531
|
+
const GIT_TIMEOUT_MS = 3e4;
|
|
2466
2532
|
function git(root, args) {
|
|
2467
2533
|
try {
|
|
2468
2534
|
return {
|
|
@@ -2474,14 +2540,15 @@ function git(root, args) {
|
|
|
2474
2540
|
"pipe",
|
|
2475
2541
|
"pipe"
|
|
2476
2542
|
],
|
|
2477
|
-
maxBuffer: 1048576
|
|
2543
|
+
maxBuffer: 1048576,
|
|
2544
|
+
timeout: GIT_TIMEOUT_MS
|
|
2478
2545
|
}).trimEnd(),
|
|
2479
2546
|
stderr: ""
|
|
2480
2547
|
};
|
|
2481
2548
|
} catch (err) {
|
|
2482
2549
|
return {
|
|
2483
2550
|
stdout: (err.stdout?.toString() ?? "").trimEnd(),
|
|
2484
|
-
stderr: (err.stderr?.toString() ?? "").trimEnd()
|
|
2551
|
+
stderr: err.killed ? `git 操作超时(${GIT_TIMEOUT_MS / 1e3}s)` : (err.stderr?.toString() ?? "").trimEnd()
|
|
2485
2552
|
};
|
|
2486
2553
|
}
|
|
2487
2554
|
}
|
|
@@ -2986,11 +3053,16 @@ function loopPresetInheritance(parentCtx) {
|
|
|
2986
3053
|
* 对齐 opencode-serenity-plugin 老 loop 语义:进度文件(loop-<label>.md/.json)、
|
|
2987
3054
|
* 续跑、轮次 prompt 结构、stop token。
|
|
2988
3055
|
*/
|
|
3056
|
+
/** label 脱敏(Windows 审计问题 17):非法字符 → '-',去尾点/空格,限长 */
|
|
3057
|
+
function sanitizeLabel(label) {
|
|
3058
|
+
return label.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "-").replace(/[ .]+$/g, "").slice(0, 50);
|
|
3059
|
+
}
|
|
2989
3060
|
function loopProgressPaths(root, label) {
|
|
2990
3061
|
const dir = join(root, "AGENT_SESSIONS");
|
|
3062
|
+
const safe = sanitizeLabel(label);
|
|
2991
3063
|
return {
|
|
2992
|
-
md: join(dir, `loop-${
|
|
2993
|
-
json: join(dir, `loop-${
|
|
3064
|
+
md: join(dir, `loop-${safe}.md`),
|
|
3065
|
+
json: join(dir, `loop-${safe}.json`)
|
|
2994
3066
|
};
|
|
2995
3067
|
}
|
|
2996
3068
|
/** 读取进度(续跑);无文件返回 round 0 */
|
|
@@ -3750,7 +3822,7 @@ function safeModeBlock(root) {
|
|
|
3750
3822
|
"remain available, subject to path-escape and blacklist guards.",
|
|
3751
3823
|
"Behavior constraints: do not attempt to bypass restrictions; do not write to",
|
|
3752
3824
|
"blacklisted paths or governance files.",
|
|
3753
|
-
blacklist.length > 0 ? `\nActive blacklist rules: ${blacklist.join(", ")}` : "",
|
|
3825
|
+
blacklist.length > 0 ? `\nActive blacklist rules: ${blacklist.map((b) => b.message ?? b.pattern).join(", ")}` : "",
|
|
3754
3826
|
""
|
|
3755
3827
|
].join("\n");
|
|
3756
3828
|
}
|
|
@@ -4235,16 +4307,17 @@ function registerEnv(ctx) {
|
|
|
4235
4307
|
*/
|
|
4236
4308
|
/** 解析 --- 分隔的 YAML frontmatter(只需 name/description/whenToUse) */
|
|
4237
4309
|
function parseFrontmatter(raw) {
|
|
4238
|
-
const
|
|
4310
|
+
const text = raw.replace(/^\uFEFF/, "");
|
|
4311
|
+
const m = /^---\s*\n([\s\S]*?)\n---\s*\n?/.exec(text);
|
|
4239
4312
|
if (!m) return {
|
|
4240
4313
|
meta: {
|
|
4241
4314
|
name: "",
|
|
4242
4315
|
description: ""
|
|
4243
4316
|
},
|
|
4244
|
-
content:
|
|
4317
|
+
content: text
|
|
4245
4318
|
};
|
|
4246
4319
|
const fm = m[1];
|
|
4247
|
-
const body =
|
|
4320
|
+
const body = text.slice(m[0].length);
|
|
4248
4321
|
const grab = (key) => {
|
|
4249
4322
|
const line = fm.split("\n").find((l) => l.startsWith(`${key}:`));
|
|
4250
4323
|
if (!line) return void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.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": {
|