Implement PTY-native session terminal control

This commit is contained in:
ms2k 2026-08-02 17:36:40 +09:00
commit 8cef037fa3

View file

@ -21,8 +21,8 @@ use lazaret_core::identity::{
use lazaret_core::network::derive_ipv4_policy;
use lazaret_core::records::{
Domain, DurableDecision, Generation, HostProcessOwnershipSlot, ParticipantRole,
ParticipantState, ProcessOwnershipCommitAck, Session, SessionPhase, SessionTerminalEvent,
SessionTerminalMessage, TerminalResize, TerminalSignal, TerminalSignalKind,
ParticipantState, ProcessOwnershipCommitAck, Session, SessionPhase, SessionTerminalEventV2,
SessionTerminalMessage, TerminalBreak, TerminalInput, TerminalModesV2, TerminalResize,
};
use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
use lazaret_host_coordinator::git_routing::GitRoutingCoordinator;
@ -363,6 +363,14 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
environment: fixture.configuration.ephemeral_environment().clone(),
internal_pid_file: runtime_domain.join("session-internal.pid"),
argv: vec![b"/bin/sh".to_vec(), b"-c".to_vec(), script.into_bytes()],
initial_size: TerminalResize {
session_id,
rows: std::num::NonZeroU16::new(24).unwrap(),
columns: std::num::NonZeroU16::new(80).unwrap(),
pixel_width: 0,
pixel_height: 0,
},
initial_modes: standard_terminal_modes(),
};
let mut engine = SessionTerminalEngine::new(
Arc::clone(&fixture.store),
@ -401,7 +409,7 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
engine
.handle(
GenerationId::new(7),
SessionTerminalEvent::Resize(TerminalResize {
SessionTerminalEventV2::Resize(TerminalResize {
session_id,
rows: std::num::NonZeroU16::new(41).unwrap(),
columns: std::num::NonZeroU16::new(121).unwrap(),
@ -410,18 +418,23 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
}),
)
.map_err(failure)?;
for signal in [
TerminalSignalKind::Interrupt,
TerminalSignalKind::Quit,
TerminalSignalKind::Suspend,
] {
for byte in [3, 28, 26] {
engine
.handle(
GenerationId::new(7),
SessionTerminalEvent::Signal(TerminalSignal { session_id, signal }),
SessionTerminalEventV2::Input(TerminalInput {
session_id,
bytes: vec![byte],
}),
)
.map_err(failure)?;
}
engine
.handle(
GenerationId::new(7),
SessionTerminalEventV2::Break(TerminalBreak { session_id }),
)
.map_err(failure)?;
wait_for_signal_lines(&mut engine, session_id, &signals, 4)?;
if second_signals.exists() {
return Err(failure("terminal signals crossed Session IDs"));
@ -593,6 +606,35 @@ fn wait_for_path(path: &Path) -> Result<(), MatrixError> {
Err(failure("Session Terminal guest did not become ready"))
}
fn standard_terminal_modes() -> TerminalModesV2 {
TerminalModesV2 {
input_modes: 1280,
output_modes: 5,
control_modes: 983_231,
local_modes: 35_387,
line_discipline: 0,
input_speed: 38_400,
output_speed: 38_400,
interrupt: 3,
quit: 28,
erase: 127,
kill: 21,
eof: 4,
time: 0,
minimum: 1,
switch: 0,
start: 17,
stop: 19,
suspend: 26,
end_of_line: 0,
reprint: 18,
discard: 15,
word_erase: 23,
literal_next: 22,
end_of_line_2: 0,
}
}
fn wait_for_signal_lines(
engine: &mut SessionTerminalEngine,
session_id: SessionId,