Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ export interface APIAgentWorkflow {
name: string;
agent_count: number;
session_count?: number;
// Finding counts
findings_total: number;
findings_open: number;
// Recommendation counts
recommendations_total: number;
recommendations_pending: number;
// Last activity timestamp (ISO format)
last_activity: string | null;
// Gate status
gate_status: 'OPEN' | 'BLOCKED';
is_blocked: boolean;
}

export interface AgentWorkflowsResponse {
Expand Down
19 changes: 19 additions & 0 deletions src/interceptors/live_trace/mcp/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,25 @@ def handle_update_agent_info(args: Dict[str, Any], store: Any) -> Dict[str, Any]

# ==================== Workflow Query Tools ====================

@register_handler("list_workflows")
def handle_list_workflows(args: Dict[str, Any], store: Any) -> Dict[str, Any]:
"""List all agent workflows with comprehensive stats."""
workflows = store.get_agent_workflows()

# Calculate summary stats
total_workflows = len([w for w in workflows if w["id"] is not None])
blocked_count = sum(1 for w in workflows if w.get("is_blocked", False))
total_findings_open = sum(w.get("findings_open", 0) for w in workflows)

return {
"workflows": workflows,
"total_count": total_workflows,
"blocked_count": blocked_count,
"total_findings_open": total_findings_open,
"message": f"Found {total_workflows} workflows ({blocked_count} blocked, {total_findings_open} open findings)",
}


@register_handler("get_workflow_agents")
def handle_get_workflow_agents(args: Dict[str, Any], store: Any) -> Dict[str, Any]:
"""List all agents in a workflow with system prompts and session info."""
Expand Down
8 changes: 8 additions & 0 deletions src/interceptors/live_trace/mcp/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,14 @@
}
},
# ==================== Workflow Query Tools ====================
{
"name": "list_workflows",
"description": "List all agent workflows with comprehensive stats. Returns workflow IDs, names, agent/session counts, finding counts, recommendation counts, gate status, and last activity timestamps.",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{
"name": "get_workflow_agents",
"description": "List all agents in a workflow with their system prompts, session counts, and last seen time. Returns the last 10 sessions across all agents in the workflow.",
Expand Down
111 changes: 100 additions & 11 deletions src/interceptors/live_trace/store/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,42 +1302,55 @@ def update_agent_info(
}

def get_agent_workflows(self) -> List[Dict[str, Any]]:
"""Get all unique agent workflows with their agent counts.
"""Get all unique agent workflows with comprehensive stats.

Returns:
List of agent workflow dicts with id, name, agent_count, and session_count.
Includes agent workflows from agents and sessions tables.
List of agent workflow dicts with:
- id, name: Basic identification
- agent_count, session_count: Activity counts
- findings_total, findings_open: Finding counts
- recommendations_total, recommendations_pending: Recommendation counts
- last_activity: ISO timestamp of last activity
- gate_status: 'OPEN' or 'BLOCKED'
- is_blocked: Boolean for quick checks
Includes "Unassigned" for agents without an agent workflow.
"""
with self._lock:
agent_workflows = []

# Get agent workflows with agent counts and session counts
# Get agent workflows with agent counts, session counts, and last activity
cursor = self.db.execute("""
SELECT
agent_workflow_id,
COALESCE(MAX(agent_workflow_name), agent_workflow_id) as name,
SUM(agent_count) as agent_count,
SUM(session_count) as session_count
SUM(session_count) as session_count,
MAX(last_activity) as last_activity
FROM (
-- Agent workflows from agents
SELECT agent_workflow_id, NULL as agent_workflow_name, COUNT(*) as agent_count, 0 as session_count
SELECT agent_workflow_id, NULL as agent_workflow_name,
COUNT(*) as agent_count, 0 as session_count,
MAX(last_seen) as last_activity
FROM agents
WHERE agent_workflow_id IS NOT NULL
GROUP BY agent_workflow_id

UNION ALL

-- Agent workflows from sessions (actual agent sessions)
SELECT agent_workflow_id, NULL as agent_workflow_name, 0 as agent_count, COUNT(*) as session_count
SELECT agent_workflow_id, NULL as agent_workflow_name,
0 as agent_count, COUNT(*) as session_count,
MAX(last_activity) as last_activity
FROM sessions
WHERE agent_workflow_id IS NOT NULL
GROUP BY agent_workflow_id

UNION ALL

-- Agent workflows from analysis_sessions (for agent workflow names)
SELECT agent_workflow_id, agent_workflow_name, 0 as agent_count, 0 as session_count
SELECT agent_workflow_id, agent_workflow_name,
0 as agent_count, 0 as session_count,
MAX(created_at) as last_activity
FROM analysis_sessions
WHERE agent_workflow_id IS NOT NULL
GROUP BY agent_workflow_id
Expand All @@ -1346,14 +1359,82 @@ def get_agent_workflows(self) -> List[Dict[str, Any]]:
ORDER BY agent_workflow_id
""")

workflow_ids = []
for row in cursor.fetchall():
workflow_id = row["agent_workflow_id"]
workflow_ids.append(workflow_id)
agent_workflows.append({
"id": row["agent_workflow_id"],
"id": workflow_id,
"name": row["name"],
"agent_count": row["agent_count"],
"session_count": row["session_count"]
"session_count": row["session_count"],
"last_activity": row["last_activity"],
# Defaults - will be populated below
"findings_total": 0,
"findings_open": 0,
"recommendations_total": 0,
"recommendations_pending": 0,
"gate_status": "OPEN",
"is_blocked": False,
})

# Batch query: Get findings counts per workflow
if workflow_ids:
placeholders = ",".join("?" * len(workflow_ids))
cursor = self.db.execute(f"""
SELECT
agent_workflow_id,
COUNT(*) as total,
SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) as open_count
FROM findings
WHERE agent_workflow_id IN ({placeholders})
GROUP BY agent_workflow_id
""", workflow_ids) # nosec B608 - safe: placeholders is only ?,?,? pattern

findings_by_workflow = {
row["agent_workflow_id"]: {
"total": row["total"],
"open": row["open_count"]
}
for row in cursor.fetchall()
}

# Batch query: Get recommendations counts per workflow
terminal_states = ('FIXED', 'VERIFIED', 'DISMISSED', 'IGNORED', 'RESOLVED', 'SUPERSEDED')
cursor = self.db.execute(f"""
SELECT
workflow_id,
COUNT(*) as total,
SUM(CASE WHEN status = 'PENDING' THEN 1 ELSE 0 END) as pending_count,
SUM(CASE WHEN severity IN ('CRITICAL', 'HIGH')
AND status NOT IN (?, ?, ?, ?, ?, ?) THEN 1 ELSE 0 END) as blocking_count
FROM recommendations
WHERE workflow_id IN ({placeholders})
GROUP BY workflow_id
""", (*terminal_states, *workflow_ids)) # nosec B608 - safe: placeholders is only ?,?,? pattern

recommendations_by_workflow = {
row["workflow_id"]: {
"total": row["total"],
"pending": row["pending_count"],
"blocking": row["blocking_count"]
}
for row in cursor.fetchall()
}

# Populate the additional fields
for wf in agent_workflows:
wf_id = wf["id"]
if wf_id in findings_by_workflow:
wf["findings_total"] = findings_by_workflow[wf_id]["total"]
wf["findings_open"] = findings_by_workflow[wf_id]["open"]
if wf_id in recommendations_by_workflow:
rec_data = recommendations_by_workflow[wf_id]
wf["recommendations_total"] = rec_data["total"]
wf["recommendations_pending"] = rec_data["pending"]
wf["is_blocked"] = rec_data["blocking"] > 0
wf["gate_status"] = "BLOCKED" if rec_data["blocking"] > 0 else "OPEN"

# Get count of unassigned agents
cursor = self.db.execute(
"SELECT COUNT(*) as count FROM agents WHERE agent_workflow_id IS NULL"
Expand All @@ -1364,7 +1445,15 @@ def get_agent_workflows(self) -> List[Dict[str, Any]]:
agent_workflows.append({
"id": None,
"name": "Unassigned",
"agent_count": unassigned_count
"agent_count": unassigned_count,
"session_count": 0,
"last_activity": None,
"findings_total": 0,
"findings_open": 0,
"recommendations_total": 0,
"recommendations_pending": 0,
"gate_status": "OPEN",
"is_blocked": False,
})

return agent_workflows
Expand Down
3 changes: 2 additions & 1 deletion src/templates/cursor-rules/.cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Agent Inspector MCP endpoint at `http://localhost:7100/mcp` provides security an
- **Proxy:** `http://localhost:4000` (point your agent here)
- **Dashboard/MCP:** `http://localhost:7100`

## Available MCP Tools (13 total)
## Available MCP Tools (14 total)

### Analysis Tools
- `get_security_patterns` - Retrieve OWASP LLM Top 10 patterns
Expand All @@ -20,6 +20,7 @@ Agent Inspector MCP endpoint at `http://localhost:7100/mcp` provides security an
- `get_fix_template` - Get remediation template for a finding type

### Agent Workflow Lifecycle Tools
- `list_workflows` - List all workflows with stats (agents, sessions, findings, gate status)
- `get_agent_workflow_state` - Check what analysis exists (static/dynamic/both)
- `get_tool_usage_summary` - Get tool usage patterns from dynamic sessions
- `get_agent_workflow_correlation` - Correlate static findings with dynamic runtime
Expand Down
3 changes: 2 additions & 1 deletion src/templates/cursor-rules/agent-inspector.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Slash commands are installed in `.cursor/commands/`. They invoke Agent Inspector
| `/agent-report` | Generate compliance report |
| `/agent-status` | Check dynamic analysis status |

## Available MCP Tools (17 total)
## Available MCP Tools (18 total)

### Analysis Tools
| Tool | Description |
Expand All @@ -44,6 +44,7 @@ Slash commands are installed in `.cursor/commands/`. They invoke Agent Inspector
### Agent Workflow Lifecycle Tools
| Tool | Description |
|------|-------------|
| `list_workflows` | List all workflows with stats (agents, sessions, findings, gate status) |
| `get_agent_workflow_state` | Check what analysis exists (static/dynamic/both) |
| `get_tool_usage_summary` | Get tool usage patterns from dynamic sessions |
| `get_agent_workflow_correlation` | Correlate static findings with dynamic runtime |
Expand Down