# dsh-plugin-log-forwarder [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![Node.js ≥ 20](https://img.shields.io/badge/Node-%E2%89%A520-brightgreen) ![Version 1.0.4](https://img.shields.io/badge/Version-1.0.4-blue) **English** | [简体中文](README.md) A real-time log forwarding plugin for **DeepSeek Harness**. It forwards every event produced by your Agent to external log systems — **WebSocket / Loki / local files** — so you can watch each step of the agent's reasoning and tool execution from an external page. Great for debugging, monitoring and demos. > Repository: ## Features | Feature | Description | |---|---| | Full event capture | Subscribes to Harness session events (`session/created`, `session/event`, `session/disposed`, `agent/error`) and normalizes them into a unified JSON format | | Three parallel outputs | WebSocket / Loki / local file channels are independent and can be **enabled simultaneously** | | Event filtering | `includeEventTypes` allowlist (takes precedence) and `excludeEventTypes` denylist | | Sensitive data redaction | Recursive, case-insensitive; redacts fields such as `api_key` / `password` / `token` / `secret` by default | | Pause / resume | Global pause (stops capture, keeps the WebSocket server online), controllable via Agent tools or panel buttons | | Channel monitoring | Per-channel runtime status and forwarded / failed / dropped counters | | Side-channel design | Read-only forwarding that never interferes with the Agent; clean shutdown of all connections and buffers on unload | How it works (topology): ``` DeepSeek Harness session event stream (session/created · session/event · session/disposed · agent/error) │ subscribe ▼ dsh-plugin-log-forwarder —— normalize → redact → filter → fan-out │ ├─► websocket ws://127.0.0.1:18765 (status page / broadcast to many clients) ├─► file *.jsonl per session (path supports {sessionId}) └─► loki POST /loki/api/v1/push (batching / buffering / backoff retry) └─► Loki ⇄ Grafana (Explore / Live / graphs) ``` ## Screenshots

WebSocket channel: live event stream DSH Settings → Log Forwarder panel

Grafana Explore querying Loki logs Grafana × Loki: another query view

Left: WebSocket live event stream · Top right: settings panel (channel toggles / stats / pause / resume) · Bottom: querying Loki logs in Grafana

