← Back to home@baldovinmarques391-design

dsh-ban-wait

拒绝会把智能体循环钉住的工具参数:阻塞等待、临场延长超时,以及可选的同步长任务。

Stars
0
Language
JavaScript
Created
Oct 17, 2026
Updated
Oct 17, 2026

Introduction

dsh-ban-wait

One Host plugin that refuses tool arguments which would park the DSH agent loop.

It exists because the shell command timeout is not a ceiling. pwsh-sandbox's timeoutMs: 1000 promotes a long command to a background job, but the model can pass timeoutMs: 180000 on the call itself and the executor honors it up to maxTimeoutMs (600000 ms by default) — three minutes inside one tool call. The same class of leak is job_output { wait: true }, clamped only by a 10-minute default. This plugin makes "no single call parks the loop" a property of the tool registry instead of a property of the model's discipline.

Files

FileRole
index.jsthe plugin: policy + global ctx.tools.guard()
check.mjsoffline self-check of the policy (30 assertions, no DSH process)
bundle-check.mjspre-install check: replays what the plugin page inspects, with the harness's own patch loader
cordis.patch.ymlthe bundle's patch: inserts the Host-plane row
package.jsondsh.bundle.patch → cordis.patch.yml; no dependencies

Policy

Config fieldDefaultMeaning
maxBlockingMs1000ceiling for any per-call blocking hint the model supplies
denyWaitTools["job_output"]tools whose wait: true is refused
timeoutHintTools["pwsh", "bash"]tools whose timeoutMs / timeout_ms is checked against maxBlockingMs
requireBackgroundTools[]tools that must be called with run_in_background: true

A refusal is a Error: … tool result that names the ban, clears the background job of blame, and states how to re-issue the call, so the model recovers in one step. An unusable config value is logged and replaced by its default rather than thrown: a typo here must not fail the instance's boot.

Install

Through the plugin page — the only channel that keeps the plugin visible in the inventory with its own toggle:

  1. 添加插件 → paste the absolute project directory (the checkout you just cloned). A local directory is recognised by isAbsolute() before any npm-spec parsing and never touches a registry, so the registry chooser in that dialog is irrelevant.
  2. The manager reads package.json, requires a name and a dsh.bundle, adds the dependency to the profile, then reconciles dsh.profile.bundles automatically ("new bundle dependencies activate automatically"). The plugin then appears under 已安装 with a toggle.
  3. If the result reports that a restart is required, restart the instance: a new Host module generation is not loaded into a running process.

Do not also add a raw loader row for this plugin to the profile patch. The bundle's cordis.patch.yml inserts row id ban-wait, and two rows sharing that id make id-targeted patches — the toggle, any override — reach only one of them.

Companion configuration, without which the guard is the only thing standing between the model and a three-minute call:

- id: pwsh-sandbox
  name: "@deepseek-ai/dsh-pwsh-sandbox"
  config:
    timeoutMs: 1000       # default for a command
    maxTimeoutMs: 1000    # ceiling for the model's timeoutMs argument — the line that matters
    maxOutputBytes: 10000

Verify

# the policy itself (30 assertions)
node check.mjs

# the package the plugin page will inspect: path, package.json, name, dsh.bundle,
# `files` coverage, and a real parse of cordis.patch.yml by the harness loader
node bundle-check.mjs

Neither proves activation — that happens when the profile mounts the bundle's layer, which the plugin page reports in its install result.

Live probes (each returns a refusal instead of executing):

job_output   { job_id: "pwsh-1", wait: true }        → Error: … (ban-wait): job_output was called with wait: true
pwsh         { command: "ok", timeoutMs: 180000 }    → Error: … caps how long one tool call may hold the agent loop at 1000 ms
pwsh         { command: "ok" }                       → runs normally
workflow     { script: "return 1" }                  → runs synchronously (not banned by default)

Verified on instance 3080: both refusals fire, plain calls and non-blocking job reads behave exactly as before, and the row mounts and unmounts live — no restart was needed for any of it.

Boundaries

  • The guard sees arguments, not duration. A tool with no blocking argument that simply runs long is out of its reach; that is what requireBackgroundTools is for, and what the shell maxTimeoutMs cap already handles for commands.
  • ask_user_question is deliberately never banned: blocking on a human is the feature.
  • workflow and a synchronous subagent are not banned by default. They do real work inside the call, so backgrounding them changes how the result is consumed. Enable per tool when that trade is wanted:
    requireBackgroundTools: [workflow, subagent]
    

Superseded

This plugin began as a single-rule ban that refused only job_output { wait: true }; that early version has been folded into the policy shipped here, so there is nothing left to install separately.