@duke-dsh-plugins/dsh-agent-approval 1.5.0 → 1.6.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
@@ -26,6 +26,7 @@
26
26
  | ⛔ 风险即拒绝 | 破坏性 / 不可逆 / 越界(含修改操作系统或其他应用数据)/ 理由与实际命令不符 → 直接 `reject`;仅"安全、可逆、与任务相符、理由诚实"才 `approve`——项目自身的安装/部署脚本写其文档指定路径属任务所需 |
27
27
  | 🔒 Fail-closed | 审批 Agent 启动失败、超时(可配 30s–600s)、结果不合法 → 一律按拒绝处理,绝不静默放行 |
28
28
  | ⚙️ 审批模型可配置 | 设置页选择 Provider + Model,不选则固定用 **Harness 默认模型**(不跟随请求会话,口径稳定);选择与超时**持久保存**,重启不丢 |
29
+ | ⚡ TypeSafe Jev 决策模型后端 | 审批模型可选 **TypeSafe Jev**(System One 结构化决策模型):审批时直连其 API,用类型化问题(Choice/Noul)毫秒级返回带校准概率的裁决;置信度低于阈值按 fail-closed 处理,审计理由由概率合成(需在设置页填 API Key,或设 `TYPESAFE_API_KEY`) |
29
30
  | 📋 审计记录(随会话) | 会话窗口顶部的**「审批」标签页**(轨迹旁)查看本会话全部审批:结论 / 风险等级 / 模型 / 耗时 / 理由;悬停看完整理由与**精确工具参数**;审批 Agent 的会话 id 可回溯完整推理;已批准行可一键**「加白」**存为放行规则。记录存在**会话存储目录内的独立文件**——随会话恢复,删除会话即随之删除 |
30
31
  | 🔁 可逆开关 | 权限菜单「Agent 审批」预设、`/agent-approval on\|off` 命令两条等价路径;关闭时**恢复开启前的权限旋钮** |
31
32
 
@@ -38,11 +39,12 @@
38
39
  工具请求提权(sandbox_permissions / 人工 ask)
39
40
  └─ ctx.approval.request() → approval/request 瀑布
40
41
  └─ 本插件 prepend 抢占(先于人工弹窗 answerer)
41
- └─ spawn 审批 Agent(独立会话 · 零工具 · 结构化裁决 · 不会递归审批)
42
+ ├─ (默认)spawn 审批 Agent(独立会话 · 零工具 · 结构化裁决 · 不会递归审批)
43
+ └─ (或)直连 TypeSafe Jev 决策模型(state + 类型化问题 → 概率化裁决 · 亚秒级)
42
44
  ├─ approve → allowed-once(该次放行)
43
45
  ├─ reject → rejected(风险操作,最终拒绝)
44
- └─ 超时/故障/取消 → fail-closed(按拒绝处理)
45
- └─ 记入审计(写入会话日志,「审批」标签页可见)
46
+ └─ 超时/故障/低置信/取消 → fail-closed(按拒绝处理)
47
+ └─ 记入审计(会话目录内的独立文件,「审批」标签页可见)
46
48
  ```
47
49
 
48
50
  - 审批 Agent 只能看到:workspace 路径、**最近的用户消息**(任务上下文)、工具名、提权理由、**精确的工具参数 JSON**(按 `callId` 从会话日志回查)。裁决看"操作 vs 用户任务"的客观对齐,不依赖理由措辞。
@@ -60,7 +62,7 @@
60
62
  dsh plugin --profile web add /path/to/dsh-agent-approval
61
63
 
62
64
  # 正式发布:从 GitHub Release tarball 安装
63
- dsh plugin --profile web add https://github.com/MoonlitDropOfBlood/dsh-agent-approval/releases/download/v1.5.0/dsh-agent-approval-1.5.0.tgz
65
+ dsh plugin --profile web add https://github.com/MoonlitDropOfBlood/dsh-agent-approval/releases/download/v1.6.0/dsh-agent-approval-1.6.0.tgz
64
66
  ```
65
67
 
66
68
  重启 DSH 后:设置面板出现 **Agent 审批** 页;`/permission` 菜单出现第四项 **Agent 审批**。
@@ -77,7 +79,7 @@ dsh plugin --profile web add https://github.com/MoonlitDropOfBlood/dsh-agent-app
77
79
  1. **开启**:在 `/permission` 菜单选 **Agent 审批**,或输入 `/agent-approval on`。
78
80
  2. **自动裁决**:之后该会话里的提权请求(例如命令被沙箱拒绝后带 `sandbox_permissions` 的重试)不再弹窗,由审批 Agent 在后台裁决并放行/拒绝。
79
81
  3. **审计**:会话窗口顶部的**「审批」标签页**(轨迹旁)查看本会话的审批记录;悬停"审批理由"看完整理由与工具参数;已批准行可「加白」存为放行规则。记录存在会话存储目录内的独立文件,删除会话即随之删除;v1.4 的旧全局记录用 `node scripts/migrate-records.mjs` 一次性迁移(`--dry-run` 预览)。
80
- 4. **配置**:设置 → **Agent 审批** 设置审批模型(不选则用 Harness 默认模型)、审批超时与放行/拒绝规则。
82
+ 4. **配置**:设置 → **Agent 审批** 设置审批模型(Harness 默认模型、指定 Provider/Model,或 **TypeSafe Jev**——选 Jev 后在下方卡片填 API Key 并按需调整 Endpoint / 模型版本 / 置信度阈值)、审批超时与放行/拒绝规则。
81
83
  5. **关闭**:菜单切回其他预设,或 `/agent-approval off`,恢复开启前的沙箱模式与审批策略。
82
84
 
83
85
  ## 目录结构
package/client.js CHANGED
@@ -12,10 +12,11 @@
12
12
  * deleted. Rows offer the one-click「加白」rule shortcut.
13
13
  *
14
14
  * 2. A "Agent 审批" page in the Settings panel (`settings.section`):
15
- * approval model picker (provider + model, or the harness default),
16
- * judge timeout setting (fail-closed), the list of sessions with the
17
- * mode enabled (session-list title + workspace), and the allow/deny
18
- * rule table.
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.
19
20
  *
