@leonhardthust/dsh-decision-engine 1.11.0 → 1.11.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/docs/releasing.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # npm 发布
2
2
 
3
- 发布目标:`@leonhardthust/dsh-decision-engine`,当前版本 `1.11.0`,公开包。
3
+ 发布目标:`@leonhardthust/dsh-decision-engine`,当前版本 `1.11.2`,公开包。
4
4
 
5
5
  本分支基于 `zuosus/dsh-decision-engine` 的 MIT 代码,保留原作者与许可证;npm scope、源码仓库、bundle 引用和浏览器主模块 / charts chunk 标识已同步为本分支身份。内部 API、样式标识和用户数据目录保留旧名称以兼容已有配置。不要同时启用原包与本 scoped fork,它们提供相同的服务与页面。
6
6
 
@@ -9,7 +9,7 @@
9
9
  - `.github/workflows/ci.yml`:main push、PR、手动运行或发布工作流复用;Ubuntu 24.04 + Node.js 24 + Vite+ 1.6.0。执行 frozen install、完整测试与接线、隔离情景、实际打包和 tarball 检查。失败不会被忽略。
10
10
  - `.github/workflows/publish.yml`:每次 `main` push 自动检测版本。先比较整个 push 的 `before` 与当前 manifest,拒绝错误包名、非法或倒退版本;再只读查询 npmjs。当前版本已上传则跳过;当前版本高于 npmjs latest 且未上传则进入完整验证及发布。多提交 push 比较整个 push 前后,不仅比较 HEAD 的父提交。
11
11
  - 后续发版只需修改 `package.json` 版本并提交到 main;无需创建 tag 或手动点运行。发布依赖检测和本次提交的完整 CI 均成功,不跳过测试。稳定版本发布到 `latest`,预发布版本自动进入 `next`。
12
- - 当前 `1.11.0` 包含第二轮代码审计修复:`cotTranslate` observe 语义与缓存键、`skillPick` 候选校验、`retrieval` 时效性保护、`turnStop` 证据与缺省边界、`delegate` 来源过滤、`compact` 默认不做不可逆裁剪、`route` 机械闸收紧。设计记录见 `DECISIONS.md` 的 DEC-067 ~ DEC-069;`1.10.0` 的五项改动见 DEC-062 ~ DEC-066。
12
+ - 当前 `1.11.2` 包含 DEC-071:审批策略为 `never` 时,模型给出的软性询问(medium / 档位升级)交给 `guardFallback` 复核,而不是静默 deny;高危、注入、未知副作用与强制权限约束仍保持失败关闭。`1.11.1` 的 DEC-070、`1.11.0` 的审计修复、`1.10.0` 的五项改动依次见 DEC-067 ~ DEC-069 与 DEC-062 ~ DEC-066。
13
13
  - checkout 与 setup-vp Action 固定到已核对的 commit SHA;不通过 `pull_request_target` 运行不可信 PR。
14
14
  - 验证任务只有 `contents: read`;只有发布 job 有 `id-token: write`。发布 step 只使用 OIDC(trusted publishing),不再读取任何 npm token secret。代码 checkout 不保留 Git 凭据。此工作流不会自动修改版本、提交源码或推送 Git。
15
15
 
@@ -58,7 +58,7 @@ Windows 全量测试最近仍有 3 个既有 POSIX 文件权限断言失败;
58
58
  授权及完整验证均满足后:
59
59
 
60
60
  ```powershell
61
- npm publish ./release-artifacts/leonhardthust-dsh-decision-engine-1.11.0.tgz --registry "https://registry.npmjs.org/" --access public --tag latest
61
+ npm publish ./release-artifacts/leonhardthust-dsh-decision-engine-1.11.2.tgz --registry "https://registry.npmjs.org/" --access public --tag latest
62
62
  npm view @leonhardthust/dsh-decision-engine version --registry "https://registry.npmjs.org/" --json
63
63
  ```
64
64
 
@@ -66,4 +66,4 @@ npm view @leonhardthust/dsh-decision-engine version --registry "https://registry
66
66
 
67
67
  ## 安装
68
68
 
