@shgroup/dsh-serenity-hooks 1.16.0 → 1.16.2

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/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @shgroup/dsh-serenity-hooks
2
+
3
+ 宁静号 ACC(Abstract Cognitive Container)harness — **DeepSeek Harness 原生 Cordis 插件**。
4
+
5
+ 为 DSH 会话提供认知容器基础设施:真实 DSH 工具(9 个)+ 拦截缝机械约束(safe-mode / 路径守卫 / 会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块,对齐 opencode-serenity-plugin)+ WebUI 状态徽章。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ dsh plugin --profile web add @shgroup/dsh-serenity-hooks
11
+ ```
12
+
13
+ 安装后重启 dsh web。在带 `.serenity` 标记的 CCC 目录中的会话自动获得全部能力。
14
+
15
+ ## 工具
16
+
17
+ `cc_fs` / `session` / `acc_kit` / `cc_git` / `acc_msm` / `eap` / `neat` / `cce` / `loop`
18
+
19
+ ## 系统提示词
20
+
21
+ 五块注入(`systemPrompt.section`,order -50):`=== Serenity ACC ===` / `=== Serenity CCE ===` / `=== Serenity Constraints ===` / 顶层入口 skill 全文(按 `.serenity` 记号发现)/ `=== Serenity Session ===` —— 平台无关文本与 [opencode-serenity-plugin](https://github.com/tellmewhattodo/opencode-serenity-plugin) 逐字节对齐;同一 CCC 可任意换用 osp / dsh 运行时。
22
+
23
+ ## 配置
24
+
25
+ `.dsh/serenity.json`(回退 `.opencode/serenity.json`):
26
+
27
+ ```jsonc
28
+ {
29
+ "loop": { "defaultModel": "provider/model" },
30
+ "sessionKeeper": { "threshold": 100 },
31
+ "safeMode": { "blacklist": [".secrets/", "regex:\\.env$"] }
32
+ }
33
+ ```
34
+
35
+ ## 文档
36
+
37
+ 完整文档见仓库 [README](https://github.com/tellmewhattodo/dsh-serenity-plugin)。
38
+
39
+ ## 许可
40
+
41
+ MIT
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
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/client.js ADDED
@@ -0,0 +1,201 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@shgroup/dsh-serenity-hooks",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
+ let react = require("react");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region \0sp-css:/home/yh/home/home-serenity/AI_LAB/dsh-serenity-plugin/hooks/dsh-serenity-hooks/src/client/SafeModePanel
11
+ const css = "/* SafeModePanel — 会话头部 Serenity 状态徽章(点击展开详情卡)。\n * 遵循 web-styling.md:颜色只用 --dsw-alias-* 语义 token(明暗自适应);\n * 尺寸与官方 header 标签一致;前缀 sp- 防冲突。 */\n\n.sp-root {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 4px 0 4px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-module-platform);\n font-size: 12px;\n line-height: 24px;\n white-space: nowrap;\n}\n\n.sp-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 22px;\n padding: 0 6px;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: inherit;\n font-size: 12px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-badge:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sp-badge:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n}\n\n.sp-dotOn {\n background: var(--dsw-alias-state-success-primary);\n}\n\n.sp-dotOff {\n background: var(--dsw-alias-label-dimmed);\n}\n\n.sp-brand {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.sp-toggle {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n height: 18px;\n padding: 0 8px;\n border: none;\n border-radius: 5px;\n font-size: 12px;\n font-weight: 600;\n line-height: 18px;\n cursor: pointer;\n transition: background var(--ds-transition-duration-fast, 0.1s) var(--ds-ease-in-out, ease);\n}\n\n.sp-toggle:focus-visible {\n outline: 2px solid var(--dsw-alias-button-ghost-active-border);\n outline-offset: 1px;\n}\n\n.sp-toggle:disabled {\n opacity: 0.55;\n cursor: default;\n}\n\n.sp-toggleOn {\n background: var(--dsw-alias-state-error-primary);\n color: var(--dsw-alias-label-primary-foreground);\n}\n\n.sp-toggleOn:hover:not(:disabled) {\n background: var(--dsw-alias-state-error-secondary);\n}\n\n.sp-toggleOff {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.sp-toggleOff:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n}\n\n.sp-toggleIcon {\n flex: none;\n}\n\n.sp-toggleLg {\n height: 26px;\n padding: 0 14px;\n font-size: 13px;\n line-height: 26px;\n border-radius: 6px;\n}\n\n/* 详情卡:相对徽章绝对定位(右对齐),卡片 token */\n.sp-pop {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n z-index: 20;\n width: 300px;\n box-sizing: border-box;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-specific-tip);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.sp-popRow {\n display: flex;\n align-items: baseline;\n gap: 10px;\n min-width: 0;\n}\n\n.sp-popLabel {\n flex: none;\n width: 34px;\n font-size: 12px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-popValue {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n.sp-popActions {\n display: flex;\n justify-content: flex-end;\n padding-top: 4px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sp-toggle,\n .sp-badge {\n transition: none;\n }\n}\n";
12
+ if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
13
+ const tag = document.createElement("style");
14
+ tag.setAttribute("data-sp-css", "1");
15
+ tag.textContent = css;
16
+ document.head.appendChild(tag);
17
+ }
18
+ //#endregion
19
+ //#region src/client/SafeModePanel.tsx
20
+ const API = "/serenity/status";
21
+ /** 拼接类名(等价 clsx 最小子集,避免额外依赖) */
22
+ function cx(...parts) {
23
+ return parts.filter(Boolean).join(" ");
24
+ }
25
+ /** 会话头部:可点击徽章 + safe-mode 开关;点击徽章展开状态详情卡 */
26
+ function SafeModePanel(props) {
27
+ const [status, setStatus] = (0, react.useState)(null);
28
+ const [busy, setBusy] = (0, react.useState)(false);
29
+ const [open, setOpen] = (0, react.useState)(false);
30
+ const rootRef = (0, react.useRef)(null);
31
+ const sessionId = props.sessionId;
32
+ const refresh = (0, react.useCallback)(async () => {
33
+ const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
34
+ try {
35
+ const r = await fetch(`${API}${qs}`, { headers: { accept: "application/json" } });
36
+ setStatus(await r.json());
37
+ } catch {
38
+ setStatus(null);
39
+ }
40
+ }, [sessionId]);
41
+ (0, react.useEffect)(() => {
42
+ refresh();
43
+ }, [refresh]);
44
+ (0, react.useEffect)(() => {
45
+ if (!open) return;
46
+ const onDown = (e) => {
47
+ if (rootRef.current !== null && !rootRef.current.contains(e.target)) setOpen(false);
48
+ };
49
+ document.addEventListener("pointerdown", onDown);
50
+ return () => document.removeEventListener("pointerdown", onDown);
51
+ }, [open]);
52
+ const toggle = async (on) => {
53
+ setBusy(true);
54
+ try {
55
+ await fetch(API, {
56
+ method: "POST",
57
+ headers: {
58
+ "content-type": "application/json",
59
+ "x-serenity-ui": "1"
60
+ },
61
+ body: JSON.stringify({
62
+ sessionId,
63
+ on
64
+ })
65
+ });
66
+ await refresh();
67
+ } finally {
68
+ setBusy(false);
69
+ }
70
+ };
71
+ if (!status) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
72
+ className: cx("sp-root"),
73
+ children: "⏳ Serenity…"
74
+ });
75
+ const inCcc = status.root !== null;
76
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
77
+ ref: rootRef,
78
+ className: cx("sp-root"),
79
+ title: status.root ?? void 0,
80
+ children: [
81
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
82
+ type: "button",
83
+ className: cx("sp-badge"),
84
+ "aria-expanded": open,
85
+ onClick: () => setOpen((v) => !v),
86
+ title: open ? "收起详情" : "查看 Serenity 状态",
87
+ children: [
88
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cx("sp-dot", inCcc ? "sp-dotOn" : "sp-dotOff") }),
89
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
90
+ className: cx("sp-brand"),
91
+ children: ["Serenity v", status.accVersion]
92
+ }),
93
+ open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, { size: 12 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 12 })
94
+ ]
95
+ }),
96
+ inCcc && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
97
+ type: "button",
98
+ className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff"),
99
+ disabled: busy,
100
+ onClick: () => void toggle(!status.safeModeOn),
101
+ title: status.safeModeOn ? "关闭 safe-mode(解除 bash 限制)" : "开启 safe-mode(隐藏写工具)",
102
+ children: [
103
+ status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
104
+ size: 12,
105
+ className: cx("sp-toggleIcon")
106
+ }),
107
+ "safe-mode ",
108
+ status.safeModeOn ? "ON" : "OFF"
109
+ ]
110
+ }),
111
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
112
+ className: cx("sp-pop"),
113
+ role: "dialog",
114
+ "aria-label": "Serenity 状态详情",
115
+ children: inCcc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
117
+ className: cx("sp-popRow"),
118
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
119
+ className: cx("sp-popLabel"),
120
+ children: "CCC"
121
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
122
+ className: cx("sp-popValue"),
123
+ title: status.root ?? void 0,
124
+ children: status.root
125
+ })]
126
+ }),
127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
128
+ className: cx("sp-popRow"),
129
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
130
+ className: cx("sp-popLabel"),
131
+ children: "loop"
132
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
133
+ className: cx("sp-popValue"),
134
+ title: status.loopModel ? `loop.defaultModel: ${status.loopModel}` : void 0,
135
+ children: status.loopModel ?? "未配置"
136
+ })]
137
+ }),
138
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
139
+ className: cx("sp-popRow"),
140
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
141
+ className: cx("sp-popLabel"),
142
+ children: "守卫"
143
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
144
+ className: cx("sp-popValue"),
145
+ children: [
146
+ "blacklist ",
147
+ status.blacklist.length,
148
+ " 条",
149
+ status.threshold !== null ? ` · keeper 阈值 ${status.threshold}` : ""
150
+ ]
151
+ })]
152
+ }),
153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
154
+ className: cx("sp-popActions"),
155
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
156
+ type: "button",
157
+ className: cx("sp-toggle", status.safeModeOn ? "sp-toggleOn" : "sp-toggleOff", "sp-toggleLg"),
158
+ disabled: busy,
159
+ onClick: () => void toggle(!status.safeModeOn),
160
+ children: [
161
+ status.safeModeOn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
162
+ size: 14,
163
+ className: cx("sp-toggleIcon")
164
+ }),
165
+ "safe-mode ",
166
+ status.safeModeOn ? "ON" : "OFF"
167
+ ]
168
+ })
169
+ })
170
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
171
+ className: cx("sp-popRow"),
172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
173
+ className: cx("sp-popLabel"),
174
+ children: "状态"
175
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
176
+ className: cx("sp-popValue"),
177
+ children: "ACC 未激活(工作区不在 CCC 内)"
178
+ })]
179
+ })
180
+ })
181
+ ]
182
+ });
183
+ }
184
+ //#endregion
185
+ //#region src/client/index.ts
186
+ const inject = ["slots", "conversation"];
187
+ function apply(ctx) {
188
+ ctx.inject(["slots", "conversation"], (scope) => {
189
+ scope.effect(() => scope.slots.register({
190
+ name: "conversation.session.header.actions",
191
+ id: "serenity-safe",
192
+ order: 10
193
+ }, SafeModePanel), "serenity: header status badge");
194
+ });
195
+ }
196
+ //#endregion
197
+ exports.apply = apply;
198
+ exports.inject = inject;
199
+ return module.exports;
200
+ }
201
+ });
package/lib/index.js CHANGED
@@ -769,29 +769,62 @@ function showSession(root, key) {
769
769
  content: readFileSync(md, "utf-8")
770
770
  };
771
771
  }
772
- /** 活动会话标记文件(sessionBlock 系统提示词注入读取) */
773
- const ACTIVE_SESSION_MARKER = join(".dsh", "active-session");
774
- /** 激活会话:写 .dsh/active-session 标记(内容 = 相对 CCC 根的 SESSION.md 路径) */
775
- function useSession(root, key) {
772
+ /**
773
+ * 活动会话标记:按 DSH 会话(agent.session.id)隔离,不再使用 CCC 级全局单文件。
774
+ * 每个 dsh 会话一个标记文件(.dsh/active-sessions/<scope>),系统提示词 Session
775
+ * 只注入当前 dsh 会话 use 的活跃会话 —— 多开 conversation / subagent / loop 牛马互不泄露。
776
+ *
777
+ * 旧版全局标记 `.dsh/active-session`(v1.16.1 及以前):use 时删除(迁移清理),
778
+ * 读取不再回退(隔离优先;升级后重新 use 一次即可)。
779
+ */
780
+ const ACTIVE_SESSIONS_DIR = join(".dsh", "active-sessions");
781
+ /** 旧版全局标记路径(v1.16.1 及以前 use 写入;新版本仅清理不再读取) */
782
+ const LEGACY_ACTIVE_SESSION_MARKER = join(".dsh", "active-session");
783
+ /** 默认 scope(agent.session.id 缺失时) */
784
+ const DEFAULT_SESSION_SCOPE = "default";
785
+ /** scope 用作文件名:仅保留安全字符(agent.session.id 可能含 / 等;`.` 排除以杜绝 `..` 路径段穿越) */
786
+ function sanitizeScope(scope) {
787
+ return scope.replace(/[^A-Za-z0-9_-]/g, "_") || "default";
788
+ }
789
+ /** scope 标记文件绝对路径 */
790
+ function activeSessionMarker(root, scope) {
791
+ return resolve(root, ACTIVE_SESSIONS_DIR, sanitizeScope(scope));
792
+ }
793
+ /** 激活会话:写 <scope> 标记(内容 = 相对 CCC 根的 SESSION.md 路径);顺带清理旧全局标记 */
794
+ function useSession(root, key, scope = DEFAULT_SESSION_SCOPE) {
776
795
  const target = findSession(root, key);
777
796
  if (!target) throw new Error(`未找到会话: ${key}`);
778
797
  const md = join(sessionsRoot(root), target.dir, "SESSION.md");
779
798
  if (!existsSync(md)) throw new Error(`会话 ${target.dir} 缺少 SESSION.md`);
780
- const marker = resolve(root, ACTIVE_SESSION_MARKER);
781
- mkdirSync(resolve(root, ".dsh"), { recursive: true });
799
+ const marker = activeSessionMarker(root, scope);
800
+ mkdirSync(resolve(root, ".dsh", "active-sessions"), { recursive: true });
782
801
  const relMd = relative(root, md);
783
802
  writeFileSync(marker, relMd, "utf-8");
803
+ const legacy = resolve(root, LEGACY_ACTIVE_SESSION_MARKER);
804
+ if (existsSync(legacy)) rmSync(legacy, { force: true });
784
805
  return {
785
806
  dir: target.dir,
786
807
  mdPath: md
787
808
  };
788
809
  }
789
- /** 关闭活动会话:删除 .dsh/active-session 标记 */
790
- function closeSession(root) {
791
- const marker = resolve(root, ACTIVE_SESSION_MARKER);
810
+ /** 关闭活动会话:删除 <scope> 标记 + 旧全局标记 */
811
+ function closeSession(root, scope = DEFAULT_SESSION_SCOPE) {
812
+ const marker = activeSessionMarker(root, scope);
792
813
  if (existsSync(marker)) rmSync(marker, { force: true });
814
+ const legacy = resolve(root, LEGACY_ACTIVE_SESSION_MARKER);
815
+ if (existsSync(legacy)) rmSync(legacy, { force: true });
793
816
  return { dir: "active-session cleared" };
794
817
  }
818
+ /** 读取指定 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
819
+ function readActiveSessionMd(root, scope = DEFAULT_SESSION_SCOPE) {
820
+ const marker = activeSessionMarker(root, scope);
821
+ if (!existsSync(marker)) return null;
822
+ const rel = readFileSync(marker, "utf-8").trim();
823
+ if (!rel) return null;
824
+ const abs = resolve(root, rel);
825
+ if (!abs.startsWith(resolve(root))) return null;
826
+ return abs;
827
+ }
795
828
  function archiveSession(root, key) {
796
829
  const target = findSession(root, key);
797
830
  if (!target) throw new Error(`未找到会话: ${key}`);
@@ -872,6 +905,10 @@ function appendHeartbeat(sessionMd) {
872
905
  function agentCwd$5(exec) {
873
906
  return exec.agent?.session?.header?.cwd ?? process.cwd();
874
907
  }
908
+ /** 当前 dsh 会话 id(use/close 按会话隔离的 scope) */
909
+ function agentScope$2(exec) {
910
+ return exec.agent?.session?.id ?? "default";
911
+ }
875
912
  function renderText$7(value) {
876
913
  return [{
877
914
  type: "text",
@@ -880,7 +917,7 @@ function renderText$7(value) {
880
917
  }
881
918
  const sessionTool = defineTool({
882
919
  name: "session",
883
- description: "工作会话全周期管理(AGENT_SESSIONS/,home-session 约定)。list/show/create/use/close/health/qa/archive/summary。多步骤工作必须先 create 会话,use 激活当前会话(写 .dsh/active-session 标记 → 系统提示词 Session 块生效)。",
920
+ description: "工作会话全周期管理(AGENT_SESSIONS/,home-session 约定)。list/show/create/use/close/health/qa/archive/summary。多步骤工作必须先 create 会话,use 激活当前 dsh 会话的活跃会话(写 .dsh/active-sessions/<scope> 标记 → 系统提示词 Session 块生效,按 dsh 会话隔离不泄露)。",
884
921
  parameters: {
885
922
  action: {
886
923
  type: "string",
@@ -934,8 +971,8 @@ const sessionTool = defineTool({
934
971
  return showSession(root, args.key);
935
972
  case "use":
936
973
  if (!args.key) throw new Error("use 需要 key");
937
- return useSession(root, args.key);
938
- case "close": return closeSession(root);
974
+ return useSession(root, args.key, agentScope$2(exec));
975
+ case "close": return closeSession(root, agentScope$2(exec));
939
976
  case "archive":
940
977
  if (!args.key) throw new Error("archive 需要 key");
941
978
  return archiveSession(root, args.key);
@@ -1862,29 +1899,17 @@ function registerGuards(ctx, opts = {}) {
1862
1899
  }
1863
1900
  //#endregion
1864
1901
  //#region src/seams/loop.ts
1865
- /**
1866
- * loop.ts 拦截缝:回合生命周期(agent/turn-stopping 机械落盘)
1867
- *
1868
- * 每个自然停止边界:若 agent 工作目录在 CCC 内且存在 `.dsh/active-session`
1869
- * 标记(内容为相对 CCC 根的 SESSION.md 路径),自动追加心跳行——
1870
- * 会话进度不再依赖模型自觉。
1871
- */
1872
- /** 解析活动会话 SESSION.md 绝对路径;无标记/越界返回 null */
1873
- function resolveActiveSession(root) {
1874
- const marker = resolve(root, ".dsh", "active-session");
1875
- if (!existsSync(marker)) return null;
1876
- const rel = readFileSync(marker, "utf-8").trim();
1877
- if (!rel) return null;
1878
- const abs = resolve(root, rel);
1879
- if (!abs.startsWith(resolve(root))) return null;
1880
- return abs;
1902
+ /** 解析当前 dsh 会话 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
1903
+ function resolveActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
1904
+ return readActiveSessionMd(root, scope);
1881
1905
  }
1882
- /** 注册 agent/turn-stopping:CCC 内活动会话自动心跳落盘 */
1906
+ /** 注册 agent/turn-stopping:CCC 内活动会话自动心跳落盘(按 dsh 会话隔离) */
1883
1907
  function registerTurnFlush(ctx) {
1884
1908
  ctx.on("agent/turn-stopping", async (payload) => {
1885
- const root = findSerenityRoot(payload.agent.session?.header?.cwd ?? process.cwd());
1909
+ const agent = payload.agent;
1910
+ const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
1886
1911
  if (!root) return;
1887
- const sessionMd = resolveActiveSession(root);
1912
+ const sessionMd = resolveActiveSession(root, agent.session?.id ?? "default");
1888
1913
  if (sessionMd) appendHeartbeat(sessionMd);
1889
1914
  });
1890
1915
  }
@@ -2176,15 +2201,11 @@ function entrySkillSectionText(root) {
2176
2201
  if (skills.length === 0) return "";
2177
2202
  return skills.map((s) => sanitizeSkillContent(s.content)).filter((c) => c.length > 0).join("\n\n");
2178
2203
  }
2179
- /** 活跃会话解析:.dsh/active-session 标记(内容 = 相对 CCC 根的 SESSION.md 路径) */
2180
- function resolveActiveSessionInfo(root) {
2204
+ /** 活跃会话解析:按 dsh 会话 scope 读标记(隔离;不回退旧全局标记) */
2205
+ function resolveActiveSessionInfo(root, scope) {
2181
2206
  try {
2182
- const marker = resolve(root, ".dsh", "active-session");
2183
- if (!existsSync(marker)) return null;
2184
- const rel = readFileSync(marker, "utf-8").trim();
2185
- if (!rel) return null;
2186
- const abs = resolve(root, rel);
2187
- if (!abs.startsWith(resolve(root))) return null;
2207
+ const abs = readActiveSessionMd(root, scope);
2208
+ if (!abs) return null;
2188
2209
  const dirName = basename(dirname(abs));
2189
2210
  const idMatch = dirName.match(/S(\d{3,})/);
2190
2211
  return {
@@ -2196,9 +2217,9 @@ function resolveActiveSessionInfo(root) {
2196
2217
  return null;
2197
2218
  }
2198
2219
  }
2199
- /** 5) Session 块:逐字对齐 osp(活跃会话 + todowrite 首位约定) */
2200
- function sessionBlock(root) {
2201
- const active = resolveActiveSessionInfo(root);
2220
+ /** 5) Session 块:逐字对齐 osp(活跃会话 + todowrite 首位约定);scope = dsh 会话维度 */
2221
+ function sessionBlock(root, scope = DEFAULT_SESSION_SCOPE) {
2222
+ const active = resolveActiveSessionInfo(root, scope);
2202
2223
  if (!active) return "";
2203
2224
  return [
2204
2225
  "",
@@ -2225,7 +2246,7 @@ function sessionBlock(root) {
2225
2246
  ].join("\n");
2226
2247
  }
2227
2248
  /** 完整系统提示词注入文本:ACC + CCE + Constraints + SKILL 全文 + Session(osp 顺序) */
2228
- function serenitySystemPrompt(root) {
2249
+ function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
2229
2250
  const parts = [
2230
2251
  accBlock(root),
2231
2252
  cceBlock(),
@@ -2233,7 +2254,7 @@ function serenitySystemPrompt(root) {
2233
2254
  ];
2234
2255
  const skill = entrySkillSectionText(root);
2235
2256
  if (skill) parts.push(skill);
2236
- const session = sessionBlock(root);
2257
+ const session = sessionBlock(root, scope);
2237
2258
  if (session) parts.push(session);
2238
2259
  return parts.join("\n\n");
2239
2260
  }
@@ -2241,6 +2262,10 @@ function serenitySystemPrompt(root) {
2241
2262
  function agentCwd(context) {
2242
2263
  return (context.agent?.session)?.header?.cwd;
2243
2264
  }
2265
+ /** 从 assembly context 解析 dsh 会话 id(Session 块按会话隔离的 scope) */
2266
+ function agentScope$1(context) {
2267
+ return (context.agent?.session)?.id ?? "default";
2268
+ }
2244
2269
  /**
2245
2270
  * 全局注册系统提示词 section(osp system.transform 的 DSH 等价)。
2246
2271
  * text 回调按 context.agent 的 cwd 上溯 .serenity → 返回该 CCC 的完整注入文本;
@@ -2256,7 +2281,7 @@ function registerEntrySkillSectionGlobal(ctx) {
2256
2281
  if (!cwd) return "";
2257
2282
  const root = findSerenityRoot(cwd);
2258
2283
  if (!root) return "";
2259
- return serenitySystemPrompt(root);
2284
+ return serenitySystemPrompt(root, agentScope$1(context));
2260
2285
  }
2261
2286
  });
2262
2287
  console.log("[serenity-hooks] ✓ 全局入口 skill section 已注册(systemPrompt 就绪)");
@@ -2293,11 +2318,12 @@ const PLUGIN_SOURCE = {
2293
2318
  /**
2294
2319
  * ACC 注入消息:完整内容 = 简短身份头 + 完整系统提示词(ACC 5 块 + CCC 顶层 skill 原文)。
2295
2320
  * 用户要求:在此处注入完整 ACC 系统提示词内容 + CCC 顶层 skill 原文(对齐 osp system.transform)。
2321
+ * scope = dsh 会话 id:Session 块按会话隔离,不跨会话泄露。
2296
2322
  */
2297
- function accMessage(root, configPaths, entrySkillMaxChars) {
2323
+ function accMessage(root, configPaths, entrySkillMaxChars, scope = DEFAULT_SESSION_SCOPE) {
2298
2324
  const content = [{
2299
2325
  type: "text",
2300
- text: `${accIdentityText(root, configPaths, entrySkillMaxChars)}\n\n${serenitySystemPrompt(root)}`
2326
+ text: `${accIdentityText(root, configPaths, entrySkillMaxChars)}\n\n${serenitySystemPrompt(root, scope)}`
2301
2327
  }];
2302
2328
  return createUserMessage({
2303
2329
  content,
@@ -2309,6 +2335,9 @@ const injected = /* @__PURE__ */ new Set();
2309
2335
  function agentKey(agent) {
2310
2336
  return agent.session.id ?? "global";
2311
2337
  }
2338
+ function agentScope(agent) {
2339
+ return agent.session.id ?? "default";
2340
+ }
2312
2341
  function registerContext(ctx, opts = {}) {
2313
2342
  const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
2314
2343
  const entrySkillMaxChars = opts.entrySkillMaxChars ?? 3e4;
@@ -2318,7 +2347,7 @@ function registerContext(ctx, opts = {}) {
2318
2347
  const key = agentKey(agent);
2319
2348
  if (injected.has(key)) return;
2320
2349
  injected.add(key);
2321
- agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
2350
+ agent.inject(accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)));
2322
2351
  syncSafeModeRestriction(agent, root);
2323
2352
  };
2324
2353
  if (opts.seedOnStart ?? true) ctx.on("agent/session-start", (payload) => {
@@ -2339,7 +2368,7 @@ function registerContext(ctx, opts = {}) {
2339
2368
  return {
2340
2369
  kind: "enter",
2341
2370
  messages: [
2342
- accMessage(root, configPaths, entrySkillMaxChars),
2371
+ accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)),
2343
2372
  ...messages,
2344
2373
  ...downstream.messages
2345
2374
  ]
@@ -2362,8 +2391,9 @@ function registerCompactRetention(ctx, opts = {}) {
2362
2391
  if (!agent) return;
2363
2392
  const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
2364
2393
  if (!root) return;
2394
+ const scope = agent.session?.id ?? "default";
2365
2395
  try {
2366
- agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
2396
+ agent.inject(accMessage(root, configPaths, entrySkillMaxChars, scope));
2367
2397
  } catch {}
2368
2398
  });
2369
2399
  }
package/lib/invariant.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import "node:path";
3
2
  //#region src/invariant.ts
4
3
  /**
5
4
  * invariant.ts — 包级不变量伴生(dsh-my-rsi creating-a-plugin 规范)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
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": {
@@ -27,7 +27,8 @@
27
27
  "lib/invariant.js",
28
28
  "lib/types/**/*.d.ts",
29
29
  "cordis.patch.yml",
30
- "dsh.plugin.json"
30
+ "dsh.plugin.json",
31
+ "README.md"
31
32
  ],
32
33
  "scripts": {
33
34
  "typecheck": "tsc --noEmit",
@@ -58,7 +59,7 @@
58
59
  "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
59
60
  "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
60
61
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
61
- "@deepseek-ai/schemastery": "^0.1.0-rc.5",
62
+ "@deepseek-ai/schemastery": "^3.18.1",
62
63
  "cordis": "^4.0.0-rc.7"
63
64
  },
64
65
  "peerDependenciesMeta": {