feat: add interactive VT and workspace command plane
CI / rust (push) Successful in 3m48s

This commit is contained in:
2026-08-31 17:05:49 -07:00
parent 32d190c6c6
commit b838d000db
54 changed files with 3004 additions and 113 deletions
+3
View File
@@ -11,5 +11,8 @@ repository.workspace = true
lumbridge-pty = { path = "../lumbridge-pty" }
thiserror = "2.0"
[dev-dependencies]
lumbridge-terminal = { path = "../lumbridge-terminal" }
[lints]
workspace = true
@@ -0,0 +1,78 @@
use lumbridge_runtime::{
CommandConfig, PtyOptions, RuntimeActor, RuntimeActorOptions, RuntimeCommand, RuntimeEvent,
TerminalSize,
};
use lumbridge_terminal::{
KeyModifiers, TerminalDimensions, TerminalEngine, TerminalEngineOptions, TerminalKey,
TerminalKeyEvent,
};
use std::time::{Duration, Instant};
const TEST_TIMEOUT: Duration = Duration::from_secs(3);
#[test]
fn actor_terminal_pipeline_accepts_input_resize_and_vt_output() {
let command = CommandConfig::new("/bin/sh")
.unwrap()
.args([
"-c",
"printf '\x1b[31mready\x1b[0m\\n'; IFS= read -r line; stty size; printf 'got:%s\\n' \"$line\"",
]);
let actor = RuntimeActor::spawn(
command,
PtyOptions::default(),
RuntimeActorOptions::default(),
)
.unwrap();
let dimensions = TerminalDimensions::new(41, 101).unwrap();
let mut terminal = TerminalEngine::new(TerminalEngineOptions {
dimensions: TerminalDimensions::default(),
scrollback_lines: 64,
});
assert!(terminal.resize(dimensions));
actor
.try_send(RuntimeCommand::Resize(TerminalSize::new(41, 101).unwrap()))
.unwrap();
let text = terminal.encode_key(&TerminalKeyEvent {
key: TerminalKey::Text("hello-pipeline".into()),
modifiers: KeyModifiers::default(),
});
actor.try_send(RuntimeCommand::Input(text)).unwrap();
let enter = terminal.encode_key(&TerminalKeyEvent {
key: TerminalKey::Enter,
modifiers: KeyModifiers::default(),
});
actor.try_send(RuntimeCommand::Input(enter)).unwrap();
let deadline = Instant::now() + TEST_TIMEOUT;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
assert!(!remaining.is_zero(), "timed out waiting for pipeline exit");
match actor.recv_timeout(remaining).unwrap() {
RuntimeEvent::Output { bytes, .. } => {
let update = terminal.process(&bytes);
for response in update.outbound {
actor.try_send(RuntimeCommand::Input(response)).unwrap();
}
}
RuntimeEvent::Exited { status, .. } => {
assert!(status.success());
break;
}
RuntimeEvent::Fault { message, .. } => panic!("runtime fault: {message}"),
RuntimeEvent::Started { .. } | RuntimeEvent::InputClosed { .. } => {}
}
}
let contents = terminal.snapshot().plain_rows().join("\n");
assert!(contents.contains("ready"), "missing ready marker");
assert!(
contents.contains("41 101"),
"missing resized PTY dimensions"
);
assert!(
contents.contains("got:hello-pipeline"),
"missing echoed input"
);
}