@team-agent/installer 0.5.111 → 0.5.112

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 (38) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -743
  4. package/crates/team-agent/src/cli/diagnose.rs +2 -424
  5. package/crates/team-agent/src/cli/emit.rs +31 -275
  6. package/crates/team-agent/src/cli/mod.rs +2 -9
  7. package/crates/team-agent/src/cli/spec.rs +0 -3
  8. package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
  9. package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
  10. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
  11. package/crates/team-agent/src/cli/tests/mod.rs +0 -2
  12. package/crates/team-agent/src/cli/types.rs +4 -108
  13. package/crates/team-agent/src/coordinator/health.rs +3 -283
  14. package/crates/team-agent/src/coordinator/mod.rs +0 -5
  15. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
  16. package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
  17. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
  18. package/crates/team-agent/src/coordinator/types.rs +1 -21
  19. package/crates/team-agent/src/db/message_store.rs +0 -42
  20. package/crates/team-agent/src/db/migration.rs +49 -9
  21. package/crates/team-agent/src/db/schema.rs +27 -20
  22. package/crates/team-agent/src/leader/owner_bind.rs +0 -24
  23. package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
  24. package/crates/team-agent/src/leader/tests/identity.rs +0 -32
  25. package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
  26. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
  27. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
  28. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
  29. package/crates/team-agent/src/messaging/mod.rs +0 -2
  30. package/crates/team-agent/src/messaging/results.rs +0 -58
  31. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
  32. package/crates/team-agent/src/state/repository.rs +1 -5
  33. package/package.json +4 -4
  34. package/templates/team_state.md +1 -1
  35. package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
  36. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
  37. package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
  38. package/crates/team-agent/src/messaging/peers.rs +0 -34
@@ -1,5 +1,5 @@
1
1
  //!
2
- //! diagnose/preflight/wait-ready CLI helpers.
2
+ //! diagnose/doctor CLI helpers.
3
3
  use super::*;
4
4
  use crate::model::pane_authority_refusal::{
5
5
  PaneAuthorityRecoveryAction, PaneAuthorityRecoveryHint, PaneAuthorityRefusal,
@@ -1159,7 +1159,6 @@ mod tests {
1159
1159
  );
1160
1160
  }
1161
1161
 
1162
-
1163
1162
  fn write_codex_identity_rollout(
1164
1163
  workspace: &std::path::Path,
1165
1164
  session_id: &str,
@@ -1305,123 +1304,6 @@ mod tests {
1305
1304
  }
1306
1305
  }
1307
1306
 
