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:
Thales Maciel 2026-03-12 23:16:10 -03:00
parent ab02ae46c7
commit 446f7fce04
21 changed files with 999 additions and 23 deletions

View file

@ -32,6 +32,8 @@ def test_create_server_registers_vm_tools(tmp_path: Path) -> None:
assert "vm_run" in tool_names
assert "vm_status" 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
@ -220,6 +222,8 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
"environment": "debian:12-base",
"allow_host_compat": True,
"seed_path": str(source_dir),
"name": "repro-fix",
"labels": {"issue": "123"},
"secrets": [
{"name": "API_TOKEN", "value": "expected"},
{"name": "FILE_TOKEN", "file_path": str(secret_file)},
@ -228,6 +232,17 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
)
)
workspace_id = str(created["workspace_id"])
listed_before = _extract_structured(await server.call_tool("workspace_list", {}))
updated = _extract_structured(
await server.call_tool(
"workspace_update",
{
"workspace_id": workspace_id,
"labels": {"owner": "codex"},
"clear_labels": ["issue"],
},
)
)
update_dir = tmp_path / "update"
update_dir.mkdir()
(update_dir / "more.txt").write_text("more\n", encoding="utf-8")
@ -385,6 +400,8 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
)
return (
created,
listed_before,
updated,
synced,
executed,
listed_files,
@ -408,6 +425,8 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
(
created,
listed_before,
updated,
synced,
executed,
listed_files,
@ -429,6 +448,11 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
deleted,
) = asyncio.run(_run())
assert created["state"] == "started"
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["labels"] == {"owner": "codex"}
assert created["workspace_seed"]["mode"] == "directory"
assert created["secrets"] == [
{"name": "API_TOKEN", "source_kind": "literal"},