@shgroup/dsh-serenity-hooks 1.16.8 → 1.16.10
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 +59 -1
- package/lib/index.js +106 -5
- package/package.json +1 -1
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.10",
|
|
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
CHANGED
|
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
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";
|
|
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/* loop 运行状态区块(WebUI 等待界面;并行任务各自一行) */\n.sp-popSection {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-top: 6px;\n border-top: 1px solid var(--dsw-alias-border-l1);\n margin-top: 2px;\n}\n\n.sp-loopItem {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 2px 0 2px 44px;\n min-width: 0;\n}\n\n.sp-loopHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.sp-loopLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.sp-loopRound {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-state-info-primary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n.sp-loopTime {\n flex: none;\n font-size: 11px;\n color: var(--dsw-alias-label-caption);\n}\n\n.sp-loopResp {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n font-family: var(--ds-font-family-code, monospace);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sp-toggle,\n .sp-badge {\n transition: none;\n }\n}\n";
|
|
12
12
|
if (typeof document !== "undefined" && document.querySelector("style[data-sp-css]") === null) {
|
|
13
13
|
const tag = document.createElement("style");
|
|
14
14
|
tag.setAttribute("data-sp-css", "1");
|
|
@@ -18,6 +18,7 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/client/SafeModePanel.tsx
|
|
20
20
|
const API = "/serenity/status";
|
|
21
|
+
const LOOPS_API = "/serenity/loops";
|
|
21
22
|
/** 拼接类名(等价 clsx 最小子集,避免额外依赖) */
|
|
22
23
|
function cx(...parts) {
|
|
23
24
|
return parts.filter(Boolean).join(" ");
|
|
@@ -27,6 +28,7 @@ window.__ModuleLoader__.load({
|
|
|
27
28
|
const [status, setStatus] = (0, react.useState)(null);
|
|
28
29
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
29
30
|
const [open, setOpen] = (0, react.useState)(false);
|
|
31
|
+
const [loops, setLoops] = (0, react.useState)([]);
|
|
30
32
|
const rootRef = (0, react.useRef)(null);
|
|
31
33
|
const sessionId = props.sessionId;
|
|
32
34
|
const refresh = (0, react.useCallback)(async () => {
|
|
@@ -41,6 +43,25 @@ window.__ModuleLoader__.load({
|
|
|
41
43
|
(0, react.useEffect)(() => {
|
|
42
44
|
refresh();
|
|
43
45
|
}, [refresh]);
|
|
46
|
+
(0, react.useEffect)(() => {
|
|
47
|
+
if (!open) return;
|
|
48
|
+
let alive = true;
|
|
49
|
+
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : "";
|
|
50
|
+
const tick = async () => {
|
|
51
|
+
try {
|
|
52
|
+
const data = await (await fetch(`${LOOPS_API}${qs}`, { headers: { accept: "application/json" } })).json();
|
|
53
|
+
if (alive) setLoops(data.loops ?? []);
|
|
54
|
+
} catch {
|
|
55
|
+
if (alive) setLoops([]);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
tick();
|
|
59
|
+
const timer = setInterval(() => void tick(), 3e3);
|
|
60
|
+
return () => {
|
|
61
|
+
alive = false;
|
|
62
|
+
clearInterval(timer);
|
|
63
|
+
};
|
|
64
|
+
}, [open, sessionId]);
|
|
44
65
|
(0, react.useEffect)(() => {
|
|
45
66
|
if (!open) return;
|
|
46
67
|
const onDown = (e) => {
|
|
@@ -166,6 +187,43 @@ window.__ModuleLoader__.load({
|
|
|
166
187
|
status.safeModeOn ? "ON" : "OFF"
|
|
167
188
|
]
|
|
168
189
|
})
|
|
190
|
+
}),
|
|
191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
192
|
+
className: cx("sp-popSection"),
|
|
193
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
194
|
+
className: cx("sp-popRow"),
|
|
195
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
196
|
+
className: cx("sp-popLabel"),
|
|
197
|
+
children: "loops"
|
|
198
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
199
|
+
className: cx("sp-popValue"),
|
|
200
|
+
children: loops.filter((l) => !l.done).length > 0 ? `运行中 ${loops.filter((l) => !l.done).length}${loops.length > 0 ? ` / 共 ${loops.length}` : ""}` : "无运行中 loop"
|
|
201
|
+
})]
|
|
202
|
+
}), loops.slice(0, 5).map((l) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
203
|
+
className: cx("sp-loopItem"),
|
|
204
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
205
|
+
className: cx("sp-loopHead"),
|
|
206
|
+
children: [
|
|
207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
208
|
+
className: cx("sp-loopLabel"),
|
|
209
|
+
title: l.label,
|
|
210
|
+
children: l.label
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
213
|
+
className: cx("sp-loopRound"),
|
|
214
|
+
children: l.done ? "✓" : `R${l.round}`
|
|
215
|
+
}),
|
|
216
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
217
|
+
className: cx("sp-loopTime"),
|
|
218
|
+
children: new Date(l.updated).toLocaleTimeString()
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
222
|
+
className: cx("sp-loopResp"),
|
|
223
|
+
title: l.lastResponse,
|
|
224
|
+
children: l.lastResponse.slice(0, 80) || (l.done ? "已完成" : "(等待首轮响应)")
|
|
225
|
+
})]
|
|
226
|
+
}, l.label))]
|
|
169
227
|
})
|
|
170
228
|
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
171
229
|
className: cx("sp-popRow"),
|
package/lib/index.js
CHANGED
|
@@ -2166,7 +2166,7 @@ function splitModel(model) {
|
|
|
2166
2166
|
model: model.slice(idx + 1)
|
|
2167
2167
|
};
|
|
2168
2168
|
}
|
|
2169
|
-
/** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 →
|
|
2169
|
+
/** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
|
|
2170
2170
|
function buildRoundPrompt(opts) {
|
|
2171
2171
|
const { root, session, label, round, stopToken, progress, task } = opts;
|
|
2172
2172
|
const resumeNote = progress && progress.round > 0 ? `上一轮(round ${progress.round})已完成:${progress.lastResponse.slice(0, 300)}\n永远从上次停止处继续,绝不重做已完成工作。` : "这是第一轮。";
|
|
@@ -2177,14 +2177,82 @@ ${session ? `工作会话:${session}(AGENT_SESSIONS/${session}/SESSION.md
|
|
|
2177
2177
|
${task ? `任务:${task}` : `任务:以 label「${label}」对应的工作为准(若存在工作会话,先读 SESSION.md 明确目标)`}
|
|
2178
2178
|
${resumeNote}
|
|
2179
2179
|
|
|
2180
|
-
|
|
2181
|
-
|
|
2180
|
+
## 工作规范(每轮固定,必须遵守)
|
|
2181
|
+
1. 本轮内自由工作:读文件、改代码、执行命令,尽一切手段推进任务。
|
|
2182
|
+
2. 若本任务属于**阅读整理或文字编写类工作**(读文件提炼、总结、撰写文档、生成文本等),
|
|
2183
|
+
请先加载 eap(acc-eap skill)并按 EAP 标准组织输出:
|
|
2184
|
+
- E↑ 显式:实体/变量明确定义,关系指明方向与基数,边界划定,不用歧义词
|
|
2185
|
+
- R↓ 可重建:关键结论记录来源与推理,可被后续 agent 重建
|
|
2186
|
+
- S↑ 稳定:输出结构可重复生成,不依赖隐含上下文
|
|
2187
|
+
3. 汇报必须具体可核验,不写空话。
|
|
2188
|
+
|
|
2189
|
+
## 每轮汇报(固定格式,逐条回答)
|
|
2182
2190
|
1. 本轮做了什么(具体)
|
|
2183
2191
|
2. 下一步计划
|
|
2184
|
-
3.
|
|
2192
|
+
3. 是否已完成(若完成,只输出 ${stopToken})
|
|
2185
2193
|
|
|
2186
2194
|
若已完成任务,只输出 ${stopToken}。`;
|
|
2187
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* loop 规模化使用指引(guide 子命令输出;S134):
|
|
2198
|
+
* 使用 loop 前必须先加载 eap 设计方案;并行策略;提示词规范(详尽固定 EAP);
|
|
2199
|
+
* 阅读/文字编写类 loop 内部也加载 eap。
|
|
2200
|
+
*/
|
|
2201
|
+
const LOOP_GUIDE = `# loop — 规模化使用指引(guide)
|
|
2202
|
+
|
|
2203
|
+
## ⚠️ 使用前:必须先加载 eap 设计方案
|
|
2204
|
+
调用 loop 前,请先加载 eap(acc-eap skill),基于 EAP 框架设计「规模化 loop 方案」。
|
|
2205
|
+
|
|
2206
|
+
### 1. 任务拆解(E↑ 显式)
|
|
2207
|
+
- 大任务拆成明确子任务:每个子任务定义 目标 / 输入 / 边界(做什么、不做什么)/ 验收标准
|
|
2208
|
+
- 依赖显式化:有依赖的串行,无依赖的可并行
|
|
2209
|
+
|
|
2210
|
+
### 2. 提示词设计(loop 的 task 参数)
|
|
2211
|
+
- task 必须详尽、固定、符合 EAP:目标明确、边界划定、验收标准可判定
|
|
2212
|
+
- 反例「处理这个文件」——歧义;正例「读取 <路径>,提取「关键决策」表格全部行,
|
|
2213
|
+
输出 JSON 数组(字段 id/结论/证据),不改动原文件」
|
|
2214
|
+
- 阅读整理 / 文字编写类工作(读文件提炼、总结、撰写文档、生成文本等):
|
|
2215
|
+
loop 内部 agent 也会被要求加载 eap,按 EAP 标准组织输出
|
|
2216
|
+
|
|
2217
|
+
### 3. 并行策略(规模化)
|
|
2218
|
+
- 无依赖的子任务可并行:每个子任务一个独立 loop(独立 label + task)
|
|
2219
|
+
- 并行执行方式:
|
|
2220
|
+
a. 后台 subagent:每个 subagent 内调 loop(各自 label),subagent 并行运行
|
|
2221
|
+
b. workflow:parallel 阶段驱动多个 loop 子 agent
|
|
2222
|
+
- 并发安全已保证:sessionId 唯一(loop-<label>-<uuid>)、进度文件按 label 隔离
|
|
2223
|
+
(AGENT_SESSIONS/loop-<label>.json)——同 label 续跑、不同 label 互不干扰
|
|
2224
|
+
- 汇总:并行 loop 各自产出进度后,主 agent 汇总合并(或再派一个汇总 loop)
|
|
2225
|
+
|
|
2226
|
+
## 完成判定
|
|
2227
|
+
- 唯一完成条件 = loop 内部 agent 精确回显本轮随机验证码(stop token);对话轮次无上限(不完成不返回)
|
|
2228
|
+
- agent 非正常停止时自动重启(≤100 次防死循环)
|
|
2229
|
+
|
|
2230
|
+
## 等待界面
|
|
2231
|
+
- WebUI 会话头部 Serenity 详情卡显示运行中 loop 的进度(label / 轮次 / 最近响应),并行任务各自一行,约 3s 刷新
|
|
2232
|
+
`;
|
|
2233
|
+
/** 列出 AGENT_SESSIONS/loop-*.json 的全部进度(按 updated 倒序;坏文件跳过) */
|
|
2234
|
+
function listActiveLoops(root) {
|
|
2235
|
+
const dir = join(root, "AGENT_SESSIONS");
|
|
2236
|
+
if (!existsSync(dir)) return [];
|
|
2237
|
+
const out = [];
|
|
2238
|
+
for (const entry of readdirSync(dir)) {
|
|
2239
|
+
if (!entry.startsWith("loop-") || !entry.endsWith(".json")) continue;
|
|
2240
|
+
try {
|
|
2241
|
+
const data = JSON.parse(readFileSync(join(dir, entry), "utf-8"));
|
|
2242
|
+
if (typeof data.label !== "string" || typeof data.round !== "number") continue;
|
|
2243
|
+
out.push({
|
|
2244
|
+
label: data.label,
|
|
2245
|
+
round: data.round,
|
|
2246
|
+
done: data.done === true,
|
|
2247
|
+
model: typeof data.model === "string" ? data.model : "",
|
|
2248
|
+
updated: typeof data.updated === "string" ? data.updated : "",
|
|
2249
|
+
lastResponse: typeof data.lastResponse === "string" ? data.lastResponse : ""
|
|
2250
|
+
});
|
|
2251
|
+
} catch {}
|
|
2252
|
+
}
|
|
2253
|
+
out.sort((a, b) => a.updated < b.updated ? 1 : -1);
|
|
2254
|
+
return out;
|
|
2255
|
+
}
|
|
2188
2256
|
//#endregion
|
|
2189
2257
|
//#region src/tools/loop.ts
|
|
2190
2258
|
/**
|
|
@@ -2249,7 +2317,7 @@ function lastAssistantText(agent) {
|
|
|
2249
2317
|
function createLoopTool(ctx) {
|
|
2250
2318
|
return defineTool({
|
|
2251
2319
|
name: "loop",
|
|
2252
|
-
description: "牛马循环(老 loop 等效):用指定模型创建专用 agent 反复执行任务直到完成。\n用法:loop 接受 task(要完成的目标)或依赖 session 上下文;模型缺省读 .opencode/serenity.json 的 loop.defaultModel(当前 minimax-cn-coding-plan/MiniMax-M3,廉价牛马)。\n行为:内部硬性 while 循环驱动 agent 逐轮推进任务,每轮等待无超时(agent 工作多久等多久)。唯一完成条件 = agent 精确回显本轮随机完成码(stop token),防止低智能模型提前结束。轮次不需要调用者指定——对话轮次**无上限**(不完成不返回),agent 非正常停止时自动重启(重启 ≤100 次,防死循环保险阀)。\n进度:写入 AGENT_SESSIONS/loop-<label>.md/.json;同 label 再次调用从上次轮次续跑(不重做)。\n约束:loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫/session-keeper)。\n示例:loop 执行「扫描 SQC 并修复 DC 问题」,label: sqc-scan",
|
|
2320
|
+
description: "牛马循环(老 loop 等效):用指定模型创建专用 agent 反复执行任务直到完成。\n用法:loop guide(输出规模化使用指引——使用前先加载 eap 设计规模化方案);loop 接受 task(要完成的目标)或依赖 session 上下文;模型缺省读 .opencode/serenity.json 的 loop.defaultModel(当前 minimax-cn-coding-plan/MiniMax-M3,廉价牛马)。\n行为:内部硬性 while 循环驱动 agent 逐轮推进任务,每轮等待无超时(agent 工作多久等多久)。唯一完成条件 = agent 精确回显本轮随机完成码(stop token),防止低智能模型提前结束。轮次不需要调用者指定——对话轮次**无上限**(不完成不返回),agent 非正常停止时自动重启(重启 ≤100 次,防死循环保险阀)。\n进度:写入 AGENT_SESSIONS/loop-<label>.md/.json;同 label 再次调用从上次轮次续跑(不重做)。\n约束:loop agent 受完整 Serenity 约束(ACC 身份/入口技能系统提示词/守卫/session-keeper)。\n示例:loop 执行「扫描 SQC 并修复 DC 问题」,label: sqc-scan;loop guide",
|
|
2253
2321
|
parameters: {
|
|
2254
2322
|
task: {
|
|
2255
2323
|
type: "string",
|
|
@@ -2267,6 +2335,10 @@ function createLoopTool(ctx) {
|
|
|
2267
2335
|
model: {
|
|
2268
2336
|
type: "string",
|
|
2269
2337
|
description: "provider/model(如 minimax-cn-coding-plan/MiniMax-M3);缺省读 loop.defaultModel"
|
|
2338
|
+
},
|
|
2339
|
+
guide: {
|
|
2340
|
+
type: "boolean",
|
|
2341
|
+
description: "输出规模化使用指引(不创建 agent;使用 loop 前先看——含 eap 设计方案要求/并行策略/提示词规范)"
|
|
2270
2342
|
}
|
|
2271
2343
|
},
|
|
2272
2344
|
output: {
|
|
@@ -2274,6 +2346,7 @@ function createLoopTool(ctx) {
|
|
|
2274
2346
|
render: (_args, value) => renderText$1(value)
|
|
2275
2347
|
},
|
|
2276
2348
|
async execute(args, exec) {
|
|
2349
|
+
if (args.guide) return { guide: LOOP_GUIDE };
|
|
2277
2350
|
const root = findSerenityRoot(agentCwd$2(exec));
|
|
2278
2351
|
if (!root) throw new Error("No CCC found: no .serenity file from agent cwd");
|
|
2279
2352
|
const cfg = loadSerenityConfig(root, DEFAULT_SERENITY_CONFIG_PATHS);
|
|
@@ -2288,6 +2361,7 @@ function createLoopTool(ctx) {
|
|
|
2288
2361
|
const parentCtx = exec.agent?.ctx;
|
|
2289
2362
|
const inherited = loopPresetInheritance(parentCtx);
|
|
2290
2363
|
if (!ctx.agents) throw new Error("loop: ctx.agents 不可用");
|
|
2364
|
+
const parentSession = (exec.agent?.session)?.id;
|
|
2291
2365
|
let handle;
|
|
2292
2366
|
let loopAgent;
|
|
2293
2367
|
const spawnAgent = async () => {
|
|
@@ -2296,6 +2370,8 @@ function createLoopTool(ctx) {
|
|
|
2296
2370
|
sessionId,
|
|
2297
2371
|
meta: {
|
|
2298
2372
|
cwd: root,
|
|
2373
|
+
origin: "subagent",
|
|
2374
|
+
...parentSession === void 0 ? {} : { parentSession },
|
|
2299
2375
|
...inherited.agentPreset === void 0 ? {} : { agentPreset: inherited.agentPreset }
|
|
2300
2376
|
},
|
|
2301
2377
|
agentOptions: {
|
|
@@ -3043,6 +3119,7 @@ function registerCompactRetention(ctx, opts = {}) {
|
|
|
3043
3119
|
//#endregion
|
|
3044
3120
|
//#region src/api.ts
|
|
3045
3121
|
const ROUTE_PATH = "/serenity/status";
|
|
3122
|
+
const LOOPS_PATH = "/serenity/loops";
|
|
3046
3123
|
function readBody(req) {
|
|
3047
3124
|
return new Promise((resolve, reject) => {
|
|
3048
3125
|
let data = "";
|
|
@@ -3076,6 +3153,30 @@ function resolveWorkspace(ctx, params) {
|
|
|
3076
3153
|
}
|
|
3077
3154
|
function registerStatusApi(ctx, opts = {}) {
|
|
3078
3155
|
const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
|
|
3156
|
+
ctx.webServer.register({
|
|
3157
|
+
kind: "exact",
|
|
3158
|
+
path: LOOPS_PATH,
|
|
3159
|
+
handler: async (req, res) => {
|
|
3160
|
+
try {
|
|
3161
|
+
if (req.method !== "GET") {
|
|
3162
|
+
sendJson(res, 405, { error: "method not allowed" });
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
3166
|
+
const root = findSerenityRoot(resolveWorkspace(ctx, {
|
|
3167
|
+
sessionId: url.searchParams.get("sessionId") ?? void 0,
|
|
3168
|
+
workspace: url.searchParams.get("workspace") ?? void 0
|
|
3169
|
+
}));
|
|
3170
|
+
if (!root) {
|
|
3171
|
+
sendJson(res, 200, { loops: [] });
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
sendJson(res, 200, { loops: listActiveLoops(root) });
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
sendJson(res, 400, { error: err.message ?? String(err) });
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3079
3180
|
ctx.webServer.register({
|
|
3080
3181
|
kind: "exact",
|
|
3081
3182
|
path: ROUTE_PATH,
|
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.10",
|
|
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": {
|