@shgroup/dsh-serenity-hooks 1.16.4 → 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 +377 -26
- 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
6
|
import { homedir, platform } from "node:os";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
|
-
import { randomBytes } from "node:crypto";
|
|
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));
|
|
@@ -1267,7 +1267,7 @@ function runKit(root, args) {
|
|
|
1267
1267
|
function agentCwd$4(exec) {
|
|
1268
1268
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1269
1269
|
}
|
|
1270
|
-
function renderText$
|
|
1270
|
+
function renderText$7(value) {
|
|
1271
1271
|
return [{
|
|
1272
1272
|
type: "text",
|
|
1273
1273
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1290,7 +1290,7 @@ const kitTool = defineTool({
|
|
|
1290
1290
|
},
|
|
1291
1291
|
output: {
|
|
1292
1292
|
schema: { type: "json" },
|
|
1293
|
-
render: (args, value) => renderText$
|
|
1293
|
+
render: (args, value) => renderText$7(value)
|
|
1294
1294
|
},
|
|
1295
1295
|
async execute(args, exec) {
|
|
1296
1296
|
const root = findSerenityRoot(agentCwd$4(exec));
|
|
@@ -1403,7 +1403,7 @@ function runGit(root, args) {
|
|
|
1403
1403
|
function agentCwd$3(exec) {
|
|
1404
1404
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1405
1405
|
}
|
|
1406
|
-
function renderText$
|
|
1406
|
+
function renderText$6(value) {
|
|
1407
1407
|
return [{
|
|
1408
1408
|
type: "text",
|
|
1409
1409
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1430,7 +1430,7 @@ const gitTool = defineTool({
|
|
|
1430
1430
|
},
|
|
1431
1431
|
output: {
|
|
1432
1432
|
schema: { type: "json" },
|
|
1433
|
-
render: (args, value) => renderText$
|
|
1433
|
+
render: (args, value) => renderText$6(value)
|
|
1434
1434
|
},
|
|
1435
1435
|
async execute(args, exec) {
|
|
1436
1436
|
const root = findSerenityRoot(agentCwd$3(exec));
|
|
@@ -1446,7 +1446,7 @@ const gitTool = defineTool({
|
|
|
1446
1446
|
function agentCwd$2(exec) {
|
|
1447
1447
|
return exec.agent?.session?.header?.cwd ?? process.cwd();
|
|
1448
1448
|
}
|
|
1449
|
-
function renderText$
|
|
1449
|
+
function renderText$5(value) {
|
|
1450
1450
|
return [{
|
|
1451
1451
|
type: "text",
|
|
1452
1452
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1490,7 +1490,7 @@ const msmTool = defineTool({
|
|
|
1490
1490
|
},
|
|
1491
1491
|
output: {
|
|
1492
1492
|
schema: { type: "json" },
|
|
1493
|
-
render: (args, value) => renderText$
|
|
1493
|
+
render: (args, value) => renderText$5(value)
|
|
1494
1494
|
},
|
|
1495
1495
|
async execute(args, exec) {
|
|
1496
1496
|
const root = findSerenityRoot(agentCwd$2(exec));
|
|
@@ -1531,7 +1531,7 @@ CCC(home-serenity 等)认知内容编码为 skill/SESSION/设计文档。产
|
|
|
1531
1531
|
|
|
1532
1532
|
## 参考
|
|
1533
1533
|
https://github.com/tellmewhattodo/theory-eap`;
|
|
1534
|
-
function renderText$
|
|
1534
|
+
function renderText$4(value) {
|
|
1535
1535
|
return [{
|
|
1536
1536
|
type: "text",
|
|
1537
1537
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1551,7 +1551,7 @@ const eapTool = defineTool({
|
|
|
1551
1551
|
} },
|
|
1552
1552
|
output: {
|
|
1553
1553
|
schema: { type: "string" },
|
|
1554
|
-
render: (_args, value) => renderText$
|
|
1554
|
+
render: (_args, value) => renderText$4(value)
|
|
1555
1555
|
},
|
|
1556
1556
|
async execute(args) {
|
|
1557
1557
|
if (args.section === "variables") return EAP_CONTENT.split("## 输出前自检清单")[0];
|
|
@@ -1592,7 +1592,7 @@ const NEAT_CONTENT = `# Neat 设计协作协议
|
|
|
1592
1592
|
2. 等待确认或修正(小步)
|
|
1593
1593
|
3. 确认后记录决策(写入会话 SESSION.md 或设计文档)
|
|
1594
1594
|
4. 推进到下一决策点`;
|
|
1595
|
-
function renderText$
|
|
1595
|
+
function renderText$3(value) {
|
|
1596
1596
|
return [{
|
|
1597
1597
|
type: "text",
|
|
1598
1598
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1608,7 +1608,7 @@ const neatTool = defineTool({
|
|
|
1608
1608
|
} },
|
|
1609
1609
|
output: {
|
|
1610
1610
|
schema: { type: "string" },
|
|
1611
|
-
render: (_args, value) => renderText$
|
|
1611
|
+
render: (_args, value) => renderText$3(value)
|
|
1612
1612
|
},
|
|
1613
1613
|
async execute(args) {
|
|
1614
1614
|
if (args.section === "rules") return NEAT_CONTENT.match(/## 四条铁律[\s\S]*?(?=## )/)?.[0] ?? NEAT_CONTENT;
|
|
@@ -1671,7 +1671,7 @@ CCE 回答"有结构的知识应如何跨时间持续演化而不丧失连贯性
|
|
|
1671
1671
|
## 与 Serenity 的关系
|
|
1672
1672
|
Serenity 的会话系统、会话追踪、熵管理机制(SQC 品质循环)都是 CCE 的工程实现;
|
|
1673
1673
|
CCC 系统提示词中嵌入的行为约束即来自 CCE。`;
|
|
1674
|
-
function renderText$
|
|
1674
|
+
function renderText$2(value) {
|
|
1675
1675
|
return [{
|
|
1676
1676
|
type: "text",
|
|
1677
1677
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1692,7 +1692,7 @@ const cceTool = defineTool({
|
|
|
1692
1692
|
} },
|
|
1693
1693
|
output: {
|
|
1694
1694
|
schema: { type: "string" },
|
|
1695
|
-
render: (_args, value) => renderText$
|
|
1695
|
+
render: (_args, value) => renderText$2(value)
|
|
1696
1696
|
},
|
|
1697
1697
|
async execute(args) {
|
|
1698
1698
|
const section = args.section;
|
|
@@ -1837,7 +1837,7 @@ ${resumeNote}
|
|
|
1837
1837
|
function agentCwd$1(exec) {
|
|
1838
1838
|
return (exec.agent?.session)?.header?.cwd ?? process.cwd();
|
|
1839
1839
|
}
|
|
1840
|
-
function renderText(value) {
|
|
1840
|
+
function renderText$1(value) {
|
|
1841
1841
|
return [{
|
|
1842
1842
|
type: "text",
|
|
1843
1843
|
text: typeof value === "string" ? value : JSON.stringify(value, null, 2)
|
|
@@ -1876,7 +1876,7 @@ function lastAssistantText(agent) {
|
|
|
1876
1876
|
function createLoopTool(ctx) {
|
|
1877
1877
|
return defineTool({
|
|
1878
1878
|
name: "loop",
|
|
1879
|
-
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",
|
|
1880
1880
|
parameters: {
|
|
1881
1881
|
task: {
|
|
1882
1882
|
type: "string",
|
|
@@ -1897,12 +1897,12 @@ function createLoopTool(ctx) {
|
|
|
1897
1897
|
},
|
|
1898
1898
|
maxRounds: {
|
|
1899
1899
|
type: "integer",
|
|
1900
|
-
description: "
|
|
1900
|
+
description: "保险阀(防死循环,默认 100;调用者通常无需设置——loop 跑到完成或达此上限)"
|
|
1901
1901
|
}
|
|
1902
1902
|
},
|
|
1903
1903
|
output: {
|
|
1904
1904
|
schema: { type: "json" },
|
|
1905
|
-
render: (_args, value) => renderText(value)
|
|
1905
|
+
render: (_args, value) => renderText$1(value)
|
|
1906
1906
|
},
|
|
1907
1907
|
async execute(args, exec) {
|
|
1908
1908
|
const root = findSerenityRoot(agentCwd$1(exec));
|
|
@@ -1916,11 +1916,11 @@ function createLoopTool(ctx) {
|
|
|
1916
1916
|
const { provider, model: modelName } = splitModel(model);
|
|
1917
1917
|
const stopToken = newStopToken();
|
|
1918
1918
|
let progress = readProgress(root, label);
|
|
1919
|
-
const startRound = progress ?
|
|
1919
|
+
const startRound = progress ? progress.round + 1 : 1;
|
|
1920
1920
|
const parentCtx = exec.agent?.ctx;
|
|
1921
1921
|
const inherited = loopPresetInheritance(parentCtx);
|
|
1922
1922
|
if (!ctx.agents) throw new Error("loop: ctx.agents 不可用");
|
|
1923
|
-
const sessionId = `loop-${label}`;
|
|
1923
|
+
const sessionId = `loop-${label}-${randomUUID()}`;
|
|
1924
1924
|
const handle = await ctx.agents.create({
|
|
1925
1925
|
sessionId,
|
|
1926
1926
|
meta: {
|
|
@@ -1996,16 +1996,365 @@ function createLoopTool(ctx) {
|
|
|
1996
1996
|
progressFile: json,
|
|
1997
1997
|
lastResponse: lastResponse.slice(0, 2e3),
|
|
1998
1998
|
usage: {
|
|
1999
|
-
how: "loop
|
|
1999
|
+
how: "loop 内部硬性 while 驱动 agent 逐轮推进,agent 精确回显随机完成码即终止",
|
|
2000
2000
|
progress: `进度在 AGENT_SESSIONS/loop-${label}.md 与 .json;同 label 再调 loop 会从下一轮续跑(不重做)`,
|
|
2001
2001
|
constraints: "loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫)",
|
|
2002
|
-
next: done ? "任务已完成;可查看进度文件收尾" :
|
|
2002
|
+
next: done ? "任务已完成;可查看进度文件收尾" : `任务未完成(已达保险阀 ${maxRounds} 轮);可同 label 续跑`
|
|
2003
2003
|
}
|
|
2004
2004
|
};
|
|
2005
2005
|
}
|
|
2006
2006
|
});
|
|
2007
2007
|
}
|
|
2008
2008
|
//#endregion
|
|
2009
|
+
//#region src/localstore-ops.ts
|
|
2010
|
+
/**
|
|
2011
|
+
* localstore-ops.ts — localstore 纯逻辑层(零 DSH 依赖,可独立单测)
|
|
2012
|
+
*
|
|
2013
|
+
* ACC 标准本地凭据/配置存储(S133 设计):
|
|
2014
|
+
* - 一个工具管理两个命名空间:credential(凭据,0600)+ config(偏好,0644)
|
|
2015
|
+
* - 存储于 ~/.serenity/(平台感知:win %USERPROFILE%\.serenity\)
|
|
2016
|
+
* - 两个 YAML 文件:credentials.yaml(扁平 REF→value)+ settings.yaml(命名空间分节)
|
|
2017
|
+
* - 目录 0700
|
|
2018
|
+
*
|
|
2019
|
+
* YAML 用轻量自实现子集(零依赖):扁平 `KEY: value` 映射 + `#` 注释。
|
|
2020
|
+
* 凭据/配置本质是给 MSM/agent 用的普通 YAML 文件——agent 可按 doc 子命令
|
|
2021
|
+
* 说明直接用 fs 工具(read/write)操作;本工具是管理入口 + 规范文档。
|
|
2022
|
+
*/
|
|
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;
|
|
2069
|
+
}
|
|
2070
|
+
return out;
|
|
2071
|
+
}
|
|
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;
|
|
2103
|
+
}
|
|
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)`);
|
|
2135
|
+
return {
|
|
2136
|
+
section,
|
|
2137
|
+
key
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
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}`));
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* doc 说明文本:输出存储位置/格式/key 规范/权限/读写方法/安全边界。
|
|
2198
|
+
* agent 据此可直接用 fs 工具(read/write)自己读写凭据/配置。
|
|
2199
|
+
*/
|
|
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");
|
|
2250
|
+
}
|
|
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)`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
//#endregion
|
|
2303
|
+
//#region src/tools/localstore.ts
|
|
2304
|
+
/**
|
|
2305
|
+
* localstore.ts — localstore 真实 DSH 工具定义(defineTool)
|
|
2306
|
+
*
|
|
2307
|
+
* ACC 标准本地凭据/配置存储(S133 设计)。进程内注册,零 DSH 依赖逻辑在
|
|
2308
|
+
* localstore-ops.ts(可单测)。doc 子命令输出存储规范,agent 可直接用 fs
|
|
2309
|
+
* 工具(read/write)自己读写。
|
|
2310
|
+
*/
|
|
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
|
+
}];
|
|
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
|
+
});
|
|
2357
|
+
//#endregion
|
|
2009
2358
|
//#region src/seams/loop.ts
|
|
2010
2359
|
/** 解析当前 dsh 会话 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
|
|
2011
2360
|
function resolveActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
@@ -2216,6 +2565,7 @@ function accBlock(root) {
|
|
|
2216
2565
|
" neat — return the full Neat design collaboration protocol",
|
|
2217
2566
|
" cce — return the full Cognitive Continuity Engineering framework",
|
|
2218
2567
|
" loop — 牛马循环:指定模型专用 agent 反复执行",
|
|
2568
|
+
" localstore — ACC 本地凭据/配置存储(credential 0600 + config 0644,~/.serenity/);doc 子命令输出规范",
|
|
2219
2569
|
"",
|
|
2220
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.",
|
|
2221
2571
|
"",
|
|
@@ -2833,6 +3183,7 @@ function apply(ctx, config) {
|
|
|
2833
3183
|
ctx.tools.register(neatTool);
|
|
2834
3184
|
ctx.tools.register(cceTool);
|
|
2835
3185
|
ctx.tools.register(createLoopTool(ctx));
|
|
3186
|
+
ctx.tools.register(localstoreTool);
|
|
2836
3187
|
}
|
|
2837
3188
|
if (config.guards) registerGuards(ctx, { configPaths: config.serenityConfigPaths });
|
|
2838
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": {
|