dsh-event-watch
Condition-driven wake for DeepSeek Harness (DSH): watch files or GitHub state, and when a condition transitions, inject a configured prompt into a target DSH session - cold-waking it if the session is closed. Think of it as cron for conditions instead of time.
v1 is server-side only. It exists as a deliberately minimal alternative to community sentinels: zero runtime dependencies, zero process execution (Node built-in fetch only - no child_process, no exec, no spawn anywhere in the shipped code), path-contained file scans, and a durable, audited, throttled fire path.
Install
dsh plugin --profile web add dsh-event-watch
The npm tarball ships prebuilt lib/; consumer installs run no build scripts.
Sensors (v1)
- FILE - one or more absolute
rootsplus glob patterns (**/*.md); fires oncreatedorchanged(mtime + size, confirmed by content hash). Metadata only: file contents are read for hashing, never executed or interpreted. - GITHUB - polls the REST API with built-in
fetchfor (a) PR state changes (open/closed/merged/draft) on a repo (+optional branch filter), (b) check-run conclusion changes on a commit/branch, (c) new commits on a branch.
Behavior
- Fires on state transition only - a condition that is already true at watch creation only establishes the baseline; it fires when it changes later. Re-observing the same state never re-fires (deduplicated by signature).
- Throttled: at most
maxWakePerWatchPerHourdeliveries per watch per rolling hour (default 3). Dropped conditions are recorded, not silently lost. - Durable: watches live in a JSON file under the DSH home (
$DSH_HOME/event-watch/watches.json), written atomically (tmp + rename), hot-reloaded across processes, guarded by a single-instance engine lock. - Audited: every watch change, fire, throttle-drop, and sensor error appends an
event-watch/auditevent to the owning session's persisted log (markedignorableso older builds skip it) and a capped per-watch ring in the store. - Cold wake: a fire targeting a closed session resumes it with its recorded preset and model (adapted from dsh-cron) and delivers as the next turn.
- Secrets: the GitHub token is read from
tokenEnv(defaultGH_TOKEN) once per poll, sent only as an Authorization header, never stored, logged, or echoed; all plugin output passes a redaction pass. - Misconfiguration fails loud at load: unknown fields are rejected, paths must be absolute, poll interval >= 5000 ms.
Configuration (cordis.yml)
- id: event-watch
name: dsh-event-watch
config:
pollIntervalMs: 30000 # minimum 5000
maxWakePerWatchPerHour: 3
trial: true # surfaced by /event-watch status
targetSession: <session id> # default target; per-watch overrides, then creating session
watchers: # optional watches created at load
- id: docs
type: file
spec:
roots: ['D:\docs']
globs: ['**/*.md']
trigger: changed
prompt: 'The docs changed; summarize what moved.'
Tool and command
The agent-facing event_watch tool manages watches:
event_watch { action: "list" }event_watch { action: "add", type: "file", spec: { roots: [...], globs: [...], trigger: "created" }, prompt: "..." }event_watch { action: "add", type: "github", spec: { repo: "owner/name", mode: "pr" }, prompt: "..." }event_watch { action: "remove", id: "watch-1" }event_watch { action: "pause" | "resume", id: "watch-1" }event_watch { action: "status" }
Humans get the /event-watch slash command (status view).
Security posture
- No
child_process/exec/spawn/fork- enforced bypnpm run verify:zero-execin CI. - File scans are contained: watched roots must be absolute, symlink escapes out of a root are blocked (fail closed), and scans are entry-count bounded.
- GitHub polling uses Node's built-in
fetchonly. - Token handling: env-read per poll, bearer header only, redaction on every log/audit path.
Development
pnpm install
pnpm test # unit + integration (no network; GitHub mode uses recorded fixtures)
pnpm run typecheck
pnpm run build
pnpm run verify:zero-exec
License
MIT
No comments yet. Be the first to write one.