cellstate/entity/
handoff.rs

1//! `cellstate handoff` — Manage handoffs (agent-to-agent work transfers).
2//!
3//! Subcommands: create, get, accept, complete.
4
5use anyhow::Result;
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{CreateHandoffRequest, HandoffResponse};
9
10use super::client::ApiClient;
11use super::output::OutputConfig;
12
13const BASE: &str = "/api/v1/handoffs";
14
15pub fn build_command() -> Command {
16    Command::new("handoff")
17        .about("Manage handoffs (agent-to-agent work transfers)")
18        .subcommand_required(true)
19        .subcommand(
20            Command::new("create")
21                .about("Create a new handoff")
22                .arg(
23                    Arg::new("from-agent-id")
24                        .long("from-agent-id")
25                        .required(true)
26                        .help("Agent initiating the handoff"),
27                )
28                .arg(
29                    Arg::new("to-agent-id")
30                        .long("to-agent-id")
31                        .required(true)
32                        .help("Agent receiving the handoff"),
33                )
34                .arg(
35                    Arg::new("trajectory-id")
36                        .long("trajectory-id")
37                        .help("Trajectory being handed off (defaults to session trajectory)"),
38                )
39                .arg(
40                    Arg::new("scope-id")
41                        .long("scope-id")
42                        .help("Current scope (defaults to session scope)"),
43                )
44                .arg(
45                    Arg::new("reason")
46                        .long("reason")
47                        .required(true)
48                        .help("Reason for handoff"),
49                )
50                .arg(
51                    Arg::new("context-snapshot")
52                        .long("context-snapshot")
53                        .default_value("")
54                        .help("Context snapshot to transfer (as a string)"),
55                ),
56        )
57        .subcommand(
58            Command::new("get")
59                .about("Get handoff details")
60                .arg(Arg::new("id").required(true).help("Handoff ID")),
61        )
62        .subcommand(
63            Command::new("accept")
64                .about("Accept a handoff")
65                .arg(Arg::new("id").required(true).help("Handoff ID")),
66        )
67        .subcommand(
68            Command::new("complete")
69                .about("Complete a handoff")
70                .arg(Arg::new("id").required(true).help("Handoff ID")),
71        )
72}
73
74pub async fn dispatch(
75    matches: &ArgMatches,
76    client: &ApiClient,
77    output: &OutputConfig,
78    session: &crate::session::CliSession,
79) -> Result<()> {
80    match matches.subcommand() {
81        Some(("create", sub)) => {
82            let req = CreateHandoffRequest {
83                from_agent_id: sub.get_one::<String>("from-agent-id").unwrap().parse()?,
84                to_agent_id: sub.get_one::<String>("to-agent-id").unwrap().parse()?,
85                trajectory_id: super::require_arg(
86                    sub,
87                    "trajectory-id",
88                    session.trajectory_id.as_deref(),
89                    "trajectory",
90                )?
91                .parse()?,
92                scope_id: super::require_arg(
93                    sub,
94                    "scope-id",
95                    session.scope_id.as_deref(),
96                    "scope",
97                )?
98                .parse()?,
99                reason: sub.get_one::<String>("reason").unwrap().clone(),
100                context_snapshot: sub
101                    .get_one::<String>("context-snapshot")
102                    .unwrap()
103                    .as_bytes()
104                    .to_vec(),
105            };
106            let resp: HandoffResponse = client.post(BASE, &req).await?;
107            output.print(&resp);
108        }
109        Some(("get", sub)) => {
110            let id = sub.get_one::<String>("id").unwrap();
111            let resp: HandoffResponse = client.get(&format!("{BASE}/{id}")).await?;
112            output.print(&resp);
113        }
114        Some(("accept", sub)) => {
115            let id = sub.get_one::<String>("id").unwrap();
116            let resp: serde_json::Value = client
117                .post_no_body_raw(&format!("{BASE}/{id}/accept"))
118                .await?;
119            output.print_value(&resp);
120        }
121        Some(("complete", sub)) => {
122            let id = sub.get_one::<String>("id").unwrap();
123            let resp: serde_json::Value = client
124                .post_no_body_raw(&format!("{BASE}/{id}/complete"))
125                .await?;
126            output.print_value(&resp);
127        }
128        _ => unreachable!("subcommand_required(true) prevents this"),
129    }
130    Ok(())
131}