# @dsh-external/dsh-auto-review-jev [English](README.md) | **中文** `@dsh-external/dsh-auto-review-jev` 为 DeepSeek Harness 的 **Auto** permission preset 提供基于 [TypeSafe Jev](https://docs.typesafe.ai) 的逐工具调用授权审查。 设计参考: - DeepSeek Harness `packages/experimental/auto-review`:沿用 Auto preset、`tools/pre-execute` 审查点、PTC inner call 覆盖、权限生命周期与 fail-closed 语义。 - `y0usaf/pi-jev`:沿用 Jev typed questions、校准阈值、参数裁剪和短期结果缓存的思路。 与原版 Auto review 的主要区别是:**不要求 reviewer 生成 `risk/decision` 自由文本 JSON**。Jev 一次返回多个独立的 `noul` / `score` 数值,本插件在本地用固定规则组合为最终 `allow` / `deny`。 各版本的改动记录见 [CHANGELOG.md](CHANGELOG.md)。 composer 上 `Auto` 预设显示的名称与图标来自另一个包: [dsh-client-ui-permission-presets-jev](https://github.com/sperictao/dsh-client-ui-permission-presets-jev), 见下文「配套 UI 包」。本插件的审查功能不依赖它。 ## 界面截图 以下截图取自真实运行的 `dsh --profile web`(DeepSeek Harness `0.1.7-alpha.1`),本插件占用 DSH 的 `Auto` 插槽。 **选择该预设。** 审查器占用 DSH 唯一的 `Auto` 插槽,开关就是 composer 的权限选择器。名称与图标来自 **配套 UI 包**(见下文);不装它时这一行仍显示 `Auto review`。 ![权限选择器中的 Auto Review Jev](docs/screenshots/permission-selector.png) **启用确认。** 弹窗本身是 DSH 的实验性提示,只有名称被替换。 ![启用 Auto Review Jev 的确认弹窗](docs/screenshots/enable-confirm.png) **被拒绝的调用与人工放行。** 拒绝不是终局:插件经 user-questions 接缝询问,弹窗写明审查结论、工具与 即将执行的调用;「允许本次执行」只放行这一次,「保持拒绝」维持拒绝。 ![拒绝后的询问弹窗](docs/screenshots/reprieve-dialog.zh-CN.png) **拒绝原因留在会话记录里。** 文案区分评审故障 `review_error: …` 与风险裁决 `risk: …`,并写明是否询问过用户。 ![会话记录中的拒绝及其原因](docs/screenshots/denial-in-transcript.png) **设置页。** 用量面板在最前,随后是凭据与端点:API 密钥 → 评估端点 → 用量端点(模型字段在折叠线以下)。 计数是本机内存态并已标注;只有配置了 `usageEndpoint` 才会显示账户额度。 ![Auto Review Jev 设置页](docs/screenshots/settings-page.zh-CN.png) 界面语言切换时弹窗与设置页随动(0.2.8 起): ![英文界面的询问弹窗](docs/screenshots/reprieve-dialog.png) ![英文界面的设置页](docs/screenshots/settings-page.png) ## 行为 当当前 Session 选择 `Auto` 时,每个支持的原生工具调用和每个已启动的 PTC inner call 在执行 body 之前都会经过 Jev 审查。 - 低风险:项目内普通读写、分析、格式化、测试、构建等,直接允许。 - 中风险:破坏已有状态、生产环境操作、外部写入、权限/安全控制变更、高影响操作。只有当前 human/direct-parent 指令明确授权动作、目标和必要范围,并且没有冲突/越界时才允许。 - 高风险:敏感数据跨信任边界泄露,始终拒绝。 - Jev 超时、限流重试后失败、响应异常、上下文/Schema 无法可靠重建:**拒绝(fail closed)**。 - 拒绝文案会带上原因:风险判定显示 `risk: …`,评审本身的故障显示 `review_error: …`(例如 `review_error: HTTP 401 (missing or invalid API key)`)。原因同时写进可见文案和结构化 `info.reason`,因此「密钥失效导致的全面拒绝」不会再被误读成「风险裁决」。 外层 `run_code` 只是 PTC transport,不单独审查;其每个 PTC inner tool call 会单独审查。与上游 Auto review 一样,`run_code` 程序内部绕过 DSH tool registry 的直接 Node.js 副作用不在本插件审查范围内。 ## 拒绝后询问用户(人工放行) 拒绝是插件的最终结论,但人类才是审查本身所服从的权威,因此真正阻断调用之前会先问一次: - 任何拒绝都会问——风险判定(`risk: …`)与评审本身的故障(`review_error: …`)一视同仁。提问走 DSH 的 user-questions 接缝(`ctx.userQuestions`,也就是模型 `ask_user_question` 工具背后的接缝)。 - 弹窗使用 DSH Web 当前的界面语言(由浏览器半端上报,非中文一律回落到英文),并写明**审了什么、为什么拒绝**:审查结论、触发的维度及其分值、工具名,以及即将执行的调用参数;选项同样是 **允许本次执行** / **保持拒绝**。 - 选「允许本次执行」只放行**这一次调用**:工具照原样执行,后续调用仍由 Jev 审查。放行不会被记住,也不会放宽之后的任何决定。 - 其余一切情况都维持拒绝:没有挂载 user-questions 应答方(无界面、缺少客户端插件)、调用发生在子代理里(人工交互只对活的 root agent 开放)、提问途中调用被取消,或回答不是可识别的放行。 - 记录会自己说明原因:拒绝文案后缀会写明「用户被询问后仍保持拒绝」「无法询问用户」或「提问期间调用被取消」。 - 提问按会话串行,因此同一步里并行的工具调用不会一次性堆出多个问题。 - 配置 `askOnDeny: false` 可恢复成从不提问的审查器。 为什么不用 DSH 自带的 `{ kind: 'ask' }` 决策?那条路会经过 `ctx.approval`,而它的 `decide()` 在会话策略为 `never` 时立刻返回 `'rejected'`——本插件自己的预设正是以 `never` 搭配 Full access(委派子会话同样固定为 `never`),这条路径不可依赖。因此这里直接向 user-questions 接缝提问,它在任何审批策略下都可用。这一偏离是刻意且有限的:人类可以推翻拒绝,模型永远不能。 ## 安装 `@dsh-external` 不是 npm 上的可发布 scope,本包通过 tarball 或源码目录安装到 Web profile: ```bash # 从任一 Release 的附件 tarball 安装(无需构建、无需 pnpm 放行) dsh plugin --profile web add ./dsh-external-dsh-auto-review-jev-.tgz ``` 或从源码目录安装(开发用): ```bash dsh plugin --profile web add /path/to/dsh-auto-review-jev ``` 配置 TypeSafe API Key: ```bash export TYPESAFE_API_KEY="..." ``` 也可以在 DSH Web 的 **设置 → Auto Review Jev** 页面里粘贴密钥(写入凭据域,密钥不会回显)。 然后在权限选择器中选择对应预设:默认是 `Auto`;若按「与 DSH 自带 auto review 共存」一节绑定到独立预设,则选择该预设的名字(示例中是 **Auto Reviewer Jev**)。 没有 `TYPESAFE_API_KEY` 时插件仍可加载,但不会允许切换到该预设;如果已有会话在该预设下运行而 key 失效,相关工具调用会 fail closed。 `Auto` 是 DSH 的单一固定集成点,同一时刻只容得下一个审查器:先注册者持有插槽,后来者拿不到。请勿同时挂载两个 Auto 审查器——内置已持有时,本插件会带警告让位(进入 INACTIVE);本插件持有时,内置自身的挂载会失败。 如果你需要两者**同时**安装,请把本插件绑定到自己的预设名(`preset: auto-jev`,显示为 "Auto Reviewer Jev")——见下节「与 DSH 自带 auto review 共存」。 ### 从 GitHub 源码安装:pnpm 会拦截构建脚本(allowBuilds) ```bash dsh plugin --profile web add github:sperictao/dsh-auto-review-jev ``` 在 pnpm ≥ 10 上这条命令会失败: ```text [ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED] Failed to prepare git-hosted package fetched from "https://codeload.github.com/sperictao/dsh-auto-review-jev/tar.gz/": The git-hosted package "@dsh-external/dsh-auto-review-jev@0.2.4" needs to execute build scripts but is not in the "allowBuilds" allowlist. ``` 原因不是本包做了什么出格的事,而是**产物不入库 + pnpm 默认不信任依赖的构建脚本**:包用 `prepare`(`tsdown`)产出 `lib/`,而 `lib/` 按 `.gitignore` 不入库,所以 git 来源的安装必须现场构建一次。pnpm 的供应链防线默认不放行第三方依赖的生命周期脚本——registry 来源会报 `Ignored build scripts: …`(`strictDepBuilds` 默认为 true),git 来源则直接硬失败。 三条出路,按推荐顺序: **1. 装预构建的 tarball(推荐,无需任何放行)** ```bash dsh plugin --profile web add ./dsh-external-dsh-auto-review-jev-.tgz ``` tarball 里已经包含构建好的 `lib/`,安装不触发 `prepare`,因此不会遇到该拦截。 **2. 用 DSH Pro Max 启动器安装(一键放行)** 在市场页 **Custom install** 里填 `github:sperictao/dsh-auto-review-jev`(`owner/repo` 形态亦可)。被 pnpm 拦截时启动器会弹出 **Allow build scripts?** 审批框并列出需要放行的精确键;点 **Approve & install** 后启动器把键写进 profile 的 `pnpm-workspace.yaml` 并自动重跑安装,不需要手工编辑文件。DSH Pro Max v0.8.26 起,`name@git+https://…#` 与 `name@https://codeload.github.com/…/tar.gz/` 两种键形态都能识别。 **3. 手工放行** 把 pnpm 报错里 `allowBuilds:` 示例块打印的键**原样**写进 profile 的 `pnpm-workspace.yaml`: - macOS / Linux:`~/.dsh/profiles/web/pnpm-workspace.yaml` - Windows:`%USERPROFILE%\.dsh\profiles\web\pnpm-workspace.yaml` ```yaml allowBuilds: # 键以 @ 开头必须加引号:@ 是 YAML 保留字符,裸写会让整个 profile 配置解析失败 '@dsh-external/dsh-auto-review-jev@git+https://github.com/sperictao/dsh-auto-review-jev.git#': true # pnpm 10 用这个列表键;pnpm 11 起改用上面的 allowBuilds,旧键不再生效 onlyBuiltDependencies: - '@dsh-external/dsh-auto-review-jev' ``` 然后重跑安装。在该 profile 目录下跑 `pnpm approve-builds` 也可以,它把放行的包写进同一份 `allowBuilds`;交互没走完时该键可能留成占位值(`set this to true or false`),需要改成 `true`(启动器的审批流程会自动覆盖占位值)。 三点值得知道: - **pnpm 打印的键是提交钉定的**:它指向本次解析到的具体来源(`#`,经 codeload 拉取时是 `/tar.gz/`),仓库有新提交后再装会打印新键、需要再放行一次。 - **想一次放行、长期有效**,手工写**仓库形态**的键(不带 `#`):`'@dsh-external/dsh-auto-review-jev@git+https://github.com/sperictao/dsh-auto-review-jev.git': true`。pnpm ≥ 11.19.0 下它同时覆盖克隆与 codeload tarball 两条拉取路径,之后的新提交无需重新放行。 - **放行是 profile 级的**:条目对该 profile 内所有安装生效,删掉即回到被拦状态。 ### 支持的版本 - **DeepSeek Harness `0.2.0-rc.1`**——peer 范围不接受更早的版本;Harness 在安装与 profile 启动时都会校验插件声明的 DSH peer 是否匹配自身运行时,不匹配即拒绝加载。 - **`@deepseek-ai/cordis` `~4.0.4`**——该 Harness 依赖族共同声明的范围。 不支持更早的 Harness 版本:peer 校验会直接拒绝;Host 半端也不再识别 0.1.6 的压缩检查点来源。如果插件没有加载,先看它报告的 peer 拒绝信息(可在 profile 里用 `dsh plugin allow-version` 授予精确版本豁免,风险自负),再看 preset 冲突告警(告警里写了修法),最后看设置页是否被禁用——被禁用说明 Host 早于 `configForms`。 ## 配套 UI 包:预设名称与图标 审查器跑在 Host 侧,而 composer 画出来的东西属于另一个包:DSH 的 `@deepseek-ai/dsh-client-ui-permission-presets` 持有权限选择器,它的 `Auto review` 文案、`EXP` 角标和 图标都硬编码在里面。文案注册在 `permission.access` locale 命名空间,而 locale 运行时拒绝对同一命名空间 重复注册;字形表是模块私有 `Map`,该包并不导出——因此 profile 配置和任何兄弟插件都够不到这两处。 [dsh-client-ui-permission-presets-jev](https://github.com/sperictao/dsh-client-ui-permission-presets-jev) 就是这份源码的副本,只改三处,并与上游 `0.1.7-alpha.1` 保持可 diff: | | 上游 | 本副本 | |---|---|---| | 模式名称 | `Auto review` | `Auto Review Jev`(中英各 4 处,含启用确认弹窗) | | 角标 | `EXP` | 不变 | | 图标 | 只有官方三个预设 | 新增 `auto` 字形:官方盾牌轮廓内填充四角星芒,全程 `currentColor` | 两者相互独立,且只有前者会做审查: - **只装本插件**:审查生效、拒绝带原因、设置页可用;composer 仍显示上游的 `Auto review` 与官方图标。 - **只装本副本**:composer 显示 `Auto Review Jev` 与自有图标,但没有任何审查。它是给 DSH 内置 auto review 改名,不是新增一个审查器。 两个都装,才有「审查器 + 一致的名称与图标」。 ### 安装本副本 本副本自带 `cordis.patch.yml`:先把内置的 `ui-permission` 行置为 `disabled: true`,再插入自己的 `ui-permission-jev`。两者注册同一批 seat(`conversation.input.permission`、 `settings.general.item#permission`、`/permission` 装饰)并抢占同一组 locale 命名空间,所以 **必须只留一个**——停用内置是安装的一部分,不是可选项。 ```bash # 从 GitHub 安装(安装时跑一次 prepare,即现场构建) dsh plugin --profile web add github:sperictao/dsh-client-ui-permission-presets-jev # 或从源码目录安装 dsh plugin --profile web add file:/abs/path/to/dsh-client-ui-permission-presets-jev ``` 安装后刷新页面即可;`patchReload: live` 会按请求重新组合模块列表,重启 `dsh web` 同样有效。 验证组合树——内置行应为 disabled,本副本应被插入: ```bash dsh --profile web --dump-config | grep -B2 -A3 'ui-permission' ``` 移除后界面恢复上游文案与图标: ```bash dsh plugin --profile web remove @dsh-external/dsh-client-ui-permission-presets-jev ``` `ui-permission` 不再被 disabled,上游包本身从未被修改。 ## 与 DSH 自带 auto review 共存 DSH 的 `auto` 预设**只允许一个集成**:第二个调用 `permissionPresets.registerAuto()` 的插件会抛 `preset "auto" is already registered`。该插槽的常见占用者是 DSH 自带的 `@deepseek-ai/dsh-experimental-auto-review`(row `auto-review`,默认关闭,在 Web 的 Plugins 页开启);权限服务把它的旋钮组合固定为 Full access + **`ask`** 审批策略,且仅在该集成存活期间出现在选择器里。为避免与它抢同一个插槽,本插件支持把审查器绑定到**自己的预设名**: ```yaml - id: permission config: presets: # 注意:id 定向覆盖会替换整行 config,需原样保留已发布的预设 read-only: sandbox: read-only approval: ask workspace-write: sandbox: workspace-write approval: ask danger-full-access: sandbox: danger-full-access approval: never auto-jev: sandbox: danger-full-access approval: never name: Auto Reviewer Jev description: TypeSafe Jev 逐工具调用授权审查(替代人工确认) - id: auto-review-jev config: preset: auto-jev ``` `auto-jev` 采用 Full access 沙箱并刻意搭配 `approval: never`,而非内置 Auto 固定的 `ask` 策略:替代人工确认的是审查器本身,它通过自己的放行对话框询问;若再叠加 `ask` 策略,只会给审查器放行的调用(`run_code` 外层传输)多加一层审批提示。 行为约定: - `preset: auto`(默认):占用 DSH 固定的 Auto 插槽。若该插槽已被其他集成占用,本插件**不会导致加载失败**,而是自动退出(纯放行、不做任何判定)并打印一条明确指向修复方式的警告——绝不会出现两个审查器同时判定同一个预设。 - `preset: <其他名字>`:完全不碰 Auto 插槽,与官方 auto review 并存;若该预设未在 `permission-presets` 中声明,同样只警告不崩溃。 - **卸载绝不移走其他集成的会话**:dispose 时迁回 `danger-full-access` 只在插件确实持有 `auto` 插槽时执行;内置占用 Auto、或本插件绑定具名预设时,相关会话与预设保持原样。 ## 账户用量与设置页 在 DSH Web 界面中,本插件只挂载**一个**界面(参考 `Mars-Sea/dsh-commandcode-provider` 的实现模式): - **设置页**:Settings 导航中的 "Auto Review Jev" 一节(`settings.section`,页面名在中文界面下也保持这个英文写法,不做翻译)。页面自上而下是:用量面板 → API 密钥(写入凭据域的 `TYPESAFE_API_KEY` 引用,密钥永不回显)→ 评估端点 → 用量端点 → 模型。 - **用量面板**(内联在设置页顶部,API 密钥上方):账户快照(头像、套餐、额度条、余额、重置时间)与本机计数的完整明细,带手动刷新与「更新于」时间戳;文案跟随界面语言。 > 侧边栏额度卡片与中间栏用量面板已移除:用量改在设置页内联呈现,侧边栏、composer 与 layout 服务不再被本插件改动。 用量数据分两层: 1. **本机计数**(始终可用):插件在 Host 侧累计每次审查的调用数、允许/拒绝/失败次数以及响应里的 `usage.input_tokens` / `usage.output_tokens`。计数器为内存态,随宿主重启清零,卡片上已明确标注。 2. **账户额度**(可选):TypeSafe 公开 API 没有官方额度查询端点,因此本插件支持一个**可配置的用量端点** `usageEndpoint`。配置后,Host 会以同一把 Bearer 密钥定期 `GET` 该端点(默认每 300 秒),并按宽松规则解析响应:字段在常见别名(`balance`/`limit`/`used`/`remaining`/`token_used`/`resets_at` 等,snake_case 与 camelCase 均可)下探测,`data`/`account`/`usage`/`quota` 等一层包裹会被展开,缺失的字段直接不显示。 浏览器永远不持有 API 密钥:所有事实通过 `jev/report` Typert Remote 由 Host 侧供给。 密钥按以下优先级解析(每次审查调用重新解析,改动无需重启): 1. 凭据域的 `TYPESAFE_API_KEY` 引用 —— 设置页保存的密钥写在这里;导出的同名环境变量也会经由凭据域的环境层被优先命中(只读来源会遮蔽已存记录)。 2. 插件配置的 `apiKey` 字段(部署层面的兜底)。 ```yaml - id: auto-review-jev name: '@dsh-external/dsh-auto-review-jev' config: # 账户额度端点(可选;留空则用量面板只显示本机计数) usageEndpoint: https://api.typesafe.ai/v1/usage # 后台轮询间隔(秒,最小 30) usageRefreshSeconds: 300 ``` ## 默认审查维度 | 维度 | 类型 | 默认阈值 | 处理 | |---|---|---:|---| | sensitive_exfiltration | noul | 0.70 | 高风险,始终拒绝 | | destructive | noul | 0.90 | 中风险;同 Session 精确清理可豁免 | | production_effect | noul | 0.80 | 中风险 | | external_write | noul | 0.80 | 中风险 | | security_change | noul | 0.80 | 中风险 | | beyond_scope | noul | 0.85 | 直接拒绝 | | explicit_authorization | noul | 0.85 | 中风险允许所需 | | authorization_conflict | noul | 0.50 | 达阈值则中风险拒绝 | | session_created_cleanup | noul | 0.80 | 仅抵消 destructive 分类 | | impact | score 0-3 | 2.50 | 中风险,最低 confidence 0.50 | `destructive=0.90` 与 `beyond_scope=0.85` 延续 `pi-jev` 的实测思路:普通、明确要求的编辑可能获得偏高的 destructive/beyond-scope 分值,阈值不能简单设成 0.5 或 0.7。 ## 配置 Cordis 配置可覆盖以下字段;通常只需要设置 `TYPESAFE_API_KEY`: ```yaml - id: auto-review-jev name: '@dsh-external/dsh-auto-review-jev' config: model: jev-latest endpoint: https://api.typesafe.ai/v1/systemone timeoutMs: 20000 retries: 2 maxStateChars: 12000 argumentChars: 600 cacheSeconds: 120 # 拒绝成为最终结论前询问用户(见「拒绝后询问用户」一节) askOnDeny: true ``` 两个端点都必须是绝对 URL,且必须使用 `https`——每次请求都会以 Bearer 凭据携带 API 密钥。仅当主机为 loopback(`localhost`、`127.0.0.1`、`[::1]`)时才允许明文 `http`, 以便本地代理仍可用于开发;其余情况会在启动、保存设置和发起请求三处被拒绝。 风险阈值也可通过同名 `*Threshold` 配置覆盖。建议在建立自己的标注集并校准前保持默认值。 ## Jev 会收到什么 每次审查最多发送: - 当前工作目录与平台; - 当前可见的项目指令(标记为 constraint); - 当前可见历史中的 human/direct-parent 指令、checkpoint/fact,以及历史工具调用事实; - 待执行工具的名称、描述、参数 Schema; - 待执行参数,长字符串按 `argumentChars` 截断; - 固定的 authority-source 元数据,明确只有 human/direct-parent 可以授权中风险动作。 不会发送 assistant 正文或 reasoning,也不会把历史 tool result 作为授权依据。整体 state 超过 `maxStateChars` 时优先丢弃最旧历史,然后进一步裁剪项目指令。 ## 开发 ```bash pnpm install pnpm typecheck pnpm test pnpm build ``` 构建产出两个 bundle:`lib/index.js`(Host 侧,ESM)与 `lib/client.js`(浏览器侧,CJS 通过 `window.__ModuleLoader__` 装载)。包通过 `dsh.bundle.patch` 声明 `cordis.patch.yml`,并通过 `dsh.client` + `exports["./client"]` 声明浏览器端入口,可作为独立 DSH plugin 安装。 ## 安全边界 Auto review 是风险降低层,不是隔离沙箱。允许的调用最终仍以 Auto preset 对应的 Full access 执行。若需要强隔离,应同时使用 DSH 自身 sandbox / deployment boundary,而不是依赖分类器代替隔离。 ## License MIT。参考实现与归属说明见 [NOTICE.md](NOTICE.md)。