Back to home@lyuwen

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 (default http://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

KeyAction
Entersend — next-step delivery while the agent is running (» prompt), native queue when idle
Ctrl+Entersteer — cancel the current step, continue with your message
Ctrl+Shift+Enterbacklog — queue as its own turn after the current one
Shift+Enternew line in the composer (multiline input)
Escinterrupt the current run
Ctrl+C / Ctrl+Dquit (press twice to force)
Tab / Shift+Tabexpand/collapse tools
Ctrl+Ttoggle reasoning visibility
Ctrl+Otoggle latest tool output
Ctrl+Pcommand palette
Ctrl+Rsession picker
PgUp / PgDn / Home / Endscroll conversation
y / nallow / deny a permission request
1-9 + Enteranswer 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; Enter applies, r cycles 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; Enter applies through the backend /permission command.

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):

  1. The TUI depends only on the HarnessClient boundary — never on transport, wire schemas, Cordis internals, or backend implementation details.
  2. All backend-specific interpretation lives in client/events.ts (normalize exactly once).
  3. The state reducer is pure and unit-tested; rendering consumes state, not wire messages.
  4. Interactions are backend-authoritative: an approval is only settled when the backend confirms it (approval/resolved / question/resolved frames).
  5. 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 stream
  • pong-session.jsonl — a complete small run (user → stream → answer)
  • interaction.jsonl — approval + question lifecycle (requested → resolved)
  • interaction-pending.jsonl — approvals/questions left pending

Documentation

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