@team-agent/installer 0.5.89 → 0.5.91

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 (30) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +102 -162
  4. package/crates/team-agent/src/cli/emit.rs +12 -75
  5. package/crates/team-agent/src/cli/mod.rs +1 -1
  6. package/crates/team-agent/src/cli/spec.rs +1 -4
  7. package/crates/team-agent/src/cli/status_port/inbox.rs +42 -17
  8. package/crates/team-agent/src/cli/tests/lane_c.rs +34 -26
  9. package/crates/team-agent/src/cli/tests/main_preserved.rs +4 -70
  10. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +0 -68
  11. package/crates/team-agent/src/cli/types.rs +3 -16
  12. package/crates/team-agent/src/coordinator/tests/tick_core.rs +5 -6
  13. package/crates/team-agent/src/coordinator/tick.rs +1 -16
  14. package/crates/team-agent/src/coordinator/types.rs +1 -1
  15. package/crates/team-agent/src/db/message_store.rs +10 -5
  16. package/crates/team-agent/src/fake_worker.rs +4 -4
  17. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +2 -2
  18. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +0 -10
  19. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +2 -9
  20. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -26
  21. package/crates/team-agent/src/main.rs +7 -2
  22. package/crates/team-agent/src/mcp_server/mod.rs +1 -1
  23. package/crates/team-agent/src/messaging/mod.rs +2 -2
  24. package/crates/team-agent/src/messaging/results.rs +234 -386
  25. package/crates/team-agent/src/messaging/tests/runtime.rs +91 -135
  26. package/crates/team-agent/src/messaging/watchers.rs +1 -1
  27. package/package.json +4 -4
  28. package/skills/team-agent/SKILL.md +3 -3
  29. package/skills/team-agent/command-coverage.json +2 -9
  30. package/skills/team-agent/references/team-agent-operator.md +661 -0
package/Cargo.lock CHANGED
@@ -581,7 +581,7 @@ dependencies = [
581
581
 
582
582
  [[package]]
583
583
  name = "team-agent"
584
- version = "0.5.89"
584
+ version = "0.5.91"
585
585
  dependencies = [
586
586
  "anyhow",
587
587
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-trans
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.89"
12
+ version = "0.5.91"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -429,83 +429,6 @@ pub fn cmd_sessions(args: &SessionsArgs) -> Result<CmdResult, CliError> {
429
429
  ))
430
430
  }
431
431
 
