@shgroup/dsh-serenity-hooks 1.16.5 → 1.16.6
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 +181 -56
- 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.6",
|
|
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
|
@@ -2,7 +2,7 @@ import z from "@deepseek-ai/schemastery";
|
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
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
|
-
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
5
|
+
import { execFile, execFileSync, spawn, 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";
|
|
@@ -37,11 +37,29 @@ function findGitRoot(cwd) {
|
|
|
37
37
|
current = parent;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* 前缀判定:abs 是否位于 rootAbs 之内。
|
|
42
|
+
* caseInsensitive(Windows 盘符/路径大小写不敏感)由调用方按平台传入;
|
|
43
|
+
* 边界必须是路径分隔符(`\` 或 `/` 任一)——不依赖平台 sep,跨平台语义一致:
|
|
44
|
+
* - 跨盘符绝对路径(root 在 D:\、target 在 C:\)前缀不匹配 → outside
|
|
45
|
+
* - 兄弟目录前缀陷阱(home vs home2)边界非分隔符 → outside
|
|
46
|
+
* (旧实现用 path.relative().startsWith('..')——跨盘时 relative 返回绝对路径原文,
|
|
47
|
+
* 不以 `..` 开头 → 漏判放行,见 Windows 兼容审计问题 1。)
|
|
48
|
+
*/
|
|
49
|
+
function pathInside(rootAbs, abs, caseInsensitive = process.platform === "win32") {
|
|
50
|
+
const r = caseInsensitive ? rootAbs.toLowerCase() : rootAbs;
|
|
51
|
+
const a = caseInsensitive ? abs.toLowerCase() : abs;
|
|
52
|
+
if (a === r) return true;
|
|
53
|
+
if (!a.startsWith(r)) return false;
|
|
54
|
+
const next = a[r.length];
|
|
55
|
+
return next === "\\" || next === "/";
|
|
56
|
+
}
|
|
40
57
|
function classifyPath(p, root) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return "
|
|
58
|
+
const rootAbs = resolve(root);
|
|
59
|
+
const abs = resolve(p);
|
|
60
|
+
const ci = process.platform === "win32";
|
|
61
|
+
if (ci ? abs.toLowerCase() === rootAbs.toLowerCase() : abs === rootAbs) return "same";
|
|
62
|
+
return pathInside(rootAbs, abs, ci) ? "inside" : "outside";
|
|
45
63
|
}
|
|
46
64
|
function resolveInside(root, p) {
|
|
47
65
|
const abs = resolve(root, p);
|
|
@@ -221,8 +239,16 @@ function runCcFs(root, args) {
|
|
|
221
239
|
else if (os === "linux") {
|
|
222
240
|
const revealPath = statSync(abs).isDirectory() ? abs : dirname(abs);
|
|
223
241
|
execFileSync("xdg-open", [revealPath], { timeout: 1e4 });
|
|
224
|
-
} else if (os === "win32")
|
|
225
|
-
|
|
242
|
+
} else if (os === "win32") {
|
|
243
|
+
const args = statSync(abs).isDirectory() ? [abs] : ["/select,", abs];
|
|
244
|
+
const child = spawn("explorer", args, {
|
|
245
|
+
detached: true,
|
|
246
|
+
stdio: "ignore",
|
|
247
|
+
windowsHide: false
|
|
248
|
+
});
|
|
249
|
+
child.on("error", () => {});
|
|
250
|
+
child.unref();
|
|
251
|
+
} else throw new Error(`unsupported platform: ${os}`);
|
|
226
252
|
return {
|
|
227
253
|
ok: true,
|
|
228
254
|
revealed: safeRel(root, abs)
|
|
@@ -348,6 +374,12 @@ const ccFsTool = defineTool({
|
|
|
348
374
|
*/
|
|
349
375
|
const execFileAsync = promisify(execFile);
|
|
350
376
|
const MSM_TIMEOUT_MS = 6e5;
|
|
377
|
+
/**
|
|
378
|
+
* Windows 兼容(审计观察点 A):`.cmd` 不能直接被 CreateProcess 解析——
|
|
379
|
+
* `execFile('npx')` / `spawnSync('npx')` 在 Windows 必 ENOENT(需 shell 或显式 .cmd)。
|
|
380
|
+
* bun 无扩展名(bun.exe 可被 libuv 按 PATHEXT 解析),保持 'bun'。
|
|
381
|
+
*/
|
|
382
|
+
const NPX_BIN = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
351
383
|
const MSM_ACTIONS = [
|
|
352
384
|
"list",
|
|
353
385
|
"exec",
|
|
@@ -435,7 +467,7 @@ function runMsm(root, args) {
|
|
|
435
467
|
"pipe"
|
|
436
468
|
]
|
|
437
469
|
});
|
|
438
|
-
if (r.error && r.error.code === "ENOENT") r = spawnSync(
|
|
470
|
+
if (r.error && r.error.code === "ENOENT") r = spawnSync(NPX_BIN, [
|
|
439
471
|
"tsx",
|
|
440
472
|
entry.path,
|
|
441
473
|
...businessArgs
|
|
@@ -653,7 +685,7 @@ async function runMsmAsync(root, args) {
|
|
|
653
685
|
} catch (e) {
|
|
654
686
|
const err = e;
|
|
655
687
|
if (err.code === "ENOENT") try {
|
|
656
|
-
const r = await execFileAsync(
|
|
688
|
+
const r = await execFileAsync(NPX_BIN, [
|
|
657
689
|
"tsx",
|
|
658
690
|
entry.path,
|
|
659
691
|
...businessArgs
|
|
@@ -825,6 +857,55 @@ function readActiveSessionMd(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
825
857
|
if (!abs.startsWith(resolve(root))) return null;
|
|
826
858
|
return abs;
|
|
827
859
|
}
|
|
860
|
+
/** 列出全部 scope 的活动标记(含 scope / mdRel / mtime);目录不存在返回空 */
|
|
861
|
+
function listActiveMarkers(root) {
|
|
862
|
+
const dir = resolve(root, ACTIVE_SESSIONS_DIR);
|
|
863
|
+
if (!existsSync(dir)) return [];
|
|
864
|
+
const out = [];
|
|
865
|
+
for (const entry of readdirSync(dir)) {
|
|
866
|
+
const full = join(dir, entry);
|
|
867
|
+
if (!statSync(full).isFile()) continue;
|
|
868
|
+
try {
|
|
869
|
+
const rel = readFileSync(full, "utf-8").trim();
|
|
870
|
+
if (!rel) continue;
|
|
871
|
+
out.push({
|
|
872
|
+
scope: entry,
|
|
873
|
+
mdRel: rel,
|
|
874
|
+
mtime: statSync(full).mtimeMs
|
|
875
|
+
});
|
|
876
|
+
} catch {}
|
|
877
|
+
}
|
|
878
|
+
return out;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* 重启恢复:当前 scope 无标记时,把"最近激活"(mtime 最新)且根内有效的标记
|
|
882
|
+
* 复制为当前 scope 标记(激活语义延续:use = 激活,重启自动恢复 = 重新激活)。
|
|
883
|
+
* 返回恢复的会话信息;已有标记 / 无候选 / 全部越界 → null。
|
|
884
|
+
* 调用方负责根会话判定(subagent / loop 牛马不恢复——见 context.ts shouldAutoRestore)。
|
|
885
|
+
*/
|
|
886
|
+
function restoreActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
|
|
887
|
+
const marker = activeSessionMarker(root, scope);
|
|
888
|
+
if (existsSync(marker)) return null;
|
|
889
|
+
const scopeName = sanitizeScope(scope);
|
|
890
|
+
const candidates = listActiveMarkers(root).filter((m) => m.scope !== scopeName);
|
|
891
|
+
if (candidates.length === 0) return null;
|
|
892
|
+
const rootAbs = resolve(root);
|
|
893
|
+
const valid = candidates.filter((m) => pathInside(rootAbs, resolve(rootAbs, m.mdRel)));
|
|
894
|
+
if (valid.length === 0) return null;
|
|
895
|
+
const best = valid.sort((a, b) => b.mtime - a.mtime)[0];
|
|
896
|
+
mkdirSync(resolve(rootAbs, ACTIVE_SESSIONS_DIR), { recursive: true });
|
|
897
|
+
writeFileSync(marker, best.mdRel, "utf-8");
|
|
898
|
+
const mdPath = resolve(rootAbs, best.mdRel);
|
|
899
|
+
const dirName = basename(dirname(mdPath));
|
|
900
|
+
const idMatch = dirName.match(/S(\d{3,})/);
|
|
901
|
+
return {
|
|
902
|
+
dir: dirName,
|
|
903
|
+
id: idMatch ? `S${idMatch[1]}` : null,
|
|
904
|
+
mdPath,
|
|
905
|
+
restored: true,
|
|
906
|
+
from: best.scope
|
|
907
|
+
};
|
|
908
|
+
}
|
|
828
909
|
function archiveSession(root, key) {
|
|
829
910
|
const target = findSession(root, key);
|
|
830
911
|
if (!target) throw new Error(`未找到会话: ${key}`);
|
|
@@ -1211,14 +1292,15 @@ function setSafeMode(root, on) {
|
|
|
1211
1292
|
*
|
|
1212
1293
|
* health: CCC 三原则检查(P1 .serenity / P2 git / 配置)
|
|
1213
1294
|
* time: ISO 8601 时间戳
|
|
1214
|
-
* wait:
|
|
1295
|
+
* wait: 等待 N 秒(纯 Node setTimeout——不依赖外部 sleep 可执行文件,
|
|
1296
|
+
* Windows 无 GNU coreutils sleep,spawn 必 ENOENT,见 Windows 兼容审计问题 3)
|
|
1215
1297
|
*/
|
|
1216
1298
|
const KIT_ACTIONS = [
|
|
1217
1299
|
"health",
|
|
1218
1300
|
"time",
|
|
1219
1301
|
"wait"
|
|
1220
1302
|
];
|
|
1221
|
-
function runKit(root, args) {
|
|
1303
|
+
async function runKit(root, args) {
|
|
1222
1304
|
switch (args.action) {
|
|
1223
1305
|
case "health": {
|
|
1224
1306
|
const gitRoot = findGitRoot(root);
|
|
@@ -1253,7 +1335,7 @@ function runKit(root, args) {
|
|
|
1253
1335
|
case "wait": {
|
|
1254
1336
|
const n = args.seconds ?? 0;
|
|
1255
1337
|
if (!Number.isFinite(n) || n < 0) throw new Error("wait 需要非负秒数");
|
|
1256
|
-
|
|
1338
|
+
await new Promise((r) => setTimeout(r, Math.round(n * 1e3)));
|
|
1257
1339
|
return { waited: n };
|
|
1258
1340
|
}
|
|
1259
1341
|
default: throw new Error(`未知 action: ${args.action}`);
|
|
@@ -1295,7 +1377,7 @@ const kitTool = defineTool({
|
|
|
1295
1377
|
async execute(args, exec) {
|
|
1296
1378
|
const root = findSerenityRoot(agentCwd$4(exec));
|
|
1297
1379
|
if (!root) throw new Error("No CCC found: no .serenity file from agent cwd");
|
|
1298
|
-
return runKit(root, args);
|
|
1380
|
+
return await runKit(root, args);
|
|
1299
1381
|
}
|
|
1300
1382
|
});
|
|
1301
1383
|
//#endregion
|
|
@@ -1337,7 +1419,12 @@ function git(root, args) {
|
|
|
1337
1419
|
function runGit(root, args) {
|
|
1338
1420
|
switch (args.action) {
|
|
1339
1421
|
case "status": {
|
|
1340
|
-
const r = git(root, [
|
|
1422
|
+
const r = git(root, [
|
|
1423
|
+
"-c",
|
|
1424
|
+
"core.quotepath=false",
|
|
1425
|
+
"status",
|
|
1426
|
+
"--porcelain"
|
|
1427
|
+
]);
|
|
1341
1428
|
if (!r.ok) throw new Error(`status 失败:${r.stderr.trim()}`);
|
|
1342
1429
|
return {
|
|
1343
1430
|
clean: r.stdout.trim() === "",
|
|
@@ -1384,6 +1471,8 @@ function runGit(root, args) {
|
|
|
1384
1471
|
case "log": {
|
|
1385
1472
|
const n = args.count ?? 10;
|
|
1386
1473
|
const r = git(root, [
|
|
1474
|
+
"-c",
|
|
1475
|
+
"core.quotepath=false",
|
|
1387
1476
|
"log",
|
|
1388
1477
|
"--oneline",
|
|
1389
1478
|
"-n",
|
|
@@ -1798,9 +1887,9 @@ function splitModel(model) {
|
|
|
1798
1887
|
}
|
|
1799
1888
|
/** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报) */
|
|
1800
1889
|
function buildRoundPrompt(opts) {
|
|
1801
|
-
const { root, session, label, round,
|
|
1890
|
+
const { root, session, label, round, stopToken, progress, task } = opts;
|
|
1802
1891
|
const resumeNote = progress && progress.round > 0 ? `上一轮(round ${progress.round})已完成:${progress.lastResponse.slice(0, 300)}\n永远从上次停止处继续,绝不重做已完成工作。` : "这是第一轮。";
|
|
1803
|
-
return `# ${label} — 牛马循环 round ${round}
|
|
1892
|
+
return `# ${label} — 牛马循环 round ${round}
|
|
1804
1893
|
|
|
1805
1894
|
CCC 根:${root}
|
|
1806
1895
|
${session ? `工作会话:${session}(AGENT_SESSIONS/${session}/SESSION.md 记录进度)` : ""}
|
|
@@ -1824,11 +1913,14 @@ ${resumeNote}
|
|
|
1824
1913
|
* label(必)任务标签 → 进度文件 loop-<label>.md/.json
|
|
1825
1914
|
* session(选)工作会话 S###(上下文提示)
|
|
1826
1915
|
* model(选)provider/model(如 minimax-cn-coding-plan/MiniMax-M3);缺省读 loop.defaultModel
|
|
1827
|
-
*
|
|
1916
|
+
* (S134 修正:轮次不需要调用者指定——内部 while 驱动 agent 逐轮推进,
|
|
1917
|
+
* 对话轮次**无上限**(不完成不返回);agent 非正常停止时自动重启,
|
|
1918
|
+
* 重启次数上限 LOOP_MAX_RESTARTS=100(防死循环保险阀))
|
|
1828
1919
|
*
|
|
1829
1920
|
* 机制:ctx.agents.create()(带 setup 钩子)创建专用 agent(进程内),
|
|
1830
|
-
* 每轮 followup → agent/status idle → 读 session.events 响应 → 写进度 → stop token 检查 →
|
|
1831
|
-
*
|
|
1921
|
+
* 每轮 followup → agent/status idle → 读 session.events 响应 → 写进度 → stop token 检查 →
|
|
1922
|
+
* 未完成继续下一轮;followup/waitIdle 抛错(非正常停止)→ dispose 并重新 create agent
|
|
1923
|
+
* (重启计数,≤100),同一轮重试。工厂模式:apply 时闭包捕获插件 ctx(工具 execute 无 ctx 参数)。
|
|
1832
1924
|
*
|
|
1833
1925
|
* preset 继承:setup 钩子里对子 agent 执行 agentPresets.composeFrom(对齐 subagent 先例),
|
|
1834
1926
|
* 使 loop agent 继承发起方会话的 agent preset 工具(read/write/edit 等 preset 层工具)。
|
|
@@ -1876,7 +1968,7 @@ function lastAssistantText(agent) {
|
|
|
1876
1968
|
function createLoopTool(ctx) {
|
|
1877
1969
|
return defineTool({
|
|
1878
1970
|
name: "loop",
|
|
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
|
|
1971
|
+
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),防止低智能模型提前结束。轮次不需要调用者指定——对话轮次**无上限**(不完成不返回),agent 非正常停止时自动重启(重启 ≤100 次,防死循环保险阀)。\n进度:写入 AGENT_SESSIONS/loop-<label>.md/.json;同 label 再次调用从上次轮次续跑(不重做)。\n约束:loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫/session-keeper)。\n示例:loop 执行「扫描 SQC 并修复 DC 问题」,label: sqc-scan",
|
|
1880
1972
|
parameters: {
|
|
1881
1973
|
task: {
|
|
1882
1974
|
type: "string",
|
|
@@ -1894,10 +1986,6 @@ function createLoopTool(ctx) {
|
|
|
1894
1986
|
model: {
|
|
1895
1987
|
type: "string",
|
|
1896
1988
|
description: "provider/model(如 minimax-cn-coding-plan/MiniMax-M3);缺省读 loop.defaultModel"
|
|
1897
|
-
},
|
|
1898
|
-
maxRounds: {
|
|
1899
|
-
type: "integer",
|
|
1900
|
-
description: "保险阀(防死循环,默认 100;调用者通常无需设置——loop 跑到完成或达此上限)"
|
|
1901
1989
|
}
|
|
1902
1990
|
},
|
|
1903
1991
|
output: {
|
|
@@ -1910,7 +1998,6 @@ function createLoopTool(ctx) {
|
|
|
1910
1998
|
const cfg = loadSerenityConfig(root, DEFAULT_SERENITY_CONFIG_PATHS);
|
|
1911
1999
|
const model = args.model ?? cfg.loop?.defaultModel;
|
|
1912
2000
|
if (!model) throw new Error("loop 需要 model:传参或配置 .dsh/serenity.json loop.defaultModel");
|
|
1913
|
-
const maxRounds = args.maxRounds ?? 100;
|
|
1914
2001
|
const label = args.label;
|
|
1915
2002
|
if (!ctx.agentLoop) throw new Error("loop: ctx.agentLoop 不可用");
|
|
1916
2003
|
const { provider, model: modelName } = splitModel(model);
|
|
@@ -1920,48 +2007,62 @@ function createLoopTool(ctx) {
|
|
|
1920
2007
|
const parentCtx = exec.agent?.ctx;
|
|
1921
2008
|
const inherited = loopPresetInheritance(parentCtx);
|
|
1922
2009
|
if (!ctx.agents) throw new Error("loop: ctx.agents 不可用");
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2010
|
+
let handle;
|
|
2011
|
+
let loopAgent;
|
|
2012
|
+
const spawnAgent = async () => {
|
|
2013
|
+
const sessionId = `loop-${label}-${randomUUID()}`;
|
|
2014
|
+
handle = await ctx.agents.create({
|
|
2015
|
+
sessionId,
|
|
2016
|
+
meta: {
|
|
2017
|
+
cwd: root,
|
|
2018
|
+
...inherited.agentPreset === void 0 ? {} : { agentPreset: inherited.agentPreset }
|
|
2019
|
+
},
|
|
2020
|
+
agentOptions: {
|
|
2021
|
+
provider,
|
|
2022
|
+
model: modelName
|
|
2023
|
+
},
|
|
2024
|
+
...inherited.setup === void 0 ? {} : { setup: inherited.setup }
|
|
2025
|
+
});
|
|
2026
|
+
loopAgent = handle.agent;
|
|
2027
|
+
};
|
|
2028
|
+
await spawnAgent();
|
|
1937
2029
|
let done = false;
|
|
1938
2030
|
let lastResponse = progress?.lastResponse ?? "";
|
|
1939
2031
|
let finalRound = startRound - 1;
|
|
2032
|
+
let restarts = 0;
|
|
1940
2033
|
try {
|
|
1941
|
-
|
|
2034
|
+
let round = startRound;
|
|
2035
|
+
while (true) {
|
|
1942
2036
|
finalRound = round;
|
|
1943
2037
|
const prompt = buildRoundPrompt({
|
|
1944
2038
|
root,
|
|
1945
2039
|
session: args.session,
|
|
1946
2040
|
label,
|
|
1947
2041
|
round,
|
|
1948
|
-
maxRounds,
|
|
1949
2042
|
stopToken,
|
|
1950
2043
|
progress,
|
|
1951
2044
|
task: args.task
|
|
1952
2045
|
});
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2046
|
+
try {
|
|
2047
|
+
loopAgent.followup(createUserMessage({
|
|
2048
|
+
content: [{
|
|
2049
|
+
type: "text",
|
|
2050
|
+
text: prompt
|
|
2051
|
+
}],
|
|
2052
|
+
source: {
|
|
2053
|
+
kind: "plugin",
|
|
2054
|
+
plugin: "dsh-serenity-hooks"
|
|
2055
|
+
}
|
|
2056
|
+
}));
|
|
2057
|
+
await waitIdle(ctx, loopAgent);
|
|
2058
|
+
lastResponse = lastAssistantText(loopAgent);
|
|
2059
|
+
} catch {
|
|
2060
|
+
restarts++;
|
|
2061
|
+
if (restarts > 100) break;
|
|
2062
|
+
await handle.dispose().catch(() => {});
|
|
2063
|
+
await spawnAgent();
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
1965
2066
|
progress = {
|
|
1966
2067
|
round,
|
|
1967
2068
|
done: false,
|
|
@@ -1975,6 +2076,7 @@ function createLoopTool(ctx) {
|
|
|
1975
2076
|
done = true;
|
|
1976
2077
|
break;
|
|
1977
2078
|
}
|
|
2079
|
+
round++;
|
|
1978
2080
|
}
|
|
1979
2081
|
writeProgress(root, label, {
|
|
1980
2082
|
round: finalRound,
|
|
@@ -1991,15 +2093,16 @@ function createLoopTool(ctx) {
|
|
|
1991
2093
|
return {
|
|
1992
2094
|
done,
|
|
1993
2095
|
rounds: finalRound,
|
|
2096
|
+
restarts,
|
|
1994
2097
|
model,
|
|
1995
2098
|
label,
|
|
1996
2099
|
progressFile: json,
|
|
1997
2100
|
lastResponse: lastResponse.slice(0, 2e3),
|
|
1998
2101
|
usage: {
|
|
1999
|
-
how: "loop 内部硬性 while 驱动 agent 逐轮推进,agent
|
|
2102
|
+
how: "loop 内部硬性 while 驱动 agent 逐轮推进,agent 精确回显随机完成码即终止;对话轮次无上限(不完成不返回),agent 非正常停止时自动重启(≤100 次)",
|
|
2000
2103
|
progress: `进度在 AGENT_SESSIONS/loop-${label}.md 与 .json;同 label 再调 loop 会从下一轮续跑(不重做)`,
|
|
2001
2104
|
constraints: "loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫)",
|
|
2002
|
-
next: done ? "任务已完成;可查看进度文件收尾" :
|
|
2105
|
+
next: done ? "任务已完成;可查看进度文件收尾" : `任务未完成(已达内部 100 次重启保险阀);可同 label 续跑`
|
|
2003
2106
|
}
|
|
2004
2107
|
};
|
|
2005
2108
|
}
|
|
@@ -2558,7 +2661,7 @@ function accBlock(root) {
|
|
|
2558
2661
|
"",
|
|
2559
2662
|
" cc_fs — CCC 内文件系统操作(root/resolve/exists/list/tree/relative/mkdir/rm/mv/cp/touch/append/reveal/info/find)",
|
|
2560
2663
|
" session — session lifecycle(list/show/create/health/qa/archive/summary)",
|
|
2561
|
-
" acc_kit — ACC utility kit(health: CCC three principles / time: now / wait:
|
|
2664
|
+
" acc_kit — ACC utility kit(health: CCC three principles / time: now / wait: wait N seconds)",
|
|
2562
2665
|
" cc_git — git operations(status/commit/push/log)",
|
|
2563
2666
|
" acc_msm — MSM framework(list/exec/register/deregister/check/guide)",
|
|
2564
2667
|
" eap — return the full EAP cognitive quality framework",
|
|
@@ -2858,6 +2961,22 @@ function agentKey(agent) {
|
|
|
2858
2961
|
function agentScope(agent) {
|
|
2859
2962
|
return agent.session.id ?? "default";
|
|
2860
2963
|
}
|
|
2964
|
+
/**
|
|
2965
|
+
* 重启恢复的根会话判定(S134 需求):
|
|
2966
|
+
* 只有"conversation 根会话"才自动恢复最近激活的宁静号会话——
|
|
2967
|
+
* - subagent:session header `origin === 'subagent'`(DSH 路由语义,agent-lookup.ts)
|
|
2968
|
+
* - 派生会话:`parentSession` 存在(任何子会话)
|
|
2969
|
+
* - loop 牛马:sessionId 固定 `loop-` 前缀(tools/loop.ts 生成)
|
|
2970
|
+
* 三者都不恢复(避免把主会话激活注入子上下文,违背 v1.16.2 scope 隔离)。
|
|
2971
|
+
*/
|
|
2972
|
+
function shouldAutoRestore(agent) {
|
|
2973
|
+
const session = agent.session;
|
|
2974
|
+
if (!session) return false;
|
|
2975
|
+
if (session.header?.origin === "subagent") return false;
|
|
2976
|
+
if (session.header?.parentSession) return false;
|
|
2977
|
+
if (session.id?.startsWith("loop-")) return false;
|
|
2978
|
+
return true;
|
|
2979
|
+
}
|
|
2861
2980
|
function registerContext(ctx, opts = {}) {
|
|
2862
2981
|
const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
|
|
2863
2982
|
const entrySkillMaxChars = opts.entrySkillMaxChars ?? 3e4;
|
|
@@ -2866,6 +2985,12 @@ function registerContext(ctx, opts = {}) {
|
|
|
2866
2985
|
if (!root) return;
|
|
2867
2986
|
const key = agentKey(agent);
|
|
2868
2987
|
registerEntrySkillSection(agent, root);
|
|
2988
|
+
if (shouldAutoRestore(agent)) try {
|
|
2989
|
+
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
2990
|
+
const restored = restoreActiveSession(root, agentScope(agent));
|
|
2991
|
+
if (restored) console.log(`[serenity-hooks] ↻ 自动恢复激活会话: ${restored.dir}(from scope ${restored.from})`);
|
|
2992
|
+
}
|
|
2993
|
+
} catch {}
|
|
2869
2994
|
if (injected.has(key)) return;
|
|
2870
2995
|
injected.add(key);
|
|
2871
2996
|
agent.inject(accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)));
|
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.6",
|
|
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": {
|