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}