Skip to main content

vtcode_exec_events/
tracker.rs

1//! Canonical durable contract for task-tracker checklist snapshots.
2//!
3//! Every successful `task_tracker` mutation publishes a [`TrackerSnapshot`]
4//! through [`ThreadEvent::TrackerUpdated`](crate::ThreadEvent::TrackerUpdated)
5//! so timeline consumers (desktop Tasks viewer, session archives) replay typed
6//! checklist state instead of re-parsing rendered tool output. The snapshot
7//! carries the user-facing surface only — title, progress counts, and the
8//! compact item tree — mirroring the CLI transcript/panel split; structured
9//! step metadata (`files`/`outcome`/`verify`) stays in the tool payload and
10//! the persisted tracker artifacts.
11use serde::{Deserialize, Serialize};
12use std::str::FromStr;
13
14/// Lifecycle status of one checklist item, using the canonical `task_tracker`
15/// wire strings (`pending`, `in_progress`, `completed`, `blocked`).
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
17#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
18#[serde(rename_all = "snake_case")]
19pub enum TrackerItemStatus {
20    /// Item is queued and not started.
21    #[default]
22    Pending,
23    /// Item is actively being worked on.
24    InProgress,
25    /// Item finished successfully.
26    Completed,
27    /// Item cannot proceed without external input.
28    Blocked,
29    /// Catch-all for statuses added in newer schema versions.
30    #[serde(other)]
31    Unknown,
32}
33
34impl TrackerItemStatus {
35    /// Canonical wire string for this status.
36    #[must_use]
37    pub const fn as_str(self) -> &'static str {
38        match self {
39            Self::Pending => "pending",
40            Self::InProgress => "in_progress",
41            Self::Completed => "completed",
42            Self::Blocked => "blocked",
43            Self::Unknown => "unknown",
44        }
45    }
46}
47
48impl FromStr for TrackerItemStatus {
49    type Err = ();
50
51    /// Parse a canonical status string; unknown shapes fail closed to `Err`
52    /// so producers skip unparseable items instead of mislabeling them.
53    fn from_str(raw: &str) -> Result<Self, Self::Err> {
54        match raw {
55            "pending" => Ok(Self::Pending),
56            "in_progress" => Ok(Self::InProgress),
57            "completed" => Ok(Self::Completed),
58            "blocked" => Ok(Self::Blocked),
59            _ => Err(()),
60        }
61    }
62}
63
64/// One checklist row in a tracker snapshot.
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
67pub struct TrackerItemState {
68    /// Dotted (`1.2`) or flat (`1`) position of the item in the checklist.
69    pub index_path: String,
70    /// Short leading-clause description of the item.
71    pub description: String,
72    /// Lifecycle status of the item.
73    pub status: TrackerItemStatus,
74}
75
76/// Bounded checklist snapshot published after a `task_tracker` mutation.
77///
78/// `completed`/`total` always describe the full checklist even when `items`
79/// is empty (explicit counts win over derived ones, mirroring the CLI
80/// progress line). Derived views replay the newest snapshot per session.
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
83pub struct TrackerSnapshot {
84    /// Raw checklist title; views humanize generated timestamp slugs.
85    pub title: String,
86    /// Finished items in the full checklist.
87    pub completed: usize,
88    /// Renderable items in the full checklist.
89    pub total: usize,
90    /// Item tree in checklist order. Tool-assigned index paths are unique
91    /// within a snapshot, so views may key rows on them.
92    pub items: Vec<TrackerItemState>,
93}