omni_dev/daemon/service.rs
1//! The [`DaemonService`] abstraction: a pluggable unit of work hosted by the
2//! daemon supervisor.
3//!
4//! A service has a stable [`name`](DaemonService::name), answers operations
5//! routed to it over the control socket ([`handle`](DaemonService::handle)),
6//! contributes a tray submenu ([`menu`](DaemonService::menu) /
7//! [`menu_action`](DaemonService::menu_action)), reports structured status for
8//! `daemon status` ([`status`](DaemonService::status)), and participates in
9//! graceful shutdown ([`shutdown`](DaemonService::shutdown)). See ADR-0039.
10
11use anyhow::Result;
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15
16/// A long-lived unit of work supervised by the daemon.
17///
18/// Implementations are registered in a [`ServiceRegistry`](super::registry::ServiceRegistry)
19/// and are assumed live from registration until [`shutdown`](Self::shutdown).
20#[async_trait]
21pub trait DaemonService: Send + Sync {
22 /// Stable identifier used to route control-socket envelopes to this service
23 /// (the envelope's `service` field) and to label its status/menu.
24 fn name(&self) -> &'static str;
25
26 /// Handles an operation routed to this service, returning a JSON payload.
27 async fn handle(&self, op: &str, payload: Value) -> Result<Value>;
28
29 /// Cheap snapshot of the service's tray submenu, polled by the menu-bar
30 /// shell. Must not block.
31 fn menu(&self) -> MenuSnapshot;
32
33 /// Performs a tray menu action previously surfaced by [`menu`](Self::menu),
34 /// identified by its [`MenuAction::id`].
35 async fn menu_action(&self, action_id: &str) -> Result<()>;
36
37 /// Structured status for `daemon status` aggregation.
38 async fn status(&self) -> ServiceStatus;
39
40 /// Gracefully stops the service, draining in-flight work. Called once on
41 /// daemon shutdown.
42 async fn shutdown(&self);
43}
44
45/// Structured per-service status, aggregated by the built-in `status` op and
46/// surfaced by `omni-dev daemon status`.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ServiceStatus {
49 /// The reporting service's [`name`](DaemonService::name).
50 pub name: String,
51 /// Whether the service is currently operating normally.
52 pub healthy: bool,
53 /// One-line human-readable summary (e.g. `"1 tab connected"`).
54 pub summary: String,
55 /// Service-specific structured detail; `null` when there is none.
56 #[serde(default, skip_serializing_if = "Value::is_null")]
57 pub detail: Value,
58}
59
60/// A cheap snapshot of a service's tray submenu, rebuilt on each poll.
61#[derive(Debug, Clone, Default)]
62pub struct MenuSnapshot {
63 /// Submenu title (the parent menu-bar entry label).
64 pub title: String,
65 /// Ordered submenu entries.
66 pub items: Vec<MenuItem>,
67}
68
69/// A single entry in a [`MenuSnapshot`].
70#[derive(Debug, Clone)]
71pub enum MenuItem {
72 /// A non-interactive status line.
73 Label(String),
74 /// A horizontal separator.
75 Separator,
76 /// A clickable action dispatched via [`DaemonService::menu_action`].
77 Action(MenuAction),
78}
79
80/// A clickable tray action.
81#[derive(Debug, Clone)]
82pub struct MenuAction {
83 /// Stable identifier passed back to [`DaemonService::menu_action`].
84 pub id: String,
85 /// Human-readable menu label.
86 pub label: String,
87 /// Whether the action is currently selectable.
88 pub enabled: bool,
89}