DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Cordis Plugin Tool Deadline Guard — DeepSeek Harness 插件(DSH…
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
C

@argszero/cordis-plugin-tool-deadline-guard

Cordis Plugin Tool Deadline Guard

dsh 的部署级工具调用截止期限:从未声明 ToolDefinition.timeoutMs 的工具会在完全没有截止期限的情况下被调度,因此一次卡死的调用(子调用始终未完成的委派、始终不返回的第三方 MCP 工具)就会让该轮运行永久持续,而日志中没有任何信息说明原因

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:argszero/cordis-plugin-tool-deadline-guard#083025cf883a0367439deb3c383b82d8a664e8a1
README兼容性版本

说明

dsh 的部署级工具调用截止期限:从未声明 ToolDefinition.timeoutMs 的工具会在完全没有截止期限的情况下被调度,因此一次卡死的调用(子调用始终未完成的委派、始终不返回的第三方 MCP 工具)就会让该轮运行永久持续,而日志中没有任何信息说明原因。该插件挂载在公开的 tools/execute 瀑布流上,仅为这些工具启用可配置的预算,将已过期的调用转换为模型可见并可重试的结构化 TOOL_DEADLINE_EXCEEDED 结果,同时不影响已声明的预算、豁免工具和未知工具。默认采用协作式处理:它永远不会放弃等待一个工具完成的调用;在可选的替换模式下则会明确说明这一点。兼容 dsh 0.1.2、0.1.3、0.1.5 和 0.1.6 预发布版本线。

兼容性与来源证明

Cordis Plugin Tool Deadline Guard 以 @argszero/cordis-plugin-tool-deadline-guard 发布,当前版本为 0.1.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
any
发布来源
github
Registry 更新时间
2026/9/21

版本

0.1.0stable
2026/9/21

相关插件

正在加载相关插件…

最新版
0.1.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
any
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/9/21
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 developer-tools 分类下经过校验的插件。

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Sdk Minimal@deepseek-ai/dsh-sdk-minimal独立的最小 SDK 配置包:JSON-RPC、一个 DeepSeek 适配器、持久化 Shell 和 JSONL 会话Sdk App@deepseek-ai/dsh-sdk-appdsh SDK 配置包:基于 dsh-base 提供 stdio JSON-RPC 服务和进程生命周期管理Subagent Codex@deepseek-ai/dsh-subagent-codex基于官方 app-server 协议的一次性 Codex 子代理提供程序

README

@argszero/cordis-plugin-tool-deadline-guard

A cordis plugin for the DeepSeek Harness (dsh) that puts a deadline on tool calls that declare none — the calls for which nothing in the harness arms anything today.

input:  user        "keep working on the migration until it is done"
        assistant   tool_calls: [subagent(prompt="...") id=call-1]
        ⋯           nothing in the log, nothing on screen, turn still "running"

A tool call is bounded exactly when its own definition says so. ToolDefinition.timeoutMs is optional ("Cooperative tool-call timeout budget in milliseconds. Omit for no deadline"), and the core enforcer (@deepseek-ai/dsh-tool-call-timeout-policy, mounted by the base bundle) opens with if (timeoutMs === undefined) return next(). Omit the field and nothing arms anything — not a timer, not a signal, not a diagnostic.

That omission is the norm rather than the exception. Of the 40 non-test source files calling defineTool( at dsh-v0.1.6-alpha.2, 26 contain no timeoutMs at all — 25 shipped tool modules plus the test fixture helper — among them tool-subagent (delegation), mcp-resources (tools owned by someone else's server), tool-terminal, tool-ask-user, the fs read/write/edit tools, and tool-workflow / tool-ralph / tool-todo / plan-mode. Two of those are exactly where a long-horizon run spends its time: a delegation whose child never settles, and a tool on the far side of a network boundary.

The symptom is not an error. It is a turn that keeps rendering as running with nothing anywhere to explain it. Compare the model call: the provider adapters announce a stall (DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000, re-armed per chunk by idleWatchdog), so silence is the signature of a wedged tool call, not a wedged model call. Reported as discussion #7339 ("long-horizon tasks stall with nothing in the output"), and adjacent to #6226 (a foreground subagent call whose child process had already exited).

Mounted, the same call ends with something the model can act on:

        tool_result [error] "tool call to "subagent" timed out: it exceeded the
                    600000ms deadline supplied by the tool-deadline-guard mount,
                    and the tool declares no timeoutMs of its own"

Install

npm install @argszero/cordis-plugin-tool-deadline-guard

In a cordis.yml / profile bundle, add the row (or merge this package's cordis.patch.yml, which contains the same one):

- insert:
    - id: tool-deadline-guard
      name: '@argszero/cordis-plugin-tool-deadline-guard'

With no configuration it bounds every tool that declares no timeoutMs at ten minutes and dispatches every other call untouched.

fielddefaultmeaning
modeautoauto arms deadlines; observe reports what it would bound and dispatches unchanged; off registers nothing
defaultTimeoutMs600000budget for a tool that declares none (10 minutes)
perTool{}per-tool budgets by resolved tool name; wins over defaultTimeoutMs
exempt[]tool names this mount never bounds, whatever else is configured
applyToundeclaredundeclared leaves a tool that declares timeoutMs to the core policy; all bounds every known tool
onExpirycooperativewhat an expiry does; see Honest limits
warnLimit5diagnostics emitted per mount; 0 silences them

A budget must be a positive finite number ≤ 2147483647 (setTimeout's saturation point): a larger value would fire immediately, so "no deadline" is expressed by leaving a tool out of scope — exempt, or mode: off — rather than by asking for infinity.

What it does, precisely

The plugin mounts on the documented tools/execute waterfall — the same public seam the core policy uses — and for each dispatch:

  1. Resolves the tool's own definition (ctx.tools.get(exec.name, exec.agent)) and decides whether to arm at all. An unknown tool is the registry's business (UNKNOWN_TOOL), and there is no definition to weigh a budget against.
  2. Arms one timer and one controller, and hands the tool a fused signal (AbortSignal.any([caller, own])) through exec.signal, exactly as the core policy hands it its derived deadline.
  3. Restores the caller's signal in a finally, so post-execute listeners and every later wrapper never see this mount's (possibly already-aborted) signal — the rule the core policy documents for the same reason.
  4. Substitutes a result only when its own timer fired. A call that settles inside its budget returns the tool's own result object, unchanged: no re-dispatch, no wrapper, no diagnostic.

An expired budget produces the shape the loop already understands — isError: true with error.info.code === 'TOOL_DEADLINE_EXCEEDED' — under a code this plugin owns, deliberately not the core policy's TOOL_TIMEOUT: a deployment can then tell "the tool declared a budget and blew it" from "this mount supplied a budget the tool never declared", which is the difference between a tool bug and a deployment policy. ToolTimeoutError-style routing on TOOL_TIMEOUT keeps working for declared tools and does not silently capture these.

Honest limits

It does not race by default, so it cannot free a tool that ignores its signal. timeoutMs is a cooperative contract: declaring it asserts that the tool forwards exec.signal to an implementation that reaches quiescence when the signal aborts. A tool that never looks at its signal cannot be unblocked from this seam — the wrapper awaits the tool's promise, and a promise that never settles keeps the wrapper (and the turn) waiting. In cooperative mode an expiry is therefore converted into the structured error after the call has settled, which is what keeps the invariant that a committed tool/result corresponds to work that has stopped, and never before. Measured on a fixture that ignores its signal with a 60 ms budget (probe-seam.mjs): the call returned at 402 ms, and only then did the wrapper report.

It cannot see why a call settled. A tool that honors the signal returns an abort artifact; one that ignores it and finishes anyway returns its own late result; both arrive here as "the promise resolved after the timer fired". The core policy resolves that ambiguity by contract — the tool's author declared a budget, so the tool promised to stop — and this plugin resolves it the same way for a tool that declared nothing, which is a promise the deployment is making on the tool's behalf. The consequence, stated plainly: a stubborn tool's late successful result is replaced by the deadline failure. A tool whose late result matters must be listed in exempt. The diagnostic claims only the timing ("x" settled after 402ms, past its 40ms budget; its result was replaced) and never asserts that the tool observed the abort, because this seam cannot know that.

onExpiry: 'replace' buys a usable turn with a weaker guarantee. The wrapper returns the deadline failure as soon as its timer fires and lets the call keep running, unobserved. It does not leave an orphan tool/call in the log — the loop records a tool/result for the call it dispatched, ours (packages/core/agent-loop/src/tool-calls.ts:282), so the transcript stays balanced and the next request is accepted; the abandoned branch is settled with a no-op so a later rejection cannot surface as an unhandled rejection. What it does do is let a tool's side effects happen after the model was told the call failed, so a model that retries may duplicate them. For a tool that never settles, this is the only mode that returns at all.

applyTo: 'all' is for deployments without the core policy. The base bundle mounts timeout-policy unconditionally, so on a stock profile undeclared is the correct default: two wrappers racing one budget would produce two contradictory codes for one call. all exists for a composition that dropped the policy — and for the case where a tool's declared budget is larger than the deployment is willing to wait.

Verifying it

npm test                # tsc, then the wiring suite and the packaging guard
python3 mutations.py    # each claim above, broken one at a time, must go red
node probe-seam.mjs     # the numbers above, against a real registry
node probe-tools.mjs    # what a real ToolRuntime accepts and what a wrapper sees

test/cordis.spec.mjs mounts a real Cordis context, a real ToolRuntime and a real tools/execute waterfall, with an unmounted control arm that demonstrates the defect (with no plugin, a tool that declares no budget and never settles stays pending and nothing reports it). mutations.py edits the built artifact — never the source — and requires the suite to fail for each of the eight mutations, so a claim no test actually holds cannot pass unnoticed. test/packaging.spec.mjs asserts both directions of the manifest against the shipped imports (a runtime import declared only in devDependencies is a consumer's ERR_MODULE_NOT_FOUND; a declared package never imported widens the install closure for nothing).

Compatibility

Requires the tools/execute waterfall, ToolDispatchExecution, ToolDefinition.timeoutMs and ctx.tools.get(name, agent), all present in @deepseek-ai/dsh-tools from 0.1.2-rc.1 through 0.1.6-alpha.2 (checked by unpacking each published tarball and reading its d.ts and lib/index.js). @deepseek-ai/cordis ^4.0.2 is a peer; @deepseek-ai/schemastery is the only runtime dependency.

License

MIT