@dsh-external/dsh-auto-review-jev
English | 中文
@dsh-external/dsh-auto-review-jev gives DeepSeek Harness's Auto permission preset a per-tool-call authorization review powered by TypeSafe Jev.
Design references:
- DeepSeek Harness
packages/experimental/auto-review: the Auto preset, the tools/pre-execute review point, PTC inner-call coverage, the permission lifecycle and fail-closed semantics.
y0usaf/pi-jev: Jev typed questions, calibrated thresholds, argument trimming and a short-lived verdict cache.
The main difference from the upstream Auto review: this plugin does not ask the reviewer for free-text risk/decision JSON. Jev returns several independent noul / score numbers and the plugin combines them locally with fixed rules into a final allow / deny.
Changes per release are listed in CHANGELOG.md.
The name and the icon the composer shows for the Auto preset come from a separate package,
dsh-client-ui-permission-presets-jev
— see Companion UI plugin. This plugin reviews
calls with or without it.
Screenshots
Captured from a live dsh --profile web on DeepSeek Harness 0.1.7-alpha.1, with this plugin
occupying DSH's Auto slot.
Picking the preset. The reviewer owns DSH's single Auto slot, so the composer's permission
selector is the only switch there is. The name and the icon come from the
companion UI package; without it this row still
reads Auto review.
The permission selector, with Auto Review Jev EXP
Turning it on. The experimental-enable dialog is DSH's own; only the name is replaced.
The enable Auto Review Jev confirmation
A denied call, and the human reprieve. A denial is not the end of the story: the plugin asks
through the user-questions seam, and the dialog states the verdict, the tool and the call about
to run. Allow once runs that one call; Keep denied keeps the denial.
The reprieve dialog
Why it was denied, in the transcript. The denial carries its reason (review_error: … for a
review that could not complete, risk: … for a verdict) and says whether the human was asked.
The denial, with its reason, in the transcript
The settings page. The usage panel leads, then the credentials and the endpoints: the API
key, the evaluation endpoint and the usage endpoint, with the model field below the fold. The
counters are this host's, in memory and labelled as such; the account quota appears here only
when a usageEndpoint is configured.
The Auto Review Jev settings page
The dialog and the page follow the interface language — Chinese, in the same host:
The reprieve dialog in Chinese
The settings page in Chinese
Behavior
When the current session selects Auto, every supported native tool call and every started PTC inner call passes through Jev before its body executes.
- Low risk: ordinary in-project reads and writes, analysis, formatting, tests, builds — allowed.
- Medium risk: breaking existing state, production effects, external writes, permission/security-control changes, high-impact operations. Allowed only when the current human/direct-parent instruction explicitly authorizes the action, its target and the necessary scope, with no conflict or overreach.
- High risk: leaking sensitive data across a trust boundary — always denied.
- Jev timeout, throttling that survives the retries, malformed responses, or a context/schema that cannot be rebuilt reliably: deny (fail closed).
- Denials carry their reason: a risk verdict shows
risk: …, a failure of the review itself shows review_error: … (for example review_error: HTTP 401 (missing or invalid API key)). The reason lands in both the visible copy and the structured info.reason, so a wall of denials caused by a bad key can no longer be misread as a risk decision.
The outer run_code is PTC transport and is not reviewed on its own; each of its PTC inner tool calls is reviewed separately. As with upstream Auto review, direct Node.js side effects inside a run_code program that bypass the DSH tool registry are outside this plugin's review scope.
Human reprieve on a denial
A denial is final by design, but the human is the authority the review itself defers to — so the plugin asks before the call is really blocked:
- On any denial — a risk verdict (
risk: …) or a failure of the review itself (review_error: …) — the plugin asks through DSH's user-questions seam (ctx.userQuestions, the seam behind the model's ask_user_question tool).
- The dialog is written in the language the Web UI is rendering in (the browser half reports it, and anything but Chinese falls back to English) and it states what was reviewed: the verdict, the dimensions that fired with their scores, the tool, and the call about to run. Its options are 允许本次执行 / Allow once and 保持拒绝 / Keep denied in that same language.
- An allow lifts the denial for that one call only: the tool runs exactly as it would have, and later calls are still reviewed. A grant is never remembered and never widens a later decision.
- Everything else keeps the denial: no user-questions answerer mounted (headless, or the client plugin absent), a call inside a subagent (human interaction is available only to a live root agent), a call aborted while asking, or an answer that is not recognisably an allow.
- The transcript keeps explaining itself — the denial suffix records that the user was asked and kept the denial, that nobody could be asked, or that the call was aborted mid-question.
- Prompts are serialized per session, so parallel tool calls in one step cannot stack several questions at once.
askOnDeny: false restores a reviewer that never asks.
Why not DSH's own { kind: 'ask' } decision? That path routes through ctx.approval, whose decide() answers 'rejected' immediately while the session policy is never — the policy this plugin's own preset pairs with Full access, and the one delegated children pin. The reprieve therefore asks the user-questions seam directly, which works under any approval policy. The deviation is deliberate and narrow: the human may override a denial, the model never may.
Installation
@dsh-external is not a publishable scope on npm, so the package is installed into a web profile from a release tarball or from a source checkout:
# from the tarball attached to any release (no build step, no pnpm allowlist needed)
dsh plugin --profile web add ./dsh-external-dsh-auto-review-jev-<version>.tgz
# or straight from a source checkout (development)
dsh plugin --profile web add /path/to/dsh-auto-review-jev
Configure the TypeSafe API key:
export TYPESAFE_API_KEY="..."
You can also paste it into DSH Web's Settings → Auto Review Jev page (it is written through the credentials domain and never echoed back).
Then pick the preset in the permission selector: Auto by default, or the dedicated preset name when you bind the reviewer to its own preset (see Coexisting with DSH's built-in auto review).
Without TYPESAFE_API_KEY the plugin still loads, but it will not let you switch to that preset; if a session is already running under the preset while the key is invalid, its tool calls fail closed.
Auto is DSH's single fixed integration point: exactly one reviewer can hold it, the first registrar keeps it, and a second claimant cannot take it. Do not run two Auto reviewers at once — when the built-in already holds the slot this plugin steps aside with a warning (INACTIVE), and when this plugin holds it the built-in's own mount fails.
If you need both installed at the same time, bind this plugin to its own preset name (preset: auto-jev, displayed as "Auto Reviewer Jev") — see the coexistence section below.
Installing from GitHub: pnpm blocks the build script (allowBuilds)
dsh plugin --profile web add github:sperictao/dsh-auto-review-jev
On pnpm ≥ 10 that command fails:
[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/<sha>": 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.
The cause is not anything unusual in this package: build output is not committed, and pnpm does not trust dependency lifecycle scripts by default. The package uses prepare (tsdown) to produce lib/, and lib/ is git-ignored, so a git-sourced install has to build once on the spot. pnpm's supply-chain defence refuses third-party lifecycle scripts by default — registry sources report Ignored build scripts: … (strictDepBuilds defaults to true), git sources fail outright.
Three ways out, in order of preference:
1. Install the prebuilt tarball (recommended, no allowlisting at all)
dsh plugin --profile web add ./dsh-external-dsh-auto-review-jev-<version>.tgz
The tarball already contains a built lib/, so the install never runs prepare and never trips the block.
2. Install through the DSH Pro Max launcher (one-click approval)
Enter github:sperictao/dsh-auto-review-jev (the owner/repo form works too) under Custom install on the marketplace page. When pnpm blocks it, the launcher raises an Allow build scripts? dialog listing the exact keys that need approving; Approve & install writes those keys into the profile's pnpm-workspace.yaml and reruns the install for you — no manual file editing. Since DSH Pro Max v0.8.26 both key shapes are recognised: name@git+https://…#<sha> and name@https://codeload.github.com/…/tar.gz/<sha>.
3. Allowlist it by hand
Copy the keys printed in pnpm's allowBuilds: example block verbatim into the profile's pnpm-workspace.yaml:
- macOS / Linux:
~/.dsh/profiles/web/pnpm-workspace.yaml
- Windows:
%USERPROFILE%\.dsh\profiles\web\pnpm-workspace.yaml
allowBuilds:
# a key starting with @ MUST be quoted: @ is a YAML reserved character and a
# bare key breaks the whole profile config
'@dsh-external/dsh-auto-review-jev@git+https://github.com/sperictao/dsh-auto-review-jev.git#<sha>': true
# pnpm 10 uses this list key; pnpm 11 and later use allowBuilds above, where the old key no longer applies
onlyBuiltDependencies:
- '@dsh-external/dsh-auto-review-jev'
Then rerun the install. Running pnpm approve-builds inside that profile directory also works — it writes the approved package into the same allowBuilds; if the interaction does not finish, the key may be left as a placeholder (set this to true or false) that you have to change to true (the launcher's approval flow overwrites the placeholder automatically).
Three things worth knowing:
- The key pnpm prints is pinned to a commit: it points at the concrete source resolved this time (
#<sha>, or /tar.gz/<sha> when fetched through codeload). Once the repository has a new commit, the next install prints a new key and needs another approval.
- To approve once and stay approved, write the repository-shaped key by hand (no
#<sha>): '@dsh-external/dsh-auto-review-jev@git+https://github.com/sperictao/dsh-auto-review-jev.git': true. On pnpm ≥ 11.19.0 it covers both fetch paths (clone and codeload tarball), so later commits need no re-approval.
- Approval is profile-scoped: an entry applies to every install in that profile and deleting it restores the block.
Supported versions
- DeepSeek Harness
0.2.0-rc.1 — the peer ranges admit nothing older, and the harness checks a plugin's declared DSH peers against its own runtime, at install and at profile startup alike; a mismatch refuses the plugin.
@deepseek-ai/cordis ~4.0.4 — the range every package in that harness family declares.
Older harness versions are not supported: the peer check refuses them outright, and the host half no longer recognises a 0.1.6 compaction checkpoint source. If the plugin does not load, check the peer refusal it reports first (a profile can grant an exact-version exemption with dsh plugin allow-version, at its own risk), then the preset-conflict warning it logs (the warning names the fix), then whether the settings page is disabled — a disabled page means the Host predates configForms.
Companion UI plugin: the preset's name and icon
The reviewer runs host-side. What the composer draws is another package's business: DSH's
@deepseek-ai/dsh-client-ui-permission-presets owns the permission selector, and its
Auto review label, its EXP badge and its icon are hard-coded in there. The label is
registered into the permission.access locale namespace, which the locale runtime refuses to
re-register, and the glyph table is a module-private Map the package does not export — so no
profile config and no second plugin can reach either one.
dsh-client-ui-permission-presets-jev
is that package with three changes, kept as a copy of the upstream source at 0.1.7-alpha.1 and
diffable against it:
| upstream | the fork |
|---|
| preset name | Auto review | Auto Review Jev (both languages, the enable dialog included) |
| badge | EXP | unchanged |
| icon | official presets only | a new auto glyph: the official shield outline with a four-point star inside, in currentColor |
The two halves are independent, and only one of them reviews anything:
- This plugin alone: the reviewer runs, denials carry reasons, the settings page works — and
the composer still reads
Auto review with the upstream icon.
- The fork alone: the composer reads
Auto Review Jev with its own icon and nothing reviews
the calls. It renames DSH's built-in auto review; it does not add a reviewer.
Install both to get the reviewer and matching naming.
Installing the fork
The fork ships a cordis.patch.yml that sets the built-in ui-permission row to
disabled: true and inserts its own ui-permission-jev. Both register the same seats
(conversation.input.permission, settings.general.item#permission, the /permission
decoration) and claim the same locale namespaces, so exactly one may be loaded — the disable
is part of the install, not an option.
# from GitHub — the install runs `prepare` (one tsdown build) on the spot
dsh plugin --profile web add github:sperictao/dsh-client-ui-permission-presets-jev
# or from a source checkout
dsh plugin --profile web add file:/abs/path/to/dsh-client-ui-permission-presets-jev
Then reload the page. patchReload: live recomposes the module list per request, and restarting
dsh web works too.
Verify the composed tree — the built-in row must be disabled, the fork inserted:
dsh --profile web --dump-config | grep -B2 -A3 'ui-permission'
Removing it restores the upstream UI:
dsh plugin --profile web remove @dsh-external/dsh-client-ui-permission-presets-jev
ui-permission stops being disabled and the label and icons revert; the upstream package itself
was never modified.
Coexisting with DSH's built-in auto review
DSH's auto preset admits exactly one integration: a second plugin calling permissionPresets.registerAuto() throws preset "auto" is already registered. The usual occupant is DSH's own shipped reviewer, @deepseek-ai/dsh-experimental-auto-review (row auto-review, off by default and switched on from the Web Plugins page); the permission service fixes its bundle as Full access with the ask approval policy, and the option exists only while that integration is live. To avoid fighting it for the same slot, this plugin can bind its reviewer to its own preset name:
- id: permission
config:
presets:
# note: an id-targeted override replaces the WHOLE config row, so every
# shipped preset has to be restated
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 per-tool-call authorization review (replaces manual confirmation)
- id: auto-review-jev
config:
preset: auto-jev
auto-jev pairs Full access's sandbox with approval: never, deliberately not the ask policy the built-in Auto fixes: the reviewer is what takes the place of manual confirmation, and it asks on a denial through its own reprieve dialog, so an ask policy would only stack a second prompt onto calls the reviewer passes through (the run_code transport).
Behavior guarantees:
preset: auto (default): occupies DSH's fixed Auto slot. If another integration already owns that slot, this plugin does not fail to load; it steps aside (pure pass-through, no verdicts) and logs a warning that names the fix — two reviewers can never judge the same preset.
preset: <other name>: never touches the Auto slot and coexists with the official auto review; with an undeclared preset it again only warns instead of crashing.
- Unloading never migrates another integration's sessions. The dispose-time move back to
danger-full-access runs only while this plugin actually owns the auto slot; with the built-in holding Auto, or with this plugin on a named preset, live sessions and their presets are left exactly as they are.
Account usage and the settings page
In the DSH Web UI this plugin mounts one surface (following the pattern of Mars-Sea/dsh-commandcode-provider):
- Settings page: the "Auto Review Jev" section in the Settings navigation (
settings.section; the page name keeps that English spelling in every UI language, deliberately untranslated). Top to bottom: usage panel → API key (a TYPESAFE_API_KEY reference in the credentials domain; the key is never echoed back) → evaluation endpoint → usage endpoint → model.
- Usage panel, rendered inline at the top of the settings page, directly above the API key: the account snapshot (avatar, plan, quota bars, balance, reset time) and this host's counters in full, with a manual refresh and an "Updated" timestamp. Its copy follows the UI language.
0.2.4 removed the sidebar quota card and the center-column dashboard: usage is presented inline on the settings page, and the sidebar, the composer and the layout service are no longer touched by this plugin.
Usage data comes in two layers:
- Local counters (always available): the host accumulates the number of review calls, the allow/deny/failure tallies and the
usage.input_tokens / usage.output_tokens from every response. Counters are in-memory, reset when the host restarts, and say so in the UI.
- Account quota (optional): TypeSafe's public API has no official quota endpoint, so the plugin supports a configurable
usageEndpoint. Once set, the host polls it with the same Bearer key (every 300 seconds by default) and decodes the body permissively: fields are probed under common aliases (balance/limit/used/remaining/token_used/resets_at, snake_case and camelCase alike), one level of envelope (data/account/usage/quota) is unwrapped, and missing fields are simply not shown.
The browser never holds the API key: every fact is served host-side over the jev/report Typert Remote.
Key resolution order (re-resolved on every review, so edits need no restart):
- The credentials-domain
TYPESAFE_API_KEY reference — where the settings page stores the key; an exported environment variable of the same name is also picked up through the credentials domain's environment layer (a read-only source shadows a stored record).
- The plugin config's
apiKey field (a deployment-level fallback).
- id: auto-review-jev
name: '@dsh-external/dsh-auto-review-jev'
config:
# account-quota endpoint (optional; empty = the panel shows local counters only)
usageEndpoint: https://api.typesafe.ai/v1/usage
# background poll interval in seconds (minimum 30)
usageRefreshSeconds: 300
Default review dimensions
| Dimension | Type | Default threshold | Handling |
|---|
| sensitive_exfiltration | noul | 0.70 | high risk, always denied |
| destructive | noul | 0.90 | medium risk; exempted by exact same-session cleanup |
| production_effect | noul | 0.80 | medium risk |
| external_write | noul | 0.80 | medium risk |
| security_change | noul | 0.80 | medium risk |
| beyond_scope | noul | 0.85 | denied outright |
| explicit_authorization | noul | 0.85 | required for a medium-risk allow |
| authorization_conflict | noul | 0.50 | at threshold, medium risk is denied |
| session_created_cleanup | noul | 0.80 | only offsets the destructive classification |
| impact | score 0-3 | 2.50 | medium risk, minimum confidence 0.50 |
destructive=0.90 and beyond_scope=0.85 follow pi-jev's measured experience: ordinary, explicitly requested edits can score surprisingly high on destructive/beyond-scope, so those thresholds cannot simply be set to 0.5 or 0.7.
Configuration
Cordis config can override the following fields; usually you only need TYPESAFE_API_KEY:
- 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
# ask the human before a denial becomes final (see "Human reprieve on a denial")
askOnDeny: true
Both endpoints must be absolute URLs, and both must be https — the API key is
sent on every request as a Bearer credential. Plain http is accepted only for
loopback hosts (localhost, 127.0.0.1, [::1]), so a local proxy stays
usable in development; anything else is refused at boot, at the settings save,
and at the request itself.
Risk thresholds can also be overridden through same-named *Threshold config keys. Keep the defaults until you have built and calibrated your own labelled set.
What Jev receives
Each review sends at most:
- the current working directory and platform;
- the currently visible project instructions (marked as constraints);
- human/direct-parent instructions, checkpoint/fact entries and historical tool-call facts from the visible history;
- the name, description and argument schema of the tool about to run;
- the arguments themselves, with long strings truncated to
argumentChars;
- fixed authority-source metadata stating that only a human/direct-parent can authorize a medium-risk action.
Assistant prose and reasoning are never sent, and historical tool results are never used as authorization evidence. When the whole state exceeds maxStateChars, the oldest history is dropped first, then the project instructions are trimmed further.
Development
pnpm install
pnpm typecheck
pnpm test
pnpm build
The build emits two bundles: lib/index.js (host side, ESM) and lib/client.js (browser side, CJS loaded through window.__ModuleLoader__). The package declares cordis.patch.yml via dsh.bundle.patch and its browser entry via dsh.client + exports["./client"], so it installs as a standalone DSH plugin.
Security boundary
Auto review is a risk-reduction layer, not an isolation sandbox. Allowed calls still execute with the Full access the Auto preset grants. If you need hard isolation, use DSH's own sandbox / deployment boundary as well instead of relying on a classifier in place of isolation.
License
MIT. See NOTICE.md for the reference implementations and attribution.