papachong/deepseek-harness-tui--packages-bundle-tui ↗★ 3
@ruhooai/dsh-tui
dsh 终端界面捆绑:基于代理主干的多轮 TUI REPL,含审批与提问应答,无主机或浏览器层。 适合偏好终端交互的用户,逐行驱动代理轮次并渲染流式输出;需 Bun 运行时,独立于配置档模板。
安裝
此插件尚未提供可驗證的 bundle,或相容性檢查未通過。請先閱讀倉庫說明。 閱讀完整 README ↗
說明文件
閱讀完整 README ↗@ruhooai/dsh-tui
English | 中文
The dsh terminal UI bundle: a multi-turn in-process TUI REPL over the agent spine with approval and ask-user answerers, no Host, HTTP server, or browser layer. The published dsh-tui bin boots an external cordis.yml, reads stdin lines, drives one agent turn per line, renders streaming assistant text + tool lines to stdout, and answers approval/ask-user prompts from stdin. It is a standalone bin — not registered in PROFILE_TEMPLATES — so it adds no in-tree core edit (per the upstream-follow strategy).
Runtime: Bun
The dsh-tui bin requires the Bun runtime: OpenTUI's bun:ffi loads the platform .so/.dylib/.dll that draws the terminal, and that FFI is Bun-only. The bin shebang is #!/usr/bin/env node for tooling compatibility, but launching it under Node crashes at the first FFI call.
Run it under Bun explicitly:
bun $(which dsh-tui)
bunx --bun dsh-tui
bun lib/bin.js
Set DSH_CORDIS_CONFIG or pass a config path as argv[2] to point at an external cordis.yml. The view layer's lib/view/app.js is produced by Bun.build (see scripts/build-view.ts); tsdown cannot compile Solid JSX, and @opentui/solid's transform plugin is Bun-only.
Config discovery
The first non-empty channel wins: $DSH_CORDIS_CONFIG, then positional argv[2]. If neither names an existing file, the bin prints one-line usage to stderr and exits 1. Set DSH_SNAPSHOT=replay to swap cordis.yml → cordis.snapshot.yml in the same directory for keyless llm-replay (no DEEPSEEK_API_KEY). DSH_SESSION_ROOT overrides the JSONL backend root; DSH_CWD overrides the bash/filesystem cwd.
stdin is the REPL
stdin carries one task line per turn (piped or typed). The bin pauses stdin before boot so a piped writer's data survives the async boot, then creates the line dispatcher after boot (src/input.ts) and drains it in a REPL loop — each line drives one agent.followup turn. stdout is the terminal surface; diagnostics go to stderr.
The dispatcher is the single owner of the readline interface: each line goes to exactly one consumer — a pending approval/ask-user prompt reader if any, otherwise the task-line queue. This fixes the shared-readline double-consumption bug where an approval answer line was also queued into the REPL iterator and replayed to the agent as a fake task. output: process.stdout is wired into readline because without it Node leaves rl.output undefined and silently drops every echo/refresh write (while terminal: true already disabled the driver-level ECHO), so keystrokes would never be displayed.
Built-in REPL commands: exit / quit / /exit / /quit end the REPL cleanly (exit 0) instead of being sent to the agent; Ctrl-D (EOF) and Ctrl-C (SIGINT) also exit.
Model Experience
Indirectly, through the plugins loaded from the external cordis.yml, which own every model-bound prompt, schema, message, and result; this bin adds none of its own.
KV Cache effect
No direct invalidation; the named consumer owns any request-prefix changes.
Known Limitations and Deferred Work
- Raw stdout, not a terminal renderer — streaming text and
[tool/call]/[tool/result]lines are written directly viaprocess.stdout.write; there is no ANSI SGR, no markdown folding, no card components, no diff/todo/plan rendering. The render layer (pi-tui port +presentation.tscard dispatch) is Phase 2. - Line-mode stdin, not raw-mode —
terminal: !isTTY ? false : truereadline; no single-keystroke approval (y/n needs ``), no keypress handling, no autocomplete, no slash-commands. Raw-mode keyboard input is Phase 2. - No
--resume— the bin creates a fresh session per run; there is no JSONL reconstruction of a prior session.--resumeis Phase 3. - Answerers are line-mode and block the turn — the approval/ask-user answerers read a full line via the single-owner line dispatcher; under warp session-share,
SharedSessionWriteToLongRunningCommandsmay gate this and require a non-blocking answerer (see the analysis note §8). - Capture is dry-run only — the SessionEnd hook logs a capture intent to stderr; the real
sf memory captureis deferred until the user confirms (Phase 3 risk #4: must reuse ai-cli's redaction/idempotence). - Locale is not persisted across runs —
detectLocalereads the system env each launch;/langis session-scoped. Persisting the choice awaits adsh-tuiconfig block.