--- ## Quick Start ### 0. Prerequisites - DeepSeek Harness (desktop or headless) installed and running. - Know your active profile directory: `~/.dsh/profiles/` (Windows: `%USERPROFILE%\.dsh\profiles\`). The examples below use `desktop` — replace it with your own. - Node.js ≥ 20 (only needed to build from source). ### 1. Install the plugin (pick one) #### Option A: source + directory link — no release needed, editable ```bash git clone https://github.com/zhaoxuejie/dsh-plugin-log-forwarder.git cd dsh-plugin-log-forwarder npm install # install dependencies (@deepseek-ai/cordis, ...) npm run build # generate lib/ (build artifacts are not committed) ``` Link the package into the profile's `node_modules` so Harness can resolve it by name: - Windows (PowerShell, directory junction): ```powershell New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-plugin-log-forwarder" -Target "D:\path\to\dsh-plugin-log-forwarder" ``` - macOS / Linux (symlink): ```bash ln -s /path/to/dsh-plugin-log-forwarder ~/.dsh/profiles/desktop/node_modules/dsh-plugin-log-forwarder ``` #### Option B: official CLI install (no source code, recommended) DeepSeek Harness ships a plugin management command (it runs `pnpm add` in the profile directory). Once the package is **published on npm**: ```bash dsh plugin --profile desktop add dsh-plugin-log-forwarder # replace desktop with your profile ``` If it is not published yet, either of these works too (both resolvable by pnpm): ```bash # Alternative 1: install straight from the GitHub repo (pnpm runs prepare to build lib/) dsh plugin --profile desktop add github:zhaoxuejie/dsh-plugin-log-forwarder # Alternative 2: build a local tarball and install it (npm pack runs prepare, tarball already contains lib/) npm pack # inside the repo → dsh-plugin-log-forwarder-1.0.4.tgz dsh plugin --profile desktop add D:/path/to/dsh-plugin-log-forwarder-1.0.4.tgz ``` > With Option B the plugin is ready right after install (WebSocket channel on by default). The package ships `dsh.bundle.patch` (root `cordis.patch.yml`), so its default config is merged automatically once the profile references it. To enable file / loki or tweak options, paste the override config in "2. Enable the plugin" below. ### 2. Enable the plugin > Option A (source + directory link) needs the manual declaration below. Option B (`dsh plugin add`) auto-merges the built-in default config and works out of the box. The block below is for **customizing channels** (enable file / loki, change the port, …) — paste it as-is for either install method; it merges with the defaults without conflicts. Edit the profile patch `~/.dsh/profiles/desktop/cordis.patch.yml` and append to the `- insert:` list: ```yaml - insert: - id: log-forwarder name: dsh-plugin-log-forwarder config: enable: true # master switch autoStart: true # start forwarding right after load channels: websocket: enable: true # WebSocket channel port: 18765 # binds 127.0.0.1; auto +1 up to 5 times if busy file: enable: false # local file channel path: '' # path template; empty → ~/.deepseek-harness/logs/session-.jsonl maxFileSizeBytes: 52428800 loki: enable: false # Loki channel url: 'http://localhost:3100' tenantId: '' # required for multi-tenant Loki (X-Scope-OrgID) token: '' # optional Bearer token bufferSize: 1000 # in-memory buffer limit; drops oldest when full maxConsecutiveFailures: 10 filter: includeEventTypes: [] # allowlist, empty = all excludeEventTypes: [] redaction: enable: true sensitiveFields: [api_key, apikey, password, token, secret, authorization] ``` You may also declare only `- id:` / `name:` without `config`; omitted options fall back to the built-in defaults (only WebSocket enabled by default, other channels off). ### 3. Restart and verify **Restart DSH Desktop / reload the plugin** — channels are assembled when the plugin loads; config is **not** hot-reloaded. Verification (any of): ```bash # Open the built-in status page (live event stream + pause/resume buttons) start http://127.0.0.1:18765/ # Watch the live event stream from a terminal npx wscat -c ws://127.0.0.1:18765 ``` Or simply tell your Agent "check log forwarding status" — the plugin registers 4 model tools (below). --- ## Using the Three Channels Detailed configuration, copy-paste verification commands and advanced usage for every channel live in **[docs/usage.md](docs/usage.md)**. Essentials: ### WebSocket — live debugging / external monitor page - Default endpoint `ws://127.0.0.1:18765`, broadcast to many clients; `http://127.0.0.1:18765/` is the built-in status page. - Each message is one normalized event JSON, e.g.: ```json { "id": "evt_0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d", "timestamp": "2026-09-09T08:46:37.372Z", "sessionId": "session_1", "turnIndex": 4, "type": "tool_call", "payload": { "turn": 4, "step": 3, "callId": "call_1", "name": "shell", "arguments": { "command": "ls -la" } } } ``` - REST control endpoints (pause / resume / clear stats) and client examples: see [docs/api.md](docs/api.md). ### File — local JSONL archive - If `path` contains the `{sessionId}` placeholder, one file **per session** is written; otherwise all sessions append to a single file. - Default directory: `~/.deepseek-harness/logs/session-.jsonl`, one event JSON per line. - Tip: `sessionId` already carries a `session-` prefix, so use `{sessionId}.jsonl` — do **not** use `session-{sessionId}.jsonl` (that produces a double `session-session-…` prefix). ### Loki — centralized search + Grafana dashboards - The channel batches events and POSTs them to `{url}/loki/api/v1/push` (when 100 events pile up or every 200 ms), with fixed labels `source` / `session_id` / `event_type` / `turn_index`. - Network failures trigger exponential-backoff retries; after `maxConsecutiveFailures` (default 10) consecutive failures the channel marks itself `disconnected` and stops retrying until a manual resume (tell your Agent "resume log forwarding" → `log_forwarder_resume`). - Querying from Grafana: ```logql {source="dsh-log-forwarder"} {source="dsh-log-forwarder", session_id="session_1"} {source="dsh-log-forwarder", event_type="tool_call"} ``` ## Model Tools The plugin registers the following tools for the Agent (you can also ask your Agent to run them): | Tool | Purpose | |---|---| | `log_forwarder_status` | Global status: running, event totals, per-channel forwarded/failed/dropped | | `log_forwarder_channel_status` | Single channel detail (`channel: websocket\|file\|loki`), incl. output target | | `log_forwarder_pause` | Pause forwarding (stops capture, keeps WebSocket server online) | | `log_forwarder_resume` | Resume forwarding; also reconnects a disconnected Loki channel and retries a failed WebSocket | Natural-language examples: "check log forwarding status", "pause log forwarding", "resume log forwarding". ## Event Types Events are mapped to standard types: `session_start` / `user_input` / `turn_start` / `reasoning` / `model_output` / `tool_call` / `tool_result` / `tool_error` / `turn_end` / `session_end` / `error`; events that cannot be mapped pass through under their raw type name (fault-tolerant, no data loss). Full mapping and payload fields: see [docs/api.md](docs/api.md). ## Built-in Side Panel (optional) The plugin ships with a standard Harness client panel (`src/client/`, bundled to `lib/client.js`): it shows live events and channel stats in the Harness settings section "Log Forwarder", with pause / resume / clear buttons. No host-side change is required for it to appear; development details such as real-time push enhancement: see [docs/api.md](docs/api.md). ## FAQ | Symptom | Fix | |---|---| | Config changes have no effect | Config is read at plugin load only — restart DSH / reload the plugin | | Loki channel `disconnected` | Tell the Agent "resume log forwarding", or click "Resume" in the panel | | WebSocket port already in use | The plugin tries `+1` automatically (up to 5 times); use the port shown on the status page | | File names look like `session-session-…` | Use `{sessionId}.jsonl`, do not add a `session-` prefix in the template | | Filter or redact events | Configure `filter` and `redaction` (Quick Start §2) | | Need more copy-paste commands | See [docs/usage.md](docs/usage.md) (pause/resume behavior, Loki verification, Grafana walk-through) | ## Development & Testing (contributors) ```bash npm install npm run typecheck # host-side type check npm run typecheck:client # client panel type check npm run build # compile host side to lib/ npm run build:client # bundle client panel → lib/client.js npm test # standalone test runner (18 cases) ``` ## Docs - [docs/usage.md](docs/usage.md) — channel usage guide (config / verification commands / Grafana / FAQ) - [docs/api.md](docs/api.md) — API reference (config schema, event format, HTTP endpoints, tools) - [docs/PRD.md](docs/PRD.md) — product requirements (V1.0) - [CHANGELOG.md](CHANGELOG.md) ## License MIT © 2026