dsh-punky-swarm 0.4.0 → 0.4.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.en.md +8 -3
  3. package/README.md +8 -3
  4. package/cordis.patch.yml +18 -1
  5. package/docs/governance-technical.en.md +5 -2
  6. package/docs/governance-technical.md +5 -2
  7. package/docs/webui-governance-config.en.md +77 -0
  8. package/docs/webui-governance-config.md +77 -0
  9. package/lib/api.js +86 -1
  10. package/lib/client.js +828 -2
  11. package/lib/governance/config.d.ts +10 -0
  12. package/lib/governance/config.js +204 -1
  13. package/lib/governance/config.ts +191 -2
  14. package/lib/governance/index.d.ts +1 -0
  15. package/lib/governance/index.js +3 -1
  16. package/lib/governance/index.ts +3 -1
  17. package/lib/governance/preset-loader.d.ts +16 -0
  18. package/lib/governance/preset-loader.js +86 -0
  19. package/lib/governance/preset-loader.ts +92 -0
  20. package/lib/governance/wiring.js +8 -2
  21. package/lib/index.js +127 -28
  22. package/lib/panel/gov-config.js +749 -0
  23. package/lib/panel/locales.js +86 -2
  24. package/lib/panel/main.js +10 -0
  25. package/lib/state/event-types.js +5 -0
  26. package/lib/tools/lane-tools.js +6 -1
  27. package/lib/tools/register.js +1 -1
  28. package/lib/verify/evidence.js +1 -1
  29. package/lib/verify/mount.js +1 -1
  30. package/lib/watch/lane-heartbeat.js +294 -6
  31. package/lib/webui/config-trust.js +62 -0
  32. package/lib/webui/runtime-config.js +467 -0
  33. package/package.json +4 -2
  34. package/presets/hook-rules/README.md +63 -0
  35. package/presets/hook-rules/compose.json +209 -0
  36. package/presets/hook-rules/l1-sensitive.json +143 -0
  37. package/presets/hook-rules/l2-resource.json +82 -0
  38. package/presets/jiufeng/NOTICE +5 -5
  39. package/presets/jiufeng/agent.cordis.yml +2 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,50 @@
1
+ ## 0.4.2(2026-09-06)
2
+
3
+ ### 治理配置面板:Lane 过期检测与重派探针
4
+
5
+ - 治理配置页新增 **Lane 过期检测** 与 **长跑超时重派探针** 两级开关:前者控制 running lane 的心跳过期扫描,后者控制长时间无 checkpoint / 活动进展 lane 的重派候选探测。开关随护栏共用同一保存通道写入运行配置,保存即热更生效,进程重启后按 runtime.json 自动对账,无需重复设置;出厂默认开(缺省即开、显式关闭才停),关闭仅停止扫描与候选探测,不改动已落盘批次 / 成员状态,重新开启后自基线恢复扫描。
6
+ - 长跑时间窗口面板化:**超时窗口**(默认 20 分钟)与**无进展窗口**(默认 5 分钟)可在表单以分钟输入,保存时自动换算毫秒生效,无需手工编辑配置文件。
7
+ - 「违规自动升级」说明文案优化:直接说明触发次数 / 窗口阈值的判定与升级暂停行为,去除括号提示与实现细节,降低配置理解成本。
8
+ - 配套新增回归测试:覆盖开关保存热更、进程重启对账与长跑无进展候选场景。
9
+
10
+ ### 无 Manager 批次的长跑候选消费
11
+
12
+ - 未拉起编排 Manager 的批次运行期间,由任务负责人(Leader)兜底承担长跑候选消费:读取候选广播、核对 lane 探针状态与 checkpoint / 活动进展,按半自动规则裁决继续观察或重新派发;已拉起 Manager 的批次仍由 Manager 完成调度。
13
+
14
+ ### 注释与文档口径统一
15
+
16
+ - 「出厂默认开、显式关闭才停」口径统一:Lane 过期检测与长跑探针的默认开启说明跨代码注释、预设说明与双语文档对齐(纯注释与文档改动,零逻辑变更)。
17
+
18
+ ### 发布整理
19
+
20
+ - 版本 0.4.1 → 0.4.2;变更记录整理。
21
+
22
+ ## 0.4.1(2026-09-05)
23
+
24
+ ### 治理预设规则包
25
+
26
+ - 出厂护栏规则预设随包发布(presets/hook-rules):l1-sensitive(L1 敏感数据防护 12 条)、l2-resource(L2 资源上限 6 条)、compose(L1+L2 全量 18 条),wrapper 结构(`_meta` 元数据 + `rules` 数组),规则字段与引擎 Rule 类型逐字段对齐、零扩展字段。
27
+ - preset 装载与引用:装载器剥离 `_meta` 取 rules 并做受控资产早失败校验;`governance.hook.preset` 支持注册 id / id 数组引用(如 `"preset": "compose"` 或 `["l1-sensitive","l2-resource"]`),跨 preset 规则 id 全局唯一性校验拒绝重复。
28
+
29
+ ### Web UI 治理配置页 + runtime.json 写通道
30
+
31
+ - 治理配置设置页(Web UI 设置区):护栏开关、规则预设、违规自动升级(触发次数 / 窗口)可视化配置;页面保存即时生效、无需重启。
32
+ - runtime.json 热写通道:保存请求经 config-trust 校验(顶层白名单 / 值域 / preset 与内联规则冲突守卫)后落盘 runtime.json,400 校验拒绝不回写;窗口秒输入后端毫秒归一化(windowSeconds → windowMs,线协议键不落盘)。
33
+ - 随包双语主题文档:docs/webui-governance-config(.en).md。
34
+
35
+ ### lane_longrun 超时无进展探针
36
+
37
+ - watch 长跑档(默认开启):running lane 运行超时且长期无 checkpoint / 活动进展 → 产候选并广播给 Manager(探针只产候选,不改成员状态),重派裁决归 Manager / Leader。
38
+ - 与心跳 stalled 档并列扫描;事件留痕可审计。
39
+
40
+ ### Web UI 修复
41
+
42
+ - 治理配置页 UI 修复:重命名、preset 多选、放大字号、移除全组合提示。
43
+
44
+ ### 发布整理
45
+
46
+ - 版本 0.4.0 → 0.4.1;根 README(GitHub 面)精简:170 → ≈100 行,中文 / 英文 1:1 同构重写,去除过期版本与测试数(实测刷新 816);删除 README.market.md(人话版内容并入精简后根 README 机制表与能力段)。
47
+
1
48
  ## 0.4.0(2026-09-03)
2
49
 
3
50
  ### 工具调用级治理护栏
package/README.en.md CHANGED
@@ -15,8 +15,8 @@
15
15
  - **In-process messaging with loop protection** — the mailbox's three boxes (inbox / outbox / broadcast) use atomic writes and acknowledgements; loop protection suppresses message storms, and communication paths stay traceable.
