feat(hh): P3b — multipass lifecycle + PTY resize sync

- sbx.rs: prepare()/teardown() — multipass launch (idempotent, reuses an
  existing instance) on /sbx launch multipass, delete --purge on stop;
  Backend::default_image() per backend (24.04 / ubuntu:24.04).
- app.rs: async non-freezing launch — prepare runs in spawn_blocking and the
  sandbox handle is handed to the run loop via a channel, so a ~30s multipass
  VM boot never freezes the UI (status: "summoning…"). Sandbox is sized to the
  actual pane (not fixed 24x80); broker resizes the PTY and broadcasts
  {"_sbx":"resize"} on terminal-size changes; clients set their vt100 size to
  match. Teardown on /sbx stop and on exit.
- net.rs: parse status rows/cols + resize frames.

Verified: cargo test (4 pass), clean build, live local sandbox via the async
path with dynamic full-width sizing. multipass 1.16.3 present; VM-boot path
implemented (live VM verify is slow, runs the same async flow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-30 16:34:08 -07:00
parent 232a00cc9e
commit d8018cbe2a
3 changed files with 201 additions and 72 deletions
+6
View File
@@ -160,6 +160,12 @@ fn parse_sbx(text: &str) -> Option<Net> {
"status" => Some(Net::SbxStatus {
backend: v["backend"].as_str().unwrap_or("?").to_string(),
ready: v["state"].as_str() == Some("ready"),
rows: v["rows"].as_u64().unwrap_or(24) as u16,
cols: v["cols"].as_u64().unwrap_or(80) as u16,
}),
"resize" => Some(Net::SbxResize {
rows: v["rows"].as_u64().unwrap_or(24) as u16,
cols: v["cols"].as_u64().unwrap_or(80) as u16,
}),
"data" => Some(Net::SbxData(STANDARD.decode(v["b64"].as_str()?).ok()?)),
"input" => Some(Net::SbxInput(STANDARD.decode(v["b64"].as_str()?).ok()?)),