@team-agent/installer 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/Cargo.lock +34 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -1
  4. package/crates/team-agent/src/cli/adapters.rs +234 -26
  5. package/crates/team-agent/src/cli/diagnose.rs +144 -10
  6. package/crates/team-agent/src/cli/emit.rs +289 -54
  7. package/crates/team-agent/src/cli/leader.rs +37 -8
  8. package/crates/team-agent/src/cli/mod.rs +1281 -196
  9. package/crates/team-agent/src/cli/status_port.rs +195 -46
  10. package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
  11. package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
  12. package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
  13. package/crates/team-agent/src/cli/tests/run_delegation.rs +59 -3
  14. package/crates/team-agent/src/cli/types.rs +18 -0
  15. package/crates/team-agent/src/compiler.rs +15 -5
  16. package/crates/team-agent/src/coordinator/health.rs +95 -17
  17. package/crates/team-agent/src/coordinator/mod.rs +4 -0
  18. package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
  19. package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
  20. package/crates/team-agent/src/coordinator/tick.rs +222 -69
  21. package/crates/team-agent/src/coordinator/types.rs +15 -3
  22. package/crates/team-agent/src/db/schema.rs +37 -2
  23. package/crates/team-agent/src/diagnose/comms.rs +226 -0
  24. package/crates/team-agent/src/diagnose/mod.rs +45 -0
  25. package/crates/team-agent/src/diagnose/orphans.rs +658 -0
  26. package/crates/team-agent/src/fake_worker.rs +146 -3
  27. package/crates/team-agent/src/leader/start.rs +121 -23
  28. package/crates/team-agent/src/leader/types.rs +44 -1
  29. package/crates/team-agent/src/lib.rs +3 -0
  30. package/crates/team-agent/src/lifecycle/display.rs +645 -47
  31. package/crates/team-agent/src/lifecycle/launch.rs +1061 -146
  32. package/crates/team-agent/src/lifecycle/mod.rs +2 -0
  33. package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
  34. package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
  35. package/crates/team-agent/src/lifecycle/restart/agent.rs +99 -23
  36. package/crates/team-agent/src/lifecycle/restart/common.rs +183 -24
  37. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
  38. package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
  39. package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
  40. package/crates/team-agent/src/lifecycle/restart.rs +24 -1
  41. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
  42. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
  43. package/crates/team-agent/src/lifecycle/types.rs +19 -0
  44. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  45. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
  46. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
  47. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
  48. package/crates/team-agent/src/mcp_server/mod.rs +3 -74
  49. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
  50. package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
  51. package/crates/team-agent/src/mcp_server/tools.rs +312 -111
  52. package/crates/team-agent/src/mcp_server/types.rs +6 -4
  53. package/crates/team-agent/src/mcp_server/wire.rs +19 -7
  54. package/crates/team-agent/src/message_store.rs +21 -4
  55. package/crates/team-agent/src/messaging/delivery.rs +470 -59
  56. package/crates/team-agent/src/messaging/mod.rs +9 -6
  57. package/crates/team-agent/src/messaging/results.rs +353 -63
  58. package/crates/team-agent/src/messaging/selftest.rs +199 -12
  59. package/crates/team-agent/src/messaging/send.rs +35 -3
  60. package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
  61. package/crates/team-agent/src/messaging/types.rs +11 -3
  62. package/crates/team-agent/src/os_probe.rs +119 -0
  63. package/crates/team-agent/src/packaging/migrate.rs +10 -2
  64. package/crates/team-agent/src/packaging/tests.rs +23 -0
  65. package/crates/team-agent/src/provider/adapter.rs +564 -63
  66. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
  67. package/crates/team-agent/src/provider/classify.rs +51 -4
  68. package/crates/team-agent/src/provider/helpers.rs +10 -1
  69. package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
  70. package/crates/team-agent/src/provider/types.rs +47 -0
  71. package/crates/team-agent/src/session_capture.rs +616 -0
  72. package/crates/team-agent/src/state/persist.rs +170 -1
  73. package/crates/team-agent/src/state/projection.rs +141 -8
  74. package/crates/team-agent/src/state/selector.rs +5 -2
  75. package/crates/team-agent/src/tmux_backend.rs +161 -64
  76. package/crates/team-agent/src/transport/test_support.rs +9 -0
  77. package/crates/team-agent/src/transport/tests/wire.rs +4 -0
  78. package/crates/team-agent/src/transport.rs +13 -2
  79. package/package.json +4 -4
@@ -40,19 +40,18 @@ pub fn run(argv: &[String], cwd: &Path) -> ExitCode {
40
40
  println!("{}", command_help(None));
41
41
  return ExitCode::Ok;
42
42
  }
43
- // CR-063/G4: every REGISTERED subcommand's `--help` must short-circuit BEFORE dispatch
44
- // before argument validation, leader-pane checks, or runtime-state writes. The prior
45
- // `command_has_help` whitelist (quick-start/start/stop/...) silently dropped `--help`
46
- // for add-agent / stop-agent / reset-agent / claim-leader / attach-leader, so those
47
- // fell into handlers that emitted "missing agent" or "caller_not_leader_shaped" and
48
- // created `.team/logs/cli-error-*.log` under cwd.
43
+ // CR-063/G4: every registered subcommand's `--help` must short-circuit before dispatch,
44
+ // before argument validation, leader-pane checks, or runtime-state writes.
49
45
  //
50
46
  // The gate stays on KNOWN subcommands so an unknown command still falls through to
51
47
  // the argparse-style invalid-choice path (golden parser.py:84; covered by
52
48
  // `cli_unknown_command_red` and the `claude_code` divergence guard which would
53
49
  // otherwise be silently passthrough-shaped).
54
50
  if is_known_subcommand(command)
55
- && argv.iter().skip(1).any(|arg| matches!(arg.as_str(), "-h" | "--help"))
51
+ && argv
52
+ .iter()
53
+ .skip(1)
54
+ .any(|arg| matches!(arg.as_str(), "-h" | "--help"))
56
55
  {
57
56
  println!("{}", command_help(Some(command)));
58
57
  return ExitCode::Ok;
@@ -78,8 +77,11 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
78
77
  "quick-start" => cmd_quick_start(&quick_start_args(args, cwd)?).map(emit_result),
79
78
  "compile" => cmd_compile(&compile_args(args, cwd)?).map(emit_result),
80
79
  "send" => cmd_send(&send_args(args, cwd)?).map(emit_result),
81
- "allow-peer-talk" => cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result),
82
- "status" => cmd_status(&status_args(args, cwd)).map(emit_result),
80
+ "allow-peer-talk" => {
81
+ cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result)
82
+ }
83
+ "status" => cmd_status_for_team(&status_args(args, cwd), parse_args(args).team.as_deref())
84
+ .map(emit_result),
83
85
  "stop" => cmd_shutdown(&shutdown_args(args, cwd)).map(emit_result),
84
86
  "shutdown" => cmd_shutdown(&shutdown_args(args, cwd)).map(emit_result),
85
87
  "restart" => cmd_restart(&restart_args(args, cwd)).map(emit_result),
@@ -92,9 +94,13 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
92
94
  "remove-agent" => cmd_remove_agent(&remove_agent_args(args, cwd)?).map(emit_result),
93
95
  "stuck-list" => cmd_stuck_list(&stuck_list_args(args, cwd)).map(emit_result),
94
96
  "stuck-cancel" => cmd_stuck_cancel(&stuck_cancel_args(args, cwd)?).map(emit_result),
95
- "acknowledge-idle" => cmd_acknowledge_idle(&acknowledge_idle_args(args, cwd)).map(emit_result),
97
+ "acknowledge-idle" => {
98
+ cmd_acknowledge_idle(&acknowledge_idle_args(args, cwd)).map(emit_result)
99
+ }
96
100
  "takeover" => cmd_takeover(&takeover_args(args, cwd)).map(emit_result),
97
101
  "claim-leader" => cmd_claim_leader(&claim_leader_args(args, cwd)).map(emit_result),
102
+ // Real dispatch: `cmd_attach_leader` writes the `leader_receiver` binding.
103
+ "attach-leader" => cmd_attach_leader(&attach_leader_args(args, cwd)?).map(emit_result),
98
104
  "identity" => cmd_identity(&identity_args(args, cwd)).map(emit_result),
99
105
  "approvals" => cmd_approvals(&approvals_args(args, cwd)).map(emit_result),
100
106
  "inbox" => cmd_inbox(&inbox_args(args, cwd)?).map(emit_result),
@@ -108,7 +114,10 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
108
114
  Ok(ExitCode::Usage)
109
115
  }
110
116
  "validate-result" => cmd_validate_result(&validate_result_args(args)?).map(emit_result),
111
- "collect" => cmd_collect(&collect_args(args, cwd)).map(emit_result),
117
+ "collect" => {
118
+ cmd_collect_for_team(&collect_args(args, cwd), parse_args(args).team.as_deref())
119
+ .map(emit_result)
120
+ }
112
121
  "settle" => cmd_settle(&settle_args(args, cwd)).map(emit_result),
113
122
  "repair-state" => cmd_repair_state(&repair_state_args(args, cwd)?).map(emit_result),
114
123
  "diagnose" => cmd_diagnose(&diagnose_args(args, cwd)).map(emit_result),
@@ -143,6 +152,7 @@ const DISPATCH_COMMANDS: &[&str] = &[
143
152
  "acknowledge-idle",
144
153
  "takeover",
145
154
  "claim-leader",
155
+ "attach-leader",
146
156
  "identity",
147
157
  "approvals",
148
158
  "inbox",
@@ -163,15 +173,15 @@ const DISPATCH_COMMANDS: &[&str] = &[
163
173
  "coordinator",
164
174
  ];
165
175
 
166
- const SPEC_ONLY_HELP_COMMANDS: &[&str] = &["start", "purge-agent", "attach-leader"];
176
+ const SPEC_ONLY_HELP_COMMANDS: &[&str] = &["start", "purge-agent"];
167
177
 
168
178
  fn emit_missing_subcommand_usage() -> ExitCode {
169
179
  emit_usage_error("the following arguments are required: {codex,claude,...,doctor}");
170
180
  ExitCode::Usage
171
181
  }
172
182
 
173
- /// Registered subcommands (the dispatch table) PLUS spec-only verbs that have no
174
- /// dispatch arm yet but must still respond to `--help` per CR-063/G4 (`attach-leader`).
183
+ /// Registered subcommands (the dispatch table) plus spec-only verbs that have no
184
+ /// dispatch arm yet but must still respond to `--help` per CR-063/G4.
175
185
  /// Used by the `--help` short-circuit gate so unknown commands keep falling through
176
186
  /// to the argparse invalid-choice path.
177
187
  fn is_known_subcommand(command: &str) -> bool {
@@ -195,10 +205,10 @@ fn command_help(command: Option<&str>) -> String {
195
205
  Some("compile") => "usage: team-agent compile --team TEAM [--out FILE] [--json]".to_string(),
196
206
  Some("send") => "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--targets AGENTS] [--task TASK] [--sender SENDER] [--watch-result] [--requires-ack|--no-ack] [--no-wait] [--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]".to_string(),
197
207
  Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
198
- Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--summary|--json] [--detail]".to_string(),
208
+ Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]".to_string(),
199
209
  Some("stop") => "usage: team-agent stop [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
200
210
  Some("shutdown") => "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
201
- Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--json]".to_string(),
211
+ Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]".to_string(),
202
212
  Some("restart-agent") => "usage: team-agent restart-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]".to_string(),
203
213
  Some("reset-agent") => "usage: team-agent reset-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]".to_string(),
204
214
  Some("start-agent") => "usage: team-agent start-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--force] [--allow-fresh] [--no-display] [--json]".to_string(),
@@ -212,7 +222,7 @@ fn command_help(command: Option<&str>) -> String {
212
222
  Some("acknowledge-idle") => "usage: team-agent acknowledge-idle [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
213
223
  Some("takeover") => "usage: team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
214
224
  Some("claim-leader") => "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
215
- Some("attach-leader") => "usage: team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]".to_string(),
225
+ Some("attach-leader") => "usage: team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] [--pane PANE] [--provider PROVIDER] [--confirm] [--json]".to_string(),
216
226
  Some("identity") => "usage: team-agent identity [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
217
227
  Some("approvals") => "usage: team-agent approvals [AGENT] [--workspace WORKSPACE] [--json]".to_string(),
218
228
  Some("inbox") => "usage: team-agent inbox AGENT [--workspace WORKSPACE] [--limit N] [--since CURSOR] [--json]".to_string(),
@@ -222,14 +232,14 @@ fn command_help(command: Option<&str>) -> String {
222
232
  Some("validate") => "usage: team-agent validate [SPEC] [--json]".to_string(),
223
233
  Some("profile") => "usage: team-agent profile COMMAND NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--json]".to_string(),
224
234
  Some("validate-result") => "usage: team-agent validate-result [ENVELOPE] [--file FILE|--result JSON] [--json]".to_string(),
225
- Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--result-file FILE] [--json]".to_string(),
226
- Some("settle") => "usage: team-agent settle [--workspace WORKSPACE] [--json]".to_string(),
235
+ Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]".to_string(),
236
+ Some("settle") => "usage: team-agent settle [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
227
237
  Some("repair-state") => "usage: team-agent repair-state --task TASK --status STATUS [SUMMARY] [--assignee AGENT] [--workspace WORKSPACE] [--json]".to_string(),
228
238
  Some("diagnose") => "usage: team-agent diagnose [--workspace WORKSPACE] [--json]".to_string(),
229
239
  Some("preflight") => "usage: team-agent preflight [TEAMDIR] [--json]".to_string(),
230
240
  Some("wait-ready") => "usage: team-agent wait-ready [--workspace WORKSPACE] [--timeout SECONDS] [--json]".to_string(),
231
241
  Some("e2e") => "usage: team-agent e2e [--workspace WORKSPACE] [--providers LIST] [--real] [--json]".to_string(),
232
- Some("peek") => "usage: team-agent peek AGENT [--workspace WORKSPACE] [--tail N] [--allow-raw-screen] [--json]".to_string(),
242
+ Some("peek") => "usage: team-agent peek AGENT [--workspace WORKSPACE] [--tail N|--head N] [--search TEXT] [--allow-raw-screen] [--json]".to_string(),
233
243
  Some("coordinator") => "usage: team-agent coordinator [--workspace WORKSPACE] [--once] [--tick-interval SECONDS]".to_string(),
234
244
  Some(other) => format!("usage: team-agent {other} [options]"),
235
245
  }
@@ -261,8 +271,8 @@ pub fn cmd_validate(args: &ValidateArgs) -> Result<CmdResult, CliError> {
261
271
  fn validate_spec_file(spec_path: &Path) -> Result<Value, CliError> {
262
272
  let text = std::fs::read_to_string(spec_path)?;
263
273
  let base_dir = spec_path.parent().unwrap_or_else(|| Path::new("."));
264
- let spec = crate::model::spec::load_and_validate_spec(&text, base_dir)
265
- .map_err(model_error_to_cli)?;
274
+ let spec =
275
+ crate::model::spec::load_and_validate_spec(&text, base_dir).map_err(model_error_to_cli)?;
266
276
  let team = spec
267
277
  .get("team")
268
278
  .and_then(|team| team.get("name"))
@@ -381,12 +391,28 @@ fn cli_error_log_path(workspace: &Path) -> PathBuf {
381
391
  struct PythonCompactFormatter;
382
392
 
383
393
  impl serde_json::ser::Formatter for PythonCompactFormatter {
384
- fn begin_array_value<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
385
- if first { Ok(()) } else { w.write_all(b", ") }
394
+ fn begin_array_value<W: ?Sized + std::io::Write>(
395
+ &mut self,
396
+ w: &mut W,
397
+ first: bool,
398
+ ) -> std::io::Result<()> {
399
+ if first {
400
+ Ok(())
401
+ } else {
402
+ w.write_all(b", ")
403
+ }
386
404
  }
387
405
 
388
- fn begin_object_key<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
389
- if first { Ok(()) } else { w.write_all(b", ") }
406
+ fn begin_object_key<W: ?Sized + std::io::Write>(
407
+ &mut self,
408
+ w: &mut W,
409
+ first: bool,
410
+ ) -> std::io::Result<()> {
411
+ if first {
412
+ Ok(())
413
+ } else {
414
+ w.write_all(b", ")
415
+ }
390
416
  }
391
417
 
392
418
  fn begin_object_value<W: ?Sized + std::io::Write>(&mut self, w: &mut W) -> std::io::Result<()> {
@@ -430,6 +456,7 @@ struct ParsedArgs {
430
456
  summary: bool,
431
457
  keep_logs: bool,
432
458
  allow_fresh: bool,
459
+ session_converge_deadline_ms: Option<u64>,
433
460
  force: bool,
434
461
  no_display: bool,
435
462
  discard_session: bool,
@@ -452,6 +479,8 @@ struct ParsedArgs {
452
479
  providers: Option<String>,
453
480
  allow_raw_screen: bool,
454
481
  tail: Option<usize>,
482
+ head: Option<usize>,
483
+ search: Option<String>,
455
484
  result_file: Option<PathBuf>,
456
485
  file: Option<PathBuf>,
457
486
  result: Option<String>,
@@ -459,6 +488,8 @@ struct ParsedArgs {
459
488
  assignee: Option<String>,
460
489
  out: Option<PathBuf>,
461
490
  auth_mode: Option<String>,
491
+ pane: Option<String>,
492
+ provider: Option<String>,
462
493
  message_id: Option<String>,
463
494
  }
464
495
 
@@ -486,12 +517,18 @@ fn parse_args(args: &[String]) -> ParsedArgs {
486
517
  "--requires-ack" => parsed.requires_ack = true,
487
518
  "--no-ack" => parsed.no_ack = true,
488
519
  "--no-wait" => parsed.no_wait = true,
489
- "--timeout" => parsed.timeout = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok()),
520
+ "--timeout" => {
521
+ parsed.timeout = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok())
522
+ }
490
523
  "--confirm-human" => parsed.confirm_human = true,
491
524
  "--detail" => parsed.detail = true,
492
525
  "--summary" => parsed.summary = true,
493
526
  "--keep-logs" => parsed.keep_logs = true,
494
527
  "--allow-fresh" => parsed.allow_fresh = true,
528
+ "--session-converge-deadline" => {
529
+ parsed.session_converge_deadline_ms =
530
+ next_arg(args, &mut i).and_then(|v| parse_seconds_ms(&v));
531
+ }
495
532
  "--force" => parsed.force = true,
496
533
  "--no-display" => parsed.no_display = true,
497
534
  "--discard-session" => parsed.discard_session = true,
@@ -501,7 +538,9 @@ fn parse_args(args: &[String]) -> ParsedArgs {
501
538
  "--from-spec" => parsed.from_spec = true,
502
539
  "--confirm" => parsed.confirm = true,
503
540
  "--alert-type" => parsed.alert_type = next_arg(args, &mut i),
504
- "--limit" => parsed.limit = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
541
+ "--limit" => {
542
+ parsed.limit = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok())
543
+ }
505
544
  "--since" => parsed.since = next_arg(args, &mut i),
506
545
  "--gate" => parsed.gate = next_arg(args, &mut i),
507
546
  "--comms" => parsed.comms = true,
@@ -509,11 +548,15 @@ fn parse_args(args: &[String]) -> ParsedArgs {
509
548
  "--fix-schema" => parsed.fix_schema = true,
510
549
  "--cleanup-orphans" => parsed.cleanup_orphans = true,
511
550
  "--once" => parsed.once = true,
512
- "--tick-interval" => parsed.tick_interval = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok()),
551
+ "--tick-interval" => {
552
+ parsed.tick_interval = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok())
553
+ }
513
554
  "--status" => parsed.status_value = next_arg(args, &mut i),
514
555
  "--providers" => parsed.providers = next_arg(args, &mut i),
515
556
  "--allow-raw-screen" => parsed.allow_raw_screen = true,
516
557
  "--tail" => parsed.tail = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
558
+ "--head" => parsed.head = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
559
+ "--search" => parsed.search = next_arg(args, &mut i),
517
560
  "--result-file" => parsed.result_file = next_arg(args, &mut i).map(PathBuf::from),
518
561
  "--file" => parsed.file = next_arg(args, &mut i).map(PathBuf::from),
519
562
  "--result" => parsed.result = next_arg(args, &mut i),
@@ -521,11 +564,19 @@ fn parse_args(args: &[String]) -> ParsedArgs {
521
564
  "--assignee" => parsed.assignee = next_arg(args, &mut i),
522
565
  "--out" => parsed.out = next_arg(args, &mut i).map(PathBuf::from),
523
566
  "--auth-mode" => parsed.auth_mode = next_arg(args, &mut i),
567
+ "--pane" => parsed.pane = next_arg(args, &mut i),
568
+ "--provider" => parsed.provider = next_arg(args, &mut i),
524
569
  "--message-id" => parsed.message_id = next_arg(args, &mut i),
525
570
  "-h" | "--help" => {}
526
571
  other if other.starts_with("--team=") => {
527
572
  parsed.team = Some(other.trim_start_matches("--team=").to_string());
528
573
  }
574
+ other if other.starts_with("--pane=") => {
575
+ parsed.pane = Some(other.trim_start_matches("--pane=").to_string());
576
+ }
577
+ other if other.starts_with("--provider=") => {
578
+ parsed.provider = Some(other.trim_start_matches("--provider=").to_string());
579
+ }
529
580
  other if other.starts_with('-') => {}
530
581
  other => parsed.positionals.push(other.to_string()),
531
582
  }
@@ -539,8 +590,26 @@ fn next_arg(args: &[String], index: &mut usize) -> Option<String> {
539
590
  args.get(*index).cloned()
540
591
  }
541
592
 
593
+ fn parse_seconds_ms(raw: &str) -> Option<u64> {
594
+ let seconds = raw.parse::<f64>().ok()?;
595
+ if seconds.is_finite() && seconds >= 0.0 {
596
+ Some((seconds * 1000.0).round() as u64)
597
+ } else {
598
+ None
599
+ }
600
+ }
601
+
602
+ fn parse_cli_provider(raw: Option<&str>) -> Result<crate::provider::Provider, CliError> {
603
+ let raw = raw.unwrap_or("codex");
604
+ serde_json::from_value::<crate::provider::Provider>(serde_json::json!(raw))
605
+ .map_err(|_| CliError::Runtime(format!("unknown provider: {raw}")))
606
+ }
607
+
542
608
  fn workspace(parsed: &ParsedArgs, cwd: &Path) -> PathBuf {
543
- parsed.workspace.clone().unwrap_or_else(|| cwd.to_path_buf())
609
+ parsed
610
+ .workspace
611
+ .clone()
612
+ .unwrap_or_else(|| cwd.to_path_buf())
544
613
  }
545
614
 
546
615
  fn required_pos(parsed: &ParsedArgs, index: usize, name: &str) -> Result<String, CliError> {
@@ -565,6 +634,7 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
565
634
  workspace.join(agents_dir)
566
635
  };
567
636
  Ok(QuickStartArgs {
637
+ workspace,
568
638
  agents_dir,
569
639
  name: parsed.name,
570
640
  team_id: parsed.team_id.or(parsed.team),
@@ -590,7 +660,9 @@ fn compile_args(args: &[String], cwd: &Path) -> Result<CompileArgs, CliError> {
590
660
  .as_deref()
591
661
  .map(PathBuf::from)
592
662
  .ok_or_else(|| CliError::Usage("missing --team".to_string()))?;
593
- let out = parsed.out.unwrap_or_else(|| PathBuf::from("team.spec.yaml"));
663
+ let out = parsed
664
+ .out
665
+ .unwrap_or_else(|| PathBuf::from("team.spec.yaml"));
594
666
  Ok(CompileArgs {
595
667
  team: resolve_cli_path(cwd, &team),
596
668
  out: resolve_cli_path(cwd, &out),
@@ -617,7 +689,12 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
617
689
  let workspace = workspace(&parsed, cwd);
618
690
  Ok(SendArgs {
619
691
  target,
620
- message: parsed.positionals.iter().skip(message_start).cloned().collect(),
692
+ message: parsed
693
+ .positionals
694
+ .iter()
695
+ .skip(message_start)
696
+ .cloned()
697
+ .collect(),
621
698
  targets: parsed.targets,
622
699
  workspace,
623
700
  team: parsed.team,
@@ -702,6 +779,21 @@ fn claim_leader_args(args: &[String], cwd: &Path) -> ClaimLeaderArgs {
702
779
  }
703
780
  }
704
781
 
782
+ fn attach_leader_args(args: &[String], cwd: &Path) -> Result<AttachLeaderArgs, CliError> {
783
+ let parsed = parse_args(args);
784
+ Ok(AttachLeaderArgs {
785
+ workspace: workspace(&parsed, cwd),
786
+ team: parsed.team,
787
+ pane: parsed
788
+ .pane
789
+ .filter(|pane| !pane.is_empty())
790
+ .map(crate::transport::PaneId::new),
791
+ provider: parse_cli_provider(parsed.provider.as_deref())?,
792
+ confirm: parsed.confirm,
793
+ json: parsed.json,
794
+ })
795
+ }
796
+
705
797
  fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
706
798
  let parsed = parse_args(args);
707
799
  IdentityArgs {
@@ -731,6 +823,7 @@ fn restart_args(args: &[String], cwd: &Path) -> RestartArgs {
731
823
  .unwrap_or_else(|| workspace(&parsed, cwd)),
732
824
  team: parsed.team,
733
825
  allow_fresh: parsed.allow_fresh,
826
+ session_converge_deadline_ms: parsed.session_converge_deadline_ms,
734
827
  json: parsed.json,
735
828
  }
736
829
  }
@@ -776,7 +869,9 @@ fn add_agent_args(args: &[String], cwd: &Path) -> Result<AddAgentArgs, CliError>
776
869
  agent: required_pos(&parsed, 0, "agent")?,
777
870
  workspace: workspace(&parsed, cwd),
778
871
  team: parsed.team,
779
- role_file: parsed.role_file.ok_or_else(|| CliError::Usage("missing --role-file".to_string()))?,
872
+ role_file: parsed
873
+ .role_file
874
+ .ok_or_else(|| CliError::Usage("missing --role-file".to_string()))?,
780
875
  no_display: parsed.no_display,
781
876
  json: parsed.json,
782
877
  })
@@ -788,7 +883,9 @@ fn fork_agent_args(args: &[String], cwd: &Path) -> Result<ForkAgentArgs, CliErro
788
883
  source_agent: required_pos(&parsed, 0, "source_agent")?,
789
884
  workspace: workspace(&parsed, cwd),
790
885
  team: parsed.team,
791
- as_agent: parsed.as_agent.ok_or_else(|| CliError::Usage("missing --as".to_string()))?,
886
+ as_agent: parsed
887
+ .as_agent
888
+ .ok_or_else(|| CliError::Usage("missing --as".to_string()))?,
792
889
  label: parsed.label,
793
890
  no_display: parsed.no_display,
794
891
  json: parsed.json,
@@ -934,7 +1031,9 @@ fn repair_state_args(args: &[String], cwd: &Path) -> Result<RepairStateArgs, Cli
934
1031
  let parsed = parse_args(args);
935
1032
  Ok(RepairStateArgs {
936
1033
  workspace: workspace(&parsed, cwd),
937
- task_id: parsed.task.ok_or_else(|| CliError::Usage("missing --task".to_string()))?,
1034
+ task_id: parsed
1035
+ .task
1036
+ .ok_or_else(|| CliError::Usage("missing --task".to_string()))?,
938
1037
  assignee: parsed.assignee,
939
1038
  status: parsed
940
1039
  .status_value
@@ -1024,6 +1123,8 @@ fn peek_args(args: &[String], cwd: &Path) -> Result<PeekArgs, CliError> {
1024
1123
  agent: required_pos(&parsed, 0, "agent")?,
1025
1124
  workspace: workspace(&parsed, cwd),
1026
1125
  tail: parsed.tail.unwrap_or(80),
1126
+ head: parsed.head,
1127
+ search: parsed.search,
1027
1128
  allow_raw_screen: parsed.allow_raw_screen,
1028
1129
  json: parsed.json,
1029
1130
  })
@@ -1061,13 +1162,23 @@ fn json_dumps_like(value: &Value) -> String {
1061
1162
  Err(_) => "\"\"".to_string(),
1062
1163
  },
1063
1164
  Value::Array(arr) => {
1064
- let inner = arr.iter().map(json_dumps_like).collect::<Vec<_>>().join(", ");
1165
+ let inner = arr
1166
+ .iter()
1167
+ .map(json_dumps_like)
1168
+ .collect::<Vec<_>>()
1169
+ .join(", ");
1065
1170
  format!("[{inner}]")
1066
1171
  }
1067
1172
  Value::Object(obj) => {
1068
1173
  let inner = obj
1069
1174
  .iter()
1070
- .map(|(k, v)| format!("{}: {}", json_dumps_like(&Value::String(k.clone())), json_dumps_like(v)))
1175
+ .map(|(k, v)| {
1176
+ format!(
1177
+ "{}: {}",
1178
+ json_dumps_like(&Value::String(k.clone())),
1179
+ json_dumps_like(v)
1180
+ )
1181
+ })
1071
1182
  .collect::<Vec<_>>()
1072
1183
  .join(", ");
1073
1184
  format!("{{{inner}}}")
@@ -1159,26 +1270,144 @@ mod tests {
1159
1270
  #[test]
1160
1271
  fn t0_help_catalog_lists_command_flags() {
1161
1272
  for (command, flags) in [
1162
- ("quick-start", &["--workspace", "--team-id", "--yes", "--fresh", "--json"][..]),
1163
- ("send", &["--workspace", "--team", "--targets", "--watch-result", "--timeout", "--json"][..]),
1164
- ("status", &["--workspace", "--summary", "--json", "--detail"][..]),
1165
- ("shutdown", &["--workspace", "--team", "--keep-logs", "--json"][..]),
1166
- ("restart", &["--team", "--allow-fresh", "--json"][..]),
1167
- ("start-agent", &["--workspace", "--team", "--force", "--allow-fresh", "--no-display", "--json"][..]),
1168
- ("reset-agent", &["--workspace", "--team", "--discard-session", "--no-display", "--json"][..]),
1169
- ("add-agent", &["--role-file", "--workspace", "--team", "--no-display", "--json"][..]),
1170
- ("fork-agent", &["--as", "--label", "--workspace", "--team", "--no-display", "--json"][..]),
1171
- ("remove-agent", &["--workspace", "--team", "--from-spec", "--confirm", "--force", "--json"][..]),
1172
- ("doctor", &["--workspace", "--team", "--gate", "--fix-schema", "--cleanup-orphans", "--json"][..]),
1173
- ("collect", &["--workspace", "--result-file", "--json"][..]),
1174
- ("repair-state", &["--task", "--status", "--assignee", "--workspace", "--json"][..]),
1273
+ (
1274
+ "quick-start",
1275
+ &["--workspace", "--team-id", "--yes", "--fresh", "--json"][..],
1276
+ ),
1277
+ (
1278
+ "send",
1279
+ &[
1280
+ "--workspace",
1281
+ "--team",
1282
+ "--targets",
1283
+ "--watch-result",
1284
+ "--timeout",
1285
+ "--json",
1286
+ ][..],
1287
+ ),
1288
+ (
1289
+ "status",
1290
+ &["--workspace", "--team", "--summary", "--json", "--detail"][..],
1291
+ ),
1292
+ (
1293
+ "shutdown",
1294
+ &["--workspace", "--team", "--keep-logs", "--json"][..],
1295
+ ),
1296
+ (
1297
+ "restart",
1298
+ &[
1299
+ "--team",
1300
+ "--allow-fresh",
1301
+ "--session-converge-deadline",
1302
+ "--json",
1303
+ ][..],
1304
+ ),
1305
+ (
1306
+ "start-agent",
1307
+ &[
1308
+ "--workspace",
1309
+ "--team",
1310
+ "--force",
1311
+ "--allow-fresh",
1312
+ "--no-display",
1313
+ "--json",
1314
+ ][..],
1315
+ ),
1316
+ (
1317
+ "reset-agent",
1318
+ &[
1319
+ "--workspace",
1320
+ "--team",
1321
+ "--discard-session",
1322
+ "--no-display",
1323
+ "--json",
1324
+ ][..],
1325
+ ),
1326
+ (
1327
+ "add-agent",
1328
+ &[
1329
+ "--role-file",
1330
+ "--workspace",
1331
+ "--team",
1332
+ "--no-display",
1333
+ "--json",
1334
+ ][..],
1335
+ ),
1336
+ (
1337
+ "fork-agent",
1338
+ &[
1339
+ "--as",
1340
+ "--label",
1341
+ "--workspace",
1342
+ "--team",
1343
+ "--no-display",
1344
+ "--json",
1345
+ ][..],
1346
+ ),
1347
+ (
1348
+ "remove-agent",
1349
+ &[
1350
+ "--workspace",
1351
+ "--team",
1352
+ "--from-spec",
1353
+ "--confirm",
1354
+ "--force",
1355
+ "--json",
1356
+ ][..],
1357
+ ),
1358
+ (
1359
+ "doctor",
1360
+ &[
1361
+ "--workspace",
1362
+ "--team",
1363
+ "--gate",
1364
+ "--fix-schema",
1365
+ "--cleanup-orphans",
1366
+ "--json",
1367
+ ][..],
1368
+ ),
1369
+ (
1370
+ "attach-leader",
1371
+ &[
1372
+ "--workspace",
1373
+ "--team",
1374
+ "--pane",
1375
+ "--provider",
1376
+ "--confirm",
1377
+ "--json",
1378
+ ][..],
1379
+ ),
1380
+ (
1381
+ "collect",
1382
+ &["--workspace", "--team", "--result-file", "--json"][..],
1383
+ ),
1384
+ (
1385
+ "repair-state",
1386
+ &["--task", "--status", "--assignee", "--workspace", "--json"][..],
1387
+ ),
1175
1388
  ("wait-ready", &["--workspace", "--timeout", "--json"][..]),
1176
- ("peek", &["--workspace", "--tail", "--allow-raw-screen", "--json"][..]),
1177
- ("coordinator", &["--workspace", "--once", "--tick-interval"][..]),
1389
+ (
1390
+ "peek",
1391
+ &[
1392
+ "--workspace",
1393
+ "--tail",
1394
+ "--head",
1395
+ "--search",
1396
+ "--allow-raw-screen",
1397
+ "--json",
1398
+ ][..],
1399
+ ),
1400
+ (
1401
+ "coordinator",
1402
+ &["--workspace", "--once", "--tick-interval"][..],
1403
+ ),
1178
1404
  ] {
1179
1405
  let help = command_help(Some(command));
1180
1406
  for flag in flags {
1181
- assert!(help.contains(flag), "`team-agent {command} --help` is missing {flag}");
1407
+ assert!(
1408
+ help.contains(flag),
1409
+ "`team-agent {command} --help` is missing {flag}"
1410
+ );
1182
1411
  }
1183
1412
  }
1184
1413
  }
@@ -1188,7 +1417,13 @@ mod tests {
1188
1417
  let cwd = tmp_workspace();
1189
1418
  let ws = tmp_workspace();
1190
1419
  let args = quick_start_args(
1191
- &cli_argv(&["--workspace", &ws.to_string_lossy(), "agents", "--yes", "--json"]),
1420
+ &cli_argv(&[
1421
+ "--workspace",
1422
+ &ws.to_string_lossy(),
1423
+ "agents",
1424
+ "--yes",
1425
+ "--json",
1426
+ ]),
1192
1427
  &cwd,
1193
1428
  )
1194
1429
  .unwrap();