Ambient fullscreen status grid for DeepSeek Harness sessions with token/rate graphs, plus ntfy push and alarm notifications when a turn ends, errors, or waits for approval.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-obvious-grid
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:ray062/dsh-obvious-grid
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Host plugin for DeepSeek Harness: makes session status obvious — visible from
across the room and reaching you when you are AFK. The harness itself serves an
ambient, glanceable status grid at /obvious-grid, and the plugin pushes to
ntfy (phone) and/or plays an alarm on the machine when a turn finishes,
an error occurs, or an approval is waiting on you.
Install
dsh plugin --profile web add dsh-obvious-grid # npm package name — resolved from the registry as-is
dsh plugin --profile web add @deepseek-ai/schemastery # the plugin's config schema; an optional peer, so pnpm won't auto-install it
dsh web # restart, then open http://localhost:3080/obvious-grid
That's it — the package ships a dsh.bundle manifest (its own
cordis.patch.yml), so dsh plugin add registers the plugin as a profile
layer automatically: no manual patch editing, no config file. dsh-obvious-grid
in the first command is the npm package name itself (published unscoped on
npmjs.com), so a fresh install needs no scope or repo prefix:
dsh plugin forwards the bare name to pnpm, which resolves it from the
registry and adds it to the profile's dependencies; the reconciler then
appends it to dsh.profile.bundles because the installed manifest declares
dsh.bundle. The second line installs @deepseek-ai/schemastery as a plain
dependency (it declares no dsh.bundle, hence the harmless "no dsh.bundle"
warning) — it is required at boot because the plugin's entry imports it
directly while declaring it only as an optional peer, and the web profile
template sets autoInstallPeers: false. (dsh plugin forwards pnpm, which
must be on PATH — it prints dsh: pnpm not found on PATH otherwise; for local
development a tarball or file: path works the same way, e.g.
dsh plugin --profile web add file:/path/to/dsh-obvious-grid.)
How to use
- Boot the harness:
dsh web(or restart a running instance), then open http://localhost:3080/obvious-grid — the grid fills the viewport and updates itself; nothing to click to get started. - Read each session at a glance: the card tint is the state — orange running (scrolling RUNNING marquee), blue waiting on you, red error, green idle — and the card shows title, workspace · git branch, model/provider, tokens, context %, cache hit %, speed, and time breakdown.
- Per-card controls: hide/restore a card, and toggle that session's push (ntfy) and sound (alarm) — or flip the global switch to opt all sessions in.
- To be reached when AFK, give the plugin a ntfy topic and/or an alarm command — set them on the page, or statically in your profile patch (see "User-specific config" below). You are notified on exactly three triggers: a turn finishing, an error, and an approval waiting on you.
- Sanity check:
GET /obvious-grid/statusmust return JSON. If you get the app's HTML shell instead, the plugin did not register — see Troubleshooting.

