@shgroup/dsh-serenity-hooks 1.16.13 → 1.16.14

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.16.13",
3
+ "version": "1.16.14",
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
@@ -806,109 +806,93 @@ function showSession(root, key) {
806
806
  };
807
807
  }
808
808
  /**
809
- * 活动会话标记:按 DSH 会话(agent.session.id)隔离,不再使用 CCC 级全局单文件。
810
- * 每个 dsh 会话一个标记文件(.dsh/active-sessions/<scope>),系统提示词 Session
811
- * 只注入当前 dsh 会话 use 的活跃会话 —— 多开 conversation / subagent / loop 牛马互不泄露。
812
- *
813
- * 旧版全局标记 `.dsh/active-session`(v1.16.1 及以前):use 时删除(迁移清理),
814
- * 读取不再回退(隔离优先;升级后重新 use 一次即可)。
809
+ * 活动会话跟踪(S134 v1.16.14 内存化,对齐 osp active-state):
810
+ * **不落盘**——活跃会话状态在内存 Map(key = scope = dsh 会话 id),避免落盘标记
811
+ * 文件累积与跨会话串台(落盘版 `.dsh/active-sessions/<scope>` 已被此方案取代)。
812
+ * 进程重启恢复:从**当前会话历史(events)**解析 `[SESSION CONTEXT]` 标记(use 时注入),
813
+ * 只扫自己会话——无全局扫描、无跨会话污染。
815
814
  */
816
- const ACTIVE_SESSIONS_DIR = join(".dsh", "active-sessions");
817
- /** 旧版全局标记路径(v1.16.1 及以前 use 写入;新版本仅清理不再读取) */
818
- const LEGACY_ACTIVE_SESSION_MARKER = join(".dsh", "active-session");
819
- /** 默认 scope(agent.session.id 缺失时) */
820
815
  const DEFAULT_SESSION_SCOPE = "default";
