@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.24.10",
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)解析最后一条 [SESSION CONTEXT] 标记(进程重启恢复;
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 idx = s.indexOf(SESSION_CONTEXT_MARKER);
5239
- if (idx < 0) continue;
5240
- const dirName = s.slice(idx + 28).trim().split("\n")[0]?.trim() ?? "";
5241
- const mdMatch = s.match(/SESSION\.md path:\s*([^\r\n]+)/);
5242
- if (/^\d{4}-\d{2}-\d{2}--/.test(dirName) && mdMatch) {
5243
- const idMatch = dirName.match(/--S(\d{3,})--/);
5244
- return {
5245
- sessionId: idMatch ? `S${idMatch[1]}` : basename(dirName),
5246
- dirName,
5247
- mdPath: mdMatch[1].trim()
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 (SESSION.md, unmoved): ${rel}`,
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
- if (!ctx.sessions?.get?.(dshSessionId)) throw new Error(`Unable to locate dsh session ${dshSessionId} (session may be closed)`);
6149
- const scope = dshSessionId;
6150
- let sessionMdPath = null;
6151
- let sessionName = "";
6152
- try {
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
- setActiveSessionInfo(scope, info);
7499
- console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${info.dirName}`);
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.10",
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": {