What you get
- Ambient grid page served by the harness itself at
/obvious-grid(web profile): distance-readable cards that fill the viewport, the whole card tinted by state: orange = running (with a giant scrolling RUNNING marquee), blue = waiting / blocked on you (flashing attention), red = error, green = idle. Cards show the session title (folded from the log-onlysession/titleevent, seeded fromctx.sessionTitleat adoption), workspace path · git branch (read from.git/HEADup the directory tree), model/provider, per-request tokens (current request) + session totals, reasoning tokens (usage-reported when the adapter provides them, otherwise counted from the token-sizedreasoning-deltastream chunks — the harness's own token boundaries), context-window % (the RAW prompt footprint of the newest measured request — uncached input + cache read over the window, NOT the session-cumulative cache total which would inflate the gauge to 100%+), prompt-cache hit % (last request | session average), token/s speed (last | average), per-state time breakdown (run/wait/idle/err)- pid + llm time in the meta line, sub-agent
↳+ parent line, per-card sound/notify toggles, hide/restore, and live per-request graphs (token usage stacked bars + token/s rate and average lines, hover for per-request detail with cumulative totals). Zero interaction required; the page polls/obvious-grid/statusand stays live. The state follows the session’s open turn: running covers the whole turn — each step, the gaps between steps (tool calls), and compaction (compaction/start..compaction/end) included — so the grid never flickers to idle between steps, and a manual/compactbetween turns shows as running while the summary is being produced. Idle only when no turn is open.
- pid + llm time in the meta line, sub-agent
- AFK notifications on exactly three triggers (obvious-grid semantics):
turn-end— a turn finished, come look;error—agent/erroron the live bus;approval-wait— anapproval/askedis parked, you are the blocker. Each trigger pushes to ntfy (phone) and/or plays an alarm on the machine. Per-session opt-in toggles + a global topic, changed from the page; a globalnotifyDefaultswitch in config opts all sessions in.
- History resumes across restarts: DSH never rebroadcasts constructor
seeds (replay/fork/resume) on the
session/eventfirehose, so the registry folds each session's full event log (session.events) once at adoption — turns/steps/tokens/title from before a harness restart reappear. Live appends keep coming from the firehose, and the two sources are disjoint (no double counting). - Nothing new to run: no own HTTP server (routes register on the harness webserver), no temp-file registry, no PID liveness / staleness window. The page file is read fresh per request, so UI edits appear on a browser refresh without a harness restart.
Config
| Key | Default | Meaning |
|---|---|---|
ntfyUrl |
https://ntfy.sh |
ntfy server base |
topic |
"" |
ntfy topic. Empty = push disabled (page can set it at runtime) |
notifyOn |
[turn-end, error, approval-wait] |
which triggers push |
notifyDefault |
false |
notify sessions that have no explicit per-session flag |
alarmCmd |
"" |
optional alarm shell command; unset = silent |
minIntervalMs |
5000 |
per-session push throttle |
pageEnabled |
true |
mount the /obvious-grid routes (web profile) |
Runtime user config (topic + per-session toggles) lives in
$DSH_HOME/obvious-grid.json and is editable from the page
(POST /obvious-grid/notify).
User-specific config (optional)
Per-user settings — your ntfy topic, alarmCmd, push defaults — belong in
the profile's own patch layer $DSH_HOME/profiles/web/cordis.patch.yml as an
id-targeted override (only the keys you set are needed):
- id: obvious-grid
config:
topic: my-dsh-alerts # ntfy topic; push is off until set
notifyOn: [turn-end, error, approval-wait]
notifyDefault: false # true = notify all sessions unless overridden
alarmCmd: "" # e.g. paplay /usr/share/sounds/freedesktop/stereo/complete.oga
minIntervalMs: 5000 # max one push per session per interval
ntfyUrl: https://ntfy.sh
pageEnabled: true
Note for setups from before v0.2.0: if you previously registered the plugin by hand via an
insertrow in your profile patch, remove that row after upgrading — it duplicates the bundle's own insert.
Endpoints (web profile)
| Route | Description |
|---|---|
GET /obvious-grid |
the ambient page (plain HTML file, zero build) |
GET /obvious-grid/status |
JSON snapshot of live sessions |
GET /obvious-grid/notify |
current topic + per-session flags |
POST /obvious-grid/notify |
set topic and/or toggle one session |
Troubleshooting
file:/ tarball installs are snapshots, not live links. pnpm copies the package at install time, so after changing the source repo you must reinstall (re-rundsh plugin --profile web add file:/path/to/dsh-obvious-grid) — otherwise the installed copy silently keeps stale files. The npm-package install (dsh plugin --profile web add dsh-obvious-grid) avoids this.- Bundle not active after a
dsh plugincommand? The reconciler maintainsdsh.profile.bundlesfrom installed state — the entry is added when the installed package declaresdsh.bundle, and removed when it doesn't. If your copy is stale (see above), the manifest may be missing: refresh the install, then checkdsh.profile.bundlesin the profile'spackage.jsoncontainsdsh-obvious-grid. - Quick sanity check:
/obvious-grid/statusmust return JSON. If it returns the SPA shell (DeepSeek Harness app HTML) instead, the plugin did not register — check the two bullets above.
Files
dsh-obvious-grid/
lib/index.js plugin entry: name, Config (schemastery), apply()
lib/sessions.js live per-session fold of the session firehose
lib/notify.js ntfy push + bounded alarm subprocess + user config store
lib/page.html the ambient grid page (plain file, no template processing)
cordis.patch.yml bundle patch layer (dsh.bundle manifest) — auto-registers the
plugin on `dsh plugin add`, no manual patch editing
scripts/check-page-script.mjs syntax-checks the page's embedded <script>
scripts/seeder.mjs test seeder: creates a multi-session grid (parent +
sub-agent + extra session) in an isolated profile to
exercise several cards without touching a live instance
Safety rules
- No top-level side effects;
apply(ctx, config)does all wiring. fetchis always bounded (AbortController, 3 s); the alarm subprocess isdetached,unref'd, stdio ignored, killed after 5 s — it can never hang the harness.- All file config I/O swallows errors; a broken store must not break the page.
lib/page.htmlis a plain file — no template processing. The embedded script uses only string concatenation (no template literals), andnpm run checksyntax-checks it.
Limits
- Cards show tokens and wall times, not $ — DSH does not report message cost today. If providers expose usage cost later, it drops into the same fold.
- The grid shows the serving instance's live sessions. A machine-wide grid over
several concurrent
dshprocesses would scan the shared canonical logs under$DSH_HOME— deliberately deferred.
Verify
npm run check # node --check on lib/*.js + the extracted page script
Verification limits: this package was written against the published
@deepseek-ai package contracts (dsh-session-telemetry event subscription,
dsh-host-webserver route registration, dsh-session-title-first-prompt-llm
plugin shape, dsh-session-stats event vocabulary) and has been live-loaded
against DSH rc.6 in this environment: boot failed until the loader entry declared
inject: [sessions, webServer, sessionTitle] (cordis forbids touching
undeclared services), and apply() now reads services through a guarded
accessor so a missing service degrades instead of killing the boot. A
multi-session grid (parent + sub-agent + second session, waiting + idle states)
is exercised by scripts/seeder.mjs in an isolated profile. A load failure
still surfaces in the Loader log; the Logger row message names the missing
export, schema field, or service.
License
MIT. Idea and page design derive from ray062/opencode-obvious-grid (MIT).
Links
More in this category
xmanrui/dsh-im★ 1579
Connect IM bots to DeepSeek Harness via QR codes or bot credentials (9 channels: Feishu, WeChat, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp).
shaobeichen/dsh-pocket★ 1456
Remote phone access to the DSH Web UI: scan a QR code for LAN or public (cloudflared tunnel) access with real-time sync, a mobile-adaptive layout, and a settings tab.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 602
Connects DeepSeek Harness to Avernet's Bot Collaboration Network over WebSocket V2, with automatic onboarding, isolated agent sessions, tool-call events, and multi-bot routing tools.
omdsh-dev/dsh-notification★ 85
Desktop notifications for turn completions, with per-outcome controls and keyword rules.
whyihaveyou/dsh-suite#plugin-notify★ 56
IM webhook and local notifications on turn completion, errors, or approval (Feishu/WeCom/DingTalk/Slack/Discord/custom).
omdsh-dev/dsh-lark★ 55
Lark/Feishu bot channel for DeepSeek Harness: each chat drives its own agent, and tool approvals, model questions, and plan reviews return as cards answered by a button or a reply. Switch workspace and model from the chat (`/cd`, `/model`, `/new`), and run several bots that keep separate sessions and can hand turns to each other in one group.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.