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}