DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Test Closure — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
T

dsh-test-closure

Test Closure

Closed-loop test evaluation for DeepSeek Harness: probe the test framework, run tests under the session sandbox policy, and block turn completion while tests still fail

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:freeze-F/dsh-test-closure#dd49cc3deebaa2a5a417d45938ac38d32268cbca
READMECompatibilityVersions

Compatibility and provenance

Test Closure is published as dsh-test-closure and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
github
Registry updated
9/30/2026

Versions

0.1.0stable
9/30/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
MIT
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
9/30/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in developer-tools.

Cc Safety Netcc-safety-netA coding agent CLI hook - block destructive commands and secret file accessVision Toolkit@anionex/dsh-vision-toolkitDeepSeek Harness-native integration for agent-vision-toolkit: image Q&A, OCR, grounding, UI restoration, pixel diff, Artifacts, and Web UI.Remotedsh-remoteRemote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive/proxy jump), pick a remote workspace, operate on it with 22 rw_* tools (incl. rw_edit/rw_stat/rw_mkdir/rw_remove/rw_move/rw_forward), conflict-aware mirror syBridge@wenbin_wb/dsh-bridgeDSH remote connection/remote control plugin: scan a QR code with your phone to continue using DeepSeek Harness on mobile or over the public internet, even when you are away from your computer. One-click LAN QR codes, Cloudflare public tunnels, self-hosted tunnels, and WeChat / QQ / Feishu / Telegram

README

dsh-test-closure

A closed loop for DeepSeek Harness: probe the test framework → run the suite → summarize structurally → block turn completion while it is still red → retry → release or mark the session unresolved.

The plugin is a Cordis plugin bundle. It contributes three model tools and one turn-stopping gate, and it owns no service of its own.

What it contributes

RowKindPurpose
test_probetoolDetect vitest / jest / an npm test script / pytest / node:test, without running anything
test_runtoolRun the suite under the session sandbox policy, return counts + per-case failures + an output tail
test_closure_checktoolReport whether this session satisfies the completion condition
agent/turn-stopping listenergateSteer a corrective message back into the agent while the last run is red

Install

Into a profile (this is what makes the row part of the composition):

dsh plugin --profile web add /absolute/path/to/dsh-test-closure

Then list it as a bundle layer in the profile's package.json:

{
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-test-closure"]
    }
  }
}

The bundle layer inserts one row over whatever composition it is stacked onto, so nothing else has to be edited. Address the row by id from a later layer to reconfigure or disable it — a patch replaces a row's whole config, so restate every field you need:

# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: dsh-test-closure
  disabled: true

- id: dsh-test-closure
  config:
    maxInterceptRounds: 1
    summaryTailChars: 4000

Which plane it belongs to

The patch inserts the row at the bundle layer, i.e. the host composition: the plugin publishes no service, so it cannot collide with a second session, and its turn-stopping listener is a single process-wide listener keyed by session id. It is equally usable from an agent preset — it is a pure consumer of tools, shell, and sandboxPolicy, so it needs no isolate realm — but a preset-local copy would give each session its own closure state, which is only interesting if you want per-preset testing policy rather than per-deployment.

Configuration

Validated by the loader before apply runs; anything the schema cannot express (integrality, positivity) is rejected in apply with a message naming every offending field.

FieldTypeDefaultMeaning
maxInterceptRoundsinteger ≥ 02Corrective interceptions allowed per turn
defaultTimeoutMsinteger ≥ 1120000Timeout when a call omits timeout_ms
summaryTailCharsinteger ≥ 03000Characters of raw output kept as outputTail
enabledbooleantrueMaster switch: false contributes no tools and no gate

The loop

test_probe        → framework, config path, test files, suggested command
test_run          → { ok, summary{total,passed,failed,skipped}, failures[], outputTail }
   ↓ red
agent/turn-stopping ── gate ──▶ agent.steer("…请修复后重新运行 test_run 验证。")
   ↓ the loop re-reads its next-step inbox and runs another step
test_run again    → green → test_closure_check passes → the turn closes
   ↓ still red after `maxInterceptRounds`
turn released, session marked `unresolved`

How the interception actually works

agent/turn-stopping is a serial event: its listeners run in order and its return value is not a veto. The documented objection channel is agent.steer(message) — the loop re-reads its next-step inbox immediately after the dispatch and runs another step when a listener put something there. The injected message carries the plugin source tag ({kind:'plugin', plugin:'dsh-test-closure', form:'notice'}), so it renders as a plugin notice rather than as a user prompt.

Budget semantics

The budget is counted per turn, not per session — the contract says "each turn may be intercepted at most maxInterceptRounds times". A session-lifetime budget would mean two unlucky turns early in a long session silently disable the gate for the rest of it.

unresolved is therefore a record, not a switch: it reports that a turn ended still red after its budget was spent, and it is surfaced by test_closure_check. A later turn gets a fresh budget; a passing run is what clears the marker. Within any single turn the gate can never loop, because the budget bounds it and a new turn requires new input.

