@team-agent/installer 0.5.61 → 0.5.62

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 (250) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +5 -2
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +1 -0
  6. package/crates/team-agent/src/cli/emit.rs +41 -7
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +6 -1
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +33 -3
  11. package/crates/team-agent/src/cli/named_address.rs +8 -1
  12. package/crates/team-agent/src/cli/profile.rs +2 -1
  13. package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
  14. package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
  15. package/crates/team-agent/src/cli/send/persist.rs +1 -0
  16. package/crates/team-agent/src/cli/send/presentation.rs +1 -0
  17. package/crates/team-agent/src/cli/send/resolve.rs +206 -1
  18. package/crates/team-agent/src/cli/send.rs +2 -1
  19. package/crates/team-agent/src/cli/spec.rs +1 -0
  20. package/crates/team-agent/src/cli/status.rs +2 -1
  21. package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
  22. package/crates/team-agent/src/cli/status_port/format.rs +1 -0
  23. package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
  24. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  25. package/crates/team-agent/src/cli/types.rs +3 -2
  26. package/crates/team-agent/src/communication_mode/mod.rs +1 -0
  27. package/crates/team-agent/src/compiler.rs +1 -0
  28. package/crates/team-agent/src/conpty/backend.rs +1 -0
  29. package/crates/team-agent/src/conpty/mod.rs +1 -0
  30. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  31. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  32. package/crates/team-agent/src/coordinator/health.rs +1 -0
  33. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  34. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  35. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  36. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  37. package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
  38. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  39. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  41. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  44. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  45. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
  46. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  47. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
  49. package/crates/team-agent/src/coordinator/tick.rs +24 -28
  50. package/crates/team-agent/src/coordinator/types.rs +3 -176
  51. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  52. package/crates/team-agent/src/db/message_store.rs +2 -1
  53. package/crates/team-agent/src/db/migration.rs +5 -4
  54. package/crates/team-agent/src/db/mod.rs +1 -0
  55. package/crates/team-agent/src/db/schema.rs +1 -0
  56. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  57. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  58. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  59. package/crates/team-agent/src/layout/manager.rs +3 -7
  60. package/crates/team-agent/src/layout/mod.rs +2 -2
  61. package/crates/team-agent/src/layout/overlay.rs +6 -1
  62. package/crates/team-agent/src/layout/placement.rs +1 -0
  63. package/crates/team-agent/src/layout/recovery.rs +3 -0
  64. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  65. package/crates/team-agent/src/layout/sessions.rs +8 -1
  66. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  67. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  68. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  69. package/crates/team-agent/src/leader/helpers.rs +1 -0
  70. package/crates/team-agent/src/leader/incident.rs +1 -0
  71. package/crates/team-agent/src/leader/lease.rs +27 -22
  72. package/crates/team-agent/src/leader/mod.rs +2 -3
  73. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  74. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  75. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  76. package/crates/team-agent/src/leader/registry.rs +3 -15
  77. package/crates/team-agent/src/leader/start.rs +373 -125
  78. package/crates/team-agent/src/leader/takeover.rs +10 -232
  79. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  80. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  81. package/crates/team-agent/src/leader/tests/identity.rs +7 -2
  82. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  83. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  84. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  85. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  86. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  87. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  88. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  89. package/crates/team-agent/src/leader/types.rs +1 -28
  90. package/crates/team-agent/src/lib.rs +3 -0
  91. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  92. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  93. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  95. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  96. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  97. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  99. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  100. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
  101. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  102. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  104. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  105. package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -3
  106. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  107. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  108. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  109. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  110. package/crates/team-agent/src/lifecycle/restart.rs +13 -11
  111. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  112. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  113. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  114. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  115. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  116. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  117. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  118. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  119. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  120. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  121. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  122. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  123. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  124. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  125. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  126. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  127. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  128. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  129. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  130. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  131. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  132. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  133. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  134. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  135. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  136. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  137. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  138. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  139. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  140. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  141. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  142. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  143. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  144. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  145. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  146. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  147. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  148. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  149. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  150. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  151. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  152. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  153. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  154. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  155. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  156. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  157. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  158. package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
  159. package/crates/team-agent/src/mcp_server/tools.rs +1 -0
  160. package/crates/team-agent/src/mcp_server/types.rs +1 -18
  161. package/crates/team-agent/src/mcp_server/wire.rs +5 -5
  162. package/crates/team-agent/src/messaging/activity.rs +1 -0
  163. package/crates/team-agent/src/messaging/address.rs +1 -0
  164. package/crates/team-agent/src/messaging/delivery.rs +4 -3
  165. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  166. package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
  167. package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
  168. package/crates/team-agent/src/messaging/mod.rs +10 -14
  169. package/crates/team-agent/src/messaging/peers.rs +2 -0
  170. package/crates/team-agent/src/messaging/persist.rs +3 -1
  171. package/crates/team-agent/src/messaging/presentation.rs +2 -1
  172. package/crates/team-agent/src/messaging/results.rs +1 -0
  173. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  174. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  175. package/crates/team-agent/src/messaging/send.rs +41 -8
  176. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  177. package/crates/team-agent/src/messaging/types.rs +2 -2
  178. package/crates/team-agent/src/messaging/watchers.rs +19 -13
  179. package/crates/team-agent/src/model/enums.rs +1 -0
  180. package/crates/team-agent/src/model/errors.rs +1 -0
  181. package/crates/team-agent/src/model/ids.rs +1 -0
  182. package/crates/team-agent/src/model/mod.rs +1 -0
  183. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  184. package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
  185. package/crates/team-agent/src/model/paths.rs +1 -0
  186. package/crates/team-agent/src/model/permissions.rs +3 -2
  187. package/crates/team-agent/src/model/routing.rs +1 -0
  188. package/crates/team-agent/src/model/spec.rs +1 -0
  189. package/crates/team-agent/src/model/task_graph.rs +1 -0
  190. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  191. package/crates/team-agent/src/model/yaml.rs +1 -0
  192. package/crates/team-agent/src/packaging/install.rs +9 -147
  193. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  194. package/crates/team-agent/src/packaging/mod.rs +1 -0
  195. package/crates/team-agent/src/packaging/repair.rs +2 -0
  196. package/crates/team-agent/src/packaging/tests.rs +29 -218
  197. package/crates/team-agent/src/packaging/types.rs +8 -15
  198. package/crates/team-agent/src/platform/argv.rs +4 -0
  199. package/crates/team-agent/src/platform/errors.rs +10 -1
  200. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  201. package/crates/team-agent/src/platform/mod.rs +0 -61
  202. package/crates/team-agent/src/platform/process.rs +1 -0
  203. package/crates/team-agent/src/provider/adapter.rs +1 -0
  204. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  205. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  206. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  207. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  208. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  209. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  210. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  211. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  212. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  213. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  214. package/crates/team-agent/src/provider/classify.rs +1 -0
  215. package/crates/team-agent/src/provider/faults.rs +1 -26
  216. package/crates/team-agent/src/provider/helpers.rs +1 -0
  217. package/crates/team-agent/src/provider/mod.rs +1 -1
  218. package/crates/team-agent/src/provider/session/capture.rs +1 -4
  219. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  220. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  221. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  222. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  223. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  224. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  225. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
  226. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  227. package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
  228. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  229. package/crates/team-agent/src/provider/session_scan.rs +1 -42
  230. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  231. package/crates/team-agent/src/provider/types.rs +1 -0
  232. package/crates/team-agent/src/provider/wire.rs +1 -0
  233. package/crates/team-agent/src/state/identity.rs +16 -5
  234. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  235. package/crates/team-agent/src/state/mod.rs +2 -0
  236. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  237. package/crates/team-agent/src/state/ownership.rs +10 -4
  238. package/crates/team-agent/src/state/paths.rs +12 -0
  239. package/crates/team-agent/src/state/persist.rs +9 -3
  240. package/crates/team-agent/src/state/projection.rs +15 -2
  241. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  242. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  243. package/crates/team-agent/src/state/repository.rs +7 -0
  244. package/crates/team-agent/src/state/selector.rs +1 -0
  245. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  246. package/crates/team-agent/src/tmux_backend.rs +63 -0
  247. package/crates/team-agent/src/transport.rs +15 -0
  248. package/package.json +4 -4
  249. package/crates/team-agent/src/leader/inject.rs +0 -33
  250. package/crates/team-agent/src/provider/command.rs +0 -80
@@ -0,0 +1,542 @@
1
+ //! Team-in-team explicit identity / workspace / add-agent scope contracts.
2
+ //!
3
+ //! #245/#246/#247 share the same failure family: the user-visible selected team
4
+ //! identity is accepted on the command surface, but later state/projection code
5
+ //! falls back to the team directory basename or raw top-level runtime state.
6
+
7
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
8
+
9
+ #[path = "../../../tests/support/hermetic.rs"]
10
+ mod hermetic_guard;
11
+ #[allow(dead_code)]
12
+ fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
13
+
14
+ use std::collections::{BTreeMap, HashSet};
15
+ use std::path::{Path, PathBuf};
16
+ use std::sync::atomic::{AtomicU64, Ordering};
17
+ use std::sync::Mutex;
18
+
19
+ use serde_json::{json, Value};
20
+ use serial_test::serial;
21
+ use team_agent::lifecycle::{
22
+ add_agent_with_transport, quick_start_with_transport, start_agent_with_transport,
23
+ QuickStartReport,
24
+ };
25
+ use team_agent::model::ids::AgentId;
26
+ use team_agent::state::persist::{load_runtime_state, save_runtime_state};
27
+ use team_agent::state::StateError;
28
+ use team_agent::transport::{
29
+ AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
30
+ InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, PaneLiveness, SessionName,
31
+ SetEnvOutcome, SpawnResult, SubmitVerification, Target, Transport, TransportError,
32
+ TurnVerification, WindowName,
33
+ };
34
+
35
+ #[test]
36
+ #[serial(env)]
37
+ fn quick_start_team_id_persists_requested_team_key_for_lifecycle_selection() {
38
+ let _env = EnvGuard::unset();
39
+ let root = tmp_dir("team-id-key");
40
+ seed_healthy_coordinator(&root);
41
+ let team_dir = write_team_dir(&root, "teamdir", "w1");
42
+ let transport = RecordingTransport::default();
43
+
44
+ let report = quick_start_with_transport(&team_dir, None, true, Some("lifeteam"), &transport)
45
+ .expect("quick-start --team-id lifeteam should launch via fake transport");
46
+ assert_ready("quick-start --team-id lifeteam", &report, "team-lifeteam");
47
+
48
+ let state = load_runtime_state(&root).expect("runtime state after quick-start");
49
+ assert!(
50
+ state.pointer("/teams/lifeteam").is_some(),
51
+ "quick-start --team-id lifeteam must persist state.teams.lifeteam so lifecycle commands can select it; state={state}"
52
+ );
53
+ assert!(
54
+ state.pointer("/teams/teamdir").is_none(),
55
+ "requested --team-id lifeteam must not be silently replaced by the teamdir basename key; state={state}"
56
+ );
57
+
58
+ let start = start_agent_with_transport(
59
+ &root,
60
+ &AgentId::new("w1"),
61
+ false,
62
+ false,
63
+ true,
64
+ Some("lifeteam"),
65
+ &transport,
66
+ );
67
+ assert!(
68
+ start.is_ok(),
69
+ "lifecycle command start-agent --team lifeteam must select the quick-started team by requested team id, not only by teamdir basename; result={start:?} state={state}"
70
+ );
71
+ }
72
+
73
+ #[test]
74
+ fn quick_start_workspace_override_is_not_dropped_before_runtime_state_write() {
75
+ let cli_types =
76
+ std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/types.rs")).unwrap();
77
+ let quick_start_args = source_section(
78
+ &cli_types,
79
+ "pub struct QuickStartArgs",
80
+ "pub struct InitArgs",
81
+ );
82
+ assert!(
83
+ quick_start_args.contains("pub workspace: PathBuf"),
84
+ "quick-start TEAMDIR --workspace WS must preserve WS in QuickStartArgs; otherwise runtime state is written to TEAMDIR's parent before lifecycle can honor --workspace. QuickStartArgs={quick_start_args}"
85
+ );
86
+
87
+ let cli_mod =
88
+ std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/mod.rs")).unwrap();
89
+ let lifecycle_port = source_section(&cli_mod, "pub fn quick_start(", "pub fn start_leader");
90
+ assert!(
91
+ !lifecycle_port.contains("let _ = workspace;"),
92
+ "lifecycle_port::quick_start must not discard --workspace; runtime state must be written to the explicit WS. source={lifecycle_port}"
93
+ );
94
+
95
+ let emit =
96
+ std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/emit.rs")).unwrap();
97
+ let quick_start_args_fn = source_section(&emit, "fn quick_start_args", "fn init_args");
98
+ assert!(
99
+ quick_start_args_fn.contains("workspace: workspace.clone()")
100
+ || quick_start_args_fn.contains("workspace,"),
101
+ "quick_start_args must carry parsed --workspace into QuickStartArgs, not use it only to resolve a relative TEAMDIR. source={quick_start_args_fn}"
102
+ );
103
+ }
104
+
105
+ #[test]
106
+ #[serial(env)]
107
+ fn add_agent_writes_new_agent_into_selected_team_projection_used_by_start_agent() {
108
+ let _env = EnvGuard::set([
109
+ ("TEAM_AGENT_ID", "worker_b"),
110
+ ("TEAM_AGENT_TEAM_ID", "lifeteam"),
111
+ ]);
112
+ let root = tmp_dir("add-agent-team-projection");
113
+ seed_healthy_coordinator(&root);
114
+ let team_dir = write_team_dir(&root, "teamdir", "worker_a");
115
+ seed_lifeteam_state(&root, &team_dir);
116
+ let role_file = root.join("worker_b.md");
117
+ std::fs::write(&role_file, role_doc("worker_b")).unwrap();
118
+ let transport = RecordingTransport::default().with_session_present(true);
119
+
120
+ let add = add_agent_with_transport(
121
+ &team_dir,
122
+ &AgentId::new("worker_b"),
123
+ &role_file,
124
+ false,
125
+ Some("lifeteam"),
126
+ &transport,
127
+ );
128
+ assert!(
129
+ add.is_ok(),
130
+ "add-agent --team lifeteam must write worker_b into the same teams.lifeteam projection start-agent reads; current failure is the real 'agent added not found' split. result={add:?}"
131
+ );
132
+
133
+ let state = load_runtime_state(&root).expect("runtime state after add-agent");
134
+ assert!(
135
+ state.pointer("/teams/lifeteam/agents/worker_b").is_some(),
136
+ "add-agent --team lifeteam must persist the new worker in state.teams.lifeteam.agents, not only raw top-level agents; state={state}"
137
+ );
138
+ let start = start_agent_with_transport(
139
+ &root,
140
+ &AgentId::new("worker_b"),
141
+ false,
142
+ false,
143
+ true,
144
+ Some("lifeteam"),
145
+ &transport,
146
+ );
147
+ assert!(
148
+ start.is_ok(),
149
+ "start-agent --team lifeteam worker_b must see the worker added by add-agent in the same selected-team scope; result={start:?} state={state}"
150
+ );
151
+ }
152
+
153
+ #[test]
154
+ #[serial(env)]
155
+ fn add_agent_committed_roster_survives_live_coordinator_stale_snapshot_save() {
156
+ let _env = EnvGuard::unset();
157
+ let root = tmp_dir("add-agent-live-coordinator-clobber");
158
+ let team_dir = write_team_dir(&root, "teamdir", "worker_a");
159
+ seed_lifeteam_state(&root, &team_dir);
160
+
161
+ let stale_coordinator_snapshot =
162
+ load_runtime_state(&root).expect("coordinator captured pre-add snapshot");
163
+ let mut after_add = stale_coordinator_snapshot.clone();
164
+ insert_agent_in_state(&mut after_add, "worker_b");
165
+ save_runtime_state(&root, &after_add).expect("simulate add-agent committed worker_b");
166
+
167
+ // Phase C design §Behavior Preservation / Caller Audit: the old contract
168
+ // expected merge to resurrect rows from disk. The new contract rejects stale
169
+ // live-topology saves with SaveConflict; coordinator tick degrades and
170
+ // retries from a fresh snapshot on its next pass.
171
+ let stale_save = save_runtime_state(&root, &stale_coordinator_snapshot);
172
+ assert!(
173
+ matches!(stale_save, Err(StateError::SaveConflict(_))),
174
+ "stale coordinator tick snapshot must be rejected instead of merging over add-agent topology; result={stale_save:?}"
175
+ );
176
+
177
+ let final_state = load_runtime_state(&root).expect("state after stale coordinator save");
178
+ assert_eq!(
179
+ final_state
180
+ .pointer("/agents/worker_b/window")
181
+ .and_then(Value::as_str),
182
+ Some("worker_b"),
183
+ "SaveConflict must leave add-agent's committed top-level topology intact; final_state={final_state}"
184
+ );
185
+ assert_eq!(
186
+ final_state
187
+ .pointer("/teams/lifeteam/agents/worker_b/window")
188
+ .and_then(Value::as_str),
189
+ Some("worker_b"),
190
+ "SaveConflict must leave add-agent's committed team-scoped topology intact; final_state={final_state}"
191
+ );
192
+ save_runtime_state(&root, &final_state).expect("fresh tick snapshot should save cleanly");
193
+ }
194
+
195
+ fn assert_ready(label: &str, report: &QuickStartReport, expected_session: &str) {
196
+ match report {
197
+ QuickStartReport::Ready { session_name, .. } => assert_eq!(
198
+ session_name.as_str(),
199
+ expected_session,
200
+ "{label}: session_name must derive from explicit requested identity"
201
+ ),
202
+ other => panic!("{label}: expected Ready, got {other:?}"),
203
+ }
204
+ }
205
+
206
+ fn source_section<'a>(source: &'a str, start: &str, end: &str) -> &'a str {
207
+ let idx = source.find(start).expect("source start marker");
208
+ let rest = &source[idx..];
209
+ let end_idx = rest.find(end).unwrap_or(rest.len());
210
+ &rest[..end_idx]
211
+ }
212
+
213
+ fn insert_agent_in_state(state: &mut Value, agent_id: &str) {
214
+ let agent = agent_state("lifeteam", agent_id);
215
+ state
216
+ .get_mut("agents")
217
+ .and_then(Value::as_object_mut)
218
+ .expect("top-level agents")
219
+ .insert(agent_id.to_string(), agent.clone());
220
+ state
221
+ .pointer_mut("/teams/lifeteam/agents")
222
+ .and_then(Value::as_object_mut)
223
+ .expect("team-scoped agents")
224
+ .insert(agent_id.to_string(), agent);
225
+ }
226
+
227
+ fn write_team_dir(root: &Path, dir_name: &str, agent: &str) -> PathBuf {
228
+ let team = root.join(dir_name);
229
+ std::fs::create_dir_all(team.join("agents")).unwrap();
230
+ std::fs::write(
231
+ team.join("TEAM.md"),
232
+ format!(
233
+ "---\nname: {dir_name}\nobjective: Identity scope contract.\nprovider: codex\n---\n\nTeam.\n"
234
+ ),
235
+ )
236
+ .unwrap();
237
+ std::fs::write(
238
+ team.join("agents").join(format!("{agent}.md")),
239
+ role_doc(agent),
240
+ )
241
+ .unwrap();
242
+ team
243
+ }
244
+
245
+ fn role_doc(agent: &str) -> String {
246
+ format!(
247
+ "---\nname: {agent}\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
248
+ )
249
+ }
250
+
251
+ fn seed_lifeteam_state(root: &Path, team_dir: &Path) {
252
+ let state = json!({
253
+ "active_team_key": "lifeteam",
254
+ "session_name": "team-lifeteam",
255
+ "team_dir": team_dir.to_string_lossy().to_string(),
256
+ "spec_path": team_dir.join("team.spec.yaml").to_string_lossy().to_string(),
257
+ "owner_epoch": 1,
258
+ "agents": {
259
+ "worker_a": agent_state("lifeteam", "worker_a")
260
+ },
261
+ "tasks": [],
262
+ "teams": {
263
+ "lifeteam": {
264
+ "status": "alive",
265
+ "session_name": "team-lifeteam",
266
+ "team_dir": team_dir.to_string_lossy().to_string(),
267
+ "spec_path": team_dir.join("team.spec.yaml").to_string_lossy().to_string(),
268
+ "owner_epoch": 1,
269
+ "agents": {
270
+ "worker_a": agent_state("lifeteam", "worker_a")
271
+ },
272
+ "tasks": []
273
+ }
274
+ }
275
+ });
276
+ save_runtime_state(root, &state).unwrap();
277
+ let spec = team_agent::compiler::compile_team(team_dir).unwrap();
278
+ std::fs::write(
279
+ team_dir.join("team.spec.yaml"),
280
+ team_agent::model::yaml::dumps(&spec),
281
+ )
282
+ .unwrap();
283
+ }
284
+
285
+ fn agent_state(team: &str, agent: &str) -> Value {
286
+ json!({
287
+ "agent_id": agent,
288
+ "owner_team_id": team,
289
+ "status": "running",
290
+ "provider": "codex",
291
+ "role": "Worker",
292
+ "window": agent
293
+ })
294
+ }
295
+
296
+ fn seed_healthy_coordinator(workspace: &Path) {
297
+ let workspace = team_agent::coordinator::WorkspacePath::new(workspace.to_path_buf());
298
+ std::fs::create_dir_all(team_agent::model::paths::runtime_dir(workspace.as_path())).unwrap();
299
+ let _ = team_agent::message_store::MessageStore::open(workspace.as_path()).unwrap();
300
+ let pid = team_agent::coordinator::Pid::new(std::process::id());
301
+ team_agent::coordinator::write_coordinator_metadata(
302
+ &workspace,
303
+ pid,
304
+ team_agent::coordinator::MetadataSource::Boot,
305
+ )
306
+ .unwrap();
307
+ std::fs::write(
308
+ team_agent::coordinator::coordinator_pid_path(&workspace),
309
+ pid.to_string(),
310
+ )
311
+ .unwrap();
312
+ }
313
+
314
+ fn tmp_dir(tag: &str) -> PathBuf {
315
+ static N: AtomicU64 = AtomicU64::new(0);
316
+ let dir = std::env::temp_dir().join(format!(
317
+ "ta-rs-team-in-team-identity-scope-{tag}-{}-{}",
318
+ std::process::id(),
319
+ N.fetch_add(1, Ordering::Relaxed)
320
+ ));
321
+ let _ = std::fs::remove_dir_all(&dir);
322
+ std::fs::create_dir_all(&dir).unwrap();
323
+ std::fs::canonicalize(dir).unwrap()
324
+ }
325
+
326
+ struct EnvGuard {
327
+ previous: Vec<(&'static str, Option<String>)>,
328
+ }
329
+
330
+ impl EnvGuard {
331
+ fn unset() -> Self {
332
+ let keys = [
333
+ "TMUX",
334
+ "TMUX_PANE",
335
+ "TEAM_AGENT_ID",
336
+ "TEAM_AGENT_TEAM_ID",
337
+ "TEAM_AGENT_OWNER_TEAM_ID",
338
+ "TEAM_AGENT_LEADER_PANE_ID",
339
+ "TEAM_AGENT_LEADER_SESSION_UUID",
340
+ "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
341
+ "TEAM_AGENT_LEADER_PROVIDER",
342
+ ];
343
+ let previous = keys
344
+ .iter()
345
+ .map(|key| (*key, std::env::var(key).ok()))
346
+ .collect::<Vec<_>>();
347
+ for key in keys {
348
+ unsafe {
349
+ std::env::remove_var(key);
350
+ }
351
+ }
352
+ Self { previous }
353
+ }
354
+
355
+ fn set(values: [(&'static str, &'static str); 2]) -> Self {
356
+ let previous = values
357
+ .iter()
358
+ .map(|(key, _)| (*key, std::env::var(key).ok()))
359
+ .collect::<Vec<_>>();
360
+ for (key, value) in values {
361
+ unsafe {
362
+ std::env::set_var(key, value);
363
+ }
364
+ }
365
+ Self { previous }
366
+ }
367
+ }
368
+
369
+ impl Drop for EnvGuard {
370
+ fn drop(&mut self) {
371
+ for (key, value) in self.previous.drain(..).rev() {
372
+ unsafe {
373
+ if let Some(value) = value {
374
+ std::env::set_var(key, value);
375
+ } else {
376
+ std::env::remove_var(key);
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ #[derive(Debug, Default)]
384
+ struct RecordingTransport {
385
+ sessions: Mutex<HashSet<String>>,
386
+ spawned: Mutex<Vec<SpawnedPane>>,
387
+ session_present: Mutex<bool>,
388
+ }
389
+
390
+ #[derive(Debug, Clone)]
391
+ struct SpawnedPane {
392
+ pane_id: PaneId,
393
+ session: SessionName,
394
+ window: WindowName,
395
+ }
396
+
397
+ impl RecordingTransport {
398
+ fn with_session_present(self, present: bool) -> Self {
399
+ *self.session_present.lock().unwrap() = present;
400
+ self
401
+ }
402
+
403
+ fn spawn_result(&self, session: &SessionName, window: &WindowName) -> SpawnResult {
404
+ self.sessions
405
+ .lock()
406
+ .unwrap()
407
+ .insert(session.as_str().to_string());
408
+ let mut spawned = self.spawned.lock().unwrap();
409
+ let pane_id = PaneId::new(format!("%{}", spawned.len() + 1));
410
+ spawned.push(SpawnedPane {
411
+ pane_id: pane_id.clone(),
412
+ session: session.clone(),
413
+ window: window.clone(),
414
+ });
415
+ SpawnResult {
416
+ pane_id,
417
+ session: session.clone(),
418
+ window: window.clone(),
419
+ child_pid: None,
420
+ }
421
+ }
422
+ }
423
+
424
+ impl Transport for RecordingTransport {
425
+ fn kind(&self) -> BackendKind {
426
+ BackendKind::Tmux
427
+ }
428
+
429
+ fn spawn_first(
430
+ &self,
431
+ session: &SessionName,
432
+ window: &WindowName,
433
+ _argv: &[String],
434
+ _cwd: &Path,
435
+ _env: &BTreeMap<String, String>,
436
+ ) -> Result<SpawnResult, TransportError> {
437
+ Ok(self.spawn_result(session, window))
438
+ }
439
+
440
+ fn spawn_into(
441
+ &self,
442
+ session: &SessionName,
443
+ window: &WindowName,
444
+ _argv: &[String],
445
+ _cwd: &Path,
446
+ _env: &BTreeMap<String, String>,
447
+ ) -> Result<SpawnResult, TransportError> {
448
+ Ok(self.spawn_result(session, window))
449
+ }
450
+
451
+ fn inject(
452
+ &self,
453
+ _target: &Target,
454
+ _payload: &InjectPayload,
455
+ _submit: Key,
456
+ _bracketed: bool,
457
+ ) -> Result<InjectReport, TransportError> {
458
+ Ok(InjectReport {
459
+ stage_reached: InjectStage::Submit,
460
+ inject_verification: InjectVerification::CaptureContainsToken,
461
+ submit_verification: SubmitVerification::PastedContentPromptAbsentAfterSubmit,
462
+ turn_verification: TurnVerification::NotYetObserved,
463
+ attempts: 1,
464
+ submit_diagnostics: None,
465
+ })
466
+ }
467
+
468
+ fn send_keys(&self, _target: &Target, _keys: &[Key]) -> Result<(), TransportError> {
469
+ Ok(())
470
+ }
471
+
472
+ fn capture(
473
+ &self,
474
+ _target: &Target,
475
+ range: CaptureRange,
476
+ ) -> Result<CapturedText, TransportError> {
477
+ Ok(CapturedText {
478
+ text: String::new(),
479
+ range,
480
+ })
481
+ }
482
+
483
+ fn query(&self, _target: &Target, _field: PaneField) -> Result<Option<String>, TransportError> {
484
+ Ok(None)
485
+ }
486
+
487
+ fn liveness(&self, _pane: &PaneId) -> Result<PaneLiveness, TransportError> {
488
+ Ok(PaneLiveness::Live)
489
+ }
490
+
491
+ fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
492
+ Ok(self
493
+ .spawned
494
+ .lock()
495
+ .unwrap()
496
+ .iter()
497
+ .map(|pane| PaneInfo {
498
+ pane_id: pane.pane_id.clone(),
499
+ session: pane.session.clone(),
500
+ window_index: None,
501
+ window_name: Some(pane.window.clone()),
502
+ pane_index: None,
503
+ tty: None,
504
+ current_command: Some("codex".to_string()),
505
+ current_path: None,
506
+ active: false,
507
+ pane_pid: None,
508
+ leader_env: BTreeMap::new(),
509
+ })
510
+ .collect())
511
+ }
512
+
513
+ fn has_session(&self, session: &SessionName) -> Result<bool, TransportError> {
514
+ Ok(*self.session_present.lock().unwrap()
515
+ || self.sessions.lock().unwrap().contains(session.as_str()))
516
+ }
517
+
518
+ fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
519
+ Ok(Vec::new())
520
+ }
521
+
522
+ fn set_session_env(
523
+ &self,
524
+ _session: &SessionName,
525
+ _key: &str,
526
+ _value: &str,
527
+ ) -> Result<SetEnvOutcome, TransportError> {
528
+ Ok(SetEnvOutcome::Applied)
529
+ }
530
+
531
+ fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
532
+ Ok(())
533
+ }
534
+
535
+ fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
536
+ Ok(())
537
+ }
538
+
539
+ fn attach_session(&self, _session: &SessionName) -> Result<AttachOutcome, TransportError> {
540
+ Ok(AttachOutcome::Attached)
541
+ }
542
+ }