@team-agent/installer 0.5.60 → 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 (265) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +49 -3
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +184 -0
  6. package/crates/team-agent/src/cli/emit.rs +104 -21
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +14 -2
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +296 -8
  11. package/crates/team-agent/src/cli/named_address.rs +26 -2
  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 +3 -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 +5 -1
  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/status_port/store.rs +7 -3
  25. package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  27. package/crates/team-agent/src/cli/types.rs +36 -2
  28. package/crates/team-agent/src/communication_mode/mod.rs +54 -0
  29. package/crates/team-agent/src/compiler/tests.rs +5 -5
  30. package/crates/team-agent/src/compiler.rs +54 -1
  31. package/crates/team-agent/src/conpty/backend.rs +1 -0
  32. package/crates/team-agent/src/conpty/mod.rs +1 -0
  33. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  34. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  35. package/crates/team-agent/src/coordinator/health.rs +1 -0
  36. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  37. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  38. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  39. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
  41. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  44. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  45. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  46. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  47. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  48. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
  49. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  50. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  51. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
  52. package/crates/team-agent/src/coordinator/tick.rs +24 -28
  53. package/crates/team-agent/src/coordinator/types.rs +3 -176
  54. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  55. package/crates/team-agent/src/db/message_store.rs +47 -2
  56. package/crates/team-agent/src/db/migration.rs +5 -4
  57. package/crates/team-agent/src/db/mod.rs +1 -0
  58. package/crates/team-agent/src/db/schema.rs +1 -0
  59. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  60. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  61. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  62. package/crates/team-agent/src/fake_worker.rs +21 -1
  63. package/crates/team-agent/src/layout/manager.rs +3 -7
  64. package/crates/team-agent/src/layout/mod.rs +2 -2
  65. package/crates/team-agent/src/layout/overlay.rs +6 -1
  66. package/crates/team-agent/src/layout/placement.rs +1 -0
  67. package/crates/team-agent/src/layout/recovery.rs +3 -0
  68. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  69. package/crates/team-agent/src/layout/sessions.rs +8 -1
  70. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  71. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  72. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  73. package/crates/team-agent/src/leader/helpers.rs +1 -0
  74. package/crates/team-agent/src/leader/incident.rs +1 -0
  75. package/crates/team-agent/src/leader/lease.rs +27 -22
  76. package/crates/team-agent/src/leader/mod.rs +2 -3
  77. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  78. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  79. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  80. package/crates/team-agent/src/leader/registry.rs +3 -15
  81. package/crates/team-agent/src/leader/start.rs +1072 -186
  82. package/crates/team-agent/src/leader/takeover.rs +10 -232
  83. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  84. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  85. package/crates/team-agent/src/leader/tests/identity.rs +71 -59
  86. package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
  87. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  88. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  89. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  90. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  91. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  92. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  93. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  94. package/crates/team-agent/src/leader/types.rs +1 -28
  95. package/crates/team-agent/src/lib.rs +7 -0
  96. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  97. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  98. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  99. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  100. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  102. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  104. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  105. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  106. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
  107. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  108. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  109. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  110. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  111. package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
  112. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  113. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  114. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  115. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  116. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  117. package/crates/team-agent/src/lifecycle/restart.rs +13 -11
  118. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  119. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  120. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  121. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  122. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  123. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  124. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  125. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  126. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  127. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  128. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  129. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  130. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  131. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  132. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  133. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  134. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  135. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  136. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  137. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  138. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  139. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  140. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  141. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  142. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  143. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  144. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  145. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  146. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  147. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  148. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  149. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  150. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  151. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  152. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  153. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  154. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  155. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  156. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  157. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  158. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  159. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  160. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  161. package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
  162. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  163. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  164. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  165. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  166. package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
  167. package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
  168. package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
  169. package/crates/team-agent/src/mcp_server/tools.rs +101 -66
  170. package/crates/team-agent/src/mcp_server/types.rs +6 -18
  171. package/crates/team-agent/src/mcp_server/wire.rs +32 -26
  172. package/crates/team-agent/src/messaging/activity.rs +1 -0
  173. package/crates/team-agent/src/messaging/address.rs +1 -0
  174. package/crates/team-agent/src/messaging/delivery.rs +109 -41
  175. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  176. package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
  177. package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
  178. package/crates/team-agent/src/messaging/mod.rs +10 -14
  179. package/crates/team-agent/src/messaging/peers.rs +2 -0
  180. package/crates/team-agent/src/messaging/persist.rs +3 -1
  181. package/crates/team-agent/src/messaging/presentation.rs +111 -1
  182. package/crates/team-agent/src/messaging/results.rs +166 -17
  183. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  184. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  185. package/crates/team-agent/src/messaging/send.rs +135 -89
  186. package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
  187. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
  188. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  189. package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
  190. package/crates/team-agent/src/messaging/types.rs +7 -2
  191. package/crates/team-agent/src/messaging/watchers.rs +24 -13
  192. package/crates/team-agent/src/model/enums.rs +1 -0
  193. package/crates/team-agent/src/model/errors.rs +1 -0
  194. package/crates/team-agent/src/model/ids.rs +1 -0
  195. package/crates/team-agent/src/model/mod.rs +2 -0
  196. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  197. package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
  198. package/crates/team-agent/src/model/paths.rs +1 -0
  199. package/crates/team-agent/src/model/permissions.rs +3 -2
  200. package/crates/team-agent/src/model/routing.rs +1 -0
  201. package/crates/team-agent/src/model/spec.rs +17 -0
  202. package/crates/team-agent/src/model/task_graph.rs +1 -0
  203. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  204. package/crates/team-agent/src/model/yaml.rs +1 -0
  205. package/crates/team-agent/src/packaging/install.rs +9 -147
  206. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  207. package/crates/team-agent/src/packaging/mod.rs +1 -0
  208. package/crates/team-agent/src/packaging/repair.rs +2 -0
  209. package/crates/team-agent/src/packaging/tests.rs +29 -218
  210. package/crates/team-agent/src/packaging/types.rs +8 -15
  211. package/crates/team-agent/src/platform/argv.rs +4 -0
  212. package/crates/team-agent/src/platform/errors.rs +10 -1
  213. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  214. package/crates/team-agent/src/platform/mod.rs +0 -61
  215. package/crates/team-agent/src/platform/process.rs +1 -0
  216. package/crates/team-agent/src/provider/adapter.rs +1 -0
  217. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  218. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  219. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  220. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  221. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  222. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  223. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  224. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  225. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  226. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  227. package/crates/team-agent/src/provider/classify.rs +1 -0
  228. package/crates/team-agent/src/provider/faults.rs +1 -26
  229. package/crates/team-agent/src/provider/helpers.rs +1 -0
  230. package/crates/team-agent/src/provider/mod.rs +1 -1
  231. package/crates/team-agent/src/provider/session/capture.rs +41 -22
  232. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  233. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  234. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  235. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  236. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  237. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  238. package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
  239. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  240. package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
  241. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  242. package/crates/team-agent/src/provider/session_scan.rs +6 -42
  243. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  244. package/crates/team-agent/src/provider/types.rs +1 -0
  245. package/crates/team-agent/src/provider/wire.rs +1 -0
  246. package/crates/team-agent/src/state/identity.rs +16 -5
  247. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  248. package/crates/team-agent/src/state/mod.rs +2 -0
  249. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  250. package/crates/team-agent/src/state/ownership.rs +10 -4
  251. package/crates/team-agent/src/state/paths.rs +12 -0
  252. package/crates/team-agent/src/state/persist.rs +9 -3
  253. package/crates/team-agent/src/state/projection.rs +15 -2
  254. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  255. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  256. package/crates/team-agent/src/state/repository.rs +7 -0
  257. package/crates/team-agent/src/state/selector.rs +1 -0
  258. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  259. package/crates/team-agent/src/tmux_backend.rs +63 -0
  260. package/crates/team-agent/src/topology.rs +3 -0
  261. package/crates/team-agent/src/transport.rs +15 -0
  262. package/package.json +4 -4
  263. package/skills/team-agent/command-coverage.json +379 -0
  264. package/crates/team-agent/src/leader/inject.rs +0 -33
  265. package/crates/team-agent/src/provider/command.rs +0 -80