432
- /// `cmd_collect`(`parser.py:292`)。
433
- #[cfg(test)]
434
- pub(crate) fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
435
- cmd_collect_for_team(args, args.team.as_deref())
436
- }
437
-
438
- pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
439
- let selected = match crate::state::selector::resolve_active_team(
440
- &args.workspace,
441
- team,
442
- crate::state::selector::SelectorMode::RuntimeOnly,
443
- ) {
444
- Ok(selected) => selected,
445
- Err(error) => {
446
- return Ok(CmdResult::from_json(
447
- json!({
448
- "ok": false,
449
- "error": error.to_string(),
450
- "workspace": args.workspace.to_string_lossy().to_string(),
451
- }),
452
- args.json,
453
- ));
454
- }
455
- };
456
- let value = match if team.is_some() {
457
- messaging::collect_for_team(
458
- &selected.run_workspace,
459
- args.result_file.as_deref(),
460
- false,
461
- Some(&selected.team_key),
462
- )
463
- } else {
464
- messaging::collect(&selected.run_workspace, args.result_file.as_deref(), false)
465
- } {
466
- Ok(value) => value,
467
- Err(error) => {
468
- return Ok(CmdResult::from_json(
469
- json!({
470
- "ok": false,
471
- "error": error.to_string(),
472
- "workspace": selected.run_workspace.to_string_lossy().to_string(),
473
- }),
474
- args.json,
475
- ));
476
- }
477
- };
478
- let results = value.get("results").cloned().unwrap_or_else(|| json!({}));
479
- let ok = value.get("ok").and_then(Value::as_bool).unwrap_or(true);
480
- Ok(CmdResult::from_json(
481
- json!({
482
- "collected": [],
483
- "collected_results": value.get("collected_results").cloned().unwrap_or_else(|| json!([])),
484
- "coordinator": {
485
- "ok": false,
486
- "status": "not_required",
487
- },
488
- "delivered_messages": value.get("delivered_messages").cloned().unwrap_or_else(|| json!([])),
489
- "invalid_results": value.get("invalid_results").cloned().unwrap_or_else(|| json!([])),
490
- "ok": ok,
491
- "results": results,
492
- "state_file": value
493
- .get("state_file")
494
- .cloned()
495
- .unwrap_or_else(|| {
496
- json!(selected
497
- .spec_workspace
498
- .as_deref()
499
- .unwrap_or(&selected.run_workspace)
500
- .join("team_state.md")
501
- .to_string_lossy()
502
- .to_string())
503
- }),
504
- }),
505
- args.json,
506
- ))
507
- }
508
-
509
432
  pub fn cmd_results(args: &ResultsArgs) -> Result<CmdResult, CliError> {
510
433
  let selected = match crate::state::selector::resolve_active_team(
511
434
  &args.workspace,
@@ -846,14 +769,14 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
846
769
  .mark(message_id, "delivered", None)
847
770
  .map_err(crate::messaging::MessagingError::from)?;
848
771
  }
849
- let _ = messaging::report_result(
772
+ let report = messaging::report_result(
850
773
  workspace,
851
774
  &json!({
852
775
  "schema_version": "result_envelope_v1",
853
776
  "task_id": "task_impl",
854
777
  "agent_id": "fake_impl",
855
778
  "status": "success",
856
- "summary": "fake result collected",
779
+ "summary": "fake result finalized",
857
780
  "changes": [],
858
781
  "tests": [],
859
782
  "risks": [],
@@ -861,25 +784,29 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
861
784
  "next_actions": [],
862
785
  }),
863
786
  )?;
864
- }
865
- let mut collect = messaging::collect(workspace, None, false)?;
866
- let collected = collect
867
- .get("collected_results")
868
- .and_then(Value::as_array)
869
- .is_some_and(|items| !items.is_empty());
870
- if let Some(obj) = collect.as_object_mut() {
871
- obj.insert("collected".to_string(), Value::Bool(collected));
787
+ let auto_finalized = report
788
+ .get("finalization_status")
789
+ .and_then(Value::as_str)
790
+ == Some("auto_finalized");
791
+ let shutdown = fake_shutdown(workspace)?;
792
+ let ok = launch.get("ok").and_then(Value::as_bool) == Some(true)
793
+ && send.ok
794
+ && auto_finalized
795
+ && shutdown.get("ok").and_then(Value::as_bool) == Some(true);
796
+ return Ok(json!({
797
+ "ok": ok,
798
+ "launch": launch,
799
+ "send": send_value,
800
+ "report": report,
801
+ "shutdown": shutdown,
802
+ }));
872
803
  }
873
804
  let shutdown = fake_shutdown(workspace)?;
874
- let ok = launch.get("ok").and_then(Value::as_bool) == Some(true)
875
- && send.ok
876
- && collected
877
- && shutdown.get("ok").and_then(Value::as_bool) == Some(true);
878
805
  Ok(json!({
879
- "ok": ok,
806
+ "ok": false,
880
807
  "launch": launch,
881
808
  "send": send_value,
882
- "collect": collect,
809
+ "report": null,
883
810
  "shutdown": shutdown,
884
811
  }))
885
812
  }
@@ -1286,89 +1213,82 @@ pub fn cmd_inbox(args: &InboxArgs) -> Result<CmdResult, CliError> {
1286
1213
  &selected.run_workspace,
1287
1214
  &args.agent,
1288
1215
  args.limit,
1289
- args.since.as_deref(),
1290
- args.json,
1291
1216
  Some(&selected.team_key),
1292
1217
  )?;
1293
1218
  if args.json {
1294
1219
  Ok(CmdResult::from_json(value, true))
1295
1220
  } else {
1296
- Ok(CmdResult::human(format_inbox_human(
1297
- &selected.run_workspace,
1298
- &args.agent,
1299
- args.since.as_deref(),
1300
- &value,
1301
- Some(&selected.team_key),
1302
- )?))
1221
+ Ok(CmdResult::human(format_inbox_human(&args.agent, &value)))
1303
1222
  }
1304
1223
  }
1305
1224
 
1306
- fn format_inbox_human(
1307
- workspace: &Path,
1308
- agent: &str,
1309
- since: Option<&str>,
1310
- value: &Value,
1311
- owner_team_id: Option<&str>,
1312
- ) -> Result<String, CliError> {
1225
+ const INBOX_LINE_LIMIT_BYTES: usize = 160;
1226
+
1227
+ fn clean_inbox_field(value: &str) -> String {
1228
+ value.chars().filter(|ch| !ch.is_control()).collect()
1229
+ }
1230
+
1231
+ fn truncate_inbox_line(line: &str) -> String {
1232
+ if line.len() <= INBOX_LINE_LIMIT_BYTES {
1233
+ return line.to_string();
1234
+ }
1235
+ let budget = INBOX_LINE_LIMIT_BYTES.saturating_sub("…".len());
1236
+ let mut end = 0;
1237
+ for (index, ch) in line.char_indices() {
1238
+ let next = index + ch.len_utf8();
1239
+ if next > budget {
1240
+ break;
1241
+ }
1242
+ end = next;
1243
+ }
1244
+ format!("{}…", &line[..end])
1245
+ }
1246
+
1247
+ fn format_inbox_human(agent: &str, value: &Value) -> String {
1313
1248
  let messages = value
1314
1249
  .get("messages")
1315
1250
  .and_then(Value::as_array)
1316
1251
  .cloned()
1317
1252
  .unwrap_or_default();
1318
- let mut lines = Vec::new();
1319
1253
  if messages.is_empty() {
1320
- let mut line = format!("{agent}: no messages");
1321
- if let Some(since) = since {
1322
- line.push_str(" since ");
1323
- line.push_str(since);
1324
- }
1325
- lines.push(line);
1326
- } else {
1327
- lines.push(format!("{agent}: {} message(s)", messages.len()));
1328
- for message in messages {
1329
- let sender = message.get("sender").and_then(Value::as_str).unwrap_or("-");
1330
- let content = message.get("content").and_then(Value::as_str).unwrap_or("");
1331
- let status = message.get("status").and_then(Value::as_str).unwrap_or("-");
1332
- let attempts = message
1333
- .get("delivery_attempts")
1334
- .and_then(Value::as_i64)
1335
- .unwrap_or(0);
1336
- let error = message.get("error").and_then(Value::as_str).unwrap_or("-");
1337
- lines.push(format!(
1338
- "- {sender}: {content} [status={status} attempts={attempts} error={error}]"
1339
- ));
1340
- }
1254
+ return format!("{agent}: no messages");
1341
1255
  }
1342
- let pending = uncollected_result_count(workspace, owner_team_id)?;
1343
- let mut note = "final results are not in inbox; use team-agent collect".to_string();
1344
- if pending > 0 {
1345
- note.push_str(&format!(" ({pending} uncollected result(s) pending)"));
1346
- }
1347
- lines.push(note);
1348
- Ok(lines.join("\n"))
1349
- }
1350
-
1351
- fn uncollected_result_count(
1352
- workspace: &Path,
1353
- owner_team_id: Option<&str>,
1354
- ) -> Result<i64, CliError> {
1355
- let store = crate::message_store::MessageStore::open(workspace)
1356
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1357
- let conn = crate::db::schema::open_db(store.db_path())
1358
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1359
- match owner_team_id {
1360
- Some(team) => conn.query_row(
1361
- "select count(*) from results where status not in ('collected', 'invalid') and owner_team_id = ?1",
1362
- [team],
1363
- |row| row.get::<_, i64>(0),
1364
- ),
1365
- None => conn.query_row(
1366
- "select count(*) from results where status not in ('collected', 'invalid')",
1367
- [],
1368
- |row| row.get::<_, i64>(0),
1369
- ),
1370
- }
1371
- .map_err(|e| CliError::Runtime(e.to_string()))
1256
+ messages
1257
+ .iter()
1258
+ .map(|message| {
1259
+ let id = clean_inbox_field(
1260
+ message
1261
+ .get("message_id")
1262
+ .and_then(Value::as_str)
1263
+ .unwrap_or("-"),
1264
+ );
1265
+ let sender = clean_inbox_field(
1266
+ message.get("sender").and_then(Value::as_str).unwrap_or("-"),
1267
+ );
1268
+ let recipient = clean_inbox_field(
1269
+ message
1270
+ .get("recipient")
1271
+ .and_then(Value::as_str)
1272
+ .unwrap_or("-"),
1273
+ );
1274
+ let status = clean_inbox_field(
1275
+ message.get("status").and_then(Value::as_str).unwrap_or("-"),
1276
+ );
1277
+ let time = clean_inbox_field(
1278
+ message
1279
+ .get("created_at")
1280
+ .and_then(Value::as_str)
1281
+ .unwrap_or("-"),
1282
+ );
1283
+ let summary = clean_inbox_field(
1284
+ message.get("summary").and_then(Value::as_str).unwrap_or(""),
1285
+ );
1286
+ truncate_inbox_line(&format!(
1287
+ "[{id}] [{sender} -> {recipient}] [{status}] [{time}] [{summary}]"
1288
+ ))
1289
+ })
1290
+ .collect::<Vec<_>>()
1291
+ .join("\n")
1372
1292
  }
1373
1293
 
1374
1294
  /// `cmd_takeover`(`commands.py:152`)。
@@ -1629,11 +1549,31 @@ mod tests {
1629
1549
  #![allow(clippy::unwrap_used)]
1630
1550
 
1631
1551
  use super::{
1632
- agent_pane_id, append_send_guidance, quickstart_human, send_command, split_shell_argv,
1552
+ agent_pane_id, append_send_guidance, format_inbox_human, quickstart_human, send_command,
1553
+ split_shell_argv,
1633
1554
  };
1634
1555
  use serde_json::{json, Value};
1635
1556
  use std::path::{Path, PathBuf};
1636
1557
 
1558
+ #[test]
1559
+ fn inbox_human_output_sanitizes_controls_and_caps_each_line() {
1560
+ let value = json!({
1561
+ "messages": [{
1562
+ "message_id": "msg-\u{001b}[31m".repeat(20),
1563
+ "sender": "sender\t\u{001b}[2J".repeat(20),
1564
+ "recipient": "worker".repeat(20),
1565
+ "status": "delivered",
1566
+ "created_at": "2026-09-19T00:00:00Z",
1567
+ "summary": "中".repeat(120)
1568
+ }]
1569
+ });
1570
+ let output = format_inbox_human("worker", &value);
1571
+ for line in output.lines() {
1572
+ assert!(line.len() <= 160, "line is {} bytes: {line:?}", line.len());
1573
+ assert!(!line.chars().any(char::is_control), "controls leaked: {line:?}");
1574
+ }
1575
+ }
1576
+
1637
1577
  // E13:happy 人类输出必须带 attach 块(此前 else 分支只打 summary 丢 attach_commands)。
1638
1578
  #[test]
1639
1579
  fn e13_quickstart_human_includes_attach_commands() {
@@ -223,10 +223,6 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
223
223
  "validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
224
224
  "install-skill" => cmd_install_skill(&install_skill_args(args)?).map(emit_result),
225
225
  "profile" => cmd_profile(&profile_args(args, cwd)?).map(emit_result),
226
- "collect" => {
227
- cmd_collect_for_team(&collect_args(args, cwd)?, parse_args(args).team.as_deref())
228
- .map(emit_result)
229
- }
230
226
  "results" => cmd_results(&results_args(args, cwd)?).map(emit_result),
231
227
  "wait" => cmd_wait(&wait_args(args, cwd)?).map(emit_result),
232
228
  "diagnose" => cmd_diagnose(&diagnose_args(args, cwd)).map(emit_result),
@@ -275,7 +271,6 @@ const DISPATCH_COMMANDS: &[&str] = &[
275
271
  "validate",
276
272
  "install-skill",
277
273
  "profile",
278
- "collect",
279
274
  "results",
280
275
  "wait",
281
276
  "diagnose",
@@ -328,7 +323,6 @@ pub(crate) fn default_help() -> String {
328
323
  "quick-start",
329
324
  "send",
330
325
  "status",
331
- "collect",
332
326
  "results",
333
327
  "models",
334
328
  "leaders",
@@ -441,14 +435,13 @@ fn command_help(command: Option<&str>) -> String {
441
435
  Some("attach-app-server-leader") => "usage: team-agent attach-app-server-leader [--workspace WORKSPACE] [--team TEAM] --socket unix:///path.sock --thread-id THREAD_ID [--json]".to_string(),
442
436
  Some("identity") => "usage: team-agent identity [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
443
437
  Some("approvals") => "usage: team-agent approvals [AGENT] [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
444
- Some("inbox") => "usage: team-agent inbox AGENT [--workspace WORKSPACE] [--team TEAM] [--limit N] [--since CURSOR] [--json]".to_string(),
438
+ Some("inbox") => "usage: team-agent inbox AGENT [-n N|--limit N] [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
445
439
  Some("doctor") => "usage: team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--gate orphans|comms] [--comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]".to_string(),
446
440
  Some("watch") => "usage: team-agent watch [--workspace WORKSPACE] [--team TEAM]".to_string(),
447
441
  Some("sessions") => "usage: team-agent sessions [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
448
442
  Some("validate") => "usage: team-agent validate [SPEC] [--json]".to_string(),
449
443
  Some("install-skill") => "usage: team-agent install-skill (--source DIR | --uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]".to_string(),
450
444
  Some("profile") => "usage: team-agent profile COMMAND NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--proxy-mode direct|inherit] [--json]".to_string(),
451
- Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]".to_string(),
452
445
  Some("results") => "usage: team-agent results --case CASE_ID [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
453
446
  Some("wait") => "usage: team-agent wait --task TASK [--workspace WORKSPACE] [--json]".to_string(),
454
447
  Some("diagnose") => "usage: team-agent diagnose [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
@@ -842,7 +835,6 @@ struct ParsedArgs {
842
835
  confirm: bool,
843
836
  alert_type: Option<String>,
844
837
  limit: Option<usize>,
845
- since: Option<String>,
846
838
  gate: Option<String>,
847
839
  comms: bool,
848
840
  fix: bool,
@@ -856,7 +848,6 @@ struct ParsedArgs {
856
848
  tail: Option<usize>,
857
849
  head: Option<usize>,
858
850
  search: Option<String>,
859
- result_file: Option<PathBuf>,
860
851
  file: Option<PathBuf>,
861
852
  result: Option<String>,
862
853
  real: bool,
@@ -936,10 +927,9 @@ fn parse_args(args: &[String]) -> ParsedArgs {
936
927
  "--from-spec" => parsed.from_spec = true,
937
928
  "--confirm" => parsed.confirm = true,
938
929
  "--alert-type" => parsed.alert_type = next_arg(args, &mut i),
939
- "--limit" => {
930
+ "-n" | "--limit" => {
940
931
  parsed.limit = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok())
941
932
  }
942
- "--since" => parsed.since = next_arg(args, &mut i),
943
933
  "--gate" => parsed.gate = next_arg(args, &mut i),
944
934
  "--comms" => parsed.comms = true,
945
935
  "--fix" => parsed.fix = true,
@@ -955,7 +945,6 @@ fn parse_args(args: &[String]) -> ParsedArgs {
955
945
  "--tail" => parsed.tail = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
956
946
  "--head" => parsed.head = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
957
947
  "--search" => parsed.search = next_arg(args, &mut i),
958
- "--result-file" => parsed.result_file = next_arg(args, &mut i).map(PathBuf::from),
959
948
  "--file" => parsed.file = next_arg(args, &mut i).map(PathBuf::from),
960
949
  "--result" => parsed.result = next_arg(args, &mut i),
961
950
  "--real" => parsed.real = true,
@@ -1438,12 +1427,19 @@ fn approvals_args(args: &[String], cwd: &Path) -> ApprovalsArgs {
1438
1427
  }
1439
1428
 
1440
1429
  fn inbox_args(args: &[String], cwd: &Path) -> Result<InboxArgs, CliError> {
1430
+ if args
1431
+ .iter()
1432
+ .any(|arg| arg == "--since" || arg.starts_with("--since="))
1433
+ {
1434
+ return Err(CliError::Usage(
1435
+ "inbox only supports -n/--limit; --since is not supported".to_string(),
1436
+ ));
1437
+ }
1441
1438
  let parsed = parse_args(args);
1442
1439
  Ok(InboxArgs {
1443
1440
  agent: required_pos(&parsed, 0, "agent")?,
1444
1441
  workspace: workspace(&parsed, cwd),
1445
- limit: parsed.limit.unwrap_or(20),
1446
- since: parsed.since,
1442
+ limit: parsed.limit.unwrap_or(3),
1447
1443
  json: parsed.json,
1448
1444
  team: parsed.team,
1449
1445
  })
@@ -1848,18 +1844,6 @@ fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
1848
1844
  })
1849
1845
  }
1850
1846
 
1851
- fn collect_args(args: &[String], cwd: &Path) -> Result<CollectArgs, CliError> {
1852
- let parsed = parse_args(args);
1853
- let workspace = workspace(&parsed, cwd);
1854
- refuse_if_multi_alive_team_missing_scope("collect", &workspace, parsed.team.as_deref())?;
1855
- Ok(CollectArgs {
1856
- workspace,
1857
- result_file: parsed.result_file,
1858
- json: parsed.json,
1859
- team: parsed.team,
1860
- })
1861
- }
1862
-
1863
1847
  fn results_args(args: &[String], cwd: &Path) -> Result<ResultsArgs, CliError> {
1864
1848
  if args
1865
1849
  .iter()
@@ -2349,15 +2333,11 @@ mod tests {
2349
2333
  "--json",
2350
2334
  ][..],
2351
2335
  ),
2352
- (
2353
- "collect",
2354
- &["--workspace", "--team", "--result-file", "--json"][..],
2355
- ),
2356
2336
  ("stuck-list", &["--workspace", "--team", "--json"][..]),
2357
2337
  ("approvals", &["--workspace", "--team", "--json"][..]),
2358
2338
  (
2359
2339
  "inbox",
2360
- &["--workspace", "--team", "--limit", "--since", "--json"][..],
2340
+ &["--workspace", "--team", "-n", "--limit", "--json"][..],
2361
2341
  ),
2362
2342
  ("sessions", &["--workspace", "--team", "--json"][..]),
2363
2343
  ("diagnose", &["--workspace", "--team", "--json"][..]),
@@ -2665,37 +2645,6 @@ mod tests {
2665
2645
  );
2666
2646
  }
2667
2647
 
2668
- #[test]
2669
- fn refuse_helper_passes_when_explicit_team_provided_even_in_multi_alive() {
2670
- let ws = tmp_workspace();
2671
- seed_two_alive_teams_in(&ws);
2672
- assert!(
2673
- refuse_if_multi_alive_team_missing_scope("collect", &ws, Some("alpha")).is_ok(),
2674
- "explicit --team alpha must bypass the ambiguity gate"
2675
- );
2676
- }
2677
-
2678
- #[test]
2679
- fn refuse_helper_refuses_when_multi_alive_team_and_no_explicit_team() {
2680
- let ws = tmp_workspace();
2681
- seed_two_alive_teams_in(&ws);
2682
- let err = refuse_if_multi_alive_team_missing_scope("collect", &ws, None)
2683
- .expect_err("multi-alive-team must refuse without --team");
2684
- let message = err.to_string();
2685
- assert!(
2686
- message.contains("multiple alive teams"),
2687
- "refusal must name the ambiguity; got: {message}"
2688
- );
2689
- assert!(
2690
- message.contains("alpha") && message.contains("beta"),
2691
- "refusal must list candidate teams; got: {message}"
2692
- );
2693
- assert!(
2694
- message.contains("collect"),
2695
- "refusal must name the command for diagnostic clarity; got: {message}"
2696
- );
2697
- }
2698
-
2699
2648
  #[test]
2700
2649
  fn stuck_cancel_args_builder_refuses_on_multi_alive_team() {
2701
2650
  let ws = tmp_workspace();
@@ -2708,18 +2657,6 @@ mod tests {
2708
2657
  );
2709
2658
  }
2710
2659
 
2711
- #[test]
2712
- fn collect_args_builder_refuses_on_multi_alive_team() {
2713
- let ws = tmp_workspace();
2714
- seed_two_alive_teams_in(&ws);
2715
- let argv = cli_argv(&["--workspace", &ws.to_string_lossy()]);
2716
- let err = collect_args(&argv, &ws).expect_err("must refuse");
2717
- assert!(
2718
- err.to_string().contains("multiple alive teams"),
2719
- "collect args builder must surface the refusal; got: {err}"
2720
- );
2721
- }
2722
-
2723
2660
  #[test]
2724
2661
  fn shutdown_args_builder_refuses_on_multi_alive_team() {
2725
2662
  let ws = tmp_workspace();
@@ -51,7 +51,7 @@ use crate::model::ids::{TaskId, TeamKey};
51
51
  pub(crate) const COMMS_BOUNDARY_TEXT: &str = "validates live pane binding consistency and zero-token comms contracts. Does NOT perform live runtime message round-trip. (zero token, zero pollution)";
52
52
  pub(crate) const QUICK_START_REMINDER: &str = "Reminder: Do not inspect raw worker terminal output during normal operation. Use team-agent status / inbox / collect instead. Wait for report_result.";
53
53
  pub(crate) const SEND_REMINDER: &str = "Message delivered. Wait for the worker to report_result. Do not poll the worker terminal with capture-pane.";
54
- pub(crate) const STATUS_REMINDER: &str = "To wait for results use --watch-result or team-agent collect. Do not capture-pane worker terminals.";
54
+ pub(crate) const STATUS_REMINDER: &str = "Results are finalized automatically; use --watch-result or team-agent inbox AGENT -n 3 for transport fallback. Do not capture-pane worker terminals.";
55
55
 
56
56
  pub mod adapters;
57
57
  pub mod attach_app_server_leader;
@@ -76,7 +76,6 @@ pub(crate) enum DispatchKind {
76
76
  Validate,
77
77
  InstallSkill,
78
78
  Profile,
79
- Collect,
80
79
  Results,
81
80
  Wait,
82
81
  Diagnose,
@@ -123,7 +122,6 @@ pub(crate) const ALL_DISPATCH_KINDS: &[DispatchKind] = &[
123
122
  DispatchKind::Validate,
124
123
  DispatchKind::InstallSkill,
125
124
  DispatchKind::Profile,
126
- DispatchKind::Collect,
127
125
  DispatchKind::Results,
128
126
  DispatchKind::Wait,
129
127
  DispatchKind::Diagnose,
@@ -170,7 +168,6 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
170
168
  CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "persist a message for an in-team short name or fully-qualified logical recipient", usage: "usage: team-agent send TO MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--mailbox] [--json]\nTO forms are co-equal: an in-team short name (for example `team-agent send reviewer \"Review\"`) or `<workspace>::<team>/<agent>`.", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
171
169
  CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "show current team status", usage: "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
172
170
  CommandSpec { name: "models", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Models), summary: "list exact provider model ids", usage: "usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
173
- CommandSpec { name: "collect", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Collect), summary: "collect reported results", usage: "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
174
171
  CommandSpec { name: "results", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Results), summary: "read reported results for a case", usage: "usage: team-agent results --case CASE_ID [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
175
172
  CommandSpec { name: "wait", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Wait), summary: "block until a task result is stored", usage: "usage: team-agent wait --task TASK [--workspace WORKSPACE] [--json]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
176
173
  CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "restart the selected team", usage: "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
@@ -208,7 +205,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
208
205
  CommandSpec { name: "approvals", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Approvals), summary: "inspect provider approval state", usage: "usage: team-agent approvals [AGENT] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: Some(GovernanceNote { decision: "secondary", reason: "approval diagnostics" }) },
209
206
  CommandSpec { name: "profile", tier: CommandTier::Secondary, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Profile), summary: "manage provider profiles", usage: "usage: team-agent profile COMMAND NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--proxy-mode direct|inherit] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: Some(GovernanceNote { decision: "secondary", reason: "provider/profile setup" }) },
210
207
  CommandSpec { name: "install-skill", tier: CommandTier::Secondary, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::InstallSkill), summary: "install or uninstall Team Agent skills", usage: "usage: team-agent install-skill (--source DIR | --uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: Some(GovernanceNote { decision: "secondary", reason: "capability setup" }) },
211
- CommandSpec { name: "inbox", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Inbox), summary: "inspect queued messages", usage: "usage: team-agent inbox AGENT [--workspace WORKSPACE] [--team TEAM] [--limit N] [--since CURSOR] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
208
+ CommandSpec { name: "inbox", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Inbox), summary: "inspect recent message summaries", usage: "usage: team-agent inbox AGENT [-n N|--limit N] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
212
209
 
213
210
  CommandSpec { name: "identity", tier: CommandTier::DevInternal, category: CommandCategory::Dev, kind: CommandKind::Dispatch(DispatchKind::Identity), summary: "inspect identity metadata", usage: "usage: team-agent identity [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
214
211
  CommandSpec { name: "watch", tier: CommandTier::DevInternal, category: CommandCategory::Dev, kind: CommandKind::Dispatch(DispatchKind::Watch), summary: "watch event logs", usage: "usage: team-agent watch [--workspace WORKSPACE] [--team TEAM]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },