Back to home

THEWOLFWALKER

dsh-coyote

Agent- and GUI-controlled DG-LAB Coyote e-stim plugin for DeepSeek Harness: safety-bounded strength, programmable waveforms, DSH-aligned web panel

Stars
1
Language
TypeScript
Created
Aug 15, 2026
Updated
Aug 16, 2026

Introduction

dsh-coyote

English · 简体中文

Agent- and GUI-controlled DG-LAB Coyote e-stim plugin for DeepSeek Harness (DSH).

One safety envelope serves two faces with equal bounds: eight model-facing coyote_* tools and a DSH-aligned browser panel. Neither can bypass soft limits, the asymmetric increase rate limiter, session cooldown, playback caps, or the disconnect fail-safe. Since v0.2 an opt-in auto-stim layer can also map agent events (tool calls, errors, turn ends…) to bounded pulses — see Auto-stim.

Adults only. This plugin controls a real e-stim power box. Read Safety before use.

How it works

DSH agent ──coyote_* tools──┐
                             ├─▶ CoyoteRuntime (safety envelope) ─▶ V3 socket server ─▶ DG-LAB App (QR) ─▶ Coyote device
DSH web GUI ──/gui bridge───┘         soft limits · rate limit · cooldown · hard caps · fail-safe
agent events ──auto-stim (opt-in)──┘  armed gate · cooldown · maxIntensity cap · baseline restore

The plugin is the WebSocket server side of the official DG-LAB V3 socket protocol: pairing mints a QR the official App scans; from then on the plugin is the control terminal and the App relays commands to the device.

Quick start

dsh plugin --profile web add dsh-coyote

The plugin's bundle patch inserts its row automatically — zero config needed, the defaults below apply. To tune, target the inserted row in your profile overlay:

