@shgroup/dsh-serenity-hooks 1.16.1 → 1.16.3

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.1",
3
+ "version": "1.16.3",
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);
@@ -1461,6 +1498,24 @@ const cceTool = defineTool({
1461
1498
  }
1462
1499
  });
1463
1500
  //#endregion
1501
+ //#region src/loop-preset-inherit.ts
1502
+ /**
1503
+ * 解析 loop agent 从父 agent 继承的 preset,并组装创建 setup 钩子。
1504
+ * @param parentCtx - 发起 loop 的 agent 的 scope ctx;无(headless/非 agent 上下文)时为 undefined。
1505
+ * @returns 继承结果:meta 用的 agentPreset 与创建 setup 钩子。
1506
+ */
1507
+ function loopPresetInheritance(parentCtx) {
1508
+ if (parentCtx === void 0) return {};
1509
+ const agentPreset = parentCtx.get("agentPresets")?.composedPreset(parentCtx);
1510
+ if (agentPreset === void 0) return {};
1511
+ return {
1512
+ agentPreset,
1513
+ setup: (childCtx) => {
1514
+ childCtx.get("agentPresets")?.composeFrom(childCtx, parentCtx);
1515
+ }
1516
+ };
1517
+ }
1518
+ //#endregion
1464
1519
  //#region src/loop-ops.ts
1465
1520
  /**
1466
1521
  * loop-ops.ts — acc_loop 纯逻辑层(零 DSH 依赖,可独立单测)
@@ -1550,9 +1605,13 @@ ${resumeNote}
1550
1605
  * model(选)provider/model(如 minimax-cn-coding-plan/MiniMax-M3);缺省读 loop.defaultModel
1551
1606
  * maxRounds(默认 100)轮次上限;每轮等待 agent **无超时**(loop 可永续,agent 工作多久等多久)
1552
1607
  *
1553
- * 机制:ctx.agentLoop.create({provider, model}) 创建专用 agent(进程内),
1608
+ * 机制:ctx.agents.create()(带 setup 钩子)创建专用 agent(进程内),
1554
1609
  * 每轮 followup → agent/status idle → 读 session.events 响应 → 写进度 → stop token 检查 → 续跑。
1555
1610
  * 工厂模式:apply 时闭包捕获插件 ctx(工具 execute 无 ctx 参数)。
1611
+ *
1612
+ * preset 继承:setup 钩子里对子 agent 执行 agentPresets.composeFrom(对齐 subagent 先例),
1613
+ * 使 loop agent 继承发起方会话的 agent preset 工具(read/write/edit 等 preset 层工具)。
1614
+ * agentPresets 是可选服务——无 preset 装配的环境(无 roster 部署)退化为空工具层(历史行为)。
1556
1615
  */
