TUI & JSON-RPC

Ink Terminal UI & Dual-Process RPC Bridge

Comprehensive overview of ui-tui terminal canvas rendering and tui_gateway binary dual-channel async bridge logic
TUI & RPC Bridge
๐Ÿ“Š Figure 6-1: React Ink TUI and Python agent core dual-process communication architecture diagram

๐Ÿ“บ 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 Ink runs 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:

JSON-RPC Data Frame Sequence
๐Ÿ“Š Figure 6-2: Dual-process WebSocket JSON-RPC uplink/downlink data frame sequence diagram
  • 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 MethodParamsPython 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: