Skip to main content

do_memory_mcp/server/tools/
episode_timeline.rs

1//! Episode timeline tool for MCP server
2//!
3//! This module provides the tool for retrieving a chronological view
4//! of all steps in an episode.
5
6use crate::server::MemoryMCPServer;
7use anyhow::{Result, anyhow};
8use do_memory_core::{ExecutionResult, TaskOutcome};
9use serde_json::{Value, json};
10use tracing::debug;
11use uuid::Uuid;
12
13impl MemoryMCPServer {
14    /// Get a timeline view of episode execution
15    ///
16    /// This tool provides a chronological view of all steps in an episode,
17    /// useful for visualizing task progression.
18    ///
19    /// # Arguments (from JSON)
20    ///
21    /// * `episode_id` - UUID of the episode
22    pub async fn get_episode_timeline_tool(&self, args: Value) -> Result<Value> {
23        debug!("Getting episode timeline with args: {}", args);
24
25        let episode_id_str = args
26            .get("episode_id")
27            .and_then(|v| v.as_str())
28            .ok_or_else(|| anyhow!("Missing required field: episode_id"))?;
29
30        let episode_id = Uuid::parse_str(episode_id_str)
31            .map_err(|e| anyhow!("Invalid episode_id format: {}", e))?;
32
33        let episode = self
34            .memory
35            .get_episode(episode_id)
36            .await
37            .map_err(|e| anyhow!("Failed to get episode: {}", e))?;
38
39        // Build timeline
40        let timeline: Vec<Value> = episode
41            .steps
42            .iter()
43            .map(|step| {
44                json!({
45                    "step_number": step.step_number,
46                    "timestamp": step.timestamp.to_rfc3339(),
47                    "tool": step.tool,
48                    "action": step.action,
49                    "result_type": match &step.result {
50                        Some(ExecutionResult::Success { .. }) => "success",
51                        Some(ExecutionResult::Error { .. }) => "error",
52                        Some(ExecutionResult::Timeout) => "timeout",
53                        None => "pending",
54                    },
55                    "latency_ms": step.latency_ms,
56                })
57            })
58            .collect();
59
60        let duration_seconds = episode
61            .end_time
62            .map(|end| (end.timestamp() - episode.start_time.timestamp()) as f64);
63
64        Ok(json!({
65            "success": true,
66            "episode_id": episode_id.to_string(),
67            "task_description": episode.task_description,
68            "start_time": episode.start_time.to_rfc3339(),
69            "end_time": episode.end_time.map(|t| t.to_rfc3339()),
70            "duration_seconds": duration_seconds,
71            "step_count": episode.steps.len(),
72            "timeline": timeline,
73            "outcome": episode.outcome.as_ref().map(|o| match o {
74                TaskOutcome::Success { .. } => "success",
75                TaskOutcome::PartialSuccess { .. } => "partial_success",
76                TaskOutcome::Failure { .. } => "failure",
77            }),
78        }))
79    }
80}