1557
1616
  function agentCwd$1(exec) {
1558
1617
  return (exec.agent?.session)?.header?.cwd ?? process.cwd();
@@ -1592,6 +1651,7 @@ function lastAssistantText(agent) {
1592
1651
  return "";
1593
1652
  }
1594
1653
  /** 创建 loop 工具(闭包捕获插件 ctx → 可访问 ctx.agentLoop) */
1654
+ /** 创建 loop 工具(闭包捕获插件 ctx → 可访问 ctx.agentLoop) */
1595
1655
  function createLoopTool(ctx) {
1596
1656
  return defineTool({
1597
1657
  name: "loop",
@@ -1636,10 +1696,23 @@ function createLoopTool(ctx) {
1636
1696
  const stopToken = newStopToken();
1637
1697
  let progress = readProgress(root, label);
1638
1698
  const startRound = progress ? Math.min(progress.round + 1, maxRounds) : 1;
1639
- const loopAgent = ctx.agentLoop.create(`loop-${label}`, {
1640
- provider,
1641
- model: modelName
1642
- }, { cwd: root });
1699
+ const parentCtx = exec.agent?.ctx;
1700
+ const inherited = loopPresetInheritance(parentCtx);
1701
+ if (!ctx.agents) throw new Error("loop: ctx.agents 不可用");
1702
+ const sessionId = `loop-${label}`;
1703
+ const handle = await ctx.agents.create({
1704
+ sessionId,
1705
+ meta: {
1706
+ cwd: root,
1707
+ ...inherited.agentPreset === void 0 ? {} : { agentPreset: inherited.agentPreset }
1708
+ },
1709
+ agentOptions: {
1710
+ provider,
1711
+ model: modelName
1712
+ },
1713
+ ...inherited.setup === void 0 ? {} : { setup: inherited.setup }
1714
+ });
1715
+ const loopAgent = handle.agent;
1643
1716
  let done = false;
1644
1717
  let lastResponse = progress?.lastResponse ?? "";
1645
1718
  let finalRound = startRound - 1;
@@ -1690,7 +1763,9 @@ function createLoopTool(ctx) {
1690
1763
  updated: (/* @__PURE__ */ new Date()).toISOString(),
1691
1764
  lastResponse
1692
1765
  });
1693
- } finally {}
1766
+ } finally {
1767
+ await handle.dispose().catch(() => {});
1768
+ }
1694
1769
  const { json } = loopProgressPaths(root, label);
1695
1770
  return {
1696
1771
  done,
@@ -1862,29 +1937,17 @@ function registerGuards(ctx, opts = {}) {
1862
1937
  }
1863
1938
  //#endregion
1864
1939
  //#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;
1940
+ /** 解析当前 dsh 会话 scope 的活跃会话 SESSION.md 绝对路径;无标记/越界返回 null */
1941
+ function resolveActiveSession(root, scope = DEFAULT_SESSION_SCOPE) {
1942
+ return readActiveSessionMd(root, scope);
1881
1943
  }
1882
- /** 注册 agent/turn-stopping:CCC 内活动会话自动心跳落盘 */
1944
+ /** 注册 agent/turn-stopping:CCC 内活动会话自动心跳落盘(按 dsh 会话隔离) */
1883
1945
  function registerTurnFlush(ctx) {
1884
1946
  ctx.on("agent/turn-stopping", async (payload) => {
1885
- const root = findSerenityRoot(payload.agent.session?.header?.cwd ?? process.cwd());
1947
+ const agent = payload.agent;
1948
+ const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
1886
1949
  if (!root) return;
1887
- const sessionMd = resolveActiveSession(root);
1950
+ const sessionMd = resolveActiveSession(root, agent.session?.id ?? "default");
1888
1951
  if (sessionMd) appendHeartbeat(sessionMd);
1889
1952
  });
1890
1953
  }
@@ -2176,15 +2239,11 @@ function entrySkillSectionText(root) {
2176
2239
  if (skills.length === 0) return "";
2177
2240
  return skills.map((s) => sanitizeSkillContent(s.content)).filter((c) => c.length > 0).join("\n\n");
2178
2241
  }
2179
- /** 活跃会话解析:.dsh/active-session 标记(内容 = 相对 CCC 根的 SESSION.md 路径) */
2180
- function resolveActiveSessionInfo(root) {
2242
+ /** 活跃会话解析:按 dsh 会话 scope 读标记(隔离;不回退旧全局标记) */
2243
+ function resolveActiveSessionInfo(root, scope) {
2181
2244
  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;
2245
+ const abs = readActiveSessionMd(root, scope);
2246
+ if (!abs) return null;
2188
2247
  const dirName = basename(dirname(abs));
2189
2248
  const idMatch = dirName.match(/S(\d{3,})/);
2190
2249
  return {
@@ -2196,9 +2255,9 @@ function resolveActiveSessionInfo(root) {
2196
2255
  return null;
2197
2256
  }
2198
2257
  }
2199
- /** 5) Session 块:逐字对齐 osp(活跃会话 + todowrite 首位约定) */
2200
- function sessionBlock(root) {
2201
- const active = resolveActiveSessionInfo(root);
2258
+ /** 5) Session 块:逐字对齐 osp(活跃会话 + todowrite 首位约定);scope = dsh 会话维度 */
2259
+ function sessionBlock(root, scope = DEFAULT_SESSION_SCOPE) {
2260
+ const active = resolveActiveSessionInfo(root, scope);
2202
2261
  if (!active) return "";
2203
2262
  return [
2204
2263
  "",
@@ -2225,7 +2284,7 @@ function sessionBlock(root) {
2225
2284
  ].join("\n");
2226
2285
  }
2227
2286
  /** 完整系统提示词注入文本:ACC + CCE + Constraints + SKILL 全文 + Session(osp 顺序) */
2228
- function serenitySystemPrompt(root) {
2287
+ function serenitySystemPrompt(root, scope = DEFAULT_SESSION_SCOPE) {
2229
2288
  const parts = [
2230
2289
  accBlock(root),
2231
2290
  cceBlock(),
@@ -2233,7 +2292,7 @@ function serenitySystemPrompt(root) {
2233
2292
  ];
2234
2293
  const skill = entrySkillSectionText(root);
2235
2294
  if (skill) parts.push(skill);
2236
- const session = sessionBlock(root);
2295
+ const session = sessionBlock(root, scope);
2237
2296
  if (session) parts.push(session);
2238
2297
  return parts.join("\n\n");
2239
2298
  }
@@ -2241,6 +2300,10 @@ function serenitySystemPrompt(root) {
2241
2300
  function agentCwd(context) {
2242
2301
  return (context.agent?.session)?.header?.cwd;
2243
2302
  }
2303
+ /** 从 assembly context 解析 dsh 会话 id(Session 块按会话隔离的 scope) */
2304
+ function agentScope$1(context) {
2305
+ return (context.agent?.session)?.id ?? "default";
2306
+ }
2244
2307
  /**
2245
2308
  * 全局注册系统提示词 section(osp system.transform 的 DSH 等价)。
2246
2309
  * text 回调按 context.agent 的 cwd 上溯 .serenity → 返回该 CCC 的完整注入文本;
@@ -2256,7 +2319,7 @@ function registerEntrySkillSectionGlobal(ctx) {
2256
2319
  if (!cwd) return "";
2257
2320
  const root = findSerenityRoot(cwd);
2258
2321
  if (!root) return "";
2259
- return serenitySystemPrompt(root);
2322
+ return serenitySystemPrompt(root, agentScope$1(context));
2260
2323
  }
2261
2324
  });
2262
2325
  console.log("[serenity-hooks] ✓ 全局入口 skill section 已注册(systemPrompt 就绪)");
@@ -2293,11 +2356,12 @@ const PLUGIN_SOURCE = {
2293
2356
  /**
2294
2357
  * ACC 注入消息:完整内容 = 简短身份头 + 完整系统提示词(ACC 5 块 + CCC 顶层 skill 原文)。
2295
2358
  * 用户要求:在此处注入完整 ACC 系统提示词内容 + CCC 顶层 skill 原文(对齐 osp system.transform)。
2359
+ * scope = dsh 会话 id:Session 块按会话隔离,不跨会话泄露。
2296
2360
  */
2297
- function accMessage(root, configPaths, entrySkillMaxChars) {
2361
+ function accMessage(root, configPaths, entrySkillMaxChars, scope = DEFAULT_SESSION_SCOPE) {
2298
2362
  const content = [{
2299
2363
  type: "text",
2300
- text: `${accIdentityText(root, configPaths, entrySkillMaxChars)}\n\n${serenitySystemPrompt(root)}`
2364
+ text: `${accIdentityText(root, configPaths, entrySkillMaxChars)}\n\n${serenitySystemPrompt(root, scope)}`
2301
2365
  }];
2302
2366
  return createUserMessage({
2303
2367
  content,
@@ -2309,6 +2373,9 @@ const injected = /* @__PURE__ */ new Set();
2309
2373
  function agentKey(agent) {
2310
2374
  return agent.session.id ?? "global";
2311
2375
  }
2376
+ function agentScope(agent) {
2377
+ return agent.session.id ?? "default";
2378
+ }
2312
2379
  function registerContext(ctx, opts = {}) {
2313
2380
  const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
2314
2381
  const entrySkillMaxChars = opts.entrySkillMaxChars ?? 3e4;
@@ -2318,7 +2385,7 @@ function registerContext(ctx, opts = {}) {
2318
2385
  const key = agentKey(agent);
2319
2386
  if (injected.has(key)) return;
2320
2387
  injected.add(key);
2321
- agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
2388
+ agent.inject(accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)));
2322
2389
  syncSafeModeRestriction(agent, root);
2323
2390
  };
2324
2391
  if (opts.seedOnStart ?? true) ctx.on("agent/session-start", (payload) => {
@@ -2339,7 +2406,7 @@ function registerContext(ctx, opts = {}) {
2339
2406
  return {
2340
2407
  kind: "enter",
2341
2408
  messages: [
2342
- accMessage(root, configPaths, entrySkillMaxChars),
2409
+ accMessage(root, configPaths, entrySkillMaxChars, agentScope(agent)),
2343
2410
  ...messages,
2344
2411
  ...downstream.messages
2345
2412
  ]
@@ -2362,8 +2429,9 @@ function registerCompactRetention(ctx, opts = {}) {
2362
2429
  if (!agent) return;
2363
2430
  const root = findSerenityRoot(agent.session?.header?.cwd ?? process.cwd());
2364
2431
  if (!root) return;
2432
+ const scope = agent.session?.id ?? "default";
2365
2433
  try {
2366
- agent.inject(accMessage(root, configPaths, entrySkillMaxChars));
2434
+ agent.inject(accMessage(root, configPaths, entrySkillMaxChars, scope));
2367
2435
  } catch {}
2368
2436
  });
2369
2437
  }
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.1",
3
+ "version": "1.16.3",
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": {
@@ -50,6 +50,7 @@
50
50
  "peerDependencies": {
51
51
  "@deepseek-ai/dsh-agent": "^0.1.0-rc.5",
52
52
  "@deepseek-ai/dsh-agent-loop": "^0.1.0-rc.5",
53
+ "@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.5",
53
54
  "@deepseek-ai/dsh-compaction": "^0.1.0-rc.5",
54
55
  "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
55
56
  "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
@@ -59,7 +60,7 @@
59
60
  "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
60
61
  "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
61
62
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
62
- "@deepseek-ai/schemastery": "^0.1.0-rc.5",
63
+ "@deepseek-ai/schemastery": "^3.18.1",
63
64
  "cordis": "^4.0.0-rc.7"
64
65
  },
65
66
  "peerDependenciesMeta": {
@@ -75,6 +76,9 @@
75
76
  "@deepseek-ai/dsh-agent-loop": {
76
77
  "optional": true
77
78
  },
79
+ "@deepseek-ai/dsh-agent-presets": {
80
+ "optional": true
81
+ },
78
82
  "@deepseek-ai/dsh-compaction": {
79
83
  "optional": true
80
84
  }