Skip to main content

lean_ctx/tools/registered/
ctx_workflow.rs

1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
6use crate::tool_defs::tool_def;
7
8pub struct CtxWorkflowTool;
9
10impl McpTool for CtxWorkflowTool {
11    fn name(&self) -> &'static str {
12        "ctx_workflow"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_workflow",
18            "Workflow rails — state machine with evidence tracking.\n\
19             WORKFLOW: start → transition (multiple) → complete. evidence_add before\n\
20             transition to attach proof. Built-in plan_code_test when spec omitted.\n\
21             Actions: start|status|transition|complete|evidence_add|evidence_list|stop.\n\
22             spec=WorkflowSpec JSON for custom states/transitions.\n\
23             ANTIPATTERN: NOT for one-shot tasks — use direct tool calls instead.",
24            json!({
25                "type": "object",
26                "properties": {
27                    "action": {
28                        "type": "string",
29                        "enum": ["start", "status", "transition", "complete", "evidence_add", "evidence_list", "stop"],
30                        "description": "start|status|transition|complete|evidence_add|evidence_list|stop"
31                    },
32                    "name": { "type": "string", "description": "Workflow name (for start)" },
33                    "spec": { "type": "string", "description": "WorkflowSpec JSON (for start; omit for builtin)" },
34                    "to": { "type": "string", "description": "Target state (for transition)" },
35                    "key": { "type": "string", "description": "Evidence key (for evidence_add)" },
36                    "value": { "type": "string", "description": "Evidence value or transition note" }
37                },
38                "allOf": [
39                    { "if": { "properties": { "action": { "const": "transition" } }, "required": ["action"] },
40                      "then": { "required": ["action", "to"] } },
41                    { "if": { "properties": { "action": { "const": "evidence_add" } }, "required": ["action"] },
42                      "then": { "required": ["action", "key"] } }
43                ]
44            }),
45        )
46    }
47
48    fn handle(
49        &self,
50        args: &Map<String, Value>,
51        ctx: &ToolContext,
52    ) -> Result<ToolOutput, ErrorData> {
53        let action = get_str(args, "action").unwrap_or_else(|| "status".to_string());
54
55        let agent_id_str = ctx
56            .agent_id
57            .as_ref()
58            .and_then(|h| h.blocking_read().clone());
59
60        let result = {
61            let session_handle = ctx
62                .session
63                .as_ref()
64                .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
65            let mut session = session_handle.blocking_write();
66            crate::tools::ctx_workflow::handle_with_session_agent(
67                Some(args),
68                &mut session,
69                agent_id_str.as_deref(),
70            )
71        };
72
73        if let Some(workflow_handle) = ctx.workflow.as_ref() {
74            let mut wf = workflow_handle.blocking_write();
75            *wf = crate::core::workflow::load_active_for_agent(agent_id_str.as_deref())
76                .ok()
77                .flatten();
78        }
79
80        Ok(ToolOutput {
81            text: result,
82            original_tokens: 0,
83            saved_tokens: 0,
84            mode: Some(action),
85            path: None,
86            changed: false,
87            shell_outcome: None,
88            content_blocks: None,
89        })
90    }
91}