cellstate/entity/
delegation.rs

1//! `cellstate delegation` — Manage task delegations between agents.
2//!
3//! Subcommands: create, get, accept, reject, complete.
4
5use anyhow::{anyhow, Result};
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{
9    CreateDelegationRequest, DelegationResponse, DelegationResultRequest,
10};
11use cellstate_core::DelegationResultStatus;
12
13use super::client::ApiClient;
14use super::output::OutputConfig;
15
16const BASE: &str = "/api/v1/delegations";
17
18pub fn build_command() -> Command {
19    Command::new("delegation")
20        .about("Manage task delegations between agents")
21        .subcommand_required(true)
22        .subcommand(
23            Command::new("create")
24                .about("Create a new delegation")
25                .arg(
26                    Arg::new("from-agent-id")
27                        .long("from-agent-id")
28                        .required(true)
29                        .help("Agent delegating the task"),
30                )
31                .arg(
32                    Arg::new("to-agent-id")
33                        .long("to-agent-id")
34                        .required(true)
35                        .help("Agent receiving the delegation"),
36                )
37                .arg(
38                    Arg::new("trajectory-id")
39                        .long("trajectory-id")
40                        .help("Trajectory for the delegated task (defaults to session trajectory)"),
41                )
42                .arg(
43                    Arg::new("scope-id")
44                        .long("scope-id")
45                        .help("Scope for the delegated task (defaults to session scope)"),
46                )
47                .arg(
48                    Arg::new("task-description")
49                        .long("task-description")
50                        .required(true)
51                        .help("Task description"),
52                )
53                .arg(
54                    Arg::new("context")
55                        .long("context")
56                        .help("Additional context (JSON string)"),
57                ),
58        )
59        .subcommand(
60            Command::new("get")
61                .about("Get delegation details")
62                .arg(Arg::new("id").required(true).help("Delegation ID")),
63        )
64        .subcommand(
65            Command::new("accept")
66                .about("Accept a delegation")
67                .arg(Arg::new("id").required(true).help("Delegation ID"))
68                .arg(
69                    Arg::new("agent-id")
70                        .long("agent-id")
71                        .help("Agent accepting the delegation (defaults to session agent)"),
72                ),
73        )
74        .subcommand(
75            Command::new("reject")
76                .about("Reject a delegation")
77                .arg(Arg::new("id").required(true).help("Delegation ID"))
78                .arg(
79                    Arg::new("agent-id")
80                        .long("agent-id")
81                        .help("Agent rejecting the delegation (defaults to session agent)"),
82                )
83                .arg(
84                    Arg::new("reason")
85                        .long("reason")
86                        .required(true)
87                        .help("Reason for rejection"),
88                ),
89        )
90        .subcommand(
91            Command::new("complete")
92                .about("Complete a delegation with results")
93                .arg(Arg::new("id").required(true).help("Delegation ID"))
94                .arg(
95                    Arg::new("agent-id")
96                        .long("agent-id")
97                        .help("Agent completing the delegation (defaults to session agent)"),
98                )
99                .arg(
100                    Arg::new("status")
101                        .long("status")
102                        .required(true)
103                        .help("Result status (success, partial, failure)"),
104                )
105                .arg(
106                    Arg::new("output")
107                        .long("output")
108                        .help("Output from the delegated task"),
109                )
110                .arg(
111                    Arg::new("error")
112                        .long("error")
113                        .help("Error message (if failed)"),
114                ),
115        )
116}
117
118pub async fn dispatch(
119    matches: &ArgMatches,
120    client: &ApiClient,
121    output: &OutputConfig,
122    session: &crate::session::CliSession,
123) -> Result<()> {
124    match matches.subcommand() {
125        Some(("create", sub)) => {
126            let req = CreateDelegationRequest {
127                from_agent_id: sub.get_one::<String>("from-agent-id").unwrap().parse()?,
128                to_agent_id: sub.get_one::<String>("to-agent-id").unwrap().parse()?,
129                trajectory_id: super::require_arg(
130                    sub,
131                    "trajectory-id",
132                    session.trajectory_id.as_deref(),
133                    "trajectory",
134                )?
135                .parse()?,
136                scope_id: super::require_arg(
137                    sub,
138                    "scope-id",
139                    session.scope_id.as_deref(),
140                    "scope",
141                )?
142                .parse()?,
143                task_description: sub.get_one::<String>("task-description").unwrap().clone(),
144                expected_completion: None,
145                context: sub
146                    .get_one::<String>("context")
147                    .map(|s| serde_json::from_str(s))
148                    .transpose()
149                    .map_err(|e| anyhow!("Invalid context JSON: {e}"))?,
150            };
151            let resp: DelegationResponse = client.post(BASE, &req).await?;
152            output.print(&resp);
153        }
154        Some(("get", sub)) => {
155            let id = sub.get_one::<String>("id").unwrap();
156            let resp: DelegationResponse = client.get(&format!("{BASE}/{id}")).await?;
157            output.print(&resp);
158        }
159        Some(("accept", sub)) => {
160            let id = sub.get_one::<String>("id").unwrap();
161            let agent_id =
162                super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
163            let req = serde_json::json!({
164                "accepting_agent_id": agent_id,
165            });
166            let resp: serde_json::Value = client
167                .post_raw(&format!("{BASE}/{id}/accept"), &req)
168                .await?;
169            output.print_value(&resp);
170        }
171        Some(("reject", sub)) => {
172            let id = sub.get_one::<String>("id").unwrap();
173            let agent_id =
174                super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
175            let reason = sub.get_one::<String>("reason").unwrap();
176            let req = serde_json::json!({
177                "rejecting_agent_id": agent_id,
178                "reason": reason,
179            });
180            let resp: serde_json::Value = client
181                .post_raw(&format!("{BASE}/{id}/reject"), &req)
182                .await?;
183            output.print_value(&resp);
184        }
185        Some(("complete", sub)) => {
186            let id = sub.get_one::<String>("id").unwrap();
187            let agent_id =
188                super::require_arg(sub, "agent-id", session.agent_id.as_deref(), "agent")?;
189            let status: DelegationResultStatus =
190                sub.get_one::<String>("status").unwrap().parse()?;
191            let result = DelegationResultRequest {
192                status,
193                output: sub.get_one::<String>("output").cloned(),
194                artifacts: vec![],
195                error: sub.get_one::<String>("error").cloned(),
196            };
197            let req = serde_json::json!({
198                "completing_agent_id": agent_id,
199                "result": result,
200            });
201            let resp: serde_json::Value = client
202                .post_raw(&format!("{BASE}/{id}/complete"), &req)
203                .await?;
204            output.print_value(&resp);
205        }
206        _ => unreachable!("subcommand_required(true) prevents this"),
207    }
208    Ok(())
209}