返回 CodeWhale
session_structcopy.rs
根目录 / crates / command-contract / src / facets / session_structcopy.rs
1 //! Read-only observations for one human-selected structural copy.
2 //! Replaces the command's concrete session/model/plan observations. The host
3 //! excludes private instruction/reasoning and inline image bodies before this
4 //! boundary; portable rendering still sanitizes visible content. These are not
5 //! complete session objects, executable callbacks, or pre-rendered envelopes.
6
7 use serde::{Deserialize, Serialize};
8 use serde_json::Value;
9
10 #[derive(Debug, Clone, PartialEq, Eq)]
11 pub enum StructcopyError {
12 Unavailable,
13 Busy,
14 /// Raw preparation error text already exposed by the original command.
15 Preparation(String),
16 }
17
18 #[derive(Debug, Clone, PartialEq)]
19 pub struct StructcopyTranscript {
20 pub index: usize,
21 pub role: String,
22 pub content: StructcopyContent,
23 }
24
25 #[derive(Debug, Clone, PartialEq)]
26 pub enum StructcopyContent {
27 InternalContext,
28 Visible(Vec<StructcopyBlock>),
29 }
30
31 #[derive(Debug, Clone, PartialEq)]
32 pub enum StructcopyBlock {
33 Text(String),
34 ThinkingOmitted,
35 ToolUse {
36 id: String,
37 name: String,
38 input: Value,
39 caller_type: Option<String>,
40 },
41 ToolResult {
42 tool_use_id: String,
43 result: StructcopyToolResult,
44 },
45 /// Only HTTP(S) URLs; the host must omit local and inline image payloads.
46 ImageUrl(String),
47 ImageOmitted,
48 ServerToolUse {
49 id: String,
50 name: String,
51 input: Value,
52 },
53 ToolSearchToolResult {
54 tool_use_id: String,
55 content: Value,
56 },
57 CodeExecutionToolResult {
58 tool_use_id: String,
59 content: Value,
60 },
61 }
62
63 #[derive(Debug, Clone, PartialEq)]
64 pub struct StructcopyToolPair {
65 pub name: String,
66 pub input: Value,
67 /// No result is distinct from a result with unknown error status.
68 pub result: Option<StructcopyToolResult>,
69 }
70
71 #[derive(Debug, Clone, PartialEq)]
72 pub struct StructcopyToolResult {
73 pub content: String,
74 pub is_error: Option<bool>,
75 /// The host's existing safe structured-result filter has already run.
76 pub content_blocks: Option<Vec<Value>>,
77 }
78
79 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
80 #[serde(deny_unknown_fields)]
81 pub struct StructcopyPlan {
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub title: Option<String>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub objective: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub context_summary: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub explanation: Option<String>,
90 #[serde(default, skip_serializing_if = "Vec::is_empty")]
91 pub sources_used: Vec<String>,
92 #[serde(default, skip_serializing_if = "Vec::is_empty")]
93 pub critical_files: Vec<String>,
94 #[serde(default, skip_serializing_if = "Vec::is_empty")]
95 pub constraints: Vec<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub recommended_approach: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub verification_plan: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub risks_and_unknowns: Option<String>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub handoff_packet: Option<String>,
104 #[serde(default, skip_serializing_if = "Vec::is_empty")]
105 pub items: Vec<StructcopyPlanItem>,
106 }
107
108 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109 pub struct StructcopyPlanItem {
110 pub step: String,
111 pub status: StructcopyPlanStatus,
112 }
113
114 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
115 #[serde(rename_all = "snake_case")]
116 pub enum StructcopyPlanStatus {
117 Pending,
118 InProgress,
119 Completed,
120 }
121
122 /// Typed form of the existing bounded workflow summary projection. It carries
123 /// no raw journal events, owner IDs, paths, or executable runtime state.
124 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125 #[serde(deny_unknown_fields)]
126 pub struct StructcopyWorkflow {
127 pub run_id: String,
128 pub status: StructcopyWorkflowStatus,
129 pub lifecycle_seq: u64,
130 pub started_at_ms: u64,
131 pub completed_at_ms: Option<u64>,
132 pub source_file: Option<String>,
133 pub workflow_id: Option<String>,
134 pub workflow_goal: Option<String>,
135 pub token_budget: Option<u64>,
136 pub child_count: usize,
137 pub schema_error_count: usize,
138 pub schema_repair_count: u64,
139 pub dispatch_failure_count: u64,
140 pub progress_count: u64,
141 pub last_progress: Option<String>,
142 pub event_count: usize,
143 pub last_event_type: Option<String>,
144 /// Unknown execution counts serialize as null, never measured zero.
145 pub leaf_count: Option<usize>,
146 pub branch_count: Option<usize>,
147 pub control_count: Option<usize>,
148 pub execution_status: Option<StructcopyExecutionStatus>,
149 pub gate_count: usize,
150 pub blocked_gate_count: usize,
151 pub gate_status: Vec<StructcopyGate>,
152 pub error: Option<String>,
153 pub usage: Option<StructcopyUsage>,
154 pub events_dropped: u64,
155 }
156
157 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
158 #[serde(rename_all = "snake_case")]
159 pub enum StructcopyWorkflowStatus {
160 Running,
161 Completed,
162 Degraded,
163 Failed,
164 Cancelled,
165 }
166
167 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168 #[serde(rename_all = "snake_case")]
169 pub enum StructcopyExecutionStatus {
170 Pending,
171 Running,
172 Succeeded,
173 Failed,
174 Cancelled,
175 BudgetExceeded,
176 }
177
178 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179 pub struct StructcopyGate {
180 pub gate_id: String,
181 pub state: String,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub blocked_reason: Option<String>,
184 }
185
186 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
187 pub struct StructcopyUsage {
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub input_tokens: Option<u64>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub output_tokens: Option<u64>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub total_tokens: Option<u64>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub cost_microusd: Option<u64>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub tool_calls: Option<u64>,
198 #[serde(default)]
199 pub tasks_reported: u64,
200 }
201
202 /// Host observations only. Root eligibility, sorting and labels are command
203 /// policy, and remain portable. Canonicalization failure is ordinary absence.
204 #[derive(Debug, Clone, Default, PartialEq, Eq)]
205 pub struct StructcopyPathRoots {
206 pub workspace: String,
207 pub canonical_workspace: Option<String>,
208 pub home: Option<String>,
209 }
210
211 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
212 pub enum StructcopyTransport {
213 Native,
214 /// Terminal-client output is queued, never delivery confirmation.
215 TerminalQueued,
216 }
217
218 /// Exact authority for `/structcopy`, independent of session export/recovery.
219 /// Observations do not mutate the session. Clipboard is the sole side effect;
220 /// the caller chooses it only after successful rendering and never for stdout.
221 pub trait CommandSessionStructcopyContext {
222 fn transcript_item(&self, index: usize) -> Result<StructcopyTranscript, StructcopyError>;
223 fn tool_pair(&self, call_id: &str) -> Result<StructcopyToolPair, StructcopyError>;
224 fn plan_snapshot(&self) -> Result<StructcopyPlan, StructcopyError>;
225 fn workflow_projection(&self, run_id: &str) -> Result<StructcopyWorkflow, StructcopyError>;
226 fn path_roots(&self) -> StructcopyPathRoots;
227 fn write_clipboard(&self, text: &str) -> Result<StructcopyTransport, String>;
228 }
229
229 lines RUST