@duke-dsh-plugins/dsh-agent-approval 1.8.2 → 1.10.0

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/README.md CHANGED
@@ -23,11 +23,11 @@
23
23
  | 🛡 **权限菜单第四项** | `/permission` 菜单新增 **自动审批** 预设;选中即开启,切到其他预设自动关闭,跨重启保持 |
24
24
  | 🤖 新权限模式 | 开启后:沙箱基线固定 `workspace-write`,审批策略切到 `ask`(内部接管),**不再弹人工审批** |
25
25
  | 🤖 自动裁决(默认 LLM 直连) | 每次提权请求由**一次 LLM 直连调用**裁决(v1.8.0 起默认):与子代理同一套审批人格 / 提示词 / 结构化裁决 `{decision, riskLevel, rationale}`,但**不创建审批子会话**(零上下文污染);设置页可切回「隔离子代理」(一次性 `spawn` 子代理:独立会话、零工具、只读材料) |
26
- | 🕵️ 自动审查(逐调用,实验) | 可选预设(需先在设置页启用 Jev):**Full access 基线**,每个工具调用(含 PTC 内层)执行前经 Jev 判定一次,风险调用**直接拒绝、body 不执行、不转人工**(fail-closed);规则表与会话内信任先行短路降噪;`/agent-review on\|off` 或菜单「自动审查」开启 |
26
+ | 🕵️ 自动审查(逐调用,实验) | 与自动审批**完全独立**的第二种模式(需先在设置页配置 Jev,配置即启用):**Full access 基线**,每个工具调用(含 PTC 内层)执行前经 Jev 判定一次,风险调用**直接拒绝、body 不执行、不转人工**(fail-closed);规则表与会话内信任先行短路降噪;`/agent-review on\|off` 或菜单「自动审查」开启。v1.9.0 起**有效代码展开**:命令引用的解释器脚本(node / python / bash / ps1)内容会被读出并放进裁决 state,agent 临时写的脚本按**实际代码**受审而不是只看命令行(仅读 workspace 内文件,越界只标注、永不读取) |
27
27
  | ⛔ 风险即拒绝 | 破坏性 / 不可逆 / 越界(含修改操作系统或其他应用数据)/ 理由与实际命令不符 → 直接 `reject`;仅"安全、可逆、与任务相符、理由诚实"才 `approve`——项目自身的安装/部署脚本写其文档指定路径属任务所需 |
28
28
  | 🔒 Fail-closed | 审批 Agent 启动失败、超时(可配 30s–600s)、结果不合法 → 一律按拒绝处理,绝不静默放行 |
29
- | ⚙️ 审批模型可配置 | 设置页选择 Provider + Model,不选则固定用 **Harness 默认模型**(不跟随请求会话,口径稳定);选择与超时**持久保存**,重启不丢 |
30
- | ⚡ TypeSafe Jev 决策模型后端 | 审批模型可选 **TypeSafe Jev**(System One 结构化决策模型):审批时直连其 API,用类型化问题(Choice/Noul)毫秒级返回带校准概率的裁决;置信度低于阈值按 fail-closed 处理,审计理由由概率合成(需在设置页填 API Key,或设 `TYPESAFE_API_KEY`) |
29
+ | ⚙️ 审批模型可配置 | 设置页选择 Provider + Model(**只能是 LLM 路由**),不选则固定用 **Harness 默认模型**(不跟随请求会话,口径稳定);选择与超时**持久保存**,重启不丢 |
30
+ | ⚡ TypeSafe Jev 决策模型(仅自动审查) | Jev(System One 结构化决策模型)是**自动审查模式专属**的判定后端:设置页「自动审查」卡片独立配置(API Key / Endpoint / 模型 / 置信度阈值),直连其 API 用类型化问题(Choice/Noul)毫秒级返回带校准概率的裁决;置信度低于阈值按 fail-closed 处理,审计理由由概率合成(也可设 `TYPESAFE_API_KEY`)。**v1.10.0 起不再是自动审批的判定选项**——Jev 的风险判断弱于 LLM,而提权正是人工审批原本要守住的路径 |
31
31
  | 📋 审计记录(随会话) | 会话窗口顶部的**「审批」标签页**(轨迹旁)查看本会话全部审批:结论 / 风险等级 / 模型 / 耗时 / 理由;悬停看完整理由与**精确工具参数**;审批 Agent 的会话 id 可回溯完整推理;已批准行可一键**「加白」**存为放行规则。记录存在**会话存储目录内的独立文件**——随会话恢复,删除会话即随之删除 |