1308
- pub(crate) fn build_preflight_report(team: &std::path::Path) -> Result<Value, CliError> {
1309
- let mut checks = Vec::new();
1310
- let mut next_actions = Vec::new();
1311
-
1312
- let compiled = match crate::compiler::compile_team(team) {
1313
- Ok(spec) => {
1314
- checks.push(json!({
1315
- "name": "compile",
1316
- "ok": true,
1317
- "agents": compiled_agent_ids(&spec),
1318
- }));
1319
- Some(spec)
1320
- }
1321
- Err(error) => {
1322
- checks.push(json!({
1323
- "name": "compile",
1324
- "ok": false,
1325
- "error": error.to_string(),
1326
- }));
1327
- next_actions.push(json!(
1328
- "fix TEAM.md and role front matter, then run preflight again"
1329
- ));
1330
- None
1331
- }
1332
- };
1333
-
1334
- let tmux_path = command_path("tmux");
1335
- checks.push(json!({
1336
- "name": "tmux",
1337
- "ok": tmux_path.is_some(),
1338
- "path": tmux_path,
1339
- }));
1340
- if tmux_path.is_none() {
1341
- next_actions.push(json!("install tmux or add it to PATH"));
1342
- }
1343
-
1344
- let workspace = crate::model::paths::team_workspace(team)
1345
- .map_err(|error| CliError::Runtime(error.to_string()))?;
1346
- let profile_dir_exists = workspace
1347
- .join(".team")
1348
- .join("current")
1349
- .join("profiles")
1350
- .exists()
1351
- || team.join("profiles").exists();
1352
- let profile_dir_check = json!({
1353
- "name": "profile_dir",
1354
- "ok": true,
1355
- "status": if profile_dir_exists { "present" } else { "not_required" },
1356
- });
1357
- let profile_smoke_check = build_profile_smoke_check_for_team(team)?;
1358
- if profile_smoke_check.get("ok").and_then(Value::as_bool) == Some(false) {
1359
- let reason = profile_smoke_check
1360
- .get("reason")
1361
- .or_else(|| profile_smoke_check.get("error"))
1362
- .and_then(Value::as_str)
1363
- .unwrap_or("profile smoke failed");
1364
- next_actions.push(json!(format!("fix compatible_api profile smoke: {reason}")));
1365
- }
1366
- checks.push(json!({
1367
- "name": "profiles",
1368
- "ok": true,
1369
- "checks": compact_profile_checks(team),
1370
- }));
1371
- checks.push(profile_smoke_check);
1372
- checks.push(if compiled.is_some() {
1373
- json!({
1374
- "name": "models",
1375
- "ok": true,
1376
- })
1377
- } else {
1378
- json!({
1379
- "name": "models",
1380
- "ok": true,
1381
- "status": "skipped",
1382
- })
1383
- });
1384
- checks.push(json!({
1385
- "name": "core_runtime",
1386
- "ok": true,
1387
- "status": core_runtime_status(),
1388
- }));
1389
- checks.push(profile_dir_check);
1390
-
1391
- let blockers = preflight_blockers(&checks);
1392
- let ok = blockers.is_empty();
1393
- let summary = if ok {
1394
- "preflight passed".to_string()
1395
- } else {
1396
- format!(
1397
- "preflight found blockers: {}",
1398
- blocker_names(&blockers).join(", ")
1399
- )
1400
- };
1401
- let checks_value = Value::Array(checks);
1402
- let details_log = write_details_log(
1403
- team,
1404
- "preflight",
1405
- &json!({
1406
- "ok": ok,
1407
- "summary": summary,
1408
- "checks": checks_value,
1409
- "blockers": blockers,
1410
- "next_actions": next_actions,
1411
- }),
1412
- )?;
1413
- let report = json!({
1414
- "blockers": blockers,
1415
- "checks": checks_value,
1416
- "details_log": details_log.to_string_lossy().to_string(),
1417
- "next_actions": next_actions,
1418
- "ok": ok,
1419
- "summary": summary,
1420
- });
1421
- let _ = crate::event_log::EventLog::new(team).write("preflight.complete", report.clone());
1422
- Ok(report)
1423
- }
1424
-
1425
1307
  pub(crate) fn build_profile_smoke_check_for_team(
1426
1308
  team: &std::path::Path,
1427
1309
  ) -> Result<Value, CliError> {
@@ -1519,179 +1401,7 @@ fn copy_optional_field(from: &Value, to: &mut Value, key: &str) {
1519
1401
  obj.insert(key.to_string(), value);
1520
1402
  }
1521
1403
 
1522
- pub(crate) fn build_wait_ready_report(
1523
- workspace: &std::path::Path,
1524
- timeout: f64,
1525
- team: Option<&str>,
1526
- ) -> Result<Value, CliError> {
1527
- // swallow batch 3 ③: an unreadable runtime state must never read as "ready" — the
1528
- // read error is surfaced verbatim (state_read_error) with ready=false instead of
1529
- // silently degrading to an empty/stale state.
1530
- let selected = match crate::state::selector::resolve_active_team(
1531
- workspace,
1532
- team,
1533
- crate::state::selector::SelectorMode::RuntimeOnly,
1534
- ) {
1535
- Ok(selected) => selected,
1536
- Err(error) => {
1537
- return Ok(json!({
1538
- "ok": false,
1539
- "status": "error",
1540
- "reason": "state_read_error",
1541
- "state_read_error": error.to_string(),
1542
- "readiness": {"ready": false},
1543
- "summary": "runtime state could not be read",
1544
- "next_actions": [json!("inspect .team/runtime/state.json (corrupt or unreadable) and retry")],
1545
- }));
1546
- }
1547
- };
1548
- let timeout = if timeout.is_finite() && timeout > 0.0 {
1549
- timeout
1550
- } else {
1551
- 0.0
1552
- };
1553
- let deadline = std::time::Instant::now() + std::time::Duration::from_secs_f64(timeout);
1554
- let mut readiness;
1555
- let mut state_read_error: Option<String>;
1556
- loop {
1557
- let mut state = match crate::state::projection::select_runtime_state(
1558
- &selected.run_workspace,
1559
- Some(&selected.team_key),
1560
- ) {
1561
- Ok(state) => {
1562
- state_read_error = None;
1563
- state
1564
- }
1565
- Err(error) => {
1566
- state_read_error = Some(error.to_string());
1567
- readiness = json!({"ready": false, "state_read_error": error.to_string()});
1568
- break;
1569
- }
1570
- };
1571
- inject_tmux_session_present(&selected.run_workspace, &mut state);
1572
- inject_message_counts(&selected.run_workspace, &mut state)?;
1573
- readiness = wait_readiness(&state);
1574
- let awaiting_trust = readiness
1575
- .get("awaiting_trust_prompt")
1576
- .and_then(Value::as_bool)
1577
- == Some(true);
1578
- let ready = readiness.get("ready").and_then(Value::as_bool) == Some(true);
1579
- if awaiting_trust || ready || std::time::Instant::now() >= deadline {
1580
- break;
1581
- }
1582
- std::thread::sleep(std::time::Duration::from_millis(100));
1583
- }
1584
- let awaiting_trust = readiness
1585
- .get("awaiting_trust_prompt")
1586
- .and_then(Value::as_bool)
1587
- == Some(true);
1588
- let ready = readiness.get("ready").and_then(Value::as_bool) == Some(true);
1589
- let (ok, status, reason, summary, next_actions) = if state_read_error.is_some() {
1590
- (
1591
- false,
1592
- "error",
1593
- "state_read_error",
1594
- "runtime state could not be read",
1595
- vec![json!(
1596
- "inspect .team/runtime/state.json (corrupt or unreadable) and retry"
1597
- )],
1598
- )
1599
- } else if awaiting_trust {
1600
- (
1601
- false,
1602
- "pending",
1603
- "awaiting_trust_prompt",
1604
- "workers are awaiting trust prompt",
1605
- vec![json!(
1606
- "answer the provider trust prompt, then run wait-ready again"
1607
- )],
1608
- )
1609
- } else if ready {
1610
- (true, "ready", "ready", "workers ready", Vec::new())
1611
- } else if readiness
1612
- .get("session_capture_complete")
1613
- .and_then(Value::as_bool)
1614
- == Some(false)
1615
- {
1616
- (
1617
- false,
1618
- "pending",
1619
- "session_capture_incomplete",
1620
- "provider session capture is incomplete",
1621
- vec![json!("wait for provider session capture before restart")],
1622
- )
1623
- } else {
1624
- (
1625
- false,
1626
- "timeout",
1627
- "workers_not_ready",
1628
- "workers not ready before timeout",
1629
- vec![json!(
1630
- "inspect team-agent doctor output and worker terminals"
1631
- )],
1632
- )
1633
- };
1634
- let details_log = write_details_log(
1635
- &selected.run_workspace,
1636
- "wait-ready",
1637
- &json!({
1638
- "ok": ok,
1639
- "status": status,
1640
- "reason": reason,
1641
- "timeout": timeout,
1642
- "readiness": readiness,
1643
- }),
1644
- )?;
1645
- let mut report = json!({
1646
- "details_log": details_log.to_string_lossy().to_string(),
1647
- "next_actions": next_actions,
1648
- "ok": ok,
1649
- "reason": reason,
1650
- "readiness": readiness,
1651
- "status": status,
1652
- "summary": summary,
1653
- });
1654
- if let Some(error) = state_read_error {
1655
- report["state_read_error"] = json!(error);
1656
- }
1657
- Ok(report)
1658
- }
1659
-
1660
- fn inject_tmux_session_present(workspace: &std::path::Path, state: &mut Value) {
1661
- // Bug #7 (prerelease 0.4.0 gate review §6): probe the SAME endpoint the
1662
- // runtime actually uses (state.tmux_endpoint / tmux_socket), not the
1663
- // workspace-hash socket. Otherwise readiness reports `process_started=false`
1664
- // even though the session is alive on the persisted socket.
1665
- let Some(session_name) = state
1666
- .get("session_name")
1667
- .and_then(Value::as_str)
1668
- .filter(|s| !s.is_empty())
1669
- else {
1670
- return;
1671
- };
1672
- let session_name_owned = session_name.to_string();
1673
- // 0.5.x Phase 1d Batch 3: factory-routed read path. conpty state
1674
- // hits the shim; tmux state hits the persisted endpoint (byte-
1675
- // equivalent). Diagnose no longer reports `tmux_session_missing`
1676
- // for a conpty team just because it can't find a tmux session on
1677
- // the workspace socket (design §Batch 3 Verification anchor).
1678
- let resolved = crate::transport_factory::resolve_read_only_transport(
1679
- workspace,
1680
- Some(state),
1681
- crate::transport_factory::TransportPurpose::Diagnose,
1682
- );
1683
- let present = match resolved {
1684
- Ok(r) => r
1685
- .backend
1686
- .has_session(&crate::transport::SessionName::new(session_name_owned))
1687
- .unwrap_or(false),
1688
- Err(_) => false,
1689
- };
1690
- if let Value::Object(map) = state {
1691
- map.insert("tmux_session_present".to_string(), Value::Bool(present));
1692
- }
1693
- }
1694
-
1404
+ /// Shared status facts, independent of any polling command.
1695
1405
  pub(crate) fn wait_readiness(state: &Value) -> Value {
1696
1406
  let agents = state.get("agents").and_then(Value::as_object);
1697
1407
  let mut process_started = false;
@@ -1767,29 +1477,6 @@ pub(crate) fn wait_readiness(state: &Value) -> Value {
1767
1477
  })
1768
1478
  }
1769
1479
 
1770
- fn inject_message_counts(workspace: &std::path::Path, state: &mut Value) -> Result<(), CliError> {
1771
- let store = crate::message_store::MessageStore::open(workspace)
1772
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1773
- let conn = crate::db::schema::open_db(store.db_path())
1774
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1775
- let mut stmt = conn
1776
- .prepare("select status, count(*) from messages group by status order by status")
1777
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1778
- let mut rows = stmt
1779
- .query([])
1780
- .map_err(|e| CliError::Runtime(e.to_string()))?;
1781
- let mut messages = Map::new();
1782
- while let Some(row) = rows.next().map_err(|e| CliError::Runtime(e.to_string()))? {
1783
- let status: String = row.get(0).map_err(|e| CliError::Runtime(e.to_string()))?;
1784
- let count: i64 = row.get(1).map_err(|e| CliError::Runtime(e.to_string()))?;
1785
- messages.insert(status, json!(count));
1786
- }
1787
- if let Some(obj) = state.as_object_mut() {
1788
- obj.insert("messages".to_string(), Value::Object(messages));
1789
- }
1790
- Ok(())
1791
- }
1792
-
1793
1480
  fn message_counts_positive(value: Option<&Value>) -> bool {
1794
1481
  let Some(Value::Object(counts)) = value else {
1795
1482
  return false;
@@ -1839,98 +1526,6 @@ fn agent_mcp_ready(agent: &Value) -> bool {
1839
1526
  })
1840
1527
  }
1841
1528
 
1842
- fn command_path(command: &str) -> Option<String> {
1843
- let output = std::process::Command::new("which")
1844
- .arg(command)
1845
- .output()
1846
- .ok()?;
1847
- if !output.status.success() {
1848
- return None;
1849
- }
1850
- let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
1851
- if path.is_empty() {
1852
- None
1853
- } else {
1854
- Some(path)
1855
- }
1856
- }
1857
-
1858
- fn compact_profile_checks(team: &std::path::Path) -> Vec<Value> {
1859
- let profiles = team.join("profiles");
1860
- let Ok(entries) = std::fs::read_dir(&profiles) else {
1861
- return Vec::new();
1862
- };
1863
- let mut checks = Vec::new();
1864
- for entry in entries.filter_map(std::result::Result::ok) {
1865
- let path = entry.path();
1866
- if !path.is_file() {
1867
- continue;
1868
- }
1869
- let name = path
1870
- .file_stem()
1871
- .and_then(|stem| stem.to_str())
1872
- .unwrap_or("profile")
1873
- .to_string();
1874
- checks.push(json!({
1875
- "name": name,
1876
- "ok": true,
1877
- "path": path.to_string_lossy().to_string(),
1878
- }));
1879
- }
1880
- checks
1881
- }
1882
-
1883
- fn core_runtime_status() -> &'static str {
1884
- if std::env::current_exe().is_ok() {
1885
- "available"
1886
- } else {
1887
- "python_fallback"
1888
- }
1889
- }
1890
-
1891
- fn compiled_agent_ids(spec: &crate::model::yaml::Value) -> Vec<String> {
1892
- spec.get("agents")
1893
- .and_then(crate::model::yaml::Value::as_list)
1894
- .map(|agents| {
1895
- agents
1896
- .iter()
1897
- .filter_map(|agent| agent.get("id").and_then(crate::model::yaml::Value::as_str))
1898
- .map(ToString::to_string)
1899
- .collect()
1900
- })
1901
- .unwrap_or_default()
1902
- }
1903
-
1904
- fn preflight_blockers(checks: &[Value]) -> Vec<Value> {
1905
- checks
1906
- .iter()
1907
- .filter(|check| check.get("ok").and_then(Value::as_bool) == Some(false))
1908
- .map(|check| {
1909
- let name = check
1910
- .get("name")
1911
- .and_then(Value::as_str)
1912
- .unwrap_or("unknown");
1913
- let reason = check
1914
- .get("error")
1915
- .or_else(|| check.get("reason"))
1916
- .and_then(Value::as_str)
1917
- .unwrap_or("failed");
1918
- json!({
1919
- "name": name,
1920
- "reason": reason,
1921
- })
1922
- })
1923
- .collect()
1924
- }
1925
-
1926
- fn blocker_names(blockers: &[Value]) -> Vec<String> {
1927
- blockers
1928
- .iter()
1929
- .filter_map(|blocker| blocker.get("name").and_then(Value::as_str))
1930
- .map(ToString::to_string)
1931
- .collect()
1932
- }
1933
-
1934
1529
  fn yaml_path_str<'a>(value: &'a crate::model::yaml::Value, keys: &[&str]) -> Option<&'a str> {
1935
1530
  let mut current = value;
1936
1531
  for key in keys {
@@ -1939,23 +1534,6 @@ fn yaml_path_str<'a>(value: &'a crate::model::yaml::Value, keys: &[&str]) -> Opt
1939
1534
  current.as_str()
1940
1535
  }
1941
1536
 
1942
- fn write_details_log(
1943
- workspace: &std::path::Path,
1944
- prefix: &str,
1945
- value: &Value,
1946
- ) -> Result<std::path::PathBuf, CliError> {
1947
- let logs = workspace.join(".team").join("logs");
1948
- std::fs::create_dir_all(&logs)?;
1949
- let path = logs.join(format!("{prefix}-{}.json", timestamp_slug()));
1950
- let bytes = serde_json::to_vec_pretty(value)?;
1951
- std::fs::write(&path, bytes)?;
1952
- Ok(path)
1953
- }
1954
-
1955
- fn timestamp_slug() -> String {
1956
- chrono::Utc::now().timestamp().to_string()
1957
- }
1958
-
1959
1537
  pub(crate) fn count_dir_entries(path: &std::path::Path) -> usize {
1960
1538
  std::fs::read_dir(path)
1961
1539
  .map(|entries| entries.filter_map(std::result::Result::ok).count())