Ink Terminal UI & Dual-Process RPC Bridge
๐บ 1. Ink (React) Terminal Canvas Rendering
The ui-tui/ directory provides a richly styled ANSI terminal dashboard. It completely breaks away from traditional character streaming, implementing dynamic layout within the console:
- React-to-Terminal:
React Inkruns in a Node.js process, mapping React component state and lifecycle to ANSI escape control characters, achieving real-time partial refresh of the terminal UI while avoiding the severe flickering of traditional CLI output. - NanoStores State Bus: Instead of using React Context for state passing, the TUI uses lightweight NanoStores to manage active sessions, input prompt states, and collapsed task trace trees, achieving zero render latency.
๐ 2. Dual-Process WebSocket JSON-RPC Bridge & Data Frames
To ensure that inference and terminal UI updates don't block each other, the Node TUI frontend and Python core run as two separate processes, communicating via a local WebSocket JSON-RPC protocol bridge. This communication mechanism is centralized in the tui_gateway/ directory, with the interaction data flow shown in the sequence diagram below:
- tui_gateway/ws.py: Starts the WebSocket server.
- tui_gateway/transport.py: Downstream event broadcast center. Token fragments and thinking steps from the backend agent are formatted as JSON frames and pushed over WebSocket.
- tui_gateway/server.py: Upstream command response center. Defines interception handlers for the following core JSON-RPC methods:
๐ Core JSON-RPC Upstream Method Mapping
| RPC Method | Params | Python Backend Handler |
|---|---|---|
send_message | {"message": "user question", "session_id": "api-..."} | The corresponding method in server.py receives and dispatches the command to a background thread to start AIAgent.run_conversation(). |
interrupt_conversation | (empty) | Directly sets the current active AIAgent's _interrupt_requested property to True, forcing the LLM streaming loop to roll back and terminate. |
get_session_info | {"session_id": "api-..."} | Directly calls SessionDB to execute SQL queries and returns a formatted Turn list. |
process_slash_command | {"command": "/compress"} | Extracts the slash action and dispatches it asynchronously to the SlashWorker execution queue. |
โ๏ธ 3. Why SlashWorker Async Queue? (tui_gateway/slash_worker.py)
In the TUI interface, when a user issues slash commands such as /compress (force conversation summarization) or /branch (branch creation), these tasks involve high-overhead database locking and LLM long-text reasoning. If executed synchronously in the main RPC service thread, severe blocking would occur: WebSocket freeze and SQLite concurrent lock conflicts. To ensure high availability under this concurrency scenario, a single-threaded pipeline queue is maintained in the background:
# SlashWorker queued execution implementation
class SlashWorker(threading.Thread):
def __init__(self):
super().__init__()
self.queue = queue.Queue()
self.daemon = True
def run(self):
while True:
# Serial queue pulling, completely avoids lock conflicts between high-IO commands
task = self.queue.get()
try:
# Call core CLI logic to process the shortcut command and broadcast results to TUI
result = self.execute_cmd(task.command)
self.publish_result_to_tui(result)
except Exception as e:
self.publish_error(e)
finally:
self.queue.task_done()
๐ In-Depth Subtopic Analysis
For a deeper understanding of the TUI console, we recommend the following technical deep-dives: