@team-agent/installer 0.3.2 → 0.3.4

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 (82) 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 +196 -19
  5. package/crates/team-agent/src/cli/diagnose.rs +145 -11
  6. package/crates/team-agent/src/cli/emit.rs +287 -53
  7. package/crates/team-agent/src/cli/leader.rs +37 -8
  8. package/crates/team-agent/src/cli/mod.rs +807 -316
  9. package/crates/team-agent/src/cli/status_port.rs +25 -2
  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 +57 -3
  14. package/crates/team-agent/src/cli/types.rs +17 -0
  15. package/crates/team-agent/src/compiler/tests.rs +2 -2
  16. package/crates/team-agent/src/compiler.rs +16 -6
  17. package/crates/team-agent/src/coordinator/health.rs +89 -20
  18. package/crates/team-agent/src/coordinator/mod.rs +4 -0
  19. package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
  20. package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
  21. package/crates/team-agent/src/coordinator/tests/watch.rs +4 -2
  22. package/crates/team-agent/src/coordinator/tick.rs +222 -69
  23. package/crates/team-agent/src/coordinator/types.rs +15 -3
  24. package/crates/team-agent/src/db/schema.rs +37 -2
  25. package/crates/team-agent/src/diagnose/comms.rs +226 -0
  26. package/crates/team-agent/src/diagnose/mod.rs +45 -0
  27. package/crates/team-agent/src/diagnose/orphans.rs +658 -0
  28. package/crates/team-agent/src/fake_worker.rs +146 -3
  29. package/crates/team-agent/src/leader/start.rs +121 -23
  30. package/crates/team-agent/src/leader/types.rs +44 -1
  31. package/crates/team-agent/src/lib.rs +3 -0
  32. package/crates/team-agent/src/lifecycle/display.rs +648 -50
  33. package/crates/team-agent/src/lifecycle/launch.rs +1048 -264
  34. package/crates/team-agent/src/lifecycle/mod.rs +3 -0
  35. package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
  36. package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
  37. package/crates/team-agent/src/lifecycle/restart/agent.rs +113 -26
  38. package/crates/team-agent/src/lifecycle/restart/common.rs +189 -102
  39. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +465 -25
  40. package/crates/team-agent/src/lifecycle/restart/remove.rs +22 -6
  41. package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
  42. package/crates/team-agent/src/lifecycle/restart.rs +4 -1
  43. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -4
  44. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
  45. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +39 -9
  46. package/crates/team-agent/src/lifecycle/types.rs +23 -0
  47. package/crates/team-agent/src/lifecycle/worker_command_context.rs +326 -0
  48. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  49. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
  50. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
  51. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
  52. package/crates/team-agent/src/mcp_server/mod.rs +3 -74
  53. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
  54. package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
  55. package/crates/team-agent/src/mcp_server/tools.rs +312 -111
  56. package/crates/team-agent/src/mcp_server/types.rs +6 -4
  57. package/crates/team-agent/src/mcp_server/wire.rs +19 -7
  58. package/crates/team-agent/src/message_store.rs +21 -4
  59. package/crates/team-agent/src/messaging/delivery.rs +87 -37
  60. package/crates/team-agent/src/messaging/mod.rs +9 -6
  61. package/crates/team-agent/src/messaging/results.rs +153 -16
  62. package/crates/team-agent/src/messaging/selftest.rs +199 -12
  63. package/crates/team-agent/src/messaging/send.rs +35 -3
  64. package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
  65. package/crates/team-agent/src/messaging/types.rs +11 -3
  66. package/crates/team-agent/src/os_probe.rs +119 -0
  67. package/crates/team-agent/src/packaging/migrate.rs +10 -2
  68. package/crates/team-agent/src/packaging/tests.rs +23 -0
  69. package/crates/team-agent/src/provider/adapter.rs +483 -67
  70. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
  71. package/crates/team-agent/src/provider/classify.rs +51 -4
  72. package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
  73. package/crates/team-agent/src/provider/types.rs +47 -0
  74. package/crates/team-agent/src/session_capture.rs +616 -0
  75. package/crates/team-agent/src/state/persist.rs +57 -0
  76. package/crates/team-agent/src/state/projection.rs +32 -23
  77. package/crates/team-agent/src/state/selector.rs +5 -2
  78. package/crates/team-agent/src/tmux_backend.rs +151 -60
  79. package/crates/team-agent/src/transport/test_support.rs +9 -0
  80. package/crates/team-agent/src/transport/tests/wire.rs +4 -0
  81. package/crates/team-agent/src/transport.rs +13 -2
  82. 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_for_team(&status_args(args, cwd), parse_args(args).team.as_deref()).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_for_team(&collect_args(args, cwd), parse_args(args).team.as_deref()).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 {
@@ -190,7 +200,7 @@ fn command_help(command: Option<&str>) -> String {
190
200
  )
191
201
  }
192
202
  Some("init") => "usage: team-agent init [--workspace WORKSPACE] [--force] [--json]".to_string(),
193
- Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--fresh] [--json]".to_string(),
203
+ Some("quick-start") => "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--fresh] [--json]\n\ndefaults: display_backend=none; set display_backend: adaptive in TEAM.md to opt in to adaptive display windows.".to_string(),
194
204
  Some("start") => "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]".to_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(),
@@ -198,7 +208,7 @@ fn command_help(command: Option<&str>) -> String {
198
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(),
@@ -223,13 +233,13 @@ fn command_help(command: Option<&str>) -> 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
235
  Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]".to_string(),
226
- Some("settle") => "usage: team-agent settle [--workspace WORKSPACE] [--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> {
@@ -591,7 +660,9 @@ fn compile_args(args: &[String], cwd: &Path) -> Result<CompileArgs, CliError> {
591
660
  .as_deref()
592
661
  .map(PathBuf::from)
593
662
  .ok_or_else(|| CliError::Usage("missing --team".to_string()))?;
594
- 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"));
595
666
  Ok(CompileArgs {
596
667
  team: resolve_cli_path(cwd, &team),
597
668
  out: resolve_cli_path(cwd, &out),
@@ -618,7 +689,12 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
618
689
  let workspace = workspace(&parsed, cwd);
619
690
  Ok(SendArgs {
620
691
  target,
621
- message: parsed.positionals.iter().skip(message_start).cloned().collect(),
692
+ message: parsed
693
+ .positionals
694
+ .iter()
695
+ .skip(message_start)
696
+ .cloned()
697
+ .collect(),
622
698
  targets: parsed.targets,
623
699
  workspace,
624
700
  team: parsed.team,
@@ -703,6 +779,21 @@ fn claim_leader_args(args: &[String], cwd: &Path) -> ClaimLeaderArgs {
703
779
  }
704
780
  }
705
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
+
706
797
  fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
707
798
  let parsed = parse_args(args);
708
799
  IdentityArgs {
@@ -732,6 +823,7 @@ fn restart_args(args: &[String], cwd: &Path) -> RestartArgs {
732
823
  .unwrap_or_else(|| workspace(&parsed, cwd)),
733
824
  team: parsed.team,
734
825
  allow_fresh: parsed.allow_fresh,
826
+ session_converge_deadline_ms: parsed.session_converge_deadline_ms,
735
827
  json: parsed.json,
736
828
  }
737
829
  }
@@ -777,7 +869,9 @@ fn add_agent_args(args: &[String], cwd: &Path) -> Result<AddAgentArgs, CliError>
777
869
  agent: required_pos(&parsed, 0, "agent")?,
778
870
  workspace: workspace(&parsed, cwd),
779
871
  team: parsed.team,
780
- 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()))?,
781
875
  no_display: parsed.no_display,
782
876
  json: parsed.json,
783
877
  })
@@ -789,7 +883,9 @@ fn fork_agent_args(args: &[String], cwd: &Path) -> Result<ForkAgentArgs, CliErro
789
883
  source_agent: required_pos(&parsed, 0, "source_agent")?,
790
884
  workspace: workspace(&parsed, cwd),
791
885
  team: parsed.team,
