@shgroup/dsh-serenity-hooks 1.26.2 → 1.26.3
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 +194 -13
- 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.26.
|
|
3
|
+
"version": "1.26.3",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -785,6 +785,17 @@ const CC_FS_WRITE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
785
785
|
"touch",
|
|
786
786
|
"append"
|
|
787
787
|
]);
|
|
788
|
+
/**
|
|
789
|
+
* 凭据文件硬名单(数据面守卫,v1.26.3,S142 用户:localstore.json 需要在 read 工具的黑名单里):
|
|
790
|
+
* **任何工具**(含 read/grep/glob/cc_fs 只读子命令)命中这些相对 CCC 根的路径 → deny。
|
|
791
|
+
*
|
|
792
|
+
* 与 safeMode.blacklist(写黑名单,v1.18.5 决策只拦写)语义独立:
|
|
793
|
+
* - 黑名单 = 用户可配置的写保护(REPOSITORIES/ 等只读参考源不误伤读)
|
|
794
|
+
* - 凭据硬名单 = **结构性数据面边界**(不依赖 safe-mode 开关,凭据文件任何时候不可读)——
|
|
795
|
+
* 对齐 CCE Bounded Space + wardn "structural guarantee, not policy":localstore.json
|
|
796
|
+
* 含 API keys/tokens/SSH 密码,即使 prompt 纪律失败也无值可吐(机械保证)。
|
|
797
|
+
*/
|
|
798
|
+
const SENSITIVE_CREDENTIAL_FILES = /* @__PURE__ */ new Set(["localstore.json"]);
|
|
788
799
|
/** 判定工具是否为写类:普通工具按名;cc_fs 复合工具按子命令 action */
|
|
789
800
|
function isWriteTool(toolName, action) {
|
|
790
801
|
if (!WRITE_TOOLS.has(toolName)) return false;
|
|
@@ -817,8 +828,12 @@ function decideGuard(input) {
|
|
|
817
828
|
deny: `path escape blocked: "${pathArg}" escapes the CCC root`,
|
|
818
829
|
kind: "deny"
|
|
819
830
|
};
|
|
831
|
+
const rel = relative(root, abs).split("\\").join("/");
|
|
832
|
+
if (SENSITIVE_CREDENTIAL_FILES.has(rel)) return {
|
|
833
|
+
deny: `access blocked: "${pathArg}" is a sensitive credential file (${rel})`,
|
|
834
|
+
kind: "deny"
|
|
835
|
+
};
|
|
820
836
|
if (isWriteTool(toolName, action)) {
|
|
821
|
-
const rel = relative(root, abs).split("\\").join("/");
|
|
822
837
|
if (rel === ".serenity-safe-on" || rel === ".serenity" || rel.startsWith(".serenity-safe-on/") || rel.startsWith(".serenity/")) return {
|
|
823
838
|
deny: `CCC governance file "${rel}" is reserved for the user — agent must not write`,
|
|
824
839
|
kind: "deny"
|
|
@@ -2392,7 +2407,7 @@ function listActiveHandymen(root) {
|
|
|
2392
2407
|
}
|
|
2393
2408
|
//#endregion
|
|
2394
2409
|
//#region src/skiff-core.ts
|
|
2395
|
-
const PLUGIN_SOURCE$
|
|
2410
|
+
const PLUGIN_SOURCE$4 = {
|
|
2396
2411
|
kind: "plugin",
|
|
2397
2412
|
plugin: "dsh-serenity-hooks"
|
|
2398
2413
|
};
|
|
@@ -2491,7 +2506,7 @@ function waitIdle$1(ctx, agent) {
|
|
|
2491
2506
|
});
|
|
2492
2507
|
}
|
|
2493
2508
|
/** 读会话最后一个 assistant/message 文本(handyman 同款) */
|
|
2494
|
-
function lastAssistantText$
|
|
2509
|
+
function lastAssistantText$2(agent) {
|
|
2495
2510
|
const events = agent.session.events;
|
|
2496
2511
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2497
2512
|
const e = events[i];
|
|
@@ -2560,10 +2575,10 @@ async function askSkiff(ctx, agent, question, eventsStart) {
|
|
|
2560
2575
|
type: "text",
|
|
2561
2576
|
text: question
|
|
2562
2577
|
}],
|
|
2563
|
-
source: PLUGIN_SOURCE$
|
|
2578
|
+
source: PLUGIN_SOURCE$4
|
|
2564
2579
|
}));
|
|
2565
2580
|
await waitIdle$1(ctx, agent);
|
|
2566
|
-
const answer = lastAssistantText$
|
|
2581
|
+
const answer = lastAssistantText$2(agent);
|
|
2567
2582
|
const trajectory = eventsToTrajectory(agent.session.events.slice(before));
|
|
2568
2583
|
return {
|
|
2569
2584
|
answer,
|
|
@@ -2973,7 +2988,7 @@ function waitIdle(ctx, agent) {
|
|
|
2973
2988
|
});
|
|
2974
2989
|
}
|
|
2975
2990
|
/** 读取会话最后一个 assistant/message 文本 */
|
|
2976
|
-
function lastAssistantText(agent) {
|
|
2991
|
+
function lastAssistantText$1(agent) {
|
|
2977
2992
|
const events = agent.session.events;
|
|
2978
2993
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
2979
2994
|
const e = events[i];
|
|
@@ -3071,7 +3086,7 @@ async function runHandymanJob(ctx, opts) {
|
|
|
3071
3086
|
}
|
|
3072
3087
|
}));
|
|
3073
3088
|
await waitIdle(ctx, workerAgent);
|
|
3074
|
-
lastResponse = lastAssistantText(workerAgent);
|
|
3089
|
+
lastResponse = lastAssistantText$1(workerAgent);
|
|
3075
3090
|
} catch {
|
|
3076
3091
|
restarts++;
|
|
3077
3092
|
if (restarts > 100) {
|
|
@@ -6567,7 +6582,7 @@ function registerBootstrap(ctx) {
|
|
|
6567
6582
|
}
|
|
6568
6583
|
//#endregion
|
|
6569
6584
|
//#region src/rebuild.ts
|
|
6570
|
-
const PLUGIN_SOURCE$
|
|
6585
|
+
const PLUGIN_SOURCE$3 = {
|
|
6571
6586
|
kind: "plugin",
|
|
6572
6587
|
plugin: "dsh-serenity-hooks"
|
|
6573
6588
|
};
|
|
@@ -6753,7 +6768,7 @@ function registerRebuildTurnHook(ctx) {
|
|
|
6753
6768
|
type: "text",
|
|
6754
6769
|
text: "[TRAJECTORY-REBUILD] The conversation has been cleared and rebuilt. Follow the anchor instructions above now: read the persistent trajectory (SESSION.md) and continue the work automatically from the last checkpoint."
|
|
6755
6770
|
}],
|
|
6756
|
-
source: PLUGIN_SOURCE$
|
|
6771
|
+
source: PLUGIN_SOURCE$3
|
|
6757
6772
|
}));
|
|
6758
6773
|
console.log(`[serenity-hooks] session_rebuild executed with auto-continue (turn ${payload.turn ?? "?"} ended): ${id}`);
|
|
6759
6774
|
}
|
|
@@ -7150,7 +7165,7 @@ function readContextPressure(ctx, session) {
|
|
|
7150
7165
|
return null;
|
|
7151
7166
|
}
|
|
7152
7167
|
}
|
|
7153
|
-
const PLUGIN_SOURCE$
|
|
7168
|
+
const PLUGIN_SOURCE$2 = {
|
|
7154
7169
|
kind: "plugin",
|
|
7155
7170
|
plugin: "dsh-serenity-hooks"
|
|
7156
7171
|
};
|
|
@@ -7213,7 +7228,7 @@ function registerKeeper(ctx, opts = {}) {
|
|
|
7213
7228
|
if (blocks.length === 0) return downstream;
|
|
7214
7229
|
const reminderAll = createUserMessage({
|
|
7215
7230
|
content: blocks,
|
|
7216
|
-
source: PLUGIN_SOURCE$
|
|
7231
|
+
source: PLUGIN_SOURCE$2
|
|
7217
7232
|
});
|
|
7218
7233
|
if (downstream.kind === "block") return {
|
|
7219
7234
|
kind: "block",
|
|
@@ -8168,7 +8183,7 @@ function accIdentityText(root, configPaths = DEFAULT_SERENITY_CONFIG_PATHS, entr
|
|
|
8168
8183
|
}
|
|
8169
8184
|
return lines.join("\n");
|
|
8170
8185
|
}
|
|
8171
|
-
const PLUGIN_SOURCE = {
|
|
8186
|
+
const PLUGIN_SOURCE$1 = {
|
|
8172
8187
|
kind: "plugin",
|
|
8173
8188
|
plugin: "dsh-serenity-hooks"
|
|
8174
8189
|
};
|
|
@@ -8185,7 +8200,7 @@ function accMessage(root, configPaths, entrySkillMaxChars) {
|
|
|
8185
8200
|
}];
|
|
8186
8201
|
return createUserMessage({
|
|
8187
8202
|
content,
|
|
8188
|
-
source: PLUGIN_SOURCE
|
|
8203
|
+
source: PLUGIN_SOURCE$1
|
|
8189
8204
|
});
|
|
8190
8205
|
}
|
|
8191
8206
|
/** 每 agent 是否已注入过(进程内存态) */
|
|
@@ -9562,6 +9577,171 @@ function registerGateway(ctx) {
|
|
|
9562
9577
|
});
|
|
9563
9578
|
}
|
|
9564
9579
|
//#endregion
|
|
9580
|
+
//#region src/output-guard.ts
|
|
9581
|
+
/**
|
|
9582
|
+
* output-guard.ts — 最终输出敏感词检测 + 打回重生成(v1.26.3,S142 用户需求)
|
|
9583
|
+
*
|
|
9584
|
+
* 认知容器对外提供认知结果时,**支撑宁静号自身的凭据与机制**不应透露给用户。
|
|
9585
|
+
* 用户拍板方案(讨论稿定稿):不做工具结果 redaction(那是模型工作内存),
|
|
9586
|
+
* 改为**卡最终输出**——`agent/turn-stopping`(serial)时取 turn 最后 assistant 文本,
|
|
9587
|
+
* 命中敏感词表 → `agent.steer()` 打回重生成,直到合规或达上限。
|
|
9588
|
+
*
|
|
9589
|
+
* 敏感词表(有限,ACC 立场,用户确认"这样比较全面了"):
|
|
9590
|
+
* ① 凭据词 — localstore.json credentials 条目名 + 值精确匹配(运行时读)
|
|
9591
|
+
* ② 机制词 — 内部结构静态词(插件名/配置路径/端口/内部实现词)
|
|
9592
|
+
* ③ MSM 词 — mech-registry.json 注册的工具名(用户补充:msm_list 里的工具名)
|
|
9593
|
+
* **不含** 公开概念(宁静号/Serenity/认知容器/EAP/CCE/Neat——正常交流词,误伤灾难)
|
|
9594
|
+
*
|
|
9595
|
+
* 机制依据(DSH 官方):agent/turn-stopping 注释 "a listener that objects steers and
|
|
9596
|
+
* the machine re-reads its inbox: fresh steering runs another step, none closes the
|
|
9597
|
+
* turn"——steer 打回是官方支持的"重生成"通道(v1.22.5 rebuild 自动继续同款先例)。
|
|
9598
|
+
*/
|
|
9599
|
+
/** 插件/内部实现词(静态内置;命中即视为机制泄露) */
|
|
9600
|
+
const MECHANISM_WORDS = [
|
|
9601
|
+
"dsh-serenity-hooks",
|
|
9602
|
+
"serenity-hooks",
|
|
9603
|
+
"serenity-hooks.json",
|
|
9604
|
+
"mech-registry",
|
|
9605
|
+
"mech-registry.json",
|
|
9606
|
+
"localstore.json",
|
|
9607
|
+
".opencode/serenity.json",
|
|
9608
|
+
"AGENT_SESSIONS",
|
|
9609
|
+
"agent/session-start",
|
|
9610
|
+
"agent/turn-stopping",
|
|
9611
|
+
"tools/pre-execute",
|
|
9612
|
+
"tools/post-execute",
|
|
9613
|
+
"session_rebuild",
|
|
9614
|
+
"Trajectory Steward",
|
|
9615
|
+
"first-anchor",
|
|
9616
|
+
"拦截缝",
|
|
9617
|
+
"装配层",
|
|
9618
|
+
"acp-core",
|
|
9619
|
+
"skiff-core",
|
|
9620
|
+
"skiff-registry",
|
|
9621
|
+
"skiff-role",
|
|
9622
|
+
"output-guard"
|
|
9623
|
+
];
|
|
9624
|
+
/** 内部端口(宁静号服务面) */
|
|
9625
|
+
const MECHANISM_PORTS = [
|
|
9626
|
+
"3080",
|
|
9627
|
+
"3081",
|
|
9628
|
+
"3099",
|
|
9629
|
+
"3100"
|
|
9630
|
+
];
|
|
9631
|
+
/**
|
|
9632
|
+
* 构建敏感词表(每次调用实时构建——词源文件小,开销可接受):
|
|
9633
|
+
* ① localstore.json credentials:条目名(UPPER_SNAKE)+ 值(精确匹配)
|
|
9634
|
+
* ② 静态机制词 + 端口
|
|
9635
|
+
* ③ mech-registry 注册的 MSM 工具名(loadMsmEntries 全注册表去重)
|
|
9636
|
+
* @param root CCC 根(localstore.json / mech-registry 所在)
|
|
9637
|
+
*/
|
|
9638
|
+
function buildSensitiveTable(root) {
|
|
9639
|
+
const exact = /* @__PURE__ */ new Set();
|
|
9640
|
+
const substring = [];
|
|
9641
|
+
try {
|
|
9642
|
+
const p = join(root, "localstore.json");
|
|
9643
|
+
if (existsSync(p)) {
|
|
9644
|
+
const creds = JSON.parse(readFileSync(p, "utf-8").replace(/^\uFEFF/, ""))?.credentials;
|
|
9645
|
+
if (creds && typeof creds === "object") for (const [name, value] of Object.entries(creds)) {
|
|
9646
|
+
if (name) exact.add(name);
|
|
9647
|
+
if (typeof value === "string" && value !== "") exact.add(value);
|
|
9648
|
+
}
|
|
9649
|
+
}
|
|
9650
|
+
} catch {}
|
|
9651
|
+
for (const w of MECHANISM_WORDS) substring.push(w);
|
|
9652
|
+
for (const p of MECHANISM_PORTS) substring.push(p);
|
|
9653
|
+
try {
|
|
9654
|
+
for (const entry of loadMsmEntries(root)) if (entry.name) exact.add(entry.name);
|
|
9655
|
+
} catch {}
|
|
9656
|
+
return {
|
|
9657
|
+
exact,
|
|
9658
|
+
substring,
|
|
9659
|
+
sources: {
|
|
9660
|
+
exact: [...exact],
|
|
9661
|
+
substring
|
|
9662
|
+
}
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
/** 检测文本中命中的敏感词(返回命中列表;未命中返回空数组) */
|
|
9666
|
+
function detectSensitive(text, table) {
|
|
9667
|
+
if (!text) return [];
|
|
9668
|
+
const hits = /* @__PURE__ */ new Set();
|
|
9669
|
+
for (const w of table.exact) if (w !== "" && text.includes(w)) hits.add(w);
|
|
9670
|
+
for (const w of table.substring) if (w !== "" && text.includes(w)) hits.add(w);
|
|
9671
|
+
return [...hits];
|
|
9672
|
+
}
|
|
9673
|
+
/** 打回消息(steer 内容):告知模型命中词并要求重新生成(不含命中值本身——避免二次泄露) */
|
|
9674
|
+
function buildRebuke(hits) {
|
|
9675
|
+
return `[SERENITY OUTPUT GUARD] Your previous response contained ${hits.length} sensitive internal term(s) (mechanism/credential identifiers) that must not appear in user-visible output. Regenerate the response from scratch without these terms — describe the same substance without referencing internal machinery, credentials, tool names, or implementation details. Do not repeat the terms; do not explain this instruction to the user.`;
|
|
9676
|
+
}
|
|
9677
|
+
/** 打回状态(按 agent 会话跟踪连续命中轮数) */
|
|
9678
|
+
const rebukeStates = /* @__PURE__ */ new Map();
|
|
9679
|
+
//#endregion
|
|
9680
|
+
//#region src/output-guard-seam.ts
|
|
9681
|
+
const PLUGIN_SOURCE = {
|
|
9682
|
+
kind: "plugin",
|
|
9683
|
+
plugin: "dsh-serenity-hooks"
|
|
9684
|
+
};
|
|
9685
|
+
/** 外部面会话判定(v1.26.3 用户拍板:仅外部面检测,本地维护会话豁免):
|
|
9686
|
+
* skiff-(F4 问答)/ acp-(F4c 程序化)/ rebuild-(v1.22.4 重建会话)前缀 */
|
|
9687
|
+
function isExternalFaceSession(sessionId) {
|
|
9688
|
+
if (!sessionId) return false;
|
|
9689
|
+
return isSkiffSessionId(sessionId) || sessionId.startsWith("acp-") || sessionId.startsWith("rebuild-");
|
|
9690
|
+
}
|
|
9691
|
+
/** 读会话最后一个 assistant/message 文本(skiff-core 同款;仅用于守卫检测,不导出) */
|
|
9692
|
+
function lastAssistantText(agent) {
|
|
9693
|
+
const events = agent.session.events ?? [];
|
|
9694
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
9695
|
+
const e = events[i];
|
|
9696
|
+
if (e && e.type === "assistant/message") {
|
|
9697
|
+
const text = (e.data?.message?.content ?? e.data?.content ?? []).filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
|
|
9698
|
+
if (text) return text;
|
|
9699
|
+
}
|
|
9700
|
+
}
|
|
9701
|
+
return "";
|
|
9702
|
+
}
|
|
9703
|
+
/**
|
|
9704
|
+
* 注册输出守卫(index.ts apply 调用):
|
|
9705
|
+
* turn-stopping 时检测最终输出,命中敏感词 → steer 打回重生成(官方机制,零改 DSH)。
|
|
9706
|
+
*/
|
|
9707
|
+
function registerOutputGuardHook(ctx) {
|
|
9708
|
+
ctx.on("agent/turn-stopping", (payload) => {
|
|
9709
|
+
const agent = payload?.agent;
|
|
9710
|
+
if (!agent) return;
|
|
9711
|
+
if (!isExternalFaceSession(String(agent.session.id ?? ""))) return;
|
|
9712
|
+
const root = findSerenityRoot(agent.session.header?.cwd ?? process.cwd());
|
|
9713
|
+
if (!root) return;
|
|
9714
|
+
const id = agent.id;
|
|
9715
|
+
const text = lastAssistantText(agent);
|
|
9716
|
+
if (!text) return;
|
|
9717
|
+
const hits = detectSensitive(text, buildSensitiveTable(root));
|
|
9718
|
+
if (hits.length === 0) {
|
|
9719
|
+
rebukeStates.delete(id);
|
|
9720
|
+
return;
|
|
9721
|
+
}
|
|
9722
|
+
const st = rebukeStates.get(id) ?? { consecutive: 0 };
|
|
9723
|
+
st.consecutive += 1;
|
|
9724
|
+
rebukeStates.set(id, st);
|
|
9725
|
+
if (st.consecutive > 3) {
|
|
9726
|
+
rebukeStates.delete(id);
|
|
9727
|
+
console.warn(`[serenity-hooks] output-guard: giving up after 3 rebukes (agent=${id}, turn=${payload.turn ?? "?"}) — response kept for manual review`);
|
|
9728
|
+
return;
|
|
9729
|
+
}
|
|
9730
|
+
console.log(`[serenity-hooks] output-guard: rebuke ${st.consecutive}/3 (agent=${id}, turn=${payload.turn ?? "?"}, hits=${hits.length})`);
|
|
9731
|
+
try {
|
|
9732
|
+
agent.steer(createUserMessage({
|
|
9733
|
+
content: [{
|
|
9734
|
+
type: "text",
|
|
9735
|
+
text: buildRebuke(hits)
|
|
9736
|
+
}],
|
|
9737
|
+
source: PLUGIN_SOURCE
|
|
9738
|
+
}));
|
|
9739
|
+
} catch (error) {
|
|
9740
|
+
console.warn(`[serenity-hooks] output-guard steer failed: ${String(error?.message ?? error)}`);
|
|
9741
|
+
}
|
|
9742
|
+
});
|
|
9743
|
+
}
|
|
9744
|
+
//#endregion
|
|
9565
9745
|
//#region node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
|
|
9566
9746
|
/**
|
|
9567
9747
|
* marked v18.0.11 - a markdown parser
|
|
@@ -12252,6 +12432,7 @@ function apply(ctx, config) {
|
|
|
12252
12432
|
registerSettingsSection(ctx, config);
|
|
12253
12433
|
registerGateway(ctx);
|
|
12254
12434
|
registerRebuildTurnHook(ctx);
|
|
12435
|
+
registerOutputGuardHook(ctx);
|
|
12255
12436
|
if (config.env) registerEnv(ctx);
|
|
12256
12437
|
if (config.opencodeSkills) registerOpencodeSkills(ctx);
|
|
12257
12438
|
registerBootstrap(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.3",
|
|
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": {
|