fffiloni commited on
Commit
a2ac6c1
·
verified ·
1 Parent(s): 7c7f084

Upload 6 files

Browse files
Files changed (3) hide show
  1. CHANGELOG.md +8 -0
  2. README.md +5 -0
  3. app.py +26 -2
CHANGELOG.md CHANGED
@@ -210,3 +210,11 @@
210
  - Persisted `launch.json`, `summary.json`, and a minimal `state.json` immediately after a Job launch, so running Jobs appear in the Run Explorer before the worker writes final artifacts.
211
  - Progress polling now falls back to launch metadata when `state.json`/`events.jsonl` are not ready yet.
212
  - Job links are preserved or reconstructed from launch metadata for active and historical runs.
 
 
 
 
 
 
 
 
 
210
  - Persisted `launch.json`, `summary.json`, and a minimal `state.json` immediately after a Job launch, so running Jobs appear in the Run Explorer before the worker writes final artifacts.
211
  - Progress polling now falls back to launch metadata when `state.json`/`events.jsonl` are not ready yet.
212
  - Job links are preserved or reconstructed from launch metadata for active and historical runs.
213
+
214
+ ## v43 — Canonical run view model + mockup-oriented dashboard
215
+
216
+ - Added a canonical Run View Model that normalizes heterogeneous worker/bucket statuses into stable product states.
217
+ - Added `/api/runs/resumable` and `/api/runs/{run_id}/view` endpoints for bucket-led recovery and UI rendering.
218
+ - Enriched `/api/runs/{run_id}/progress` with the new `view` payload while preserving the previous progress contract.
219
+ - Reworked the custom UI toward the product mockup: sidebar, run summary header, product pipeline stepper, agent activity feed, diagnostics panel, and Space Test preview.
220
+ - Added explicit tests for running, manual hardware, success, stale, and resumable run selection states.
README.md CHANGED
@@ -173,3 +173,8 @@ The Run Explorer reads launch metadata, summary metadata, state, gate files, and
173
 
174
  The live progress timeline is designed to avoid horizontal scrolling and uses explicit step states: completed, active, pending, and failed. Historical run Job links are reconstructed from `job_id` when possible, so the Job button should remain available even when older summaries did not store a full `job_url`.
175
 
 
 
 
 
 
 
173
 
174
  The live progress timeline is designed to avoid horizontal scrolling and uses explicit step states: completed, active, pending, and failed. Historical run Job links are reconstructed from `job_id` when possible, so the Job button should remain available even when older summaries did not store a full `job_url`.
175
 
176
+
177
+
178
+ ## v45 Simple Custom Dashboard
179
+
180
+ This version keeps the working Agentic Space Factory workflows and presents them in a simpler custom interface: Build on the left, Runs & Progress on the right, plus a full Run Explorer and Space Test view.
app.py CHANGED
@@ -20,6 +20,7 @@ from src.jobs import (
20
  )
21
  from src.runs import make_run_id, validate_run_id
22
  from src.progress import progress_from_events
 
23
  from src.security import redact
24
 
25
 
@@ -149,7 +150,7 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
149
  return JSONResponse(
150
  {
151
  "name": "Agentic Space Factory",
152
- "version": "v42-tabbed-runs-recovery",
153
  "bucket_default": settings.bucket_name,
154
  "workflows": ["build_from_model_card", "validate_existing_space", "runs_explorer"],
155
  "custom_ui_status": "root_custom_ui",
@@ -317,13 +318,35 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
317
  }
318
  )
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  @fastapi_app.get("/api/runs/{run_id}")
321
  async def api_run_detail(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
322
  ctx = _oauth_context_from_request(request)
323
  run_id = validate_run_id(run_id)
324
  bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
325
  bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
326
- return JSONResponse({"run_id": run_id, "bucket_source": bucket_source, **bundle})
 
 
 
 
 
 
 
 
 
327
 
328
  @fastapi_app.get("/api/runs/{run_id}/progress")
329
  async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
@@ -348,6 +371,7 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
348
  "hardware_strategy": bundle.get("hardware_strategy") or {},
349
  "technical_blockers": bundle.get("technical_blockers") or {},
350
  "events": events[-20:],
 
351
  "links": _api_links(
352
  run_id=run_id,
353
  bucket_source=bucket_source,
 
20
  )
21
  from src.runs import make_run_id, validate_run_id
22
  from src.progress import progress_from_events
23
+ from src.view_models import build_run_view_model, find_latest_resumable_run
24
  from src.security import redact
25
 
26
 
 
150
  return JSONResponse(
151
  {
152
  "name": "Agentic Space Factory",
153
+ "version": "v45-simple-custom-dashboard",
154
  "bucket_default": settings.bucket_name,
155
  "workflows": ["build_from_model_card", "validate_existing_space", "runs_explorer"],
156
  "custom_ui_status": "root_custom_ui",
 
318
  }
319
  )
320
 
321
+ @fastapi_app.get("/api/runs/resumable")
322
+ async def api_resumable_run(request: Request, bucket_name: str = settings.bucket_name, limit: int = 100): # type: ignore[no-untyped-def]
323
+ ctx = _oauth_context_from_request(request)
324
+ bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
325
+ runs = list_recent_runs(bucket_source=bucket_source, token=ctx["token"], limit=limit)
326
+ resumable = find_latest_resumable_run(runs)
327
+ if not resumable:
328
+ return JSONResponse({"run": None, "view": None, "bucket_source": bucket_source, "reason": "no_resumable_run"})
329
+ run_id = validate_run_id(str(resumable.get("run_id")))
330
+ bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
331
+ view = build_run_view_model(run_id, bundle, bucket_source=bucket_source)
332
+ return JSONResponse({"run": resumable, "view": view, "bucket_source": bucket_source, "reason": "latest_resumable_run"})
333
+
334
  @fastapi_app.get("/api/runs/{run_id}")
335
  async def api_run_detail(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
336
  ctx = _oauth_context_from_request(request)
337
  run_id = validate_run_id(run_id)
338
  bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
339
  bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
340
+ view = build_run_view_model(run_id, bundle, bucket_source=bucket_source)
341
+ return JSONResponse({"run_id": run_id, "bucket_source": bucket_source, "view": view, **bundle})
342
+
343
+ @fastapi_app.get("/api/runs/{run_id}/view")
344
+ async def api_run_view(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
345
+ ctx = _oauth_context_from_request(request)
346
+ run_id = validate_run_id(run_id)
347
+ bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
348
+ bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
349
+ return JSONResponse(build_run_view_model(run_id, bundle, bucket_source=bucket_source))
350
 
351
  @fastapi_app.get("/api/runs/{run_id}/progress")
352
  async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
 
371
  "hardware_strategy": bundle.get("hardware_strategy") or {},
372
  "technical_blockers": bundle.get("technical_blockers") or {},
373
  "events": events[-20:],
374
+ "view": build_run_view_model(run_id, bundle, bucket_source=bucket_source),
375
  "links": _api_links(
376
  run_id=run_id,
377
  bucket_source=bucket_source,