Skip to content
dsh.fish
Bundle

dsh-coyote

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

Source
THEWOLFWALKER
stars
1 stars
License
MIT
Updated
Updated 3 days ago

Readme

# dsh-coyote

**English** · [**简体中文**](README.zh-CN.md)

![DSH](https://img.shields.io/badge/DSH-DeepSeek%20Harness-1F6FEB?style=flat-square)
![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)
![Node.js](https://img.shields.io/badge/Node.js-339933?style=flat-square&logo=node.js&logoColor=white)
![Cordis](https://img.shields.io/badge/Cordis-plugin-FF6B6B?style=flat-square)
![pnpm](https://img.shields.io/badge/pnpm-F69220?style=flat-square&logo=pnpm&logoColor=white)
![Vitest](https://img.shields.io/badge/Vitest-6E9F18?style=flat-square&logo=vitest&logoColor=white)

![npm version](https://img.shields.io/npm/v/dsh-coyote?style=flat-square&logo=npm&logoColor=white)
![tests](https://img.shields.io/badge/tests-148-brightgreen?style=flat-square)
![license](https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square)
![awesome-dsh-plugin](https://img.shields.io/badge/awesome--dsh--plugin-listed-00B4D8?style=flat-square)

![18+](https://img.shields.io/badge/18%2B-adults%20only-E91E63?style=flat-square)
![safety](https://img.shields.io/badge/panic%20stop-always%20safe-00C853?style=flat-square)
![bzz](https://img.shields.io/badge/bzz%20bzz-zap-FF9800?style=flat-square)

Agent- and GUI-controlled [DG-LAB Coyote](https://www.dungeon-lab.com/) e-stim plugin for [DeepSeek Harness](https://github.com/deepseek-ai/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](#auto-stim).

> Adults only. This plugin controls a real e-stim power box. Read [Safety](#safety) before use.

**Control panel** — the DSH-aligned web GUI (⚡ Coyote button in the DSH sidebar footer):

![dsh-coyote control panel](docs/screenshots/panel.png)

## 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

```bash
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:

```yaml
- 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

| Tool | What it does |
|---|---|
| `coyote_status` | Full snapshot: link state, QR, device strengths, effective caps, cooldown, and (when enabled) the auto-stim block. Read-only. |
| `coyote_pair` | Start pairing; returns QR payload + renderable QR image. |
| `coyote_disconnect` | End the session: stop everything, break the App relation, arm the cooldown. |
| `coyote_set_strength` | Set A/B/both in the raw 0..200 domain. Absolute `value` or relative `delta`. Reports clamping. |
| `coyote_play_wave` | Play by preset name, declarative `spec`, or raw hex entries; `once`/`loop`, intensity scale, B-mirror. |
| `coyote_stop_wave` | Stop waveform playback, keep strength levels. |
| `coyote_panic_stop` | Emergency: clear both waveform queues, zero both strengths. Idempotent, always safe. |
| `coyote_waveforms` | List 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:

| Mechanism | Default | Effect |
|---|---|---|
| Soft limits `softLimitA/B` | 100 | Agent-side cap per channel, 0..200. |
| Device hard limits | App-side | Effective cap = `min(soft, device)`; re-read on every report, so lowering the limit on the phone wins immediately. |
| Asymmetric rate limit | 40/s, burst 40 | Strength **increases** draw from a refilling token bucket; **decreases always pass instantly**. |
| Session cooldown `sessionCooldownSec` | 3 s (adjustable, 0 disables) | A fresh pairing must wait after the previous session ended. Prevents rapid re-pair abuse. |
| Session hard cap `maxSessionSec` | 3600 s (0 disables) | One bound session auto-ends (stop + zero). |
| Playback cap `maxPlaySec` | 600 s | Every playback self-terminates; the device queue is cleared at the cap. |
| Disconnect fail-safe | always on | App 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](https://github.com/SweetSmellFox/DG-Lab-Coyote-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:

| Gate | Effect |
|---|---|
| rule enabled | Events without an enabled rule do nothing. |
| armed | The GUI 布防/解除 switch; disarmed drops every event silently. |
| not busy | One pulse at a time, restore included. |
| cooldown | Default 5 s minimum between auto triggers. |
| App bound | No device connected → skipped, counted. |
| maxIntensity | Independent cap (default 30) on top of every rule. |

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

| Event | Fires when | Default |
|---|---|---|
| `turn_start` | A new agent turn begins | tap @12, 2 s, on |
| `assistant_start` | First streamed chunk of a turn | tap @15, 2 s, on |
| `stream_tick` | Every `tickIntervalSec` (5 s) of streaming | tremor @15, 2 s, **off** |
| `tool_call` | The model invokes a tool | tap @20, 2 s, on |
| `tool_error` | A tool call fails | punish @25, 6 s, on |
| `agent_error` | Turn fails (deduped per turn across both event sources) | punish @30, 8 s, on |
| `turn_end_completed` | Turn completes | heartbeat @20, 4 s, on |
| `turn_end_aborted` | Turn aborted / interrupted / blocked | calm @12, 3 s, off |
| `turn_end_max_tokens` | Turn hit the token ceiling | saw @20, 3 s, off |
| `todo_clear` | Todo list becomes all-completed (fires once per list) | heartbeat @18, 4 s, on |
| `agent_idle` | Agent running→idle edge | calm @12, 4 s, off |

```yaml
- 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.

| Key | Default | Notes |
|---|---|---|
| `host` | `0.0.0.0` | Bind address. `0.0.0.0` lets LAN phones reach the QR URL. |
| `port` | `9999` | WebSocket port. `0` = OS-assigned. |
| `publicWsUrl` | — | QR base override for `wss://` reverse proxies. |
| `waveformDir` | `coyote-waveforms` | Where community imports persist. |
| `softLimitA` / `softLimitB` | `100` | Per-channel agent-side caps, 0..200. |
| `sessionCooldownSec` | `3` | Cooldown before re-pairing; `0` disables. |
| `maxSessionSec` | `3600` | Hard cap per bound session; `0` disables. |
| `maxPlaySec` | `600` | Hard cap per playback. |
| `defaultPlaySec` | `30` | Duration when a tool call omits one. |
| `increaseRatePerSec` | `40` | Sustained increase speed (units/s). |
| `increaseBurst` | `40` | Immediate increase budget (units). |
| `autoStim` | `enabled: false` | The whole [Auto-stim](#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](https://github.com/ZGQ-inc/DG-LAB-OPENSOURCE) socket/V3 documentation.

## Development

```bash
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](LICENSE) · third-party notices in [NOTICE.md](NOTICE.md)

Install

dsh plugin --profile web add github:THEWOLFWALKER/dsh-coyote

Profile: web

  • 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.
Source