- Home
- Plugin
- Strumenti e capacità
- dsh-tool-wait-until
dsh-tool-wait-until
tongsh6/dsh-tool-wait-until
Event-driven wait for DeepSeek Harness: returns the moment the target completes, fails early, or stalls — instead of burning a full timeout and querying again. Also blocks sleep-polling and over-long waits.
Installazione
dsh plugin --profile web add github:tongsh6/dsh-tool-wait-untilREADME
dsh-tool-wait-until
Event-driven waiting for DeepSeek Harness (dsh). It gives the model a wait_until tool that returns as soon as a watched target settles, fails, or stalls. Job conditions are woken by job events (settle / output growth); file, log and teammate conditions are sampled at poll_interval_sec (default 2s). Fail-fast on nonzero exit codes, error patterns and stalls applies instead of waiting out a full timeout. It also blocks the model's favorite empty-wait paths: sleep-polling in bash and over-long job_output / wait_agent waits.
dsh plugin --profile web add dsh-tool-wait-until
Requires dsh 0.2.1 or later (declared as engines.dsh); verified against 0.2.1-rc.5. In the desktop app, install it from its plugin manager instead.
- One call, one verdict:
statusiscompleted/failed/stalled/timeout/cancelled, pluselapsed,failure_reasonandoutput_tail— no second query needed. - Event-driven: job settle / output growth wakes the wait immediately, independent of
poll_interval_sec. - Stall detection: no progress for N seconds returns
stalledinstead of waiting out the timeout. Job conditions default to 180s (stall_after_sec: 0disables). - Actionable timeout: a timeout returns
suggested_timeout_sec(≈2× elapsed, min 120) so the model retries once instead of creeping 60→90→120. - Behavior guard: foreground
sleep >= 20sand waits over 120s are rejected with a ready-to-use replacement; repeatedjob_outputpolling is discouraged in the prompt policy. - Zero dependencies, standard JSON Schema, Node 20+.
中文说明见下。
消除 DSH 里「空等」的 Host 插件。它给模型一个事件驱动的等待原语 wait_until:目标完成、失败或连续无进展时立即返回。事件唤醒只适用于作业条件(订阅 jobs.events);文件、日志、队友条件按 poll_interval_sec 采样(默认 2 秒)。插件主动拦截模型最容易走的空等路径(sleep 轮询、超长 job_output / wait_agent、反复轮询 job_output)。作业条件默认 180 秒无进展即返回 stalled;timeout 会顺带给出 suggested_timeout_sec,避免模型用 60→90→120 小步试错。
要解决的问题
我们实际踩过的现象:
| 现象 | 现有行为 |
|---|---|
| 等 teammate | wait_agent(timeout=30min) 在对方卡死时等满 30 分钟,且只返回一个布尔值,事后还得查 list_agents |
| 等后台脚本 | job_output(wait:true, timeout=15min) 在作业卡住时等满上限,等待期间看不到任何进展 |
| 手工轮询 | sleep 90 → 查文件 → 再估时间 → 再 sleep,一轮往返 1.5 分钟 |
| 等待中不可观测 | 只看得到「在等」,看不到中间日志、产出进度,无法判断「在跑」还是「卡住」 |
| 超时 ≠ 失败 | 超时返回后还要再查一轮才知道是超时、失败还是成功 |
关键观察:光提供一个新工具不够。模型在思考里会自己决定「等 180 秒再查」,如果行为不变,工具再好也不会被用。所以本插件做三层:
- 决策前给规则:往系统提示词注入「等待纪律」,要求等待一律用
wait_until,不许sleep空等。 - 决策落地时硬拦截:注册
tools.guard,把sleep ≥ 20s的前台 bash、超过 120s 的job_output/wait_agent等待直接拒绝,并在拒绝原因里给出可复制的wait_until替代调用。 - 替代原语本身:
wait_until工具,条件满足、目标失败或连续无进展时立即返回结构化结果。
wait_until 工具
wait_until({
conditions: [
{ kind: 'job', id: '<job_id>' }, // 后台作业
{ kind: 'agent', target: 'worker' }, // 团队成员
{ kind: 'file', path: '/tmp/out.zip', until: 'exists' },
{ kind: 'log', path: '/tmp/build.log', matches: '/EXIT=0/' },
],
fail_fast: true, // 或 ['job_failed', 'stderr_matches:/error|failed|Traceback/']
stall_after_sec: 120, // 连续 120s 无任何进展 → 立刻返回 stalled
timeout_sec: 900, // 总兜底,不是主要判定手段
poll_interval_sec: 2,
})
返回(一次调用即可决策,无需二次查询):
{
"status": "failed",
"elapsed_ms": 1250,
"elapsed": "1.2s",
"matched": ["后台作业 bash-xxxx(结束)"],
"conditions": [{ "condition": "后台作业 bash-xxxx(结束)", "state": "failed", "detail": "状态 completed — exit code: 3" }],
"failure_reason": "后台作业 bash-xxxx 失败:退出码 3",
"output_tail": "BOOM: intentional failure\n",
"hint": "目标已失败,不必再等:按 output_tail / failure_reason 决策……"
}
注意上例的 detail:平台把「进程退出但退出码非零」记为 completed,本插件会识别该退出码并自行判定为 failed —— 否则下游会把「脚本报错」当成成功。
status 五态:completed / failed / stalled / timeout / cancelled。超时不是失败,单独一态。
多个 conditions 是 wait_all 语义:全部条件都满足才返回 completed;fail_fast 命中时任一条件失败立即返回 failed。(只想要「任一完成就返回」,请把条件拆成多次 wait_until。)
条件语义
| kind | 必填 | until | 说明 |
|---|---|---|---|
job | id | settled(默认)/ failed | 作业结束(含失败)或仅失败 |
agent | — | inactive(默认)/ failed / status_changed | 成员全部空闲 / 任一失败 / 任意状态变化 |
file | path | exists(默认)/ changed | 文件出现 / 版本变化 |
log | path, matches | matches | 日志尾部或新增内容命中正则 |
fail_fast 模式
true(默认):job_failed+job_killed+agent_failedfalse:关闭- 数组:
job_failed/job_killed/exit_code_nonzero/agent_failed/stderr_matches:/re//output_matches:/re/
job_failed 与 exit_code_nonzero 都会识别两种失败信号:平台记录的退出码(detail: "exit code: N",N≠0)和作业输出里的 EXIT=N(N≠0)。因此进程还没退出、但日志已宣布失败时也能提前返回。
事件驱动到什么程度
- 作业条件:订阅
jobs.events,作业 settle / 输出增长 / progress 时立刻唤醒主循环,不受poll_interval_sec限制。 - 卡住提前返回:作业条件未显式设置
stall_after_sec时默认 180 秒无进展即返回stalled(显式传0关闭),不需要靠「小 timeout + 反复重试」试探。 - 文件 / 日志 / 队友:按
poll_interval_sec采样(默认 2s)。 - 取消:全程响应
exec.signal,调用方打断时立即返回cancelled。
拦截规则(硬约束)
| 拦截对象 | 条件 | 拒绝原因 |
|---|---|---|
bash | 前台命令里含 sleep ≥ 20s(不含 run_in_background、不含 & 后台) | 给出 5 条 wait_until 替代示例 |
job_output | wait:true 且 timeout_ms > 120000 | 要求改用 wait_until 分段等待 |
wait_agent | timeout_ms > 120000 | 要求改用 wait_until 的 agent 条件 |
其余调用一律放行。sleep 3、后台 sleep、普通命令不受影响。
job_output 的短轮询(不带 wait 的反复调用)不做硬拦截,只在系统提示词里劝阻:每次轮询占一个步骤,且拿不到失败判定;等待作业应使用 wait_until,需要日志时读一次即可。
文件结构
index.js 插件本体(零依赖,不 import 任何 @deepseek-ai 包)
cordis.patch.yml 插入 Loader 的条目(name 必须与 package.json 的 name 一致)
package.json bundle 清单(dsh.bundle.patch 指向 patch)
locale/ 中英文显示元数据(Plugin Manager / Settings 用)
icon.svg 插件图标
test/unit.mjs 本地单测(mock 服务,274 条断言,不依赖运行时)
docs/ 仓库内文档,不发布到 npm:改造前基线实测、独立交叉验收报告
零依赖的原因:插件运行在宿主进程里,profile 的 node_modules 并不保证提供 @deepseek-ai/dsh-tools 等包,import 它们会解析失败。工具 schema 直接手写标准 JSON Schema(parameters 原样投影给模型,output.schema 由 tools.register 校验)。
本地测试
node test/unit.mjs
覆盖:立即失败、非零退出码识别、完成事件唤醒(poll 间隔 5s 时仍 <1.5s 返回)、卡住检测、作业条件默认 stall、超时(含 suggested_timeout_sec)、取消、批量 fail_fast、wait_all 语义、文件出现/变化、日志已命中/新增命中、队友空闲/失败、队友 stalled 语义、参数校验、两条 guard 规则、系统提示词注册、渲染格式。
安装
从 npm 安装:
dsh plugin --profile web add dsh-tool-wait-until
桌面应用里也可以直接在插件管理器中搜索安装。要求 dsh 0.2.1 或更高(见 engines.dsh),实测版本 0.2.1-rc.5。
本地开发时用链接方式安装源码目录,改动即时生效:
dsh plugin --profile web add link:/path/to/dsh-tool-wait-until
安装后 Plugin Manager 会把包写入 profile 的 package.json 与 bundle 列表,cordis.patch.yml 插入 tool-wait-until 条目;卸载时从插件管理器移除即可。
已知限制
- 模型在思考阶段是否萌生「等 180 秒」的念头无法直接干预;本插件只保证这个念头一旦落成工具调用就会被拦下并改道。
agent条件依赖 Agent Teams 服务;非 Team 会话调用会立即返回failed并说明原因,不会空等。agent条件没有「活动」信号,只有成员状态与 diagnostics:队友跑长任务时,stalled表示「状态长时间未变化」而非一定卡死,返回提示已改为「可能是长任务」。wait_agent/job_output的拦截是硬拒绝,模型会消耗一步重试;拒绝原因里已附可直接复制的替代调用。- 日志跨轮读取按字节窗口进行,多字节字符在窗口边界可能短暂出现替换符,不影响 ASCII 正则(如
EXIT=0、error)匹配。 - 多
conditions是wait_all语义(全部满足才completed),fail_fast命中时任一失败即failed。
开发注意
DSH 会在宿主进程内缓存已加载的插件模块:只修改本地 link 包的 JS 再重装,运行中的进程仍跑旧代码,需要重启 Desktop 才生效。改动模块路径(改包名或目录)会得到新的 ESM URL,重装后可免重启加载。
发布用 GitHub Actions:推送 v* tag 触发 .github/workflows/publish.yml,先跑单测、校验 tag 与 package.json 版本一致、检查该版本是否已存在,然后发布到 npm。
Plugin correlati
archify (deepseek-harness)
tt-a1i/archify
WeKnora (dsh-weknora)
tencent/weknora
next-ai-draw-io (dsh-plugin)
dayuanjiang/next-ai-draw-io
weknora
tencent/weknora