Use native S-expression for recipe/plan display
- Display recipe's original S-expression when available (code is data) - Fall back to generating S-expression from plan for legacy JSON - Run service now prefers .sexp plan files over .json - Add get_run_plan_sexp() for direct S-expression access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,6 +178,7 @@ async def get_run(
|
||||
|
||||
# Try to load the recipe to show the plan
|
||||
plan = None
|
||||
plan_sexp = None # Native S-expression if available
|
||||
recipe_id = run.get("recipe")
|
||||
if recipe_id and len(recipe_id) == 64: # Looks like a hash
|
||||
try:
|
||||
@@ -185,7 +186,11 @@ async def get_run(
|
||||
recipe_service = RecipeService(get_redis_client(), get_cache_manager())
|
||||
recipe = await recipe_service.get_recipe(recipe_id)
|
||||
if recipe:
|
||||
# Use the new build_dag method if available
|
||||
# Use native S-expression if available (code is data!)
|
||||
if recipe.get("sexp"):
|
||||
plan_sexp = recipe["sexp"]
|
||||
|
||||
# Build steps for DAG visualization
|
||||
dag = recipe.get("dag", {})
|
||||
nodes = dag.get("nodes", [])
|
||||
|
||||
@@ -311,8 +316,9 @@ async def get_run(
|
||||
}
|
||||
})
|
||||
|
||||
# Generate S-expression representation of the plan
|
||||
plan_sexp = plan_to_sexp(plan, run.get("recipe_name"))
|
||||
# Use native S-expression if available, otherwise generate from plan
|
||||
if not plan_sexp and plan:
|
||||
plan_sexp = plan_to_sexp(plan, run.get("recipe_name"))
|
||||
|
||||
templates = get_templates(request)
|
||||
return render(templates, "runs/detail.html", request,
|
||||
|
||||
@@ -482,10 +482,28 @@ class RunService:
|
||||
|
||||
async def get_run_plan(self, run_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get execution plan for a run."""
|
||||
plan_path = self.cache_dir / "plans" / f"{run_id}.json"
|
||||
if plan_path.exists():
|
||||
with open(plan_path) as f:
|
||||
return json.load(f)
|
||||
# Prefer S-expression plan
|
||||
sexp_path = self.cache_dir / "plans" / f"{run_id}.sexp"
|
||||
if sexp_path.exists():
|
||||
with open(sexp_path) as f:
|
||||
return {"sexp": f.read(), "format": "sexp"}
|
||||
|
||||
# Fall back to JSON for legacy plans
|
||||
json_path = self.cache_dir / "plans" / f"{run_id}.json"
|
||||
if json_path.exists():
|
||||
with open(json_path) as f:
|
||||
plan = json.load(f)
|
||||
plan["format"] = "json"
|
||||
return plan
|
||||
|
||||
return None
|
||||
|
||||
async def get_run_plan_sexp(self, run_id: str) -> Optional[str]:
|
||||
"""Get execution plan as S-expression string."""
|
||||
sexp_path = self.cache_dir / "plans" / f"{run_id}.sexp"
|
||||
if sexp_path.exists():
|
||||
with open(sexp_path) as f:
|
||||
return f.read()
|
||||
return None
|
||||
|
||||
async def get_run_artifacts(self, run_id: str) -> List[Dict[str, Any]]:
|
||||
|
||||
@@ -156,15 +156,17 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Plan S-expression -->
|
||||
<!-- Recipe/Plan S-expression -->
|
||||
{% if plan_sexp %}
|
||||
<details class="mt-6" open>
|
||||
<summary class="cursor-pointer text-gray-400 hover:text-white text-sm mb-2">
|
||||
Plan (S-expression)
|
||||
Recipe (S-expression)
|
||||
</summary>
|
||||
<div class="bg-gray-900 rounded-lg border border-gray-700 p-4 overflow-x-auto">
|
||||
<pre class="text-sm font-mono sexp-code">{{ plan_sexp }}</pre>
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.sexp-code {
|
||||
|
||||
Reference in New Issue
Block a user