32
32
  | 🔁 可逆开关 | 权限菜单「自动审批」预设、`/agent-approval on\|off` 命令两条等价路径;关闭时**恢复开启前的权限旋钮** |
33
33
 
@@ -42,10 +42,9 @@
42
42
  └─ 本插件 prepend 抢占(先于人工弹窗 answerer)
43
43
  ├─ (默认)LLM 直连裁决(一次 stream 调用 · 同套人格/提示词/裁决格式 · 零子会话)
44
44
  ├─ (可选)spawn 审批 Agent(独立会话 · 零工具 · 结构化裁决 · 不会递归审批)
45
- └─ (或)直连 TypeSafe Jev 决策模型(state + 类型化问题 → 概率化裁决 · 亚秒级)
46
- ├─ approve → allowed-once(该次放行)
47
- ├─ reject → rejected(风险操作,最终拒绝)
48
- └─ 超时/故障/低置信/取消 → fail-closed(按拒绝处理)
45
+ │ ├─ approve → allowed-once(该次放行)
46
+ │ ├─ reject → rejected(风险操作,最终拒绝)
47
+ │ └─ 超时/故障/低置信/取消 → fail-closed(按拒绝处理)
49
48
  └─ 记入审计(会话目录内的独立文件,「审批」标签页可见)
50
49
  ```
51
50
 
@@ -81,15 +80,15 @@ dsh plugin --profile web add https://github.com/MoonlitDropOfBlood/dsh-agent-app
81
80
  1. **开启**:在 `/permission` 菜单选 **自动审批**,或输入 `/agent-approval on`。
82
81
  2. **自动裁决**:之后该会话里的提权请求(例如命令被沙箱拒绝后带 `sandbox_permissions` 的重试)不再弹窗,由审批 Agent 在后台裁决并放行/拒绝。
83
82
  3. **审计**:会话窗口顶部的**「审批」标签页**(轨迹旁)查看本会话的审批记录;悬停"审批理由"看完整理由与工具参数;已批准行可「加白」存为放行规则。记录存在会话存储目录内的独立文件,删除会话即随之删除;v1.4 的旧全局记录用 `node scripts/migrate-records.mjs` 一次性迁移(`--dry-run` 预览)。
84
- 4. **配置**:设置 → **自动审批** 设置审批模型(Harness 默认模型、指定 Provider/Model,或 **TypeSafe Jev**——选 Jev 后在下方卡片填 API Key 并按需调整 Endpoint / 模型版本 / 置信度阈值)、**裁决方式**(LLM 直连 / 隔离子代理)、审批超时与放行/拒绝规则。
85
- 5. **自动审查(可选)**:设置页启用 Jev 后,`/permission` 菜单出现 **自动审查**(或执行 `/agent-review on`)——该会话切到 Full access 基线,**每个工具调用**执行前经 Jev 判定,风险调用直接拒绝(不转人工);关闭恢复原旋钮。
83
+ 4. **配置自动审批**:设置 → **自动审批** 设置审批模型(Harness 默认模型或指定 Provider/Model——**只能是 LLM 路由**)、**裁决方式**(LLM 直连 / 隔离子代理)、审批超时与放行/拒绝规则。
84
+ 5. **配置自动审查(独立)**:设置页「自动审查」卡片填 Jev 的 API Key(可调 Endpoint / 模型版本 / 置信度阈值)——**保存即启用**逐调用审查(新开会话自动进入,菜单出现 **自动审查** 预设,或执行 `/agent-review on`):该会话切到 Full access 基线,**每个工具调用**执行前经 Jev 判定,风险调用直接拒绝(不转人工);关闭恢复原旋钮。
86
85
  6. **关闭**:菜单切回其他预设,或 `/agent-approval off`,恢复开启前的沙箱模式与审批策略。
87
86
 
88
87
  ## 目录结构
89
88
 
90
89
  ```