- id: coyote
  config:
    port: 9999          # WebSocket port; the phone must reach this machine
    softLimitA: 60      # agent-side caps, 0..200 — tune to the user's comfort
    softLimitB: 60
  1. The DSH web GUI shows a ⚡ Coyote button in the sidebar footer — it opens the control panel.
  2. Tap 开始配对 (pair); a QR appears.
  3. Scan it with the official DG-LAB App on a phone on the same network (or set publicWsUrl behind a wss:// reverse proxy).
  4. The panel and the agent can now both drive the device — always within the same limits.

The eight tools

ToolWhat it does
coyote_statusFull snapshot: link state, QR, device strengths, effective caps, cooldown, and (when enabled) the auto-stim block. Read-only.
coyote_pairStart pairing; returns QR payload + renderable QR image.
coyote_disconnectEnd the session: stop everything, break the App relation, arm the cooldown.
coyote_set_strengthSet A/B/both in the raw 0..200 domain. Absolute value or relative delta. Reports clamping.
coyote_play_wavePlay by preset name, declarative spec, or raw hex entries; once/loop, intensity scale, B-mirror.
coyote_stop_waveStop waveform playback, keep strength levels.
coyote_panic_stopEmergency: clear both waveform queues, zero both strengths. Idempotent, always safe.
coyote_waveformsList the library; import Game-Hub .pulses JSON or bare hex lists.

Tool descriptions teach the safety model, so the model behaves well without reading source.

Safety model

Every bound — enforced in CoyoteRuntime before anything is sent:

MechanismDefaultEffect
Soft limits softLimitA/B100Agent-side cap per channel, 0..200.
Device hard limitsApp-sideEffective cap = min(soft, device); re-read on every report, so lowering the limit on the phone wins immediately.
Asymmetric rate limit40/s, burst 40Strength increases draw from a refilling token bucket; decreases always pass instantly.
Session cooldown sessionCooldownSec3 s (adjustable, 0 disables)A fresh pairing must wait after the previous session ended. Prevents rapid re-pair abuse.
Session hard cap maxSessionSec3600 s (0 disables)One bound session auto-ends (stop + zero).
Playback cap maxPlaySec600 sEvery playback self-terminates; the device queue is cleared at the cap.
Disconnect fail-safealways onApp socket loss stops playback, clears queues, resets state.

The GUI panel goes through the identical runtime — the browser cannot bypass the agent's bounds, and vice versa.

Waveforms

  • 12 built-in presets (呼吸 Breathing, 心跳 Heartbeat, 惩罚 Punish, …) with suggested starting intensities, synthesized deterministically from declarative specs (frequency sweep 10..1000 ms, intensity sweep 0..100, curves linear|sine|pulse|random, optional on/off duty cycle).
  • Community import: paste Game-Hub .pulses JSON ([{name, pulseData:[hex…]}]) or bare hex lists; validated, persisted under waveformDir, reloaded on start. Re-importing a name replaces it.

Auto-stim

Opt-in event-driven stimulation (v0.2): the plugin listens to the DSH session event firehose plus the agent/error / agent/status runtime events, reduces them to a closed vocabulary of eleven domain events, and fires a bounded pulse for each rule you enable. Off by default — nothing listens and no section appears until autoStim.enabled: true.

Every pulse is an absolute transient: channel strength is boosted to min(rule.intensity, maxIntensity) (the runtime still clamps to soft/device limits and the rate limiter), the waveform plays once, then the pre-pulse strength is restored. Works from a freshly paired device at strength 0.

Gate chain — a pulse fires only if every gate passes, and gated events are dropped and counted, never queued:

GateEffect
rule enabledEvents without an enabled rule do nothing.
armedThe GUI 布防/解除 switch; disarmed drops every event silently.
not busyOne pulse at a time, restore included.
cooldownDefault 5 s minimum between auto triggers.
App boundNo device connected → skipped, counted.
maxIntensityIndependent cap (default 30) on top of every rule.

Default rule table (all intensities ≤ the default maxIntensity 30):

EventFires whenDefault
turn_startA new agent turn beginstap @12, 2 s, on
assistant_startFirst streamed chunk of a turntap @15, 2 s, on
stream_tickEvery tickIntervalSec (5 s) of streamingtremor @15, 2 s, off
tool_callThe model invokes a tooltap @20, 2 s, on
tool_errorA tool call failspunish @25, 6 s, on
agent_errorTurn fails (deduped per turn across both event sources)punish @30, 8 s, on
turn_end_completedTurn completesheartbeat @20, 4 s, on
turn_end_abortedTurn aborted / interrupted / blockedcalm @12, 3 s, off
turn_end_max_tokensTurn hit the token ceilingsaw @20, 3 s, off
todo_clearTodo list becomes all-completed (fires once per list)heartbeat @18, 4 s, on
agent_idleAgent running→idle edgecalm @12, 4 s, off
- id: coyote
  config:
    autoStim:
      enabled: true          # opt-in master switch
      maxIntensity: 30       # cap over every rule, 1..200
      cooldownSec: 5         # min seconds between auto triggers
      tickIntervalSec: 5     # stream_tick cadence
      restoreBaseline: true  # restore pre-pulse strength after each pulse
      events:
        tool_error: { intensity: 25, durationSec: 6, waveform: punish, channel: A }
        agent_error: { enabled: false }   # any field overrides, the rest inherit

Unknown event names fail loudly at startup with the valid list. Waveform names resolve case-insensitively: built-in id first, then imported waveform names (a typo fails soft per pulse and is logged — it cannot crash the host). The GUI panel gains an 自动电击 section with live armed state, fire/skip counters, and the 布防/解除 button; coyote_status carries the same block for the model. Teardown disarms, cuts an in-flight pulse short, and restores the baseline immediately.

Configuration

All fields optional; defaults shown.

KeyDefaultNotes
host0.0.0.0Bind address. 0.0.0.0 lets LAN phones reach the QR URL.
port9999WebSocket port. 0 = OS-assigned.
publicWsUrlQR base override for wss:// reverse proxies.
waveformDircoyote-waveformsWhere community imports persist.
softLimitA / softLimitB100Per-channel agent-side caps, 0..200.
sessionCooldownSec3Cooldown before re-pairing; 0 disables.
maxSessionSec3600Hard cap per bound session; 0 disables.
maxPlaySec600Hard cap per playback.
defaultPlaySec30Duration when a tool call omits one.
increaseRatePerSec40Sustained increase speed (units/s).
increaseBurst40Immediate increase budget (units).
autoStimenabled: falseThe whole Auto-stim block: enabled, maxIntensity (30), cooldownSec (5), tickIntervalSec (5), restoreBaseline (true), and per-events rule overrides.

Architecture

src/
  protocol/   pure V3 codec: frames, wave entries, QR payload
  waveform/   composer (spec→windows), library (12 presets), importer, scheduler
  transport/  WebSocket server + control-terminal role (bind, heartbeat, fail-safe)
  runtime/    the safety envelope everything routes through
  auto-stim/  rules (vocabulary+defaults+normalize), mapper (host→domain events), engine (gates+pulse), attach (cordis listeners)
  gui/        /gui bridge: JSON ops ↔ runtime, broadcasts status
  tools/      the 8 coyote_* tool definitions
client/index.js   browser panel (no build step, loader-injected React, DSH CSS variables)
tests/            148 tests: unit + protocol-level MockApp + offline client harness

Design rules: pure protocol functions, one safety choke point, thin honest tools, no over-design. Evidence for every protocol decision is the official DG-LAB-OPENSOURCE socket/V3 documentation.

Development

pnpm install
pnpm test         # 148 tests
pnpm typecheck
pnpm build        # lib/ (tsdown)

Note: pnpm build prints one tsdown↔rolldown upstream validation warning (Invalid input options … "define"); it is cosmetic, the artifacts are correct.

Safety

This plugin drives a real e-stim device on a human body. Follow the DG-LAB safety guidance that ships with your device. In short:

  • Start from single-digit strength; increase gradually; agree on limits and a stop signal with the user before starting.
  • Keep the App-side hard limit as the final guard — the runtime honors it dynamically.
  • Chest, neck, and head are unsafe electrode placements; stop immediately at any discomfort or unexpected behavior (coyote_panic_stop never makes things worse).
  • Use only with informed adult consent.

License

MIT · third-party notices in NOTICE.md