Skip to main content

vtcode_webmcp/
companion.rs

1//! Transport-neutral task interface for the opt-in VT Code Companion.
2
3use async_trait::async_trait;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7/// Requested execution policy. Planning is always the default.
8#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10pub enum TaskMode {
11    /// Run through the read-only exec policy.
12    #[default]
13    Plan,
14    /// Implement after a successful planning task and explicit user request.
15    Implement,
16}
17
18/// Arguments accepted from the MCP client; workspace and executable are server-owned.
19#[derive(Debug, Clone, Deserialize, JsonSchema)]
20#[serde(deny_unknown_fields)]
21pub struct StartTaskInput {
22    /// Coding task, treated as prompt data rather than command-line syntax.
23    pub prompt: String,
24    /// Defaults to read-only planning.
25    #[serde(default)]
26    pub mode: TaskMode,
27    /// Required for implementation; identifies a completed plan from this service.
28    pub plan_task_id: Option<String>,
29}
30
31/// Identify a task owned by this service instance.
32#[derive(Debug, Clone, Deserialize, JsonSchema)]
33#[serde(deny_unknown_fields)]
34pub struct TaskInput {
35    /// Opaque identity returned by start_task.
36    pub task_id: String,
37}
38
39/// Process lifecycle, independent of verification.
40#[derive(Debug, Clone, Copy, Serialize, JsonSchema, PartialEq, Eq)]
41#[serde(rename_all = "snake_case")]
42pub enum TaskState {
43    /// Execution resources are still owned by the service.
44    Running,
45    /// Both the process and canonical thread completed successfully.
46    Completed,
47    /// Execution or event collection failed.
48    Failed,
49    /// Cancellation completed and the child was reaped.
50    Cancelled,
51}
52
53/// Verification is never inferred from the agent's final message or process exit.
54#[derive(Debug, Clone, Copy, Default, Serialize, JsonSchema, PartialEq, Eq)]
55#[serde(rename_all = "snake_case")]
56pub enum TaskVerification {
57    /// No current-generation terminal verification was observed.
58    #[default]
59    Unverified,
60    /// The runtime recorded a successful verifier with exit zero.
61    Verified,
62    /// The runtime recorded a failed verifier.
63    Failed,
64}
65
66/// Bounded public projection of canonical task evidence.
67#[derive(Debug, Clone, Serialize, JsonSchema)]
68pub struct TaskSnapshot {
69    /// Service-assigned identity, distinct from the runtime session identity.
70    pub task_id: String,
71    /// Requested execution policy.
72    pub mode: TaskMode,
73    /// Current process lifecycle.
74    pub state: TaskState,
75    /// Canonical execution session, when observed.
76    pub session_id: Option<String>,
77    /// Last public agent response, sanitized and bounded.
78    pub final_response: Option<String>,
79    /// Successful recorded changes only; never a workspace-wide attribution claim.
80    pub changes: Vec<String>,
81    /// Recorded unified diff, sanitized and bounded.
82    pub unified_diff: String,
83    /// Whether public evidence was omitted, withheld, or collection was incomplete.
84    pub evidence_truncated: bool,
85    /// Current verification outcome.
86    pub verification: TaskVerification,
87    /// Most recent recorded verifier command, when available.
88    pub verification_command: Option<String>,
89    /// Exit code from the recorded verifier, never the task process exit.
90    pub verification_exit_code: Option<i32>,
91    /// Bounded public lifecycle description. Raw stderr and private reasoning are excluded.
92    pub progress: String,
93}
94
95impl TaskSnapshot {
96    /// Invalidate proof after later tool activity; old commands cannot describe
97    /// the current mutation generation.
98    pub fn invalidate_verification(&mut self) {
99        self.verification = TaskVerification::Unverified;
100        self.verification_command = None;
101        self.verification_exit_code = None;
102    }
103
104    /// A stopped or failed collector may have missed later evidence. Retain a
105    /// known verifier failure, but never retain a positive completion claim.
106    pub fn mark_incomplete(&mut self) {
107        self.evidence_truncated = true;
108        if self.verification == TaskVerification::Verified {
109            self.invalidate_verification();
110        }
111    }
112}
113
114/// Binary-owned execution boundary. The transport never launches processes.
115#[async_trait]
116pub trait CompanionTasks: Send + Sync {
117    /// Start one task or reject it without side effects.
118    async fn start(&self, input: StartTaskInput) -> Result<TaskSnapshot, String>;
119    /// Inspect a task owned by this service.
120    async fn get(&self, task_id: &str) -> Result<TaskSnapshot, String>;
121    /// Request cancellation. False means the task is already terminal.
122    async fn cancel(&self, task_id: &str) -> Result<bool, String>;
123}