69
- DSH 插件管理中使用 `@leonhardthust/dsh-decision-engine` 或指定 `@leonhardthust/dsh-decision-engine@1.11.0`。安装和启用是另外的用户动作,发布工作流不会修改本机 DSH profile。
69
+ DSH 插件管理中使用 `@leonhardthust/dsh-decision-engine` 或指定 `@leonhardthust/dsh-decision-engine@1.11.2`。安装和启用是另外的用户动作,发布工作流不会修改本机 DSH profile。
@@ -129,8 +129,29 @@ export async function buildTrustedContext({ agent, ctx, exec, host = {}, downstr
129
129
  const descendants = await subagents.listDescendants(rootId, exec.signal);
130
130
  const member = descendants.find((entry) => entry.kind === "child" && entry.id === targetId && entry.mode === "continuable");
131
131
  const verified = targetId === rootId || Boolean(member);
132
+ /**
133
+ * 「目标是不是本机常驻成员」。
134
+ *
135
+ * 修之前只认 `agents.get(targetId)`——而 `agents` 只登记**活着的** Agent。
136
+ * 于是给一个已空闲、但仍可接续的同一棵树的子代理发消息时:
137
+ * `agents.get()` 返回 undefined → `hostLocal=false` →
138
+ * `downstreamIndependentApproval="unknown"` → mandatory 判定
139
+ * `requires_approval`,用户被问「无法核验接收方权限」。
140
+ *
141
+ * 实测这会让**所有**发给空闲队友的消息都弹审批(甚至审批策略为 never
142
+ * 时被直接拒绝),而它们恰恰是宿主的 `subagents.sendMessage` 明确支持的
143
+ * 「同树、可接续」目标。
144
+ *
145
+ * `listDescendants()` 读的是宿主自己的会话目录:目标出现在这里、且是
146
+ * `continuable`,本身就是比「当前碰巧活着」更强的常驻证据。宿主在投递时
147
+ * 还会再校验一次发送者/目标身份与可恢复性(`subagents.sendMessage` 会
148
+ * 拒绝树外或不可恢复的目标),所以这里用目录成员补齐 hostLocal 不会放宽
149
+ * 任何权限边界:一次性 / 外部子代理仍不在 `verified` 集合里。
150
+ */
132
151
  const resident = targetId === rootId ? root : scopedService("agents")?.get?.(targetId);
133
- const hostLocal = (resident?.id ?? resident?.session?.id) === targetId;
152
+ const hostLocal = targetId === rootId
153
+ || Boolean(member)
154
+ || (resident?.id ?? resident?.session?.id) === targetId;
134
155
  return { id: targetId, membership: verified ? "same-tree" : "unknown", continuable: verified ? true : "unknown", hostLocal, permissions: "unknown", downstreamIndependentApproval: verified && hostLocal && downstreamPolicyEnforced === true ? true : "unknown" };
135
156
  });