@@ -94,8 +94,13 @@ fn leader_start_plan_pins_mode_and_leader_env() {
94
94
  );
95
95
  assert!(!plan.is_external_leader);
96
96
  assert!(
97
- plan.argv.iter().any(|arg| arg == "attach-session"),
98
- "no-tmux managed launch attaches the user client to the leader window: {:?}",
97
+ plan.argv.len() >= 5
98
+ && plan.argv[0] == "tmux"
99
+ && plan.argv[1] == "-L"
100
+ && plan.argv[2].starts_with("ta-")
101
+ && plan.argv.iter().any(|arg| arg == "attach-session")
102
+ && plan.argv.iter().any(|arg| arg.ends_with(":fake")),
103
+ "no-tmux managed launch uses the workspace socket and attaches the user client to the leader window: {:?}",
99
104
  plan.argv
100
105
  );
101
106
  // plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。
@@ -130,7 +135,7 @@ fn leader_start_plan_external_leader_keeps_exec_provider_in_tmux() {
130
135
  std::fs::create_dir_all(&ws).unwrap();
131
136
 
132
137
  let provider_args = vec!["--".to_string(), "--model".to_string(), "opus".to_string()];
133
- let plan = leader_start_plan(
138
+ let plan = crate::leader::start::leader_start_plan_after_ambient_authority_check(
134
139
  Provider::Fake,
135
140
  &provider_args,
136
141
  &ws,
@@ -202,7 +207,7 @@ fn managed_leader_uses_dedicated_leader_session_independent_of_state_session_nam
202
207
 
203
208
  #[test]
204
209
  #[serial_test::serial(env)]
205
- fn in_tmux_default_leader_runs_provider_in_current_pane() {
210
+ fn in_tmux_default_leader_uses_managed_client_and_external_opt_out_keeps_exec_provider() {
206
211
  let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
207
212
  let ws = std::env::temp_dir().join(format!("ta_rs_lsp_switch_{}", std::process::id()));
208
213
  std::fs::create_dir_all(&ws).unwrap();
@@ -210,26 +215,65 @@ fn in_tmux_default_leader_runs_provider_in_current_pane() {
210
215
  let endpoint = format!("/private/tmp/tmux-501/{socket},88432,187");
211
216
  let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint)), ("TMUX_PANE", Some("%7"))]);
212
217
 
213
- let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
218
+ let managed = crate::leader::start::leader_start_plan_after_ambient_authority_check(
219
+ Provider::Fake,
220
+ &[],
221
+ &ws,
222
+ false,
223
+ false,
224
+ None,
225
+ false,
226
+ )
227
+ .unwrap();
214
228
 
215
- assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
216
- assert!(!plan.is_external_leader);
217
- assert!(plan.session_name.is_none());
218
- assert_eq!(plan.leader_window, None);
219
- assert_eq!(plan.argv, vec!["fake".to_string()]);
229
+ assert_eq!(
230
+ managed.mode,
231
+ LeaderStartMode::ManagedTmuxClient,
232
+ "N41 managed default: an in-tmux caller on the workspace server must use the managed client path"
233
+ );
234
+ assert!(!managed.is_external_leader);
235
+ assert!(managed.session_name.is_some());
236
+ assert_eq!(
237
+ managed.leader_window.as_ref().map(WindowName::as_str),
238
+ Some("fake")
239
+ );
220
240
  assert!(
221
- !plan
241
+ managed.argv.iter().any(|arg| arg == "switch-client")
242
+ && !managed.argv.iter().any(|arg| arg == "attach-session"),
243
+ "N41 managed default: same-server dispatch must switch the existing client, not nested-attach: {:?}",
244
+ managed.argv
245
+ );
246
+ assert_eq!(managed.provider_argv, vec!["fake".to_string()]);
247
+
248
+ let external = crate::leader::start::leader_start_plan_after_ambient_authority_check(
249
+ Provider::Fake,
250
+ &[],
251
+ &ws,
252
+ false,
253
+ false,
254
+ None,
255
+ true,
256
+ )
257
+ .unwrap();
258
+
259
+ assert_eq!(external.mode, LeaderStartMode::ExecProvider);
260
+ assert!(external.is_external_leader);
261
+ assert_eq!(external.leader_window, None);
262
+ assert_eq!(external.argv, vec!["fake".to_string()]);
263
+ assert_eq!(external.provider_argv, vec!["fake".to_string()]);
264
+ assert!(
265
+ !external
222
266
  .argv
223
267
  .iter()
224
268
  .any(|arg| arg == "switch-client" || arg == "attach-session"),
225
- "in-tmux default launch must not create or attach a background leader session: {:?}",
226
- plan.argv
269
+ "N41 external opt-out: explicit external topology must remain a direct provider path: {:?}",
270
+ external.argv
227
271
  );
228
272
  }
229
273
 
230
274
  #[test]
231
275
  #[serial_test::serial(env)]
232
- fn in_tmux_default_leader_does_not_refuse_different_tmux_server() {
276
+ fn in_tmux_external_opt_out_keeps_exec_provider_across_server_boundary() {
233
277
  let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
234
278
  let _e = EnvGuard::apply(&[
235
279
  ("TMUX", Some("/private/tmp/tmux-501/default,88432,187")),
@@ -238,12 +282,21 @@ fn in_tmux_default_leader_does_not_refuse_different_tmux_server() {
238
282
  let ws = std::env::temp_dir().join(format!("ta_rs_lsp_refuse_{}", std::process::id()));
239
283
  std::fs::create_dir_all(&ws).unwrap();
240
284
 
241
- let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
285
+ let plan = crate::leader::start::leader_start_plan_after_ambient_authority_check(
286
+ Provider::Fake,
287
+ &[],
288
+ &ws,
289
+ false,
290
+ false,
291
+ None,
292
+ true,
293
+ )
294
+ .unwrap();
242
295
 
243
296
  assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
244
- assert!(!plan.is_external_leader);
245
- assert!(plan.session_name.is_none());
297
+ assert!(plan.is_external_leader);
246
298
  assert_eq!(plan.argv, vec!["fake".to_string()]);
299
+ assert_eq!(plan.provider_argv, vec!["fake".to_string()]);
247
300
  }
248
301
 
249
302
  #[test]
@@ -420,45 +473,4 @@ fn p2_leader_detect_divergence_catches_owner_uuid_split() {
420
473
  // refactor must also update these tests.
421
474
  // ═══════════════════════════════════════════════════════════════════════
422
475
 
423
- #[test]
424
- fn unit0_leader_session_prefix_constant_is_stable() {
425
- // Pinned: the literal leader session prefix that gates "is this a
426
- // leader session" decisions everywhere in the runtime.
427
- assert_eq!(
428
- crate::leader::start::LEADER_SESSION_PREFIX,
429
- "team-agent-leader-"
430
- );
431
- // The layout module re-exports the same value (kept in sync via
432
- // const re-export).
433
- assert_eq!(
434
- crate::layout::sessions::LEADER_SESSION_PREFIX,
435
- crate::leader::start::LEADER_SESSION_PREFIX
436
- );
437
- }
438
-
439
- #[test]
440
- fn unit0_leader_prefixed_name_must_never_be_taken_as_worker_session() {
441
- // Pinned invariant fed into unit-1 typed identity: any session
442
- // name starting with `LEADER_SESSION_PREFIX` is a leader launcher
443
- // session, never a worker session. unit-1 will wrap this rule in
444
- // typed constructors (`WorkerSession::new` rejects the prefix,
445
- // `LeaderLauncherSession::new` requires it).
446
- let leader = "team-agent-leader-claude-x-aaaaaaaaaaaa";
447
- let worker = "team-real-worker";
448
- let prefix = crate::leader::start::LEADER_SESSION_PREFIX;
449
- assert!(leader.starts_with(prefix));
450
- assert!(!worker.starts_with(prefix));
451
- // Symmetry: the runtime today uses this exact check
452
- // (cli/mod.rs:261 `starts_with(LEADER_SESSION_PREFIX)`) to spare
453
- // leader sessions during shutdown.
454
- let leader_name = crate::transport::SessionName::new(leader);
455
- let worker_name = crate::transport::SessionName::new(worker);
456
- assert!(
457
- crate::layout::sessions::is_leader_session(&leader_name),
458
- "is_leader_session must accept a leader-prefixed session",
459
- );
460
- assert!(
461
- !crate::layout::sessions::is_leader_session(&worker_name),
462
- "is_leader_session must reject a non-prefixed session",
463
- );
464
- }
476
+ include!("identity_session_names.rs");
@@ -0,0 +1,42 @@
1
+ #[test]
2
+ fn unit0_leader_session_prefix_constant_is_stable() {
3
+ // Pinned: the literal leader session prefix that gates "is this a
4
+ // leader session" decisions everywhere in the runtime.
5
+ assert_eq!(
6
+ crate::leader::start::LEADER_SESSION_PREFIX,
7
+ "team-agent-leader-"
8
+ );
9
+ // The layout module re-exports the same value (kept in sync via
10
+ // const re-export).
11
+ assert_eq!(
12
+ crate::layout::sessions::LEADER_SESSION_PREFIX,
13
+ crate::leader::start::LEADER_SESSION_PREFIX
14
+ );
15
+ }
16
+
17
+ #[test]
18
+ fn unit0_leader_prefixed_name_must_never_be_taken_as_worker_session() {
19
+ // Pinned invariant fed into unit-1 typed identity: any session
20
+ // name starting with `LEADER_SESSION_PREFIX` is a leader launcher
21
+ // session, never a worker session. unit-1 will wrap this rule in
22
+ // typed constructors (`WorkerSession::new` rejects the prefix,
23
+ // `LeaderLauncherSession::new` requires it).
24
+ let leader = "team-agent-leader-claude-x-aaaaaaaaaaaa";
25
+ let worker = "team-real-worker";
26
+ let prefix = crate::leader::start::LEADER_SESSION_PREFIX;
27
+ assert!(leader.starts_with(prefix));
28
+ assert!(!worker.starts_with(prefix));
29
+ // Symmetry: the runtime today uses this exact check
30
+ // (cli/mod.rs:261 `starts_with(LEADER_SESSION_PREFIX)`) to spare
31
+ // leader sessions during shutdown.
32
+ let leader_name = crate::transport::SessionName::new(leader);
33
+ let worker_name = crate::transport::SessionName::new(worker);
34
+ assert!(
35
+ crate::layout::sessions::is_leader_session(&leader_name),
36
+ "is_leader_session must accept a leader-prefixed session",
37
+ );
38
+ assert!(
39
+ !crate::layout::sessions::is_leader_session(&worker_name),
40
+ "is_leader_session must reject a non-prefixed session",
41
+ );
42
+ }
@@ -1,198 +1,5 @@
1
1
  use super::*;
2
2
 
3
- // =====================================================================
4
- // 8. idle-takeover 接线 — build_idle_nodes / leader_node(unimplemented → RED)
5
- // 命门:rollout_path=None → Unknown → never idle;leader path/provider 缺 → 省略;
6
- // MUST-NOT-13:经 TurnStateClassifier mock,断言零 provider client 直连。
7
- // =====================================================================
8
-
9
- // build_idle_nodes:一个 worker 有 rollout_path(可读)→ classifier.classify 被调一次;
10
- // 经注入分类器(零 provider client)。stopped/paused 跳过。
11
- #[test]
12
- fn build_idle_nodes_uses_injected_classifier_no_provider_client() {
13
- // 真实 session 文件,使 _read_session_tail 读到非空 → classifier 返回注入 state。
14
- let dir = std::env::temp_dir().join(format!("ta_rs_idle_{}", std::process::id()));
15
- std::fs::create_dir_all(&dir).unwrap();
16
- let log = dir.join("w1.jsonl");
17
- std::fs::write(&log, b"{\"type\":\"turn_complete\"}\n").unwrap();
18
- let state = serde_json::json!({
19
- "agents": {
20
- "w1": {"provider": "codex", "rollout_path": log.to_string_lossy(), "status": "running"},
21
- "w_stopped": {"provider": "codex", "rollout_path": log.to_string_lossy(), "status": "stopped"},
22
- }
23
- });
24
- let clf = CountingClassifier::new(TurnState::Idle);
25
- let nodes = build_idle_nodes(&state, &clf).unwrap();
26
- // stopped 被跳过(__init__ wiring:29)→ 仅 w1。
27
- assert_eq!(nodes.len(), 1);
28
- assert_eq!(nodes[0].node_id, "w1");
29
- assert_eq!(nodes[0].role, NodeRole::Worker);
30
- assert_eq!(nodes[0].state, TurnState::Idle);
31
- // MUST-NOT-13:分类只经注入 classifier(此 mock 计数==1),零 provider client 直连。
32
- assert_eq!(clf.calls.get(), 1, "每个 live node 恰调一次注入 classify");
33
- }
34
-
35
- // bug-085:rollout_path=None → 读到空串 → Unknown(不当 idle)。
36
- #[test]
37
- fn build_idle_nodes_none_rollout_path_yields_unknown_not_idle() {
38
- let state = serde_json::json!({
39
- "agents": {
40
- "w1": {"provider": "codex", "status": "running"} // 无 rollout_path。
41
- }
42
- });
43
- // 即使 mock 默认想返 Idle,空 session-log → classifier 返 Unknown(见 mock 逻辑)。
44
- let clf = CountingClassifier::new(TurnState::Idle);
45
- let nodes = build_idle_nodes(&state, &clf).unwrap();
46
- assert_eq!(nodes.len(), 1);
47
- assert_eq!(
48
- nodes[0].state,
49
- TurnState::Unknown,
50
- "None rollout_path → Unknown,绝不 idle"
51
- );
52
- assert!(
53
- !nodes[0].state.is_idle_for_takeover(),
54
- "unknown ≠ idle:不得对 takeover 放行"
55
- );
56
- }
57
-
58
- // _leader_node:leader path 或 provider 缺 → None(省略而非猜 idle)。
59
- #[test]
60
- fn leader_node_omitted_when_path_or_provider_missing() {
61
- let clf = CountingClassifier::new(TurnState::Idle);
62
- // 既无 leader.rollout_path 也无 receiver.rollout_path → None。
63
- let state_no_path = serde_json::json!({
64
- "leader": {"id": "leader", "provider": "codex"},
65
- "leader_receiver": {"provider": "codex"}
66
- });
67
- assert!(
68
- leader_node(&state_no_path, &clf).unwrap().is_none(),
69
- "缺 path → 省略 leader 节点"
70
- );
71
- // 有 path 但无 provider → None。
72
- let dir = std::env::temp_dir().join(format!("ta_rs_lnode_{}", std::process::id()));
73
- std::fs::create_dir_all(&dir).unwrap();
74
- let log = dir.join("leader.jsonl");
75
- std::fs::write(&log, b"{\"type\":\"turn_open\"}\n").unwrap();
76
- let state_no_provider = serde_json::json!({
77
- "leader": {"id": "leader", "rollout_path": log.to_string_lossy()}
78
- });
79
- assert!(
80
- leader_node(&state_no_provider, &clf).unwrap().is_none(),
81
- "缺 provider → 省略 leader 节点(不猜 idle)"
82
- );
83
- }
84
-
85
- // _leader_node:path+provider 齐 → 经 classifier 产 role=leader 节点(C13)。
86
- #[test]
87
- fn leader_node_classified_via_injected_classifier() {
88
- let dir = std::env::temp_dir().join(format!("ta_rs_lnode2_{}", std::process::id()));
89
- std::fs::create_dir_all(&dir).unwrap();
90
- let log = dir.join("leader.jsonl");
91
- std::fs::write(&log, b"{\"type\":\"turn_complete\"}\n").unwrap();
92
- let state = serde_json::json!({
93
- "leader": {"id": "leader", "provider": "codex", "rollout_path": log.to_string_lossy()}
94
- });
95
- let clf = CountingClassifier::new(TurnState::Working);
96
- let node = leader_node(&state, &clf)
97
- .unwrap()
98
- .expect("path+provider 齐 → 有 leader 节点");
99
- assert_eq!(node.role, NodeRole::Leader);
100
- assert_eq!(node.node_id, "leader");
101
- assert_eq!(node.state, TurnState::Working);
102
- assert_eq!(
103
- clf.calls.get(),
104
- 1,
105
- "leader 分类经注入 classifier 一次,零 provider client"
106
- );
107
- }
108
-
109
- // =====================================================================
110
- // 9. classify_provider_turn_state 门面(unimplemented → RED)
111
- // unknown/abnormal 且有 event_sink → 写 idle_takeover.classify。
112
- // =====================================================================
113
-
114
- // 门面经注入 classifier 分类;空文本 → Unknown(本 mock),验证返回 TurnClassification。
115
- #[test]
116
- fn classify_provider_turn_state_returns_classification_via_injected_classifier() {
117
- let clf = CountingClassifier::new(TurnState::Idle);
118
- let c =
119
- classify_provider_turn_state(Provider::Codex, "{\"type\":\"turn_complete\"}", &clf, None)
120
- .unwrap();
121
- assert_eq!(c.state, TurnState::Idle);
122
- assert_eq!(clf.calls.get(), 1);
123
- // 空文本 → Unknown(unknown ≠ idle 命门下游)。
124
- let clf2 = CountingClassifier::new(TurnState::Idle);
125
- let c2 = classify_provider_turn_state(Provider::Codex, "", &clf2, None).unwrap();
126
- assert_eq!(c2.state, TurnState::Unknown);
127
- assert!(!c2.state.is_idle_for_takeover());
128
- }
129
-
130
- // event_sink + unknown/abnormal → 写 idle_takeover.classify(事件名字节锁)。
131
- #[test]
132
- fn classify_event_name_is_idle_takeover_classify() {
133
- assert_eq!(
134
- LeaderEvent::IdleTakeoverClassify.name(),
135
- "idle_takeover.classify"
136
- );
137
- }
138
-
139
- // =====================================================================
140
- // 10. push_idle_reminder(unimplemented → RED):!should_ping → no-op。
141
- // =====================================================================
142
-
143
- #[test]
144
- fn push_idle_reminder_noop_when_should_not_ping() {
145
- let ws = std::env::temp_dir().join(format!("ta_rs_push_{}", std::process::id()));
146
- std::fs::create_dir_all(&ws).unwrap();
147
- let event_log = crate::event_log::EventLog::new(&ws);
148
- let state = serde_json::json!({"leader": {"id": "leader"}});
149
- let result = TakeoverReminderResult {
150
- should_ping: false,
151
- message: None,
152
- interrupted_nodes: vec![],
153
- reason: Some("not_armed_no_worker_turn".into()),
154
- };
155
- // should_ping=false → no-op,返回 Ok(())。现 unimplemented → RED。
156
- push_idle_reminder(&ws, &state, &event_log, &result).unwrap();
157
- // 强化:no-op 必须真的什么都不做 —— 不写 idle_takeover.reminder 事件(EventLog 无该事件)。
158
- let events = event_log.tail(50).unwrap();
159
- assert!(
160
- !events
161
- .iter()
162
- .any(|e| e["event"] == serde_json::json!("idle_takeover.reminder")),
163
- "should_ping=false 时绝不写 reminder 事件"
164
- );
165
- }
166
-
167
- // #236 nag_removal (N35) — push_idle_reminder is now a no-op shim.
168
- // [OLD] assertion: should_ping=true → writes idle_takeover.reminder event (with
169
- // interrupted/reason byte-locked golden payload).
170
- // [NEW] assertion: even when should_ping=true, push_idle_reminder writes NO event
171
- // and emits NO leader-bound message; ownership/handover happens only via explicit
172
- // `claim-leader` / `takeover` commands. The function signature is preserved so
173
- // existing callers (coordinator/tick.rs, lifecycle wiring) still resolve.
174
- #[test]
175
- fn push_idle_reminder_is_silent_no_op_under_n35_even_when_should_ping_true() {
176
- let ws = std::env::temp_dir().join(format!("ta_rs_push2_{}", std::process::id()));
177
- std::fs::create_dir_all(&ws).unwrap();
178
- let event_log = crate::event_log::EventLog::new(&ws);
179
- let state = serde_json::json!({"leader": {"id": "leader"}});
180
- let result = TakeoverReminderResult {
181
- should_ping: true,
182
- message: Some("neutral reminder body".into()),
183
- interrupted_nodes: vec!["w1".into()],
184
- reason: Some("armed_all_idle".into()),
185
- };
186
- push_idle_reminder(&ws, &state, &event_log, &result).unwrap();
187
- let events = event_log.tail(50).unwrap();
188
- assert!(
189
- !events
190
- .iter()
191
- .any(|e| e["event"] == serde_json::json!("idle_takeover.reminder")),
192
- "#236 N35: push_idle_reminder must no longer emit the reminder nag event; got {events:?}"
193
- );
194
- }
195
-
196
3
  // idle_takeover.reminder / push_failed 事件名字节锁。
197
4
  #[test]
198
5
  fn idle_takeover_event_names_byte_locked() {
@@ -67,51 +67,6 @@ fn attach_leader_binds_pane_advances_epoch_and_persists() {
67
67
  assert_eq!(persisted["team_owner"]["owner_epoch"], serde_json::json!(1));
68
68
  }
69
69
 
70
- // autobind:$TMUX_PANE 缺 → Ok(None)(__init__.py:885-887,锁前直接返回,不开锁)。
71
- // 强化:断言这是 lock-not-acquired 早退 —— 不写 state.json(无 receiver/owner 落盘),
72
- // 不发任何 leader_receiver.* 事件(锁前 return,连 EventLog 都不构造)。
73
- #[test]
74
- fn autobind_returns_none_when_tmux_pane_missing() {
75
- if std::env::var_os("TMUX_PANE").is_some() {
76
- return; // 在 tmux 内:走绑定路径,本用例只验缺失分支。
77
- }
78
- let ws = std::env::temp_dir().join(format!("ta_rs_auto_{}", std::process::id()));
79
- std::fs::create_dir_all(&ws).unwrap();
80
- let r = autobind_leader_receiver_from_env(&ws, Provider::Codex, LeaseSource::Restart).unwrap();
81
- assert!(r.is_none(), "$TMUX_PANE 缺 → autobind 返回 Ok(None)");
82
- // lock-not-acquired 早退:state.json 未被写(load 兜底成空 state,无 team_owner/receiver)。
83
- let st = crate::state::persist::load_runtime_state(&ws).unwrap();
84
- assert!(
85
- st.get("leader_receiver").is_none(),
86
- "skip 路径不应落 receiver"
87
- );
88
- assert!(st.get("team_owner").is_none(), "skip 路径不应落 owner");
89
- // 早退发生在 EventLog 构造前 → 无任何事件文件。
90
- let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
91
- assert!(events.is_empty(), "skip 路径绝不写审计事件");
92
- }
93
-
94
- // autobind 成功支:$TMUX_PANE 命中 → 锁内 attach_leader_to_state → Ok(Some(receiver))。
95
- // receiver.pane_id == 注入 pane,discovery==EnvPane($TMUX_PANE 直接命中)。
96
- // env 变更进程全局且与并行 test race,且依赖跨 lane 的 live pane resolver,故 real-machine-gated。
97
- #[test]
98
- #[ignore = "needs live $TMUX_PANE + cross-lane _resolve_leader_pane; env mutation races parallel tests"]
99
- fn autobind_binds_env_pane_on_success() {
100
- let ws = std::env::temp_dir().join(format!("ta_rs_auto_ok_{}", std::process::id()));
101
- std::fs::create_dir_all(&ws).unwrap();
102
- // SAFETY: #[ignore]d,单独 `--ignored --test-threads=1` 跑,不与并行 test race。
103
- unsafe { std::env::set_var("TMUX_PANE", "%42") };
104
- let r = autobind_leader_receiver_from_env(&ws, Provider::Codex, LeaseSource::Restart).unwrap();
105
- unsafe { std::env::remove_var("TMUX_PANE") };
106
- let receiver = r.expect("$TMUX_PANE 命中 → Ok(Some(receiver))");
107
- assert_eq!(receiver.pane_id, PaneId::new("%42"));
108
- assert_eq!(
109
- receiver.discovery,
110
- Some(Discovery::EnvPane),
111
- "$TMUX_PANE 命中 → discovery=env_pane"
112
- );
113
- }
114
-
115
70
  // claim_leader:无 ambiguous incident → 走 claim_lease_no_incident 直接 acquire/CAS。
116
71
  // 现 unimplemented → RED;锁住返回 LeaseResult。
117
72
  #[test]
@@ -1,6 +1,5 @@
1
1
  #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2
2
  use super::*;
3
- use std::cell::Cell;
4
3
 
5
4
  // ── helpers ────────────────────────────────────────────────────────────
6
5
  /// derive 一个真 LeaderSessionUuid(骨架无公开裸构造器,仅 `derive`)。
@@ -8,46 +7,6 @@ fn uuid(fp: &str, ws: &str, user: &str, team: &str) -> LeaderSessionUuid {
8
7
  LeaderSessionUuid::derive(fp, ws, user, team).unwrap()
9
8
  }
10
9
 
11
- /// 注入式 turn-state 分类器:MUST-NOT-13 命门 —— 统计 classify 调用次数,
12
- /// 据此断言 idle 面经此 trait 分类、绝不直连任何 provider client。
13
- struct CountingClassifier {
14
- calls: Cell<usize>,
15
- result: TurnState,
16
- }
17
- impl CountingClassifier {
18
- fn new(result: TurnState) -> Self {
19
- Self {
20
- calls: Cell::new(0),
21
- result,
22
- }
23
- }
24
- }
25
- impl TurnStateClassifier for CountingClassifier {
26
- fn classify(
27
- &self,
28
- _provider: Provider,
29
- session_log_text: &str,
30
- ) -> Result<TurnClassification, LeaderError> {
31
- self.calls.set(self.calls.get() + 1);
32
- // 空 session-log 文本 → unknown(bug-085:None rollout_path 漏穿后读到空串)。
33
- let state = if session_log_text.is_empty() {
34
- TurnState::Unknown
35
- } else {
36
- self.result
37
- };
38
- Ok(TurnClassification {
39
- state,
40
- turn_id: None,
41
- annotations: vec![],
42
- reason: if state == TurnState::Unknown {
43
- Some("empty_session_log".into())
44
- } else {
45
- None
46
- },
47
- })
48
- }
49
- }
50
-
51
10
  static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
52
11
 
53
12
  /// RAII env override: restores prior values on drop (incl. panic unwind), so a
@@ -133,9 +92,13 @@ fn event_names(ws: &std::path::Path) -> Vec<String> {
133
92
 
134
93
  mod basics;
135
94
  mod byte_findings;
95
+ mod claim_leader_mailbox_flush_contract;
96
+ mod historical_inventory_upgrade_gate_red;
136
97
  mod identity;
137
98
  mod idle;
138
99
  mod lease_api;
139
100
  mod lease_claim;
140
101
  mod rediscover;
102
+ mod team_in_team_state_scope_red;
103
+ mod terminal_and_replay_vs_rerender_invariants_red;
141
104
  mod wake_start_owner;
@@ -0,0 +1,83 @@
1
+ use std::path::PathBuf;
2
+ use std::sync::atomic::{AtomicU64, Ordering};
3
+
4
+ const CALLER_IDENTITY_ENVS: &[&str] = &[
5
+ "TMUX",
6
+ "TMUX_PANE",
7
+ "TEAM_AGENT_LEADER_PANE_ID",
8
+ "TEAM_AGENT_LEADER_SESSION_UUID",
9
+ "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
10
+ "TEAM_AGENT_LEADER_SESSION_NAME",
11
+ "TEAM_AGENT_LEADER_PROVIDER",
12
+ "TEAM_AGENT_MACHINE_FINGERPRINT",
13
+ "TEAM_AGENT_WORKSPACE",
14
+ "TEAM_AGENT_TEAM_ID",
15
+ "TEAM_AGENT_OWNER_TEAM_ID",
16
+ "TEAM_AGENT_ACTIVE_TEAM",
17
+ "TEAM_AGENT_ID",
18
+ "TEAM_AGENT_AGENT_ID",
19
+ "TEAM_AGENT_AUTH_MODE",
20
+ "TEAM_AGENT_LEADER_BYPASS",
21
+ "TEAM_AGENT_LEADER_BYPASS_SOURCE",
22
+ "TEAM_AGENT_LEADER_BYPASS_PROVIDER",
23
+ "TEAM_AGENT_LEADER_BYPASS_FLAG",
24
+ "TEAM_AGENT_MCP_AUTO_APPROVE",
25
+ "TEAM_AGENT_MCP_AUTO_APPROVE_SOURCE",
26
+ ];
27
+
28
+ static COUNTER: AtomicU64 = AtomicU64::new(0);
29
+
30
+ pub(super) struct RehomedTestEnv {
31
+ root: PathBuf,
32
+ previous: Vec<(&'static str, Option<String>)>,
33
+ }
34
+
35
+ impl RehomedTestEnv {
36
+ pub(super) fn enter(tag: &str) -> Self {
37
+ let root = std::env::temp_dir().join(format!(
38
+ "ta-leader-rehomed-{tag}-{}-{}",
39
+ std::process::id(),
40
+ COUNTER.fetch_add(1, Ordering::Relaxed)
41
+ ));
42
+ let _ = std::fs::remove_dir_all(&root);
43
+ let home = root.join("home");
44
+ std::fs::create_dir_all(&home).expect("create rehomed test HOME");
45
+
46
+ let mut previous = Vec::new();
47
+ for key in std::iter::once("HOME").chain(CALLER_IDENTITY_ENVS.iter().copied()) {
48
+ previous.push((key, std::env::var(key).ok()));
49
+ }
50
+ unsafe {
51
+ std::env::set_var("HOME", &home);
52
+ for key in CALLER_IDENTITY_ENVS {
53
+ std::env::remove_var(key);
54
+ }
55
+ }
56
+
57
+ Self { root, previous }
58
+ }
59
+
60
+ pub(super) fn workspace(&self, tag: &str) -> PathBuf {
61
+ let path = self.root.join(format!(
62
+ "workspace-{tag}-{}",
63
+ COUNTER.fetch_add(1, Ordering::Relaxed)
64
+ ));
65
+ std::fs::create_dir_all(&path).expect("create rehomed test workspace");
66
+ path
67
+ }
68
+ }
69
+
70
+ impl Drop for RehomedTestEnv {
71
+ fn drop(&mut self) {
72
+ for (key, value) in self.previous.drain(..).rev() {
73
+ unsafe {
74
+ if let Some(value) = value {
75
+ std::env::set_var(key, value);
76
+ } else {
77
+ std::env::remove_var(key);
78
+ }
79
+ }
80
+ }
81
+ let _ = std::fs::remove_dir_all(&self.root);
82
+ }
83
+ }