@shgroup/dsh-serenity-hooks 1.16.1 → 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/dsh.plugin.json +1 -1
- package/lib/client.js +201 -0
- package/lib/index.js +80 -50
- package/lib/invariant.js +0 -1
- package/package.json +2 -2
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-serenity-hooks",
|
|
3
|
-
"version": "1.16.
|
|
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
|
-
/**
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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 =
|
|
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
|
-
/** 关闭活动会话:删除
|
|
790
|
-
function closeSession(root) {
|
|
791
|
-
const 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
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
2180
|
-
function resolveActiveSessionInfo(root) {
|
|
2204
|
+
/** 活跃会话解析:按 dsh 会话 scope 读标记(隔离;不回退旧全局标记) */
|
|
2205
|
+
function resolveActiveSessionInfo(root, scope) {
|
|
2181
2206
|
try {
|
|
2182
|
-
const
|
|
2183
|
-
if (!
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shgroup/dsh-serenity-hooks",
|
|
3
|
-
"version": "1.16.
|
|
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": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
|
|
60
60
|
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
|
|
61
61
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
|
|
62
|
-
"@deepseek-ai/schemastery": "^
|
|
62
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
63
63
|
"cordis": "^4.0.0-rc.7"
|
|
64
64
|
},
|
|
65
65
|
"peerDependenciesMeta": {
|