Expand description
Generic adoption helpers for the client command channel.
A client protocol opts into injected commands (the dashboard’s [send] button, and any future programmatic control) with a ~10-line diff to its connection loop:
let mut command_rx = command_support::register_command_channel(&app_state, client_id).await;
// ... inside the read loop:
tokio::select! {
read = read_half.read(&mut buffer) => { /* existing arms unchanged */ }
Some(cmd) = command_rx.recv() => {
if command_support::handle_stream_client_command(
&MyClientProtocol, &write_half_arc, cmd, client_id, &app_state, &status_tx,
).await { break; }
}
}handle_stream_client_command is fully generic for split-stream clients:
it executes the action through the protocol’s own execute_action (the
same machinery LLM-produced actions use), writes SendData results to the
write half, and replies with a ClientSendOutcome. A protocol whose
vocabulary produces Custom results (IRC’s send_privmsg, for instance)
writes its own arm body and uses reply directly.
Concurrency note: the command arm runs in the same single task as the read loop, so a command never races the loop’s own writes. A command arriving while the loop is mid-LLM-call simply waits its turn in the bounded channel; user injection is deliberately independent of the protocol’s Idle/Processing state machine.
Cancellation-safety note for adopters: AsyncReadExt::read is
cancellation-safe, so wrapping it in tokio::select! with the command arm
does not lose data.
Functions§
- handle_
stream_ client_ command - Generic command-arm body for split-stream clients. Executes the action, maps the result to wire writes, records a client access-log entry, and replies on the command’s oneshot.
- register_
command_ channel - Create and register the command channel for a running client. Call once the connection is established; move the returned receiver into the loop task.
- reply
- Reply on a command’s oneshot, tolerating a caller that stopped listening.