821
- /** scope 用作文件名:仅保留安全字符(agent.session.id 可能含 / 等;`.` 排除以杜绝 `..` 路径段穿越) */
822
- function sanitizeScope(scope) {
823
- return scope.replace(/[^A-Za-z0-9_-]/g, "_") || "default";
824
- }
825
- /** scope 标记文件绝对路径 */
826
- function activeSessionMarker(root, scope) {
827
- return resolve(root, ACTIVE_SESSIONS_DIR, sanitizeScope(scope));
816
+ /** [SESSION CONTEXT] 恢复标记(use 时注入 events 历史;进程重启后从 events 解析) */
817
+ const SESSION_CONTEXT_MARKER = "[SESSION CONTEXT] Activated:";
818
+ /** 内存活跃会话:scope(dsh 会话 id)→ 会话信息(不落盘;并行多会话各自 key 隔离) */
819
+ const activeStore = /* @__PURE__ */ new Map();
820
+ /** 全局最近活跃(对齐 osp lastActive;供无 scope 上下文使用) */
821
+ let lastActive = null;
822
+ function getActiveSessionInfo(scope) {
823
+ return activeStore.get(scope) ?? null;
824
+ }
825
+ function setActiveSessionInfo(scope, info) {
826
+ activeStore.set(scope, info);
827
+ lastActive = info;
828
+ }
829
+ function clearActiveSessionInfo(scope) {
830
+ activeStore.delete(scope);
831
+ if (lastActive && ![...activeStore.values()].some((v) => v === lastActive)) lastActive = null;
828
832
  }
829
- /** 激活会话:写 <scope> 标记(内容 = 相对 CCC 根的 SESSION.md 路径);顺带清理旧全局标记 */
833
+ /**
834
+ * 激活会话:写内存 Map(scope)+ 返回含 `[SESSION CONTEXT]` 标记的 context
835
+ * (标记随工具结果进 events 历史——进程重启后从当前会话 events 解析恢复,对齐 osp)。
836
+ */
830
837
  function useSession(root, key, scope = DEFAULT_SESSION_SCOPE) {
831
838
  const target = findSession(root, key);
832
839
  if (!target) throw new Error(`未找到会话: ${key}`);
833
840
  const md = join(sessionsRoot(root), target.dir, "SESSION.md");
834
841
  if (!existsSync(md)) throw new Error(`会话 ${target.dir} 缺少 SESSION.md`);
835
- const marker = activeSessionMarker(root, scope);
836
- mkdirSync(resolve(root, ".dsh", "active-sessions"), { recursive: true });
837
- const relMd = relative(root, md);
838
- writeFileSync(marker, relMd, "utf-8");
839
- const legacy = resolve(root, LEGACY_ACTIVE_SESSION_MARKER);
840
- if (existsSync(legacy)) rmSync(legacy, { force: true });
841
- return {
842
- dir: target.dir,
842
+ const dirName = target.dir;
843
+ const idMatch = dirName.match(/S(\d{3,})/);
844
+ setActiveSessionInfo(scope, {
845
+ sessionId: idMatch ? `S${idMatch[1]}` : dirName,
846
+ dirName,
843
847
  mdPath: md
848
+ });
849
+ return {
850
+ dir: dirName,
851
+ mdPath: md,
852
+ context: `${SESSION_CONTEXT_MARKER} ${dirName}\nSESSION.md path: ${md}`
844
853
  };
845
854
  }
846
- /** 关闭活动会话:删除 <scope> 标记 + 旧全局标记 */
855
+ /** 关闭活动会话:删除 scope 的内存条目(+ lastActive 修正)——不落盘,无文件残留 */
847
856
  function closeSession(root, scope = DEFAULT_SESSION_SCOPE) {
848
- const marker = activeSessionMarker(root, scope);
849
- if (existsSync(marker)) rmSync(marker, { force: true });
850
- const legacy = resolve(root, LEGACY_ACTIVE_SESSION_MARKER);
851
- if (existsSync(legacy)) rmSync(legacy, { force: true });
857
+ clearActiveSessionInfo(scope);
852
858
  return { dir: "active-session cleared" };
853
859
  }
854
- /** 读取指定 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
860
+ /** 读取指定 scope 的活跃会话 SESSION.md 绝对路径;无激活返回 null(读内存,不落盘) */
855
861
  function readActiveSessionMd(root, scope = DEFAULT_SESSION_SCOPE) {
856
- const marker = activeSessionMarker(root, scope);
857
- if (!existsSync(marker)) return null;
858
- const rel = readFileSync(marker, "utf-8").trim();
859
- if (!rel) return null;
860
- const abs = resolve(root, rel);
861
- if (!abs.startsWith(resolve(root))) return null;
862
- return abs;
863
- }
864
- /** 列出全部 scope 的活动标记(含 scope / mdRel / mtime);目录不存在返回空 */
865
- function listActiveMarkers(root) {
866
- const dir = resolve(root, ACTIVE_SESSIONS_DIR);
867
- if (!existsSync(dir)) return [];
868
- const out = [];
869
- for (const entry of readdirSync(dir)) {
870
- const full = join(dir, entry);
871
- if (!statSync(full).isFile()) continue;
872
- try {
873
- const rel = readFileSync(full, "utf-8").trim();
874
- if (!rel) continue;
875
- out.push({
876
- scope: entry,
877
- mdRel: rel,
878
- mtime: statSync(full).mtimeMs
879
- });
880
- } catch {}
881
- }
882
- return out;
862
+ return getActiveSessionInfo(scope)?.mdPath ?? null;
883
863
  }
884
864
  /**
885
- * 重启恢复:当前 scope 无标记时,把"最近激活"(mtime 最新)且根内有效的标记
886
- * 复制为当前 scope 标记(激活语义延续:use = 激活,重启自动恢复 = 重新激活)。
887
- * 返回恢复的会话信息;已有标记 / 无候选 / 全部越界 → null。
888
- * 调用方负责根会话判定(subagent / loop 牛马不恢复——见 context.ts shouldAutoRestore)。
865
+ * 从会话历史(events)解析最后一条 [SESSION CONTEXT] 标记(进程重启恢复;
866
+ * 只扫**当前会话**自己的历史——无跨会话串台)。
889
867
  */
890
- function restoreActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
891
- const marker = activeSessionMarker(root, scope);
892
- if (existsSync(marker)) return null;
893
- const scopeName = sanitizeScope(scope);
894
- const candidates = listActiveMarkers(root).filter((m) => m.scope !== scopeName);
895
- if (candidates.length === 0) return null;
896
- const rootAbs = resolve(root);
897
- const valid = candidates.filter((m) => pathInside(rootAbs, resolve(rootAbs, m.mdRel)));
898
- if (valid.length === 0) return null;
899
- const best = valid.sort((a, b) => b.mtime - a.mtime)[0];
900
- mkdirSync(resolve(rootAbs, ACTIVE_SESSIONS_DIR), { recursive: true });
901
- writeFileSync(marker, best.mdRel, "utf-8");
902
- const mdPath = resolve(rootAbs, best.mdRel);
903
- const dirName = basename(dirname(mdPath));
904
- const idMatch = dirName.match(/S(\d{3,})/);
905
- return {
906
- dir: dirName,
907
- id: idMatch ? `S${idMatch[1]}` : null,
908
- mdPath,
909
- restored: true,
910
- from: best.scope
911
- };
868
+ function parseSessionContextFromEvents(events) {
869
+ for (let i = events.length - 1; i >= 0; i--) {
870
+ const strs = [];
871
+ collectStrings(events[i], strs);
872
+ for (const s of strs) {
873
+ const idx = s.indexOf(SESSION_CONTEXT_MARKER);
874
+ if (idx < 0) continue;
875
+ const dirName = s.slice(idx + 28).trim().split("\n")[0]?.trim() ?? "";
876
+ const mdMatch = s.match(/SESSION\.md path:\s*(\S+)/);
877
+ if (/^\d{4}-\d{2}-\d{2}--/.test(dirName) && mdMatch) {
878
+ const idMatch = dirName.match(/S(\d{3,})/);
879
+ return {
880
+ sessionId: idMatch ? `S${idMatch[1]}` : dirName,
881
+ dirName,
882
+ mdPath: mdMatch[1]
883
+ };
884
+ }
885
+ }
886
+ }
887
+ return null;
888
+ }
889
+ /** 递归收集对象/数组/字符串中的全部字符串(保留原文,无 JSON 转义) */
890
+ function collectStrings(v, out) {
891
+ if (typeof v === "string") {
892
+ out.push(v);
893
+ return;
894
+ }
895
+ if (v && typeof v === "object") for (const val of Object.values(v)) collectStrings(val, out);
912
896
  }
913
897
  function archiveSession(root, key) {
914
898
  const target = findSession(root, key);
@@ -3113,10 +3097,14 @@ function registerContext(ctx, opts = {}) {
3113
3097
  if (!root) return;
3114
3098
  const key = agentKey(agent);
3115
3099
  registerEntrySkillSection(agent, root);
3116
- if (shouldRestoreActive(agent)) try {
3100
+ const scope = agentScope(agent);
3101
+ if (shouldRestoreActive(agent) && getActiveSessionInfo(scope) === null) try {
3117
3102
  if (loadSerenityConfig(root, configPaths).hooks?.autoRestoreSession ?? true) {
3118
- const restored = restoreActiveSession(root, agentScope(agent));
3119
- if (restored) console.log(`[serenity-hooks] ↻ 自动恢复激活会话: ${restored.dir}(from scope ${restored.from})`);
3103
+ const info = parseSessionContextFromEvents(agent.session.events ?? []);
3104
+ if (info) {
3105
+ setActiveSessionInfo(scope, info);
3106
+ console.log(`[serenity-hooks] ↻ 从历史恢复激活会话: ${info.dirName}`);
3107
+ }
3120
3108
  }
3121
3109
  } catch {}
3122
3110
  if (injected.has(key)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.16.13",
3
+ "version": "1.16.14",
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": {