deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:woshi-Tom/dsh-status-plugin
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
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, raises toasts when the host reports overload or memory-pressure alerts, and a settings page that tunes alert thresholds live.
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.
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.json → dsh.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.
normal:
alert:
The plugin registers three exact routes on the profile's web server:
GET /api/status # JSON snapshot on demand
GET /api/status/metrics # Prometheus text exposition format
GET /api/status/events # Server-Sent Events stream
GET /api/statusExample 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,
"eventLoopDelayMs": 2.3,
"memory": { "rss": 123456, "heapTotal": 654321, "heapUsed": 432100, "external": 12345 },
"systemMemory": { "total": 17179869184, "free": 4294967296, "used": 12884901888 },
"disk": { "mount": "/", "total": 107374182400, "free": 64424509440, "avail": 60129542144, "used": 42949672960, "percent": 0.416 },
"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" }
]
}
}
| Field | Source |
|---|---|
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; eventLoopDelayMs is the mean event-loop delay (perf_hooks) since the last sample; loadAvg is the Unix load average — always [0, 0, 0] on Windows; systemMemory.* is machine-wide memory (os.totalmem() − os.freemem()); disk is the working disk — fs.statfsSync on process.cwd(), falling back to the OS temp directory, so the filesystem the harness actually runs on is what gets reported (null only when every probe fails); disk.percent follows the df convention used / (used + avail), so reserved blocks (the 5% ext4 keeps for root) do not overstate usage |
webServer.* |
ctx.webServer (bind host and actual listening port) |
apiKey |
DEEPSEEK_API_KEY in process.env, else the working directory .env or $DSH_HOME/.env (the exact layers the dsh CLI loads — ~/.env is deliberately not checked) — presence only, never the value; an empty assignment (DEEPSEEK_API_KEY="") does not count as configured |
plugins.entries |
ctx.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. The check result is cached for 60 s, so snapshots do not re-read .env files on the event loop.
Privacy note (0.2.1+):
host.lanAddressesis[]by default. SetexposeLanAddresses: trueif you need the LAN IPv4 addresses in the payload.
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, eventLoopDelayMs, 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. Reasons: cpu, memory (Linux memory pressure uses /proc/meminfo MemAvailable, not raw freemem() — page cache no longer causes false alarms), eventLoop (mean event-loop delay in ms), and disk (working-disk usage fraction, df-style). 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):
| Config | Default | Meaning |
|---|---|---|
cpuWarning |
0.8 |
CPU utilization above which a CPU overload alert fires |
memoryWarning |
0.85 |
system memory pressure above which a memory alert fires |
diskWarning |
0.9 |
working-disk usage above which a disk alert fires |
eventLoopWarning |
100 |
mean event-loop delay (ms) above which a stall alert fires |
hysteresis |
0.1 |
recovery margin: an alert clears only below threshold × (1 − hysteresis) |
heartbeatMs |
30000 |
snapshot push interval |
checkIntervalMs |
5000 |
alert monitor sampling interval |
authToken |
'' |
shared secret required on all three routes; empty disables auth. See Authentication |
allowedOrigins |
[] |
exact Origin values allowed to call the routes; empty disables origin checks. See Authentication |
exposeLanAddresses |
false |
include host.lanAddresses in snapshots (off by default for privacy) |
rateLimitPerMinute |
300 |
per-IP request cap per minute across all routes; 0 disables |
maxSubscribers |
32 |
SSE subscriber cap; new connections over the cap fail with an error response |
maxBufferedBytes |
65536 |
per-subscriber write-buffer high-water mark; a slow consumer over it is dropped |
webhookUrl |
'' |
webhook URL notified on every alert transition; empty disables. See Webhook notifications |
webhookTimeoutMs |
5000 |
webhook request timeout in milliseconds |
Since 0.4.0 the plugin registers a dsh-status settings namespace (@deepseek-ai/dsh-settings). When the profile mounts the settings service, a Status page appears in the web UI settings panel (contributed to the settings.section slot) and the fields below become live-tunable — a write takes effect without restarting the harness:
| Field | Default | Effect timing |
|---|---|---|
cpuWarning, memoryWarning, diskWarning, eventLoopWarning |
as above | live — the alert monitor re-judges on the next sample |
hysteresis |
0.1 |
live |
heartbeatMs, checkIntervalMs |
as above | live — timers are re-armed |
exposeLanAddresses |
false |
live — the next snapshot honors it |
rateLimitPerMinute |
300 |
live — the limiter's cap is replaced |
Security boundary. The settings page deliberately edits only this subset. authToken (a secret), allowedOrigins, webhookUrl/webhookTimeoutMs (channel endpoints), and maxSubscribers/maxBufferedBytes (hub caps) stay in cordis.yml — secrets and endpoints are never reachable from the browser surface. Without a settings service, the plugin runs exactly on its entry config, as before.
The settings transport is loopback-only (like the SSE auth constraint): a browser on a remote machine cannot read or write the namespace — it renders an unavailable state. Per-field "reset" buttons drop the user override so the field re-inherits the cordis.yml entry value.
When authToken is set, all three routes require it. The token can travel in either channel:
GET /api/status and GET /api/status/metrics — Authorization: Bearer <token> header, or ?token=<token>.GET /api/status/events — ?token=<token> query parameter, or the Authorization header when the client uses the fetch-stream subscriber (see below); 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 and keep the SSE stream on a loopback-only webserver.
Origin policy. Set allowedOrigins to a list of exact origins (e.g. ["https://dsh.example.com"]) to reject cross-origin browser reads when the web server is reachable beyond loopback. Requests without an Origin header (curl, servers, same-origin navigation) always pass. An empty list (the default) accepts every origin.
Rate limiting. Every route is limited to rateLimitPerMinute requests per client IP (default 300, 0 disables). Exceeding the cap answers 429 with { "ok": false, "error": "rate limited" }.
Header-auth SSE client. The bundled badge has no channel to receive the host's token, so enabling authToken disables the badge's status views. Custom UIs can subscribe with the exported createSseClient(url, headers, onEvent, onConnectionChange) — pass { Authorization: 'Bearer <token>' } and get header-authenticated SSE with automatic exponential-backoff reconnects.
The browser side subscribes through a fetch-based SSE reader (auto-reconnects with exponential backoff, honoring the server's retry: frame when present) and renders:
Set webhookUrl to receive a JSON POST on every alert transition (enter and recover). The payload:
{
"event": "alert",
"active": true,
"reason": "cpu",
"value": 0.87,
"threshold": 0.8,
"timestamp": "2026-08-14T03:50:00.000Z",
"hostname": "host",
"pid": 23185
}
Delivery is fire-and-forget: a failed or timed-out request is logged and never disturbs the alert pipeline, so the harness keeps monitoring even when the notification channel is down. Wire it to any webhook-capable service (Slack, DingTalk, 企业微信, ntfy, …).
GET /api/status/metrics serves the snapshot in Prometheus text exposition format (text/plain; version=0.0.4) for scraping by existing monitoring stacks:
dsh_status_up 1
dsh_status_uptime_seconds 3600
dsh_status_cpu_percent 12.4
dsh_status_event_loop_delay_ms 2.3
dsh_status_loadavg_1 0.5
dsh_status_process_rss_bytes 123456
dsh_status_system_memory_used_bytes 12884901888
dsh_status_disk_total_bytes{mount="/"} 107374182400
dsh_status_disk_free_bytes{mount="/"} 64424509440
dsh_status_disk_avail_bytes{mount="/"} 60129542144
dsh_status_disk_used_bytes{mount="/"} 42949672960
dsh_status_disk_percent{mount="/"} 0.416
dsh_status_api_key_configured 1
dsh_status_plugins_total 42
dsh_status_plugins_active 40
The endpoint honors authToken, allowedOrigins, and rateLimitPerMinute exactly like the other routes.
500 with { "ok": false, "error": "internal error" } — sanitized, no internals or stack leaked; the real message goes to the host log.pluginInventory is optional: when the service is absent, plugins.entries is [] rather than an error.cache-control: no-store (runtime data must not be cached); the SSE stream uses text/event-stream with x-accel-buffering: no.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.@deepseek-ai/dsh-web-app) — provides ctx.webServer and ctx.pluginInventory.^22.19 || >=24.conversation.session.header.utilities slot); it is not shown on the empty/home screen.@deepseek-ai/dsh-client-ui-settings + the settings shell) in the web composition — present in the standard bundle. Without it, the badge and panel still work; only the settings page is absent.0.00. CPU utilization and memory metrics work on every platform.pnpm install
pnpm run build # host tsc + client typecheck + esbuild bundle
pnpm test # vitest unit tests
pnpm run lint # biome lint (no auto-formatting)
npm pack --dry-run # verify tarball contents (prepack runs the build)
node scripts/check-pack.mjs # verify the tarball contains every module lib/ imports
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.
MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。