20
21
  * Session-level on/off lives in the /permission menu (the "Agent 审批"
21
22
  * preset, registered by the package's cordis.patch.yml bundle patch) and the
@@ -256,6 +257,15 @@ window.__ModuleLoader__.load({
256
257
  parameters: param("dsh-agent-approval#AgentApprovalSetModelRequest"),
257
258
  result: result("dsh-agent-approval#AgentApprovalSetModelResult"),
258
259
  },
260
+ {
261
+ id: "dsh-agent-approval#agentApproval/setJevConfig",
262
+ service: "agentApproval",
263
+ namespace: "agentApproval",
264
+ method: "setJevConfig",
265
+ invocation: { kind: "direct" },
266
+ parameters: param("dsh-agent-approval#AgentApprovalSetJevRequest"),
267
+ result: result("dsh-agent-approval#AgentApprovalSetJevResult"),
268
+ },
259
269
  {
260
270
  id: "dsh-agent-approval#agentApproval/setApprovalTimeout",
261
271
  service: "agentApproval",
@@ -413,6 +423,16 @@ window.__ModuleLoader__.load({
413
423
  const setModel = modelSlot[1];
414
424
  const timeoutSlot = React.useState("");
415
425
  const setTimeoutDraft = timeoutSlot[1];
426
+ // Jev backend drafts (edited in the Jev card shown when the judge
427
+ // provider is the synthetic "typesafe" entry).
428
+ const jevKeySlot = React.useState("");
429
+ const setJevKey = jevKeySlot[1];
430
+ const jevEndpointSlot = React.useState("");
431
+ const setJevEndpoint = jevEndpointSlot[1];
432
+ const jevModelSlot = React.useState("");
433
+ const setJevModel = jevModelSlot[1];
434
+ const jevConfSlot = React.useState("0.5");
435
+ const setJevConf = jevConfSlot[1];
416
436
  const noteSlot = React.useState("");
417
437
  const note = noteSlot[0];
418
438
  const setNote = noteSlot[1];
@@ -431,6 +451,11 @@ window.__ModuleLoader__.load({
431
451
  setProvider(s.model.provider);
432
452
  setModel(s.model.model);
433
453
  setTimeoutDraft(String(s.timeoutMs));
454
+ // A not-yet-restarted old host sends no `jev` field — keep drafts.
455
+ setJevKey(s.jev && typeof s.jev.apiKey === "string" ? s.jev.apiKey : "");
456
+ setJevEndpoint(s.jev && typeof s.jev.endpoint === "string" ? s.jev.endpoint : "");
457
+ setJevModel(s.jev && typeof s.jev.model === "string" ? s.jev.model : "");
458
+ setJevConf(s.jev && typeof s.jev.confidence === "number" ? String(s.jev.confidence) : "0.5");
434
459
  })
435
460
  .catch((e) => setNote("无法读取状态:" + (e && e.message ? e.message : String(e))));
436
461
  };
@@ -458,6 +483,28 @@ window.__ModuleLoader__.load({
458
483
  })
459
484
  .catch((e) => setNote("保存失败:" + (e && e.message ? e.message : String(e))));
460
485
  };
486
+ const jevKey = jevKeySlot[0];
487
+ const jevEndpoint = jevEndpointSlot[0];
488
+ const jevModel = jevModelSlot[0];
489
+ const jevConf = jevConfSlot[0];
490
+ const saveJev = () => {
491
+ if (typeof remote.setJevConfig !== "function") {
492
+ setNote("Host 半未更新(缺少 setJevConfig):请重装本插件并重启 DSH。");
493
+ return;
494
+ }
495
+ const conf = Number(jevConf);
496
+ if (!Number.isFinite(conf) || conf <= 0 || conf >= 1) {
497
+ setNote("置信度阈值必须是 0–1 之间的小数(如 0.5)");
498
+ return;
499
+ }
500
+ remote
501
+ .setJevConfig({ apiKey: jevKey, endpoint: jevEndpoint, model: jevModel, confidence: conf })
502
+ .then(() => {
503
+ refresh();
504
+ setNote("Jev 配置已保存");
505
+ })
506
+ .catch((e) => setNote("保存失败:" + (e && e.message ? e.message : String(e))));
507
+ };
461
508
  const saveTimeout = () => {
462
509
  const parsed = Number(timeoutDraft);
463
510
  if (!Number.isFinite(parsed)) {
@@ -520,19 +567,30 @@ window.__ModuleLoader__.load({
520
567
  .catch(() => {});
521
568
  };
522
569
 
570
+ // The synthetic "typesafe" provider routes judging through the
571
+ // TypeSafe Jev HTTP API — it is not part of the harness directory.
523
572
  const providerOptions = [{ id: "", name: "默认(Harness 默认模型)" }].concat(
573
+ [{ id: "typesafe", name: "TypeSafe Jev(决策模型·直连 API)" }],
524
574
  dir ? dir.providers : [],
525
575
  );
526
- const modelOptions = [{ provider: "", id: "", name: "默认(Harness 默认模型)" }].concat(
527
- dir && dir.models ? dir.models.filter((m) => m.provider === provider) : [],
528
- );
576
+ const modelOptions =
577
+ provider === "typesafe"
578
+ ? [
579
+ { provider: "typesafe", id: "jev-latest", name: "jev-latest(跟随最新版本)" },
580
+ { provider: "typesafe", id: "jev-1.13.0", name: "jev-1.13.0(锁定版本)" },
581
+ ]
582
+ : [{ provider: "", id: "", name: "默认(Harness 默认模型)" }].concat(
583
+ dir && dir.models ? dir.models.filter((m) => m.provider === provider) : [],
584
+ );
529
585
  const defaultHint =
530
- dir && dir.defaultSelection
531
- ? "未配置时使用 Harness 默认模型;当前默认路由:" +
532
- dir.defaultSelection.provider +
533
- " / " +
534
- dir.defaultSelection.model
535
- : "未配置时使用 Harness 默认模型路由";
586
+ provider === "typesafe"
587
+ ? "当前审批判定直连 TypeSafe Jev API,不经过 Harness 模型路由;下方 Jev 配置在该模式下生效。"
588
+ : dir && dir.defaultSelection
589
+ ? "未配置时使用 Harness 默认模型;当前默认路由:" +
590
+ dir.defaultSelection.provider +
591
+ " / " +
592
+ dir.defaultSelection.model
593
+ : "未配置时使用 Harness 默认模型路由";
536
594
 
537
595
  return h(
538
596
  "div",
@@ -568,7 +626,7 @@ window.__ModuleLoader__.load({
568
626
  value: provider,
569
627
  onChange: (e) => {
570
628
  setProvider(e.target.value);
571
- setModel("");
629
+ setModel(e.target.value === "typesafe" ? "jev-latest" : "");
572
630
  },
573
631
  },
574
632
  providerOptions.map((p) =>
@@ -586,7 +644,7 @@ window.__ModuleLoader__.load({
586
644
  className: "aapr-select",
587
645
  value: model,
588
646
  onChange: (e) => setModel(e.target.value),
589
- disabled: dir === null,
647
+ disabled: dir === null && provider !== "typesafe",
590
648
  },
591
649
  modelOptions.map((m) =>
592
650
  h("option", { key: m.provider + "/" + m.id, value: m.id }, m.id === "" ? m.name : m.name + "(" + m.id + ")"),
@@ -597,6 +655,75 @@ window.__ModuleLoader__.load({
597
655
  ),
598
656
  h("div", { className: "aapr-muted" }, defaultHint),
599
657
  ),
658
+ provider === "typesafe"
659
+ ? h(
660
+ "div",
661
+ { className: "aapr-card" },
662
+ h("h3", null, "TypeSafe Jev 配置"),
663
+ h(
664
+ "div",
665
+ { className: "aapr-muted" },
666
+ "Jev 是结构化决策模型(System One):审批时直连 TypeSafe API,不创建审批子会话,毫秒级返回带校准概率的裁决。审计「理由」由概率分布合成(Jev 本身不生成文字);置信度低于阈值时按 fail-closed 处理(记 unavailable,不批准也不记拒绝)。对中文任务上下文的准确率略低于英语。API Key 明文保存在本机 config.json;留空时使用环境变量 TYPESAFE_API_KEY。",
667
+ ),
668
+ h(
669
+ "div",
670
+ { className: "aapr-row" },
671
+ h(
672
+ "label",
673
+ null,
674
+ "API Key:",
675
+ h("input", {
676
+ className: "aapr-input",
677
+ type: "password",
678
+ placeholder: "TYPESAFE_API_KEY",
679
+ value: jevKey,
680
+ onChange: (e) => setJevKey(e.target.value),
681
+ }),
682
+ ),
683
+ h(
684
+ "label",
685
+ null,
686
+ "模型:",
687
+ h("input", {
688
+ className: "aapr-input",
689
+ placeholder: "jev-latest",
690
+ value: jevModel,
691
+ onChange: (e) => setJevModel(e.target.value),
692
+ }),
693
+ ),
694
+ ),
695
+ h(
696
+ "div",
697
+ { className: "aapr-row" },
698
+ h(
699
+ "label",
700
+ null,
701
+ "Endpoint:",
702
+ h("input", {
703
+ className: "aapr-input aapr-input-wide",
704
+ placeholder: "https://api.typesafe.ai/v1/systemone",
705
+ value: jevEndpoint,
706
+ onChange: (e) => setJevEndpoint(e.target.value),
707
+ }),
708
+ ),
709
+ h(
710
+ "label",
711
+ null,
712
+ "置信度阈值:",
713
+ h("input", {
714
+ className: "aapr-input",
715
+ type: "number",
716
+ step: "0.05",
717
+ min: "0.01",
718
+ max: "0.99",
719
+ value: jevConf,
720
+ onChange: (e) => setJevConf(e.target.value),
721
+ }),
722
+ ),
723
+ h(ui.Button, { variant: "primary", size: "sm", onClick: saveJev }, "保存"),
724
+ ),
725
+ )
726
+ : null,
600
727
  h(
601
728
  "div",
602
729
  { className: "aapr-card" },
package/index.js CHANGED
@@ -31,6 +31,11 @@
31
31
  * `callId`) plus the asker's stated reason. A rejection must name the
32
32
  * concrete, credible risk the operation creates (destructive /
33
33
  * irreversible / out-of-scope / dishonest); vague unease is approved.
34
+ * v1.6.0: the judge can alternatively be the TypeSafe Jev "System One"
35
+ * decision model (synthetic provider id `typesafe`) — a direct HTTP
36
+ * call that answers typed Choice/Noul questions with calibrated
37
+ * probabilities; a confidence below the configured gate resolves
38
+ * fail-closed like any other fault (see `_judgeWithJev`).
34
39
  *
35
40
  * 3. FAIL CLOSED — any infrastructure fault, timeout, malformed verdict, or
36
41
  * cancellation maps to the fail-closed approval outcomes
@@ -104,6 +109,60 @@ const RECORDS_SIDECAR = "agent-approval.jsonl";
104
109
  const DATA_DIR = join(process.env.DSH_HOME || join(homedir(), ".dsh"), "agent-approval");
105
110
  const CONFIG_FILE = join(DATA_DIR, "config.json");
106
111
 
112
+ /**
113
+ * v1.6.0: the TypeSafe Jev judge backend. Jev is a "System One" decision
114
+ * model (https://api.typesafe.ai/v1/systemone): it does not generate text —
115
+ * it answers typed questions (Choice / Score / Noul) over one `state` with
116
+ * calibrated probability distributions in ~70–500ms. That is exactly the
117
+ * approval-verdict shape, so instead of spawning a judge subagent through
118
+ * the harness model registry (Jev is not a chat route and cannot appear in
119
+ * `llm.listProviders()`), the Host half calls its HTTP API directly when the
120
+ * configured judge provider is the synthetic `typesafe` id. Fail-closed is
121
+ * preserved end to end: any transport fault, non-200, malformed answer, or a
122
+ * confidence below the configured gate resolves `unavailable` — never a
123
+ * grant, and (below the gate) not a recorded rejection either.
124
+ */
125
+ const JEV_PROVIDER = "typesafe";
126
+ const JEV_DEFAULT_MODEL = "jev-latest";
127
+ const JEV_DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
128
+ const JEV_DEFAULT_CONFIDENCE = 0.5;
129
+
130
+ /**
131
+ * The typed questions sent to Jev. Policy lives in the criteria descriptions
132
+ * (Jev reads instructions literally and injects domain knowledge only through
133
+ * state + criteria); the wording mirrors the subagent judge prompt's APPROVE
134
+ * conditions / REJECT list / 误杀治理 so both backends judge to the same
135
+ * standard. `decision` and `riskLevel` are Choices (discrete options +
136
+ * probabilities + confidence); `concreteRisk` is a Noul probe whose
137
+ * probability is folded into the synthesized audit rationale.
138
+ */
139
+ const JEV_QUESTIONS = {
140
+ decision: {
141
+ type: "choice",
142
+ instructions: "Should this one-time sandbox-escalation request be approved?",
143
+ criteria: {
144
+ approve:
145
+ "Yes, approve once. The operation is plausibly safe, non-destructive and reversible; it is clearly required by or consistent with the user's task; the stated reason honestly matches the actual arguments; and granting it once cannot leak secrets or cause irreversible system-wide changes. The project's own documented install/build/deploy steps that place the project's own files into the tool's own documented directories, and reading tool-owned config or logs needed for the task, count as task-scoped.",
146
+ reject:
147
+ "No, reject. The operation is destructive or irreversible (mass deletion, disk formatting, registry/service/system-wide changes), exfiltrates credentials or secrets, touches resources unrelated to the task, modifies the operating system or other applications' data, hides intent behind encoded or obfuscated content, the stated reason does not match the actual arguments, or there is a concrete, credible risk — something it would destroy, leak, or change beyond the user's task.",
148
+ },
149
+ },
150
+ riskLevel: {
151
+ type: "choice",
152
+ instructions: "How risky is the requested operation?",
153
+ criteria: {
154
+ low: "Routine and easily reversible: reading files, or writing within the project workspace that can be regenerated or undone.",
155
+ medium: "Awkward to undo or touches more than the immediate task outputs, but not destructive and not security-sensitive.",
156
+ high: "Destructive, irreversible, system-wide, or touching credentials, secrets, or other applications' data.",
157
+ },
158
+ },
159
+ concreteRisk: {
160
+ type: "noul",
161
+ instructions:
162
+ "Does this specific operation create a concrete, credible risk — destroying data, leaking secrets or credentials, or changing the operating system, other applications, or resources beyond the user's task? Answer false when the operation is task-scoped and reversible; vague unease or an unfamiliar command is NOT a risk.",
163
+ },
164
+ };
165
+
107
166
  /**
108
167
  * The structured verdict the judge subagent MUST produce. Constrained to the
109
168
  * JSON-Schema subset `assertObjectJsonSchema` enforces for subagent outputs
@@ -225,6 +284,7 @@ export class AgentApprovalService extends TypertRemoteService {
225
284
  async [Service.init]() {
226
285
  markRemoteMethod(this, "getState", "getState");
227
286
  markRemoteMethod(this, "setModel", "setModel");
287
+ markRemoteMethod(this, "setJevConfig", "setJevConfig");
228
288
  markRemoteMethod(this, "setApprovalTimeout", "setApprovalTimeout");
229
289
  markRemoteMethod(this, "toggle", "toggle");
230
290
  markRemoteMethod(this, "addRule", "addRule");
@@ -234,6 +294,18 @@ export class AgentApprovalService extends TypertRemoteService {
234
294
 
235
295
  /** Judge model override; empty strings = use the harness default route. */
236
296
  this._model = { provider: "", model: "" };
297
+ /**
298
+ * TypeSafe Jev direct backend settings (used when `_model.provider` is
299
+ * the synthetic `typesafe` id). The API key lives in plaintext on this
300
+ * machine only (config.json, same trust domain as the rest of the
301
+ * settings); an empty key falls back to the TYPESAFE_API_KEY env var.
302
+ */
303
+ this._jev = {
304
+ apiKey: "",
305
+ endpoint: JEV_DEFAULT_ENDPOINT,
306
+ model: JEV_DEFAULT_MODEL,
307
+ confidence: JEV_DEFAULT_CONFIDENCE,
308
+ };
237
309
  /** Judge timeout in ms (clamped); a timeout resolves fail-closed. */
238
310
  this._timeoutMs = DEFAULT_TIMEOUT_MS;
239
311
  /** sessionId -> { prevSandbox?: string, prevApproval?: string } */
@@ -693,6 +765,7 @@ export class AgentApprovalService extends TypertRemoteService {
693
765
  _persistConfig() {
694
766
  const body = JSON.stringify({
695
767
  model: { provider: this._model.provider, model: this._model.model },
768
+ jev: this._jevShape(),
696
769
  timeoutMs: this._timeoutMs,
697
770
  rules: this._rules,
698
771
  });
@@ -719,6 +792,18 @@ export class AgentApprovalService extends TypertRemoteService {
719
792
  ) {
720
793
  this._model = { provider: cfg.model.provider, model: cfg.model.model };
721
794
  }
795
+ if (cfg.jev && typeof cfg.jev === "object") {
796
+ if (typeof cfg.jev.apiKey === "string") this._jev.apiKey = cfg.jev.apiKey;
797
+ if (typeof cfg.jev.endpoint === "string" && cfg.jev.endpoint !== "") {
798
+ this._jev.endpoint = cfg.jev.endpoint;
799
+ }
800
+ if (typeof cfg.jev.model === "string" && cfg.jev.model !== "") {
801
+ this._jev.model = cfg.jev.model;
802
+ }
803
+ if (typeof cfg.jev.confidence === "number" && Number.isFinite(cfg.jev.confidence)) {
804
+ this._jev.confidence = Math.min(0.99, Math.max(0.01, cfg.jev.confidence));
805
+ }
806
+ }
722
807
  if (typeof cfg.timeoutMs === "number" && Number.isFinite(cfg.timeoutMs)) {
723
808
  this._timeoutMs = Math.min(MAX_TIMEOUT_MS, Math.max(MIN_TIMEOUT_MS, Math.floor(cfg.timeoutMs)));
724
809
  }
@@ -892,6 +977,10 @@ export class AgentApprovalService extends TypertRemoteService {
892
977
  * records display — "p/m" = selected, "default(p/m)" = harness default.
893
978
  */
894
979
  _judgeRoute() {
980
+ if (this._model.provider === JEV_PROVIDER) {
981
+ const model = this._jevEffective().model;
982
+ return { provider: JEV_PROVIDER, model: model, label: "jev(" + model + ")" };
983
+ }
895
984
  if (this._model.provider !== "" && this._model.model !== "") {
896
985
  return {
897
986
  provider: this._model.provider,
@@ -957,7 +1046,13 @@ export class AgentApprovalService extends TypertRemoteService {
957
1046
  return "allowed-once";
958
1047
  }
959
1048
 
960
- // 3. The model judge.
1049
+ // 3. The judge. The TypeSafe Jev backend is a direct HTTP call (no
1050
+ // subagent, no harness model route); anything else spawns the judge
1051
+ // child through the `spawn` provider as before.
1052
+ if (this._model.provider === JEV_PROVIDER) {
1053
+ return this._judgeWithJev(session, req, argsRaw, base, trustKey);
1054
+ }
1055
+
961
1056
  const route = this._judgeRoute();
962
1057
 
963
1058
  let run;
@@ -1059,6 +1154,276 @@ export class AgentApprovalService extends TypertRemoteService {
1059
1154
  return "unavailable";
1060
1155
  }
1061
1156
 
1157
+ // ---- the TypeSafe Jev direct backend ---------------------------------------
1158
+
1159
+ /**
1160
+ * Effective Jev settings with env fallback and clamping applied. The key
1161
+ * may come from config.json or the TYPESAFE_API_KEY environment variable;
1162
+ * an absent key keeps the backend selected but every judgment resolves
1163
+ * `unavailable` (fail closed) until one is configured.
1164
+ */
1165
+ _jevEffective() {
1166
+ const key = String(this._jev.apiKey || process.env.TYPESAFE_API_KEY || "").trim();
1167
+ const endpoint = String(this._jev.endpoint || "").trim() || JEV_DEFAULT_ENDPOINT;
1168
+ const model = String(this._jev.model || "").trim() || JEV_DEFAULT_MODEL;
1169
+ let confidence = Number(this._jev.confidence);
1170
+ if (!Number.isFinite(confidence)) confidence = JEV_DEFAULT_CONFIDENCE;
1171
+ confidence = Math.min(0.99, Math.max(0.01, confidence));
1172
+ return { key: key, endpoint: endpoint, model: model, confidence: confidence };
1173
+ }
1174
+
1175
+ /** Owned plain copy of the Jev settings for the wire and config.json. */
1176
+ _jevShape() {
1177
+ return {
1178
+ apiKey: String(this._jev.apiKey || ""),
1179
+ endpoint: String(this._jev.endpoint || JEV_DEFAULT_ENDPOINT),
1180
+ model: String(this._jev.model || JEV_DEFAULT_MODEL),
1181
+ confidence: Number(this._jev.confidence) || JEV_DEFAULT_CONFIDENCE,
1182
+ };
1183
+ }
1184
+
1185
+ /**
1186
+ * The `state` sent to Jev: the same ground truth the subagent judge sees
1187
+ * (workspace, exact arguments, stated reason, first + recent genuine user
1188
+ * messages), as a named object — the shape TypeSafe recommends. All values
1189
+ * are pre-truncated strings so the 32K state budget is respected.
1190
+ */
1191
+ _jevStateOf(session, req, argsRaw) {
1192
+ let cwd = "";
1193
+ try {
1194
+ if (session.header && typeof session.header.cwd === "string") cwd = session.header.cwd;
1195
+ } catch (e) {
1196
+ /* header access is best-effort */
1197
+ }
1198
+ const task = this._recentUserContext(session);
1199
+ return {
1200
+ workspace: cwd || "(unknown)",
1201
+ tool: String(req.toolName),
1202
+ statedReason:
1203
+ typeof req.reason === "string" && req.reason !== "" ? trunc(req.reason, 300) : "(none)",
1204
+ toolArguments: argsRaw === undefined ? "(not available)" : trunc(argsRaw, 4000) || "(empty)",
1205
+ firstUserMessage: task.first !== "" ? task.first : "(no user messages available)",
1206
+ recentUserMessages: task.recent,
1207
+ };
1208
+ }
1209
+
1210
+ /**
1211
+ * Judge one escalation through the Jev HTTP API (state + typed questions →
1212
+ * calibrated probability distributions). Mirrors `_judge`'s spawn-path
1213
+ * contract exactly — rules and the session trust cache have already run —
1214
+ * and every abnormal shape resolves fail-closed:
1215
+ * - no API key / transport fault / non-200 / malformed answer → `unavailable`
1216
+ * - request cancelled mid-flight → `cancelled`
1217
+ * - overall timeout (the same `this._timeoutMs` budget) → `unavailable`
1218
+ * - confidence below the configured gate → `unavailable` (the model is
1219
+ * not sure enough to decide: never a grant, and not a recorded
1220
+ * rejection either — the v1.4.0 误杀治理 applies symmetrically)
1221
+ * Jev does not generate text, so the audit rationale is synthesized from
1222
+ * the returned distributions; the served model version (`body.model`,
1223
+ * which resolves aliases like jev-latest) is what the audit displays.
1224
+ */
1225
+ async _judgeWithJev(session, req, argsRaw, base, trustKey) {
1226
+ const cfg = this._jevEffective();
1227
+ if (cfg.key === "") {
1228
+ this._record(session, {
1229
+ ...base,
1230
+ outcome: "unavailable",
1231
+ riskLevel: "-",
1232
+ model: "jev(" + cfg.model + ")",
1233
+ rationale: "Jev backend selected but no API key configured (Settings → Agent 审批, or the TYPESAFE_API_KEY environment variable)",
1234
+ });
1235
+ return "unavailable";
1236
+ }
1237
+ if (typeof fetch !== "function") {
1238
+ this._record(session, { ...base, outcome: "unavailable", riskLevel: "-", model: "jev(" + cfg.model + ")", rationale: "fetch is unavailable in this runtime" });
1239
+ return "unavailable";
1240
+ }
1241
+
1242
+ const startedAt = Date.now();
1243
+ const controller = new AbortController();
1244
+ const signal = req.signal;
1245
+ const onAbort = () => controller.abort();
1246
+ if (signal && typeof signal.addEventListener === "function") {
1247
+ signal.addEventListener("abort", onAbort, { once: true });
1248
+ }
1249
+
1250
+ let winner;
1251
+ try {
1252
+ const state = this._jevStateOf(session, req, argsRaw);
1253
+ winner = await Promise.race([
1254
+ this._jevRequest(cfg, state, controller.signal)
1255
+ .then((body) => ({ kind: "result", body: body }))
1256
+ .catch((error) => ({
1257
+ kind: "fault",
1258
+ error: error,
1259
+ aborted: error && error.name === "AbortError",
1260
+ })),
1261
+ (signal
1262
+ ? new Promise((resolve) => {
1263
+ if (signal.aborted) {
1264
+ resolve(true);
1265
+ return;
1266
+ }
1267
+ signal.addEventListener("abort", () => resolve(true), { once: true });
1268
+ })
1269
+ : Promise.resolve(false)
1270
+ ).then((v) => ({ kind: "aborted", aborted: v })),
1271
+ this.ctx.timeout(this._timeoutMs).then(() => ({ kind: "timeout" })),
1272
+ ]);
1273
+ } finally {
1274
+ if (signal && typeof signal.removeEventListener === "function") {
1275
+ signal.removeEventListener("abort", onAbort);
1276
+ }
1277
+ // Whether we lost the race to timeout/cancel or the request already
1278
+ // settled, closing the transport is always safe.
1279
+ try {
1280
+ controller.abort();
1281
+ } catch (e) {
1282
+ /* controller abort never blocks the outcome */
1283
+ }
1284
+ }
1285
+ const durationMs = Date.now() - startedAt;
1286
+
1287
+ if (winner.kind === "result") {
1288
+ return this._jevVerdict(session, winner.body, cfg, base, trustKey, durationMs);
1289
+ }
1290
+ if (winner.kind === "aborted") {
1291
+ this._record(session, { ...base, outcome: "cancelled", riskLevel: "-", model: "jev(" + cfg.model + ")", rationale: "request cancelled while Jev was judging" });
1292
+ return "cancelled";
1293
+ }
1294
+ if (winner.kind === "timeout") {
1295
+ this._record(session, {
1296
+ ...base,
1297
+ outcome: "unavailable",
1298
+ riskLevel: "-",
1299
+ model: "jev(" + cfg.model + ")",
1300
+ rationale: "Jev request timed out after " + String(this._timeoutMs) + "ms (fail closed)",
1301
+ });
1302
+ return "unavailable";
1303
+ }
1304
+ if (winner.aborted) {
1305
+ this._record(session, { ...base, outcome: "cancelled", riskLevel: "-", model: "jev(" + cfg.model + ")", rationale: "request cancelled while Jev was judging" });
1306
+ return "cancelled";
1307
+ }
1308
+ this._record(session, { ...base, outcome: "unavailable", riskLevel: "-", model: "jev(" + cfg.model + ")", rationale: "Jev request failed: " + errText(winner.error) });
1309
+ return "unavailable";
1310
+ }
1311
+
1312
+ /** The single POST to the System One endpoint; resolves the parsed body. */
1313
+ async _jevRequest(cfg, state, abortSignal) {
1314
+ const response = await fetch(cfg.endpoint, {
1315
+ method: "POST",
1316
+ headers: {
1317
+ "Authorization": "Bearer " + cfg.key,
1318
+ "Content-Type": "application/json",
1319
+ },
1320
+ body: JSON.stringify({
1321
+ state: state,
1322
+ model: cfg.model,
1323
+ questions: JEV_QUESTIONS,
1324
+ }),
1325
+ signal: abortSignal,
1326
+ });
1327
+ if (!response.ok) {
1328
+ let detail = "";
1329
+ try {
1330
+ detail = trunc(String(await response.text()), 200);
1331
+ } catch (e) {
1332
+ /* body read is best-effort */
1333
+ }
1334
+ throw new Error("HTTP " + String(response.status) + (detail !== "" ? " " + detail : ""));
1335
+ }
1336
+ const body = await response.json();
1337
+ if (!body || typeof body !== "object") throw new Error("response body is not an object");
1338
+ return body;
1339
+ }
1340
+
1341
+ /**
1342
+ * Map a Jev response to the same outcomes the subagent path produces.
1343
+ * Returns the waterfall outcome string; records the audit line itself.
1344
+ */
1345
+ _jevVerdict(session, body, cfg, base, trustKey, durationMs) {
1346
+ const served = typeof body.model === "string" && body.model !== "" ? body.model : cfg.model;
1347
+ const label = "jev(" + served + ")";
1348
+ const answers = body.answers && typeof body.answers === "object" ? body.answers : {};
1349
+ const decision = answers.decision && typeof answers.decision === "object" ? answers.decision : undefined;
1350
+ const risk = answers.riskLevel && typeof answers.riskLevel === "object" ? answers.riskLevel : undefined;
1351
+ const probe = answers.concreteRisk && typeof answers.concreteRisk === "object" ? answers.concreteRisk : undefined;
1352
+
1353
+ const choice = decision && (decision.choice === "approve" || decision.choice === "reject") ? decision.choice : undefined;
1354
+ const confidence = decision ? Number(decision.confidence) : NaN;
1355
+ const probabilities = decision && decision.probabilities && typeof decision.probabilities === "object" ? decision.probabilities : {};
1356
+ const riskChoice =
1357
+ risk && (risk.choice === "low" || risk.choice === "medium" || risk.choice === "high") ? risk.choice : undefined;
1358
+ const probeNoul = probe ? Number(probe.noul) : NaN;
1359
+
1360
+ // Any missing or out-of-shape answer is fail-closed, not guessed.
1361
+ if (
1362
+ choice === undefined ||
1363
+ !Number.isFinite(confidence) ||
1364
+ confidence < 0 ||
1365
+ confidence > 1 ||
1366
+ riskChoice === undefined ||
1367
+ !Number.isFinite(probeNoul)
1368
+ ) {
1369
+ this._record(session, {
1370
+ ...base,
1371
+ durationMs: durationMs,
1372
+ outcome: "unavailable",
1373
+ riskLevel: "-",
1374
+ model: label,
1375
+ rationale: "Jev returned no valid verdict shape (decision/riskLevel/concreteRisk incomplete)",
1376
+ });
1377
+ return "unavailable";
1378
+ }
1379
+
1380
+ // Confidence gate: below the threshold the model is not sure enough to
1381
+ // decide at all — never a grant, never a recorded rejection.
1382
+ if (confidence < cfg.confidence) {
1383
+ this._record(session, {
1384
+ ...base,
1385
+ durationMs: durationMs,
1386
+ outcome: "unavailable",
1387
+ riskLevel: riskChoice,
1388
+ model: label,
1389
+ rationale:
1390
+ "Jev confidence " + confidence.toFixed(2) + " is below the gate " + cfg.confidence.toFixed(2) + " (decision draft: " + choice + ") — fail closed",
1391
+ });
1392
+ return "unavailable";
1393
+ }
1394
+
1395
+ const pApprove = Number(probabilities.approve);
1396
+ const pReject = Number(probabilities.reject);
1397
+ const rationale =
1398
+ "Jev 决策=" + choice +
1399
+ "(置信度 " + confidence.toFixed(2) +
1400
+ (Number.isFinite(pApprove) && Number.isFinite(pReject)
1401
+ ? ",p approve/reject " + pApprove.toFixed(2) + "/" + pReject.toFixed(2)
1402
+ : "") +
1403
+ ");风险=" + riskChoice +
1404
+ ";具体风险概率=" + probeNoul.toFixed(2) +
1405
+ "。Jev 为结构化决策模型,不生成文字,本理由由概率分布合成。";
1406
+
1407
+ base.durationMs = durationMs;
1408
+ const approved = choice === "approve";
1409
+ this._record(session, {
1410
+ ...base,
1411
+ outcome: approved ? "allowed-once" : "rejected",
1412
+ riskLevel: riskChoice,
1413
+ model: label,
1414
+ rationale: trunc(rationale, 600),
1415
+ });
1416
+ if (approved && trustKey !== undefined) {
1417
+ let set = this._trusted.get(session.id);
1418
+ if (set === undefined) {
1419
+ set = new Set();
1420
+ this._trusted.set(session.id, set);
1421
+ }
1422
+ set.add(trustKey);
1423
+ }
1424
+ return approved ? "allowed-once" : "rejected";
1425
+ }
1426
+
1062
1427
  // ---- Remote API ------------------------------------------------------------
1063
1428
 
1064
1429
  /**
@@ -1100,6 +1465,7 @@ export class AgentApprovalService extends TypertRemoteService {
1100
1465
  ok: true,
1101
1466
  value: {
1102
1467
  model: { provider: this._model.provider, model: this._model.model },
1468
+ jev: this._jevShape(),
1103
1469
  timeoutMs: this._timeoutMs,
1104
1470
  enabledSessions: this._sessionInfos(),
1105
1471
  rules: this._rulesSnapshot(),
@@ -1114,12 +1480,35 @@ export class AgentApprovalService extends TypertRemoteService {
1114
1480
  async setModel(request) {
1115
1481
  const provider = request && typeof request.provider === "string" ? request.provider : "";
1116
1482
  const model = request && typeof request.model === "string" ? request.model : "";
1117
- this._model =
1118
- provider !== "" && model !== "" ? { provider, model } : { provider: "", model: "" };
1483
+ if (provider === JEV_PROVIDER) {
1484
+ // The Jev backend ignores the harness route table; an unset model just
1485
+ // means the latest alias.
1486
+ this._model = { provider: JEV_PROVIDER, model: model !== "" ? model : JEV_DEFAULT_MODEL };
1487
+ } else {
1488
+ this._model =
1489
+ provider !== "" && model !== "" ? { provider, model } : { provider: "", model: "" };
1490
+ }
1119
1491
  this._persistConfig();
1120
1492
  return { ok: true, value: { model: { provider: this._model.provider, model: this._model.model } } };
1121
1493
  }
1122
1494
 
1495
+ /**
1496
+ * Set the TypeSafe Jev backend settings (only provided fields change).
1497
+ * `confidence` is the gate below which Jev's answer is not trusted and the
1498
+ * outcome resolves fail-closed; clamped to [0.01, 0.99]. Persisted.
1499
+ */
1500
+ async setJevConfig(request) {
1501
+ const r = request && typeof request === "object" ? request : {};
1502
+ if (typeof r.apiKey === "string") this._jev.apiKey = r.apiKey.trim();
1503
+ if (typeof r.endpoint === "string") this._jev.endpoint = r.endpoint.trim();
1504
+ if (typeof r.model === "string") this._jev.model = r.model.trim();
1505
+ if (typeof r.confidence === "number" && Number.isFinite(r.confidence)) {
1506
+ this._jev.confidence = Math.min(0.99, Math.max(0.01, r.confidence));
1507
+ }
1508
+ this._persistConfig();
1509
+ return { ok: true, value: { jev: this._jevShape() } };
1510
+ }
1511
+
1123
1512
  /** Set the judge timeout (clamped to [MIN, MAX] milliseconds). Persisted. */
1124
1513
  async setApprovalTimeout(request) {
1125
1514
  const raw = request && typeof request.timeoutMs === "number" ? request.timeoutMs : 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@duke-dsh-plugins/dsh-agent-approval",
3
- "version": "1.5.0",
4
- "description": "Agent-decided approvals for DeepSeek Harness: a workspace-write base permission mode where an independent approval subagent judges every sandbox escalation (risky operations are rejected), with a configurable approval model and a per-session audit trail in the conversation window's 审批 tab.",
3
+ "version": "1.6.0",
4
+ "description": "Agent-decided approvals for DeepSeek Harness: a workspace-write base permission mode where an independent approval judge (an isolated subagent, or the TypeSafe Jev decision model via direct API) evaluates every sandbox escalation (risky operations are rejected), with a configurable approval model and a per-session audit trail in the conversation window's 审批 tab.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/MoonlitDropOfBlood/dsh-agent-approval.git"
package/typert.host.js CHANGED
@@ -41,6 +41,15 @@ const modelRouteSchema = z
41
41
  })
42
42
  .readonly();
43
43
 
44
+ const jevConfigSchema = z
45
+ .object({
46
+ apiKey: z.string(),
47
+ endpoint: z.string(),
48
+ model: z.string(),
49
+ confidence: z.number(),
50
+ })
51
+ .readonly();
52
+
44
53
  const enabledSessionSchema = z
45
54
  .object({
46
55
  id: z.string(),
@@ -63,6 +72,7 @@ const ruleSchema = z
63
72
  const stateValueSchema = z
64
73
  .object({
65
74
  model: modelRouteSchema,
75
+ jev: jevConfigSchema,
66
76
  timeoutMs: z.number(),
67
77
  enabledSessions: z.array(enabledSessionSchema).readonly(),
68
78
  rules: z.array(ruleSchema).readonly(),
@@ -75,6 +85,12 @@ const setModelValueSchema = z
75
85
  })
76
86
  .readonly();
77
87
 
88
+ const setJevValueSchema = z
89
+ .object({
90
+ jev: jevConfigSchema,
91
+ })
92
+ .readonly();
93
+
78
94
  const setTimeoutValueSchema = z
79
95
  .object({
80
96
  timeoutMs: z.number(),
@@ -152,6 +168,7 @@ function okResult(valueSchema) {
152
168
 
153
169
  const stateResultSchema = okResult(stateValueSchema);
154
170
  const setModelResultSchema = okResult(setModelValueSchema);
171
+ const setJevResultSchema = okResult(setJevValueSchema);
155
172
  const setTimeoutResultSchema = okResult(setTimeoutValueSchema);
156
173
  const toggleResultSchema = okResult(toggleValueSchema);
157
174
  const addRuleResultSchema = okResult(rulesValueSchema);
@@ -166,6 +183,13 @@ const _agentApproval_setModel_parameter_0$schema = z.object({
166
183
  model: z.string(),
167
184
  });
168
185
 
186
+ const _agentApproval_setJevConfig_parameter_0$schema = z.object({
187
+ apiKey: z.string(),
188
+ endpoint: z.string(),
189
+ model: z.string(),
190
+ confidence: z.number(),
191
+ });
192
+
169
193
  const _agentApproval_setApprovalTimeout_parameter_0$schema = z.object({
170
194
  timeoutMs: z.number(),
171
195
  });
@@ -234,6 +258,31 @@ export const TYPERT = {
234
258
  },
235
259
  sourceLocation: { file: "index.js", line: 1, column: 1 },
236
260
  },
261
+ {
262
+ id: "dsh-agent-approval#agentApproval/setJevConfig",
263
+ service: "agentApproval",
264
+ namespace: "agentApproval",
265
+ method: "setJevConfig",
266
+ invocation: { kind: "direct" },
267
+ parameters: [
268
+ {
269
+ name: "request",
270
+ wire: "request",
271
+ source: "json",
272
+ codec: {
273
+ mode: "strict",
274
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetJevRequest",
275
+ schema: _agentApproval_setJevConfig_parameter_0$schema,
276
+ },
277
+ },
278
+ ],
279
+ result: {
280
+ mode: "strict",
281
+ typeSymbol: "dsh-agent-approval#AgentApprovalSetJevResult",
282
+ schema: setJevResultSchema,
283
+ },
284
+ sourceLocation: { file: "index.js", line: 1, column: 1 },
285
+ },
237
286
  {
238
287
  id: "dsh-agent-approval#agentApproval/setApprovalTimeout",
239
288
  service: "agentApproval",
@@ -390,17 +439,25 @@ export const TYPERT = {
390
439
  kind: "method",
391
440
  name: "getState",
392
441
  signature: "@Remote('getState') async getState(): Promise<AgentApprovalStateResult>",
393
- summary: "Snapshot for the Settings page (model route, timeout, enabled sessions, rules).",
442
+ summary: "Snapshot for the Settings page (model route, Jev backend, timeout, enabled sessions, rules).",
394
443
  jsDoc:
395
- "/**\n * Return the judge model route, timeout, enabled sessions (id + session-list title + workspace cwd), and the rule table.\n * @returns success or a business failure.\n */",
444
+ "/**\n * Return the judge model route, the TypeSafe Jev backend settings, timeout, enabled sessions (id + session-list title + workspace cwd), and the rule table.\n * @returns success or a business failure.\n */",
396
445
  },
397
446
  {
398
447
  kind: "method",
399
448
  name: "setModel",
400
449
  signature: "@Remote('setModel') async setModel(request: AgentApprovalSetModelRequest): Promise<AgentApprovalSetModelResult>",
401
- summary: "Set the judge model route (empty strings = inherit the requesting session's).",
450
+ summary: "Set the judge model route (empty strings = inherit the requesting session's; provider 'typesafe' selects the Jev backend).",
451
+ jsDoc:
452
+ "/**\n * Set provider/model used by the approval judge; empty strings clear the override. The synthetic provider 'typesafe' routes judging through the TypeSafe Jev HTTP API instead of a harness subagent.\n * @param request - { provider, model }.\n * @returns the stored route.\n */",
453
+ },
454
+ {
455
+ kind: "method",
456
+ name: "setJevConfig",
457
+ signature: "@Remote('setJevConfig') async setJevConfig(request: AgentApprovalSetJevRequest): Promise<AgentApprovalSetJevResult>",
458
+ summary: "Set the TypeSafe Jev backend settings (API key, endpoint, model, confidence gate; persisted).",
402
459
  jsDoc:
403
- "/**\n * Set provider/model used by the approval subagent; empty strings clear the override.\n * @param request - { provider, model }.\n * @returns the stored route.\n */",
460
+ "/**\n * Update the Jev backend settings used when the judge provider is 'typesafe'. Only provided fields change; confidence (the fail-closed gate) is clamped to [0.01, 0.99].\n * @param request - { apiKey, endpoint, model, confidence }.\n * @returns the stored Jev settings.\n */",
404
461
  },
405
462
  {
406
463
  kind: "method",
@@ -462,6 +519,21 @@ export const TYPERT = {
462
519
  declaration:
463
520
  "export interface AgentApprovalModelRoute {\n readonly provider: string;\n readonly model: string;\n}",
464
521
  },
522
+ {
523
+ name: "AgentApprovalJevConfig",
524
+ declaration:
525
+ "export interface AgentApprovalJevConfig {\n readonly apiKey: string;\n readonly endpoint: string;\n readonly model: string;\n readonly confidence: number;\n}",
526
+ },
527
+ {
528
+ name: "AgentApprovalSetJevRequest",
529
+ declaration:
530
+ "export interface AgentApprovalSetJevRequest {\n readonly apiKey: string;\n readonly endpoint: string;\n readonly model: string;\n readonly confidence: number;\n}",
531
+ },
532
+ {
533
+ name: "AgentApprovalSetJevResult",
534
+ declaration:
535
+ "export type AgentApprovalSetJevResult = { ok: true; value: { readonly jev: AgentApprovalJevConfig } } | { ok: false; error: { code: string; message?: string } };",
536
+ },
465
537
  {
466
538
  name: "AgentApprovalEnabledSession",
467
539
  declaration:
@@ -480,7 +552,7 @@ export const TYPERT = {
480
552
  {
481
553
  name: "AgentApprovalStateValue",
482
554
  declaration:
483
- "export interface AgentApprovalStateValue {\n readonly model: AgentApprovalModelRoute;\n readonly timeoutMs: number;\n readonly enabledSessions: readonly AgentApprovalEnabledSession[];\n readonly rules: readonly AgentApprovalRule[];\n}",
555
+ "export interface AgentApprovalStateValue {\n readonly model: AgentApprovalModelRoute;\n readonly jev: AgentApprovalJevConfig;\n readonly timeoutMs: number;\n readonly enabledSessions: readonly AgentApprovalEnabledSession[];\n readonly rules: readonly AgentApprovalRule[];\n}",
484
556
  },
485
557
  {
486
558
  name: "AgentApprovalSessionRecordsRequest",