16
16
  - **Crash-recoverable** — heartbeat expiry detection plus progress checkpoint preservation: after a crash the scene and artifacts remain inspectable and a new worker can take over; no automatic resume — a failed task is redone by opening a new batch.
17
17
  - **Read-only monitoring panel** — the Web UI shows batches, lane states, and the event timeline directly; read-only, non-intrusive, so nothing can be altered by accident.
18
- - **Call-level guardrails (6 primitives)** — the call-level line of defense in two-layer governance (local-first, evidence-auditable): besides the pre-dispatch difficulty gate, every tool call is adjudicated per call against the six primitives ALLOW / DENY / REQUIRE_APPROVAL / NARROW / DEFER / PAUSE to decide whether it is out of bounds; a hit produces a **tamper-evident refusal receipt** (sha256 hash-chain anchored; re-checking can locate where tampering happened). Adjudication is deterministic, predictable, and easy to test — suited to local multi-agent orchestration needing **auditable out-of-bounds prevention with deterministic, testable governance**. Guardrail events are observable at the event level through receipts and event-stream files (verifiable), with no dedicated UI panel; out of the box `rules` is empty (zero interception) and takes effect once rules are configured on demand.
19
- - **Hot-updatable configuration, no restart** — guardrail rules and switches written to `runtime.json` take effect immediately, with no process restart.
18
+ - **Call-level guardrails (6 primitives)** — the call-level line of defense in two-layer governance (local-first, evidence-auditable): besides the pre-dispatch difficulty gate, every tool call is adjudicated per call against the six primitives ALLOW / DENY / REQUIRE_APPROVAL / NARROW / DEFER / PAUSE to decide whether it is out of bounds; a hit produces a **tamper-evident refusal receipt** (sha256 hash-chain anchored; re-checking can locate where tampering happened). Adjudication is deterministic, predictable, and easy to test — suited to local multi-agent orchestration needing **auditable out-of-bounds prevention with deterministic, testable governance**. Guardrail events are observable at the event level through receipts and event-stream files (verifiable); the switches and rules are configured on the "Punky Swarm Governance" page in the Settings sidebar (out of the box `rules` is empty (zero interception) and takes effect once rules are configured on demand).
19
+ - **Hot-updatable configuration, no restart** — guardrail rules and switches, plus the watch capability switches (lane heartbeat / longrun probe), written to `runtime.json` take effect immediately, with no process restart; on restart they are also reconciled against `runtime.json`.
20
20
  - **National-standard AIP compatible** — follows the descriptor structures of GB/Z 185-2026 (Artificial Intelligence — Agent Interconnection): tool 6 attributes / agent ACS / message-task-session mapping; additive only, pluggable.
21
21
  - **Optional ACPs communication** — external mTLS service endpoint, registry registration, and external discovery, all off by default (secure default).
22
22
  - **Runs locally, works out of the box** — zero cloud dependency, zero network exposure by default; a single npm package contains the plugin engine, the Punky Swarm preset, and the jiufeng-team role guide, with bilingual (Chinese / English) documentation.
@@ -68,6 +68,8 @@ The plugin ships a **read-only** monitoring panel — the third tab, "Conversati
68
68
  - **Batch detail**: lane status cards (state, task summary, gate missing items, layer and dependencies), event timeline, inbox counts;
69
69
  - **Read-only by design**: 3-second auto-refresh, follows the light/dark theme; batch and gate states are view-only, and governance operations are carried out by the Leader through governance tools.
70
70
 
71
+ The Settings sidebar also provides a "**Punky Swarm Governance**" page (writable, saveable): configure the guardrails (`governance.hook` switch/preset/escalation/narrowing) and the lane capability switches (`watch.enabled` / `watch.longrun.enabled`) here; saving writes `runtime.json` and takes effect immediately with no dsh restart. See [docs/webui-governance-config.en.md](docs/webui-governance-config.en.md).
72
+
71
73
  ## Configuration at a Glance
72
74
 
73
75
  Plugin configuration is centralized in `cordis.patch.yml`; the key assembly keys and their defaults:
@@ -81,6 +83,8 @@ Plugin configuration is centralized in `cordis.patch.yml`; the key assembly keys
81
83
  | Identity system (AIC / CAI / signing) | `aip.identity.enabled` | Off |
82
84
  | ACPs communication (mTLS endpoint / bridge / registry / discovery) | `acps.*` | Off (when off: no listeners, no timers, no network) |
83
85
 
86
+ > Note: `capabilities.watch` includes a `longrun` sub-switch (longrun timeout re-dispatch probe, on by factory default, off only when explicitly set to `false`); both `watch.enabled` and `watch.longrun.enabled` can be hot-toggled on the "Punky Swarm Governance" page, written to `runtime.json` and taking effect immediately (see "Hot-updatable configuration, no restart" above and [docs/webui-governance-config.en.md](docs/webui-governance-config.en.md)).
87
+
84
88
  Key semantics, configuration examples, and rule authoring: see [docs/governance-technical.en.md](docs/governance-technical.en.md) and [docs/guardrails-hook.en.md](docs/guardrails-hook.en.md).
85
89
 
86
90
  ### Secure defaults
