DSH Hub
dsh-sentinel cover

fuhefei/dsh-sentinel

dsh-sentinel

UIWeb UI16 GitHub stars· updated 2026-09-12

Condition-driven wakeup for DeepSeek Harness: durable file/command/http/process/webhook watches that wake the agent, with dock, sidebar branch, and a global dashboard.

Install

npx @deepseek-ai/dsh plugin --profile web add dsh-sentinel

Restart `dsh web` after install. Bundle APIs can change during the developer preview.

README badge

dsh-sentinel DSH Hub badge
[![DSH Hub](https://dshhub.dev/badge/dsh-sentinel.svg)](https://dshhub.dev/plugins/dsh-sentinel)

Paste this into your README. The star count updates with every catalog sync.

From the README

Excerpt from fuhefei/dsh-sentinel, cleaned of badges and images.

dsh-sentinel

English | 中文

Condition-driven wakeup for DeepSeek Harness: the agent registers a watch, goes to sleep — even closes the session — and the sentinel wakes it when the condition happens. Every subscription and every fire is a user-visible session event, and the browser dock shows what is on duty.

How it works

The node half owns one server-lifetime runtime that folds a plugin-owned sidecar log ($DSH_HOME/sentinel.jsonl) into live subscriptions, probes every sensor on a shared 5s heartbeat, and delivers wakeups through the official followup channel — resuming a dormant session's agent first when needed. Subscriptions therefore survive process restarts, and conditions that become true while the server is down late-fire on the next probe.

Watching is a resident-process concern: probing and fire delivery only run while a long-running dsh process (typically dsh web) is up. Headless one-shot runs load the plugin and can create, list and cancel watches, but nothing probes after the process exits — those watches become active once a resident process starts.

One duty owner per $DSH_HOME: a lease file (sentinel.lease) makes the first process own probing and delivery; a second dsh process on the same home stays passive (tools work, writes persist to the shared sidecar) and takes over within one lease TTL of the owner dying. The owner re-reads the sidecar every heartbeat, so watches created on a passive instance are adopted automatically. Delivery is at-least-once: a fire logged but not delivered before a crash is requeued on the next boot from its delivered watermark.

The browser half is a dock card above the composer (the conversation.input.dock family) listing the session's active watches — sensor, target, live probe state, fire budget, next-probe countdown — plus recent fire history when expanded. It polls the read-only state route and renders nothing when the session has no watches.

Two surfaces make the server-global watch set visible. A sidebar branch grows under every session row that has active watches (sidebar.workspaces.sessionRow.branch, one shared poller for all rows) — collapsed it is a 👁 count, expanded it lists the session's watches and links to the dashboard. The dashboard is a standalone table of every watch across every session: session (active/dormant), sensor, target, pattern, fire budget, last probe state, next probe.

Sidebar branchGlobal dashboard

Sensors

KindEngineFires on
filepath snapshot + inotify pushsnapshot change (sub-second); accelerated by fs events
commandread-only shell line, probed on an intervaloutput/exit-code change
httpURL probed on an intervalstatus/body change
processpgrep -f pattern, probed on an intervalmatch-set change
portTCP connect to [host:]port, probed on an intervalreachability change (open/closed/timeout)
webhookpure pushany POST to the returned hook URL

With pattern, probe kinds fire on the no-match→match edge of that regex and webhooks accept only matching payloads; without it, probe kinds fire on any change after the baseline.

Configuration

All deployment-tunable knobs live in the plugin's config schema (defaults in parentheses); override them on the bundle row in your profile's cordis.patch.yml:

…

Related plugins