Add workspace review summaries
Add workspace summary across the CLI, SDK, and MCP, and include it in the workspace-core profile so chat hosts can review one concise view of the current session. Persist lightweight review events for syncs, file edits, patch applies, exports, service lifecycle, and snapshot activity, then synthesize them with command history, current services, snapshot state, and current diff data since the last reset. Update the walkthroughs, use-case docs, public contract, changelog, and roadmap for 4.3.0, and make dist-check invoke the CLI module directly so local package reinstall quirks do not break the packaging gate. Validation: uv lock; ./.venv/bin/pytest --no-cov tests/test_vm_manager.py tests/test_cli.py tests/test_api.py tests/test_server.py tests/test_public_contract.py tests/test_workspace_use_case_smokes.py; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make check; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed workspace create -> patch apply -> workspace summary --json -> delete smoke.
This commit is contained in:
parent
899a6760c4
commit
dc86d84e96
24 changed files with 994 additions and 31 deletions
|
|
@ -455,6 +455,7 @@ def test_pyro_workspace_methods_delegate_to_manager(tmp_path: Path) -> None:
|
|||
services = pyro.list_services(workspace_id)
|
||||
service_status = pyro.status_service(workspace_id, "app")
|
||||
service_logs = pyro.logs_service(workspace_id, "app", all=True)
|
||||
summary = pyro.summarize_workspace(workspace_id)
|
||||
reset = pyro.reset_workspace(workspace_id, snapshot="checkpoint")
|
||||
deleted_snapshot = pyro.delete_snapshot(workspace_id, "checkpoint")
|
||||
status = pyro.status_workspace(workspace_id)
|
||||
|
|
@ -491,6 +492,9 @@ def test_pyro_workspace_methods_delegate_to_manager(tmp_path: Path) -> None:
|
|||
assert service_status["state"] == "running"
|
||||
assert service_logs["stderr"].count("[REDACTED]") >= 1
|
||||
assert service_logs["tail_lines"] is None
|
||||
assert summary["workspace_id"] == workspace_id
|
||||
assert summary["commands"]["total"] >= 1
|
||||
assert summary["changes"]["available"] is True
|
||||
assert reset["workspace_reset"]["snapshot_name"] == "checkpoint"
|
||||
assert reset["secrets"] == created["secrets"]
|
||||
assert deleted_snapshot["deleted"] is True
|
||||
|
|
@ -1054,6 +1058,14 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
|
|||
calls.append(("logs_workspace", {"workspace_id": workspace_id}))
|
||||
return {"workspace_id": workspace_id, "count": 0, "entries": []}
|
||||
|
||||
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
|
||||
calls.append(("summarize_workspace", {"workspace_id": workspace_id}))
|
||||
return {
|
||||
"workspace_id": workspace_id,
|
||||
"state": "started",
|
||||
"changes": {"available": True, "changed": False, "summary": None, "entries": []},
|
||||
}
|
||||
|
||||
def open_shell(
|
||||
self,
|
||||
workspace_id: str,
|
||||
|
|
@ -1185,6 +1197,9 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
|
|||
status = _extract_structured(
|
||||
await server.call_tool("workspace_status", {"workspace_id": "workspace-123"})
|
||||
)
|
||||
summary = _extract_structured(
|
||||
await server.call_tool("workspace_summary", {"workspace_id": "workspace-123"})
|
||||
)
|
||||
logs = _extract_structured(
|
||||
await server.call_tool("workspace_logs", {"workspace_id": "workspace-123"})
|
||||
)
|
||||
|
|
@ -1286,6 +1301,7 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
|
|||
)
|
||||
return (
|
||||
status,
|
||||
summary,
|
||||
logs,
|
||||
opened,
|
||||
read,
|
||||
|
|
@ -1300,13 +1316,15 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
|
|||
|
||||
results = asyncio.run(_run())
|
||||
assert results[0]["state"] == "started"
|
||||
assert results[1]["count"] == 0
|
||||
assert results[2]["shell_id"] == "shell-1"
|
||||
assert results[6]["closed"] is True
|
||||
assert results[7]["state"] == "running"
|
||||
assert results[10]["state"] == "running"
|
||||
assert results[1]["workspace_id"] == "workspace-123"
|
||||
assert results[2]["count"] == 0
|
||||
assert results[3]["shell_id"] == "shell-1"
|
||||
assert results[7]["closed"] is True
|
||||
assert results[8]["state"] == "running"
|
||||
assert results[11]["state"] == "running"
|
||||
assert calls == [
|
||||
("status_workspace", {"workspace_id": "workspace-123"}),
|
||||
("summarize_workspace", {"workspace_id": "workspace-123"}),
|
||||
("logs_workspace", {"workspace_id": "workspace-123"}),
|
||||
(
|
||||
"open_shell",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ def test_cli_help_guides_first_run() -> None:
|
|||
assert "pyro host print-config opencode" in help_text
|
||||
assert "If you want terminal-level visibility into the workspace model:" in help_text
|
||||
assert "pyro workspace exec WORKSPACE_ID -- cat note.txt" in help_text
|
||||
assert "pyro workspace summary WORKSPACE_ID" in help_text
|
||||
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in help_text
|
||||
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in help_text
|
||||
assert "pyro workspace sync push WORKSPACE_ID ./changes" in help_text
|
||||
|
|
@ -127,6 +128,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
|||
assert "pyro workspace start WORKSPACE_ID" in workspace_help
|
||||
assert "pyro workspace snapshot create WORKSPACE_ID checkpoint" in workspace_help
|
||||
assert "pyro workspace reset WORKSPACE_ID --snapshot checkpoint" in workspace_help
|
||||
assert "pyro workspace summary WORKSPACE_ID" in workspace_help
|
||||
assert "pyro workspace shell open WORKSPACE_ID --id-only" in workspace_help
|
||||
|
||||
workspace_create_help = _subparser_choice(
|
||||
|
|
@ -181,6 +183,12 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
|
|||
assert "--label" in workspace_update_help
|
||||
assert "--clear-label" in workspace_update_help
|
||||
|
||||
workspace_summary_help = _subparser_choice(
|
||||
_subparser_choice(parser, "workspace"), "summary"
|
||||
).format_help()
|
||||
assert "Summarize the current workspace session since the last reset" in workspace_summary_help
|
||||
assert "pyro workspace summary WORKSPACE_ID" in workspace_summary_help
|
||||
|
||||
workspace_file_help = _subparser_choice(
|
||||
_subparser_choice(parser, "workspace"), "file"
|
||||
).format_help()
|
||||
|
|
@ -2515,6 +2523,170 @@ def test_cli_workspace_logs_prints_json(
|
|||
assert payload["count"] == 0
|
||||
|
||||
|
||||
def test_cli_workspace_summary_prints_json(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
|
||||
assert workspace_id == "workspace-123"
|
||||
return {
|
||||
"workspace_id": workspace_id,
|
||||
"name": "review-eval",
|
||||
"labels": {"suite": "smoke"},
|
||||
"environment": "debian:12",
|
||||
"state": "started",
|
||||
"last_activity_at": 2.0,
|
||||
"session_started_at": 1.0,
|
||||
"outcome": {
|
||||
"command_count": 1,
|
||||
"last_command": {"command": "cat note.txt", "exit_code": 0},
|
||||
"service_count": 0,
|
||||
"running_service_count": 0,
|
||||
"export_count": 1,
|
||||
"snapshot_count": 1,
|
||||
"reset_count": 0,
|
||||
},
|
||||
"commands": {"total": 1, "recent": []},
|
||||
"edits": {"recent": []},
|
||||
"changes": {"available": True, "changed": False, "summary": None, "entries": []},
|
||||
"services": {"current": [], "recent": []},
|
||||
"artifacts": {"exports": []},
|
||||
"snapshots": {"named_count": 1, "recent": []},
|
||||
}
|
||||
|
||||
class SummaryParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="workspace",
|
||||
workspace_command="summary",
|
||||
workspace_id="workspace-123",
|
||||
json=True,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: SummaryParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
cli.main()
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["workspace_id"] == "workspace-123"
|
||||
assert payload["outcome"]["export_count"] == 1
|
||||
|
||||
|
||||
def test_cli_workspace_summary_prints_human(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
class StubPyro:
|
||||
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
|
||||
assert workspace_id == "workspace-123"
|
||||
return {
|
||||
"workspace_id": workspace_id,
|
||||
"name": "review-eval",
|
||||
"labels": {"suite": "smoke", "use_case": "review-eval"},
|
||||
"environment": "debian:12",
|
||||
"state": "started",
|
||||
"last_activity_at": 3.0,
|
||||
"session_started_at": 1.0,
|
||||
"outcome": {
|
||||
"command_count": 2,
|
||||
"last_command": {"command": "sh review.sh", "exit_code": 0},
|
||||
"service_count": 1,
|
||||
"running_service_count": 0,
|
||||
"export_count": 1,
|
||||
"snapshot_count": 1,
|
||||
"reset_count": 0,
|
||||
},
|
||||
"commands": {
|
||||
"total": 2,
|
||||
"recent": [
|
||||
{
|
||||
"sequence": 2,
|
||||
"command": "sh review.sh",
|
||||
"cwd": "/workspace",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 12,
|
||||
"execution_mode": "guest_vsock",
|
||||
"recorded_at": 3.0,
|
||||
}
|
||||
],
|
||||
},
|
||||
"edits": {
|
||||
"recent": [
|
||||
{
|
||||
"event_kind": "patch_apply",
|
||||
"recorded_at": 2.0,
|
||||
"path": "/workspace/note.txt",
|
||||
}
|
||||
]
|
||||
},
|
||||
"changes": {
|
||||
"available": True,
|
||||
"changed": True,
|
||||
"summary": {
|
||||
"total": 1,
|
||||
"added": 0,
|
||||
"modified": 1,
|
||||
"deleted": 0,
|
||||
"type_changed": 0,
|
||||
"text_patched": 1,
|
||||
"non_text": 0,
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"path": "/workspace/note.txt",
|
||||
"status": "modified",
|
||||
"artifact_type": "file",
|
||||
}
|
||||
],
|
||||
},
|
||||
"services": {
|
||||
"current": [{"service_name": "app", "state": "stopped"}],
|
||||
"recent": [
|
||||
{
|
||||
"event_kind": "service_stop",
|
||||
"service_name": "app",
|
||||
"state": "stopped",
|
||||
}
|
||||
],
|
||||
},
|
||||
"artifacts": {
|
||||
"exports": [
|
||||
{
|
||||
"workspace_path": "review-report.txt",
|
||||
"output_path": "/tmp/review-report.txt",
|
||||
}
|
||||
]
|
||||
},
|
||||
"snapshots": {
|
||||
"named_count": 1,
|
||||
"recent": [
|
||||
{"event_kind": "snapshot_create", "snapshot_name": "checkpoint"}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
class SummaryParser:
|
||||
def parse_args(self) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
command="workspace",
|
||||
workspace_command="summary",
|
||||
workspace_id="workspace-123",
|
||||
json=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(cli, "_build_parser", lambda: SummaryParser())
|
||||
monkeypatch.setattr(cli, "Pyro", StubPyro)
|
||||
cli.main()
|
||||
output = capsys.readouterr().out
|
||||
assert "Workspace review: workspace-123" in output
|
||||
assert "Outcome: commands=2 services=0/1 exports=1 snapshots=1 resets=0" in output
|
||||
assert "Recent commands:" in output
|
||||
assert "Recent edits:" in output
|
||||
assert "Changes: total=1 added=0 modified=1 deleted=0 type_changed=0 non_text=0" in output
|
||||
assert "Recent exports:" in output
|
||||
assert "Recent snapshot events:" in output
|
||||
|
||||
|
||||
def test_cli_workspace_delete_prints_human(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
|
|
@ -3028,6 +3200,16 @@ def test_content_only_read_docs_are_aligned() -> None:
|
|||
assert 'workspace patch apply "$WORKSPACE_ID" --patch-file fix.patch' in first_run
|
||||
|
||||
|
||||
def test_workspace_summary_docs_are_aligned() -> None:
|
||||
readme = Path("README.md").read_text(encoding="utf-8")
|
||||
install = Path("docs/install.md").read_text(encoding="utf-8")
|
||||
first_run = Path("docs/first-run.md").read_text(encoding="utf-8")
|
||||
|
||||
assert 'workspace summary "$WORKSPACE_ID"' in readme
|
||||
assert 'workspace summary "$WORKSPACE_ID"' in install
|
||||
assert 'workspace summary "$WORKSPACE_ID"' in first_run
|
||||
|
||||
|
||||
def test_cli_workspace_shell_write_signal_close_json(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ from pyro_mcp.contract import (
|
|||
PUBLIC_CLI_WORKSPACE_START_FLAGS,
|
||||
PUBLIC_CLI_WORKSPACE_STOP_FLAGS,
|
||||
PUBLIC_CLI_WORKSPACE_SUBCOMMANDS,
|
||||
PUBLIC_CLI_WORKSPACE_SUMMARY_FLAGS,
|
||||
PUBLIC_CLI_WORKSPACE_SYNC_PUSH_FLAGS,
|
||||
PUBLIC_CLI_WORKSPACE_SYNC_SUBCOMMANDS,
|
||||
PUBLIC_CLI_WORKSPACE_UPDATE_FLAGS,
|
||||
|
|
@ -272,6 +273,11 @@ def test_public_cli_help_lists_commands_and_run_flags() -> None:
|
|||
).format_help()
|
||||
for flag in PUBLIC_CLI_WORKSPACE_STOP_FLAGS:
|
||||
assert flag in workspace_stop_help_text
|
||||
workspace_summary_help_text = _subparser_choice(
|
||||
_subparser_choice(parser, "workspace"), "summary"
|
||||
).format_help()
|
||||
for flag in PUBLIC_CLI_WORKSPACE_SUMMARY_FLAGS:
|
||||
assert flag in workspace_summary_help_text
|
||||
workspace_shell_help_text = _subparser_choice(
|
||||
_subparser_choice(parser, "workspace"),
|
||||
"shell",
|
||||
|
|
|
|||
|
|
@ -602,6 +602,9 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
|
|||
},
|
||||
)
|
||||
)
|
||||
summary = _extract_structured(
|
||||
await server.call_tool("workspace_summary", {"workspace_id": workspace_id})
|
||||
)
|
||||
reset = _extract_structured(
|
||||
await server.call_tool(
|
||||
"workspace_reset",
|
||||
|
|
@ -639,6 +642,7 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
|
|||
service_status,
|
||||
service_logs,
|
||||
service_stopped,
|
||||
summary,
|
||||
reset,
|
||||
deleted_snapshot,
|
||||
logs,
|
||||
|
|
@ -664,6 +668,7 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
|
|||
service_status,
|
||||
service_logs,
|
||||
service_stopped,
|
||||
summary,
|
||||
reset,
|
||||
deleted_snapshot,
|
||||
logs,
|
||||
|
|
@ -700,6 +705,10 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
|
|||
assert service_logs["stderr"].count("[REDACTED]") >= 1
|
||||
assert service_logs["tail_lines"] is None
|
||||
assert service_stopped["state"] == "stopped"
|
||||
assert summary["workspace_id"] == created["workspace_id"]
|
||||
assert summary["commands"]["total"] >= 1
|
||||
assert summary["changes"]["available"] is True
|
||||
assert summary["artifacts"]["exports"][0]["workspace_path"] == "/workspace/subdir/more.txt"
|
||||
assert reset["workspace_reset"]["snapshot_name"] == "checkpoint"
|
||||
assert reset["secrets"] == created["secrets"]
|
||||
assert reset["command_count"] == 0
|
||||
|
|
|
|||
|
|
@ -699,6 +699,124 @@ def test_workspace_diff_and_export_round_trip(tmp_path: Path) -> None:
|
|||
assert logs["count"] == 0
|
||||
|
||||
|
||||
def test_workspace_summary_synthesizes_current_session(tmp_path: Path) -> None:
|
||||
seed_dir = tmp_path / "seed"
|
||||
seed_dir.mkdir()
|
||||
(seed_dir / "note.txt").write_text("hello\n", encoding="utf-8")
|
||||
update_dir = tmp_path / "update"
|
||||
update_dir.mkdir()
|
||||
(update_dir / "more.txt").write_text("more\n", encoding="utf-8")
|
||||
|
||||
manager = VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
|
||||
workspace_id = str(
|
||||
manager.create_workspace(
|
||||
environment="debian:12-base",
|
||||
allow_host_compat=True,
|
||||
seed_path=seed_dir,
|
||||
name="review-eval",
|
||||
labels={"suite": "smoke"},
|
||||
)["workspace_id"]
|
||||
)
|
||||
manager.push_workspace_sync(workspace_id, source_path=update_dir)
|
||||
manager.write_workspace_file(workspace_id, "src/app.py", text="print('hello')\n")
|
||||
manager.apply_workspace_patch(
|
||||
workspace_id,
|
||||
patch=(
|
||||
"--- a/note.txt\n"
|
||||
"+++ b/note.txt\n"
|
||||
"@@ -1 +1 @@\n"
|
||||
"-hello\n"
|
||||
"+patched\n"
|
||||
),
|
||||
)
|
||||
manager.exec_workspace(workspace_id, command="cat note.txt", timeout_seconds=30)
|
||||
manager.create_snapshot(workspace_id, "checkpoint")
|
||||
export_path = tmp_path / "exported-note.txt"
|
||||
manager.export_workspace(workspace_id, "note.txt", output_path=export_path)
|
||||
manager.start_service(
|
||||
workspace_id,
|
||||
"app",
|
||||
command='sh -lc \'trap "exit 0" TERM; touch .ready; while true; do sleep 60; done\'',
|
||||
readiness={"type": "file", "path": ".ready"},
|
||||
)
|
||||
manager.stop_service(workspace_id, "app")
|
||||
|
||||
summary = manager.summarize_workspace(workspace_id)
|
||||
|
||||
assert summary["workspace_id"] == workspace_id
|
||||
assert summary["name"] == "review-eval"
|
||||
assert summary["labels"] == {"suite": "smoke"}
|
||||
assert summary["outcome"]["command_count"] == 1
|
||||
assert summary["outcome"]["export_count"] == 1
|
||||
assert summary["outcome"]["snapshot_count"] == 1
|
||||
assert summary["commands"]["total"] == 1
|
||||
assert summary["commands"]["recent"][0]["command"] == "cat note.txt"
|
||||
assert [event["event_kind"] for event in summary["edits"]["recent"]] == [
|
||||
"patch_apply",
|
||||
"file_write",
|
||||
"sync_push",
|
||||
]
|
||||
assert summary["changes"]["available"] is True
|
||||
assert summary["changes"]["changed"] is True
|
||||
assert summary["changes"]["summary"]["total"] == 4
|
||||
assert summary["services"]["current"][0]["service_name"] == "app"
|
||||
assert [event["event_kind"] for event in summary["services"]["recent"]] == [
|
||||
"service_stop",
|
||||
"service_start",
|
||||
]
|
||||
assert summary["artifacts"]["exports"][0]["workspace_path"] == "/workspace/note.txt"
|
||||
assert summary["snapshots"]["named_count"] == 1
|
||||
assert summary["snapshots"]["recent"][0]["snapshot_name"] == "checkpoint"
|
||||
|
||||
|
||||
def test_workspace_summary_degrades_gracefully_for_stopped_and_legacy_workspaces(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
seed_dir = tmp_path / "seed"
|
||||
seed_dir.mkdir()
|
||||
(seed_dir / "note.txt").write_text("hello\n", encoding="utf-8")
|
||||
|
||||
manager = VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
|
||||
stopped_workspace_id = str(
|
||||
manager.create_workspace(
|
||||
environment="debian:12-base",
|
||||
allow_host_compat=True,
|
||||
seed_path=seed_dir,
|
||||
)["workspace_id"]
|
||||
)
|
||||
manager.exec_workspace(stopped_workspace_id, command="cat note.txt", timeout_seconds=30)
|
||||
manager.stop_workspace(stopped_workspace_id)
|
||||
stopped_summary = manager.summarize_workspace(stopped_workspace_id)
|
||||
assert stopped_summary["commands"]["total"] == 1
|
||||
assert stopped_summary["changes"]["available"] is False
|
||||
assert "must be in 'started' state" in str(stopped_summary["changes"]["reason"])
|
||||
|
||||
legacy_workspace_id = str(
|
||||
manager.create_workspace(
|
||||
environment="debian:12-base",
|
||||
allow_host_compat=True,
|
||||
seed_path=seed_dir,
|
||||
)["workspace_id"]
|
||||
)
|
||||
baseline_path = (
|
||||
tmp_path / "vms" / "workspaces" / legacy_workspace_id / "baseline" / "workspace.tar"
|
||||
)
|
||||
baseline_path.unlink()
|
||||
legacy_summary = manager.summarize_workspace(legacy_workspace_id)
|
||||
assert legacy_summary["changes"]["available"] is False
|
||||
assert "baseline snapshot" in str(legacy_summary["changes"]["reason"])
|
||||
|
||||
|
||||
def test_workspace_file_ops_and_patch_round_trip(tmp_path: Path) -> None:
|
||||
seed_dir = tmp_path / "seed"
|
||||
seed_dir.mkdir()
|
||||
|
|
|
|||
|
|
@ -391,6 +391,69 @@ class _FakePyro:
|
|||
"workspace_reset": {"snapshot_name": snapshot},
|
||||
}
|
||||
|
||||
def summarize_workspace(self, workspace_id: str) -> dict[str, Any]:
|
||||
workspace = self._resolve_workspace(workspace_id)
|
||||
changed = self._diff_changed(workspace)
|
||||
return {
|
||||
"workspace_id": workspace_id,
|
||||
"name": workspace.name,
|
||||
"labels": dict(workspace.labels),
|
||||
"environment": workspace.environment,
|
||||
"state": "started",
|
||||
"last_activity_at": workspace.last_activity_at,
|
||||
"session_started_at": workspace.created_at,
|
||||
"outcome": {
|
||||
"command_count": 0,
|
||||
"last_command": None,
|
||||
"service_count": len(workspace.services),
|
||||
"running_service_count": sum(
|
||||
1
|
||||
for service in workspace.services.values()
|
||||
if service["state"] == "running"
|
||||
),
|
||||
"export_count": 1,
|
||||
"snapshot_count": max(len(workspace.snapshots) - 1, 0),
|
||||
"reset_count": workspace.reset_count,
|
||||
},
|
||||
"commands": {"total": 0, "recent": []},
|
||||
"edits": {"recent": []},
|
||||
"changes": {
|
||||
"available": True,
|
||||
"reason": None,
|
||||
"changed": changed,
|
||||
"summary": {"total": 1 if changed else 0},
|
||||
"entries": (
|
||||
[
|
||||
{
|
||||
"path": "/workspace/artifact.txt",
|
||||
"status": "modified",
|
||||
"artifact_type": "file",
|
||||
}
|
||||
]
|
||||
if changed
|
||||
else []
|
||||
),
|
||||
},
|
||||
"services": {
|
||||
"current": [
|
||||
{"service_name": name, "state": service["state"]}
|
||||
for name, service in sorted(workspace.services.items())
|
||||
],
|
||||
"recent": [],
|
||||
},
|
||||
"artifacts": {
|
||||
"exports": [
|
||||
{
|
||||
"workspace_path": "review-report.txt",
|
||||
"output_path": str(
|
||||
self._workspace_dir(workspace_id) / "exported-review.txt"
|
||||
),
|
||||
}
|
||||
]
|
||||
},
|
||||
"snapshots": {"named_count": max(len(workspace.snapshots) - 1, 0), "recent": []},
|
||||
}
|
||||
|
||||
def open_shell(self, workspace_id: str, **_: Any) -> dict[str, Any]:
|
||||
workspace = self._resolve_workspace(workspace_id)
|
||||
self._shell_counter += 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue