diff --git a/backend/apps/agents/schedule_mcp_server.py b/backend/apps/agents/schedule_mcp_server.py index 001f29df..5a75a603 100644 --- a/backend/apps/agents/schedule_mcp_server.py +++ b/backend/apps/agents/schedule_mcp_server.py @@ -122,6 +122,43 @@ TOOLS = [ "required": ["workflow_id"], }, }, + { + "name": "EditWorkflowStep", + "description": ( + "Edit a single step's prompt text on an existing workflow. Use " + "when the user has accepted a proposed change during an Edit " + "Agent conversation; the new prompt replaces the existing one " + "and persists immediately. The next scheduled run uses the new " + "version. Always confirm the change with the user before " + "calling this; AskUserQuestion FIRST if there is any ambiguity." + ), + "inputSchema": { + "type": "object", + "properties": { + "workflow_id": {"type": "string", "description": "The workflow to edit."}, + "step_idx": {"type": "integer", "description": "0-based index of the step to modify."}, + "new_text": {"type": "string", "description": "Full replacement prompt text for the step."}, + }, + "required": ["workflow_id", "step_idx", "new_text"], + }, + }, + { + "name": "TestWorkflow", + "description": ( + "Spawn a sibling Test Agent that runs the workflow end-to-end " + "(with the latest persisted steps) so the user can watch it " + "work. Use after editing a step to verify the change. The Test " + "Agent renders as a sibling card on the dashboard with a " + "'Testing' arrow chip linking back to this workflow." + ), + "inputSchema": { + "type": "object", + "properties": { + "workflow_id": {"type": "string", "description": "The workflow to test."}, + }, + "required": ["workflow_id"], + }, + }, ] @@ -286,6 +323,42 @@ def _err(text: str) -> dict: return {"content": [{"type": "text", "text": f"Error: {text}"}], "isError": True} +def handle_edit_step(args: dict) -> dict: + wid = args.get("workflow_id") or "" + if not wid: + return _err("workflow_id is required.") + try: + idx = int(args.get("step_idx")) + except (TypeError, ValueError): + return _err("step_idx must be an integer.") + new_text = (args.get("new_text") or "").strip() + if not new_text: + return _err("new_text is required.") + cur = _call("GET", f"/{wid}") + if "_error" in cur: + return _err(cur["_error"]) + steps = cur.get("steps") or [] + if idx < 0 or idx >= len(steps): + return _err(f"step_idx {idx} out of range (workflow has {len(steps)} steps).") + new_steps = list(steps) + new_steps[idx] = {**new_steps[idx], "text": new_text} + r = _call("PATCH", f"/{wid}", {"steps": new_steps}) + if "_error" in r: + return _err(r["_error"]) + return _ok(f"Step {idx + 1} updated. The next run uses the new prompt.") + + +def handle_test_workflow(args: dict) -> dict: + wid = args.get("workflow_id") or "" + if not wid: + return _err("workflow_id is required.") + r = _call("POST", f"/{wid}/test-run", {}) + if "_error" in r: + return _err(r["_error"]) + sid = r.get("session_id", "") + return _ok(f"Test Agent spawned (session {sid[:8]}...). It runs the latest workflow on the dashboard with a Testing arrow chip.") + + HANDLERS = { "ScheduleWorkflow": handle_schedule_workflow, "ListScheduledWorkflows": handle_list, @@ -294,6 +367,8 @@ HANDLERS = { "PauseAllWorkflows": handle_pause_all, "ResumeAllWorkflows": handle_resume_all, "RunWorkflowNow": handle_run_now, + "EditWorkflowStep": handle_edit_step, + "TestWorkflow": handle_test_workflow, } diff --git a/backend/apps/workflows/workflows.py b/backend/apps/workflows/workflows.py index 50ff8954..282737d4 100644 --- a/backend/apps/workflows/workflows.py +++ b/backend/apps/workflows/workflows.py @@ -552,18 +552,23 @@ async def edit_agent_session(workflow_id: str): from backend.apps.agents.agent_manager import agent_manager steps_lines = "\n".join(f"{i+1}. {(s.label or '').strip() or (s.text or '')[:60]}\n Prompt: {s.text}" for i, s in enumerate(wf.steps)) system_prompt = ( - f"You are the Edit Agent for the user's saved workflow \"{wf.title}\". " - f"Help the user iterate on it. The workflow's purpose: {wf.description or '(unspecified)'}.\n\n" + f"You are the Edit Agent for the user's saved workflow \"{wf.title}\" " + f"(id: {wf.id}). Help the user iterate on it. The workflow's purpose: " + f"{wf.description or '(unspecified)'}.\n\n" f"Current steps:\n{steps_lines}\n\n" - "When the user asks for a change, briefly confirm what you'll do, " - "then either edit the prompt text or test the workflow before suggesting " - "they Save. You have access to the full tool surface (Read, Edit, Write, " - "Bash, MCP servers, etc.) so you can run searches, look at files, or " - "activate integrations the user already has connected. To test the workflow " - "end-to-end, call TestWorkflow (it spawns a sibling Test Agent that runs " - "the latest draft). When you've made a concrete prompt change you're " - "confident about, tell the user clearly so they can apply it via the Save " - "button at the top of the card." + "How to work:\n" + "1. When the user describes a change, briefly confirm what you'll do.\n" + "2. If you need to look at files / search / activate an MCP / etc. to " + "verify your idea, use your tools.\n" + "3. Call EditWorkflowStep(workflow_id, step_idx, new_text) to apply a " + "prompt change to a specific step. The change persists immediately. " + "Confirm with the user via AskUserQuestion FIRST if there's any " + "ambiguity about what they want.\n" + "4. Call TestWorkflow(workflow_id) to spawn a sibling Test Agent that " + "runs the latest version end-to-end. Use this after a change to verify " + "it works.\n\n" + "Be brief in your replies. Don't restate the whole workflow back; the " + "user can see it. Just confirm what changed and what you're doing." ) config = AgentConfig( name=f"Edit Agent: {wf.title}",