diff --git a/src-tauri/src/core/agent_session_execution.rs b/src-tauri/src/core/agent_session_execution.rs index f1dbb980..9b5d7e0a 100644 --- a/src-tauri/src/core/agent_session_execution.rs +++ b/src-tauri/src/core/agent_session_execution.rs @@ -1021,17 +1021,17 @@ impl AgentSession { // meets the quality contract enforced by the system prompt. if artifact.title == "Implementation Plan" { return agent_error_result( - "update_plan requires a non-empty title that describes the change (not the default placeholder).", + "update_plan failed: missing a real \"title\". Pass all required fields as non-empty top-level arguments: title, summary, context, design, keyImplementation, steps, verification, risks. Minimal example: {\"title\": \"Add X to Y\", \"summary\": \"...\", \"context\": \"...\", \"design\": \"...\", \"keyImplementation\": \"...\", \"steps\": [\"...\"], \"verification\": \"...\", \"risks\": [\"...\"]}.", ); } if artifact.context.trim().is_empty() { return agent_error_result( - "update_plan requires a non-empty context section. Describe the current state of the relevant code with inspected file paths and confirmed facts.", + "update_plan failed: \"context\" is empty. Provide a non-empty top-level \"context\" string describing the current state of the relevant code with inspected file paths and confirmed facts.", ); } if artifact.design.trim().is_empty() { return agent_error_result( - "update_plan requires a non-empty design section. Explain the recommended approach, architecture changes, data flow, and tradeoffs.", + "update_plan failed: \"design\" is empty. Provide a non-empty top-level \"design\" string explaining the recommended approach, architecture changes, data flow, and tradeoffs.", ); } diff --git a/src-tauri/src/core/agent_session_tests.rs b/src-tauri/src/core/agent_session_tests.rs index 93c33137..1fcdfd4f 100644 --- a/src-tauri/src/core/agent_session_tests.rs +++ b/src-tauri/src/core/agent_session_tests.rs @@ -1944,14 +1944,33 @@ Used for prompt assembly coverage. assert!(properties.contains_key("verification")); assert!(properties.contains_key("assumptions")); - let nested_plan_properties = update_plan.parameters["properties"]["plan"]["properties"] - .as_object() - .expect("nested plan properties should be object"); - assert!(nested_plan_properties.contains_key("context")); - assert!(nested_plan_properties.contains_key("design")); - assert!(nested_plan_properties.contains_key("keyImplementation")); - assert!(nested_plan_properties.contains_key("verification")); - assert!(nested_plan_properties.contains_key("assumptions")); + assert!( + !properties.contains_key("plan"), + "update_plan should no longer expose a nested plan object" + ); + + let required = update_plan.parameters["required"] + .as_array() + .expect("update_plan required should be an array"); + let required: Vec<&str> = required + .iter() + .filter_map(serde_json::Value::as_str) + .collect(); + for field in [ + "title", + "summary", + "context", + "design", + "keyImplementation", + "steps", + "verification", + "risks", + ] { + assert!( + required.contains(&field), + "update_plan required should contain {field}" + ); + } } #[test] diff --git a/src-tauri/src/core/agent_session_tools.rs b/src-tauri/src/core/agent_session_tools.rs index 97966181..c2266685 100644 --- a/src-tauri/src/core/agent_session_tools.rs +++ b/src-tauri/src/core/agent_session_tools.rs @@ -272,6 +272,7 @@ Phase 4 — Call update_plan:\n\ - risks: main risks, edge cases, compatibility concerns, regression areas.\n\ - assumptions (optional): only non-blocking assumptions, not open questions.\n\n\ Prohibited: unresolved core ambiguities (use clarify first), TODO placeholders, vague steps, architecture guesses not backed by exploration, lengthy background essays without actionable information.\n\n\ +All required fields (title, summary, context, design, keyImplementation, steps, verification, risks) must be passed as non-empty top-level arguments. Do not wrap them in a nested object and never call this tool with empty arguments.\n\n\ You may call this tool multiple times in a run to incrementally refine the plan. Each call overwrites the previous version.", serde_json::json!({ "type": "object", @@ -310,49 +311,18 @@ You may call this tool multiple times in a run to incrementally refine the plan. "assumptions": { "type": "array", "items": { "type": "string" } - }, - "plan": { - "type": "object", - "description": "Optional nested plan payload. If provided, the runtime reads planning fields from this object.", - "properties": { - "title": { "type": "string" }, - "summary": { "type": "string" }, - "context": { "type": "string" }, - "design": { "type": "string" }, - "keyImplementation": { "type": "string" }, - "steps": { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { - "type": "object", - "properties": { - "id": { "type": "string" }, - "title": { "type": "string" }, - "description": { "type": "string" }, - "status": { "type": "string" }, - "files": { - "type": "array", - "items": { "type": "string" } - } - } - } - ] - } - }, - "verification": { "type": "string" }, - "risks": { - "type": "array", - "items": { "type": "string" } - }, - "assumptions": { - "type": "array", - "items": { "type": "string" } - } - } } - } + }, + "required": [ + "title", + "summary", + "context", + "design", + "keyImplementation", + "steps", + "verification", + "risks" + ] }), ), ]; diff --git a/src-tauri/src/core/plan_checkpoint.rs b/src-tauri/src/core/plan_checkpoint.rs index 0d70740f..a9898967 100644 --- a/src-tauri/src/core/plan_checkpoint.rs +++ b/src-tauri/src/core/plan_checkpoint.rs @@ -195,10 +195,7 @@ pub fn build_plan_artifact_from_tool_input( tool_input: &serde_json::Value, plan_revision: u32, ) -> PlanArtifact { - let root = tool_input - .get("plan") - .and_then(serde_json::Value::as_object) - .or_else(|| tool_input.as_object()); + let root = tool_input.as_object(); let title = root .and_then(|value| value.get("title"))