ai_crew_sync/tools/
presence.rs1use 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 #[serde(default)]
18 pub status: Option<String>,
19 #[serde(default)]
21 pub repo: Option<String>,
22 #[serde(default)]
24 pub branch: Option<String>,
25 #[serde(default)]
28 pub activity: Option<String>,
29 #[serde(default)]
32 pub ttl_seconds: Option<i64>,
33}
34
35#[derive(Debug, Deserialize, JsonSchema)]
36pub struct ListAgentsArgs {
37 #[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}