136
157
  if (target) {
@@ -22,7 +22,18 @@ export function approvalOutcomeForCall(session, callId, toolName, afterSeq) {
22
22
  const numberOrNull = (value) => Number.isFinite(value) ? value : null;
23
23
  const identityOrNull = (value) => typeof value === "string" && value ? redactGuardData(value).replace(/[\r\n\0]/g, " ").slice(0, 256) : null;
24
24
  const idleEvaluation = () => ({ status: "not_requested", provider: null, model: null, latencyMs: null, reason: null });
25
- const automaticReview = (context, config) => context?.permissionPreset === "auto" || config?.tolerance === "autonomous";
25
+ /**
26
+ * 是否走「自动审查」:把软性询问交给回退审查者,而不是直接拒绝。
27
+ *
28
+ * 这里刻意**不**把 `approvalPolicy === "never"` 也算进来:这个开关同时
29
+ * 决定「模型失效 / 答案畸形 / 审查异常」是否走回退,而那条链路必须保持
30
+ * fail-closed(失败不能变成一次放行)。never 下真正需要改的只是「模型给出
31
+ * 了**有效**意见、结论是 ask」这一种情况——它在下面用 `automaticReview`
32
+ * 之外的分支单独交给 `guardFallback` 处理。
33
+ */
34
+ const automaticReview = (context, config) =>
35
+ context?.permissionPreset === "auto"
36
+ || config?.tolerance === "autonomous";
26
37
 
27
38
  export function mountGuard(ctx, deps) {
28
39
  const { getConfig, reloadOwnConfig, effectivePointModes, client, toleranceCalibrator, digestRecent, previewArguments, logDecision, recordGuardDetail } = deps;
@@ -224,7 +235,31 @@ export function mountGuard(ctx, deps) {
224
235
  } else if (primary.status === "failed") {
225
236
  action = { kind: context.approvalAvailable === true ? "ask" : "deny", reason: "advisory-failed-unknown-effect" };
226
237
  } else if (action.kind === "ask" && context.approvalPolicy === "never") {
227
- action = { ...action, kind: "deny" };
238
+ /**
239
+ * 审批通道不存在(DSH 的 `never` = 每个 ask 确定性 rejected)。
240
+ *
241
+ * 只有「模型给出了有效意见,且这次询问属于**软性**升级」才交给
242
+ * `guardFallback`——那是用户配置好的「没人可问时谁来裁决」。
243
+ *
244
+ * 刻意排除三类,它们必须保持 fail-closed,不能被回退模型洗成放行:
245
+ * - `mandatory.status === "unknown"`:工具副作用没判定,这不是
246
+ * 「软性询问」,而是一条未知边界(现有情景测试专门钉住
247
+ * `manual unknown + never` 不得伪造批准);
248
+ * - `risk.choice === "high"` / 注入达线:高危与注入嫌疑不接受
249
+ * 「再问一个模型」的软化;
250
+ * - 模型失效 / 答案畸形:没有可复核的有效意见(走上面的失败分支)。
251
+ */
252
+ const injectionThreshold = Number.isFinite(config.guardInjectionAskThreshold)
253
+ ? config.guardInjectionAskThreshold
254
+ : 0.8;
255
+ const softAsk = advisory?.status === "valid"
256
+ && mandatory.status !== "unknown"
257
+ && advisory.risk.choice !== "high"
258
+ && !(Number.isFinite(advisory.injectionP) && advisory.injectionP >= injectionThreshold)
259
+ && (advisory.risk.choice === "medium" || tolerated?.changed === true);
260
+ action = softAsk
261
+ ? await runFallback()
262
+ : { ...action, kind: "deny" };
228
263
  }
229
264
  // 档位把「默认放行」升级成「询问」时,`action.reason` 是档位 note(含
230
265
  // `severity=` 与阈值小数)——那是审计口径,不能当审批卡标题。这里换成
@@ -1,6 +1,6 @@
1
1
  const MESSAGES = {
2
2
  "host-tool-access-denied": ["宿主未授权此工具,已阻止调用", "The host has not authorized this tool"],
3
- "recipient-authority-unverified": ["无法核验接收方权限,需要用户确认", "The recipient's authority could not be verified"],
3
+ "recipient-authority-unverified": ["目标不是本机可持续接续的子代理,发消息需要你确认", "The recipient is not a resumable child on this host; sending requires your approval"],
4
4
  "high-impact-requires-explicit-approval": ["此操作涉及提权或破坏性变更,需要用户明确批准", "Privilege escalation or destructive changes require explicit approval"],
5
5
  "write-exceeds-read-only-permission": ["当前为只读权限,写入需要用户明确批准", "Writing exceeds the current read-only permission"],
6
6
  "write-outside-authorized-workspace": ["写入目标超出已授权工作区,需要用户明确批准", "Writing outside the authorized workspace requires explicit approval"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leonhardthust/dsh-decision-engine",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "dsh-decision-engine: a cheap judgement layer for DeepSeek Harness — a small decision model answers structured questions before the main model acts, so calls can be allowed, escalated, rerouted to a cheaper model, or skipped. All eight decision points (tool authorization, model routing, delegation, compaction, turn continuation, retrieval, skill pick, COT translation) are implemented and individually switchable. Works against any service exposing a decision endpoint: built-in presets (system one, bamboonode, SiliconFlow, Silicon Flow) or a custom base URL.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",