在会话头部的后台任务列表里停止正在运行的任务,确认弹窗会再次显示完整命令。
安装
# npm 包(预构建)
dsh plugin --profile web add @gorban/dsh-job-stop
# Release 预构建包
dsh plugin --profile web add "https://github.com/gorban/dsh-job-stop/releases/latest/download/dsh-job-stop.tgz"
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:gorban/dsh-job-stop
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
该插件的 README 只有英文版本。
Stop a running background job from the DeepSeek Harness web session header — hover its row in the background-job list, click the stop sign, confirm in a dialog that restates the full command.

Hovering a running row covers its animated state dot with a stop button; the dialog restates the command, kind, status, elapsed time, and job id before anything is cancelled.
What it does
The session header's background-job list becomes actionable for the human, not just readable:
- Hover a running row and the animated state dot is covered by a red stop button, with a tooltip and an accessible name that both name the job (
Stop background job: <command>). - Activate it and the house confirmation dialog opens — the same
Modal+Buttoncomposition the session rename and workspace delete dialogs use — restating the full command, the job kind, its status, how long it had been running, and its registry id before anything is cancelled. - Confirm and the host kills the job through
ctx.jobs, the row flips tostoppingand thenkilledfrom the ordinary pushed frame, and the owning agent is told that a human stopped its job.
Everything is localized (English and Simplified Chinese) and keyboard reachable: the stop button takes focus and reveals itself on :focus-visible, Escape closes the dialog, and prefers-reduced-motion disables the fades.
Why it exists
ctx.jobs already ran every background bash/pwsh/PTY/subagent job, and dsh-tool-jobs already exposed job_kill — to the model. The web list (dsh-client-ui-jobs) was a deliberately read-only projection, and its own README named the reason cancellation was deferred:
Rows are read-only — … Cancellation additionally owes a model-facing decision the seam does not answer:
kill()marks terminal delivery reported, so an interrupt written against the current contract would leave the model believing its job is still running.
That is the whole problem this plugin solves, and the design note 2026-08-08-web-background-job-display left stopping in the wire union precisely so this phase would not need a wire change. Two consequences shape the implementation:
- The model must be told.
JobRegistry.killsetsreported = true, anddsh-tool-jobsskips its completion notice for a reported job. So after a successful kill this plugin injects its own plugin-sourced notice (injectwhile the agent is busy,followupwhen it is idle andnotice: wakeup), or the model would keep waiting on a job that no longer exists. - A job that already ended must not be killed. Calling
killon a terminal record also setsreported, which would swallow the completion notice the model is still owed. So the endpoint reportsalready-finishedwithout callingkillwhenever the job has leftrunning.
Install
From npm — prebuilt, so the install needs no build step and no allowBuilds approval:
dsh plugin --profile web add @gorban/dsh-job-stop
From GitHub — no npm account involved:
dsh plugin --profile web add github:gorban/dsh-job-stop
From a release tarball — for a host that cannot reach npm or a GitHub source archive:
dsh plugin --profile web add https://github.com/gorban/dsh-job-stop/releases/latest/download/dsh-job-stop.tgz
Then restart dsh (a plugin row is mounted at profile load). The command adds the dependency and, because package.json declares dsh.bundle, lists the package in dsh.profile.bundles so its cordis.patch.yml row is applied.
Requires dsh 0.1.2-alpha.1 or newer, declared as engines.dsh so the Plugin Market's host-aware filter can read it: both the jobsBySession mirror this list renders from and the ctx.connection.fetch route seam the stop endpoint is claimed on first shipped in that release.
The shipped @deepseek-ai/dsh-client-ui-jobs row can stay enabled: this plugin registers its list under that entry's own slot cell id (job-list) at a lower priority, so the header keeps exactly one control and the read-only list is shadowed rather than duplicated. This plugin also renders correctly with that row disabled, because it reads the same jobsBySession mirror.
How it works
Host half (src/index.ts) claims one authenticated route on the Connection fetch registry:
ctx.connection.fetch.register({
path: '/api/dsh-job-stop/kill',
methods: ['POST'],
requestBody: 'buffered',
fetch: request => handleStop(ctx, config, request),
})
That registry is deliberate, and it is not the obvious choice. ctx.webServer.register({ kind: 'exact', path: '/api/...' }) — the pattern a plugin reaches for first — matches the web server's exact table before Connection's /api prefix route, so it runs ahead of requestRejection and therefore outside the host/Origin trust and browser-authentication fence. That is fine for a prompt toggle and wrong for a mutating kill, so this endpoint is claimed on the shared fetch handler instead, where the fence has already run.
The handler then:
- validates
{ sessionId, jobId }and rejects malformed bodies (400); - resolves the Session's exact live Agent with
ctx.agents.get(...)and refuses when there is none (409) — ids are predictable, so authorization, not secrecy, is the boundary, and the registry fenceskillby that Agent; - reads the job with
ctx.jobs.get(...), neverctx.jobs.read(...):readconsumes the job's single output cursor and would silently take bytes the model'sjob_outputwill never see; - reports
already-finishedfor a job that has leftrunning, otherwise callsctx.jobs.kill(...)and, onrequested, delivers the notice.
Browser half (src/client/) shadows the built-in list entry and adds the hover affordance. It talks to the registry only through that one POST; job state still arrives through the pushed jobsBySession mirror, so no polling or optimistic row state is involved.
Configuration
- id: job-stop
name: "@gorban/dsh-job-stop"
config:
notice: wakeup # or: quiet
maxConsecutiveWakes: 3 # turns this plugin may open per agent between its own inputs
| Option | Default | Meaning |
|---|---|---|
notice |
wakeup |
wakeup opens a turn on an idle owning agent so it learns immediately (one model request, the same default dsh-tool-jobs ships for its own completion notices). quiet leaves the account pending until something else wakes the agent. A busy agent is injected either way. |
maxConsecutiveWakes |
3 |
How many turns this plugin may open on one agent between that agent's own human inputs. Mirrors dsh-tool-jobs' bound of the same name and for the same reason: a burst of stops should not spend a model request each. Past the budget the account is injected instead, so the model still learns — it just is not woken for it. |
Requirements
Every dependency is provided by the dsh installation this plugin runs inside, and is resolved from the host's module graph — the plugin pins itself to no harness build:
| At runtime | Used for |
|---|---|
ctx.jobs, ctx.agents |
the registry and the owner fence |
ctx.connection.fetch |
the authenticated route (@deepseek-ai/dsh-client-connection) |
@deepseek-ai/dsh-jobs, dsh-session, dsh-llm, schemastery |
job/session ids, the identified notice message, config schema |
@deepseek-ai/dsh-client-ui-primitives, dsh-client-ui-slots, dsh-client-locale, dsh-client-ui-conversation, React |
the list, the dialog, and the slot contract |
These are deliberately not declared as peerDependencies. Declaring them makes a standalone pnpm install try to fetch harness packages from npm, and at least one published range currently resolves to a build whose tree depends on @deepseek-ai/dsh-type-meta, which is not on the public registry. The plugin therefore installs with no network resolution of harness code at all.
Development
pnpm install
pnpm build # host half -> lib/index.js, browser half -> lib/client.js
pnpm test # builds, then runs the host-half behavior suite (node --test)
Both halves are built by tsdown through build/tsdown.client.ts, which emits the browser bundle as a window.__ModuleLoader__.load({ id, factory }) closure whose id is this package's published name — the browser module system rejects a bundle that registers any other id.
Releasing
npm version patch # or minor/major; commits and tags
git push --follow-tags
Then publish a GitHub Release for the tag. That one action attaches the prebuilt dsh-job-stop.tgz asset (which the tarball install route above points at) and, once NPM_PUBLISH_ENABLED is set, publishes to npm. The release notes are what the Plugin Market shows for an update, so write them for users rather than as a commit log. .github/workflows/release.yml documents the npm trusted-publisher setup that has to exist once.
The client half is not typechecked by the build (the published rc packages on npm predate the slot contract and the jobsBySession mirror this plugin targets). To typecheck against a real checkout instead:
DSH_CHECKOUT=/path/to/deepseek-harness node scripts/typecheck-harness.mjs
That project spans the plugin and the harness sources and fails only on errors inside this repo's src/; the harness's own guest diagnostics are reported as a note. It is worth running after touching the slot registration or the host route, because those are the two places where a stale published type would hide a real mistake.
Known limitations
- Only
runningrows offer the stop affordance. Astoppingjob is already ending; offering to stop it again would be a second request for the same outcome. - The dialog restates what the registry published. For
bashthat is the complete command (args.command, untruncated), which is why the dialog is worth opening. A producer that publishes a truncated label gives the dialog only that. - A Session with no live Agent cannot have its jobs stopped (
409). The kill is fenced by that exact owner instance, and guessing one would be an authorization hole. - Stopping a job does not remove its
run_in_backgroundcard from the transcript; the card was never live-updating, and the list is where the outcome is legible. - The shadow depends on one cell id. If upstream renames the
job-listslot entry, this plugin's entry stops replacing it and both lists would render. The fix is a one-line id change here. - The recorded kill reason is a fixed English string (
stopped by the user from the background-job list), kept stable so it greps in logs and in a job's terminaldetail; it is not user-facing copy.
License
MIT
链接
同类插件
zhu1090093659/dsh-web#packages/dsh-task-board★ 7907
侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
zhu1090093659/dsh-web#packages/dsh-web-all★ 7907
DSH Web UI 插件与皮肤合集:任务看板、git 图、右侧面板、远程移动端 UI、桌宠、实时 token 统计与皮肤中心。
omdsh-dev/DSH-better-sidebar★ 3701
侧边栏完整工作台:内置文件渲染编辑、终端、Git 与子代理,支持三方插件注册新 Tab。
ccch1mneyyy/dsh-TUI★ 3136
Claude Code 风格全屏终端 UI:像素鲸鱼顶栏、实时工作状态行、思考流式展开。
MeteorNOX/DeepSeek-Balance-Whale-Widget★ 2895
右下角常驻的小鲸鱼挂件:余额、今日已用与每轮对话消耗(含峰谷价),余额预警与今日预算的泡泡内容都可编辑;泡泡点击序列模块化自定义,支持并列加权 A/B、随机台词与随机图片;内置 30+ 厂商模板(OpenAI / OpenRouter / Kimi / 硅基流动 / 方舟 / 智谱 / MiniMax 等),按模型查余额与订阅额度;另有任务结束音效、导入音频、自定义角色与资源管理。数据全在本机,无遥测。
Devin-AXIS/deepseek-design#deepseek-idesign★ 1434
可视化设计工作室,支持网站、App 原型、海报、信息卡、报告和杂志的模板创建、元素编辑、选区级 AI 草稿衔接与导出。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。