feat: AI marker system prompt fixes, TaskLane activation, and FlowPilot updates
- Fix system prompt to ensure [QUESTIONS]/[ACTIONS] markers in AI responses - Add format reminder injection to user messages for marker compliance - Wire TaskLane activation in prefill and resume paths - Add ActionCardGroup component for structured question/action rendering - Update FlowPilot session and step card components - Update ai-session schemas and types for marker data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,10 @@ Your response MUST be a valid JSON object with one of these shapes:
|
||||
{"type": "action", "content": "What to do", "reasoning": "Internal why", "context_message": "Here's what to try", "action_type": "instruction | script_generation | verification | info_request | open_script_builder", "expected_outcome": "What success looks like", "confidence": 0.78}
|
||||
|
||||
3. Resolution suggestion:
|
||||
{"type": "resolution_suggestion", "content": "Summary of what we did", "reasoning": "Internal why", "resolution_summary": "Issue was caused by X, fixed by Y", "confidence": 0.92, "follow_up_recommendations": ["Monitor for 24 hours"]}\
|
||||
{"type": "resolution_suggestion", "content": "Summary of what we did", "reasoning": "Internal why", "resolution_summary": "Issue was caused by X, fixed by Y", "confidence": 0.92, "follow_up_recommendations": ["Monitor for 24 hours"]}
|
||||
|
||||
4. Diagnostic fork (explore multiple hypotheses in parallel):
|
||||
{"type": "fork", "content": "Why we need to branch", "reasoning": "Internal why", "context_message": "Shown to engineer explaining the fork", "fork_reason": "Multiple possible root causes need independent investigation", "options": [{"label": "Branch name", "description": "What this branch will investigate"}], "confidence": 0.45}\
|
||||
"""
|
||||
|
||||
FLOWPILOT_SYSTEM_PROMPT = """\
|
||||
@@ -83,6 +86,17 @@ Every response must have a "type" field: "question", "action", or "resolution_su
|
||||
- Never suggest restarting or rebooting as a first step — diagnose first
|
||||
- Be specific: "Check Event Viewer > System > source NTFS" not "check the logs"
|
||||
|
||||
## DIAGNOSTIC FORKING
|
||||
When you detect MULTIPLE equally plausible root causes that require DIFFERENT investigation paths, use a "fork" response to let the engineer explore them as parallel branches. Use forks when:
|
||||
- Two or more hypotheses have similar probability and investigating one doesn't help eliminate the other
|
||||
- The engineer has tried the obvious path and results are ambiguous (could be DNS OR firewall OR auth)
|
||||
- Symptoms point to multiple subsystems (e.g., "slow login" could be AD replication, DNS, or group policy)
|
||||
Do NOT fork when:
|
||||
- One hypothesis is clearly more likely — just investigate that first
|
||||
- You can ask a single question that would eliminate most possibilities
|
||||
- The session has fewer than 3 steps (gather more info first)
|
||||
Fork options should be 2-4 independent investigation paths. Each option label should be a clear, short hypothesis name (e.g., "DNS Resolution Issue", "AD Replication Lag").
|
||||
|
||||
{team_context}
|
||||
|
||||
{matched_flow_context}\
|
||||
@@ -121,7 +135,7 @@ def _parse_structured_output(raw_text: str) -> dict[str, Any]:
|
||||
if not isinstance(data, dict) or "type" not in data:
|
||||
raise ValueError("LLM response missing required 'type' field")
|
||||
|
||||
valid_types = {"question", "action", "resolution_suggestion"}
|
||||
valid_types = {"question", "action", "resolution_suggestion", "fork"}
|
||||
if data["type"] not in valid_types:
|
||||
raise ValueError(f"Unknown response type: {data['type']}")
|
||||
|
||||
@@ -428,6 +442,43 @@ async def process_response(
|
||||
|
||||
await db.flush()
|
||||
|
||||
# Handle fork: create branches and enrich step content with branch IDs
|
||||
if parsed["type"] == "fork":
|
||||
from app.services.branch_manager import BranchManager
|
||||
mgr = BranchManager(db)
|
||||
|
||||
# Create root branch if this is the first fork in the session
|
||||
if not session.is_branching:
|
||||
root = await mgr.create_root_branch(session.id)
|
||||
# Reassign the step to the root branch
|
||||
step.branch_id = root.id
|
||||
|
||||
fork_options = parsed.get("options", [])
|
||||
fork_point, new_branches = await mgr.create_fork(
|
||||
session_id=session.id,
|
||||
parent_branch_id=session.active_branch_id,
|
||||
trigger_step_id=step.id,
|
||||
fork_reason=parsed.get("fork_reason", ""),
|
||||
options=[{"label": o["label"], "description": o.get("description", "")} for o in fork_options],
|
||||
)
|
||||
|
||||
# Enrich the step content with fork_point_id and branch IDs for frontend
|
||||
enriched_content = dict(step.content or {})
|
||||
enriched_content["fork_point_id"] = str(fork_point.id)
|
||||
enriched_content["fork_branches"] = [
|
||||
{"branch_id": str(b.id), "label": b.label}
|
||||
for b in new_branches
|
||||
]
|
||||
step.content = enriched_content
|
||||
step.is_fork_point = True
|
||||
step.fork_point_id = fork_point.id
|
||||
|
||||
# Auto-switch to the first branch
|
||||
first_branch = new_branches[0]
|
||||
await mgr.switch_branch(session.id, first_branch.id)
|
||||
|
||||
await db.flush()
|
||||
|
||||
# Check if resolution was suggested
|
||||
resolution_suggested = parsed["type"] == "resolution_suggestion"
|
||||
resolution_summary = parsed.get("resolution_summary") if resolution_suggested else None
|
||||
@@ -1239,6 +1290,11 @@ def _create_step_from_parsed(
|
||||
content["follow_up_recommendations"] = parsed.get("follow_up_recommendations", [])
|
||||
content["allow_free_text"] = False
|
||||
content["allow_skip"] = False
|
||||
elif parsed["type"] == "fork":
|
||||
content["fork_reason"] = parsed.get("fork_reason", "")
|
||||
content["fork_options"] = parsed.get("options", [])
|
||||
content["allow_free_text"] = False
|
||||
content["allow_skip"] = False
|
||||
|
||||
# Extract options for question type
|
||||
options = None
|
||||
|
||||
Reference in New Issue
Block a user