huskies: merge 563_story_build_agent_join_mechanism_agents_register_with_the_gateway_via_token

This commit is contained in:
dave
2026-04-14 12:02:17 +00:00
parent efe434ede3
commit d0d2b17484
6 changed files with 871 additions and 2 deletions
+100 -1
View File
@@ -56,6 +56,10 @@ struct CliArgs {
rendezvous: Option<String>,
/// Whether `--gateway` mode was requested (proxy MCP calls to per-project containers).
gateway: bool,
/// One-time join token for registering this build agent with a gateway (`--join-token`).
join_token: Option<String>,
/// HTTP URL of the gateway to register with when a join token is provided (`--gateway-url`).
gateway_url: Option<String>,
}
/// Parse CLI arguments into `CliArgs`, or exit early for `--help` / `--version`.
@@ -66,6 +70,8 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
let mut agent = false;
let mut gateway = false;
let mut rendezvous: Option<String> = None;
let mut join_token: Option<String> = None;
let mut gateway_url: Option<String> = None;
let mut i = 0;
while i < args.len() {
@@ -106,6 +112,26 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
let val = &a["--rendezvous=".len()..];
rendezvous = Some(val.to_string());
}
"--join-token" => {
i += 1;
if i >= args.len() {
return Err("--join-token requires a value".to_string());
}
join_token = Some(args[i].clone());
}
a if a.starts_with("--join-token=") => {
join_token = Some(a["--join-token=".len()..].to_string());
}
"--gateway-url" => {
i += 1;
if i >= args.len() {
return Err("--gateway-url requires a value".to_string());
}
gateway_url = Some(args[i].clone());
}
a if a.starts_with("--gateway-url=") => {
gateway_url = Some(a["--gateway-url=".len()..].to_string());
}
"--gateway" => {
gateway = true;
}
@@ -139,6 +165,8 @@ fn parse_cli_args(args: &[String]) -> Result<CliArgs, String> {
agent,
rendezvous,
gateway,
join_token,
gateway_url,
})
}
@@ -172,6 +200,11 @@ fn print_help() {
println!(
" (or cwd) and proxies MCP calls to per-project containers."
);
println!(" --join-token <TOKEN> One-time token for registering this build agent with a");
println!(" gateway. Also readable from HUSKIES_JOIN_TOKEN env var.");
println!(" --gateway-url <URL> HTTP URL of the gateway to register with when");
println!(" --join-token is provided (agent mode only).");
println!(" Also readable from HUSKIES_GATEWAY_URL env var.");
}
/// Resolve the optional positional path argument into an absolute `PathBuf`.
@@ -411,7 +444,16 @@ async fn main() -> Result<(), std::io::Error> {
if is_agent {
let agent_root = app_state.project_root.lock().unwrap().clone();
let rendezvous = agent_rendezvous.expect("agent mode requires --rendezvous");
return agent_mode::run(agent_root, rendezvous, port).await;
// Join token / gateway URL can come from CLI flags or environment variables.
let join_token = cli
.join_token
.clone()
.or_else(|| std::env::var("HUSKIES_JOIN_TOKEN").ok());
let agent_gateway_url = cli
.gateway_url
.clone()
.or_else(|| std::env::var("HUSKIES_GATEWAY_URL").ok());
return agent_mode::run(agent_root, rendezvous, port, join_token, agent_gateway_url).await;
}
let workflow = Arc::new(std::sync::Mutex::new(WorkflowState::default()));
@@ -1087,6 +1129,63 @@ name = "coder"
assert_eq!(result.path, Some("/my/project".to_string()));
}
#[test]
fn parse_join_token_flag() {
let args = vec![
"agent".to_string(),
"--rendezvous".to_string(),
"ws://host:3001/crdt-sync".to_string(),
"--join-token".to_string(),
"my-secret-token".to_string(),
];
let result = parse_cli_args(&args).unwrap();
assert_eq!(result.join_token, Some("my-secret-token".to_string()));
}
#[test]
fn parse_join_token_equals_syntax() {
let args = vec![
"agent".to_string(),
"--rendezvous".to_string(),
"ws://host:3001/crdt-sync".to_string(),
"--join-token=abc123".to_string(),
];
let result = parse_cli_args(&args).unwrap();
assert_eq!(result.join_token, Some("abc123".to_string()));
}
#[test]
fn parse_gateway_url_flag() {
let args = vec![
"agent".to_string(),
"--rendezvous".to_string(),
"ws://host:3001/crdt-sync".to_string(),
"--gateway-url".to_string(),
"http://gateway:3000".to_string(),
];
let result = parse_cli_args(&args).unwrap();
assert_eq!(result.gateway_url, Some("http://gateway:3000".to_string()));
}
#[test]
fn parse_join_token_missing_value_is_error() {
let args = vec!["--join-token".to_string()];
assert!(parse_cli_args(&args).is_err());
}
#[test]
fn parse_gateway_url_missing_value_is_error() {
let args = vec!["--gateway-url".to_string()];
assert!(parse_cli_args(&args).is_err());
}
#[test]
fn parse_no_args_join_token_and_gateway_url_are_none() {
let result = parse_cli_args(&[]).unwrap();
assert_eq!(result.join_token, None);
assert_eq!(result.gateway_url, None);
}
// ── resolve_path_arg ────────────────────────────────────────────
#[test]