What the gate does not do

It never guesses. A session with no recorded run passes: the gate withholds completion only on evidence of failure. It also does not watch the filesystem — if code changes after a green run without re-running the tests, the recorded verdict stays green until the next test_run.

Sandbox compliance

test_run does not spawn anything itself. It resolves the calling session's policy through ctx.sandboxPolicy.resolve({ session }) and hands the command to ctx.shell — the same seam, policy, process management, and output budget the built-in bash tool uses:

const policy = ctx.sandboxPolicy.resolve(exec.agent === undefined ? {} : { session: exec.agent.session })
await ctx.shell.run(ctx.shell.resolve({
  command, workdir: policy.workspaceRoot, timeoutMs,
  dshEnv: ctx.shellEnv.collect(exec),
  sandboxPolicy: policy,
  signal: exec.signal,
}))

Resolution priority is the policy owner's: an explicitly approved mode beats the session's last sandbox/mode event, which beats the deployment default. The workspace root and the confinement are read from the same resolved object, so they cannot disagree. The resulting policy mode is reported on every run (sandbox.mode), and a sandbox denial is reported as such rather than as a test failure.

The probe only reads: it walks the workspace breadth-first with an entry budget, never follows symlinked directories, and skips node_modules, build output, and virtualenv directories.

Framework detection

First match wins:

  1. package.json declares vitest
  2. package.json declares jest
  3. package.json has a scripts.test entry
  4. pytest markers (pyproject.toml, pytest.ini, conftest.py, setup.cfg, tox.ini)
  5. *.test.* / *.spec.* files, or a test/-style directory

One documented refinement: when only Python test files exist and nothing declares pytest, the probe answers pytest rather than node:test. Rule 5 would name a runner that collects no Python file at all.

Output parsing

Each runner's default and compact reporters are parsed:

FrameworkCountersFailures
vitestTests 1 failed | 3 passed (4)FAIL file > name blocks, × name + → message
jestTests: 1 failed, 3 passed, 4 total● name blocks, ✕ name
pytest===== 1 failed, 11 passed in 3.2s =====FAILED nodeid - message, traceback sections
node:test# tests 2 (TAP) and ℹ tests 2 (default reporter)not ok N - name (TAP), ✖ name under ✖ failing tests:

Parsing is total — unrecognizable output yields empty counts instead of throwing — and ok is decided by the process outcome, never by a parse: ok = !timedOut && !aborted && exitCode === 0 && failed === 0.

test_run parameters

ParameterMeaning
frameworkOverride detection: vitest / jest / npm-test / pytest / node:test
targetSpace-separated paths or node ids appended to the command
last_failedRe-run only the cases the previous run reported as failing
commandFull command override, run verbatim; ignores target and last_failed
timeout_msTimeout for this run (defaults to defaultTimeoutMs)
run_in_backgroundStart the suite as a job and return its id

last_failed is expressed in each runner's own dialect: -t <regex> for vitest/jest, -k <expr> for pytest, --test-name-pattern for node:test. A runner that cannot filter by name (npm-test) degrades to the full suite and says so in notes.

Known limits, all reported through notes rather than silently ignored:

  • target values are passed as path filters; vitest and jest do not accept a bare test name there.
  • A background run records its result when the job settles, so the gate sees it one job-poll later, and its outputTail is assembled from the job's stream reads.

Development

npm install     # dev dependencies, including TypeScript
npm run build   # tsc: src/ → lib/, declarations to lib/types/
npm test        # node --test test/*.test.mjs

The test suite has three layers:

  • unit — parsing (fixtures captured from real runners), probing, command planning, closure state;
  • integration — the real apply against a stub context, so defineTool compiles both schemas for real and the gate is driven with a fake agent;
  • real runtime — an actual Cordis application (real tool registry, real ctx.shell, real subprocess backend, real ctx.sandboxPolicy) running an actual test suite in a temporary workspace, including the mount → red run → interception → fix → green → turn closes sequence.

Setup notes

lib/ is committed, because a profile installs this package by path or URL and must find built output. Run npm run build and commit the result whenever src/ changes.

The runtime packages are peer dependencies and are never bundled: at run time they resolve from the dsh installation's shared module fallback ($DSH_HOME/profiles/node_modules). npm install fetches the newest release matching each declared range, which is the ordinary way to get a build. To type-check against the exact versions one deployment loads instead, point node_modules/@deepseek-ai at that installation's own node_modules/@deepseek-ai — that is what this working copy does.

The plugin needs @deepseek-ai/cordis, @deepseek-ai/dsh-tools, @deepseek-ai/dsh-llm, @deepseek-ai/dsh-shell, @deepseek-ai/dsh-sandbox-policy and @deepseek-ai/schemastery at runtime; they are declared as peer dependencies and resolve from the dsh installation's shared module fallback ($DSH_HOME/profiles/node_modules), not from this package.