Skip to content

Commit 43c25eb

Browse files
feat(home): show probing interpreter on family snapshot (#59)
* feat(home): show probing interpreter on family snapshot The "Family snapshot" tile sometimes renders "Not installed" for a package that is installed in a venv — the dashboard is running under a different Python. Surface sys.executable and the Python version under the section heading so the env mismatch is self-diagnosing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: extract sys.version_info local to satisfy ruff E501 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 224783f commit 43c25eb

5 files changed

Lines changed: 50 additions & 3 deletions

File tree

sidecar/attune_gui/home_summary.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class HomeSummary:
8484
sparkline_points: str = "" # SVG polyline points string ("" = no data)
8585
recent_jobs: list[RecentJob] = field(default_factory=list)
8686
family: list[FamilyVersion] = field(default_factory=list)
87+
family_interpreter: str | None = None # sys.executable of dashboard process
88+
family_python_version: str | None = None # e.g. "3.11.7"
8789
workspace_path: str | None = None
8890
manifest_path: str | None = None
8991
feature_count: int = 0
@@ -236,9 +238,13 @@ async def build_home_summary() -> HomeSummary:
236238
logger.debug("home: cowork_templates.list_templates failed; using empty list")
237239

238240
layers: dict[str, dict[str, Any]] = {}
241+
interpreter: str | None = None
242+
python_version: str | None = None
239243
try:
240244
layer_data = await cowork_health.layer_health()
241245
layers = layer_data.get("layers", {})
246+
interpreter = layer_data.get("interpreter")
247+
python_version = layer_data.get("python_version")
242248
except Exception: # noqa: BLE001
243249
logger.debug("home: cowork_health.layer_health failed; using empty list")
244250

@@ -277,6 +283,8 @@ async def build_home_summary() -> HomeSummary:
277283
sparkline_points=points,
278284
recent_jobs=recent,
279285
family=family,
286+
family_interpreter=interpreter,
287+
family_python_version=python_version,
280288
workspace_path=workspace_path,
281289
manifest_path=manifest_path,
282290
feature_count=feature_count,

sidecar/attune_gui/routes/cowork_health.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from __future__ import annotations
1111

1212
import importlib.metadata as ilm
13+
import sys
1314
from typing import Any
1415

1516
from fastapi import APIRouter
@@ -35,8 +36,19 @@ def _probe(pkg: str) -> dict[str, Any]:
3536

3637
@router.get("/layers")
3738
async def layer_health() -> dict[str, Any]:
38-
"""Return version + importability for each attune layer."""
39-
return {"layers": {key: _probe(pkg) for key, pkg in _PACKAGES}}
39+
"""Return version + importability for each attune layer.
40+
41+
Also surfaces the interpreter probing for metadata — a "not installed"
42+
result is usually an env-mismatch (dashboard running under a different
43+
Python than the venv that has the package), so the interpreter path
44+
makes the situation self-diagnosing.
45+
"""
46+
vi = sys.version_info
47+
return {
48+
"layers": {key: _probe(pkg) for key, pkg in _PACKAGES},
49+
"interpreter": sys.executable,
50+
"python_version": f"{vi.major}.{vi.minor}.{vi.micro}",
51+
}
4052

4153

4254
@router.get("/corpus")

sidecar/attune_gui/templates/home.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ <h2 class="section-title">Workspace</h2>
123123

124124
<section class="section">
125125
<h2 class="section-title">Family snapshot</h2>
126+
{% if summary.family_interpreter %}
127+
<p class="meta-line">
128+
Probed under
129+
<code title="Dashboard process interpreter. A 'Not installed' result usually means this Python differs from the venv where the package lives.">{{ summary.family_interpreter }}</code>
130+
{% if summary.family_python_version %}(Python {{ summary.family_python_version }}){% endif %}
131+
</p>
132+
{% endif %}
126133
<div class="grid grid-4">
127134
{% for v in summary.family %}
128135
<article class="card layer-card {% if v.importable %}ok{% else %}danger{% endif %}">

sidecar/tests/test_cowork_health.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ def test_layers_returns_all_known_packages(client: TestClient) -> None:
2626
assert "version" in info
2727

2828

29+
def test_layers_includes_interpreter_diagnostic(client: TestClient) -> None:
30+
"""A 'not installed' result is usually env-mismatch; the response surfaces
31+
the interpreter so the dashboard is self-diagnosing."""
32+
r = client.get("/api/cowork/layers", headers={"Origin": "http://localhost:5173"})
33+
assert r.status_code == 200
34+
body = r.json()
35+
assert isinstance(body["interpreter"], str) and body["interpreter"]
36+
assert isinstance(body["python_version"], str)
37+
# "3.11.7" style
38+
assert body["python_version"].count(".") == 2
39+
40+
2941
def test_layers_handles_missing_package(
3042
client: TestClient, monkeypatch: pytest.MonkeyPatch
3143
) -> None:

sidecar/tests/test_home_summary.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,13 @@ async def test_build_home_summary_composes_all_sources():
209209
"templates_root": "/fake/help",
210210
}
211211
)
212-
fake_layers = AsyncMock(return_value={"layers": {"ai": {"importable": True, "version": "9"}}})
212+
fake_layers = AsyncMock(
213+
return_value={
214+
"layers": {"ai": {"importable": True, "version": "9"}},
215+
"interpreter": "/fake/.venv/bin/python",
216+
"python_version": "3.11.7",
217+
}
218+
)
213219
fake_corpus = AsyncMock(
214220
return_value={"manifest_path": "/fake/help/features.yaml", "feature_count": 4}
215221
)
@@ -241,6 +247,8 @@ async def test_build_home_summary_composes_all_sources():
241247
assert summary.manifest_path == "/fake/help/features.yaml"
242248
assert len(summary.family) == 1
243249
assert summary.family[0].package == "attune-ai"
250+
assert summary.family_interpreter == "/fake/.venv/bin/python"
251+
assert summary.family_python_version == "3.11.7"
244252
assert len(summary.recent_jobs) == 1
245253
assert summary.recent_jobs[0].name == "demo.run"
246254
assert len(summary.sparkline) == 7

0 commit comments

Comments
 (0)