Skip to content

feat(alerts): add alerts daemon for real-time WS streaming (ECINT-6417) - #354

Draft
gulshngill wants to merge 2 commits into
mainfrom
feat/ecint-6417-alerts-daemon-v2
Draft

feat(alerts): add alerts daemon for real-time WS streaming (ECINT-6417)#354
gulshngill wants to merge 2 commits into
mainfrom
feat/ecint-6417-alerts-daemon-v2

Conversation

@gulshngill

Copy link
Copy Markdown
Contributor

Summary

Adds nansen alerts daemon — a persistent WebSocket client that connects outbound to Nansen's alert stream. No public URL or inbound ports required; works behind any firewall/NAT.

Builds on top of #341 (webhook channel support).

Changes

  • src/daemon/alerts-daemon.jsAlertsDaemon class: WS connect/reconnect, backfill, action hooks, keepalive, state persistence
  • src/commands/daemon.js — CLI subcommand: run, start, stop, status, logs
  • src/daemon/mock-server.js — local dev mock server
  • skills/nansen-alerts-daemon/SKILL.md — agent skill
  • 318 unit tests

Usage

nansen alerts daemon run                                         # foreground NDJSON
nansen alerts daemon start --action 'openclaw inject ...'       # background + hook
nansen alerts daemon status
nansen alerts daemon stop

Linear

Closes ECINT-6417

@gulshngill
gulshngill marked this pull request as draft March 26, 2026 14:33
Base automatically changed from feat/ecint-6515-webhook-channel to main March 26, 2026 17:45
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gulshngill
gulshngill force-pushed the feat/ecint-6417-alerts-daemon-v2 branch from ff0173b to 11b50b1 Compare July 23, 2026 13:04
@gulshngill

Copy link
Copy Markdown
Contributor Author

🤖 Automated Branch Sync (claude-code) — 2026-07-23 13:05 UTC
Action: Rebased onto main
Conflicts resolved: src/cli.js (boolean flag list — kept PR's superset adding no-backfill and action-env)
Status: mergeable

@gulshngill

Copy link
Copy Markdown
Contributor Author

🤖 Automated PR Review (codex) — 2026-07-23 13:19 UTC
Reviewed: alerts daemon WebSocket/auth flow, reconnect/backfill state, process lifecycle, shell hooks, input security, schema/docs, and test coverage
Fixed: runtime WebSocket dependency/API compatibility; apikey auth; backfill persistence/deduplication; background logging and private file modes; PID, URL, and option validation; malformed-message/action errors; lint and regression gaps
Tests: passed — npm test (30 files, 1520 passed, 2 skipped), npm run lint, local foreground/background daemon integration, npm audit --omit=dev

@gulshngill

Copy link
Copy Markdown
Contributor Author

✅ Opus final review — approved

Reviewed all changes on feat/ecint-6417-alerts-daemon-v2 vs main after the automated agent runs.

Branch state

  • 2 commits ahead of main, main fully contained in branch → rebase is clean, PR MERGEABLE.
  • No leftover conflict markers (<<<<<<</=======/>>>>>>>) anywhere in the diff or tree.

Merge-conflict resolution (src/cli.js) — correct. The boolean-flag list kept the PR version as a superset of main; the two added flags (no-backfill, action-env) are exactly the daemon's boolean flags. Verified against daemon.js usage.

Automated fix(pr-loop): harden… commit — reviewed in full; net hardening, no regressions:

  • ws promoted to a real dependency + static import (removes the dynamic-import failure path).
  • Auth switched to the apikey header — matches the rest of the CLI (api.js:556).
  • Added: PID validation (positive safe int), --ws-url scheme validation (wss:// or ws://localhost), non-empty string-option validation, alert dedup + monotonic state writes, atomic state file (0700 dir / 0600 file), stdin error handling on the action hook, backfill routed through _handleMessage so replays dedup correctly.
  • No dangling refs to the removed --reconnect-delay / Authorization: Bearer.

Checks run locally (Node v22)

  • vitest run1520 passed, 2 skipped, 30 files (incl. 8 daemon tests).
  • eslint on daemon/cli files → clean.
  • check-changeset.js (CI pretest) → pass; changeset present.
  • Smoke: daemon --help, status (renders JSON), and --ws-url rejection of non-local ws:// all behave correctly.

Shippable. No fixes required.

(minor, non-blocking, not introduced by these changes: stop() doesn't abort an in-flight backoff sleep(), but the SIGINT/SIGTERM process.exit(0) covers it.)

— Claude Opus 4.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant