cellstate/entity/
handoff.rs1use 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}