Add workspace naming and discovery
Make concurrent workspaces easier to rediscover and resume without relying on opaque IDs alone. Add optional workspace names, key/value labels, workspace list, and workspace update across the CLI, Python SDK, and MCP surface, and persist last_activity_at so list ordering reflects real mutating activity. Update the stable contract, install/first-run docs, roadmap, and Python workspace example to teach the new discovery flow, and validate it with focused manager/CLI/API/server coverage plus uv lock, make check, make dist-check, and a real multi-workspace smoke for create, list, update, exec, reorder, and delete.
This commit is contained in:
parent
ab02ae46c7
commit
446f7fce04
21 changed files with 999 additions and 23 deletions
|
|
@ -50,6 +50,8 @@ def test_pyro_create_server_registers_vm_run(tmp_path: Path) -> None:
|
|||
assert "vm_run" in tool_names
|
||||
assert "vm_create" in tool_names
|
||||
assert "workspace_create" in tool_names
|
||||
assert "workspace_list" in tool_names
|
||||
assert "workspace_update" in tool_names
|
||||
assert "workspace_start" in tool_names
|
||||
assert "workspace_stop" in tool_names
|
||||
assert "workspace_diff" in tool_names
|
||||
|
|
@ -140,6 +142,14 @@ def test_pyro_workspace_network_policy_and_published_ports_delegate() -> None:
|
|||
calls.append(("create_workspace", kwargs))
|
||||
return {"workspace_id": "workspace-123"}
|
||||
|
||||
def list_workspaces(self) -> dict[str, Any]:
|
||||
calls.append(("list_workspaces", {}))
|
||||
return {"count": 1, "workspaces": [{"workspace_id": "workspace-123"}]}
|
||||
|
||||
def update_workspace(self, workspace_id: str, **kwargs: Any) -> dict[str, Any]:
|
||||
calls.append(("update_workspace", {"workspace_id": workspace_id, **kwargs}))
|
||||
return {"workspace_id": workspace_id, "name": "repro-fix", "labels": {"owner": "codex"}}
|
||||
|
||||
def start_service(
|
||||
self,
|
||||
workspace_id: str,
|
||||
|
|
@ -163,6 +173,15 @@ def test_pyro_workspace_network_policy_and_published_ports_delegate() -> None:
|
|||
pyro.create_workspace(
|
||||
environment="debian:12",
|
||||
network_policy="egress+published-ports",
|
||||
name="repro-fix",
|
||||
labels={"issue": "123"},
|
||||
)
|
||||
pyro.list_workspaces()
|
||||
pyro.update_workspace(
|
||||
"workspace-123",
|
||||
name="repro-fix",
|
||||
labels={"owner": "codex"},
|
||||
clear_labels=["issue"],
|
||||
)
|
||||
pyro.start_service(
|
||||
"workspace-123",
|
||||
|
|
@ -182,9 +201,25 @@ def test_pyro_workspace_network_policy_and_published_ports_delegate() -> None:
|
|||
"allow_host_compat": False,
|
||||
"seed_path": None,
|
||||
"secrets": None,
|
||||
"name": "repro-fix",
|
||||
"labels": {"issue": "123"},
|
||||
},
|
||||
)
|
||||
assert calls[1] == (
|
||||
"list_workspaces",
|
||||
{},
|
||||
)
|
||||
assert calls[2] == (
|
||||
"update_workspace",
|
||||
{
|
||||
"workspace_id": "workspace-123",
|
||||
"name": "repro-fix",
|
||||
"clear_name": False,
|
||||
"labels": {"owner": "codex"},
|
||||
"clear_labels": ["issue"],
|
||||
},
|
||||
)
|
||||
assert calls[3] == (
|
||||
"start_service",
|
||||
{
|
||||
"workspace_id": "workspace-123",
|
||||
|
|
@ -219,12 +254,20 @@ def test_pyro_workspace_methods_delegate_to_manager(tmp_path: Path) -> None:
|
|||
environment="debian:12-base",
|
||||
allow_host_compat=True,
|
||||
seed_path=source_dir,
|
||||
name="repro-fix",
|
||||
labels={"issue": "123"},
|
||||
secrets=[
|
||||
{"name": "API_TOKEN", "value": "expected"},
|
||||
{"name": "FILE_TOKEN", "file_path": str(secret_file)},
|
||||
],
|
||||
)
|
||||
workspace_id = str(created["workspace_id"])
|
||||
listed_before = pyro.list_workspaces()
|
||||
updated_metadata = pyro.update_workspace(
|
||||
workspace_id,
|
||||
labels={"owner": "codex"},
|
||||
clear_labels=["issue"],
|
||||
)
|
||||
updated_dir = tmp_path / "updated"
|
||||
updated_dir.mkdir()
|
||||
(updated_dir / "more.txt").write_text("more\n", encoding="utf-8")
|
||||
|
|
@ -293,6 +336,11 @@ def test_pyro_workspace_methods_delegate_to_manager(tmp_path: Path) -> None:
|
|||
{"name": "API_TOKEN", "source_kind": "literal"},
|
||||
{"name": "FILE_TOKEN", "source_kind": "file"},
|
||||
]
|
||||
assert created["name"] == "repro-fix"
|
||||
assert created["labels"] == {"issue": "123"}
|
||||
assert listed_before["count"] == 1
|
||||
assert listed_before["workspaces"][0]["name"] == "repro-fix"
|
||||
assert updated_metadata["labels"] == {"owner": "codex"}
|
||||
assert executed["stdout"] == "[REDACTED]\n"
|
||||
assert any(entry["path"] == "/workspace/note.txt" for entry in listed_files["entries"])
|
||||
assert file_read["content"] == "ok\n"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue