fffiloni commited on
Commit
128b2df
·
verified ·
1 Parent(s): 0f17186

Upload 6 files

Browse files
Files changed (3) hide show
  1. CHANGELOG.md +8 -16
  2. README.md +15 -14
  3. app.py +85 -7
CHANGELOG.md CHANGED
@@ -1,18 +1,10 @@
1
- # v164Premium Timeline Refactor
2
 
3
- - Reworked the Active Run timeline as a premium, single-row milestone system with clockwise partial rings for grouped substeps.
4
- - Renamed user-facing timeline labels where helpful while retaining worker-step precision underneath.
5
- - Kept Agent recovery and Run traces roles from v158/v159; v120 Agent recovery remains documented and visible as the operational recovery summary.
6
- - Reduced redundant terminal Failure milestone: precise failed stages are marked directly, with Failure only as a fallback terminal anchor.
 
 
7
 
8
- Validation: 452 tests pass.
9
-
10
- # v163 — Section Harmony Audit
11
-
12
- - Reviewed the one-page UI section by section for state ownership, event-handler conflicts and cross-panel interference.
13
- - Fixed duplicate DOM IDs in the support/compatibility area so hidden legacy elements no longer shadow visible Active Run hardware fields.
14
- - Separated Space Test's visible “Started at” field from the Active Run polling compatibility field. Active Run progress no longer writes into the Space Test timestamp.
15
- - Hardened Run traces document state so backend-provided `present: true` document links are not downgraded when a light payload lacks a `files` listing.
16
- - Added regression tests covering unique DOM IDs, Active Run vs Space Test timestamp separation, hidden-field compatibility boundaries, and stable Run traces document presence.
17
-
18
- Validation: 448 tests pass.
 
1
+ # v171delete Space option, ghost-run cleanup, storage polish
2
 
3
+ - Added an opt-in checkbox in the premium delete modal to also delete the associated Hugging Face Space for build runs.
4
+ - Extended the DELETE run API to accept `{ "delete_space": true }`, delete the Space repo in the authenticated user's namespace, and return an explicit Space deletion report.
5
+ - Kept Space deletion disabled for validation runs to avoid deleting existing Spaces that were only tested.
6
+ - Made incomplete/unknown ghost runs deletable from Run Explorer so stale bucket prefixes can be cleaned up.
7
+ - Polished the Run Storage card error state with compact, user-readable messages instead of dumping long bucket HTTP errors into the card.
8
+ - Preserved the v170 real recursive bucket deletion behavior.
9
 
10
+ Validation: `478 passed, 2 warnings`.
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,21 +1,14 @@
1
- ---
2
- title: Agentic Space Factory
3
- sdk: docker
4
- hf_oauth: true
5
- hf_oauth_expiration_minutes: 480
6
- hf_oauth_scopes:
7
- - read-repos
8
- - write-repos
9
- - manage-repos
10
- - gated-repos
11
- - inference-api
12
- - jobs
13
- - read-billing
14
- ---
15
 
 
 
 
16
 
17
  # Agentic Space Factory
18
 
 
 
 
19
  - **v161** fixes duplicate delete modal triggers and stuck `Deleting…` states with single-flight delete guards.
20
 
21
  ---
@@ -261,3 +254,11 @@ Run traces now renders as a horizontal document dock: compact source-file icons
261
  ## v164 timeline
262
 
263
  The Active Run timeline uses compact product milestones backed by exact worker steps. Grouped milestones expose clockwise partial progress rings and substep counts, while recovery details remain in Agent recovery and source files remain in Run traces.
 
 
 
 
 
 
 
 
 
1
+ ## v170 — Real bucket recursive run deletion
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ - DELETE `/api/runs/{run_id}` now removes concrete bucket objects under `runs/<run_id>/` using recursive discovery instead of relying only on virtual-folder deletion.
4
+ - The API returns a cleanup report with deleted and remaining object counts.
5
+ - Frontend cache invalidation remains as a protection layer, but deletion is now a real backend bucket cleanup.
6
 
7
  # Agentic Space Factory
8
 
9
+ - **v170** fixes run deletion so the backend recursively removes all concrete Bucket objects for the run prefix.
10
+ - **v168** refreshes Pi assistant model choices and surfaces requested/effective provider model routing in Active Run.
11
+ - **v165** fixes fresh-install recovery failures, README metadata preservation, timeline failure anchoring, premium pre-scan/progress polish, and broader Pi assistant model choices.
12
  - **v161** fixes duplicate delete modal triggers and stuck `Deleting…` states with single-flight delete guards.
13
 
14
  ---
 
254
  ## v164 timeline
255
 
256
  The Active Run timeline uses compact product milestones backed by exact worker steps. Grouped milestones expose clockwise partial progress rings and substep counts, while recovery details remain in Agent recovery and source files remain in Run traces.
