返回 CodeWhale
types.rs
根目录 / crates / tui / src / plugins / types.rs
1 use std::collections::HashMap;
2 use std::fmt;
3 use std::path::PathBuf;
4
5 use serde::{Deserialize, Serialize};
6
7 use super::manifest::{PluginInventory, PluginManifest, ResolvedPluginComponents};
8
9 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
10 #[serde(rename_all = "snake_case")]
11 pub enum PluginScope {
12 Builtin,
13 User,
14 Workspace,
15 }
16
17 impl PluginScope {
18 #[must_use]
19 pub fn as_str(self) -> &'static str {
20 match self {
21 Self::Builtin => "builtin",
22 Self::User => "user",
23 Self::Workspace => "workspace",
24 }
25 }
26 }
27
28 impl fmt::Display for PluginScope {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.write_str(self.as_str())
31 }
32 }
33
34 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35 #[serde(rename_all = "snake_case")]
36 pub enum PluginOrigin {
37 Builtin,
38 CodeWhaleHome,
39 Workspace,
40 }
41
42 impl PluginOrigin {
43 #[must_use]
44 pub fn as_str(self) -> &'static str {
45 match self {
46 Self::Builtin => "codewhale-builtin",
47 Self::CodeWhaleHome => "codewhale-home",
48 Self::Workspace => "workspace-codewhale",
49 }
50 }
51 }
52
53 impl fmt::Display for PluginOrigin {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.write_str(self.as_str())
56 }
57 }
58
59 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
60 #[serde(transparent)]
61 pub struct PluginId(pub String);
62
63 impl PluginId {
64 #[must_use]
65 pub fn as_str(&self) -> &str {
66 &self.0
67 }
68 }
69
70 /// Persistable proof of the exact reviewed plugin authority attached to a
71 /// Skill or MCP server. Runtime contexts keep this receipt instead of a
72 /// pointer to mutable process-global discovery state, and revalidate it at
73 /// every side-effect boundary.
74 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75 #[serde(deny_unknown_fields)]
76 pub struct PluginAuthority {
77 pub plugin_id: PluginId,
78 pub plugin_name: String,
79 pub workspace: PathBuf,
80 pub state_path: PathBuf,
81 pub source_manifest: PathBuf,
82 pub staged_manifest: PathBuf,
83 pub content_hash: String,
84 pub capability_hash: String,
85 /// Monotonic generation of this plugin's persisted authority. Any trust,
86 /// enablement, disablement, or revocation transition invalidates receipts
87 /// held by another process immediately, even when hashes are unchanged.
88 pub state_generation: u64,
89 }
90
91 impl fmt::Display for PluginId {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str(&self.0)
94 }
95 }
96
97 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
98 pub enum PluginDiagnosticLevel {
99 Warning,
100 Error,
101 }
102
103 #[derive(Debug, Clone, PartialEq, Eq)]
104 pub struct PluginDiagnostic {
105 pub level: PluginDiagnosticLevel,
106 pub code: &'static str,
107 pub message: String,
108 pub path: Option<PathBuf>,
109 }
110
111 impl PluginDiagnostic {
112 #[must_use]
113 pub fn warning(code: &'static str, message: impl Into<String>, path: Option<PathBuf>) -> Self {
114 Self {
115 level: PluginDiagnosticLevel::Warning,
116 code,
117 message: message.into(),
118 path,
119 }
120 }
121
122 #[must_use]
123 pub fn error(code: &'static str, message: impl Into<String>, path: Option<PathBuf>) -> Self {
124 Self {
125 level: PluginDiagnosticLevel::Error,
126 code,
127 message: message.into(),
128 path,
129 }
130 }
131 }
132
133 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
134 pub enum PluginTrustStatus {
135 Trusted,
136 NeverReviewed,
137 ContentChanged,
138 CapabilitiesChanged,
139 }
140
141 impl PluginTrustStatus {
142 #[must_use]
143 pub fn as_str(self) -> &'static str {
144 match self {
145 Self::Trusted => "trusted",
146 Self::NeverReviewed => "not-reviewed",
147 Self::ContentChanged => "content-changed",
148 Self::CapabilitiesChanged => "capabilities-changed",
149 }
150 }
151 }
152
153 #[derive(Debug, Clone)]
154 pub struct PluginSkillSnapshot {
155 pub name: String,
156 pub description: String,
157 pub localized_descriptions: HashMap<String, String>,
158 pub invocation: crate::skills::SkillInvocation,
159 pub aliases: Vec<String>,
160 pub body: String,
161 pub path: PathBuf,
162 /// Digest of the exact UTF-8 bytes parsed into this snapshot. This is the
163 /// corresponding entry in the reviewed bundle's file-hash inventory.
164 pub source_hash: String,
165 }
166
167 #[derive(Debug, Clone)]
168 pub struct LoadedPlugin {
169 pub id: PluginId,
170 pub manifest: PluginManifest,
171 pub base_path: PathBuf,
172 pub canonical_root: PathBuf,
173 /// Codewhale-owned, content-addressed copy used for active execution.
174 /// `None` means the reviewed bundle has not been staged safely and cannot
175 /// become active even if an older state file says it was enabled.
176 pub staged_root: Option<PathBuf>,
177 pub scope: PluginScope,
178 pub origin: PluginOrigin,
179 pub enabled: bool,
180 pub trust_status: PluginTrustStatus,
181 pub applicable: bool,
182 pub inventory: PluginInventory,
183 pub components: ResolvedPluginComponents,
184 pub content_hash: String,
185 pub capability_hash: String,
186 pub state_generation: u64,
187 pub skill_snapshots: Vec<PluginSkillSnapshot>,
188 pub diagnostics: Vec<PluginDiagnostic>,
189 }
190
191 impl LoadedPlugin {
192 #[must_use]
193 pub fn name(&self) -> &str {
194 &self.manifest.plugin.name
195 }
196
197 #[must_use]
198 pub fn trusted(&self) -> bool {
199 self.trust_status == PluginTrustStatus::Trusted
200 }
201
202 #[must_use]
203 pub fn active(&self) -> bool {
204 self.enabled
205 && self.trusted()
206 && self.staged_root.is_some()
207 && self.applicable
208 && !self.inventory.has_unsupported_capabilities()
209 && !self
210 .diagnostics
211 .iter()
212 .any(|diagnostic| diagnostic.level == PluginDiagnosticLevel::Error)
213 }
214
215 #[must_use]
216 pub fn authority(&self, state_path: PathBuf, workspace: PathBuf) -> Option<PluginAuthority> {
217 let staged_root = self.staged_root.as_ref()?;
218 Some(PluginAuthority {
219 plugin_id: self.id.clone(),
220 plugin_name: self.name().to_string(),
221 workspace,
222 state_path,
223 source_manifest: self.canonical_root.join("plugin.toml"),
224 staged_manifest: staged_root.join("plugin.toml"),
225 content_hash: self.content_hash.clone(),
226 capability_hash: self.capability_hash.clone(),
227 state_generation: self.state_generation,
228 })
229 }
230
231 #[must_use]
232 pub fn state_label(&self) -> &'static str {
233 if self.active() {
234 "active"
235 } else if !self.enabled {
236 "disabled"
237 } else if !self.trusted() {
238 "enabled-untrusted"
239 } else if self.staged_root.is_none() {
240 "unstaged"
241 } else if !self.applicable {
242 "inapplicable"
243 } else if self.inventory.has_unsupported_capabilities() {
244 "unsupported"
245 } else {
246 "inactive"
247 }
248 }
249 }
250
250 lines RUST