@team-agent/installer 0.5.99 → 0.5.101

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 (47) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -156
  4. package/crates/team-agent/src/cli/diagnose.rs +32 -10
  5. package/crates/team-agent/src/cli/emit.rs +6 -185
  6. package/crates/team-agent/src/cli/mod.rs +32 -182
  7. package/crates/team-agent/src/cli/send/coordinator.rs +2 -2
  8. package/crates/team-agent/src/cli/spec.rs +41 -67
  9. package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -185
  10. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +9 -9
  11. package/crates/team-agent/src/cli/triage.rs +1 -1
  12. package/crates/team-agent/src/cli/types.rs +0 -49
  13. package/crates/team-agent/src/compiler.rs +3 -0
  14. package/crates/team-agent/src/leader/lease.rs +1 -1
  15. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +211 -16
  16. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +1 -0
  17. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +15 -5
  18. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +89 -6
  19. package/crates/team-agent/src/lifecycle/launch/role_source.rs +168 -32
  20. package/crates/team-agent/src/lifecycle/launch.rs +11 -3
  21. package/crates/team-agent/src/lifecycle/profile_launch.rs +23 -8
  22. package/crates/team-agent/src/lifecycle/restart/agent.rs +2 -2
  23. package/crates/team-agent/src/lifecycle/restart/avatar.rs +1806 -0
  24. package/crates/team-agent/src/lifecycle/restart/common.rs +114 -1
  25. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +14 -8
  26. package/crates/team-agent/src/lifecycle/restart/selection.rs +14 -2
  27. package/crates/team-agent/src/lifecycle/restart.rs +11 -4
  28. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +2 -2
  29. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +1 -1
  30. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +9 -2
  31. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1398 -0
  32. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +0 -60
  33. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +2 -2
  34. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  35. package/crates/team-agent/src/lifecycle/types.rs +10 -2
  36. package/crates/team-agent/src/messaging/send.rs +2 -2
  37. package/crates/team-agent/src/model/spec.rs +1 -0
  38. package/crates/team-agent/src/provider/types.rs +3 -1
  39. package/crates/team-agent/src/state/persist.rs +58 -2
  40. package/crates/team-agent/src/state/projection.rs +5 -1
  41. package/crates/team-agent/src/state/repository.rs +125 -9
  42. package/crates/team-agent/src/transport/test_support.rs +23 -3
  43. package/package.json +4 -4
  44. package/skills/team-agent/SKILL.md +17 -46
  45. package/skills/team-agent/references/recovery-runbook.md +2 -2
  46. package/skills/team-agent/references/team-agent-operator.md +4 -2
  47. package/skills/team-agent/references/team-in-team.md +0 -6
