Back to home@andreagosto

dsh-tab-watchdog

Tab Watchdog for DeepSeek Harness Web: blinks a green/yellow badge in the browser tab title when a workspace finishes, errors, or needs your attention. Persistent dsh-plugin bundle.

Stars
0
Language
JavaScript
Created
Sep 7, 2026
Updated
Sep 7, 2026
GitHub repo

Introduction

dsh-tab-watchdog

A persistent plugin for DeepSeek Harness Web: when one of your workspaces/sessions finishes a work round, errors out, or needs your input, the browser tab signals it — a little whale (the DeepSeek Harness logo) blinks green or yellow in the favicon, and the tab title shows the overall pending counter.

This is the "bundle" version (persistent, installable, shareable) of the Tab Watchdog dynamic plugin originally developed in Creator mode. Unlike a dynamic plugin it survives dsh restarts and browser reloads (F5).

How it works (architecture)

Two halves in a single npm package, like the official plugins:

  • Host half (index.js) — profile composition row (cordis.patch.yml + dsh.bundle.patch). It makes the package a Loader entry: that row is what pulls the browser half into the boot manifest. No business logic here.
  • Browser half (lib/client.js, served via dsh.client + the ./client export) — a bundle in the window.__ModuleLoader__.load({ id, factory }) format. It subscribes to the Host events forwarded to the browser (ctx.remote.$on) and handles everything (state, watermark, blink). No UI is added to the harness.

Triggers (what makes it blink)

The browser only receives the events on the dsh-api-remotes allowlist. The watchdog uses:

EventLevelLabel
api-session/status(sessionId, running=false)🟢 green whale"Finished a work round"
api-session/error(sessionId, message)🟡 yellow whale"Error…"
pending approval interaction🟡 yellow whale"Requires approval"
pending question / plan-review interaction🟡 yellow whale"Asking for a response"

Green/errors use the forwarded emit events (ctx.remote.$on). Yellow for questions and approvals does not rely on the user-questions/request/approval/request waterfall events: those are single-consumer and claimed in registration order by the official UI answerers, and the forwarded request does not carry a stable session id. Instead the watchdog polls the state the official UI publishes while it waits on you: ctx.uiSession.pendingInteractions.getSnapshot() — a Map<sessionId, interaction> with kindapproval | question | plan-review. The poll runs in the existing 1s tick; entries are re-bumped while pending and removed when the interaction clears, so an answer stops the blink by itself. This covers every session in the app, not just the one in view.

Note: workflow/end, agent/status, and goal/changed are not forwarded to the browser in this version's allowlist; observing them would require host-side aggregation over a custom channel (out of scope by design). The yellow path depends on the app's own pending-interaction UI (ui-approval/ui-user-questions) publishing its wait state; if a third-party answerer claims a request without going through that UI, that interaction cannot be seen from the browser.

Blink model (watermark)

  • Events only arrive while the page is open (even on another browser tab — that is exactly the intended use case).
  • New events stay "pending" until you come back to the page.
  • While the page is not focused and at least one item is pending, the favicon whale blinks (alternating with the original favicon) and the title alternates between N · <title> and <title>, where N is the overall pending count across sessions and levels.
  • Whale color: green when only finished rounds are pending, yellow when there is any error/question/approval, and it alternates green↔yellow when both kinds are pending at once.
  • The original favicon is captured on load and always restored when you come back (or when pending clears) — nothing is permanently altered.
  • An event that arrives while you are focused is marked read immediately (no leftover blinking).
  • When a session restarts (running=true), its pending entries are removed.
  • An error on a session "freezes" its entry in yellow: a later running=false does not downgrade it to green.

Installation (local web profile)

From the plugin checkout:

# from the directory that contains dsh-tab-watchdog/
npx @deepseek-ai/dsh@latest plugin --profile web add ./dsh-tab-watchdog

Then restart dsh web. The browser half activates on the next reload/startup. The added row is the tab-watchdog entry in the profile's cordis.yml.

Removal:

npx @deepseek-ai/dsh@latest plugin --profile web remove dsh-tab-watchdog

dsh plugin forwards to pnpm inside the profile. If pnpm is not installed: corepack enable or npm i -g pnpm.

Development

The browser half source is src/client.js (a CJS fragment living inside the loader factory). The lib/client.js artifact is committed (no build step at git-install time).

npm run build   # regenerates lib/client.js from src/client.js
npm test        # verify (bundle structure) + smoke (blink/watermark logic)

No prepare/monorepo toolchain needed: out-of-tree plugins cannot use the internal clientBundle preset, so the loader format is reproduced by hand (scripts/build.mjs).

Sharing with the community

The package declares dsh.bundle.patch, so it installs as a profile bundle. Because lib/client.js and index.js are committed artifacts, a git install works without prepare:

npx @deepseek-ai/dsh@latest plugin --profile web add github:andreagosto/dsh-tab-watchdog

For discoverability, the GitHub repository carries the dsh-plugin topic (see the official deepseek-harness README, "Community and support"). Alternatively: npm publish, then dsh plugin --profile web add dsh-tab-watchdog.

Notes and limitations

  • Tested against runtime @deepseek-ai/dsh 0.1.2-rc.1 (mind the "breaking changes" notice on rc prereleases).
  • The browser half is hand-written against the APIs observed in the installed bundles; the structure is validated by npm test, but verify it live in your own profile.
  • No settings namespace: the plugin does not appear in Settings → Plugins. To disable it use dsh plugin remove (or set disabled: true on the row in the profile patch).

License

MIT