1use std::sync::OnceLock;
27
28use serde::{Deserialize, Serialize};
29
30use lean_ctx_protocol::hive::HIVE_DEFAULT_URL;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum HiveBusMode {
35 #[default]
36 Auto,
37 Off,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(default)]
42pub struct HiveConfig {
43 #[serde(skip_serializing_if = "Option::is_none")]
46 pub enabled: Option<bool>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub url: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub root_key: Option<String>,
53 pub auto_capture: bool,
56 pub pull_interval_minutes: u32,
59 pub metrics_upload: bool,
60 pub receipts_upload: bool,
61 pub bus: HiveBusMode,
62 pub vault_labels: bool,
63 pub runtime_consent: bool,
67 pub worker: HiveWorkerConfig,
69 #[serde(skip_serializing_if = "Option::is_none")]
74 pub queen_identity: Option<String>,
75}
76
77impl Default for HiveConfig {
78 fn default() -> Self {
79 Self {
80 enabled: None,
81 url: None,
82 root_key: None,
83 auto_capture: true,
84 pull_interval_minutes: DEFAULT_PULL_INTERVAL_MINUTES,
85 metrics_upload: true,
86 receipts_upload: true,
87 bus: HiveBusMode::Auto,
88 vault_labels: true,
89 runtime_consent: false,
90 worker: HiveWorkerConfig::default(),
91 queen_identity: None,
92 }
93 }
94}
95
96pub const DEFAULT_PULL_INTERVAL_MINUTES: u32 = 5;
98pub const PULL_INTERVAL_BOUNDS: std::ops::RangeInclusive<u32> = 1..=60;
100
101impl HiveConfig {
102 pub fn pull_interval(&self) -> u32 {
104 self.pull_interval_minutes
105 .clamp(*PULL_INTERVAL_BOUNDS.start(), *PULL_INTERVAL_BOUNDS.end())
106 }
107}
108
109pub type OffSwitches = fn(&HiveConfig);
111
112static OFF_SWITCHES: OnceLock<OffSwitches> = OnceLock::new();
113
114pub fn register_off_switches(apply: OffSwitches) {
118 let _ = OFF_SWITCHES.set(apply);
119}
120
121pub(crate) fn apply_off_switches(config: &HiveConfig) {
124 if let Some(apply) = OFF_SWITCHES.get() {
125 apply(config);
126 }
127}
128
129#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(default)]
132pub struct HiveWorkerConfig {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub max_tasks_per_day: Option<u32>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub max_tokens_per_day: Option<u64>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub max_usd_micros_per_day: Option<u64>,
139}
140
141pub fn schema_section() -> crate::core::config::schema::SectionSchema {
143 use crate::core::config::schema::{KeySchema, SectionSchema};
144 let defaults = HiveConfig::default();
145 let key = |ty: &str, default: serde_json::Value, description: &str, values: Option<&[&str]>| {
146 KeySchema {
147 ty: ty.into(),
148 default,
149 description: description.into(),
150 values: values.map(|v| v.iter().map(|s| (*s).to_string()).collect()),
151 env_override: None,
152 }
153 };
154 let keys = [
155 (
156 "enabled",
157 key(
158 "bool",
159 serde_json::Value::Null,
160 "Pause all Hive traffic with false; unset means on while this machine is connected.",
161 None,
162 ),
163 ),
164 (
165 "url",
166 key(
167 "string",
168 serde_json::json!(HIVE_DEFAULT_URL),
169 "Hive origin (https; a self-hosted Hive may use any https URL).",
170 None,
171 ),
172 ),
173 (
174 "root_key",
175 key(
176 "string",
177 serde_json::Value::Null,
178 "Root public key (base64 Ed25519) of a self-hosted Hive.",
179 None,
180 ),
181 ),
182 (
183 "auto_capture",
184 key(
185 "bool",
186 serde_json::json!(defaults.auto_capture),
187 "Run the capture gate on session learnings and share what passes (renamed from auto_draft).",
188 None,
189 ),
190 ),
191 (
192 "pull_interval_minutes",
193 key(
194 "integer",
195 serde_json::json!(defaults.pull_interval_minutes),
196 "Minutes between Hive sync passes (1-60; values outside are clamped).",
197 None,
198 ),
199 ),
200 (
201 "metrics_upload",
202 key(
203 "bool",
204 serde_json::json!(defaults.metrics_upload),
205 "Send hourly instance metrics to Hive (counts only, including full re-fetches and unregulated program names; never content).",
206 None,
207 ),
208 ),
209 (
210 "receipts_upload",
211 key(
212 "bool",
213 serde_json::json!(defaults.receipts_upload),
214 "Upload context receipts to Hive.",
215 None,
216 ),
217 ),
218 (
219 "bus",
220 key(
221 "string",
222 serde_json::json!("auto"),
223 "Relay the agent bus through Hive (auto) or keep it local (off).",
224 Some(&["auto", "off"]),
225 ),
226 ),
227 (
228 "queen_identity",
229 KeySchema {
230 env_override: Some("LEAN_CTX_HIVE_QUEEN_IDENTITY".into()),
231 ..key(
232 "string",
233 serde_json::json!("warn"),
234 "A bus sender presenting as the Queen that is no registered Queen session: warn (pass, log in bus/queen-warnings.json, mark for the receiver) or enforce (refuse); any other value counts as enforce. Enforce only once queen-sessions.json lists every Queen session.",
235 Some(&["warn", "enforce"]),
236 )
237 },
238 ),
239 (
240 "vault_labels",
241 key(
242 "bool",
243 serde_json::json!(defaults.vault_labels),
244 "Send checkout folder names as vault labels.",
245 None,
246 ),
247 ),
248 (
249 "runtime_consent",
250 key(
251 "bool",
252 serde_json::json!(defaults.runtime_consent),
253 "You agreed to install the proprietary Intelligence runtime your plan licenses (set by `hive connect` or `hive runtime install`).",
254 None,
255 ),
256 ),
257 (
258 "worker.max_tasks_per_day",
259 key(
260 "integer",
261 serde_json::Value::Null,
262 "Worker mode: at most this many claimed tasks per day on this machine; only tightens the opt-in budget set on the web.",
263 None,
264 ),
265 ),
266 (
267 "worker.max_tokens_per_day",
268 key(
269 "integer",
270 serde_json::Value::Null,
271 "Worker mode: stop claiming once the day's results reported this many tokens; only tightens the opt-in budget.",
272 None,
273 ),
274 ),
275 (
276 "worker.max_usd_micros_per_day",
277 key(
278 "integer",
279 serde_json::Value::Null,
280 "Worker mode: stop claiming once the day's results reported this cost (USD millionths); only tightens the opt-in budget.",
281 None,
282 ),
283 ),
284 ];
285 SectionSchema {
286 description: "LeanCTX | Hive: workspace knowledge, policies and the shared agent bus."
287 .into(),
288 keys: keys
289 .into_iter()
290 .map(|(name, key)| (name.to_string(), key))
291 .collect(),
292 }
293}