跳过主要内容
返回插件列表
W

dsh-status-plugin

woshi-tom/dsh-status-plugin

A dsh status plugin with host-plane and browser halves: harness runtime health (process, listener, API-key presence, memory, uptime, plugin inventory) as JSON plus server-pushed SSE alerts, surfaced as a header badge with an expanded panel in the Web UI

安装

dsh plugin --profile web add github:woshi-tom/dsh-status-plugin

README

dsh-status-plugin

English | 中文

A status plugin for DeepSeek Harness (dsh). Two planes in one package:

  • Host plane — HTTP endpoints exposing the running harness's runtime health as JSON: process, listener, API-key presence, memory, uptime, and the live plugin inventory.

  • Client plane — a header badge in the web UI (top-right of a conversation session) that shows uptime, opens a detail panel, and raises toasts when the host reports overload or memory-pressure alerts.

  • Package: dsh-status-plugin

  • Runtime: host (ESM) + browser bundle (a CJS factory wrapped for the dsh client-modules __ModuleLoader__ contract), built with tsc + esbuild to lib/.

  • Language: TypeScript.

Install

dsh plugin --profile web add dsh-status-plugin

The CLI reconciles dsh.profile.bundles automatically: because the manifest declares dsh.bundle.patch, the package joins the profile's bundle layer stack. Stop the running process and restart the profile to load it:

dsh web   # or: dsh --profile <name>

Verify the plugin joined the composed tree without booting:

dsh --profile web --dump-config

The client manifest (package.jsondsh.client) declares the browser entry; the profile's client-modules scanner picks it up and injects dsh-status-plugin/client.js into the web app automatically — no bundle or overlay configuration needed.

Usage

abcddf52074cd98f465253a6619de744

The plugin registers two exact routes on the profile's web server:

GET /api/status          # JSON snapshot on demand
GET /api/status/events   # Server-Sent Events stream

GET /api/status

Example response:

{
  "ok": true,
  "timestamp": "2026-08-14T03:50:00.000Z",
  "host": {
    "hostname": "host",
    "platform": "linux",
    "arch": "x64",
    "nodeVersion": "v22.23.2",
    "pid": 23185,
    "cwd": "/root/.dsh",
    "uptimeSeconds": 3600,
    "loadAvg": [0.1, 0.1, 0.1],
    "cpuPercent": 12.4,
    "memory": { "rss": 123456, "heapTotal": 654321, "heapUsed": 432100, "external": 12345 },
    "systemMemory": { "total": 17179869184, "free": 4294967296, "used": 12884901888 },
    "lanAddresses": ["192.168.5.227"]
  },
  "webServer": {
    "host": "0.0.0.0",
    "port": 3080,
    "url": "http://localhost:3080"
  },
  "apiKey": {
    "configured": true,
    "source": "env"
  },
  "plugins": {
    "entries": [
      { "entryId": "llm", "moduleName": "@deepseek-ai/dsh-llm", "enabled": true, "fiberPhase": "active" }
    ]
  }
}

Fields

FieldSource
host.*process + node:os (pid, uptime, process memory, LAN IPv4 addresses); cpuPercent is CPU utilization sampled from os.cpus() deltas and works on every platform; loadAvg is the Unix load average — always [0, 0, 0] on Windows; systemMemory.* is machine-wide memory (os.totalmem()os.freemem())
webServer.*ctx.webServer (bind host and actual listening port)
apiKeyDEEPSEEK_API_KEY in process.env, else the working directory .env, ~/.env, or $DSH_HOME/.env (checked in loadLayeredEnv priority order) — presence only, never the value; an empty assignment (DEEPSEEK_API_KEY="") does not count as configured
plugins.entriesctx.pluginInventory.list() (live Cordis Loader entry state)

The API key check reports only whether a key is configured and where it was found; the value itself never leaves the process.

GET /api/status/events (SSE)

The host pushes to open browser streams — the server decides when the page needs new state, so idle pages make zero requests:

  • snapshot — a full status snapshot, emitted immediately on connect and then every heartbeatMs (default 30 s). Each snapshot card dissects to cpuPercent, loadAvg, systemMemory, and process-memory fields inside host.* for alert-driven UIs.
  • alert — emitted when an indicator enters or leaves its alert band. Entering requires value > threshold; an active alert only clears when the value drops below threshold × (1 − hysteresis), so a value hovering near the threshold does not flap. Events are emitted on every transition and re-synchronized on connect so a page that opens mid-alert still learns about it:
