dsh-perm-gate 5.4.2 → 5.4.3
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/CHANGELOG.md +8 -0
- package/dsh.plugin.json +1 -1
- package/lib/client.js +64 -0
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +22 -6
- package/lib/config.js +6 -3
- package/lib/events.js +8 -1
- package/lib/host-llm.d.ts +19 -2
- package/lib/host-llm.js +31 -8
- package/lib/index.js +37 -2
- package/lib/risk.js +5 -7
- package/lib/rule.d.ts +11 -0
- package/lib/rule.js +13 -0
- package/lib/runtime.d.ts +27 -1
- package/lib/runtime.js +40 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 5.4.3 (2026-11-03)
|
|
6
|
+
|
|
7
|
+
- **#6 fail-open 修复**:宿主把 volatile 字段以**引用**交给 `apply`,此前 `...config` 把引用原样展开进运行时选项;引用对象既不等于 `deny` 也不等于 `ask`,运行时两条分支都落空,未命中任何规则的调用直接放过——即使配的是 ask / 默认审查。现在构造前先 `resolveVolatileConfig` 解引用,`coerceRuleAction` 把非三值一律 fail-closed 成 `ask`(规则文档路径同样过一遍),`defaultAction` / `caseInsensitivePaths` 改由 live 钩子读取并纳入热更新 key,改设置后 `reload()` 生效(此前只有首轮播种,改值不生效)。
|
|
8
|
+
- **#7 假 SAFE 修复**:host 审查只采纳 `text-delta`,`reasoning-delta`(模型内心独白)不再拼进待解析文本;`max-tokens` / `length` / `content-filter` 等截断收尾判为失败而不是短答案;`parseRiskVerdict` 删掉裸 `safe` 关键字兜底——“not safe”、半截 JSON、纯文本 `SAFE` 一律 unresolved,留在人工确认上。
|
|
9
|
+
- **#8 host-review 集成修复**:(1) `reasoningEffort` 不再硬编码 `'off'`,改由新 volatile 配置 `classifierEffort` 控制(设置卡新增「推理强度」,留空=不发送、交给宿主模型配置),被宿主拒绝时可选 low/off;(2) `POST /api/dsh-perm-gate/health` 曾用 `{ ...result, ok: true }` 把检查自己给出的 `ok:false` 覆盖成健康,现改为以检查结果为准;(3) host 模式清空 Model 真正回落到会话模型组——`classifierModel` 不再带 schema 默认值,空值不再被当成显式覆盖(custom 端点模式仍在请求时兜底 `deepseek-chat`)。
|
|
10
|
+
|
|
11
|
+
## 5.4.2 (2026-11-02)
|
|
12
|
+
|
|
5
13
|
- **拦截意见不再冒充用户消息**:被拦后给主模型的 `工具审核意见` 曾以 `mode:'queue'` 投到 next-turn——那是用户的输入队列,意见投进去会在 input-traffic 等待区里像用户自己排的一条,并与待发消息抢队列,且要等整个回合结束才送达(模型多反刍一轮)。现在交由 `buildBlockNoteSender` 投递:首选 `agent.inject()`(next-step 带内、不唤醒 driver,贴着被拒的 tool 结果在下一个 pre-step 送达)并标上自有 `source.kind='perm-gate'`,因为宿主的语义里只有 `source.kind==='user'`(带 `rpcId`)才是“人说的话”,其余一律按注入上下文渲染;没有活 agent 时才回退到 steer 车道(`session.prompt mode:'steer'`),绝不静默降级成 `followup`。yolo / 机械直通 / 降级审查 / 确定性 deny 四条拦截路径统一如此;评审页「撤销此改动」仍走 next-turn(那是人想发的一句话)。
|
|
6
14
|
- 配套:`dsh-input-traffic` 同步把「插话中」限定为真人插话(`source.kind === 'user'`),宿主自有通知(如 `user-approval` 的审批策略变更)与第三方意见不再占位等待区;没带 `source` 的旧行仍照常显示,不会误伤真队列。
|
|
7
15
|
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-perm-gate",
|
|
3
3
|
"name": "dsh-perm-gate",
|
|
4
|
-
"version": "5.4.
|
|
4
|
+
"version": "5.4.3",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. Credential handling is a seven-family ladder (ssh/cloud/package/gitcreds/env/cli/db) with deny/ask/readonly/readwrite rungs kept in the rules document plus a paired write-exemption list; a protected-branch push switch keeps the P0 floor by default. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
|
|
7
7
|
"zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。凭据处理为七族阶梯(ssh/cloud/package/gitcreds/env/cli/db)× deny/ask/readonly/readwrite 四档,档位存规则文档 + 成对写豁免清单;保护分支强推开关默认保留 P0 地板。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
|
package/lib/client.js
CHANGED
|
@@ -78,6 +78,9 @@ window.__ModuleLoader__.load({
|
|
|
78
78
|
"card.llmReceiver": "llmAssist 接收 LLM(OpenAI 兼容,可用自定义 API)",
|
|
79
79
|
"card.llmEndpoint": "Endpoint(BaseURL,如 https://api.openai.com/v1)",
|
|
80
80
|
"card.llmModel": "Model(模型 id,如 deepseek-chat)",
|
|
81
|
+
"card.llmEffort": "推理强度(仅 host 模式)",
|
|
82
|
+
"card.llmEffortHost": "跟随宿主默认",
|
|
83
|
+
"card.llmEffortHint": "留空=不干预,由宿主模型配置决定;被宿主拒绝时改 low 或 off。",
|
|
81
84
|
"card.llmKey": "API Key(保密)",
|
|
82
85
|
"card.llmKeyHidden": "未设置 · 首次输入后以 * 显示",
|
|
83
86
|
"card.llmKeyMasked": "已设置 · 输入新值覆盖",
|
|
@@ -277,6 +280,9 @@ window.__ModuleLoader__.load({
|
|
|
277
280
|
"card.llmReceiver": "llmAssist receiving LLM (OpenAI-compatible; any custom API)",
|
|
278
281
|
"card.llmEndpoint": "Endpoint (BaseURL, e.g. https://api.openai.com/v1)",
|
|
279
282
|
"card.llmModel": "Model (model id, e.g. deepseek-chat)",
|
|
283
|
+
"card.llmEffort": "Reasoning effort (host mode only)",
|
|
284
|
+
"card.llmEffortHost": "Follow the host default",
|
|
285
|
+
"card.llmEffortHint": "Empty = send no effort and let the host model config decide; switch to low or off if the host rejects it.",
|
|
280
286
|
"card.llmKey": "API Key (secret)",
|
|
281
287
|
"card.llmKeyHidden": "Unset · shown as * after first entry",
|
|
282
288
|
"card.llmKeyMasked": "Set · type a new value to overwrite",
|
|
@@ -476,6 +482,9 @@ window.__ModuleLoader__.load({
|
|
|
476
482
|
"card.llmReceiver": "llmAssist 受信 LLM(OpenAI 互換、カスタム API 可)",
|
|
477
483
|
"card.llmEndpoint": "Endpoint(BaseURL、例:https://api.openai.com/v1)",
|
|
478
484
|
"card.llmModel": "Model(モデル id、例:deepseek-chat)",
|
|
485
|
+
"card.llmEffort": "推論強度(host モードのみ)",
|
|
486
|
+
"card.llmEffortHost": "ホスト既定に従う",
|
|
487
|
+
"card.llmEffortHint": "空 = 指定せずホストのモデル設定に任せる。ホストに拒否されたら low か off に変更。",
|
|
479
488
|
"card.llmKey": "API Key(機密)",
|
|
480
489
|
"card.llmKeyHidden": "未設定・初回入力後は * 表示",
|
|
481
490
|
"card.llmKeyMasked": "設定済み・新しい値を入力すると上書き",
|
|
@@ -675,6 +684,9 @@ window.__ModuleLoader__.load({
|
|
|
675
684
|
"card.llmReceiver": "llmAssist 수신 LLM (OpenAI 호환, 사용자 지정 API 가능)",
|
|
676
685
|
"card.llmEndpoint": "Endpoint (BaseURL, 예: https://api.openai.com/v1)",
|
|
677
686
|
"card.llmModel": "Model (모델 id, 예: deepseek-chat)",
|
|
687
|
+
"card.llmEffort": "추론 강도 (host 모드 전용)",
|
|
688
|
+
"card.llmEffortHost": "호스트 기본값 따름",
|
|
689
|
+
"card.llmEffortHint": "비움 = 지정하지 않고 호스트 모델 설정을 따름. 호스트가 거부하면 low 나 off 로 변경.",
|
|
678
690
|
"card.llmKey": "API Key (비밀)",
|
|
679
691
|
"card.llmKeyHidden": "미설정 · 최초 입력 후 * 표시",
|
|
680
692
|
"card.llmKeyMasked": "설정됨 · 새 값을 입력하면 덮어씀",
|
|
@@ -874,6 +886,9 @@ window.__ModuleLoader__.load({
|
|
|
874
886
|
"card.llmReceiver": "LLM récepteur llmAssist (compatible OpenAI ; API personnalisée possible)",
|
|
875
887
|
"card.llmEndpoint": "Endpoint (BaseURL, ex. https://api.openai.com/v1)",
|
|
876
888
|
"card.llmModel": "Model (identifiant du modèle, ex. deepseek-chat)",
|
|
889
|
+
"card.llmEffort": "Effort de raisonnement (mode host)",
|
|
890
|
+
"card.llmEffortHost": "Suivre l’hôte",
|
|
891
|
+
"card.llmEffortHint": "Vide = ne rien imposer, la config du modèle hôte décide ; passer à low ou off si l’hôte refuse.",
|
|
877
892
|
"card.llmKey": "API Key (secret)",
|
|
878
893
|
"card.llmKeyHidden": "Non défini · affiché comme * après la première saisie",
|
|
879
894
|
"card.llmKeyMasked": "Défini · saisir une nouvelle valeur pour écraser",
|
|
@@ -1073,6 +1088,9 @@ window.__ModuleLoader__.load({
|
|
|
1073
1088
|
"card.llmReceiver": "llmAssist empfangendes LLM (OpenAI-kompatibel; jede benutzerdefinierte API)",
|
|
1074
1089
|
"card.llmEndpoint": "Endpoint (BaseURL, z. B. https://api.openai.com/v1)",
|
|
1075
1090
|
"card.llmModel": "Model (Modell-ID, z. B. deepseek-chat)",
|
|
1091
|
+
"card.llmEffort": "Reasoning-Aufwand (nur Host-Modus)",
|
|
1092
|
+
"card.llmEffortHost": "Host-Standard folgen",
|
|
1093
|
+
"card.llmEffortHint": "Leer = nichts vorgeben, die Host-Modellkonfiguration entscheidet; auf low oder off wechseln, wenn der Host es ablehnt.",
|
|
1076
1094
|
"card.llmKey": "API Key (geheim)",
|
|
1077
1095
|
"card.llmKeyHidden": "Nicht gesetzt · nach erster Eingabe als * angezeigt",
|
|
1078
1096
|
"card.llmKeyMasked": "Gesetzt · neuen Wert eingeben zum Überschreiben",
|
|
@@ -1272,6 +1290,9 @@ window.__ModuleLoader__.load({
|
|
|
1272
1290
|
"card.llmReceiver": "LLM ricevente llmAssist (compatibile OpenAI; qualsiasi API personalizzata)",
|
|
1273
1291
|
"card.llmEndpoint": "Endpoint (BaseURL, es. https://api.openai.com/v1)",
|
|
1274
1292
|
"card.llmModel": "Model (ID modello, es. deepseek-chat)",
|
|
1293
|
+
"card.llmEffort": "Sforzo di ragionamento (solo modalità host)",
|
|
1294
|
+
"card.llmEffortHost": "Segui l’host",
|
|
1295
|
+
"card.llmEffortHint": "Vuoto = non imporre nulla, decide la configurazione del modello host; passa a low o off se l’host lo rifiuta.",
|
|
1275
1296
|
"card.llmKey": "API Key (segreta)",
|
|
1276
1297
|
"card.llmKeyHidden": "Non impostata · mostrata come * dopo il primo inserimento",
|
|
1277
1298
|
"card.llmKeyMasked": "Impostata · inserisci un nuovo valore per sovrascrivere",
|
|
@@ -1471,6 +1492,9 @@ window.__ModuleLoader__.load({
|
|
|
1471
1492
|
"card.llmReceiver": "Принимающая LLM для llmAssist (совместимая с OpenAI; любой пользовательский API)",
|
|
1472
1493
|
"card.llmEndpoint": "Endpoint (BaseURL, напр. https://api.openai.com/v1)",
|
|
1473
1494
|
"card.llmModel": "Model (идентификатор модели, напр. deepseek-chat)",
|
|
1495
|
+
"card.llmEffort": "Усилие рассуждения (только режим host)",
|
|
1496
|
+
"card.llmEffortHost": "Как у хоста",
|
|
1497
|
+
"card.llmEffortHint": "Пусто = не навязывать, решает конфигурация модели хоста; выберите low или off, если хост отклоняет.",
|
|
1474
1498
|
"card.llmKey": "API Key (секрет)",
|
|
1475
1499
|
"card.llmKeyHidden": "Не задан · после первого ввода отображается как *",
|
|
1476
1500
|
"card.llmKeyMasked": "Задан · введите новое значение для перезаписи",
|
|
@@ -1670,6 +1694,9 @@ window.__ModuleLoader__.load({
|
|
|
1670
1694
|
"card.llmReceiver": "LLM receptor de llmAssist (compatible con OpenAI; cualquier API personalizada)",
|
|
1671
1695
|
"card.llmEndpoint": "Endpoint (BaseURL, ej. https://api.openai.com/v1)",
|
|
1672
1696
|
"card.llmModel": "Model (id del modelo, ej. deepseek-chat)",
|
|
1697
|
+
"card.llmEffort": "Esfuerzo de razonamiento (solo modo host)",
|
|
1698
|
+
"card.llmEffortHost": "Seguir el host",
|
|
1699
|
+
"card.llmEffortHint": "Vacío = no imponer nada, decide la configuración del modelo del host; cambia a low u off si el host lo rechaza.",
|
|
1673
1700
|
"card.llmKey": "API Key (secreta)",
|
|
1674
1701
|
"card.llmKeyHidden": "No establecida · se muestra como * tras la primera entrada",
|
|
1675
1702
|
"card.llmKeyMasked": "Establecida · introduce un nuevo valor para sobrescribir",
|
|
@@ -3165,6 +3192,43 @@ window.__ModuleLoader__.load({
|
|
|
3165
3192
|
})
|
|
3166
3193
|
]
|
|
3167
3194
|
}),
|
|
3195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3196
|
+
style: fieldStyle,
|
|
3197
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3198
|
+
style: fieldLabelStyle,
|
|
3199
|
+
children: t("card.llmEffort")
|
|
3200
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3201
|
+
value: value.classifierEffort ?? "",
|
|
3202
|
+
disabled: readonly || receiverSource !== "host",
|
|
3203
|
+
style: controlStyle,
|
|
3204
|
+
title: t("card.llmEffortHint"),
|
|
3205
|
+
onChange: (event) => {
|
|
3206
|
+
scope.set("classifierEffort", event.currentTarget.value);
|
|
3207
|
+
},
|
|
3208
|
+
children: [
|
|
3209
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3210
|
+
value: "",
|
|
3211
|
+
children: t("card.llmEffortHost")
|
|
3212
|
+
}),
|
|
3213
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3214
|
+
value: "off",
|
|
3215
|
+
children: "off"
|
|
3216
|
+
}),
|
|
3217
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3218
|
+
value: "low",
|
|
3219
|
+
children: "low"
|
|
3220
|
+
}),
|
|
3221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3222
|
+
value: "medium",
|
|
3223
|
+
children: "medium"
|
|
3224
|
+
}),
|
|
3225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3226
|
+
value: "high",
|
|
3227
|
+
children: "high"
|
|
3228
|
+
})
|
|
3229
|
+
]
|
|
3230
|
+
})]
|
|
3231
|
+
}),
|
|
3168
3232
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3169
3233
|
style: fieldStyle,
|
|
3170
3234
|
children: [
|