@@ -954,7 +954,7 @@ pub(crate) fn lifecycle_worker_transport_for_selected_state(
954
954
  /// returns: 后端与 endpoint 来源
955
955
  /// errors: state 声明后端是 conpty 时返回 TeamSelect
956
956
  /// ---
957
- pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
957
+ pub(crate) fn lifecycle_worker_tmux_backend_selection_for_state(
958
958
  run_workspace: &Path,
959
959
  state: &serde_json::Value,
960
960
  ) -> Result<crate::tmux_backend::RuntimeTmuxBackendSelection, LifecycleError> {
@@ -1344,6 +1344,119 @@ pub(crate) fn session_identity_probe_for_agent(
1344
1344
  }
1345
1345
  }
1346
1346
 
1347
+ /// ---
1348
+ /// purpose: 将合法 Pi fork 血统纳入共享会话身份判定
1349
+ /// params:
1350
+ /// workspace/state/agent: 当前被验证席位及同队来源记录
1351
+ /// rollout_path: 当前席位的精确 backing
1352
+ /// returns: 字面身份一致,或经独立 Pi backing 与 fork 血统验证后合法继承时为 true
1353
+ /// ---
1354
+ pub(crate) fn session_identity_probe_for_agent_with_fork_ancestry(
1355
+ workspace: &Path,
1356
+ state: &serde_json::Value,
1357
+ agent_id: &AgentId,
1358
+ agent: &serde_json::Value,
1359
+ provider: Provider,
1360
+ rollout_path: Option<&RolloutPath>,
1361
+ ) -> SessionIdentityProbeResult {
1362
+ let mut probe = session_identity_probe_for_agent(agent_id, provider, rollout_path);
1363
+ if probe.identity_ok != Some(false)
1364
+ || !pi_fork_ancestor_identity_is_valid(
1365
+ workspace,
1366
+ state,
1367
+ agent_id,
1368
+ agent,
1369
+ provider,
1370
+ rollout_path,
1371
+ probe.embedded_agent_id.as_deref(),
1372
+ )
1373
+ {
1374
+ return probe;
1375
+ }
1376
+ probe.identity_ok = Some(true);
1377
+ probe
1378
+ }
1379
+
1380
+ fn pi_fork_ancestor_identity_is_valid(
1381
+ workspace: &Path,
1382
+ state: &serde_json::Value,
1383
+ agent_id: &AgentId,
1384
+ agent: &serde_json::Value,
1385
+ provider: Provider,
1386
+ rollout_path: Option<&RolloutPath>,
1387
+ embedded_agent_id: Option<&str>,
1388
+ ) -> bool {
1389
+ if provider != Provider::Pi
1390
+ || agent.get("attribution_ambiguous").and_then(serde_json::Value::as_bool) == Some(true)
1391
+ || agent.get("capture_state").and_then(serde_json::Value::as_str)
1392
+ == Some("attribution_ambiguous")
1393
+ || !matches!(
1394
+ agent
1395
+ .get("attribution_confidence")
1396
+ .and_then(serde_json::Value::as_str),
1397
+ Some("high" | "medium")
1398
+ )
1399
+ {
1400
+ return false;
1401
+ }
1402
+ let (Some(embedded_agent_id), Some(forked_from), Some(target_path)) = (
1403
+ embedded_agent_id,
1404
+ agent
1405
+ .get("forked_from")
1406
+ .and_then(serde_json::Value::as_str)
1407
+ .filter(|value| !value.is_empty()),
1408
+ rollout_path.map(RolloutPath::as_path),
1409
+ ) else {
1410
+ return false;
1411
+ };
1412
+ if forked_from != embedded_agent_id || forked_from == agent_id.as_str() {
1413
+ return false;
1414
+ }
1415
+ let Some(source_agent) = state
1416
+ .get("agents")
1417
+ .and_then(|agents| agents.get(forked_from))
1418
+ .filter(|source| agent_provider(source) == Provider::Pi)
1419
+ else {
1420
+ return false;
1421
+ };
1422
+ let Some(source_path) = agent_rollout_path(source_agent) else {
1423
+ return false;
1424
+ };
1425
+ if source_path.as_path() == target_path {
1426
+ return false;
1427
+ }
1428
+ let (Some(session_id), Ok(target_path), Some(target_root)) = (
1429
+ agent_session_id(agent),
1430
+ std::fs::canonicalize(target_path),
1431
+ agent
1432
+ .get("claude_projects_root")
1433
+ .and_then(serde_json::Value::as_str)
1434
+ .filter(|path| !path.is_empty())
1435
+ .map(std::path::PathBuf::from),
1436
+ ) else {
1437
+ return false;
1438
+ };
1439
+ let Ok(target_root) = std::fs::canonicalize(target_root) else {
1440
+ return false;
1441
+ };
1442
+ if !target_path.starts_with(target_root)
1443
+ || std::fs::canonicalize(source_path.as_path())
1444
+ .is_ok_and(|source_path| source_path == target_path)
1445
+ || !resume_backing_probe_for_agent(
1446
+ workspace,
1447
+ agent_id,
1448
+ agent,
1449
+ provider,
1450
+ &session_id,
1451
+ rollout_path,
1452
+ )
1453
+ .exists
1454
+ {
1455
+ return false;
1456
+ }
1457
+ true
1458
+ }
1459
+
1347
1460
  /// ---
1348
1461
  /// purpose: 按 provider 分支探测 resume 依据,并记录所有查过的路径
1349
1462
  /// returns: 存在位与查过的路径列表;持久化的 rollout 路径即使不存在也记进列表
@@ -336,7 +336,7 @@ fn restart_with_selected_team_and_transport(
336
336
  .first()
337
337
  .cloned()
338
338
  .unwrap_or_else(|| "dirty_topology".to_string()),
339
- error: "restart refused: tmux endpoint/socket topology is inconsistent; run diagnose from the intended leader socket before restarting".to_string(),
339
+ error: "restart refused: tmux endpoint/socket topology is inconsistent; run doctor from the intended leader socket before restarting".to_string(),
340
340
  issue_ids: topology_issue_ids,
341
341
  });
342
342
  }
@@ -1133,8 +1133,11 @@ fn restart_with_selected_team_and_transport(
1133
1133
  &session_id,
1134
1134
  agent_rollout_path(&agent).as_ref(),
1135
1135
  );