91
90
  dsh-agent-approval/
92
- ├── index.js # Host 半:AgentApprovalService(审批瀑布抢占 + LLM 直连/子代理/Jev 裁决 + 逐调用审查 + 审计)
91
+ ├── index.js # Host 半:AgentApprovalService(审批瀑布抢占 + LLM 直连/子代理裁决 + Jev 逐调用审查 + 审计)
93
92
  ├── client.js # Client 半:设置页「自动审批」+ 会话「审批」审计标签页 bundle
94
93
  ├── typert.host.js # Typert Host manifest(agentApproval 10 个 Remote 方法的描述)
95
94
  ├── cordis.patch.yml # dsh bundle patch(挂载行 + permission 预设表覆盖)
package/client.js CHANGED
@@ -11,12 +11,16 @@
11
11
  * session — restored with it after a restart, gone when the session is
12
12
  * deleted. Rows offer the one-click「加白」rule shortcut.
13
13
  *
14
- * 2. A "自动审批" page in the Settings panel (`settings.section`):
15
- * approval model picker (provider + model, the harness default, or the
16
- * TypeSafe Jev direct HTTP backend with its API key / endpoint /
17
- * confidence-gate settings), judge timeout setting (fail-closed), the
18
- * list of sessions with the mode enabled (session-list title +
19
- * workspace), and the allow/deny rule table.
14
+ * 2. A "自动审批" page in the Settings panel (`settings.section`). It
15
+ * presents the two modes as SEPARATE blocks since v1.10.0:
16
+ * - 自动审批: approval model picker (harness provider + model, or the
17
+ * harness default), judge invocation mode (direct LLM vs isolated
18
+ * subagent — an LLM either way, Jev is not selectable here), the
19
+ * fail-closed timeout, enabled sessions and the allow/deny rules.
20
+ * - 自动审查: the per-call review mode's own TypeSafe Jev settings
21
+ * (API key / model / endpoint / confidence gate) plus the global
22
+ * "new sessions enter 自动审查" switch. Configuring Jev here is what
23
+ * enables the mode; the approval judge is untouched by it.
20
24
  *