event: snapshot
data: {"ok":true,"timestamp":"...","host":{...},"plugins":{...}}

event: alert
data: {"active":true,"reason":"cpu","value":0.87,"threshold":0.8}

Default thresholds (configurable via the plugin config in the profile's cordis.yml):

ConfigDefaultMeaning
cpuWarning0.8CPU utilization above which a CPU overload alert fires
memoryWarning0.85system memory pressure above which a memory alert fires
hysteresis0.1recovery margin: an alert clears only below threshold × (1 − hysteresis)
heartbeatMs30000snapshot push interval
checkIntervalMs5000alert monitor sampling interval
authToken''shared secret required on both routes; empty disables auth. See Authentication
maxSubscribers32SSE subscriber cap; new connections over the cap fail with an error response
maxBufferedBytes65536per-subscriber write-buffer high-water mark; a slow consumer over it is dropped

Authentication

When authToken is set, both routes require it. The token can travel in either channel:

  • GET /api/statusAuthorization: Bearer <token> header, or ?token=<token>.
  • GET /api/status/events?token=<token> query parameter; a native EventSource cannot set custom headers.

A rejected request answers 401 with { "ok": false, "error": "unauthorized" }. Comparison is constant-time (crypto.timingSafeEqual), so a wrong token does not leak its length. Because the query parameter can appear in logs and history, prefer header auth for GET /api/status and keep the SSE stream on a loopback-only webserver.

The built-in browser badge has no channel to receive the host's token (the client manifest cannot read the host config), so enabling authToken disables the badge's status views; a custom UI can authenticate by sending the header/query token above. Instances that need the bundled UI should leave authToken empty (the default) and rely on the webserver's loopback binding.

The browser side subscribes with a native EventSource (auto-reconnects on drop) and renders:

  • a compact badge in the conversation header (status dot + uptime, click to open);
  • a detail panel with process/resource/service/plugin sections and the last update time;
  • a toast on every alert transition (auto-dismisses after 6 s) plus a pulsing badge while an alert is active;
  • a gray badge dot when the stream is disconnected or no snapshot arrived for 90 s — a monitoring widget must say unknown, not healthy, when it loses contact.

Failure behavior

  • A collection error inside the handler returns 500 with { "ok": false, "error": "<message>" } — structured, no stack leak, never a hung socket.
  • pluginInventory is optional: when the service is absent, plugins.entries is [] rather than an error.
  • Response handlers and both periodic timers (heartbeat and alert sampler) are exception-isolated: a throwing collection is logged, never propagated as an uncaught exception that could crash the harness the plugin monitors.
  • Responses carry cache-control: no-store (runtime data must not be cached); the SSE stream uses text/event-stream with x-accel-buffering: no.
  • SSE streams are bounded: at most maxSubscribers concurrent streams; a subscriber whose write buffer exceeds maxBufferedBytes (or whose socket write stalls) is dropped so a slow consumer cannot pin the process. Plugin teardown ends every open stream.

Requirements

  • dsh profile with the web bundle (@deepseek-ai/dsh-web-app) — provides ctx.webServer and ctx.pluginInventory.
  • Node ^22.19 || >=24.
  • The browser entry renders in the conversation session header (conversation.session.header.utilities slot); it is not shown on the empty/home screen.
  • The load-average row in the panel is a Unix concept: on Windows it is always 0.00. CPU utilization and memory metrics work on every platform.

Development

pnpm install
pnpm run build          # host tsc + client typecheck + esbuild bundle
pnpm test               # vitest unit tests
npm pack --dry-run      # verify tarball contents (prepack runs the build)

Publish

This package is a dsh bundle: the npm tarball ships cordis.patch.yml and the manifest's dsh.bundle.patch points at it, so installing the package into any dsh profile automatically mounts the plugin layer. Tag the repository dsh-plugin for discoverability in the dsh plugin topic.

License

MIT

相关插件