From a7bfd6cc26f4303312fa2eb4b06bc4e3a2d42d74 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 29 Jul 2026 02:55:43 +0530 Subject: [PATCH] fix: scan dashboard workflows from data-home logs too Installed dashboards could not see recordings under site-packages logs. Include get_data_home()/logs paths and stop using PROJECT_ROOT as the default cloud reporter workspace root after pip install. --- openspace/cloud/skill_quality_reporter.py | 9 ++++-- openspace/cloud/task_trace_reporter.py | 9 ++++-- openspace/config/constants.py | 35 +++++++++++++++++++++++ openspace/entrypoints/dashboard/server.py | 9 ++---- tests/config/test_data_home.py | 31 ++++++++++++++++++++ 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/openspace/cloud/skill_quality_reporter.py b/openspace/cloud/skill_quality_reporter.py index d67df90..bc46a59 100644 --- a/openspace/cloud/skill_quality_reporter.py +++ b/openspace/cloud/skill_quality_reporter.py @@ -20,7 +20,7 @@ from openspace.cloud.telemetry_payloads import ( build_skill_use_report_payload, short_cloud_request_id, ) -from openspace.config.constants import PROJECT_ROOT +from openspace.config.constants import get_data_home from openspace.skill_engine.types import ExecutionAnalysis, SkillJudgment from openspace.utils.logging import Logger @@ -47,7 +47,12 @@ class CloudSkillQualityReporter: self._client = client self._mapping_store = mapping_store self._outbox = outbox - self._workspace_root = Path(workspace_root).resolve() if workspace_root else PROJECT_ROOT + if workspace_root is not None: + self._workspace_root = Path(workspace_root).resolve() + elif Path.cwd().exists(): + self._workspace_root = Path.cwd().resolve() + else: + self._workspace_root = get_data_home(create=False) async def maybe_report_analysis( self, diff --git a/openspace/cloud/task_trace_reporter.py b/openspace/cloud/task_trace_reporter.py index f6d15ec..aa9297f 100644 --- a/openspace/cloud/task_trace_reporter.py +++ b/openspace/cloud/task_trace_reporter.py @@ -18,7 +18,7 @@ from openspace.cloud.telemetry_payloads import ( build_usage_report_payload, short_cloud_request_id, ) -from openspace.config.constants import PROJECT_ROOT, get_data_home +from openspace.config.constants import get_data_home class CloudTaskTraceReporter: @@ -39,7 +39,12 @@ class CloudTaskTraceReporter: self._artifact_dir = Path( artifact_dir or get_data_home(create=True) / "cloud-task-traces" ) - self._workspace_root = Path(workspace_root).resolve() if workspace_root else PROJECT_ROOT + if workspace_root is not None: + self._workspace_root = Path(workspace_root).resolve() + elif Path.cwd().exists(): + self._workspace_root = Path.cwd().resolve() + else: + self._workspace_root = get_data_home(create=False) async def maybe_report_execution( self, diff --git a/openspace/config/constants.py b/openspace/config/constants.py index bb28330..6b73135 100644 --- a/openspace/config/constants.py +++ b/openspace/config/constants.py @@ -94,6 +94,40 @@ def get_log_dir(*, create: bool = True) -> Path: return path +def get_workflow_roots() -> list[Path]: + """Directories the dashboard should scan for workflow/run artifacts. + + Always includes writable data-home log paths. When running from a source + checkout, also includes the historical repo-relative roots so local + development keeps finding existing recordings and benchmark runs. + """ + data_logs = get_log_dir(create=False) + roots: list[Path] = [ + data_logs / "recordings", + data_logs / "trajectories", + ] + if is_source_checkout(): + roots.extend( + [ + PROJECT_ROOT / "logs" / "recordings", + PROJECT_ROOT / "logs" / "trajectories", + PROJECT_ROOT / "benchmarks" / "gdpval" / "results", + PROJECT_ROOT / "benchmarks" / "terminal_bench" / "runs", + ] + ) + + # Preserve order while dropping duplicates (same path via symlink/resolve). + seen: set[Path] = set() + unique: list[Path] = [] + for root in roots: + key = root.resolve() if root.exists() else root + if key in seen: + continue + seen.add(key) + unique.append(root) + return unique + + __all__ = [ "CONFIG_GROUNDING", "CONFIG_SECURITY", @@ -108,4 +142,5 @@ __all__ = [ "get_default_db_path", "get_cache_dir", "get_log_dir", + "get_workflow_roots", ] diff --git a/openspace/entrypoints/dashboard/server.py b/openspace/entrypoints/dashboard/server.py index f8784ac..7825299 100644 --- a/openspace/entrypoints/dashboard/server.py +++ b/openspace/entrypoints/dashboard/server.py @@ -28,16 +28,13 @@ from openspace.skill_engine.evolution import ( from openspace.skill_engine.triggers import TriggerStore from openspace.skill_engine.types import SkillRecord +from openspace.config.constants import get_workflow_roots + API_PREFIX = "/api/v1" PACKAGE_ROOT = Path(__file__).resolve().parents[2] FRONTEND_DIST_DIR = PROJECT_ROOT / "apps" / "dashboard" / "dist" PACKAGED_DASHBOARD_STATIC_DIR = PACKAGE_ROOT / "packaged" / "dashboard" -WORKFLOW_ROOTS = [ - PROJECT_ROOT / "logs" / "recordings", - PROJECT_ROOT / "logs" / "trajectories", - PROJECT_ROOT / "benchmarks" / "gdpval" / "results", - PROJECT_ROOT / "benchmarks" / "terminal_bench" / "runs", -] +WORKFLOW_ROOTS = get_workflow_roots() PIPELINE_STAGES = [ { diff --git a/tests/config/test_data_home.py b/tests/config/test_data_home.py index 8df7000..dea349a 100644 --- a/tests/config/test_data_home.py +++ b/tests/config/test_data_home.py @@ -122,3 +122,34 @@ def test_package_version_matches_pyproject() -> None: match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE) assert match is not None assert openspace.__version__ == match.group(1) + + +def test_get_workflow_roots_include_data_home_logs( + tmp_path: Path, monkeypatch +) -> None: + monkeypatch.setenv("OPENSPACE_HOME", str(tmp_path / "data")) + monkeypatch.delenv("OPENSPACE_DATA_DIR", raising=False) + monkeypatch.setattr(constants, "is_source_checkout", lambda root=None: False) + + roots = constants.get_workflow_roots() + data_home = (tmp_path / "data").resolve() + assert data_home / "logs" / "recordings" in [p.resolve() for p in roots] + assert data_home / "logs" / "trajectories" in [p.resolve() for p in roots] + assert not any("benchmarks" in str(p) for p in roots) + + +def test_get_workflow_roots_include_repo_paths_in_checkout( + tmp_path: Path, monkeypatch +) -> None: + (tmp_path / "pyproject.toml").write_text('[project]\nname="openspace"\n', encoding="utf-8") + pkg = tmp_path / "openspace" + pkg.mkdir() + (pkg / "__init__.py").write_text("", encoding="utf-8") + monkeypatch.setenv("OPENSPACE_HOME", str(tmp_path / "data")) + monkeypatch.delenv("OPENSPACE_DATA_DIR", raising=False) + monkeypatch.setattr(constants, "PROJECT_ROOT", tmp_path) + monkeypatch.setattr(constants, "is_source_checkout", lambda root=None: True) + + roots = [str(p) for p in constants.get_workflow_roots()] + assert any(str(tmp_path / "benchmarks" / "gdpval" / "results") == r for r in roots) + assert any("recordings" in r for r in roots)