diff --git a/README.md b/README.md index aad3c65..80c45bb 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Progi Monitoring gives you a live view of every running and completed task — s **4. Optimize as you go** -Tweak playbooks between runs. Because workflows live in a database and survive context resets, every future task picks up your changes automatically — your process gets sharper with each iteration. +Tweak playbooks in Progi Monitoring between runs. Because workflows live in a database and survive context resets, every future task picks up your changes automatically — your process gets sharper with each iteration. --- @@ -88,7 +88,6 @@ Tweak playbooks between runs. Because workflows live in a database and survive c | `get_playbook_authoring_prompt` | Return the Pass 2 system prompt for authoring a step's playbook (injects workflow context) | | `save_workflow` | Persist a new workflow, its steps, and playbooks | | `list_workflows` | Return all workflows with their ordered steps | -| `update_playbook` | Replace the playbook content for a step | Authoring is two passes: Pass 1 turns a plain-language description into a structured skeleton; Pass 2 authors each step's playbook. `save_workflow` persists both. diff --git a/docs/README.md b/docs/README.md index 9acfedf..1788700 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,21 +30,15 @@ Before I save this, a few questions: Questions would be specific to your use case of course. -Then, Progi saves the playbooks: +Then, Progi saves the workflow: ```md Now let me save the skeleton first, then I'll get the playbook authoring prompts for each step. Progi [save_workflow] -Progi [update_playbook] -Progi [update_playbook] -Progi [update_playbook] -... -Progi [update_playbook] - ``` -Finally, Progi saves your complete workflow: +Finally, Progi confirms your complete workflow: ``` Done. The Agentic Fix-Up workflow is saved with 4 steps and full playbooks. diff --git a/progi/db.py b/progi/db.py index fc75318..bb884bc 100644 --- a/progi/db.py +++ b/progi/db.py @@ -935,6 +935,39 @@ def get_workflow_with_playbooks(cfg: Config, workflow_id: int) -> dict[str, Any] } +def export_workflow(cfg: Config, workflow_id: int) -> dict[str, Any]: + """Return a workflow in the save_workflow-compatible export format.""" + wf = get_workflow_with_playbooks(cfg, workflow_id) + step_by_id = {s["id"]: s for s in wf["steps"]} + return { + "name": wf["name"], + "description": wf["description"], + "process": [ + { + "order": s["order"], + "name": s["name"], + "input_spec": s["input_spec"], + "output_spec": s["output_spec"], + } + for s in wf["steps"] + ], + "edges": [ + { + "from": step_by_id[e["from_step_id"]]["name"], + "to": step_by_id[e["to_step_id"]]["name"], + "condition": e["condition"], + "priority": e["priority"], + } + for e in wf["edges"] + ], + "playbooks": { + s["name"]: s["playbook"] + for s in wf["steps"] + if s["playbook"] is not None + }, + } + + def get_step_detail(cfg: Config, workflow_id: int, step_id: int) -> dict[str, Any]: """Return a single step with its playbook, and derived prev/next steps.""" engine = get_engine(cfg) diff --git a/progi/mcp_server.py b/progi/mcp_server.py index 7086fef..c45fc73 100644 --- a/progi/mcp_server.py +++ b/progi/mcp_server.py @@ -7,8 +7,7 @@ - **Work loop**: create_task, list_tasks, start_or_continue_task, update_progress_notes, submit_output. - **Workflow authoring**: get_process_skeleton_prompt, - get_playbook_authoring_prompt, save_workflow, list_workflows, - update_playbook. + get_playbook_authoring_prompt, save_workflow, list_workflows. IMPORTANT (stdio hygiene): when running over stdio, stdout is the MCP protocol channel. Never `print()` to stdout. Use logging configured to stderr (see @@ -235,11 +234,6 @@ def list_workflows() -> dict: return {"workflows": db.list_workflows(_cfg), "monitoring_url": _monitoring_url("/workflows")} -@mcp.tool(title="Update Playbook") -def update_playbook(step_id: int, content: str) -> dict: - """Replace the playbook content for a step.""" - return db.update_playbook(_cfg, step_id, content) - def run(cfg: Config | None = None) -> None: """Run the MCP server over stdio. Blocks until the client disconnects.""" diff --git a/progi/web/routers/workflows.py b/progi/web/routers/workflows.py index 6542394..d7fd591 100644 --- a/progi/web/routers/workflows.py +++ b/progi/web/routers/workflows.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json + from fastapi import APIRouter, Body, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse, Response from fastapi.templating import Jinja2Templates @@ -78,6 +80,21 @@ def step_detail(workflow_id: int, step_id: int, request: Request): ) +@router.get("/workflows/{workflow_id}/export") +def export_workflow(workflow_id: int, request: Request): + cfg = request.app.state.cfg + try: + data = db.export_workflow(cfg, workflow_id) + except ValueError as exc: + raise HTTPException(status_code=404, detail=str(exc)) + filename = data["name"].replace(" ", "_") + ".json" + return Response( + content=json.dumps(data, indent=2), + media_type="application/json", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + @router.patch("/workflows/{workflow_id}", response_class=JSONResponse) def rename_workflow( workflow_id: int, diff --git a/progi/web/templates/pages/workflows.html b/progi/web/templates/pages/workflows.html index e6b6056..61ef1a7 100644 --- a/progi/web/templates/pages/workflows.html +++ b/progi/web/templates/pages/workflows.html @@ -106,6 +106,11 @@ @click.stop="startRename(wf)" class="w-full text-left px-3 py-1.5 text-xs text-muted hover:bg-surface-1 hover:text-primary transition-colors" >Rename + Export JSON