1136
- let identity_probe = session_identity_probe_for_agent(
1136
+ let identity_probe = session_identity_probe_for_agent_with_fork_ancestry(
1137
+ &selected.run_workspace,
1138
+ &state,
1137
1139
  &decision.agent_id,
1140
+ &agent,
1138
1141
  provider,
1139
1142
  agent_rollout_path(&agent).as_ref(),
1140
1143
  );
@@ -1221,8 +1224,11 @@ fn restart_with_selected_team_and_transport(
1221
1224
  &session_id,
1222
1225
  agent_rollout_path(&agent_after).as_ref(),
1223
1226
  );
1224
- let identity_probe_after = session_identity_probe_for_agent(
1227
+ let identity_probe_after = session_identity_probe_for_agent_with_fork_ancestry(
1228
+ &selected.run_workspace,
1229
+ &state,
1225
1230
  &decision.agent_id,
1231
+ &agent_after,
1226
1232
  provider,
1227
1233
  agent_rollout_path(&agent_after).as_ref(),
1228
1234
  );
@@ -1804,7 +1810,7 @@ fn restart_readiness_timeout_message(
1804
1810
  - tmux session created: {session}\n\
1805
1811
  - worker pane addressable: {pane}\n\
1806
1812
  - coordinator alive: {coordinator}\n\
1807
- Action: check coordinator log {log}, then inspect this workspace with `team-agent diagnose --workspace '{workspace}'`\n\
1813
+ Action: check coordinator log {log}, then inspect this workspace with `team-agent doctor --workspace '{workspace}'`\n\
1808
1814
  Log: coordinator_log={log} state={state} pid_file={pid}",
1809
1815
  missing = restart_readiness_missing_summary(readiness),
1810
1816
  session = yes_no(readiness.session_created),
@@ -2703,7 +2709,7 @@ fn restart_failure_phase(
2703
2709
  // 0.5.39 Slice 1 (tmux-server-death-locate §11.1 B): promote
2704
2710
  // spawn/readiness errors whose stderr contains "server exited
2705
2711
  // unexpectedly" to `tmux_server_crashed`, so downstream event
2706
- // classification and diagnose next_actions ("team-agent diagnose")
2712
+ // classification and doctor next_actions ("team-agent doctor")
2707
2713
  // don't misattribute a whole-server death to a per-agent provider
2708
2714
  // failure. Restart itself does not recover from server crashes in
2709
2715
  // 0.5.39 (§11.1 C is Slice 3, later car); it just classifies clearly.
@@ -2776,7 +2782,7 @@ fn write_restart_agent_failed_event(
2776
2782
  "phase": phase,
2777
2783
  "error": error,
2778
2784
  "action": format!(
2779
- "inspect worker {} output, then restart that worker with `team-agent restart-agent {}` or rerun `team-agent restart --allow-fresh`",
2785
+ "inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
2780
2786
  decision.agent_id,
2781
2787
  decision.agent_id
2782
2788
  ),
@@ -3262,7 +3268,7 @@ fn restart_failure_next_actions(failed_agents: &[RestartFailedAgent]) -> Vec<Str
3262
3268
  .iter()
3263
3269
  .map(|failure| {
3264
3270
  format!(
3265
- "inspect worker {} output, then restart that worker with `team-agent restart-agent {}` or rerun `team-agent restart --allow-fresh`",
3271
+ "inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
3266
3272
  failure.agent_id, failure.agent_id
3267
3273
  )
3268
3274
  })
@@ -4911,7 +4917,7 @@ tasks:
4911
4917
  },
4912
4918
  std::time::Duration::from_secs(30),
4913
4919
  );
4914
- assert!(message.contains("team-agent diagnose --workspace"));
4920
+ assert!(message.contains("team-agent doctor --workspace"));
4915
4921
  assert!(message.contains(workspace.to_string_lossy().as_ref()));
4916
4922
  assert!(!message.contains("--allow-fresh"));
4917
4923
  assert!(!message.contains("restart <agent>"));
@@ -349,8 +349,20 @@ pub(crate) fn classify_restart_plan_with_resume_validation(
349
349
  }
350
350
  _ => (true, Vec::new()),
351
351
  };
352
- let identity_probe =
353
- session_identity_probe_for_agent(&agent_id, provider, rollout_path.as_ref());
352
+ let identity_probe = workspace
353
+ .map(|workspace| {
354
+ session_identity_probe_for_agent_with_fork_ancestry(
355
+ workspace,
356
+ state,
357
+ &agent_id,
358
+ agent,
359
+ provider,
360
+ rollout_path.as_ref(),
361
+ )
362
+ })
363
+ .unwrap_or_else(|| {
364
+ session_identity_probe_for_agent(&agent_id, provider, rollout_path.as_ref())
365
+ });
354
366
  let session_identity_mismatch = session_id.is_some()
355
367
  && provider_can_resume
356
368
  && resume_backing_exists
@@ -66,6 +66,8 @@ struct LifecyclePathRefs<'a> {
66
66
  }
67
67
 
68
68
  mod agent;
69
+ mod avatar;
70
+ pub(crate) use avatar::fork_pi_new_seat_locked;
69
71
  mod common;
70
72
  mod orchestrator;
71
73
  pub mod preflight;
@@ -82,11 +84,16 @@ pub(crate) use agent::{
82
84
  };
83
85
  pub(crate) use common::refresh_missing_provider_sessions;
84
86
  pub(crate) use common::restart_required_missing_session_agent_ids;
85
- pub(crate) use common::session_identity_probe_for_agent;
87
+ pub(crate) use common::{
88
+ session_identity_probe_for_agent, session_identity_probe_for_agent_with_fork_ancestry,
89
+ };
86
90
  // 0.3.24 add-agent socket drift fix: state-aware tmux resolver shared with
87
91
  // `lifecycle::launch::add_agent` / `fork_agent` so all three (restart / add / fork)
88
92
  // route to the SAME tmux socket the live team uses.
89
- pub(crate) use common::lifecycle_worker_tmux_backend_for_selected_state;
93
+ pub(crate) use common::{
94
+ lifecycle_worker_tmux_backend_for_selected_state,
95
+ lifecycle_worker_tmux_backend_selection_for_state,
96
+ };
90
97
  pub(crate) use orchestrator::{halt_plan, plan_status};
91
98
  pub(crate) use rebuild::restart_with_transport_with_session_convergence_deadline;
92
99
  // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): expose the phase
@@ -100,10 +107,10 @@ pub(crate) use rebuild::{
100
107
  restart_candidates, restart_with_transport, restart_with_transport_with_readiness_deadline,
101
108
  select_restart_state,
102
109
  };
103
- pub use remove::{remove_agent, remove_agent_flag_requirements};
104
110
  pub(crate) use remove::remove_agent_with_transport;
105
- pub(crate) use selection::{classify_first_send_at, classify_restart_plan, python_type_name};
111
+ pub use remove::{remove_agent, remove_agent_flag_requirements};
106
112
  pub use selection::decide_start_mode;
113
+ pub(crate) use selection::{classify_first_send_at, classify_restart_plan, python_type_name};
107
114
  // Layer 2 (leader follow-up 2026-06-22): test-visible workspace-aware
108
115
  // classification so lifecycle/tests/restart.rs can exercise the
109
116
  // SessionBackingStoreMissing + checked_paths + RecoveryHint path
@@ -122,7 +122,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
122
122
  "--json",
123
123
  ]);
124
124
  let diagnose_before = case.run_json(&[
125
- "diagnose",
125
+ "doctor",
126
126
  "--workspace",
127
127
  case.workspace_str(),
128
128
  "--team",
@@ -142,7 +142,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
142
142
  "--json",
143
143
  ]);
144
144
  let diagnose_after = case.run_json(&[
145
- "diagnose",
145
+ "doctor",
146
146
  "--workspace",
147
147
  case.workspace_str(),
148
148
  "--team",
@@ -182,7 +182,7 @@ fn diagnose_missing_profile_is_rc1_ok_false_and_zero_token() {
182
182
  .unwrap();
183
183
 
184
184
  let output = Command::new(crate::lifecycle::tests::test_binary_path())
185
- .args(["diagnose", team.to_str().unwrap(), "--workspace", ws.to_str().unwrap(), "--json"])
185
+ .args(["doctor", team.to_str().unwrap(), "--workspace", ws.to_str().unwrap(), "--json"])
186
186
  .current_dir(&team)
187
187
  .output()
188
188
  .expect("diagnose subprocess should run");
@@ -1,7 +1,7 @@
1
1
  use super::launch_spawn::seed_healthy_coordinator;
2
2
  use super::*;
3
3
  use crate::cli::{
4
- cmd_diagnose, cmd_send, cmd_status, lifecycle_port, CmdOutput, CmdResult, DiagnoseArgs,
4
+ cmd_doctor, cmd_send, cmd_status, lifecycle_port, CmdOutput, CmdResult, DoctorArgs,
5
5
  SendArgs, StatusArgs,
6
6
  };
7
7
  use crate::transport::test_support::OfflineTransport;
@@ -582,10 +582,17 @@ fn run_diagnose_after_quick_start() -> Value {
582
582
  .is_some(),
583
583
  "index-missing diagnose requires a persisted seeded owner"
584
584
  );
585
- let diagnose = cmd_diagnose(&DiagnoseArgs {
585
+ let diagnose = cmd_doctor(&DoctorArgs {
586
586
  workspace,
587
587
  json: true,
588
588
  team: Some("teamdir".to_string()),
589
+ spec: None,
590
+ gate: None,
591
+ comms: false,
592
+ fix: false,
593
+ fix_schema: false,
594
+ cleanup_orphans: false,
595
+ confirm: false,
589
596
  });
590
597
  match cmd_value(diagnose) {
591
598
  Value::Object(map) => map