Make persistent workspaces capable of running long-lived background processes instead of forcing everything through one-shot exec calls. Add workspace service start/list/status/logs/stop across the CLI, Python SDK, and MCP server, with multiple named services per workspace, typed readiness probes (file, tcp, http, and command), and aggregate service counts on workspace status. Keep service state and logs outside /workspace so diff and export semantics stay workspace-scoped, and extend the guest agent plus backends to persist service records and logs across separate calls. Update the 2.7.0 docs, examples, changelog, and roadmap milestone to reflect the shipped surface. Validation: uv lock; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed Firecracker smoke for workspace create, two service starts, list/status/logs, diff unaffected, stop, and delete.
49 lines
2.2 KiB
Python
49 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from pyro_mcp import Pyro
|
|
|
|
|
|
def main() -> None:
|
|
pyro = Pyro()
|
|
with (
|
|
tempfile.TemporaryDirectory(prefix="pyro-workspace-seed-") as seed_dir,
|
|
tempfile.TemporaryDirectory(prefix="pyro-workspace-sync-") as sync_dir,
|
|
tempfile.TemporaryDirectory(prefix="pyro-workspace-export-") as export_dir,
|
|
):
|
|
Path(seed_dir, "note.txt").write_text("hello from seed\n", encoding="utf-8")
|
|
Path(sync_dir, "note.txt").write_text("hello from sync\n", encoding="utf-8")
|
|
created = pyro.create_workspace(environment="debian:12", seed_path=seed_dir)
|
|
workspace_id = str(created["workspace_id"])
|
|
try:
|
|
pyro.push_workspace_sync(workspace_id, sync_dir)
|
|
result = pyro.exec_workspace(workspace_id, command="cat note.txt")
|
|
print(result["stdout"], end="")
|
|
diff_result = pyro.diff_workspace(workspace_id)
|
|
print(f"changed={diff_result['changed']} total={diff_result['summary']['total']}")
|
|
exported_path = Path(export_dir, "note.txt")
|
|
pyro.export_workspace(workspace_id, "note.txt", output_path=exported_path)
|
|
print(exported_path.read_text(encoding="utf-8"), end="")
|
|
pyro.start_service(
|
|
workspace_id,
|
|
"web",
|
|
command="touch .web-ready && while true; do sleep 60; done",
|
|
readiness={"type": "file", "path": ".web-ready"},
|
|
)
|
|
services = pyro.list_services(workspace_id)
|
|
print(f"services={services['count']} running={services['running_count']}")
|
|
service_status = pyro.status_service(workspace_id, "web")
|
|
print(f"service_state={service_status['state']} ready_at={service_status['ready_at']}")
|
|
service_logs = pyro.logs_service(workspace_id, "web", tail_lines=20)
|
|
print(f"service_stdout_len={len(service_logs['stdout'])}")
|
|
pyro.stop_service(workspace_id, "web")
|
|
logs = pyro.logs_workspace(workspace_id)
|
|
print(f"workspace_id={workspace_id} command_count={logs['count']}")
|
|
finally:
|
|
pyro.delete_workspace(workspace_id)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|