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 roots plus glob patterns (**/*.md); fires on created or changed (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 fetch for (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 maxWakePerWatchPerHour deliveries 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/audit event to the owning session's persisted log (marked ignorable so 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 (default GH_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 by pnpm run verify:zero-exec in 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 fetch only.
  • 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