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_ansi, GridSurface};
|
||||||
use spacesh_core::snapshot::Snapshot;
|
use spacesh_core::snapshot::Snapshot;
|
||||||
use spacesh_proto::{SurfaceId, WorkspaceId};
|
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::sync::{broadcast, mpsc, oneshot};
|
||||||
use tokio::time::{Duration, Instant};
|
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 BROADCAST_CAP: usize = 1024;
|
||||||
const FLUSH_INTERVAL: Duration = Duration::from_millis(6);
|
const FLUSH_INTERVAL: Duration = Duration::from_millis(6);
|
||||||
const FLUSH_BYTES: usize = 16 * 1024;
|
const FLUSH_BYTES: usize = 16 * 1024;
|
||||||
@@ -181,4 +202,29 @@ mod tests {
|
|||||||
let (snap, _sub) = reply_rx.await.unwrap();
|
let (snap, _sub) = reply_rx.await.unwrap();
|
||||||
assert!(snap.ansi.contains("SNAPME"), "snapshot: {:?}", snap.ansi);
|
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