feat(daemon): spawn_from_spec to (re)start surfaces from a persisted spec
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,31 @@
|
||||
use spacesh_core::{snapshot::snapshot_ansi, GridSurface};
|
||||
use spacesh_core::snapshot::Snapshot;
|
||||
use spacesh_proto::{SurfaceId, WorkspaceId};
|
||||
use spacesh_pty::PtyHandle;
|
||||
use spacesh_proto::workspace::SurfaceSpec;
|
||||
use spacesh_pty::{PtyHandle, SpawnSpec};
|
||||
use tokio::sync::{broadcast, mpsc, oneshot};
|
||||
use tokio::time::{Duration, Instant};
|
||||
|
||||
/// Spawn (or restart) a surface actor from a persisted spec. Injects
|
||||
/// SPACESH_SURFACE_ID into the child env, mirroring `new_surface`.
|
||||
pub fn spawn_from_spec(
|
||||
id: SurfaceId,
|
||||
workspace_id: WorkspaceId,
|
||||
spec: &SurfaceSpec,
|
||||
exit_tx: mpsc::UnboundedSender<(SurfaceId, i32)>,
|
||||
) -> std::io::Result<SurfaceHandle> {
|
||||
let pty = PtyHandle::spawn(SpawnSpec {
|
||||
command: spec.command.clone(),
|
||||
args: spec.args.clone(),
|
||||
cwd: std::path::PathBuf::from(&spec.cwd),
|
||||
cols: spec.cols,
|
||||
rows: spec.rows,
|
||||
env: vec![("SPACESH_SURFACE_ID".into(), id.0.clone())],
|
||||
})
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
|
||||
Ok(spawn_surface(id, workspace_id, pty, spec.cols, spec.rows, exit_tx))
|
||||
}
|
||||
|
||||
const BROADCAST_CAP: usize = 1024;
|
||||
const FLUSH_INTERVAL: Duration = Duration::from_millis(6);
|
||||
const FLUSH_BYTES: usize = 16 * 1024;
|
||||
@@ -181,4 +202,29 @@ mod tests {
|
||||
let (snap, _sub) = reply_rx.await.unwrap();
|
||||
assert!(snap.ansi.contains("SNAPME"), "snapshot: {:?}", snap.ansi);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn spawn_from_spec_runs_the_command() {
|
||||
let _serial = crate::test_support::serial();
|
||||
let spec = SurfaceSpec {
|
||||
command: "/bin/sh".into(),
|
||||
args: vec!["-c".into(), "printf RESPAWN; sleep 0.3".into()],
|
||||
cwd: std::env::temp_dir().to_string_lossy().into(),
|
||||
agent_label: None, cols: 80, rows: 24, autostart: false,
|
||||
};
|
||||
let (exit_tx, _rx) = mpsc::unbounded_channel();
|
||||
let handle = spawn_from_spec(SurfaceId("s_r".into()), WorkspaceId("w_1".into()), &spec, exit_tx).unwrap();
|
||||
let (reply_tx, reply_rx) = oneshot::channel();
|
||||
handle.tx.send(SurfaceMsg::Attach { reply: reply_tx }).await.unwrap();
|
||||
let mut sub = reply_rx.await.unwrap();
|
||||
let mut got = String::new();
|
||||
let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(2);
|
||||
while tokio::time::Instant::now() < deadline {
|
||||
if let Ok(Ok(b)) = tokio::time::timeout(tokio::time::Duration::from_millis(100), sub.recv()).await {
|
||||
got.push_str(&String::from_utf8_lossy(&b));
|
||||
if got.contains("RESPAWN") { break; }
|
||||
}
|
||||
}
|
||||
assert!(got.contains("RESPAWN"), "got: {got:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user