792
- 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()))?,
793
889
  label: parsed.label,
794
890
  no_display: parsed.no_display,
795
891
  json: parsed.json,
@@ -935,7 +1031,9 @@ fn repair_state_args(args: &[String], cwd: &Path) -> Result<RepairStateArgs, Cli
935
1031
  let parsed = parse_args(args);
936
1032
  Ok(RepairStateArgs {
937
1033
  workspace: workspace(&parsed, cwd),
938
- 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()))?,
939
1037
  assignee: parsed.assignee,
940
1038
  status: parsed
941
1039
  .status_value
@@ -1025,6 +1123,8 @@ fn peek_args(args: &[String], cwd: &Path) -> Result<PeekArgs, CliError> {
1025
1123
  agent: required_pos(&parsed, 0, "agent")?,
1026
1124
  workspace: workspace(&parsed, cwd),
1027
1125
  tail: parsed.tail.unwrap_or(80),
1126
+ head: parsed.head,
1127
+ search: parsed.search,
1028
1128
  allow_raw_screen: parsed.allow_raw_screen,
1029
1129
  json: parsed.json,
1030
1130
  })
@@ -1062,13 +1162,23 @@ fn json_dumps_like(value: &Value) -> String {
1062
1162
  Err(_) => "\"\"".to_string(),
1063
1163
  },
1064
1164
  Value::Array(arr) => {
1065
- 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(", ");
1066
1170
  format!("[{inner}]")
1067
1171
  }
1068
1172
  Value::Object(obj) => {
1069
1173
  let inner = obj
1070
1174
  .iter()
1071
- .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
+ })
1072
1182
  .collect::<Vec<_>>()
1073
1183
  .join(", ");
1074
1184
  format!("{{{inner}}}")
@@ -1160,26 +1270,144 @@ mod tests {
1160
1270
  #[test]
1161
1271
  fn t0_help_catalog_lists_command_flags() {
1162
1272
  for (command, flags) in [
1163
- ("quick-start", &["--workspace", "--team-id", "--yes", "--fresh", "--json"][..]),
1164
- ("send", &["--workspace", "--team", "--targets", "--watch-result", "--timeout", "--json"][..]),
1165
- ("status", &["--workspace", "--team", "--summary", "--json", "--detail"][..]),
1166
- ("shutdown", &["--workspace", "--team", "--keep-logs", "--json"][..]),
1167
- ("restart", &["--team", "--allow-fresh", "--json"][..]),
1168
- ("start-agent", &["--workspace", "--team", "--force", "--allow-fresh", "--no-display", "--json"][..]),
1169
- ("reset-agent", &["--workspace", "--team", "--discard-session", "--no-display", "--json"][..]),
1170
- ("add-agent", &["--role-file", "--workspace", "--team", "--no-display", "--json"][..]),
1171
- ("fork-agent", &["--as", "--label", "--workspace", "--team", "--no-display", "--json"][..]),
1172
- ("remove-agent", &["--workspace", "--team", "--from-spec", "--confirm", "--force", "--json"][..]),
1173
- ("doctor", &["--workspace", "--team", "--gate", "--fix-schema", "--cleanup-orphans", "--json"][..]),
1174
- ("collect", &["--workspace", "--team", "--result-file", "--json"][..]),
1175
- ("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
+ ),
1176
1388
  ("wait-ready", &["--workspace", "--timeout", "--json"][..]),
1177
- ("peek", &["--workspace", "--tail", "--allow-raw-screen", "--json"][..]),
1178
- ("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
+ ),
1179
1404
  ] {
1180
1405
  let help = command_help(Some(command));
1181
1406
  for flag in flags {
1182
- 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
+ );
1183
1411
  }
1184
1412
  }
1185
1413
  }
@@ -1189,7 +1417,13 @@ mod tests {
1189
1417
  let cwd = tmp_workspace();
1190
1418
  let ws = tmp_workspace();
1191
1419
  let args = quick_start_args(
1192
- &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
+ ]),
1193
1427
  &cwd,
1194
1428
  )
1195
1429
  .unwrap();