feat: add backend runtime console target

This commit is contained in:
2026-06-26 17:42:21 +09:00
parent 65efde7651
commit 63ec9f9572
8 changed files with 944 additions and 24 deletions
+75 -14
View File
@@ -16,16 +16,16 @@ use crossterm::event::{
};
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::{Command, execute};
use protocol::{Event, Method, WorkerStatus};
#[cfg(feature = "e2e-test")]
use protocol::{Event, Greeting, RewindSummary, RewindTarget, RewindTargetId, Segment};
use protocol::{Method, WorkerStatus};
use protocol::{Greeting, RewindSummary, RewindTarget, RewindTargetId, Segment};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use session_store::SegmentId;
use tokio::sync::mpsc;
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
use client::{WorkerClient, WorkerRuntimeCommand};
use client::{BackendRuntimeClient, BackendRuntimeTarget, WorkerClient, WorkerRuntimeCommand};
use crate::app::{ActionbarNoticeLevel, ActionbarNoticeSource, App};
use crate::composer_keys::{ComposerEditAction, composer_edit_action};
@@ -171,6 +171,54 @@ pub(crate) async fn run_worker_name(
result
}
enum ConsoleConnection {
LegacySocket(WorkerClient),
BackendRuntime(BackendRuntimeClient),
}
impl ConsoleConnection {
fn try_next_event(&mut self) -> Option<Event> {
match self {
Self::LegacySocket(client) => client.try_next_event(),
Self::BackendRuntime(client) => client.try_next_event(),
}
}
async fn next_event(&mut self) -> Option<Event> {
match self {
Self::LegacySocket(client) => client.next_event().await,
Self::BackendRuntime(client) => client.next_event().await,
}
}
async fn send(&mut self, method: &Method) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::LegacySocket(client) => Ok(client.send(method).await?),
Self::BackendRuntime(client) => Ok(client.send(method).await?),
}
}
}
pub(crate) async fn run_backend_runtime(
target: BackendRuntimeTarget,
) -> Result<(), Box<dyn std::error::Error>> {
let worker_label = target.display_label();
let client = BackendRuntimeClient::connect(target).await?;
let mut terminal = enter_fullscreen()?;
let workspace_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let mut app = App::new_with_persistent_input_history(worker_label, &workspace_root);
app.connected = true;
let result = run_loop(
&mut terminal,
&mut app,
ConsoleConnection::BackendRuntime(client),
None,
)
.await;
let _ = leave_fullscreen(&mut terminal);
result
}
async fn run_connected_pod(
terminal: &mut ConsoleTerminal,
worker_name: String,
@@ -180,7 +228,13 @@ async fn run_connected_pod(
let workspace_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let mut app = App::new_with_persistent_input_history(worker_name, &workspace_root);
app.connected = true;
run_loop(terminal, &mut app, client, runtime_command).await
run_loop(
terminal,
&mut app,
ConsoleConnection::LegacySocket(client),
Some(runtime_command),
)
.await
}
pub(crate) async fn open_from_dashboard(
@@ -396,7 +450,13 @@ async fn run(
app.connected = true;
// The Worker sends `Event::Snapshot` automatically on connect;
// no explicit method call is required to fetch history.
run_loop(terminal, &mut app, client, runtime_command).await?;
run_loop(
terminal,
&mut app,
ConsoleConnection::LegacySocket(client),
Some(runtime_command),
)
.await?;
}
Err(e) => {
app.push_error(format!(
@@ -673,9 +733,9 @@ where
async fn drain_terminal_events(
app: &mut App,
client: &mut WorkerClient,
client: &mut ConsoleConnection,
term_rx: &mut mpsc::UnboundedReceiver<TerminalEventResult>,
runtime_command: &WorkerRuntimeCommand,
runtime_command: Option<&WorkerRuntimeCommand>,
) -> Result<bool, Box<dyn std::error::Error>> {
let mut handled = false;
for _ in 0..TERMINAL_EVENT_DRAIN_LIMIT {
@@ -701,7 +761,7 @@ async fn drain_terminal_events(
async fn drain_worker_events(
app: &mut App,
client: &mut WorkerClient,
client: &mut ConsoleConnection,
) -> Result<bool, Box<dyn std::error::Error>> {
let mut handled = false;
for _ in 0..POD_EVENT_DRAIN_LIMIT {
@@ -721,8 +781,8 @@ async fn drain_worker_events(
async fn run_loop(
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
app: &mut App,
mut client: WorkerClient,
runtime_command: WorkerRuntimeCommand,
mut client: ConsoleConnection,
runtime_command: Option<WorkerRuntimeCommand>,
) -> Result<(), Box<dyn std::error::Error>> {
let (_terminal_reader, mut term_rx) = TerminalEventReader::spawn()?;
@@ -734,7 +794,7 @@ async fn run_loop(
}
let handled_term_event =
drain_terminal_events(app, &mut client, &mut term_rx, &runtime_command).await?;
drain_terminal_events(app, &mut client, &mut term_rx, runtime_command.as_ref()).await?;
if app.quit {
break;
}
@@ -746,7 +806,8 @@ async fn run_loop(
match next_loop_input(&mut term_rx, app.connected, client.next_event()).await {
LoopInput::Terminal(term_event) => {
handle_terminal_event(app, &mut client, term_event?, &runtime_command).await?;
handle_terminal_event(app, &mut client, term_event?, runtime_command.as_ref())
.await?;
}
LoopInput::Worker(event) => match event {
Some(ev) => {
@@ -770,9 +831,9 @@ async fn run_loop(
async fn handle_terminal_event(
app: &mut App,
client: &mut WorkerClient,
client: &mut ConsoleConnection,
event: TermEvent,
_runtime_command: &WorkerRuntimeCommand,
_runtime_command: Option<&WorkerRuntimeCommand>,
) -> Result<(), Box<dyn std::error::Error>> {
match event {
TermEvent::Key(key) => {
+5 -1
View File
@@ -33,7 +33,7 @@ use crossterm::execute;
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode, enable_raw_mode};
use session_store::SegmentId;
use client::WorkerRuntimeCommand;
use client::{BackendRuntimeTarget, WorkerRuntimeCommand};
#[derive(Debug, Clone)]
pub struct LaunchOptions {
@@ -55,6 +55,9 @@ pub enum LaunchMode {
worker_name: String,
socket_override: Option<PathBuf>,
},
/// `yoi --backend <url> --runtime-id <id> --worker-id <id>`: connect through the
/// Workspace Backend Runtime API and observe the Backend-proxied event stream.
BackendRuntime { target: BackendRuntimeTarget },
/// `yoi resume`: open the Worker picker, then attach to the selected live Worker
/// or restore the selected stopped Worker by name. Without `--all`, the picker
/// is scoped to the current runtime workspace.
@@ -103,6 +106,7 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
worker_name,
socket_override,
} => console::run_worker_name(worker_name, socket_override, runtime_command).await,
LaunchMode::BackendRuntime { target } => console::run_backend_runtime(target).await,
LaunchMode::Resume { all } => {
console::run_resume(runtime_command, workspace_root.clone(), all).await
}