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/// Stable identities carried by an observed canonical command result.
67#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq, PartialOrd, Ord)]
68pub struct CommandEventReference {
69    /// Canonical thread identity from thread.started, when observed.
70    pub thread_id: Option<String>,
71    /// Runtime task identity, distinct from the service task ID.
72    pub runtime_task_id: Option<String>,
73    /// Canonical actor identity, when supplied.
74    pub actor_id: Option<String>,
75    /// Canonical turn identity, when supplied.
76    pub turn_id: Option<String>,
77    /// Canonical item identity within that context.
78    pub item_id: String,
79}
80
81/// Historical command observation, never a semantic assertion of task correctness.
82/// Output streams and private reasoning are intentionally excluded.
83#[derive(Debug, Clone, Serialize, JsonSchema)]
84pub struct CommandResult {
85    /// Canonical source: `tool_output` or `command_execution`.
86    pub source: String,
87    /// Reference to the canonical result item for local diagnostics.
88    pub event_ref: CommandEventReference,
89    /// Native tool-call identity, when available; not used to guess correlations.
90    pub tool_call_id: Option<String>,
91    /// Command tool name, when supplied by a matching invocation.
92    pub tool_name: Option<String>,
93    /// Recorded command text; null when a preceding invocation cannot be matched.
94    pub command: Option<String>,
95    /// Whether command text was shortened, sanitized, or separately appended argv was omitted.
96    pub command_truncated: bool,
97    /// Canonical status: `completed`, `failed`, or `in_progress`.
98    /// A completed tool call can yield a running command with a null exit code.
99    pub status: String,
100    /// Observed command exit code. Null never implies zero or terminal success.
101    pub exit_code: Option<i32>,
102}
103
104/// Bounded public projection of canonical task evidence.
105#[derive(Debug, Clone, Serialize, JsonSchema)]
106pub struct TaskSnapshot {
107    /// Service-assigned identity, distinct from the runtime session identity.
108    pub task_id: String,
109    /// Requested execution policy.
110    pub mode: TaskMode,
111    /// Current process lifecycle.
112    pub state: TaskState,
113    /// Canonical execution session, when observed.
114    pub session_id: Option<String>,
115    /// Last public agent response, sanitized and bounded.
116    pub final_response: Option<String>,
117    /// Successful recorded changes only; never a workspace-wide attribution claim.
118    pub changes: Vec<String>,
119    /// Recorded unified diff, sanitized and bounded.
120    pub unified_diff: String,
121    /// Whether public evidence was omitted, withheld, or collection was incomplete.
122    pub evidence_truncated: bool,
123    /// Historical command observations in result-event order. Later mutations do
124    /// not erase them; they do not grant current-generation verification credit.
125    pub command_results: Vec<CommandResult>,
126    /// Current runtime-verifier outcome, scoped to its recorded command.
127    pub verification: TaskVerification,
128    /// Most recent runtime-recorded verifier command; not every task assertion.
129    pub verification_command: Option<String>,
130    /// Exit code from the recorded verifier, never the task process exit.
131    pub verification_exit_code: Option<i32>,
132    /// Bounded public lifecycle description. Raw stderr and private reasoning are excluded.
133    pub progress: String,
134}
135
136impl TaskSnapshot {
137    /// Invalidate proof after later tool activity; old commands cannot describe
138    /// the current mutation generation.
139    pub fn invalidate_verification(&mut self) {
140        self.verification = TaskVerification::Unverified;
141        self.verification_command = None;
142        self.verification_exit_code = None;
143    }
144
145    /// A stopped or failed collector may have missed later evidence. Retain a
146    /// known verifier failure, but never retain a positive completion claim.
147    pub fn mark_incomplete(&mut self) {
148        self.evidence_truncated = true;
149        if self.verification == TaskVerification::Verified {
150            self.invalidate_verification();
151        }
152    }
153}
154
155/// Binary-owned execution boundary. The transport never launches processes.
156#[async_trait]
157pub trait CompanionTasks: Send + Sync {
158    /// Start one task or reject it without side effects.
159    async fn start(&self, input: StartTaskInput) -> Result<TaskSnapshot, String>;
160    /// Inspect a task owned by this service.
161    async fn get(&self, task_id: &str) -> Result<TaskSnapshot, String>;
162    /// Request cancellation. False means the task is already terminal.
163    async fn cancel(&self, task_id: &str) -> Result<bool, String>;
164}