@@ -117,7 +121,8 @@ Technical details live under `docs/` (each document ships with an English versio
117
121
 
118
122
  | Document | Contents |
119
123
  |---|---|
120
- | [docs/governance-technical.en.md](docs/governance-technical.en.md) | Batch-level governance technical manual: three-layer gates, state machine, wavePlan contract, 20 governance tools reference, assembly key table, lifecycle |
124
+ | [docs/governance-technical.en.md](docs/governance-technical.en.md) | Batch-level governance technical manual: three-layer gates, state machine, wavePlan contract, 21 governance tools reference, assembly key table, lifecycle |
125
+ | [docs/webui-governance-config.en.md](docs/webui-governance-config.en.md) | Web UI "Governance Configuration" page guide: save/activation semantics and behavior boundaries for guardrail switch/presets and the lane capability switch (watch) |
121
126
  | [docs/guardrails-hook.en.md](docs/guardrails-hook.en.md) | Call-level guardrail technical manual: runtime semantics of the 6 primitives, rule configuration and examples, refusal receipts and verification, hot update, boundaries and non-provisions, capability boundaries & trade-offs |
122
127
  | [docs/aip-compliance.en.md](docs/aip-compliance.en.md) | National-standard AIP compliance details: tool 6 attributes, ACS field set, message / task / session mapping, identity system |
123
128
  | [docs/acps-communication.en.md](docs/acps-communication.en.md) | ACPs communication details: mTLS endpoints, internal bridge, registry / discovery, configuration examples, capability boundaries |
package/README.md CHANGED
@@ -15,8 +15,8 @@ English: [README.en.md](README.en.md)
15
15
  - **进程内消息与环防护**——mailbox 三箱(inbox / outbox / broadcast)原子写与确认,环防护抑制消息风暴,通信路径可追踪。
16
16
  - **崩溃可恢复**——心跳过期检测 + 进度 checkpoint 保全:崩溃后现场与产物可查、新 worker 可接续;不自动续跑,失败任务重做即开新批次。
17
17
  - **只读监控面板**——Web UI 直接查看批次、lane 状态与事件时间线,只读不干预,人工不可误改。
18
- - **工具调用级护栏(6 原语)**——双层治理中的调用级防线(本地优先、证据可审计):除派发前难度门禁外,每次工具调用再按 ALLOW / DENY / REQUIRE_APPROVAL / NARROW / DEFER / PAUSE 六原语逐调用裁决是否越界,命中即产出**可验篡改的拒绝收据**(sha256 哈希链锚定,复核可定位篡改位置)。裁决确定、可预期、便于测试,适合需要**可审计防越界、确定性可测试治理**的本地多 Agent 编排。护栏事件以收据与事件流文件留痕、可复核(事件级可观测),暂无独立 UI 面板;出厂 `rules` 为空即零拦截,按需配置规则后生效。
19
- - **热更新配置,免重启**——护栏规则与开关写入 `runtime.json` 即时生效,进程无需重启。
18
+ - **工具调用级护栏(6 原语)**——双层治理中的调用级防线(本地优先、证据可审计):除派发前难度门禁外,每次工具调用再按 ALLOW / DENY / REQUIRE_APPROVAL / NARROW / DEFER / PAUSE 六原语逐调用裁决是否越界,命中即产出**可验篡改的拒绝收据**(sha256 哈希链锚定,复核可定位篡改位置)。裁决确定、可预期、便于测试,适合需要**可审计防越界、确定性可测试治理**的本地多 Agent 编排。护栏事件以收据与事件流文件留痕、可复核(事件级可观测);开关与规则在设置侧边栏「蟛蜞治理配置」页配置(出厂 `rules` 为空即零拦截,按需配置规则后生效)。
19
+ - **热更新配置,免重启**——护栏规则与开关、watch 能力开关(lane 心跳/长跑探针)写入 `runtime.json` 即时生效,进程无需重启;重启时亦按 `runtime.json` 对账生效。
20
20
  - **国标 AIP 兼容**——遵循《人工智能 智能体互联》GB/Z 185-2026 描述结构(工具 6 属性 / 智能体 ACS / 消息任务会话映射),仅增不改、可插拔。
21
21
  - **可选的 ACPs 通讯**——对外 mTLS 服务端点、registry 注册与外部发现,默认全部关闭(安全默认)。
22
22
  - **本地运行,开箱即用**——零云依赖、默认零网络暴露;单一 npm 包内含插件引擎、Punky Swarm 预设与 jiufeng-team 角色指引,附中英双语文档。
@@ -68,6 +68,8 @@ dsh web restart
68
68
  - **批次详情**:lane 状态卡(状态、任务简述、门禁缺件、层与依赖)、事件时间线、收件箱计数;
69
69
  - **只读设计**:3 秒自动刷新,跟随深浅主题;批次与门禁状态只能查看,治理操作由 Leader 通过治理工具完成。
70
70
 
71
+ 设置侧边栏另提供「**蟛蜞治理配置**」页(非只读,可保存):在此配置护栏(`governance.hook` 开关/预设/升级/窄化)与 lane 能力开关(`watch.enabled` / `watch.longrun.enabled`),保存即写入 `runtime.json` 并即时生效、无需重启 dsh;详见 [docs/webui-governance-config.md](docs/webui-governance-config.md)。
72
+
71
73
  ## 配置速览
72
74
 
73
75
  插件配置集中在 `cordis.patch.yml`,关键装配键与默认值:
@@ -81,6 +83,8 @@ dsh web restart
81
83
  | 身份体系(AIC / CAI / 签名) | `aip.identity.enabled` | 关 |
82
84
  | ACPs 通讯(mTLS 端点 / 桥接 / registry / discovery) | `acps.*` | 关(关闭时无监听、无定时器、无网络) |
83
85
 
86
+ > 注:`capabilities.watch` 含 `longrun` 子开关(长跑超时重派探针,出厂默认开,显式 `false` 才关);`watch.enabled` 与 `watch.longrun.enabled` 均可在「蟛蜞治理配置」页热更开关,写入 `runtime.json` 即时生效(见「热更新配置,免重启」与 [docs/webui-governance-config.md](docs/webui-governance-config.md))。
87
+
84
88
  各键语义、配置示例与规则写法见 [docs/governance-technical.md](docs/governance-technical.md) 与 [docs/guardrails-hook.md](docs/guardrails-hook.md)。
85
89
 
86
90
  ### 安全默认
@@ -117,7 +121,8 @@ flowchart LR
117
121
 
118
122
  | 文档 | 内容 |
119
123
  |---|---|
120
- | [docs/governance-technical.md](docs/governance-technical.md) | 批级治理技术手册:三层门禁、状态机、wavePlan 契约、20 治理工具参考、装配键表、生命周期 |
124
+ | [docs/governance-technical.md](docs/governance-technical.md) | 批级治理技术手册:三层门禁、状态机、wavePlan 契约、21 治理工具参考、装配键表、生命周期 |
125
+ | [docs/webui-governance-config.md](docs/webui-governance-config.md) | Web UI「治理配置」页说明:护栏开关/预设与 lane 能力开关(watch)的保存生效口径与行为边界 |
121
126
  | [docs/guardrails-hook.md](docs/guardrails-hook.md) | 调用级护栏技术手册:6 原语运行期语义、规则配置与示例、拒绝收据与验签、热更新、边界与不提供项、能力边界与取舍 |
122
127
  | [docs/aip-compliance.md](docs/aip-compliance.md) | 国标 AIP 兼容明细:工具 6 属性、ACS 字段集、消息/任务/会话映射、身份体系 |
123
128
  | [docs/acps-communication.md](docs/acps-communication.md) | ACPs 通讯明细:mTLS 端点、内部桥接、registry / discovery、配置示例、能力边界 |
package/cordis.patch.yml CHANGED
@@ -31,11 +31,20 @@
31
31
  # intervalsMinutes 退避档位(分钟,默认 [10,20,30],冷场越久追问间隔越长);maxMissed 硬停拍数
32
32
  # (默认 3,连续 N 拍无活动 → appendEvent('lane.stalled') 停止追问,只标记不自动处置);
33
33
  # scanIntervalMinutes watchdog 扫描间隔(默认 1);probeTemplate 可选覆写追问模板({lane}/{batchId}/{missed} 占位)。
34
+ # longrun 子键(长跑档探针,出厂默认开——用户定案修订,非设计默认关):running lane 持续超
35
+ # maxDurationMs(默认 1200000=20min)且近 noProgressWindowMs(默认 300000=5min)无新 checkpoint
36
+ # 且无活动(严格 AND)→ 同 tick 产 lane.longrun.candidate 事件 + mailbox broadcast 候选通知
37
+ # Manager 裁决(探针只标记不改 lane 状态;lane_longrun 工具并列注册,enabled=false 不注册、
38
+ # tick 不判、零事件零消息零行为变化)。
34
39
  watch:
35
40
  enabled: true
41
+ longrun:
42
+ enabled: true # 出厂默认开(定案修订:显式 false 才关)
43
+ maxDurationMs: 1200000 # 长跑超时阈值(默认 20min;正整数 ms,非法回退默认)
44
+ noProgressWindowMs: 300000 # 无进展窗(默认 5min;正整数 ms,非法回退默认)
36
45
  # worktree 物理隔离(lane-tools):enabled=true 时注册 lane_worktree_create / lane_worktree_merge / lane_checkpoint
37
46
  # (git worktree 隔离 + checkpoint 提交;与 lane_claim 逻辑锁互补——lane_claim 管状态层「写谁的」,
38
- # 本组工具管工作区层「写到哪」;全能力默认开 → 工具总数 18(14+heartbeat+worktree 三件+log_export),回归已覆盖)。
47
+ # 本组工具管工作区层「写到哪」;工具总数按 register.js TBD-2 实测口径:裸配置 20 / patch 全开 21(含 lane_longrun 与 lane_checkpoint_status)/ 显式关(worktree+watch 关)14)。
39
48
  worktree:
40
49
  enabled: true
41
50
  # merge agent 装配键:enabled=true 且宿主注入 mergeAgentSpawner 时,
@@ -70,6 +79,14 @@
70
79
  hook:
71
80
  enabled: true # 默认开启;空规则表 → decide 恒 ALLOW → 零行为变化
72
81
  rules: [] # 规则表(空=零拦截);Rule 结构见 lib/governance/types.ts
82
+ # M5-b preset 装载键(preset-build):引用随包预设(presets/hook-rules/ 三 JSON,注册 id 枚举:
83
+ # l1-sensitive / l2-resource / compose——不接受任意路径),保序展开拼接至 rules(inline rules 在后)。
84
+ # preset: 'l1-sensitive' 或 preset: ['l1-sensitive', 'l2-resource'](= compose 逐条等价,二选一互斥引用——
85
+ # 同批引用 compose + l1-sensitive 会产生重复 id → 装载失败回退空表 + warn,宁空勿半)。
86
+ # runtime.json 热更示例:{"governance":{"hook":{"preset":["l1-sensitive","l2-resource"]}}}(⑤ 通道即时生效)。
87
+ # 出厂不默认启用任何 preset(本键缺省 = 空表零拦截不变,与 rules:[] 出厂安全默认严格一致)——
88
+ # 需启用时经 runtime.json 下发或装配 config 注入;preset 文件内容随包版本发布生效(boot 装载一次)。
89
+ # preset: [] # ← 示例注释(默认不启用,勿取消注释即默认值)
73
90
  # M5-a:护栏违规计数升级(默认关——出厂零行为变化;开启后:归属批次的规则拒绝
74
91
  # (DENY/NARROW)在 windowMs 内达 threshold → 经棘轮校验批 paused(reason=governance-escalate);
75
92
  # 与 hook enabled:true 内核就位零拦截正交——出厂 rules:[] 本就零收据,关保持「改规则不意外武装暂停」保守面)
@@ -137,7 +137,10 @@ Tools are grouped by function; registration is controlled by assembly keys (see
137
137
 
138
138
  | Tool | Description |
139
139
  |---|---|
140
- | `lane_heartbeat` | Lane heartbeat query/trigger (watchdog scan, stalled marking) |
140
+ | `lane_heartbeat` | Lane heartbeat query/trigger (watchdog scan, stalled marking; lane omitted → returns all running lanes of the batch) |
141
+ | `lane_longrun` | Lane longrun probe query/trigger (longrun tier: runningSince/duration/no-progress window/candidate state; lane omitted → returns all running lanes of the batch; registered when both the watch and longrun sub-switches are on) |
142
+
143
+ Watch consumption for batches without a Manager (or while the Manager is absent) falls to the Leader: on each worker settlement or confirmed idle, the Leader checks `mailbox_read(broadcast)` for longrun.candidate broadcasts and cross-checks probe state (candidate/emitted/reason) with `lane_longrun` (whole-batch default); a hit candidate is handled as a semi-automatic redispatch — keep observing while the lane has recent checkpoints/activity, stop-and-redispatch or reopen the batch when there is genuinely no progress, and escalate doubtful cases to the user; once a Manager is raised, scheduling returns to the Manager.
141
144
 
142
145
  ### worktree physical isolation
143
146
 
@@ -163,7 +166,7 @@ Assembly is centralized in `cordis.patch.yml`; runtime overrides are covered in
163
166
  | Discovery service (ADP) | `capabilities.discovery` | on | Mounts `POST /api/dsh-punky-swarm/discover` + `GET /.well-known/aip`; nodes can hide per-node with active=false |
164
167
  | Diagnostics bridging | `capabilities.trajectory` | on (autoFail=false) | anomaly diagnosis → sessionId→lane mapping → notify; auto-failed only when autoFail=true (failConfidence threshold) |
165
168
  | Mailbox loop protection | `capabilities.budget` | on (hops=4 / roundTrips=2) | checkBudget before outbox/broadcast sends; inbox (Leader downlink dispatch) never limited |
166
- | Heartbeat/expiry detection | `capabilities.watch` | on | watchdog timer + lane_heartbeat; backoff-tier follow-ups + N consecutive no-activity beats → lane.stalled mark (mark only, no automatic disposition) |
169
+ | Heartbeat/expiry detection | `capabilities.watch` | on | watchdog timer + lane_heartbeat; backoff-tier follow-ups + N consecutive no-activity beats → lane.stalled mark (mark only, no automatic disposition); hot-apply/restart-reconcile surface = 5 keys {`enabled`, `longrun.enabled`, `scanIntervalMinutes`, `longrun.maxDurationMs`, `longrun.noProgressWindowMs`} — longrun thresholds can be set via the governance-config page form (minutes→ms) or runtime.json and take effect on hot-apply/restart |
167
170
  | worktree physical isolation | `capabilities.worktree` | on | lane_worktree_create/merge/checkpoint; complements the lane_claim logical lock |
168
171
  | Acceptance evidence | `capabilities.verify` | on (mode=advisory) | post-execute evidence capture (content-addressed blob + ledger); three-state adjudication (done/failed/blocked); intercepts when mode=enforce |
169
172
  | Log export | `capabilities.logs` | off | log_export tool registration (explicitly enabled by the patch) |
@@ -137,7 +137,10 @@
137
137
 
138
138
  | 工具 | 说明 |
139
139
  |---|---|
140
- | `lane_heartbeat` | lane 心跳查询/触发(watchdog 扫描,stalled 标记) |
140
+ | `lane_heartbeat` | lane 心跳查询/触发(watchdog 扫描,stalled 标记;lane 缺省 → 返回该批全部 running lane) |
141
+ | `lane_longrun` | lane 长跑超时重派探针查询/触发(longrun 档:runningSince/时长/无进展窗/候选状态;lane 缺省 → 返回该批全部 running lane;watch 与 longrun 子开关均开启时注册) |
142
+
143
+ 无 Manager(或 Manager 缺席)批次的 watch 消费由 Leader 兜底承担:每次 worker 结算或确认空闲时,Leader 以 `mailbox_read(broadcast)` 查 longrun.candidate 广播,再以 `lane_longrun` 缺省全批查询核对探针态(candidate/emitted/reason);命中候选按半自动重派处置——近窗有 checkpoint/活动则等待继续观察,确无进展则停轮重派或重开批次,处置存疑则上报用户裁决;批次拉起 Manager 后调度交还 Manager。
141
144
 
142
145
  ### worktree 物理隔离
143
146
 
@@ -163,7 +166,7 @@
163
166
  | 发现服务(ADP) | `capabilities.discovery` | 开 | 挂载 `POST /api/dsh-punky-swarm/discover` + `GET /.well-known/aip`;nodes 可逐节点 active=false 隐藏 |
164
167
  | 诊断桥接 | `capabilities.trajectory` | 开(autoFail=false) | 异常诊断 → sessionId→lane 映射 → notify;autoFail=true 时才自动 failed(failConfidence 阈值) |
165
168
  | mailbox 环防护 | `capabilities.budget` | 开(hops=4 / roundTrips=2) | outbox/broadcast 发送前 checkBudget;inbox(Leader 下行派发)永不受限 |
166
- | 心跳/过期检测 | `capabilities.watch` | 开 | watchdog 定时器 + lane_heartbeat;退避档位追问 + 连续 N 拍无活动 → lane.stalled 标记(只标记不自动处置) |
169
+ | 心跳/过期检测 | `capabilities.watch` | 开 | watchdog 定时器 + lane_heartbeat;退避档位追问 + 连续 N 拍无活动 → lane.stalled 标记(只标记不自动处置);热更/重启对账生效面 5 键 = {`enabled`, `longrun.enabled`, `scanIntervalMinutes`, `longrun.maxDurationMs`, `longrun.noProgressWindowMs`}——长跑阈值可经治理配置页表单(分钟换算 ms)或 runtime.json 写入并生效 |
167
170
  | worktree 物理隔离 | `capabilities.worktree` | 开 | lane_worktree_create/merge/checkpoint;与 lane_claim 逻辑锁互补 |
168
171
  | 验收证据 | `capabilities.verify` | 开(mode=advisory) | post-execute 证据捕获(内容寻址 blob + ledger);三态裁决(done/failed/blocked);mode=enforce 时拦截 |
169
172
  | 日志导出 | `capabilities.logs` | 关 | log_export 工具注册(patch 显式开启) |
@@ -0,0 +1,77 @@
1
+ # Governance Configuration
2
+
3
+ > This guide covers the Governance page under Settings in the dsh Web UI: which guardrail options you can adjust, how changes take effect immediately after saving, and what this page deliberately does not do.
4
+ > 中文: [webui-governance-config.md](webui-governance-config.md)
5
+
6
+ ## What this page does
7
+
8
+ Governance is a dedicated page in the Settings area of the dsh Web UI. It manages a set of guardrail options for dsh running on this machine: the guardrails inspect the tool calls an Agent makes, detect out-of-bounds calls, and then allow, deny, or escalate them according to the rules you have chosen. Changes made on this page take effect **immediately after saving — no dsh restart needed**.
9
+
10
+ Besides the guardrails, this page also provides lane capability switches and time windows: enabling and disabling lane expiry detection (watch, including the longrun timeout re-dispatch probe), and setting the two longrun time windows (timeout / no-progress).
11
+
12
+ At the top of the page you can see the current state: whether the guardrails are live, and how many rules are currently in effect.
13
+
14
+ ## Configurable options
15
+
16
+ ### Guardrail switch
17
+
18
+ - **On**: the guardrails take part in checking, judging out-of-bounds calls against the rule set below.
19
+ - **Off**: the guardrails take no part at all and calls are not held back by this feature.
20
+ - **Factory default: on**. However, the factory rule set is empty, so nothing is actually intercepted (see "Behavior boundaries").
21
+
22
+ ### Rule preset
23
+
24
+ Pick a ready-made rule set to quickly enable a group of out-of-bounds protections:
25
+
26
+ | Option | Number of rules | Purpose |
27
+ |---|---|---|
28
+ | Factory default (no interception) | 0 | No rules enabled; guardrails on but nothing is held back |
29
+ | Sensitive-data guard | 12 | For out-of-bounds calls involving sensitive data such as credentials and private keys |
30
+ | Resource limits | 6 | For calls that exceed resource ceilings such as timeout and concurrency |
31
+ | Combination (L1 + L2) | 18 | The full combination of the two rule sets above |
32
+
33
+ Once you select a preset, the page shows the **count and a purpose summary** for that rule set. Switching presets does not change the running state by itself — it takes effect when you click Save.
34
+
35
+ ### Auto-escalation
36
+
37
+ Off by default. When on, once guardrail refusals for the same batch reach the threshold within the counting window, the batch is automatically paused with a record, waiting for you to review and resume it manually.
38
+
39
+ - **Refusals within window**: how many rule refusals accumulate within one time window before the automatic pause triggers (minimum 1).
40
+ - **Window (s)**: the length of the counting window (default 600 s — 10 minutes; minimum 1). Entered in seconds on the form and stored in milliseconds internally (×1000).
41
+ - **Counted verdicts**: "Deny" and "Narrowed allowance" are counted by default; "Defer" and "Pause" can be added as needed; verdicts that require human approval are not on this list.
42
+
43
+ ### Narrowed allowance
44
+
45
+ Off by default. When enabled, calls that go beyond the allowed bounds are no longer denied outright; instead the guardrails give narrowing guidance so the caller can retry with narrowed parameters. When disabled, such calls are denied outright.
46
+
47
+ ### Lane capability switches (watch)
48
+
49
+ Control lane expiry detection (heartbeat / longrun):
50
+
51
+ - **Lane expiry watch**: the parent switch, controls the watchdog expiry scan over running lanes (heartbeat backoff follow-ups + stalled marking). When off, expiry detection does not run at all.
52
+ - **Long-run timeout probe**: the child switch, controls the longrun candidate probe for lanes making no progress for too long. **On by factory default**; it takes effect only while the parent switch is on (when the parent is off, the child is disabled).
53
+ - **Timeout window (min)**: a lane running longer than this enters longrun judging (default 20, minimum 1, whole minutes).
54
+ - **No-progress window (min)**: a lane under longrun judging with no checkpoint/activity within this window produces a redispatch candidate (default 5, minimum 1, whole minutes).
55
+
56
+ Both switches are on by factory default (`enabled` defaults to on — off only when explicitly `false`). While off, running lanes producing no stalled / longrun candidate events is expected; after re-enabling, scanning resumes from the baseline. The two time windows are entered in minutes on the form and converted to milliseconds when saved (internal keys `maxDurationMs`/`noProgressWindowMs`, defaults 1200000/300000); sub-minute tuning is done by editing the configuration file directly.
57
+
58
+ ## Saving and activation
59
+
60
+ - **Save**: after clicking Save, the settings are written to the local configuration file and then applied to the running dsh immediately — no restart at any point. The page first shows "Saved, confirming…" and turns to "Live" once confirmed.
61
+ - **Activation of the lane capability switches (watch)**: the watch group is saved together with the guardrails (same Save button, merged into `runtime.json`); **any change — either switch or a time window — hot-applies immediately on save** (the watch effective surface is 5 keys: `enabled`/`longrun.enabled`/`scanIntervalMinutes`/`longrun.maxDurationMs`/`longrun.noProgressWindowMs`); after a process restart they are also reconciled against `runtime.json`, so there is no need to set them again.
62
+ - **Reset**: discards your unsaved changes and returns to the state most recently loaded into the page.
63
+ - **Save rejected**: the page shows why the save was rejected; the common reasons are listed in "Behavior boundaries" below.
64
+
65
+ ## Behavior boundaries
66
+
67
+ - **A controlled form — no free-form rule editing**: this page offers only the switches, presets, and numeric options above; there is no entry point for editing rules one by one. Teams that need fully custom rules should maintain the rule list in the configuration file (see the technical manual linked below).
68
+ - **Factory default zero interception is unchanged**: a fresh install ships with the factory default — guardrails on but no rules loaded, so no call is intercepted and existing behavior is unaffected; interception only begins after you select a preset or configure rules manually.
69
+ - **Manual rules must be handled first when they conflict with presets**: if a hand-maintained rule list already exists in the configuration, switching presets is rejected (to avoid overwriting manual rules); remove those rules manually first, or keep the preset unchanged, then save.
70
+ - **Saving is accepted only from this machine (or trusted sources)**: this page only accepts save requests from a browser on this machine (a local address); if dsh is served through a remote address, the deployment must add the visiting source to the trust list (the dsh host and this plugin must stay consistent), otherwise saves are rejected.
71
+ - **Boundaries of the watch switches**: on by factory default — off only when explicitly disabled; turning them off only stops scanning and probe events and never changes already-landed batch/member states. The tool registration surface (whether the heartbeat/longrun query tools appear in the available list) is fixed at startup; while watch is turned off at runtime, the query tools remain available and return a "disabled" state (no error).
72
+ - **watch longrun thresholds are now configurable on this page**: the longrun timeout window and no-progress window can be configured on this page's form (entered in minutes, stored in milliseconds) and hot-apply immediately on save (see "Lane capability switches (watch)" and "Saving and activation" above); the remaining watch-level keys — `scanIntervalMinutes`/`intervalsMinutes`/`maxMissed`/`probeTemplate` — are still maintained manually in `runtime.json`.
73
+
74
+ ## Further reading
75
+
76
+ - [Call-level guardrails technical manual](guardrails-hook.en.md): guardrail mechanics, rule authoring, and runtime details (for maintainers and developers).
77
+ - [Batch governance technical manual](governance-technical.en.md): batches, gates, and state machine (for operators).
@@ -0,0 +1,77 @@
1
+ # 治理配置
2
+
3
+ > 本文介绍 dsh Web UI「设置 → 治理配置」页面:可以调整哪些护栏选项、保存后如何即时生效,以及本页面刻意不做的事。
4
+ > English: [webui-governance-config.en.md](webui-governance-config.en.md)
5
+
6
+ ## 这个页面是什么
7
+
8
+ 「治理配置」是 dsh Web UI 设置区的一个独立页面。它管理 dsh 在本机运行时的一组护栏选项:护栏会检查 Agent 的工具调用是否越界,再按你所选规则决定放行、拒绝或升级处理。页面上的改动保存后**立即生效,无需重启 dsh**。
9
+
10
+ 除护栏外,本页面还提供 lane 能力开关与时间窗口:lane 过期检测(watch,含长跑超时重派探针)的启用与关闭,以及超时/无进展两个长跑时间窗口的设置。
11
+
12
+ 页面顶部显示当前状态:护栏是否已生效,以及当前生效的规则数量。
13
+
14
+ ## 可配置项
15
+
16
+ ### 护栏开关
17
+
18
+ - **打开**:护栏参与检查,依据下方规则集判断越界调用。
19
+ - **关闭**:护栏整体不参与,调用不被本功能拦阻。
20
+ - **出厂默认:打开**。但出厂规则为空,因此实际不拦截任何调用(见「行为边界」)。
21
+
22
+ ### 规则预设
23
+
24
+ 选择一个现成的规则集,快速启用一组越界防护:
25
+
26
+ | 选项 | 规则数量 | 用途 |
27
+ |---|---|---|
28
+ | 出厂空表(零拦截) | 0 | 不启用任何规则;护栏开启但不拦阻 |
29
+ | 敏感数据防护 | 12 | 针对凭据、私钥等敏感数据的越界调用 |
30
+ | 资源上限 | 6 | 针对超时、并发等资源使用上限的调用 |
31
+ | 组合(L1 + L2) | 18 | 以上两套规则集的完整组合 |
32
+
33
+ 选择某个预设后,页面显示该套规则的**数量与用途摘要**。切换预设本身不会立即改变运行状态,需要点击「保存」生效。
34
+
35
+ ### 违规自动升级
36
+
37
+ 出厂关闭。开启后:同一批任务在设定时间窗口内被护栏拒绝达到阈值次数时,自动将该批次暂停并留痕,等待你检查后手动恢复运行。
38
+
39
+ - **窗口内触发次数**:一个时间窗口内累计多少次规则拒绝即触发自动暂停(最小为 1)。
40
+ - **窗口(秒)**:计数的窗口长度(默认 600 秒,即 10 分钟;最小为 1)。表单以秒输入,内部按毫秒存储(×1000)。
41
+ - **计入的处理结果**:默认计入「拒绝」与「收窄放行」,可按需增选「延后」「暂停」;需要人工审批的处理结果不在此列。
42
+
43
+ ### 窄化放行
44
+
45
+ 出厂关闭。打开后,对超出允许范围的调用不再直接拒绝,而是给出收窄指引,让调用方按收窄后的参数重试放行;关闭时此类调用直接拒绝。
46
+
47
+ ### Lane 能力开关(watch)
48
+
49
+ 控制 lane 过期检测(心跳/长跑)的运行:
50
+
51
+ - **Lane 过期检测**:父开关,控制 watchdog 对 running lane 的过期扫描(心跳退避追问 + stalled 标记)。关闭后过期检测整体不运行。
52
+ - **长跑超时重派探针**:子开关,控制对长时间无进展 lane 的长跑候选探测。**出厂默认开**;仅当父开关开启时才生效(父关时子项禁用)。
53
+ - **超时窗口(分钟)**:lane 运行超过该时长即进入长跑判定(默认 20,最小 1,整数分钟)。
54
+ - **无进展窗口(分钟)**:进入长跑判定的 lane 若在该窗口内无 checkpoint/活动,即产出重派候选(默认 5,最小 1,整数分钟)。
55
+
56
+ 两个开关出厂默认均为开(`enabled` 缺省即开,显式 `false` 才关)。关闭期间,running lane 不再产生 stalled / 长跑候选事件属预期;重新开启后自基线恢复扫描。两个时间窗口在表单以分钟输入,保存时换算为毫秒写入配置(内部键 `maxDurationMs`/`noProgressWindowMs`,默认值 1200000/300000);亚分钟级微调请直接编辑配置文件。
57
+
58
+ ## 保存与生效
59
+
60
+ - **保存**:点击后,设置先写入本机配置文件,再即时应用到运行中的 dsh,全程无需重启。页面先显示「已保存,生效确认中」,确认后转为「已生效」。
61
+ - **Lane 能力开关(watch)的生效**:watch 组随护栏一起保存(同一保存按钮,合并写入 `runtime.json`);两个开关与时间窗口**任一变化都保存即热更生效**(watch 生效面共 5 键:`enabled`/`longrun.enabled`/`scanIntervalMinutes`/`longrun.maxDurationMs`/`longrun.noProgressWindowMs`),进程重启后亦按 `runtime.json` 对账生效,无需重复设置。
62
+ - **重置**:放弃本次未保存的修改,回到最近一次载入页面的状态。
63
+ - **保存被拒**:页面会显示被拒原因;常见原因见下节「行为边界」。
64
+
65
+ ## 行为边界
66
+
67
+ - **受控表单,不开放任意规则编辑**:本页面只提供上述开关、预设与数值选项,没有逐条编辑规则的入口。需要逐条定制规则的团队,请在配置文件中维护规则清单(写法见延伸阅读的技术手册)。
68
+ - **出厂默认零拦截不变**:全新安装即出厂空表——护栏开启但不加载任何规则、不拦截任何调用,既有行为不受影响;只有选择预设或手工配置规则后才会开始拦截。
69
+ - **预设与手工规则冲突时先手工处置**:若配置中已存在手工维护的规则清单,切换预设会被拒绝(避免覆盖手工规则);请先手工移除这些规则,或保持预设不变,再保存。
70
+ - **仅本机(或受信任来源)可保存**:本页面只接受来自本机浏览器(本地地址)的保存请求;若 dsh 通过远程地址对外提供服务,部署方须把访问来源加入信任名单(dsh 宿主与本插件保持一致),否则保存会被拒绝。
71
+ - **watch 开关的边界**:出厂默认开,显式关闭才停;关闭只停扫描与探针事件,不改动已落盘的批次/成员状态。工具注册面(心跳/长跑查询工具是否出现在可用列表)在启动时固定;运行期关闭 watch 时查询工具仍可用并返回「已禁用」状态(不报错)。
72
+ - **watch 长跑阈值现可经本页表单配置**:长跑超时窗口与无进展窗口可经本页表单配置(分钟输入,换算毫秒落盘)并随保存即时热更生效(见上文「Lane 能力开关(watch)」「保存与生效」);`scanIntervalMinutes`/`intervalsMinutes`/`maxMissed`/`probeTemplate` 等其余 watch 级键仍走手工 `runtime.json` 维护。
73
+
74
+ ## 延伸阅读
75
+
76
+ - [调用级护栏技术手册](guardrails-hook.md):护栏机制、规则写法与运行细节(面向维护者与开发者)。
77
+ - [批级治理技术手册](governance-technical.md):批次、门禁与状态机(面向 Leader 与运维)。
package/lib/api.js CHANGED
@@ -22,6 +22,9 @@ import * as mailbox from './comms/mailbox.js';
22
22
  import { createStreamHub } from './panel/stream.js';
23
23
  // P1 批 R-07 排除项(承接归 panel 批):事件读端字面量收敛——EVT 常量源 lib/state/event-types.js(P2-07 单点)
24
24
  import * as EVT from './state/event-types.js';
25
+ // WebUI 治理配置写通道(webui-config-build-20260903):/config 端点 trusted 判定(自复刻宿主 /api
26
+ // 护栏语义,conn:184-198 未导出故复刻——插件 exact 路由不经宿主护栏,写端点须自理,见 config-trust.js)
27
+ import { isTrustedConfigRequest } from './webui/config-trust.js';
25
28
 
26
29
  function sendJson(res, status, data) {
27
30
  const body = JSON.stringify(data);
@@ -175,7 +178,7 @@ export function createApi(ctx, deps) {
175
178
 
176
179
  // P4 ACS 智能体描述目录:enabled=true 时 agentCatalog 非空,注册 /agents;
177
180
  // 端点输出 ACS 字段集(AgentCapabilitySpec,逐字字段见 lib/aip/agent-descriptor.js);只读、无参。
178
- // enabled=false(默认)时 agentCatalog 为 null,不注册该路由(既有路由契约不变)。
181
+ // aip.enabled=false(显式关闭——aip 出厂默认开,readCapability 缺省合并 {enabled:true})时 agentCatalog 为 null,不注册该路由(既有路由契约不变)。
179
182
  if (agentCatalog) {
180
183
  register({
181
184
  kind: 'exact',
@@ -217,6 +220,88 @@ export function createApi(ctx, deps) {
217
220
  });
218
221
  }
219
222
 
223
+ // WebUI 治理配置写通道(webui-config-build-20260903,设计 §1.1/§1.2,落点 = discover 段与 stream 段之间):
224
+ // GET + POST /api/dsh-punky-swarm/config —— 配置页页载取数 / 受控字段集保存(写 <root>/config/runtime.json)。
225
+ // 条件注册仿 discovery/agentCatalog(上方 :162-190 注入形态):deps.configEndpoints.runtimeConfig
226
+ // 注入时注册;未注入不注册 → 既有 7 路由/9 路由计数测试零回归(不改既有注册面,disposer 统一回收)。
227
+ // trusted 判定(GET/POST 共用,§1.3):Host loopback/trustedHosts + sec-fetch-site≠cross-site + Origin 同源
228
+ // (isTrustedConfigRequest,lib/webui/config-trust.js;trustedHosts 出厂 [] → loopback-only)。
229
+ // 写逻辑全在 service(lib/webui/runtime-config.js):白名单预检 400 → 读-改-写 → validateOverlay
230
+ // 兜底 500 → tmp+rename 原子写;GET 取数 overlay(磁盘原样)/applied(装配侧解析快照)/presets(注册目录)。
231
+ if (deps.configEndpoints?.runtimeConfig) {
232
+ const cfgEp = deps.configEndpoints;
233
+ const trustedHosts = Array.isArray(cfgEp.trustedHosts) ? cfgEp.trustedHosts : [];
234
+ register({
235
+ kind: 'exact',
236
+ path: '/api/dsh-punky-swarm/config',
237
+ handler(req, res) {
238
+ // trusted 护栏前置(GET/POST 共用;护栏语义非鉴权层、防 DNS-rebinding/跨站——§1.2)
239
+ if (!isTrustedConfigRequest(req, trustedHosts)) {
240
+ const body = req.method === 'POST' ? { ok: false, error: 'forbidden' } : { error: 'forbidden' };
241
+ return sendJson(res, 403, body);
242
+ }
243
+ if (req.method === 'GET') {
244
+ try {
245
+ const overlay = cfgEp.runtimeConfig.readOverlay();
246
+ const gov = overlay && typeof overlay === 'object' && !Array.isArray(overlay)
247
+ && overlay.governance && typeof overlay.governance === 'object' && !Array.isArray(overlay.governance)
248
+ ? overlay.governance : null;
249
+ // watch 段取数(longrun-panel-config-20260905):overlayWatch = 磁盘 capabilities.watch 段原样
250
+ // (无 = null);applied.watch = 装配侧解析生效快照(watchInstalledCfg,经 appliedWatch getter——
251
+ // 未注入时省略该键,旧 harness/旧客户端零感知)。既有 overlay=governance 语义不动。
252
+ const caps = overlay && typeof overlay === 'object' && !Array.isArray(overlay)
253
+ && overlay.capabilities && typeof overlay.capabilities === 'object' && !Array.isArray(overlay.capabilities)
254
+ ? overlay.capabilities : null;
255
+ const overlayWatch = caps && typeof caps.watch === 'object' && !Array.isArray(caps.watch) ? caps.watch : null;
256
+ const applied = typeof cfgEp.applied === 'function' ? cfgEp.applied() : null;
257
+ const appliedWatch = typeof cfgEp.appliedWatch === 'function' ? cfgEp.appliedWatch() : null;
258
+ const presets = typeof cfgEp.presets === 'function' ? (cfgEp.presets() ?? []) : [];
259
+ // overlay = 磁盘 runtime.json governance 段原样(表单未保存改动基准;无 = null);
260
+ // overlayWatch = 磁盘 capabilities.watch 段原样(watch 开关表单基准;无 = null);
261
+ // applied = 装配侧已解析快照(默认补齐 + preset 展开 rules);applied.watch = watch 生效快照
262
+ // ({ enabled, longrun:{enabled}, scanIntervalMinutes }——enabled/longrun.enabled 缺省 true);
263
+ // presets = 注册目录元数据 [{id,count}]
264
+ const appliedOut = { hook: applied };
265
+ if (appliedWatch !== null && appliedWatch !== undefined) appliedOut.watch = appliedWatch;
266
+ sendJson(res, 200, { overlay: gov, overlayWatch, applied: appliedOut, presets });
267
+ } catch (e) { sendJson(res, 500, { error: String(e?.message ?? e) }); }
268
+ return;
269
+ }
270
+ if (req.method === 'POST') {
271
+ let bodyPromise;
272
+ try {
273
+ bodyPromise = readJsonBody(req); // req.body 为 string 时 JSON.parse 同步抛 → 先包住归 400
274
+ } catch (e) {
275
+ return sendJson(res, 400, { ok: false, error: 'invalid-json: ' + String(e?.message ?? e) });
276
+ }
277
+ return bodyPromise.then((payload) => {
278
+ try {
279
+ // POST 按 body 键存在性分派(longrun-panel-config-20260905):含 capabilities 段 → writeWatch
280
+ // (单保存合并 governance + capabilities.watch 双段同 body,Leader 裁决 1——writeWatch 内部
281
+ // 同时处理可选 governance 段,分节校验 + 单次原子写);仅 governance(旧客户端/既有测试契约)
282
+ // → writeGovernance 原路径(错误形态与路由零变化)。
283
+ const hasCaps = payload && typeof payload === 'object' && !Array.isArray(payload) && 'capabilities' in payload;
284
+ const out = hasCaps
285
+ ? cfgEp.runtimeConfig.writeWatch(payload)
286
+ : cfgEp.runtimeConfig.writeGovernance(payload);
287
+ if (!out.ok) {
288
+ if (out.status === 500 || !Array.isArray(out.errors)) {
289
+ return sendJson(res, out.status || 500, { ok: false, error: out.error ?? 'write-rejected' });
290
+ }
291
+ return sendJson(res, 400, { ok: false, errors: out.errors });
292
+ }
293
+ return sendJson(res, 200, { ok: true, written: out.written, ts: new Date().toISOString() });
294
+ } catch (e) {
295
+ // 读-改-写 IO 异常(坏 base JSON / rename 失败等)→ 500 不回写(设计 §1.5「不应发生」面)
296
+ return sendJson(res, 500, { ok: false, error: String(e?.message ?? e) });
297
+ }
298
+ }).catch((e) => sendJson(res, 400, { ok: false, error: 'invalid-json: ' + String(e?.message ?? e) }));
299
+ }
300
+ return sendJson(res, 405, { ok: false, error: 'method-not-allowed' });
301
+ },
302
+ });
303
+ }
304
+
220
305
  // R3 SSE 端点(设计 §3.3.3,纯新增路由——既有 /api 路由一字不动):
221
306
  // GET /api/dsh-punky-swarm/stream?session=<sid>[&batchId=<bid>]
222
307
  // SSE 帧协议:event: batch|mailbox|heartbeat + data:<JSON>;注释心跳帧每 10s(hub 内维护)。