@shgroup/dsh-serenity-hooks 1.24.10 → 1.24.11
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 +113 -31
- 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.24.
|
|
3
|
+
"version": "1.24.11",
|
|
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 + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
|
|
6
6
|
"engines": {
|
package/lib/index.js
CHANGED
|
@@ -5227,26 +5227,38 @@ function closeSession(root, key, confirm, scope = DEFAULT_SESSION_SCOPE) {
|
|
|
5227
5227
|
return `Session "${session.dirName}" closed and marked as completed.`;
|
|
5228
5228
|
}
|
|
5229
5229
|
/**
|
|
5230
|
-
* 从会话历史(events
|
|
5231
|
-
*
|
|
5230
|
+
* 从会话历史(events)解析会话身份(进程重启恢复;只扫**当前会话**自己的历史——无跨会话串台)。
|
|
5231
|
+
*
|
|
5232
|
+
* v1.24.11 稳固化(S142 用户需求:重建后新会话必须准确知道从哪个 SESSION 恢复):
|
|
5233
|
+
* **路径规范行即可,不再要求 [SESSION CONTEXT] 标记**——`session use` 上下文与重建锚点
|
|
5234
|
+
* (buildRebuildAnchor 的 `- Persistent trajectory — SESSION.md path: <rel>` 行)同格式,
|
|
5235
|
+
* 因此**仅靠重建锚点的会话(从未显式 use)同样可恢复**。从**尾到头**扫描,最后一条
|
|
5236
|
+
* 合法路径胜出(时间序最新);会话目录名/ID 从路径本身派生(单一真相源,不猜)。
|
|
5237
|
+
* 路径可为相对(重建锚点存 rel)——绝对化与存在性校验在调用方(知道 root)执行。
|
|
5232
5238
|
*/
|
|
5233
5239
|
function parseSessionContextFromEvents(events) {
|
|
5234
5240
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
5235
5241
|
const strs = [];
|
|
5236
5242
|
collectStrings(events[i], strs);
|
|
5237
5243
|
for (const s of strs) {
|
|
5238
|
-
const
|
|
5239
|
-
if (
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
if (
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5244
|
+
const md = extractSessionMdPathFromText(s);
|
|
5245
|
+
if (!md) continue;
|
|
5246
|
+
let dirName;
|
|
5247
|
+
let filePath;
|
|
5248
|
+
if (basename(md) === SESSION_MD) {
|
|
5249
|
+
dirName = basename(dirname(md));
|
|
5250
|
+
filePath = md;
|
|
5251
|
+
} else if (isSessionDirName(basename(md))) {
|
|
5252
|
+
dirName = basename(md);
|
|
5253
|
+
filePath = join(md, SESSION_MD);
|
|
5254
|
+
} else continue;
|
|
5255
|
+
if (!isSessionDirName(dirName)) continue;
|
|
5256
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
5257
|
+
return {
|
|
5258
|
+
sessionId: idMatch ? `S${idMatch[1]}` : dirName,
|
|
5259
|
+
dirName,
|
|
5260
|
+
mdPath: filePath
|
|
5261
|
+
};
|
|
5250
5262
|
}
|
|
5251
5263
|
}
|
|
5252
5264
|
return null;
|
|
@@ -5259,6 +5271,35 @@ function collectStrings(v, out) {
|
|
|
5259
5271
|
}
|
|
5260
5272
|
if (v && typeof v === "object") for (const val of Object.values(v)) collectStrings(val, out);
|
|
5261
5273
|
}
|
|
5274
|
+
/** 规范行正则:`SESSION.md path: <路径>`(路径可含空格 → [^\r\n]+ 整行匹配) */
|
|
5275
|
+
const SESSION_MD_PATH_RE = /SESSION\.md path:\s*([^\r\n]+)/;
|
|
5276
|
+
/** 从文本提取 SESSION.md 路径(use 上下文 / 重建锚点规范行通用;无匹配返回 null)。
|
|
5277
|
+
* 同行已知尾注(如系统提示词 Session 块的 persistent-body 注释)剥除——规范行只取路径本体。 */
|
|
5278
|
+
function extractSessionMdPathFromText(text) {
|
|
5279
|
+
const m = text.match(SESSION_MD_PATH_RE);
|
|
5280
|
+
if (!m) return null;
|
|
5281
|
+
let p = m[1].trim();
|
|
5282
|
+
const suffix = p.search(/ \(the trajectory's persistent body/);
|
|
5283
|
+
if (suffix > 0) p = p.slice(0, suffix).trim();
|
|
5284
|
+
return p;
|
|
5285
|
+
}
|
|
5286
|
+
/** 会话目录名形态校验(createSession 恒带日期前缀 `YYYY-MM-DD--`) */
|
|
5287
|
+
function isSessionDirName(dirName) {
|
|
5288
|
+
return /^\d{4}-\d{2}-\d{2}--/.test(dirName) && dirName.length > 11;
|
|
5289
|
+
}
|
|
5290
|
+
/**
|
|
5291
|
+
* 约定回退(v1.24.11):AGENT_SESSIONS 下最新修改的**未完成**会话的 SESSION.md。
|
|
5292
|
+
* readAllSessions 已按「未完成优先 + mtime 降序」排序 → 首个未完成且含 SESSION.md 即最新活动。
|
|
5293
|
+
* 只作最后手段(内存/events/锚点全缺时),保证重建锚点至少指向一个真实存在的轨迹。
|
|
5294
|
+
*/
|
|
5295
|
+
function findLatestActiveSessionMd(root) {
|
|
5296
|
+
for (const s of readAllSessions(sessionsRoot(root))) {
|
|
5297
|
+
if (s.status.completed) continue;
|
|
5298
|
+
const md = join(s.path, SESSION_MD);
|
|
5299
|
+
if (existsSync(md)) return md;
|
|
5300
|
+
}
|
|
5301
|
+
return null;
|
|
5302
|
+
}
|
|
5262
5303
|
/** health 子命令(对齐 osp healthCheck:stale/stalled/ghost/drift 四类检查,文本输出) */
|
|
5263
5304
|
function healthCheck(root) {
|
|
5264
5305
|
const sessions = readAllSessions(sessionsRoot(root));
|
|
@@ -6128,10 +6169,53 @@ function buildRebuildAnchor(root, sessionName, activeMdPath, anchorMessages = DE
|
|
|
6128
6169
|
...anchorMessages.flatMap((text) => [stripAckSuffix(text), ""]),
|
|
6129
6170
|
sessionName !== "" ? `Continue the work of ${sessionName}.` : "Continue the current work.",
|
|
6130
6171
|
...sessionLine ? [sessionLine] : [],
|
|
6131
|
-
`- Persistent trajectory
|
|
6172
|
+
`- Persistent trajectory — SESSION.md path: ${rel}`,
|
|
6173
|
+
` (the trajectory's persistent body — stays in place through rebuilds)`,
|
|
6132
6174
|
`- Read that SESSION.md first (goal/decisions/progress/unresolved), then continue from the last checkpoint.`
|
|
6133
6175
|
].join("\n");
|
|
6134
6176
|
}
|
|
6177
|
+
/** 从会话 surface 首条 user 消息(重建锚点)解析 SESSION.md 路径(events 异常/缺失时的兜底) */
|
|
6178
|
+
function parseAnchorMdPath(session) {
|
|
6179
|
+
try {
|
|
6180
|
+
const nodes = [...session.surface.nodes];
|
|
6181
|
+
if (nodes.length === 0) return null;
|
|
6182
|
+
const events = session.events;
|
|
6183
|
+
if (!Array.isArray(events)) return null;
|
|
6184
|
+
const event = events[nodes[0]];
|
|
6185
|
+
if (!event) return null;
|
|
6186
|
+
const message = deriveEventMessage(event);
|
|
6187
|
+
if (!message) return null;
|
|
6188
|
+
return extractSessionMdPathFromText(message.content?.map((c) => c.type === "text" ? c.text ?? "" : "").join("") ?? "");
|
|
6189
|
+
} catch {
|
|
6190
|
+
return null;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
/** 从 SESSION.md 绝对路径派生会话名(S###;issue 目录回退完整目录名) */
|
|
6194
|
+
function sessionNameFromMdPath(mdPath) {
|
|
6195
|
+
const dirName = basename(dirname(mdPath));
|
|
6196
|
+
const idMatch = dirName.match(/--S(\d{3,})--/);
|
|
6197
|
+
return idMatch ? `S${idMatch[1]}` : dirName;
|
|
6198
|
+
}
|
|
6199
|
+
/**
|
|
6200
|
+
* 稳固的 SESSION.md 定位(多层候选 + 存在性校验,**绝不输出虚假路径**):
|
|
6201
|
+
* ① 内存活跃会话(本会话显式 use 过)→ ② events 恢复(use 标记 + 重建锚点规范行,进程重启后)
|
|
6202
|
+
* → ③ surface 首条锚点(events 异常时兜底)→ ④ AGENT_SESSIONS 约定回退(最新未完成活动目录)。
|
|
6203
|
+
* 每候选 resolve 后 existsSync 校验(相对路径按 root 解析);全部失败返回 null → 调用方报错引导。
|
|
6204
|
+
*/
|
|
6205
|
+
function resolveSessionMdPath(root, scope, session) {
|
|
6206
|
+
const candidates = [];
|
|
6207
|
+
candidates.push(getActiveSessionInfo(scope)?.mdPath ?? null);
|
|
6208
|
+
const events = session.events;
|
|
6209
|
+
if (Array.isArray(events)) candidates.push(parseSessionContextFromEvents(events)?.mdPath ?? null);
|
|
6210
|
+
candidates.push(parseAnchorMdPath(session));
|
|
6211
|
+
candidates.push(findLatestActiveSessionMd(root));
|
|
6212
|
+
for (const c of candidates) {
|
|
6213
|
+
if (!c) continue;
|
|
6214
|
+
const abs = c.startsWith(root) ? c : resolve(root, c);
|
|
6215
|
+
if (existsSync(abs)) return abs;
|
|
6216
|
+
}
|
|
6217
|
+
return null;
|
|
6218
|
+
}
|
|
6135
6219
|
const pendingRebuilds = /* @__PURE__ */ new Map();
|
|
6136
6220
|
/** 陈旧队列存活时长(毫秒):超过则丢弃(turn 异常结束/agent 崩溃时防残留误清空) */
|
|
6137
6221
|
const PENDING_TTL_MS = 6e5;
|
|
@@ -6145,19 +6229,11 @@ const PENDING_TTL_MS = 6e5;
|
|
|
6145
6229
|
async function queueRebuild(ctx, opts) {
|
|
6146
6230
|
if (!readSimpleSettings().rebuildEnabled) throw new Error("session_rebuild is disabled (rebuild.enabled=false — enable it in the dsh settings panel)");
|
|
6147
6231
|
const { root, note, dshSessionId } = opts;
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
const active = getActiveSessionInfo(scope);
|
|
6154
|
-
if (active?.mdPath) {
|
|
6155
|
-
sessionMdPath = active.mdPath;
|
|
6156
|
-
sessionName = active.sessionId ?? "";
|
|
6157
|
-
}
|
|
6158
|
-
} catch {}
|
|
6159
|
-
const mdPath = sessionMdPath ?? join(root, "AGENT_SESSIONS", "SESSION.md");
|
|
6160
|
-
const anchor = buildRebuildAnchor(root, sessionName, mdPath);
|
|
6232
|
+
const session = ctx.sessions?.get?.(dshSessionId);
|
|
6233
|
+
if (!session) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
|
|
6234
|
+
const mdPath = resolveSessionMdPath(root, dshSessionId, session);
|
|
6235
|
+
if (!mdPath) throw new Error("Unable to determine the active SESSION.md — no session context found in this conversation. Run \"session use <S###>\" first to activate the trajectory to resume, then retry session_rebuild.");
|
|
6236
|
+
const anchor = buildRebuildAnchor(root, getActiveSessionInfo(dshSessionId)?.sessionId ?? sessionNameFromMdPath(mdPath), mdPath);
|
|
6161
6237
|
pendingRebuilds.set(dshSessionId, {
|
|
6162
6238
|
anchor,
|
|
6163
6239
|
queuedAt: Date.now()
|
|
@@ -6165,7 +6241,7 @@ async function queueRebuild(ctx, opts) {
|
|
|
6165
6241
|
return {
|
|
6166
6242
|
queued: true,
|
|
6167
6243
|
anchor,
|
|
6168
|
-
sessionMdPath
|
|
6244
|
+
sessionMdPath: mdPath
|
|
6169
6245
|
};
|
|
6170
6246
|
}
|
|
6171
6247
|
/**
|
|
@@ -7495,8 +7571,14 @@ function registerContext(ctx, opts = {}) {
|
|
|
7495
7571
|
if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
|
|
7496
7572
|
const info = parseSessionContextFromEvents(agent.session.events ?? []);
|
|
7497
7573
|
if (info) {
|
|
7498
|
-
|
|
7499
|
-
|
|
7574
|
+
const abs = info.mdPath.startsWith(root) ? info.mdPath : resolve(root, info.mdPath);
|
|
7575
|
+
if (existsSync(abs)) {
|
|
7576
|
+
setActiveSessionInfo(scope, {
|
|
7577
|
+
...info,
|
|
7578
|
+
mdPath: abs
|
|
7579
|
+
});
|
|
7580
|
+
console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${info.dirName}`);
|
|
7581
|
+
}
|
|
7500
7582
|
}
|
|
7501
7583
|
}
|
|
7502
7584
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.11",
|
|
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": {
|