dsh-docks
A generic web-app docking system for DeepSeek Harness: any local web service (marimo, Pluto.jl, gradio, streamlit…) enters dsh via a one-line declaration and runs alongside the agent session. Zero rewriting, zero translation — just *.localhost wildcard host routing + iframe.
Form Factor (v2)
docks is a pure host docking service; the UI is delegated to DSH-better-sidebar:
- Docked content: open
http://<name>.localhost:3081/ in better-sidebar's embedded browser tab
- Dock control: better-sidebar's "docks" tab (app list + status dot + start/stop + open)
- agent tool: say "start marimo" in conversation, or call the
docks tool directly (list/status/start/stop/restart)
Prerequisites
- Install
dsh-better-sidebar (dsh plugin --profile web add dsh-better-sidebar)
- In better-sidebar settings enable
browserNoSandbox (globally disables the sandbox; accepts the "all web pages and GUI share an origin" tradeoff, only for locally trusted apps)
- (Optional) enable
agentOpenTools so the model can push URLs straight to the browser tab via sidebar_open
Usage
docks list (in conversation or via the agent tool) → get http://marimo.localhost:3081/
- Open the URL (sidebar_open or type it in the address bar) → marimo runs in the browser tab
- The "docks" control-panel tab → see all app states at a glance, start/stop, click "↗" to open the browser tab
Architecture
- host:
node:http listens on 127.0.0.1:3081, reverse-proxying to the app's real port by the Host header; injects FRAME_SCRIPT to collapse same-origin opens back into the iframe (docking semantics); serves /_docks/state, /_docks/command, /docks/api/origin
- client: a control-panel tab registered with better-sidebar (soft-dep; without it, pure agent tooling)
- agent: the
docks tool (list/status/start/stop/restart)
Configuration Reference
Declarations hang under - id: docks / config in the profile patch. listen.host only allows 127.0.0.1 (v1 does not offer 0.0.0.0; the security posture matches the dsh main service).
| field | default | description |
|---|
listen.host | 127.0.0.1 | 127.0.0.1 only |
listen.port | 3081 | port the docks panel service listens on (when taken, activation fails with a hint to change this value) |
restarts.maxConsecutive | 3 | consecutive-crash restart limit; exhausted → failed |
restarts.stableUptimeMs | 60000 | running continuously past this duration resets the restart counter |
restarts.backoffMs | 1000, 2000, 4000 | crash-restart backoff sequence (caps at the last value; the counter resets after continuous running ≥ stableUptimeMs) |
apps[] | [] | list of app declarations, see below |
App declarations come in two levels (v1 implements L0+L1):
L1 managed (docks handles start/stop, probing, crash restart, desired-state restore)
| field | default | description |
|---|
name | required | the subdomain; must match ^[a-z0-9-]+$, no duplicates |
title | name | display name |
command | required for L1 | executable (mutually exclusive with external) |
args | [] | command-line arguments |
port | required for L1 | the port the app actually listens on (pre-checked for occupancy before spawn) |
cwd / env | — | working directory / string environment variables |
healthPath | / | HTTP probe path, expecting a ready response (<500) |
healthTimeoutMs | 120000 | probe timeout (Pluto's first-boot precompilation can take a minute or two; 30000 suggested for marimo) |
startup | manual | manual starts on demand; auto starts on activation (a per-app exception) |
mode | attached | detached is accepted by the schema but v1 returns a "not implemented" error |
L0 pure access (no process; router passes straight through; the status dot unreachable means the external service is unreachable)
| field | description |
|---|
name | same as above |
external | required for L0, only supports local http://127.0.0.1: addresses, and must not be used with command |
Separating Compute from Observation (Security Note)
Heavy simulation does not go into docks. Run multi-core / long-running compute itself under tmux / systemd / slurm; docks only hosts the "observation surface" web app. docks shares dsh's lifecycle — a dsh crash takes attached app processes with it. The right posture: run compute in an external session and declare, in docks, only an L0 pure access pointing at its UI port (external: http://127.0.0.1:8501). The command/state endpoints listen on loopback only and reject cross-origin Origins.
Development
pnpm install
pnpm test # vitest
pnpm typecheck # tsc --noEmit
pnpm build # tsdown
(config details, L0/L1, startup model: see spec)