Bundle
dsh-obvious-grid
Host plugin for DeepSeek Harness: ambient status grid page + AFK notifications (ntfy push / alarm) on turn-end, error, and approval-wait. The "obvious" idea from opencode-obvious-grid, on DSH seams.
- Source
- ray062
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 13 days ago
Readme
# dsh-obvious-grid
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
```bash
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
1. 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.
2. 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.
3. 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.
4. 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.
5. Sanity check: `GET /obvious-grid/status` must 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-only `session/title` event, seeded
from `ctx.sessionTitle` at adoption), workspace path · git branch (read from
`.git/HEAD` up 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-sized
`reasoning-delta` stream 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/status` and 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 `/compact` between turns shows as running
while the summary is being produced. **Idle** only when no turn is open.
- **AFK notifications** on exactly three triggers (obvious-grid semantics):
- `turn-end` — a turn finished, come look;
- `error` — `agent/error` on the live bus;
- `approval-wait` — an `approval/asked` is 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 global
`notifyDefault` switch in config opts all sessions in.
- **History resumes across restarts**: DSH never rebroadcasts constructor
seeds (replay/fork/resume) on the `session/event` firehose, 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):
```yaml
- 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 `insert` row 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-run `dsh 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 plugin` command?** The reconciler maintains
`dsh.profile.bundles` from installed state — the entry is added when the
installed package declares `dsh.bundle`, and removed when it doesn't. If
your copy is stale (see above), the manifest may be missing: refresh the
install, then check `dsh.profile.bundles` in the profile's `package.json`
contains `dsh-obvious-grid`.
- **Quick sanity check:** `/obvious-grid/status` must 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
1. No top-level side effects; `apply(ctx, config)` does all wiring.
2. `fetch` is always bounded (AbortController, 3 s); the alarm subprocess is
`detached`, `unref`'d, stdio ignored, killed after 5 s — it can never hang
the harness.
3. All file config I/O swallows errors; a broken store must not break the page.
4. `lib/page.html` is a plain file — no template processing. The embedded script
uses only string concatenation (no template literals), and `npm run check`
syntax-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 `dsh` processes would scan the shared canonical logs under
`$DSH_HOME` — deliberately deferred.
## Verify
```bash
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](https://github.com/ray062/opencode-obvious-grid) (MIT).
Install
dsh plugin --profile web add github:ray062/dsh-obvious-grid
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-obvious-grid from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.