Skip to main content

task_graph_mcp/resources/
workflows.rs

1//! Workflow and overlay resources - expose available workflows and overlays via MCP resources.
2//!
3//! These resources allow agents to discover available workflows and overlays at runtime,
4//! including descriptions of what each is designed for.
5
6use crate::config::workflows::WorkflowsConfig;
7use anyhow::Result;
8use serde_json::{Value, json};
9
10/// List all available workflows with their metadata.
11pub fn list_workflows(workflows: &WorkflowsConfig) -> Result<Value> {
12    let mut workflow_list: Vec<Value> = Vec::new();
13
14    for (name, config) in &workflows.named_workflows {
15        let source = config.source_file.as_ref().map(|p| p.display().to_string());
16
17        workflow_list.push(json!({
18            "name": name,
19            "description": config.description,
20            "source_file": source,
21            "states": config.states.keys().collect::<Vec<_>>(),
22            "phases": config.phases.keys().collect::<Vec<_>>(),
23        }));
24    }
25
26    // Sort by name for consistent output
27    workflow_list.sort_by(|a, b| {
28        a.get("name")
29            .and_then(|v| v.as_str())
30            .unwrap_or("")
31            .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
32    });
33
34    // Include info about default workflow if configured
35    let default_workflow = workflows.default_workflow_key.as_ref();
36
37    // Include overlays
38    let mut overlay_list: Vec<Value> = workflows
39        .named_overlays
40        .iter()
41        .map(|(name, config)| {
42            json!({
43                "name": name,
44                "description": config.description,
45            })
46        })
47        .collect();
48
49    overlay_list.sort_by(|a, b| {
50        a.get("name")
51            .and_then(|v| v.as_str())
52            .unwrap_or("")
53            .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
54    });
55
56    Ok(json!({
57        "workflows": workflow_list,
58        "overlays": overlay_list,
59        "default_workflow": default_workflow,
60        "count": workflows.named_workflows.len(),
61    }))
62}
63
64/// Get detailed information about a specific workflow.
65pub fn get_workflow(workflows: &WorkflowsConfig, name: &str) -> Result<Value> {
66    let config = workflows
67        .named_workflows
68        .get(name)
69        .ok_or_else(|| anyhow::anyhow!("Workflow '{}' not found", name))?;
70
71    let source = config.source_file.as_ref().map(|p| p.display().to_string());
72
73    // Build state details
74    let states: Vec<Value> = config
75        .states
76        .iter()
77        .map(|(state_name, state)| {
78            json!({
79                "name": state_name,
80                "exits": state.exits,
81                "timed": state.timed,
82                "has_enter_prompt": state.prompts.enter.is_some(),
83                "has_exit_prompt": state.prompts.exit.is_some(),
84            })
85        })
86        .collect();
87
88    // Build phase details
89    let phases: Vec<Value> = config
90        .phases
91        .iter()
92        .map(|(phase_name, phase)| {
93            json!({
94                "name": phase_name,
95                "has_enter_prompt": phase.prompts.enter.is_some(),
96                "has_exit_prompt": phase.prompts.exit.is_some(),
97            })
98        })
99        .collect();
100
101    Ok(json!({
102        "name": name,
103        "description": config.description,
104        "source_file": source,
105        "settings": {
106            "initial_state": config.settings.initial_state,
107            "disconnect_state": config.settings.disconnect_state,
108            "blocking_states": config.settings.blocking_states,
109        },
110        "states": states,
111        "phases": phases,
112        "combo_count": config.combos.len(),
113    }))
114}
115
116/// List all available overlays with their metadata.
117pub fn list_overlays(workflows: &WorkflowsConfig) -> Result<Value> {
118    let mut overlay_list: Vec<Value> = Vec::new();
119
120    for (name, config) in &workflows.named_overlays {
121        let source = config.source_file.as_ref().map(|p| p.display().to_string());
122
123        overlay_list.push(json!({
124            "name": name,
125            "description": config.description,
126            "source_file": source,
127        }));
128    }
129
130    // Sort by name for consistent output
131    overlay_list.sort_by(|a, b| {
132        a.get("name")
133            .and_then(|v| v.as_str())
134            .unwrap_or("")
135            .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
136    });
137
138    Ok(json!({
139        "overlays": overlay_list,
140        "count": workflows.named_overlays.len(),
141    }))
142}
143
144/// Get detailed information about a specific overlay.
145pub fn get_overlay(workflows: &WorkflowsConfig, name: &str) -> Result<Value> {
146    let config = workflows
147        .named_overlays
148        .get(name)
149        .ok_or_else(|| anyhow::anyhow!("Overlay '{}' not found", name))?;
150
151    let source = config.source_file.as_ref().map(|p| p.display().to_string());
152
153    // Build state details
154    let states: Vec<Value> = config
155        .states
156        .iter()
157        .map(|(state_name, state)| {
158            json!({
159                "name": state_name,
160                "exits": state.exits,
161                "timed": state.timed,
162                "has_enter_prompt": state.prompts.enter.is_some(),
163                "has_exit_prompt": state.prompts.exit.is_some(),
164            })
165        })
166        .collect();
167
168    // Build phase details
169    let phases: Vec<Value> = config
170        .phases
171        .iter()
172        .map(|(phase_name, phase)| {
173            json!({
174                "name": phase_name,
175                "has_enter_prompt": phase.prompts.enter.is_some(),
176                "has_exit_prompt": phase.prompts.exit.is_some(),
177            })
178        })
179        .collect();
180
181    // Build gate details
182    let gates: Vec<Value> = config
183        .gates
184        .iter()
185        .map(|(gate_key, gate_defs)| {
186            let defs: Vec<Value> = gate_defs
187                .iter()
188                .map(|g| {
189                    json!({
190                        "type": g.gate_type,
191                        "enforcement": format!("{:?}", g.enforcement),
192                        "description": g.description,
193                    })
194                })
195                .collect();
196            json!({
197                "key": gate_key,
198                "definitions": defs,
199            })
200        })
201        .collect();
202
203    // Build role details
204    let roles: Vec<Value> = config
205        .roles
206        .iter()
207        .map(|(role_name, role)| {
208            json!({
209                "name": role_name,
210                "description": role.description,
211                "tags": role.tags,
212                "max_claims": role.max_claims,
213                "can_assign": role.can_assign,
214                "can_create_subtasks": role.can_create_subtasks,
215            })
216        })
217        .collect();
218
219    // Build advisory details
220    let advisories: Vec<Value> = config
221        .advisories
222        .iter()
223        .map(|(topic, advisory)| {
224            json!({
225                "topic": topic,
226                "level": advisory.level,
227                "phase": advisory.phase,
228                "role": advisory.role,
229                "domain": advisory.domain,
230                "content": advisory.content,
231            })
232        })
233        .collect();
234
235    // Build role_prompts details
236    let role_prompts: Value = config
237        .role_prompts
238        .iter()
239        .map(|(role_name, prompts)| (role_name.clone(), json!(prompts)))
240        .collect::<serde_json::Map<String, Value>>()
241        .into();
242
243    Ok(json!({
244        "name": name,
245        "description": config.description,
246        "source_file": source,
247        "states": states,
248        "phases": phases,
249        "gates": gates,
250        "roles": roles,
251        "advisories": advisories,
252        "role_prompts": role_prompts,
253        "combo_count": config.combos.len(),
254    }))
255}