Skip to main content

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}