21
25
  * Session-level on/off lives in the /permission menu (the "自动审批"
22
26
  * preset, registered by the package's cordis.patch.yml bundle patch) and the
@@ -106,7 +110,7 @@ window.__ModuleLoader__.load({
106
110
  would bounce the selection back anyway. */
107
111
  [data-dsh-agent-approval-review-gate="off"] [data-dsh-agent-approval-review-item]{display:none}
108
112
  [data-dsh-agent-approval-review-item]::before{content:'';flex:none;width:16px;height:16px;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8 3.2L9.1 5.9L11.8 7L9.1 8.1L8 10.8L6.9 8.1L4.2 7L6.9 5.9Z' fill='black'/%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' stroke='black' stroke-width='1.31831' stroke-linejoin='round'/%3E%3Cpath d='M8 3.2L9.1 5.9L11.8 7L9.1 8.1L8 10.8L6.9 8.1L4.2 7L6.9 5.9Z' fill='black'/%3E%3C/svg%3E") center/contain no-repeat}
109
- .aapr-mode-review{margin-left:6px;font-size:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;padding:0 4px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
113
+ .aapr-mode-review{display:inline-block;margin:2px 0 2px 6px;padding:1px 6px;font-size:10px;line-height:1.5;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);white-space:nowrap;vertical-align:1px}
110
114
  `;
111
115
 
112
116
  // ---- Settings nav icon --------------------------------------------------
@@ -551,8 +555,9 @@ window.__ModuleLoader__.load({
551
555
  const setReviewDefault = reviewDefaultSlot[1];
552
556
  const timeoutSlot = React.useState("");
553
557
  const setTimeoutDraft = timeoutSlot[1];
554
- // Jev backend drafts (edited in the Jev card shown when the judge
555
- // provider is the synthetic "typesafe" entry).
558
+ // Jev backend drafts — the 自动审查 card's own configuration, shown
559
+ // unconditionally since v1.10.0 (it no longer hangs off a judge
560
+ // provider selection).
556
561
  const jevKeySlot = React.useState("");
557
562
  const setJevKey = jevKeySlot[1];
558
563
  const jevEndpointSlot = React.useState("");
@@ -667,9 +672,19 @@ window.__ModuleLoader__.load({
667
672
  }
668
673
  remote
669
674
  .setJevConfig({ apiKey: jevKey, endpoint: jevEndpoint, model: jevModel, confidence: conf })
670
- .then(() => {
675
+ .then((res) => {
676
+ const v = pick(res) || {};
677
+ setReviewGate(v.reviewAvailable === true);
678
+ setReviewDefault(v.reviewDefault === true);
671
679
  refresh();
672
- setNote("Jev 配置已保存");
680
+ // Configuring Jev opens the review gate; the Host turns the
681
+ // per-call review default on at that exact moment (v1.10.0).
682
+ setNote(
683
+ v.reviewAvailable === true
684
+ ? "Jev 配置已保存:自动审查已就绪" +
685
+ (v.reviewDefault === true ? ",新开会话自动进入逐调用审查" : "")
686
+ : "Jev 配置已保存,但 API Key 仍为空:自动审查保持关闭",
687
+ );
673
688
  })
674
689
  .catch((e) => setNote("保存失败:" + (e && e.message ? e.message : String(e))));
675
690
  };
@@ -735,30 +750,22 @@ window.__ModuleLoader__.load({
735
750
  .catch(() => {});
736
751
  };
737
752
 
738
- // The synthetic "typesafe" provider routes judging through the
739
- // TypeSafe Jev HTTP API — it is not part of the harness directory.
753
+ // v1.10.0: the harness model directory is the WHOLE list — Jev is no
754
+ // longer a judge provider (it judges the per-call review mode only),
755
+ // so there is no synthetic "typesafe" entry to prepend.
740
756
  const providerOptions = [{ id: "", name: "默认(Harness 默认模型)" }].concat(
741
- [{ id: "typesafe", name: "TypeSafe Jev(决策模型·直连 API)" }],
742
757
  dir ? dir.providers : [],
743
758
  );
744
- const modelOptions =
745
- provider === "typesafe"
746
- ? [
747
- { provider: "typesafe", id: "jev-latest", name: "jev-latest(跟随最新版本)" },
748
- { provider: "typesafe", id: "jev-1.13.0", name: "jev-1.13.0(锁定版本)" },
749
- ]
750
- : [{ provider: "", id: "", name: "默认(Harness 默认模型)" }].concat(
751
- dir && dir.models ? dir.models.filter((m) => m.provider === provider) : [],
752
- );
759
+ const modelOptions = [{ provider: "", id: "", name: "默认(Harness 默认模型)" }].concat(
760
+ dir && dir.models ? dir.models.filter((m) => m.provider === provider) : [],
761
+ );
753
762
  const defaultHint =
754
- provider === "typesafe"
755
- ? "当前审批判定直连 TypeSafe Jev API,不经过 Harness 模型路由;下方 Jev 配置在该模式下生效。"
756
- : dir && dir.defaultSelection
757
- ? "未配置时使用 Harness 默认模型;当前默认路由:" +
758
- dir.defaultSelection.provider +
759
- " / " +
760
- dir.defaultSelection.model
761
- : "未配置时使用 Harness 默认模型路由";
763
+ dir && dir.defaultSelection
764
+ ? "未配置时使用 Harness 默认模型;当前默认路由:" +
765
+ dir.defaultSelection.provider +
766
+ " / " +
767
+ dir.defaultSelection.model
768
+ : "未配置时使用 Harness 默认模型路由";
762
769
 
763
770
  return h(
764
771
  "div",
@@ -766,13 +773,17 @@ window.__ModuleLoader__.load({
766
773
  h(
767
774
  "div",
768
775
  { className: "aapr-card" },
769
- h("h3", null, "自动审批权限"),
776
+ h("h3", null, "自动审批与自动审查"),
770
777
  h(
771
778
  "div",
772
779
  { className: "aapr-muted" },
773
- "一种新的权限模式:以 workspace-write 为基线沙箱;当工具请求提权(更宽的沙箱)时,由一个独立的审批 Agent 评估风险——安全、可逆、与任务相符的操作自动批准,破坏性、不可逆、越界或理由不符的操作直接拒绝。",
780
+ "两种互相独立的权限模式。",
781
+ h("br", null),
782
+ "① 自动审批:workspace-write 基线;工具请求提权时由 LLM 审批 Agent 自动裁决(v1.10.0 起判定模型只能是 LLM)。在输入框 /permission 菜单选择「自动审批」预设,或执行 /agent-approval on|off 为会话开启。",
774
783
  h("br", null),
775
- "在输入框 /permission 菜单选择「自动审批」预设,或执行命令 /agent-approval on|off 为会话开启;每个会话的审批审计记录在该会话窗口顶部的「审批」标签页(轨迹旁),随会话保存。",
784
+ "② 自动审查:danger-full-access 基线;每个工具调用执行前经 TypeSafe Jev 判定一次(见下方独立卡片)。在 /permission 菜单选择「自动审查」预设,或执行 /agent-review on|off。",
785
+ h("br", null),
786
+ "每个会话的审批审计记录在该会话窗口顶部的「审批」标签页(轨迹旁),随会话保存。",
776
787
  ),
777
788
  note !== "" ? h("div", { className: "aapr-muted" }, note) : null,
778
789
  ),
@@ -794,7 +805,7 @@ window.__ModuleLoader__.load({
794
805
  value: provider,
795
806
  onChange: (e) => {
796
807
  setProvider(e.target.value);
797
- setModel(e.target.value === "typesafe" ? "jev-latest" : "");
808
+ setModel("");
798
809
  },
799
810
  },
800
811
  providerOptions.map((p) =>
@@ -812,7 +823,7 @@ window.__ModuleLoader__.load({
812
823
  className: "aapr-select",
813
824
  value: model,
814
825
  onChange: (e) => setModel(e.target.value),
815
- disabled: dir === null && provider !== "typesafe",
826
+ disabled: dir === null,
816
827
  },
817
828
  modelOptions.map((m) =>
818
829
  h("option", { key: m.provider + "/" + m.id, value: m.id }, m.id === "" ? m.name : m.name + "(" + m.id + ")"),
@@ -822,144 +833,33 @@ window.__ModuleLoader__.load({
822
833
  h(ui.Button, { variant: "primary", size: "sm", onClick: saveModel }, "保存"),
823
834
  ),
824
835
  h("div", { className: "aapr-muted" }, defaultHint),
825
- provider !== "typesafe"
826
- ? h(
827
- "div",
828
- { className: "aapr-row" },
829
- h(
830
- "label",
831
- null,
832
- "裁决方式:",
833
- h(
834
- "select",
835
- {
836
- className: "aapr-select",
837
- value: judgeMode,
838
- onChange: (e) => saveJudgeMode(e.target.value),
839
- },
840
- h("option", { value: "llm" }, "LLM 直连(默认,不创建子会话)"),
841
- h("option", { value: "subagent" }, "隔离子代理(legacy,创建审批子会话)"),
842
- ),
843
- ),
844
- )
845
- : null,
846
- provider !== "typesafe"
847
- ? h(
848
- "div",
849
- { className: "aapr-muted" },
850
- "LLM 直连与隔离子代理是同一套审批人格、提示词与裁决格式({decision, riskLevel, rationale}),仅调用方式不同:前者一次直连模型调用完成裁决、不启动审批子代理(零上下文污染);后者每次裁决创建一个独立子会话(v1.8.0 前的唯一行为)。TypeSafe Jev 后端不受此设置影响。",
851
- )
852
- : null,
853
- ),
854
- provider === "typesafe"
855
- ? h(
856
- "div",
857
- { className: "aapr-card" },
858
- h("h3", null, "TypeSafe Jev 配置"),
859
- h(
860
- "div",
861
- { className: "aapr-muted" },
862
- "Jev 是结构化决策模型(System One):审批时直连 TypeSafe API,不创建审批子会话,毫秒级返回带校准概率的裁决。审计「理由」由概率分布合成(Jev 本身不生成文字);置信度低于阈值时按 fail-closed 处理(记 unavailable,不批准也不记拒绝)。对中文任务上下文的准确率略低于英语。API Key 明文保存在本机 config.json;留空时使用环境变量 TYPESAFE_API_KEY。",
863
- ),
864
- h(
865
- "div",
866
- { className: "aapr-row" },
867
- h(
868
- "label",
869
- null,
870
- "API Key:",
871
- h("input", {
872
- className: "aapr-input",
873
- type: "password",
874
- placeholder: "TYPESAFE_API_KEY",
875
- value: jevKey,
876
- onChange: (e) => setJevKey(e.target.value),
877
- }),
878
- ),
879
- h(
880
- "label",
881
- null,
882
- "模型:",
883
- h("input", {
884
- className: "aapr-input",
885
- placeholder: "jev-latest",
886
- value: jevModel,
887
- onChange: (e) => setJevModel(e.target.value),
888
- }),
889
- ),
890
- ),
891
- h(
892
- "div",
893
- { className: "aapr-row" },
894
- h(
895
- "label",
896
- null,
897
- "Endpoint:",
898
- h("input", {
899
- className: "aapr-input aapr-input-wide",
900
- placeholder: "https://api.typesafe.ai/v1/systemone",
901
- value: jevEndpoint,
902
- onChange: (e) => setJevEndpoint(e.target.value),
903
- }),
904
- ),
905
- h(
906
- "label",
907
- null,
908
- "置信度阈值:",
909
- h("input", {
910
- className: "aapr-input",
911
- type: "number",
912
- step: "0.05",
913
- min: "0.01",
914
- max: "0.99",
915
- value: jevConf,
916
- onChange: (e) => setJevConf(e.target.value),
917
- }),
918
- ),
919
- h(ui.Button, { variant: "primary", size: "sm", onClick: saveJev }, "保存"),
920
- ),
921
- )
922
- : null,
923
- provider === "typesafe"
924
- ? h(
925
- "div",
926
- { className: "aapr-card" },
927
- h("h3", null, "自动审查(逐调用审查)"),
928
- h(
929
- "div",
930
- { className: "aapr-row" },
931
- h(
932
- "label",
933
- null,
934
- "逐调用审查:",
935
- h(
936
- "select",
937
- {
938
- className: "aapr-select",
939
- value: reviewDefault ? "on" : "off",
940
- disabled: state !== null && state.reviewAvailable !== true,
941
- onChange: (e) => saveReviewDefault(e.target.value === "on"),
942
- },
943
- h("option", { value: "on" }, "开(新会话自动审查每个工具调用)"),
944
- h("option", { value: "off" }, "关(新会话按默认预设)"),
945
- ),
946
- ),
947
- ),
836
+ h(
837
+ "div",
838
+ { className: "aapr-row" },
839
+ h(
840
+ "label",
841
+ null,
842
+ "裁决方式:",
948
843
  h(
949
- "div",
950
- { className: "aapr-muted" },
951
- "开启后,新开会话自动进入自动审查:以 danger-full-access 为基线,每个工具调用(含 PTC 内层调用,外层 run_code 传输除外)执行前经 Jev 判定一次——风险调用直接拒绝、body 不执行、不转人工(fail-closed,拒绝即最终结论)。规则表与会话内信任缓存先行短路降噪;命中拒绝规则、Jev 判拒、低置信、超时、网络故障一律拒绝该调用(工具卡片显示 AGENT_REVIEW_DENIED 详情与风险理由)。",
952
- h("br", null),
953
- "已存在的会话不受此开关影响,可用 /permission 菜单「自动审查」或 /agent-review on|off 单独切换。审计逐调用记录在「审批」标签页(工具列标注「逐调用」)。",
954
- h("br", null),
955
- state === null
956
- ? "状态加载中…"
957
- : state.reviewAvailable === true
958
- ? "当前状态:Jev 判定可用。"
959
- : "当前状态:Jev 判定不可用(缺 API Key),开关已禁用,新会话不会自动审查。",
844
+ "select",
845
+ {
846
+ className: "aapr-select",
847
+ value: judgeMode,
848
+ onChange: (e) => saveJudgeMode(e.target.value),
849
+ },
850
+ h("option", { value: "llm" }, "LLM 直连(默认,不创建子会话)"),
851
+ h("option", { value: "subagent" }, "隔离子代理(legacy,创建审批子会话)"),
960
852
  ),
961
- )
962
- : null,
853
+ ),
854
+ ),
855
+ h(
856
+ "div",
857
+ { className: "aapr-muted" },
858
+ "LLM 直连与隔离子代理是同一套审批人格、提示词与裁决格式({decision, riskLevel, rationale}),仅调用方式不同:前者一次直连模型调用完成裁决、不启动审批子代理(零上下文污染);后者每次裁决创建一个独立子会话(v1.8.0 前的唯一行为)。",
859
+ h("br", null),
860
+ "自动审批的判定模型只能是 LLM:TypeSafe Jev 的结构化决策在此处不可选(它的风险判断比 LLM 弱,而这里正是人工审批原本要守住的路径)。Jev 只用于下方的「自动审查」模式,两者互不影响。",
861
+ ),
862
+ ),
963
863
  h(
964
864
  "div",
965
865
  { className: "aapr-card" },
@@ -977,6 +877,110 @@ window.__ModuleLoader__.load({
977
877
  h(ui.Button, { variant: "primary", size: "sm", onClick: saveTimeout }, "保存"),
978
878
  ),
979
879
  ),
880
+ h(
881
+ "div",
882
+ { className: "aapr-card" },
883
+ h("h3", null, "自动审查(逐调用审查)"),
884
+ h(
885
+ "div",
886
+ { className: "aapr-muted" },
887
+ "自动审查是独立的第二种权限模式,与上面的自动审批互不相关:它不看提权,而是以 danger-full-access 为基线,对每个工具调用(含 PTC 内层调用,外层 run_code 传输除外)在执行前用 TypeSafe Jev 判定一次——风险调用直接拒绝、body 不执行、不转人工(fail-closed,拒绝即最终结论)。",
888
+ h("br", null),
889
+ "Jev 是结构化决策模型(System One):直连 TypeSafe API,不创建审批子会话,毫秒级返回带校准概率的裁决。审计「理由」由概率分布合成(Jev 本身不生成文字);置信度低于阈值时按 fail-closed 处理(记 unavailable,不放行也不记拒绝)。对中文任务上下文的准确率略低于英语。API Key 明文保存在本机 config.json;留空时使用环境变量 TYPESAFE_API_KEY。",
890
+ h("br", null),
891
+ "保存一个可用的 API Key 即完成配置,自动审查随之启用(新开会话自动进入);下面同一张卡片可以随时关掉它。",
892
+ ),
893
+ h(
894
+ "div",
895
+ { className: "aapr-row" },
896
+ h(
897
+ "label",
898
+ null,
899
+ "逐调用审查:",
900
+ h(
901
+ "select",
902
+ {
903
+ className: "aapr-select",
904
+ value: reviewDefault ? "on" : "off",
905
+ disabled: state !== null && state.reviewAvailable !== true,
906
+ onChange: (e) => saveReviewDefault(e.target.value === "on"),
907
+ },
908
+ h("option", { value: "on" }, "开(新会话自动审查每个工具调用)"),
909
+ h("option", { value: "off" }, "关(新会话按默认预设)"),
910
+ ),
911
+ ),
912
+ ),
913
+ h(
914
+ "div",
915
+ { className: "aapr-row" },
916
+ h(
917
+ "label",
918
+ null,
919
+ "API Key:",
920
+ h("input", {
921
+ className: "aapr-input",
922
+ type: "password",
923
+ placeholder: "TYPESAFE_API_KEY",
924
+ value: jevKey,
925
+ onChange: (e) => setJevKey(e.target.value),
926
+ }),
927
+ ),
928
+ h(
929
+ "label",
930
+ null,
931
+ "模型:",
932
+ h("input", {
933
+ className: "aapr-input",
934
+ placeholder: "jev-latest",
935
+ value: jevModel,
936
+ onChange: (e) => setJevModel(e.target.value),
937
+ }),
938
+ ),
939
+ ),
940
+ h(
941
+ "div",
942
+ { className: "aapr-row" },
943
+ h(
944
+ "label",
945
+ null,
946
+ "Endpoint:",
947
+ h("input", {
948
+ className: "aapr-input aapr-input-wide",
949
+ placeholder: "https://api.typesafe.ai/v1/systemone",
950
+ value: jevEndpoint,
951
+ onChange: (e) => setJevEndpoint(e.target.value),
952
+ }),
953
+ ),
954
+ h(
955
+ "label",
956
+ null,
957
+ "置信度阈值:",
958
+ h("input", {
959
+ className: "aapr-input",
960
+ type: "number",
961
+ step: "0.05",
962
+ min: "0.01",
963
+ max: "0.99",
964
+ value: jevConf,
965
+ onChange: (e) => setJevConf(e.target.value),
966
+ }),
967
+ ),
968
+ h(ui.Button, { variant: "primary", size: "sm", onClick: saveJev }, "保存"),
969
+ ),
970
+ h(
971
+ "div",
972
+ { className: "aapr-muted" },
973
+ "规则表与会话内信任缓存先行短路降噪;命中拒绝规则、Jev 判拒、低置信、超时、网络故障一律拒绝该调用(工具卡片显示 AGENT_REVIEW_DENIED 详情与风险理由)。",
974
+ h("br", null),
975
+ "已存在的会话不受此开关影响,可用 /permission 菜单「自动审查」或 /agent-review on|off 单独切换。审计逐调用记录在「审批」标签页(工具列标注「逐调用」)。",
976
+ h("br", null),
977
+ state === null
978
+ ? "状态加载中…"
979
+ : state.reviewAvailable === true
980
+ ? "当前状态:Jev 判定可用,自动审查已就绪。"
981
+ : "当前状态:Jev 判定不可用(缺 API Key),开关已禁用,新会话不会自动审查。",
982
+ ),
983
+ ),
980
984
  h(
981
985
  "div",
982
986
  { className: "aapr-card" },