dsh-tui
[WIP] TUI for DeepSeeh-Harness
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 28, 2026
- Updated
- Aug 28, 2026
Introduction
dsh-tui
A lightweight terminal user interface for DeepSeek Harness (DSH). The TUI is a thin frontend client: it connects to a running DSH backend over the same HTTP/WebSocket protocol the Web UI uses, normalizes backend events into a small client-side model, and renders them in the terminal. The backend stays the single source of truth — the TUI never runs agents, tools, sandboxes, or models itself.
Browser Terminal
│ │
▼ ▼
DSH Backend ◄── HTTP RPC + WebSocket ──► dsh-tui
Features
- Attach to any existing DSH session and resume it later
- Live streaming: assistant text, reasoning (collapsible), tool cards, run state, session titles, token usage
- Interactive approvals and user questions answered through the backend
- Interrupt the current run (Esc)
- Session picker, new-session creation, command palette
- Reconnect with bounded backoff; dedup by backend identifiers
- Offline replay of captured event traces for development and regression tests
Requirements
- Node.js ≥ 20.18 (Node 22+ runs the TypeScript sources directly)
- A running DSH server:
dsh web(defaulthttp://127.0.0.1:3080)
Usage
npm install
npm run build # compile to dist/
# Attach to an existing session
node dist/cli.js --session <session-id>
# Create a fresh session in the current directory and attach
node dist/cli.js --new
# Point at a non-default server
node dist/cli.js --server http://127.0.0.1:8080 --session <id>
# No session argument → session picker on startup
node dist/cli.js
# Offline replay of a captured trace
node dist/cli.js --replay fixtures/pong-session.jsonl --speed 2
The npm bin is dsh-tui; after npm link (or installing the package) the
commands above become dsh-tui --session <id>.
Keys
| Key | Action |
|---|---|
Enter | send — next-step delivery while the agent is running (» prompt), native queue when idle |
Ctrl+Enter | steer — cancel the current step, continue with your message |
Ctrl+Shift+Enter | backlog — queue as its own turn after the current one |
Shift+Enter | new line in the composer (multiline input) |
Esc | interrupt the current run |
Ctrl+C / Ctrl+D | quit (press twice to force) |
Tab / Shift+Tab | expand/collapse tools |
Ctrl+T | toggle reasoning visibility |
Ctrl+O | toggle latest tool output |
Ctrl+P | command palette |
Ctrl+R | session picker |
PgUp / PgDn / Home / End | scroll conversation |
y / n | allow / deny a permission request |
1-9 + Enter | answer a question |
The three send modes follow the same semantics as the web UI's
queue-steer-button: Enter while running delivers the message with the next
agent step (session.prompt steer mode), Ctrl+Enter aborts the current step
first and then delivers, and Ctrl+Shift+Enter puts the message in the
whole-turn backlog.
Slash commands: typing / in the composer shows matching commands inline
(also available via Ctrl+P): /new, /sessions, /stats, /models,
/config, /permission, /reconnect, /reasoning, /tools, /clear,
/help, /quit. Any other /… text is forwarded to the backend's own
slash-command registry (e.g. /compact, /goal, /permission <preset>).
/stats— detailed session statistics from the live projections: turns, steps, LLM time, tool-call time, average TTFT, decode time, output tokens, throughput (TGS), input tokens (uncached + cache read), cache hit rate, and context pressure./models— browse provider groups and models;Enterapplies,rcycles the reasoning effort (session.selectModel)./config— read-only view of the deployment settings namespaces (settings.describe)./permission— shows the current sandbox permission and the available presets;Enterapplies through the backend/permissioncommand.
Architecture
src/
├── cli.ts CLI entry (args, TUI bootstrap, wiring)
├── test-client.ts headless smoke client (Milestone B)
├── client/
│ ├── types.ts normalized TUI event model (HarnessEvent, …)
│ ├── protocol.ts wire envelopes: /api RPC, respond, event streams
│ ├── events.ts raw frame → HarnessEvent normalization (once)
│ ├── harness-client.ts stable client boundary (interface)
│ ├── dsh-client.ts concrete DSH client (HTTP + WebSocket + reconnect)
│ └── reconnect.ts bounded-backoff helpers
├── state/
│ ├── state.ts DshTuiState
│ ├── reducer.ts pure reduceHarnessEvent / reduceUiAction
│ ├── fold.ts history folding (shares reducer semantics)
│ └── store.ts tiny observable store
├── components/ Pi-based rendering: app, header, conversation,
│ message, tool-call, interaction, composer, status-line,
│ session-picker, command-palette, help
└── testing/
└── replay-client.ts offline trace replay client
Design rules (from the implementation plan):
- The TUI depends only on the
HarnessClientboundary — never on transport, wire schemas, Cordis internals, or backend implementation details. - All backend-specific interpretation lives in
client/events.ts(normalize exactly once). - The state reducer is pure and unit-tested; rendering consumes state, not wire messages.
- Interactions are backend-authoritative: an approval is only settled when the
backend confirms it (
approval/resolved/question/resolvedframes). - Backend message/tool ids are preserved for dedup and reconnect.
Development
npm run typecheck
npm test # 46 unit + integration tests
npm run test:client # headless smoke client against a live server
npm run dev -- --replay fixtures/interaction.jsonl # live TUI on a fixture
Captured fixtures live in fixtures/:
live-trace.jsonl— 1336 frames captured from a live mux streampong-session.jsonl— a complete small run (user → stream → answer)interaction.jsonl— approval + question lifecycle (requested → resolved)interaction-pending.jsonl— approvals/questions left pending
Documentation
docs/tui/dsh-frontend-protocol.md— the DSH frontend protocol (transport, RPC map, event streams, session events, interactions, reconnect semantics), written from the shipped DSH package and verified against a live backend.deepseek-harness-tui-implementation-plan.md— the plan this project implements.
Non-goals (MVP)
No embedded terminal emulator, file explorer, diff editor, workspace management, model configuration, sandbox lifecycle, multi-session panes, local trajectory persistence, or direct LLM calls. The TUI is a client; the backend does the work.
License
MIT