Skip to main content

ai_crew_sync/tools/
presence.rs

1use rmcp::{
2    ErrorData, Json, handler::server::wrapper::Parameters, service::RequestContext, tool,
3    tool_router,
4};
5use schemars::JsonSchema;
6use serde::Deserialize;
7
8use super::{Bus, auth_of};
9use crate::{
10    model::{AgentInfo, AgentList},
11    store::presence,
12};
13
14#[derive(Debug, Deserialize, JsonSchema)]
15pub struct HeartbeatArgs {
16    /// One of "active", "idle", "busy", "blocked". Defaults to "active".
17    #[serde(default)]
18    pub status: Option<String>,
19    /// Repository you are working in, e.g. "acme/api".
20    #[serde(default)]
21    pub repo: Option<String>,
22    /// Branch you are on.
23    #[serde(default)]
24    pub branch: Option<String>,
25    /// Short description of what you are doing right now, e.g. "rewriting the
26    /// token refresh flow". This is what teammates see in list_agents.
27    #[serde(default)]
28    pub activity: Option<String>,
29    /// How long this presence stays valid before you are shown as offline.
30    /// Defaults to 600 (10 minutes).
31    #[serde(default)]
32    pub ttl_seconds: Option<i64>,
33}
34
35#[derive(Debug, Deserialize, JsonSchema)]
36pub struct ListAgentsArgs {
37    /// Only return agents whose presence has not expired.
38    #[serde(default)]
39    pub online_only: bool,
40}
41
42#[tool_router(router = presence_router, vis = "pub")]
43impl Bus {
44    #[tool(
45        description = "Publish what you are currently working on so teammates' agents can \
46                       see it. Call this when you start a piece of work and whenever the \
47                       focus changes. Omitted fields keep their previous value."
48    )]
49    async fn heartbeat(
50        &self,
51        ctx: RequestContext<rmcp::RoleServer>,
52        Parameters(args): Parameters<HeartbeatArgs>,
53    ) -> Result<Json<AgentInfo>, ErrorData> {
54        let auth = auth_of(&ctx)?;
55        let input = presence::HeartbeatInput {
56            status: args.status,
57            repo: args.repo,
58            branch: args.branch,
59            activity: args.activity,
60            ttl_seconds: args.ttl_seconds,
61        };
62        Ok(Json(presence::heartbeat(&self.db, &auth, input).await?))
63    }
64
65    #[tool(
66        description = "See who else is on the bus, whether they are online, and what each \
67                       one is working on. Useful before claiming work or asking a question."
68    )]
69    async fn list_agents(
70        &self,
71        ctx: RequestContext<rmcp::RoleServer>,
72        Parameters(args): Parameters<ListAgentsArgs>,
73    ) -> Result<Json<AgentList>, ErrorData> {
74        let auth = auth_of(&ctx)?;
75        Ok(Json(
76            presence::list_agents(&self.db, &auth, args.online_only).await?,
77        ))
78    }
79}