@team-agent/installer 0.4.1 → 0.4.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 (44) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +82 -22
  4. package/crates/team-agent/src/cli/diagnose.rs +6 -2
  5. package/crates/team-agent/src/cli/emit.rs +301 -38
  6. package/crates/team-agent/src/cli/mod.rs +7 -14
  7. package/crates/team-agent/src/cli/status_port.rs +12 -1
  8. package/crates/team-agent/src/cli/tests/base.rs +6 -2
  9. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
  10. package/crates/team-agent/src/cli/tests/leader_watch.rs +5 -3
  11. package/crates/team-agent/src/cli/tests/main_preserved.rs +28 -2
  12. package/crates/team-agent/src/cli/tests/peer_allow.rs +19 -0
  13. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +72 -0
  14. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -3
  15. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  16. package/crates/team-agent/src/cli/types.rs +35 -2
  17. package/crates/team-agent/src/coordinator/runtime_detectors.rs +7 -2
  18. package/crates/team-agent/src/diagnose/comms.rs +10 -2
  19. package/crates/team-agent/src/leader/lease.rs +98 -19
  20. package/crates/team-agent/src/leader/rediscover/tests.rs +21 -7
  21. package/crates/team-agent/src/leader/rediscover.rs +16 -7
  22. package/crates/team-agent/src/leader/start.rs +25 -12
  23. package/crates/team-agent/src/leader/tests/byte_findings.rs +11 -2
  24. package/crates/team-agent/src/leader/tests/lease_claim.rs +248 -7
  25. package/crates/team-agent/src/lifecycle/launch.rs +126 -100
  26. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +40 -12
  27. package/crates/team-agent/src/lifecycle/tests/core.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +16 -15
  29. package/crates/team-agent/src/lifecycle/types.rs +1 -1
  30. package/crates/team-agent/src/messaging/leader_receiver.rs +22 -8
  31. package/crates/team-agent/src/messaging/send.rs +21 -4
  32. package/crates/team-agent/src/provider/adapter.rs +72 -0
  33. package/crates/team-agent/src/provider/session/capture.rs +228 -0
  34. package/crates/team-agent/src/state/identity.rs +42 -12
  35. package/crates/team-agent/src/state/identity_keys.rs +134 -0
  36. package/crates/team-agent/src/state/mod.rs +8 -0
  37. package/crates/team-agent/src/state/owner_gate.rs +11 -1
  38. package/crates/team-agent/src/state/ownership.rs +556 -0
  39. package/crates/team-agent/src/state/paths.rs +358 -0
  40. package/crates/team-agent/src/state/persist.rs +43 -5
  41. package/crates/team-agent/src/state/projection.rs +13 -5
  42. package/crates/team-agent/src/tmux_backend.rs +12 -0
  43. package/package.json +4 -4
  44. package/skills/team-agent/SKILL.md +3 -3
package/Cargo.lock CHANGED
@@ -566,7 +566,7 @@ dependencies = [
566
566
 
567
567
  [[package]]
568
568
  name = "team-agent"
569
- version = "0.4.1"
569
+ version = "0.4.3"
570
570
  dependencies = [
571
571
  "anyhow",
572
572
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent"]
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.4.1"
12
+ version = "0.4.3"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -106,7 +106,6 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
106
106
  args.name.as_deref(),
107
107
  args.team_id.as_deref(),
108
108
  args.yes,
109
- args.fresh,
110
109
  !args.no_display,
111
110
  )?;
112
111
  let readiness = value.get("readiness").and_then(Value::as_object);
@@ -213,7 +212,7 @@ fn warn_ignored_owner_team_id(team_dir: &std::path::Path) {
213
212
  /// `cmd_status`(`commands.py:90`)。三态:`--summary`(xor json,xor agent)→五行文本;
214
213
  /// `--json`→`status_port::status(compact=!detail)`;else→`status_port::format_status(agent)`。
215
214
  pub fn cmd_status(args: &StatusArgs) -> Result<CmdResult, CliError> {
216
- cmd_status_for_team(args, None)
215
+ cmd_status_for_team(args, args.team.as_deref())
217
216
  }
218
217
 
219
218
  pub fn cmd_status_for_team(args: &StatusArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
@@ -326,12 +325,20 @@ pub fn cmd_watch(args: &WatchArgs) -> Result<CmdResult, CliError> {
326
325
 
327
326
  /// `cmd_sessions`(`parser.py:230`)。
328
327
  pub fn cmd_sessions(args: &SessionsArgs) -> Result<CmdResult, CliError> {
329
- let state = crate::state::persist::load_runtime_state(&args.workspace)?;
330
- let spec = load_team_spec_optional(&args.workspace, &state)?;
328
+ let selected = crate::state::selector::resolve_active_team(
329
+ &args.workspace,
330
+ args.team.as_deref(),
331
+ crate::state::selector::SelectorMode::RuntimeOnly,
332
+ )
333
+ .map_err(|e| CliError::Runtime(e.to_string()))?;
334
+ let spec = match selected.spec_path.as_ref() {
335
+ Some(path) => load_team_spec_at(path)?,
336
+ None => load_team_spec_optional(&selected.run_workspace, &selected.state)?,
337
+ };
331
338
  Ok(CmdResult::from_json(
332
339
  json!({
333
340
  "ok": true,
334
- "sessions": sessions_overview(&state, spec.as_ref()),
341
+ "sessions": sessions_overview(&selected.state, spec.as_ref()),
335
342
  "workspace": args.workspace.to_string_lossy().to_string(),
336
343
  }),
337
344
  args.json,
@@ -367,7 +374,7 @@ pub fn cmd_validate_result(args: &ValidateResultArgs) -> Result<CmdResult, CliEr
367
374
 
368
375
  /// `cmd_collect`(`parser.py:292`)。
369
376
  pub fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
370
- cmd_collect_for_team(args, None)
377
+ cmd_collect_for_team(args, args.team.as_deref())
371
378
  }
372
379
 
373
380
  pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
@@ -599,6 +606,11 @@ fn write_settle_details_log(workspace: &Path, collect: &Value, status: &Value) -
599
606
 
600
607
  /// `cmd_allow_peer_talk`(`parser.py allow-peer-talk`).
601
608
  pub fn cmd_allow_peer_talk(args: &AllowPeerTalkArgs) -> Result<CmdResult, CliError> {
609
+ if args.team.is_some() {
610
+ return Err(CliError::Usage(
611
+ "allow-peer-talk --team is not supported yet because peer allowlist storage is not team-scoped".to_string(),
612
+ ));
613
+ }
602
614
  let value = messaging::allow_peer_talk(&args.workspace, &args.a, &args.b)?;
603
615
  Ok(CmdResult::from_json(value, args.json))
604
616
  }
@@ -613,7 +625,7 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
613
625
  }
614
626
  let selected = crate::state::selector::resolve_active_team(
615
627
  &args.workspace,
616
- None,
628
+ args.team.as_deref(),
617
629
  crate::state::selector::SelectorMode::RequireSpec,
618
630
  )
619
631
  .map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -666,7 +678,7 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
666
678
  pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
667
679
  let selected = crate::state::selector::resolve_active_team(
668
680
  &args.workspace,
669
- None,
681
+ args.team.as_deref(),
670
682
  crate::state::selector::SelectorMode::RuntimeOnly,
671
683
  )
672
684
  .map_err(|e| CliError::Runtime(e.to_string()))?;
@@ -687,8 +699,8 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
687
699
  "session_name": state.get("session_name").cloned().unwrap_or(Value::Null),
688
700
  "leader_receiver": state.get("leader_receiver").cloned().unwrap_or(Value::Null),
689
701
  "agent_count": state.get("agents").and_then(Value::as_object).map_or(0, serde_json::Map::len),
690
- "message_count": count_dir_entries(&args.workspace.join(".team").join("messages")),
691
- "result_count": count_dir_entries(&args.workspace.join(".team").join("results")),
702
+ "message_count": count_dir_entries(&selected.run_workspace.join(".team").join("messages")),
703
+ "result_count": count_dir_entries(&selected.run_workspace.join(".team").join("results")),
692
704
  },
693
705
  "suggested_repairs": suggested_repairs,
694
706
  }),
@@ -707,7 +719,7 @@ pub fn cmd_preflight(args: &PreflightArgs) -> Result<CmdResult, CliError> {
707
719
 
708
720
  /// `cmd_wait_ready`(`parser.py:171`)。
709
721
  pub fn cmd_wait_ready(args: &WaitReadyArgs) -> Result<CmdResult, CliError> {
710
- let report = build_wait_ready_report(&args.workspace, args.timeout)?;
722
+ let report = build_wait_ready_report(&args.workspace, args.timeout, args.team.as_deref())?;
711
723
  Ok(CmdResult::from_json(
712
724
  report,
713
725
  args.json,
@@ -1321,7 +1333,18 @@ fn load_team_spec_at(spec_path: &Path) -> Result<Option<crate::model::yaml::Valu
1321
1333
 
1322
1334
  /// `cmd_approvals`(`commands.py:112`)。
1323
1335
  pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
1324
- let value = status_port::approvals(&args.workspace, args.agent.as_deref(), args.json)?;
1336
+ let selected = crate::state::selector::resolve_active_team(
1337
+ &args.workspace,
1338
+ args.team.as_deref(),
1339
+ crate::state::selector::SelectorMode::RuntimeOnly,
1340
+ )
1341
+ .map_err(|e| CliError::Runtime(e.to_string()))?;
1342
+ let value = status_port::approvals_scoped(
1343
+ &selected.run_workspace,
1344
+ &selected.state,
1345
+ args.agent.as_deref(),
1346
+ args.json,
1347
+ )?;
1325
1348
  if args.json {
1326
1349
  Ok(CmdResult::from_json(value, true))
1327
1350
  } else {
@@ -1331,21 +1354,29 @@ pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
1331
1354
 
1332
1355
  /// `cmd_inbox`(`commands.py:137`)。
1333
1356
  pub fn cmd_inbox(args: &InboxArgs) -> Result<CmdResult, CliError> {
1334
- let value = status_port::inbox(
1357
+ let selected = crate::state::selector::resolve_active_team(
1335
1358
  &args.workspace,
1359
+ args.team.as_deref(),
1360
+ crate::state::selector::SelectorMode::RuntimeOnly,
1361
+ )
1362
+ .map_err(|e| CliError::Runtime(e.to_string()))?;
1363
+ let value = status_port::inbox(
1364
+ &selected.run_workspace,
1336
1365
  &args.agent,
1337
1366
  args.limit,
1338
1367
  args.since.as_deref(),
1339
1368
  args.json,
1369
+ Some(&selected.team_key),
1340
1370
  )?;
1341
1371
  if args.json {
1342
1372
  Ok(CmdResult::from_json(value, true))
1343
1373
  } else {
1344
1374
  Ok(CmdResult::human(format_inbox_human(
1345
- &args.workspace,
1375
+ &selected.run_workspace,
1346
1376
  &args.agent,
1347
1377
  args.since.as_deref(),
1348
1378
  &value,
1379
+ Some(&selected.team_key),
1349
1380
  )?))
1350
1381
  }
1351
1382
  }
@@ -1355,6 +1386,7 @@ fn format_inbox_human(
1355
1386
  agent: &str,
1356
1387
  since: Option<&str>,
1357
1388
  value: &Value,
1389
+ owner_team_id: Option<&str>,
1358
1390
  ) -> Result<String, CliError> {
1359
1391
  let messages = value
1360
1392
  .get("messages")
@@ -1377,7 +1409,7 @@ fn format_inbox_human(
1377
1409
  lines.push(format!("- {sender}: {content}"));
1378
1410
  }
1379
1411
  }
1380
- let pending = uncollected_result_count(workspace)?;
1412
+ let pending = uncollected_result_count(workspace, owner_team_id)?;
1381
1413
  let mut note = "final results are not in inbox; use team-agent collect".to_string();
1382
1414
  if pending > 0 {
1383
1415
  note.push_str(&format!(" ({pending} uncollected result(s) pending)"));
@@ -1386,16 +1418,23 @@ fn format_inbox_human(
1386
1418
  Ok(lines.join("\n"))
1387
1419
  }
1388
1420
 
1389
- fn uncollected_result_count(workspace: &Path) -> Result<i64, CliError> {
1421
+ fn uncollected_result_count(workspace: &Path, owner_team_id: Option<&str>) -> Result<i64, CliError> {
1390
1422
  let store = crate::message_store::MessageStore::open(workspace)
1391
1423
  .map_err(|e| CliError::Runtime(e.to_string()))?;
1392
1424
  let conn = crate::db::schema::open_db(store.db_path())
1393
1425
  .map_err(|e| CliError::Runtime(e.to_string()))?;
1394
- conn.query_row(
1395
- "select count(*) from results where status not in ('collected', 'invalid')",
1396
- [],
1397
- |row| row.get::<_, i64>(0),
1398
- )
1426
+ match owner_team_id {
1427
+ Some(team) => conn.query_row(
1428
+ "select count(*) from results where status not in ('collected', 'invalid') and owner_team_id = ?1",
1429
+ [team],
1430
+ |row| row.get::<_, i64>(0),
1431
+ ),
1432
+ None => conn.query_row(
1433
+ "select count(*) from results where status not in ('collected', 'invalid')",
1434
+ [],
1435
+ |row| row.get::<_, i64>(0),
1436
+ ),
1437
+ }
1399
1438
  .map_err(|e| CliError::Runtime(e.to_string()))
1400
1439
  }
1401
1440
 
@@ -1527,11 +1566,32 @@ pub fn cmd_remove_agent(args: &RemoveAgentArgs) -> Result<CmdResult, CliError> {
1527
1566
 
1528
1567
  /// `cmd_stuck_list`(`commands.py:405`)。REUSE `messaging::stuck_list`。
1529
1568
  pub fn cmd_stuck_list(args: &StuckListArgs) -> Result<CmdResult, CliError> {
1530
- Ok(CmdResult::from_json(messaging::stuck_list(&args.workspace)?, args.json))
1569
+ let selected = crate::state::selector::resolve_active_team(
1570
+ &args.workspace,
1571
+ args.team.as_deref(),
1572
+ crate::state::selector::SelectorMode::RuntimeOnly,
1573
+ )
1574
+ .map_err(|e| CliError::Runtime(e.to_string()))?;
1575
+ let suppressed = selected
1576
+ .state
1577
+ .get("coordinator")
1578
+ .and_then(|v| v.get("suppressed_idle_alerts"))
1579
+ .and_then(|v| v.get(&selected.team_key))
1580
+ .cloned()
1581
+ .unwrap_or_else(|| json!({}));
1582
+ Ok(CmdResult::from_json(
1583
+ json!({"ok": true, "suppressed_idle_alerts": suppressed}),
1584
+ args.json,
1585
+ ))
1531
1586
  }
1532
1587
 
1533
1588
  /// `cmd_stuck_cancel`(`commands.py:409`)。REUSE `messaging::stuck_cancel`(suppressed_by="leader")。
1534
1589
  pub fn cmd_stuck_cancel(args: &StuckCancelArgs) -> Result<CmdResult, CliError> {
1590
+ if args.team.is_some() {
1591
+ return Err(CliError::Usage(
1592
+ "stuck-cancel --team is not supported yet because stuck suppression storage is not team-scoped".to_string(),
1593
+ ));
1594
+ }
1535
1595
  Ok(CmdResult::from_json(
1536
1596
  messaging::stuck_cancel(&args.workspace, &args.agent, args.alert_type, "leader")?,
1537
1597
  args.json,
@@ -297,13 +297,17 @@ fn copy_optional_field(from: &Value, to: &mut Value, key: &str) {
297
297
  obj.insert(key.to_string(), value);
298
298
  }
299
299
 
300
- pub(crate) fn build_wait_ready_report(workspace: &std::path::Path, timeout: f64) -> Result<Value, CliError> {
300
+ pub(crate) fn build_wait_ready_report(
301
+ workspace: &std::path::Path,
302
+ timeout: f64,
303
+ team: Option<&str>,
304
+ ) -> Result<Value, CliError> {
301
305
  // swallow batch 3 ③: an unreadable runtime state must never read as "ready" — the
302
306
  // read error is surfaced verbatim (state_read_error) with ready=false instead of
303
307
  // silently degrading to an empty/stale state.
304
308
  let selected = match crate::state::selector::resolve_active_team(
305
309
  workspace,
306
- None,
310
+ team,
307
311
  crate::state::selector::SelectorMode::RuntimeOnly,
308
312
  ) {
309
313
  Ok(selected) => selected,