hermes-agent/docs/plans/2026-04-01-ink-gateway-tui-migration-plan.md
2026-04-02 19:07:53 -05:00

1170 lines
30 KiB
Markdown

# TUI Refactor: Current to Ideal
Date: 2026-04-01
## Scope
- same repo refactor
- keep Python runtime
- replace PT-based interactive shell
- add Ink UI through a local gateway
## Current Environment
Interactive path is centered in `cli.py` with `prompt_toolkit` and `rich`.
Current technical shape:
- PT app shell and key handling in `cli.py`
- `Application`, `KeyBindings`, `TextArea`, `patch_stdout`
- queue control in `cli.py`
- `_pending_input`
- `_interrupt_queue`
- approval and sudo callback globals in `tools/terminal_tool.py`
- `_approval_callback`
- `_sudo_password_callback`
- runtime entry in `run_agent.py`
- `AIAgent.run_conversation()`
- `AIAgent.chat()`
Current constraint:
- UI logic and runtime control are mixed, so UI replacement is expensive.
## Ideal Environment
Interactive path is split into three layers:
1. Ink UI (Node/TS)
2. local `tui_gateway` over stdio JSON-RPC
3. Python runtime (`AIAgent`, tools, sessions)
Rules for ideal state:
- no direct UI to `AIAgent` calls
- no PT dependency in gateway path
- keep current Hermes state/config contracts
- `~/.hermes/.env`
- `~/.hermes/config.yaml`
- `~/.hermes/state.db`
- profile behavior via `HERMES_HOME`
## Migration Path
## Cut 1: Headless Controller
Add:
- `tui_gateway/controller.py`
- `tui_gateway/session_state.py`
- `tui_gateway/events.py`
Change:
- `run_agent.py` callback wiring for controller events
- `cli.py` compatibility bridge into controller
Done:
- create/resume/prompt/interrupt/cancel work with no PT imports
## Cut 2: Local Gateway
Add:
- `tui_gateway/protocol.py`
- `tui_gateway/server.py`
- `tui_gateway/entry.py`
Methods:
- `session.create`
- `session.resume`
- `session.list`
- `session.interrupt`
- `session.cancel`
- `prompt.submit`
- `approval.respond`
- `sudo.respond`
- `clarify.respond`
Events:
- `message.delta`
- `tool.progress`
- `approval.requested`
- `sudo.requested`
- `clarify.requested`
- `error`
Done:
- simple client completes full prompt cycle through JSON-RPC
## Cut 3: Ink UI
Add:
- `ui-tui/src/main.tsx`
- `ui-tui/src/gatewayClient.ts`
- `ui-tui/src/state/store.ts`
- `ui-tui/src/components/Transcript.tsx`
- `ui-tui/src/components/Composer.tsx`
- `ui-tui/src/components/StatusBar.tsx`
- `ui-tui/src/components/ApprovalModal.tsx`
- `ui-tui/src/components/SudoPrompt.tsx`
- `ui-tui/src/components/ClarifyPrompt.tsx`
Change:
- `tools/terminal_tool.py` prompt adapters for gateway round-trip
Done:
- chat, tools, approval, sudo, clarify, interrupt all work through gateway
## Cut 4: Opt-In and Rollout
Entry points:
- `hermes --tui`
- `HERMES_EXPERIMENTAL_TUI=1`
- `display.experimental_tui: true`
- `/tui`, `/tui on`, `/tui off`, `/tui status`
Behavior:
- `/tui` starts gateway if needed and attaches
- failed attach falls back to PT mode with explicit error text
- `/tui off` disables auto-launch only
Rollout:
1. internal opt-in
2. external opt-in beta
3. default-on after checks pass
4. remove PT path later
## Acceptance Checks
- runtime: no PT import in controller/gateway path
- state: same config/profile/session continuity
- commands: slash command registry remains `hermes_cli/commands.py`
- permissions: approval/sudo/clarify protocol round-trip
- streaming: incremental assistant and tool updates
- opt-in: flag/env/config/slash command share one launch path
## Test Commands
- `python -m pytest tests/tui_gateway/test_controller.py -q`
- `python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q`
- `python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q`
- `python -m pytest tests/hermes_cli/test_tui_opt_in.py -q`
- `cd ui-tui && npm run build`
- `cd ui-tui && npm run test`
# Prompt Toolkit to Ink Migration Plan
Date: 2026-04-01
## Scope
This is a refactor in the same repo.
- no new repo
- no runtime rewrite
- no messaging gateway reuse for terminal UI
## Current Environment
Interactive Hermes today is `prompt_toolkit` plus `rich` inside `cli.py`.
Current structure:
- PT app shell and input handling in `cli.py`
- `Application`
- `KeyBindings`
- `TextArea`
- `patch_stdout`
- queue-based control flow in `cli.py`
- `_pending_input`
- `_interrupt_queue`
- approval and sudo callbacks in `tools/terminal_tool.py`
- `_approval_callback`
- `_sudo_password_callback`
- core runtime in `run_agent.py`
- `AIAgent.run_conversation()`
- `AIAgent.chat()`
Current issue:
- UI framework logic and runtime control flow are mixed in one path.
- Tool prompt routing depends on PT callback globals.
- Replacing UI without changing runtime is harder than it should be.
## Ideal Environment
Interactive Hermes is Ink UI plus a local TUI gateway.
Target model:
- Python runtime remains the source of truth.
- UI talks to `tui_gateway` over stdio JSON-RPC.
- `tui_gateway` talks to `AIAgent`.
- no direct UI to `AIAgent` coupling.
Target compatibility:
- same `~/.hermes/.env`
- same `~/.hermes/config.yaml`
- same `~/.hermes/state.db`
- same profile behavior through `HERMES_HOME`
## How To Get There
Use three delivery cuts and one switch cut.
## Cut 1: Headless Runtime Controller
Goal:
- separate runtime control from PT.
Add:
- `tui_gateway/controller.py`
- `tui_gateway/session_state.py`
- `tui_gateway/events.py`
Change:
- `run_agent.py` callback wiring needed by controller
- `cli.py` compatibility calls into controller
Done when:
- controller supports create/resume/prompt/interrupt/cancel
- controller path imports no PT modules
- tool progress and assistant deltas are typed events
## Cut 2: Local TUI Gateway
Goal:
- add stable protocol boundary for UI.
Add:
- `tui_gateway/protocol.py`
- `tui_gateway/server.py`
- `tui_gateway/entry.py`
- `tui_gateway/__init__.py`
Protocol methods:
- `session.create`
- `session.resume`
- `session.list`
- `session.interrupt`
- `session.cancel`
- `prompt.submit`
- `approval.respond`
- `sudo.respond`
- `clarify.respond`
Protocol events:
- `message.delta`
- `tool.progress`
- `approval.requested`
- `sudo.requested`
- `clarify.requested`
- `error`
Done when:
- a simple client can complete one full prompt cycle over stdio JSON-RPC
## Cut 3: Ink UI
Goal:
- usable clone flow through gateway.
Add:
- `ui-tui/package.json`
- `ui-tui/src/main.tsx`
- `ui-tui/src/gatewayClient.ts`
- `ui-tui/src/state/store.ts`
- `ui-tui/src/components/Transcript.tsx`
- `ui-tui/src/components/Composer.tsx`
- `ui-tui/src/components/StatusBar.tsx`
- `ui-tui/src/components/ApprovalModal.tsx`
- `ui-tui/src/components/SudoPrompt.tsx`
- `ui-tui/src/components/ClarifyPrompt.tsx`
Change:
- `tools/terminal_tool.py` adapters for gateway request/response prompt routing
Done when:
- user can chat, run tools, approve, deny, clarify, interrupt, and continue
## Cut 4: Opt-In Switch and Rollout
Goal:
- ship without forced cutover.
Entry points:
- `hermes --tui`
- `HERMES_EXPERIMENTAL_TUI=1`
- `display.experimental_tui: true`
- `/tui`, `/tui on`, `/tui off`, `/tui status`
Behavior:
- `/tui` starts gateway if needed, then attaches
- attach failure returns to PT mode with clear error text
- `/tui off` disables auto-launch only
Rollout sequence:
1. internal opt-in
2. external opt-in beta
3. default-on after checks pass
4. PT path removal later
## Acceptance Checks
- runtime
- no PT import in controller or gateway path
- deterministic interrupt/cancel
- state
- same config, profile, and session continuity
- commands
- slash command registry remains centralized in `hermes_cli/commands.py`
- permissions
- approval, sudo, clarify round-trip through protocol
- streaming
- incremental assistant and tool updates
- opt-in
- flag, env, config, and slash command trigger the same launch path
## Test Commands
- `python -m pytest tests/tui_gateway/test_controller.py -q`
- `python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q`
- `python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q`
- `python -m pytest tests/hermes_cli/test_tui_opt_in.py -q`
- `cd ui-tui && npm run build`
- `cd ui-tui && npm run test`
## Non-Goals
- no ACP extraction work as prerequisite
- no new repository split
- no direct UI to `AIAgent` coupling
- no PT feature parity before gateway path is stable
# Prompt Toolkit to Ink Migration Plan
Date: 2026-04-01
## Scope
This is a refactor in the same repo.
- no new repo
- no runtime rewrite
- no messaging gateway reuse for terminal UI
## Current Environment (As-Is)
Interactive Hermes today is `prompt_toolkit` plus `rich` inside `cli.py`.
Facts from code:
- PT imports and app shell in `cli.py`
- `Application`, `KeyBindings`, `TextArea`, `patch_stdout`
- PT queue control path in `cli.py`
- `_pending_input` for normal input
- `_interrupt_queue` for input while agent is running
- tool approval and sudo prompts use CLI callbacks in `tools/terminal_tool.py`
- `_sudo_password_callback`
- `_approval_callback`
- core agent runtime is Python in `run_agent.py`
- `AIAgent.run_conversation()`
- `AIAgent.chat()`
Current coupling problem:
- UI framework and runtime control flow are mixed in `cli.py`.
- Tool prompts depend on CLI callback globals.
- This blocks clean UI replacement.
## Ideal Environment (To-Be)
Interactive Hermes is Ink UI plus a local TUI gateway.
### Runtime
- `AIAgent` stays in Python.
- Tool execution stays in Python.
- Session storage and config remain unchanged.
### Boundary
- new `tui_gateway` process over stdio JSON-RPC
- UI talks only to gateway
- gateway talks to `AIAgent`
### UI
- Node/TypeScript Ink app
- transcript, composer, status, approvals, clarify, sudo, interrupt
### Compatibility
Use existing Hermes state and config:
- `~/.hermes/.env`
- `~/.hermes/config.yaml`
- `~/.hermes/state.db`
- profile behavior via `HERMES_HOME`
## How To Get There
Use three implementation cuts plus one switch cut.
## Cut 1: Headless Runtime Controller
Goal: separate runtime control flow from PT.
Add:
- `tui_gateway/controller.py`
- `tui_gateway/session_state.py`
- `tui_gateway/events.py`
Change:
- `run_agent.py` only for callback wiring needed by controller
- `cli.py` to call controller APIs in compatibility mode
Done when:
- controller can create/resume/prompt/interrupt/cancel without importing PT
- tool progress and assistant deltas are emitted as typed events
## Cut 2: Local TUI Gateway
Goal: protocol boundary between UI and runtime.
Add:
- `tui_gateway/protocol.py`
- `tui_gateway/server.py`
- `tui_gateway/entry.py`
- `tui_gateway/__init__.py`
Protocol methods:
- `session.create`
- `session.resume`
- `session.list`
- `session.interrupt`
- `session.cancel`
- `prompt.submit`
- `approval.respond`
- `sudo.respond`
- `clarify.respond`
Protocol events:
- `message.delta`
- `tool.progress`
- `approval.requested`
- `sudo.requested`
- `clarify.requested`
- `error`
Done when:
- a simple client can run one full prompt cycle over stdio JSON-RPC
## Cut 3: Ink UI
Goal: usable clone experience through gateway.
Add:
- `ui-tui/package.json`
- `ui-tui/src/main.tsx`
- `ui-tui/src/gatewayClient.ts`
- `ui-tui/src/state/store.ts`
- `ui-tui/src/components/Transcript.tsx`
- `ui-tui/src/components/Composer.tsx`
- `ui-tui/src/components/StatusBar.tsx`
- `ui-tui/src/components/ApprovalModal.tsx`
- `ui-tui/src/components/SudoPrompt.tsx`
- `ui-tui/src/components/ClarifyPrompt.tsx`
Change:
- `tools/terminal_tool.py` adapters so prompts round-trip through gateway path, not PT-only callbacks
Done when:
- user can chat, run tools, approve, deny, clarify, interrupt, and continue
## Cut 4: Opt-In Switch and Rollout
Goal: ship safely without forced cutover.
Entry points:
- `hermes --tui`
- `HERMES_EXPERIMENTAL_TUI=1`
- `display.experimental_tui: true`
- `/tui`, `/tui on`, `/tui off`, `/tui status` in legacy CLI
Behavior:
- `/tui` starts gateway if needed, then attaches
- attach failure returns to PT mode with clear error text
- `/tui off` disables auto-launch only
Rollout:
1. internal opt-in
2. external opt-in beta
3. default-on only after acceptance checks pass
4. PT path removal later
## Acceptance Checks
- runtime
- no PT import in controller or gateway path
- deterministic interrupt/cancel
- state
- same config, profile, and session continuity
- commands
- slash command registry stays centralized in `hermes_cli/commands.py`
- permissions
- approval, sudo, clarify all round-trip through protocol
- streaming
- incremental assistant and tool updates
- opt-in
- flag, env, config, and slash command all trigger same launch path
## Test Commands
- `python -m pytest tests/tui_gateway/test_controller.py -q`
- `python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q`
- `python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q`
- `python -m pytest tests/hermes_cli/test_tui_opt_in.py -q`
- `cd ui-tui && npm run build`
- `cd ui-tui && npm run test`
## Non-Goals
- no ACP extraction work as prerequisite
- no new repository split
- no direct UI to `AIAgent` coupling
- no PT feature parity before gateway path is stable
# Ink Gateway TUI Migration Plan
Date: 2026-04-01
## Goal
Replace Hermes' interactive `prompt_toolkit` CLI with a React terminal UI built on `Ink`, while keeping the Python agent and tool runtime in place.
The new design should:
- remove `prompt_toolkit` from the interactive path entirely
- keep `AIAgent`, tool execution, and session logic in Python
- introduce a transport-neutral local UI gateway between backend and frontend
- use stock `Ink` first, not a Claude Code renderer transplant
- keep using the same Hermes config, profile, skills, memory, and session storage model
## Decision Summary
Hermes should not evolve the current `prompt_toolkit` shell. The replacement architecture is:
1. Python backend session server
2. local gateway transport over stdio JSON-RPC
3. Node/TypeScript `Ink` TUI frontend
This intentionally uses a dedicated local TUI gateway and keeps `acp_adapter` unchanged.
The new TUI is a new shell, not a new runtime.
## Compatibility Requirements
From the existing Hermes docs and setup flows, the new TUI should continue to use:
- the same `~/.hermes/.env` provider/auth configuration
- the same `~/.hermes/config.yaml` settings model
- the same `~/.hermes/state.db` session store
- the same `HERMES_HOME` profile layout and isolation rules
- the same skills, memories, and slash-command registry already shared across Hermes surfaces
The migration should not create:
- a separate TUI-only config file
- a separate TUI-only session database
- a separate prompt assembly path with drift from existing Hermes runtime behavior
## Why This Shape
The current interactive CLI is too coupled to `prompt_toolkit` to incrementally clean up in place:
- `cli.py` mixes input handling, rendering, approvals, clarify flows, voice, queues, and agent orchestration
- `tools/terminal_tool.py` assumes UI callbacks installed by the CLI
- the current event model is built around PT queues and threads, not a transport-neutral session API
At the same time, a full port to Claude Code's custom renderer is the wrong first move:
- Claude Code's TUI stack is not just `Ink`; it includes a product-coupled renderer fork and app bootstrap assumptions
- Hermes does not need that complexity to reach a good first-party TUI
- stock `Ink` is enough to validate the UI model and close the biggest UX gap first
Operationally, a Node/TypeScript frontend is acceptable here because Hermes already ships with a Node-aware install story and already supports Node-based surfaces in the wider product.
## Non-Goals
- reusing the messaging gateway as the TUI transport
- preserving `prompt_toolkit` compatibility
- matching Claude Code internals one-for-one
- rewriting Hermes' core agent or tool runtime in Node
## High-Level Architecture
The new interactive stack has three layers:
1. `python runtime`
Owns `AIAgent`, tool execution, session state, approvals, interrupts, and filesystem/terminal tools.
2. `ui gateway`
A local protocol server that exposes Hermes sessions as typed requests, responses, and events.
3. `ink tui`
A React terminal app that renders transcript, composer, status, approvals, tool cards, and slash-command UX.
Suggested process model:
```text
hermes
1. launch ink tui (node)
2. spawn python ui gateway over stdio
3. create/resume session
4. exchange JSON-RPC requests + streaming events
```
## Why Not The Existing Messaging Gateway
The messaging gateway solves a different problem:
- multi-platform message routing
- user authorization and pairing
- per-platform delivery behavior
- long-running bot process management
That stack is useful as architecture background, but it is the wrong seam for a local terminal app.
The ACP adapter demonstrates the right boundary shape:
- backend runtime behind a protocol boundary
- callback/event bridging
- permission round-trips
- explicit session lifecycle
The new local UI gateway should target a Hermes TUI protocol directly, not an editor protocol.
## ACP Isolation Strategy
Do not use ACP extraction as a prerequisite.
Instead:
1. build `tui_gateway` directly around `AIAgent`
2. keep `acp_adapter/*` untouched during early migration
3. allow shared-runtime refactors later only if they reduce real maintenance cost
Reasons:
- ACP payloads are editor-shaped and add translation overhead
- ACP-first migration adds scope and time before the new TUI ships
- owner direction favors a fast clone path with gateway indirection, not transport unification work
## Proposed Backend Split
Extract the following concerns out of `cli.py`:
1. `session controller`
A headless controller for create, resume, prompt, interrupt, cancel, and slash-command dispatch.
2. `event bridge`
Converts agent callbacks and tool progress into structured UI events.
3. `permission bridge`
Converts dangerous-command approval, sudo prompts, and clarify requests into request/response interactions.
4. `presentation adapters`
Optional formatting helpers for transcript items and tool previews, without owning terminal rendering.
5. `gateway adapter`
A thin request/event layer for `tui_gateway` over stdio JSON-RPC.
The backend must stop depending on a terminal UI framework for control flow.
## Shared Runtime Invariants
The backend remains the source of truth for:
- prompt assembly
- Honcho/memory synchronization
- tool dispatch
- approval policy
- slash-command execution
- session transitions
The frontend should render protocol state, not own core agent behavior.
In particular, the new TUI must not introduce UI-side blocking work into the turn path. Context, memory, Honcho prefetch, and similar backend concerns should stay behind the runtime boundary and preserve Hermes' existing caching and async-prefetch behavior.
## Proposed Transport
Start with stdio JSON-RPC.
Reasons:
- local CLI startup is simple
- process ownership is clear
- no port management
WebSocket can be added later if Hermes wants:
- remote terminal clients
- browser UI
- multiple concurrent viewers
But it should not be the first transport.
## Platform And Toolset Strategy
The new UI should run Hermes in a dedicated `tui` platform mode.
That mode should:
- share most behavioral semantics with the current interactive CLI
- reuse the canonical slash-command registry rather than fork it
- preserve session continuity with other Hermes surfaces where the shared state model already supports it
- avoid editor-specific payload conventions in the TUI protocol
Toolset strategy:
- start from current interactive CLI capabilities
- only introduce a dedicated `hermes-tui` toolset if the transport boundary proves it is needed
- keep transport constraints out of tool business logic as much as possible
## Protocol Shape
The TUI protocol should be explicit and event-driven.
Core requests:
- `session.create`
- `session.resume`
- `session.list`
- `session.interrupt`
- `session.cancel`
- `session.set_cwd`
- `prompt.submit`
- `command.run`
- `approval.respond`
- `sudo.respond`
- `clarify.respond`
Core events:
- `session.state`
- `message.start`
- `message.delta`
- `message.complete`
- `thinking.delta`
- `tool.started`
- `tool.progress`
- `tool.completed`
- `approval.requested`
- `sudo.requested`
- `clarify.requested`
- `error`
Design rule: every user-visible interactive state in the new TUI must come from protocol state, not local UI guesswork.
## Ink Frontend Scope
The first `Ink` frontend only needs a narrow set of surfaces:
- transcript view
- input composer
- status/footer bar
- slash-command picker/help
- approval modal
- sudo prompt
- clarify prompt
- tool activity cards
Do not start with:
- mouse-heavy interaction
- custom selection model
- custom renderer internals
- Claude-style terminal instrumentation
Those can come later if real gaps appear.
## Migration Phases
## Phase 1: Headless Runtime Extraction
Goal: make Hermes usable without `prompt_toolkit`.
Work:
- introduce a backend session/controller module
- move PT-specific queues and rendering concerns out of agent flow
- replace direct CLI callback assumptions with abstract request/response hooks
- isolate slash-command execution from the PT shell
- introduce a `platform="tui"` runtime path without forking core agent logic
Exit criteria:
- a non-PT backend can run a prompt, stream progress, request approval, and return a final response
## Phase 2: Local UI Gateway
Goal: expose the backend over stdio JSON-RPC.
Work:
- create a `ui_gateway` package or equivalent module group
- model session lifecycle and event streaming
- implement cancel/interrupt behavior
- adapt terminal approval and sudo flow into transport messages
- keep config, profile, and session storage identical to existing Hermes surfaces
Exit criteria:
- a minimal client can drive a full Hermes session over stdio without importing `cli.py`
## Phase 3: Ink MVP
Goal: ship a working Hermes TUI without `prompt_toolkit`.
Work:
- create a Node/TS package for the TUI
- connect to the Python gateway
- render transcript + composer + status
- support approvals, clarify prompts, and slash commands
- preserve interrupt-and-redirect behavior for active runs
Exit criteria:
- Hermes can be used end-to-end from the new TUI for normal chat and tool use
## Phase 4: Feature Parity
Goal: close the biggest regressions from the legacy CLI.
Work:
- port session picker/resume UX
- port tool previews and long-running command status
- port config-aware commands
- port voice or explicitly defer it behind a non-blocking boundary
Exit criteria:
- daily-driver workflows no longer require the PT CLI
## Phase 5: Cutover And Deletion
Goal: make the new TUI the default interactive path.
Work:
- switch `hermes` interactive startup to the Ink client
- keep legacy PT path only behind a temporary fallback flag if needed
- delete PT-specific code after a short stabilization window
Exit criteria:
- `prompt_toolkit` is no longer part of the main interactive CLI
## File-Level Refactor Targets
Initial hot spots:
- `cli.py`
- `tools/terminal_tool.py`
- `model_tools.py`
- `run_agent.py`
- `hermes_cli/commands.py`
Expected pattern:
- avoid importing PT code into the new backend path
- move any UI-specific formatting behind protocol events or thin adapters
## First Implementation Slices (PR Plan)
Keep early PRs narrow and mergeable. Do not start with a large branch that rewrites `cli.py` end-to-end.
1. `PR-1: headless session controller`
- add a transport-neutral controller around `AIAgent` for create/resume/prompt/interrupt/cancel
- no UI, no PT dependencies
2. `PR-2: local ui gateway (stdio json-rpc)`
- add `ui_gateway` process entry
- implement protocol requests/events for one full prompt cycle
3. `PR-3: ink shell bootstrap`
- add Node/TS package with gateway client
- render transcript + composer + status + streaming deltas
4. `PR-4: interactive controls parity`
- approvals, sudo, clarify flows
- interrupt-and-redirect and command routing
5. `PR-5: startup switch + fallback flag`
- add explicit opt-in startup flag for Ink path (`HERMES_EXPERIMENTAL_TUI=1` or equivalent)
- add CLI/config opt-in controls and `/tui` command entrypoint in legacy CLI
- keep PT path behind a temporary env/flag gate during stabilization
6. `PR-6: parity hardening and PT deletion`
- close remaining UX gaps from legacy CLI
- remove PT path after stability window
## Concrete File Plan
Use fixed locations so contributors do not invent parallel structures.
`PR-1` files:
- add `tui_gateway/controller.py`
- add `tui_gateway/session_state.py`
- add `tui_gateway/events.py`
- update `run_agent.py` only where callback wiring is needed
- update `cli.py` only to call controller entry points in compatibility mode
`PR-2` files:
- add `tui_gateway/protocol.py`
- add `tui_gateway/server.py`
- add `tui_gateway/entry.py`
- add `tui_gateway/__init__.py`
- add `tests/tui_gateway/test_protocol.py`
- add `tests/tui_gateway/test_server_flow.py`
`PR-3` files:
- add `ui-tui/package.json`
- add `ui-tui/src/main.tsx`
- add `ui-tui/src/gatewayClient.ts`
- add `ui-tui/src/state/store.ts`
- add `ui-tui/src/components/Transcript.tsx`
- add `ui-tui/src/components/Composer.tsx`
- add `ui-tui/src/components/StatusBar.tsx`
`PR-4` files:
- add `ui-tui/src/components/ApprovalModal.tsx`
- add `ui-tui/src/components/SudoPrompt.tsx`
- add `ui-tui/src/components/ClarifyPrompt.tsx`
- update `tools/terminal_tool.py` to use gateway request/response adapters instead of PT-specific assumptions
- add `tests/tui_gateway/test_permissions_roundtrip.py`
`PR-5` files:
- update `hermes_cli/main.py` startup selection for `--tui` and env/config flags
- update `hermes_cli/commands.py` with `/tui` commands
- update `cli.py` command dispatch to launch/attach behavior
- add `tests/hermes_cli/test_tui_opt_in.py`
`PR-6` files:
- remove PT-only paths from `cli.py` once parity checks pass
- remove obsolete PT wiring helpers
- update docs and command help text
If path names change, keep one module per role and avoid duplicate gateway implementations.
## Protocol Envelope (v0)
Use one JSON-RPC envelope shape for all gateway traffic.
Request:
```json
{
"jsonrpc": "2.0",
"id": "req-123",
"method": "prompt.submit",
"params": {
"session_id": "sess-1",
"text": "hello"
}
}
```
Event notification:
```json
{
"jsonrpc": "2.0",
"method": "event",
"params": {
"type": "message.delta",
"session_id": "sess-1",
"payload": {
"text": "hi"
}
}
}
```
Error:
```json
{
"jsonrpc": "2.0",
"id": "req-123",
"error": {
"code": 4001,
"message": "session not found"
}
}
```
Protocol rules:
- all event ordering is per-session FIFO
- ids are opaque strings
- unknown event types are ignored by clients and logged
- protocol version is pinned in `tui_gateway/protocol.py`
## Acceptance Checks Per Phase
Each phase should ship with explicit checks:
- `runtime`
- prompt executes end-to-end without importing `prompt_toolkit`
- interrupt and cancel are deterministic
- `state continuity`
- same `HERMES_HOME`, `config.yaml`, `state.db`, and profile behavior as existing Hermes surfaces
- `commands`
- slash-command resolution uses shared registry (`hermes_cli/commands.py`)
- `permissions`
- dangerous command approval, sudo prompt, and clarify prompt all round-trip through protocol events
- `streaming`
- message/tool progress events stream incrementally; no UI-side polling loop for core turn output
- `opt-in controls`
- `--tui`, env flag, config toggle, and `/tui` commands all resolve to the same launch behavior
- failures fall back to PT mode with explicit error output
## Test Commands Per PR
`PR-1`:
- `python -m pytest tests/tui_gateway/test_controller.py -q`
`PR-2`:
- `python -m pytest tests/tui_gateway/test_protocol.py tests/tui_gateway/test_server_flow.py -q`
`PR-3`:
- `cd ui-tui && npm run build`
- `cd ui-tui && npm run test`
`PR-4`:
- `python -m pytest tests/tui_gateway/test_permissions_roundtrip.py -q`
- `cd ui-tui && npm run test`
`PR-5`:
- `python -m pytest tests/hermes_cli/test_tui_opt_in.py -q`
`PR-6`:
- `python -m pytest tests/ -q`
## Opt-In UX Surface
Expose TUI opt-in through user-facing TUI language, not transport language.
Entry points:
- startup flag: `hermes --tui`
- env flag: `HERMES_EXPERIMENTAL_TUI=1`
- config toggle: `display.experimental_tui: true`
- slash command in legacy CLI:
- `/tui` (launch/attach)
- `/tui on` (persist opt-in)
- `/tui off` (disable auto-launch)
- `/tui status` (show mode + process/attach state)
Behavior:
- if `/tui` is called and the local TUI gateway is not running, start it and attach
- if already running, attach/reuse session
- on startup/attach failure, print clear error and stay in PT mode
- `/tui off` disables future auto-launch; it does not terminate active sessions unless requested
## Rollout And Rollback
Rollout should be staged:
1. internal opt-in (`HERMES_EXPERIMENTAL_TUI=1` or equivalent)
2. external opt-in beta (still flag-gated, PT remains default)
3. default-on with PT fallback still available, only after acceptance checks are green
4. PT removal after a short stability window
Rollback path must remain simple until PT deletion:
- one switch to restore legacy interactive startup
- no data migration required between TUI and PT modes (shared state model)
## Main Risks
1. `cli.py` currently owns more state than it appears to. Extraction will uncover hidden coupling.
2. Approval and sudo flows are global/callback-driven today and need per-session protocol state.
3. Long-running tool output may currently assume terminal-local behavior that has to be normalized before transport.
4. Voice mode may carry PT assumptions and should be treated as optional during the first cut.
5. If the frontend demands behavior beyond stock `Ink`, the team may need to introduce custom terminal primitives later.
## Recommendation
Start with stock `Ink` and a direct `tui_gateway` over stdio JSON-RPC.
Do not:
- refactor `prompt_toolkit` forward
- route the terminal UI through the messaging gateway
- begin by vendoring Claude Code's renderer
The shortest path to a good Hermes TUI is:
1. extract headless backend control flow
2. expose it over stdio JSON-RPC
3. build the TUI in `Ink`
4. only customize deeper terminal behavior after real product pressure appears
## Success Criteria
This migration succeeds if Hermes can:
- start an interactive session without `prompt_toolkit`
- stream assistant and tool activity live into an `Ink` UI
- handle approvals, clarify requests, sudo prompts, and interrupts cleanly
- preserve the existing Python agent/tool runtime
- preserve existing Hermes config, profile, and session continuity expectations
- preserve shared slash-command semantics instead of inventing a second command surface
- avoid adding new blocking UI-driven work into the prompt path
- make the legacy PT shell deletable rather than permanent