DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

FunctionHookTJU /

FunctionHookTJU/dsh-work-done-bell

Verified

DSH plugin: ring a Windows system sound when a DeepSeek Harness session is truly done

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@788a7740

dsh-work-done-bell

English | 简体中文

A DeepSeek Harness plugin that rings a Windows system sound when a session has actually finished, and shows a bottom-right toast naming the session that finished. A persistent bottom-right switch turns both off, and that preference is stored in a real settings namespace, so it survives a restart.

Why "idle" is not "done"

agent/status -> idle means only that the agent's driver has nothing scheduled. Work routinely outlives it:

  • a run_in_background job keeps running and its settlement opens another turn, and
  • a delegated subagent keeps running while its parent sits idle.

Chiming on raw idle therefore rings two or three times per task. This plugin waits for quiescence: idle and no running/stopping job owned by the session and no running agent owned by it. A 90-second deadline bounds that wait, so a long build cannot swallow the notification; once the deadline has rung, the later true quiescence stays silent for the same episode.

What it sounds like

The reason comes from the durable session log (turn/end), not from a guess:

turn/end reason Sound Toast
completed Asterisk (default notification) yes
blocked (a plugin rejected the step) Asterisk yes
max-tokens (output ceiling hit) Asterisk yes
error Hand (critical stop) yes
aborted — including your own Esc/Stop silent no
interrupted (crash-orphaned turn) silent no

Simultaneous completions within 2.5 s collapse into one sound, so five sessions finishing together do not stack five players.

Install

Clone the repository, then install it from the directory you cloned into:

git clone https://github.com/FunctionHookTJU/dsh-work-done-bell
dsh plugin --profile web add file:/absolute/path/to/dsh-work-done-bell

then add the package name to the profile's bundle list, in <DSH_HOME>/profiles/web/package.json:

{
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "...", "dsh-work-done-bell"]
    }
  }
}

(dsh plugin add reconciles that list itself when the installed package declares dsh.bundle. Restart DSH afterwards; nothing else in the composition needs editing.)

Use file:, not link:. The plugin resolves @deepseek-ai/schemastery by walking up from its own directory into the profile's node_modules. A link: install keeps the real path outside the profile and the import fails at load.

Uninstall

dsh plugin --profile web remove dsh-work-done-bell

Remove it from dsh.profile.bundles (which dsh plugin remove also reconciles), then restart. The work-done-bell namespace in settings.yaml is inert once the plugin is gone and can be deleted by hand.

The switch

Bottom-right, always visible: 完成提示已开 / 完成提示已关. It writes work-done-bell.enabled through the settings service, and both halves read it — the host half stops the sound, the browser half stops the toast. The state lives in <DSH_HOME>/settings.yaml, so it survives a restart.

Introspection

There is no model-facing tool, on purpose: @deepseek-ai/dsh-tools is not present in the profile, and a plugin may only import what the profile already carries (ESM resolution ignores NODE_PATH). The state route carries the same information instead:

curl "http://127.0.0.1:3080/dsh-work-done-bell/state?verbose=1"

That returns tracked statuses, the last turn/end reason per session, open quiescence episodes with their deadlines, the last sound exit code, and any error the plugin swallowed. Without ?verbose=1 it returns only what the browser half needs.

Requirements and known limits

  • Windows only for the sound: it plays through [System.Media.SystemSounds], executed via the composed shell service. On other platforms the plugin loads and toasts, but stays silent. Porting it means changing the command in play() to afplay / paplay.
  • The sound command carries a resolved sandbox policy. A confining shell service refuses a command that arrives without one, and the deployment default may be a mode the host cannot enforce at all — on the machine this was built on, workspace-write fails because the Windows ACL runner wants a temp root outside the workspace while the workspace is the user profile, so nothing runs confined and an unpolicied command is simply refused. The policy is therefore resolved per chime for the session being announced (sandboxPolicy.resolve({ session })), which is that session's own authority, never a widening of it. On such a host a session confined to workspace-write stays silent; ?verbose=1 reports the reason in lastError and the resolved mode in lastPolicy.
  • The toast requires an open page; the sound does not.
  • The browser half polls the host every second over /dsh-work-done-bell/state. There is no push channel: the sanctioned direction of package communication is browser -> host.
  • Toasts label the session by a short id, not its title.

Development

Four suites, all runnable with no harness running:

npm test                           # all four
node test/state-machine.test.mjs   # the decision table, on a manual clock
node test/integration.test.mjs     # the plugin inside a REAL cordis app
node test/client-bundle.test.mjs   # the browser bundle's contract
node test/docs-parity.test.mjs     # the two readmes' shared skeleton

integration.test.mjs is the one that matters most: it boots a real @deepseek-ai/cordis application with stub services and asserts that apply runs, that ctx.inject fires, and that both HTTP routes register. An earlier version of this plugin read ctx.get('webServer') once at apply time and called jobs.onJobsChanged unguarded; it looked healthy while registering nothing in production, because a host-composition row mounts during boot, before its services exist.

The suites need @deepseek-ai/cordis, @deepseek-ai/cordis-plugin-timer, @deepseek-ai/schemastery and @deepseek-ai/cosmokit resolvable from the repository. A checkout links them from the harness installation; node_modules is gitignored, so these links stay local:

$repo  = (Get-Location).Path   # this checkout
$dsh   = "$env:APPDATA\npm\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai"
$web   = "$env:USERPROFILE\.dsh\profiles\web\node_modules\@deepseek-ai"
New-Item -ItemType Directory -Force -Path "$repo\node_modules\@deepseek-ai" | Out-Null
foreach ($p in 'cordis','cordis-plugin-timer') {
  New-Item -ItemType Junction -Path "$repo\node_modules\@deepseek-ai\$p" -Target "$dsh\$p"
}
foreach ($p in 'schemastery','cosmokit') {
  New-Item -ItemType Junction -Path "$repo\node_modules\@deepseek-ai\$p" -Target "$web\$p"
}

docs-parity.test.mjs guards the two readmes against drift: the same heading skeleton, the same code blocks, the same tables, the same inline technical terms. It cannot judge the prose, but it fails the moment one language grows a section the other lacks.

Layout

lib/index.js      host half  — quiescence state machine, sound, HTTP route
client/client.js  client half — shell.overlay toast stack and switch
cordis.patch.yml  the composition row this package contributes
test/             decision table, real-cordis integration, client + readme contracts

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 788a7740ccc7

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout