@shgroup/dsh-serenity-hooks 1.16.7 → 1.16.9

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.7",
3
+ "version": "1.16.9",
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
@@ -12,7 +12,7 @@ import { randomBytes, randomUUID } from "node:crypto";
12
12
  /**
13
13
  * ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
14
14
  *
15
- * 职责:CCC 根检测(P1)、git 检测(P2)、.dsh/serenity.json 配置读取、
15
+ * 职责:CCC 根检测(P1)、git 检测(P2)、serenity.json 配置读取(.opencode 规范位置,.dsh 回退)、
16
16
  * 路径守卫(P3 语义)、安全模式黑名单匹配。
17
17
  *
18
18
  * 由 tools/ 与 seams/ 复用;逻辑移植自 dsh-serenity-plugin v0.1-v0.2
@@ -66,7 +66,11 @@ function resolveInside(root, p) {
66
66
  if (classifyPath(abs, root) === "outside") throw new Error(`Path escape blocked: "${p}" resolves outside "${root}"`);
67
67
  return abs;
68
68
  }
69
- const DEFAULT_SERENITY_CONFIG_PATHS = [".dsh/serenity.json", ".opencode/serenity.json"];
69
+ /**
70
+ * ACC 依赖的 CCC 配置路径(S134 修正:兼容历史——历史在 .opencode,规范位置 = .opencode;
71
+ * .dsh 仅作 dsh 运行时回退,不优先)。
72
+ */
73
+ const DEFAULT_SERENITY_CONFIG_PATHS = [".opencode/serenity.json", ".dsh/serenity.json"];
70
74
  function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
71
75
  for (const candidate of paths) {
72
76
  const p = resolve(root, candidate);
@@ -1389,7 +1393,7 @@ const kitTool = defineTool({
1389
1393
  * (JSON 格式——方便 MSM 直接 read + JSON.parse 读取,零解析依赖)。
1390
1394
  *
1391
1395
  * git 提交策略(可靠机制 × 用户自由):
1392
- * - 配置:.dsh/serenity.json `localstore.gitTrack`: "allow"(可提交)| "deny"(禁提交)
1396
+ * - 配置:.opencode/serenity.json `localstore.gitTrack`: "allow"(可提交)| "deny"(禁提交)
1393
1397
  * - **缺省 deny(没配就是不提交)**;且 deny 的保证**不依赖 dsh 运行**——
1394
1398
  * 写入时自动确保 .gitignore 含 localstore.json(物理保证:即使 dsh 不在、
1395
1399
  * 用户手动 git commit 也不会误提交),cc_git 检查为第二道防线(拒绝 + 提示)
@@ -1558,7 +1562,7 @@ function docText(root) {
1558
1562
  `存储于 CCC 根根目录 ${path}(JSON 格式,MSM 可直接 read + JSON.parse)。`,
1559
1563
  "",
1560
1564
  "## git 提交策略",
1561
- "- 配置:.dsh/serenity.json `localstore.gitTrack`:`\"allow\"`(可提交)| `\"deny\"`(禁提交)",
1565
+ "- 配置:.opencode/serenity.json `localstore.gitTrack`:`\"allow\"`(可提交)| `\"deny\"`(禁提交;.dsh 回退)",
1562
1566
  "- **缺省 deny(没配就是不提交)**;deny 时写入会自动确保 .gitignore 含 localstore.json",
1563
1567
  " (物理保证,不依赖 dsh 运行);cc_git commit 会检查拒绝",
1564
1568
  "- 想提交:配置 `\"localstore\": { \"gitTrack\": \"allow\" }`,并从 .gitignore 移除 localstore.json",
@@ -2162,7 +2166,7 @@ function splitModel(model) {
2162
2166
  model: model.slice(idx + 1)
2163
2167
  };
2164
2168
  }
2165
- /** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报) */
2169
+ /** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
2166
2170
  function buildRoundPrompt(opts) {
2167
2171
  const { root, session, label, round, stopToken, progress, task } = opts;
2168
2172
  const resumeNote = progress && progress.round > 0 ? `上一轮(round ${progress.round})已完成:${progress.lastResponse.slice(0, 300)}\n永远从上次停止处继续,绝不重做已完成工作。` : "这是第一轮。";
@@ -2173,14 +2177,82 @@ ${session ? `工作会话:${session}(AGENT_SESSIONS/${session}/SESSION.md
2173
2177
  ${task ? `任务:${task}` : `任务:以 label「${label}」对应的工作为准(若存在工作会话,先读 SESSION.md 明确目标)`}
2174
2178
  ${resumeNote}
2175
2179
 
2176
- 本轮内你可以自由工作:读文件、改代码、执行命令,尽一切手段推进任务。
2177
- 每轮结束时汇报:
2180
+ ## 工作规范(每轮固定,必须遵守)
2181
+ 1. 本轮内自由工作:读文件、改代码、执行命令,尽一切手段推进任务。
2182
+ 2. 若本任务属于**阅读整理或文字编写类工作**(读文件提炼、总结、撰写文档、生成文本等),
2183
+ 请先加载 eap(acc-eap skill)并按 EAP 标准组织输出:
2184
+ - E↑ 显式:实体/变量明确定义,关系指明方向与基数,边界划定,不用歧义词
2185
+ - R↓ 可重建:关键结论记录来源与推理,可被后续 agent 重建
2186
+ - S↑ 稳定:输出结构可重复生成,不依赖隐含上下文
2187
+ 3. 汇报必须具体可核验,不写空话。
2188
+
2189
+ ## 每轮汇报(固定格式,逐条回答)
2178
2190
  1. 本轮做了什么(具体)
2179
2191
  2. 下一步计划
2180
- 3. 是否已完成(若完成,输出 ${stopToken})
2192
+ 3. 是否已完成(若完成,只输出 ${stopToken})
2181
2193
 
2182
2194
  若已完成任务,只输出 ${stopToken}。`;
2183
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
+ }
2184
2256
  //#endregion
2185
2257
  //#region src/tools/loop.ts
2186
2258
  /**
@@ -2245,7 +2317,7 @@ function lastAssistantText(agent) {
2245
2317
  function createLoopTool(ctx) {
2246
2318
  return defineTool({
2247
2319
  name: "loop",
2248
- description: "牛马循环(老 loop 等效):用指定模型创建专用 agent 反复执行任务直到完成。\n用法:loop 接受 task(要完成的目标)或依赖 session 上下文;模型缺省读 .dsh/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",
2249
2321
  parameters: {
2250
2322
  task: {
2251
2323
  type: "string",
@@ -2263,6 +2335,10 @@ function createLoopTool(ctx) {
2263
2335
  model: {
2264
2336
  type: "string",
2265
2337
  description: "provider/model(如 minimax-cn-coding-plan/MiniMax-M3);缺省读 loop.defaultModel"
2338
+ },
2339
+ guide: {
2340
+ type: "boolean",
2341
+ description: "输出规模化使用指引(不创建 agent;使用 loop 前先看——含 eap 设计方案要求/并行策略/提示词规范)"
2266
2342
  }
2267
2343
  },
2268
2344
  output: {
@@ -2270,11 +2346,12 @@ function createLoopTool(ctx) {
2270
2346
  render: (_args, value) => renderText$1(value)
2271
2347
  },
2272
2348
  async execute(args, exec) {
2349
+ if (args.guide) return { guide: LOOP_GUIDE };
2273
2350
  const root = findSerenityRoot(agentCwd$2(exec));
2274
2351
  if (!root) throw new Error("No CCC found: no .serenity file from agent cwd");
2275
2352
  const cfg = loadSerenityConfig(root, DEFAULT_SERENITY_CONFIG_PATHS);
2276
2353
  const model = args.model ?? cfg.loop?.defaultModel;
2277
- if (!model) throw new Error("loop 需要 model:传参或配置 .dsh/serenity.json loop.defaultModel");
2354
+ if (!model) throw new Error("loop 需要 model:传参或配置 .opencode/serenity.json loop.defaultModel");
2278
2355
  const label = args.label;
2279
2356
  if (!ctx.agentLoop) throw new Error("loop: ctx.agentLoop 不可用");
2280
2357
  const { provider, model: modelName } = splitModel(model);
@@ -2413,7 +2490,7 @@ function renderText(value) {
2413
2490
  }
2414
2491
  const localstoreTool = defineTool({
2415
2492
  name: "localstore",
2416
- description: "ACC 标准本地凭据/配置存储:一个工具管理两个命名空间 credential(凭据)与 config(本地偏好)。存储于 CCC 根根目录 localstore.json(JSON 格式,MSM 可直接读取)。git 策略:.dsh/serenity.json localstore.gitTrack(allow 可提交 / deny 禁提交,缺省 deny)——deny 时写入自动确保 .gitignore 含该文件(物理保证),cc_git commit 会检查拒绝。子命令:list(列 key,凭据不返回值)/ get <name>(读值)/ set <name> <value>(写)/ unset <name>(删)/ show <name>(元数据,凭据不打印值)/ doc(输出存储规范——路径/格式/key 规范/git 策略,agent 可按说明直接用 read/write 操作文件)。默认 scope=credential;config 需传 --scope config(路径为 section.key,如 loop.defaultModel)。",
2493
+ description: "ACC 标准本地凭据/配置存储:一个工具管理两个命名空间 credential(凭据)与 config(本地偏好)。存储于 CCC 根根目录 localstore.json(JSON 格式,MSM 可直接读取)。git 策略:.opencode/serenity.json localstore.gitTrack(allow 可提交 / deny 禁提交,缺省 deny;.dsh 回退)——deny 时写入自动确保 .gitignore 含该文件(物理保证),cc_git commit 会检查拒绝。子命令:list(列 key,凭据不返回值)/ get <name>(读值)/ set <name> <value>(写)/ unset <name>(删)/ show <name>(元数据,凭据不打印值)/ doc(输出存储规范——路径/格式/key 规范/git 策略,agent 可按说明直接用 read/write 操作文件)。默认 scope=credential;config 需传 --scope config(路径为 section.key,如 loop.defaultModel)。",
2417
2494
  parameters: {
2418
2495
  action: {
2419
2496
  type: "string",
@@ -2656,7 +2733,9 @@ function accBlock(root) {
2656
2733
  " neat — return the full Neat design collaboration protocol",
2657
2734
  " cce — return the full Cognitive Continuity Engineering framework",
2658
2735
  " loop — 牛马循环:指定模型专用 agent 反复执行",
2659
- " localstore — ACC 本地凭据/配置存储(credential 0600 + config 0644,~/.serenity/);doc 子命令输出规范",
2736
+ " localstore — ACC 本地凭据/配置存储(CCC localstore.json,JSON 格式;git 策略 localstore.gitTrack 缺省 deny);doc 子命令输出规范",
2737
+ "",
2738
+ " ℹ️ CCC 内文件操作(read/write/edit/glob/grep 等)请使用相对 CCC 根的相对路径(如 AGENT_SESSIONS/2026-08-14--S134--x/SESSION.md);Root / SESSION.md path 等绝对路径仅作标识,不作工具入参",
2660
2739
  "",
2661
2740
  "The DSH platform tools remain available too (read/write/edit/glob/grep/web_search/ask_user_question/subagent/workflow/goal and more) — the ACC tools above are the serenity-native layer, not the only tools.",
2662
2741
  "",
@@ -3037,6 +3116,7 @@ function registerCompactRetention(ctx, opts = {}) {
3037
3116
  //#endregion
3038
3117
  //#region src/api.ts
3039
3118
  const ROUTE_PATH = "/serenity/status";
3119
+ const LOOPS_PATH = "/serenity/loops";
3040
3120
  function readBody(req) {
3041
3121
  return new Promise((resolve, reject) => {
3042
3122
  let data = "";
@@ -3070,6 +3150,30 @@ function resolveWorkspace(ctx, params) {
3070
3150
  }
3071
3151
  function registerStatusApi(ctx, opts = {}) {
3072
3152
  const configPaths = opts.configPaths ?? DEFAULT_SERENITY_CONFIG_PATHS;
3153
+ ctx.webServer.register({
3154
+ kind: "exact",
3155
+ path: LOOPS_PATH,
3156
+ handler: async (req, res) => {
3157
+ try {
3158
+ if (req.method !== "GET") {
3159
+ sendJson(res, 405, { error: "method not allowed" });
3160
+ return;
3161
+ }
3162
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
3163
+ const root = findSerenityRoot(resolveWorkspace(ctx, {
3164
+ sessionId: url.searchParams.get("sessionId") ?? void 0,
3165
+ workspace: url.searchParams.get("workspace") ?? void 0
3166
+ }));
3167
+ if (!root) {
3168
+ sendJson(res, 200, { loops: [] });
3169
+ return;
3170
+ }
3171
+ sendJson(res, 200, { loops: listActiveLoops(root) });
3172
+ } catch (err) {
3173
+ sendJson(res, 400, { error: err.message ?? String(err) });
3174
+ }
3175
+ }
3176
+ });
3073
3177
  ctx.webServer.register({
3074
3178
  kind: "exact",
3075
3179
  path: ROUTE_PATH,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.16.7",
3
+ "version": "1.16.9",
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": {