Skip to main content

Module handlers

Module handlers 

Source
Expand description

P5-4’s three load-bearing interactive handlers — the THIS-MODULE side of the three deferred chains P5-1/P5-2/P5-3 each explicitly left for tui:

  1. TuiApprovalHandler implements crate::permissions::PermissionsApprovalHandler (P5-1’s seam): installed via crate::agent::Agent::set_permissions_approval_handler.
  2. TuiElicitationHandler implements crate::mcp::McpElicitationHandler (P5-2’s seam): installed via crate::mcp::McpClient::set_elicitation_handler.
  3. TuiChildApprovalHandler — also a PermissionsApprovalHandler, installed via the factory crate::agent::Agent::set_child_approval_handler_factory (P5-4’s OWN new seam, added specifically to close P5-3’s §2.2 C6 “queued, never-blocking” chain into a genuinely answerable one).

All three follow the same shape: push a Pending* request (from crate::tui::bridge) onto a channel the render loop polls, then block (synchronously for the two PermissionsApprovalHandlers — ask is a sync trait method by design, see that trait’s doc comment; via .await for the async McpElicitationHandler) for the reply. If the reply channel is ever dropped without a reply (the render loop panicked, or the TUI process is shutting down mid-request), every handler here resolves to the FAIL-CLOSED outcome (Deny/decline) — never a hang, and never an implicit allow.

Security invariant (repeated at each handler below). None of these handlers can escalate what the permissions/elicitation engines already decided: crate::permissions::approval::resolve_ask only ever calls a PermissionsApprovalHandler::ask when the rule engine already resolved the call to Ask (Deny short-circuits before any handler runs; Allow never needs one) — a TUI “allow” here can only grant what the policy already routed to a human prompt. Likewise an elicitation answer is exactly what the user typed into the modal — never fabricated, never auto-accepted.

Structs§

TuiApprovalHandler
P5-1’s interactive ask-UI, closing the deferred chain crate::permissions::approval’s module doc comment names. ask pushes the request onto tx and blocks on a fresh one-shot reply channel; Self::tx being closed (the render loop is gone) makes the blocking recv() return an Err, which resolves to ApprovalOutcome::Deny — fail-closed, matching the trait’s own “no handler ⇒ deny” default posture for the “handler installed but unreachable” case too.
TuiBridge
The aggregate wiring point a crates/cli embedder uses: constructs every channel pair once, installs the SENDING halves onto the Agent/ McpClients that need them, and hands the RECEIVING halves to the render loop to poll each frame. See crate::tui::should_activate’s doc comment for why this is only ever built when the TUI is confirmed active — installing these on an agent that no render loop is draining would hang the first Ask-tier prompt or elicitation forever.
TuiChildApprovalHandler
P5-3’s answerable child-approval handler, closing the §2.2 C6 deferred chain — see crate::agent::Agent::set_child_approval_handler_factory’s doc comment for how this REPLACES (only when installed) the default never-blocking crate::subagents::ParentQueueApprovalHandler. Also records every request into the shared queue (the SAME Agent::pending_child_approvals audit trail ParentQueueApprovalHandler itself writes to), so crate::agent::Agent::pending_child_approvals stays a complete audit log regardless of which handler answered a given request.
TuiElicitationHandler
P5-2’s interactive elicitation UI, closing the deferred chain crate::mcp::HeadlessElicitationHandler’s doc comment names. handle is ASYNC (the MCP trait’s own shape), so this uses a tokio::sync:: oneshot reply rather than blocking a thread — awaiting it yields the executor to other work while the modal is up. A dropped reply sender (render loop gone) resolves to crate::mcp::ElicitationAction::Cancel (the MCP spec’s own “dismissed without a decision” outcome — the honest shape for “nobody answered”, distinct from an explicit Decline).