feat(runtime): add interactive local PTY sessions
test / workspace (push) Successful in 17m52s

This commit is contained in:
Tom You
2026-07-09 00:52:14 -05:00
parent ce370c740a
commit f37ac6cbf3
5 changed files with 516 additions and 3 deletions
+110 -1
View File
@@ -5,9 +5,16 @@ use rabby_core::{
use rabby_runtime::config_store::{ConfigStore, JsonFileConfigStore};
use rabby_runtime::local_terminal::{LocalCommand, LocalCommandOutput, LocalTerminal};
use rabby_runtime::ssh_runtime::{SshClient, SshCommand, SshCommandOutput};
use rabby_runtime::terminal_session::{
TerminalSessionId, TerminalSessionRegistry, TerminalSessionSize,
};
use rabby_runtime::wallet_vault::{EncryptedWalletVault, WalletVaultStatus};
use serde::Serialize;
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::Duration;
struct TerminalState(Mutex<TerminalSessionRegistry>);
#[derive(Serialize)]
struct FeatureRow {
@@ -92,6 +99,90 @@ fn run_ssh_command(
.map_err(|err| format!("{err:?}"))
}
#[tauri::command]
fn terminal_session_start(
state: tauri::State<TerminalState>,
shell: Option<String>,
cols: Option<u16>,
rows: Option<u16>,
) -> Result<u64, String> {
let shell =
shell.unwrap_or_else(|| std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()));
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.spawn_local(shell, cols.unwrap_or(80), rows.unwrap_or(24))
.map(|id| id.0)
.map_err(|err| err.to_string())
}
#[tauri::command]
fn terminal_session_write(
state: tauri::State<TerminalState>,
id: u64,
input: String,
) -> Result<(), String> {
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.write(TerminalSessionId(id), input.as_bytes())
.map_err(|err| err.to_string())
}
#[tauri::command]
fn terminal_session_read_until(
state: tauri::State<TerminalState>,
id: u64,
needle: String,
timeout_ms: Option<u64>,
) -> Result<String, String> {
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.read_until(
TerminalSessionId(id),
&needle,
Duration::from_millis(timeout_ms.unwrap_or(1500)),
)
.map_err(|err| err.to_string())
}
#[tauri::command]
fn terminal_session_resize(
state: tauri::State<TerminalState>,
id: u64,
cols: u16,
rows: u16,
) -> Result<TerminalSessionSize, String> {
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.resize(TerminalSessionId(id), cols, rows)
.map_err(|err| err.to_string())?;
registry
.size(TerminalSessionId(id))
.map_err(|err| err.to_string())
}
#[tauri::command]
fn terminal_session_kill(state: tauri::State<TerminalState>, id: u64) -> Result<(), String> {
let mut registry = state
.0
.lock()
.map_err(|_| "terminal registry lock poisoned".to_string())?;
registry
.kill(TerminalSessionId(id))
.map_err(|err| err.to_string())
}
fn resolve_vault_path(path: Option<String>) -> PathBuf {
path.map(PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("rabby-demo-wallet-vault.json"))
@@ -124,6 +215,9 @@ fn resolve_config_path(path: Option<String>) -> PathBuf {
fn main() {
tauri::Builder::default()
.manage(TerminalState(
Mutex::new(TerminalSessionRegistry::default()),
))
.invoke_handler(tauri::generate_handler![
workspace_summary,
wallet_mvp_summary,
@@ -133,7 +227,12 @@ fn main() {
run_local_command,
run_ssh_command,
load_app_config,
save_app_config
save_app_config,
terminal_session_start,
terminal_session_write,
terminal_session_read_until,
terminal_session_resize,
terminal_session_kill
])
.run(tauri::generate_context!())
.expect("failed to run Rabby Tauri application");
@@ -181,6 +280,16 @@ mod tests {
);
}
#[test]
fn terminal_session_size_is_returnable_from_resize_command() {
let size = TerminalSessionSize {
cols: 100,
rows: 30,
};
assert_eq!(size.cols, 100);
assert_eq!(size.rows, 30);
}
#[test]
fn save_and_load_app_config_round_trips_custom_theme() {
let unique = std::time::SystemTime::now()