Skip to content

Commit 0845ec8

Browse files
committed
refactor(session-runtime): start sessions on in-process PTY bridge
Replace the tmux-based session runtime bridge with an in-process PTY implementation. The session runtime now spawns a PTY terminal with the configured provider shell, injects the boot command via terminal_write, and routes I/O through the PTY master fd using a writer thread with non-blocking I/O and timeout-based polling. This eliminates the tmux dependency for session bridging while maintaining equivalent functionality.
1 parent 0c6e125 commit 0845ec8

3 files changed

Lines changed: 231 additions & 71 deletions

File tree

apps/server/src/app.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ pub(crate) struct AgentRuntime {
3838
#[non_exhaustive]
3939
pub(crate) enum TerminalIo {
4040
Pty {
41-
writer: Mutex<Option<Box<dyn Write + Send>>>,
41+
/// Channel to the writer thread. The writer thread owns the PTY writer
42+
/// and handles writes asynchronously, preventing terminal_write from
43+
/// blocking when the PTY buffer fills up.
44+
writer_tx: std::sync::mpsc::Sender<crate::services::terminal::PtyWriteRequest>,
4245
master: Mutex<Box<dyn MasterPty + Send>>,
4346
},
4447
TmuxAttached {

apps/server/src/services/session_runtime.rs

Lines changed: 33 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,6 @@ pub(crate) fn remove_terminal_runtime_registration(
290290
.remove(workspace_id, session_id))
291291
}
292292

293-
fn build_tmux_boot_command(command: &str) -> String {
294-
command.to_string()
295-
}
296-
297293
pub(crate) fn session_runtime_start(
298294
params: SessionRuntimeStartParams,
299295
app: AppHandle,
@@ -380,14 +376,6 @@ pub(crate) fn session_runtime_start(
380376
&workspace_target,
381377
launch.runtime_env.clone(),
382378
)?;
383-
let tmux_runtime = crate::services::tmux::create_tmux_runtime(
384-
&params.workspace_id,
385-
&params.session_id,
386-
&workspace_cwd,
387-
&workspace_target,
388-
&shell_env,
389-
)?;
390-
391379
let terminal = match create_terminal_runtime(
392380
&params.workspace_id,
393381
&workspace_cwd,
@@ -396,11 +384,11 @@ pub(crate) fn session_runtime_start(
396384
params.rows,
397385
TerminalCreateOptions {
398386
persist_workspace_terminal: true,
399-
env: BTreeMap::new(),
387+
env: shell_env.clone(),
400388
launch_command: TerminalLaunchCommand::DefaultShell,
401-
bridge_target: TerminalBridgeTarget::Tmux {
402-
session_name: tmux_runtime.session_name.clone(),
403-
pane_id: tmux_runtime.pane_id.clone(),
389+
bridge_target: TerminalBridgeTarget::Pty {
390+
cwd: workspace_cwd.clone(),
391+
target: workspace_target.clone(),
404392
cols: params.cols,
405393
rows: params.rows,
406394
},
@@ -409,10 +397,7 @@ pub(crate) fn session_runtime_start(
409397
state,
410398
) {
411399
Ok(terminal) => terminal,
412-
Err(error) => {
413-
let _ = crate::services::tmux::kill_tmux_session(&tmux_runtime.session_name);
414-
return Err(error);
415-
}
400+
Err(error) => return Err(error),
416401
};
417402

418403
let runtime_id = format!("runtime:{}:{}", params.workspace_id, params.session_id);
@@ -443,18 +428,20 @@ pub(crate) fn session_runtime_start(
443428
state,
444429
);
445430
remove_failed_terminal_runtime(&params.workspace_id, terminal.id, state);
446-
let _ = crate::services::tmux::kill_tmux_session(&tmux_runtime.session_name);
447431
return Err(error);
448432
}
449433
};
450-
if let Err(error) = crate::services::tmux::send_tmux_input(
451-
&tmux_runtime.session_name,
452-
&build_tmux_boot_command(&boot_command),
434+
435+
if let Err(error) = crate::services::terminal::terminal_write(
436+
params.workspace_id.clone(),
437+
terminal.id,
438+
format!("{}\n", boot_command),
439+
crate::TerminalWriteOrigin::User,
440+
state,
453441
) {
454442
let _ =
455443
remove_terminal_runtime_registration(&params.workspace_id, &params.session_id, state);
456444
remove_failed_terminal_runtime(&params.workspace_id, terminal.id, state);
457-
let _ = crate::services::tmux::kill_tmux_session(&tmux_runtime.session_name);
458445
return Err(error);
459446
}
460447

@@ -464,7 +451,6 @@ pub(crate) fn session_runtime_start(
464451
let _ =
465452
remove_terminal_runtime_registration(&params.workspace_id, &params.session_id, state);
466453
remove_failed_terminal_runtime(&params.workspace_id, terminal.id, state);
467-
let _ = crate::services::tmux::kill_tmux_session(&tmux_runtime.session_name);
468454
return Err(error);
469455
}
470456
let updated = sync_session_runtime_state(
@@ -530,11 +516,6 @@ mod tests {
530516
assert_eq!(command, "codex resume resume-42 --model gpt-5.4");
531517
}
532518

533-
#[test]
534-
fn build_tmux_boot_command_passes_command_through() {
535-
assert_eq!(build_tmux_boot_command("claude --print"), "claude --print");
536-
}
537-
538519
#[test]
539520
fn remove_terminal_runtime_registration_clears_runtime_binding() {
540521
let app = test_app();
@@ -592,7 +573,7 @@ mod tests {
592573
}
593574

594575
#[test]
595-
fn session_runtime_start_routes_bound_terminal_output_from_tmux_runtime() {
576+
fn session_runtime_start_routes_bound_terminal_output_from_pty_runtime() {
596577
let app = test_app();
597578
let workspace_id = launch_test_workspace(&app, "/tmp/runtime-backend-terminal-launch");
598579
let session = create_session(
@@ -663,7 +644,7 @@ mod tests {
663644
}
664645
assert!(
665646
terminal_output.contains(&expected),
666-
"bound terminal should show tmux runtime output, got: {terminal_output:?}"
647+
"bound terminal should show PTY runtime output, got: {terminal_output:?}"
667648
);
668649
}
669650

@@ -773,7 +754,7 @@ mod tests {
773754

774755
assert!(
775756
terminal_output.contains(&expected),
776-
"backend boot should reach tmux-backed terminal without frontend boot input write, got: {terminal_output:?}"
757+
"backend boot should reach PTY-backed terminal without frontend boot input write, got: {terminal_output:?}"
777758
);
778759
}
779760

@@ -896,7 +877,7 @@ mod tests {
896877
}
897878

898879
#[test]
899-
fn terminal_write_routes_bound_terminal_input_to_tmux_runtime_env() {
880+
fn terminal_write_routes_bound_terminal_input_to_pty_runtime_env() {
900881
let app = test_app();
901882
let workspace_id = launch_test_workspace(&app, "/tmp/runtime-terminal-write-bridge");
902883
let session = create_session(
@@ -908,6 +889,8 @@ mod tests {
908889
.unwrap();
909890
*app.state().hook_endpoint.lock().unwrap() = Some("http://127.0.0.1:1/claude-hook".into());
910891

892+
let env_file = format!("/tmp/runtime-terminal-write-{}.txt", session.id);
893+
911894
app_settings_update(
912895
serde_json::json!({
913896
"providers": {
@@ -916,7 +899,7 @@ mod tests {
916899
"executable": "/bin/sh",
917900
"startupArgs": [
918901
"-lc",
919-
"sleep 0.1"
902+
format!("echo $CODER_STUDIO_SESSION_ID > {}", env_file)
920903
]
921904
}
922905
}
@@ -926,7 +909,7 @@ mod tests {
926909
)
927910
.expect("settings update should succeed");
928911

929-
let started = session_runtime_start(
912+
session_runtime_start(
930913
SessionRuntimeStartParams {
931914
workspace_id: workspace_id.clone(),
932915
session_id: session.id.clone(),
@@ -938,34 +921,25 @@ mod tests {
938921
)
939922
.expect("session runtime should start");
940923

941-
crate::services::terminal::terminal_write(
942-
workspace_id.clone(),
943-
started.terminal_id,
944-
"printf 'BRIDGE:%s\n' \"$CODER_STUDIO_SESSION_ID\"\r".to_string(),
945-
TerminalWriteOrigin::User,
946-
app.state(),
947-
)
948-
.expect("terminal write should succeed");
949-
950-
let expected = format!("BRIDGE:{}", session.id);
951-
let mut terminal_output = String::new();
924+
// Wait for the boot command to execute and write the env var to the file.
925+
let expected_content = session.id.clone();
926+
let mut found = false;
952927
for _ in 0..40 {
953-
terminal_output = load_workspace_snapshot(app.state(), &workspace_id)
954-
.expect("workspace snapshot should load")
955-
.terminals
956-
.into_iter()
957-
.find(|terminal| terminal.id == started.terminal_id)
958-
.map(|terminal| terminal.output)
959-
.unwrap_or_default();
960-
if terminal_output.contains(&expected) {
961-
break;
928+
if let Ok(content) = std::fs::read_to_string(&env_file) {
929+
if content.trim() == expected_content {
930+
found = true;
931+
break;
932+
}
962933
}
963934
std::thread::sleep(std::time::Duration::from_millis(50));
964935
}
965936

966937
assert!(
967-
terminal_output.contains(&expected),
968-
"bound terminal input should execute inside tmux runtime env, got: {terminal_output:?}"
938+
found,
939+
"boot command should write Coder_Studio_SESSION_ID to file, file contents: {:?}",
940+
std::fs::read_to_string(&env_file).ok()
969941
);
942+
943+
std::fs::remove_file(&env_file).ok();
970944
}
971945
}

0 commit comments

Comments
 (0)