257
+
258
+ ### v167 note: run artifact manifest
259
+
260
+ The worker writes `runs/<run_id>/artifact_manifest.json` as the source of truth for run artifacts. This lets the API expose Run traces from what the worker actually wrote, including failed runs where the Space was created but later entered build/runtime error.
261
+
262
+ ## v171 notes
263
+
264
+ Run deletion now supports an explicit opt-in option to delete the associated generated Space for build runs. Validation run deletion remains run-artifact-only by default. The Run Storage card also summarizes bucket setup errors so fresh installs remain visually clean.
app.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any
8
  import gradio as gr
9
  from fastapi import FastAPI, HTTPException, Request
10
  from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
11
- from huggingface_hub import attach_huggingface_oauth
12
 
13
  from src.bucket import RunPaths, check_user_bucket, create_user_bucket, delete_run_folder, read_run_bundle, list_recent_runs, write_json, write_launch_metadata
14
  from src.config import settings, user_bucket_source
@@ -562,13 +562,33 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
562
  ctx = _oauth_context_from_request(request)
563
  run_id = validate_run_id(run_id)
564
  bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
 
565
  try:
566
- delete_run_folder(run_id, bucket_source=bucket_source, token=ctx["token"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  except FileNotFoundError:
568
- pass
569
  except Exception as exc: # noqa: BLE001
570
  raise HTTPException(status_code=400, detail=redact(str(exc))) from exc
571
- return JSONResponse({"ok": True, "run_id": run_id, "deleted": True})
572
 
573
  @fastapi_app.get("/api/runs/{run_id}/progress")
574
  async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name, include_job_logs: bool = False): # type: ignore[no-untyped-def]
@@ -905,7 +925,7 @@ def build_demo() -> gr.Blocks:
905
  with gr.Tab("Build from model card"):
906
  gr.Markdown(
907
  """
908
- Paste a Hugging Face model ID or model-card URL. The worker creates a **private** Space, asks Pi + Qwen Coder to build the best Gradio app it can, attempts ZeroGPU first, then a fixed-GPU fallback if enabled. If automatic hardware assignment fails, set the hardware manually in the generated Space settings and run the validation tab.
909
  """
910
  )
911
  with gr.Row():
@@ -921,10 +941,19 @@ Paste a Hugging Face model ID or model-card URL. The worker creates a **private*
921
  placeholder="e.g. space-factory-z-image-v1",
922
  info="Use a fresh name. The Space is created under your username and remains private.",
923
  )
924
- pi_model = gr.Textbox(
925
  label="Pi model",
 
 
 
 
 
 
 
 
926
  value="Qwen/Qwen3-Coder-Next",
927
- info="Model used by Pi through Hugging Face Inference Providers.",
 
928
  )
929
  implementation_mode = gr.Dropdown(
930
  label="Build goal",
@@ -1074,6 +1103,55 @@ It cannot guarantee that every model card becomes a working Space. It cannot byp
1074
  return demo
1075
 
1076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
  def create_app() -> FastAPI:
1078
  """Create the product FastAPI app.
1079
 
 
8
  import gradio as gr
9
  from fastapi import FastAPI, HTTPException, Request
10
  from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
11
+ from huggingface_hub import HfApi, attach_huggingface_oauth
12
 
13
  from src.bucket import RunPaths, check_user_bucket, create_user_bucket, delete_run_folder, read_run_bundle, list_recent_runs, write_json, write_launch_metadata
14
  from src.config import settings, user_bucket_source
 
562
  ctx = _oauth_context_from_request(request)
563
  run_id = validate_run_id(run_id)
564
  bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
565
+ body: dict[str, Any] = {}
566
  try:
567
+ body = await request.json()
568
+ if not isinstance(body, dict):
569
+ body = {}
570
+ except Exception:
571
+ body = {}
572
+ delete_space = bool(body.get("delete_space"))
573
+ bundle: dict[str, Any] = {}
574
+ space_report: dict[str, Any] = {"space_delete_requested": delete_space, "space_deleted": False}
575
+ if delete_space:
576
+ try:
577
+ bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"], include_heavy=False)
578
+ associated_space = _associated_space_from_bundle(bundle)
579
+ kind = str((bundle.get("summary") or {}).get("kind") or (bundle.get("state") or {}).get("kind") or (bundle.get("launch") or {}).get("kind") or "").lower()
580
+ if "validation" in kind or "space_test" in kind:
581
+ raise PermissionError("Associated Space deletion is not available for validation runs.")
582
+ space_report = _delete_associated_space(associated_space, username=ctx["username"], token=ctx["token"])
583
+ except Exception as exc: # noqa: BLE001
584
+ space_report = {"space_delete_requested": True, "space_deleted": False, "space_delete_error": redact(str(exc))}
585
+ try:
586
+ delete_report = delete_run_folder(run_id, bucket_source=bucket_source, token=ctx["token"])
587
  except FileNotFoundError:
588
+ delete_report = {"matched_count": 0, "deleted_count": 0, "remaining_count": 0}
589
  except Exception as exc: # noqa: BLE001
590
  raise HTTPException(status_code=400, detail=redact(str(exc))) from exc
591
+ return JSONResponse({"ok": True, "run_id": run_id, "deleted": True, **delete_report, **space_report})
592
 
593
  @fastapi_app.get("/api/runs/{run_id}/progress")
594
  async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name, include_job_logs: bool = False): # type: ignore[no-untyped-def]
 
925
  with gr.Tab("Build from model card"):
926
  gr.Markdown(
927
  """
928
+ Paste a Hugging Face model ID or model-card URL. The worker creates a **private** Space, asks Pi + the selected coding assistant to build the best Gradio app it can, attempts ZeroGPU first, then a fixed-GPU fallback if enabled. If automatic hardware assignment fails, set the hardware manually in the generated Space settings and run the validation tab.
929
  """
930
  )
931
  with gr.Row():
 
941
  placeholder="e.g. space-factory-z-image-v1",
942
  info="Use a fresh name. The Space is created under your username and remains private.",
943
  )
944
+ pi_model = gr.Dropdown(
945
  label="Pi model",
946
+ choices=[
947
+ "Qwen/Qwen3-Coder-Next",
948
+ "moonshotai/Kimi-K2-Instruct-0905",
949
+ "zai-org/GLM-4.7",
950
+ "zai-org/GLM-4.5-Air",
951
+ "deepseek-ai/DeepSeek-V3.2",
952
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct",
953
+ ],
954
  value="Qwen/Qwen3-Coder-Next",
955
+ allow_custom_value=True,
956
+ info="Assistant model used by Pi through Hugging Face Inference Providers.",
957
  )
958
  implementation_mode = gr.Dropdown(
959
  label="Build goal",
 
1103
  return demo
1104
 
1105
 
1106
+
1107
+ def _first_non_empty(*values: Any) -> str:
1108
+ for value in values:
1109
+ text = str(value or "").strip()
1110
+ if text:
1111
+ return text
1112
+ return ""
1113
+
1114
+
1115
+ def _associated_space_from_bundle(bundle: dict[str, Any]) -> str:
1116
+ summary = bundle.get("summary") or {}
1117
+ summary_file = bundle.get("summary_file") or {}
1118
+ launch = bundle.get("launch") or {}
1119
+ state = bundle.get("state") or {}
1120
+ links = bundle.get("links") or {}
1121
+ target = _first_non_empty(
1122
+ summary.get("target_space"),
1123
+ summary_file.get("target_space"),
1124
+ launch.get("target_space"),
1125
+ state.get("target_space"),
1126
+ links.get("target_space"),
1127
+ )
1128
+ if target:
1129
+ return target.replace("https://huggingface.co/spaces/", "").strip("/")
1130
+ url = _first_non_empty(summary.get("target_space_url"), summary_file.get("target_space_url"), launch.get("target_space_url"), state.get("target_space_url"), links.get("target_space_url"))
1131
+ marker = "huggingface.co/spaces/"
1132
+ if marker in url:
1133
+ return url.split(marker, 1)[1].split("?", 1)[0].split("#", 1)[0].strip("/")
1134
+ return ""
1135
+
1136
+
1137
+ def _delete_associated_space(space_id: str, *, username: str, token: str) -> dict[str, Any]:
1138
+ cleaned = str(space_id or "").strip().strip("/")
1139
+ if not cleaned or "/" not in cleaned:
1140
+ raise ValueError("No associated Space id is available for this run.")
1141
+ owner = cleaned.split("/", 1)[0]
1142
+ if owner != username:
1143
+ raise PermissionError(f"Refusing to delete Space {cleaned}: it is not in your namespace.")
1144
+ api = HfApi(token=token)
1145
+ try:
1146
+ api.delete_repo(repo_id=cleaned, repo_type="space")
1147
+ return {"space_delete_requested": True, "space_deleted": True, "space_id": cleaned}
1148
+ except Exception as exc: # noqa: BLE001
1149
+ message = redact(str(exc))
1150
+ missing_markers = ("404", "not found", "Repository Not Found", "does not exist")
1151
+ if any(marker.lower() in message.lower() for marker in missing_markers):
1152
+ return {"space_delete_requested": True, "space_deleted": False, "space_already_missing": True, "space_id": cleaned, "space_delete_error": message}
1153
+ return {"space_delete_requested": True, "space_deleted": False, "space_id": cleaned, "space_delete_error": message}
1154
+
1155
  def create_app() -> FastAPI:
1156
  """Create the product FastAPI app.
1157