Bundle
dsh-event-watch
Condition-driven session wake for DeepSeek Harness: durable file and GitHub watches that fire a configured prompt into a target session (cold-wake included), with transition-only dedupe and throttling
- Source
- dolcejust-spec
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# dsh-event-watch
Condition-driven wake for [DeepSeek Harness](https://github.com/deepseek-ai/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
```sh
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)
```yaml
- 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
```sh
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
Install
dsh plugin --profile web add github:dolcejust-spec/dsh-event-watch
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-event-watch from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.