@team-agent/installer 0.3.14 → 0.3.16

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 (46) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -0
  4. package/crates/team-agent/src/cli/emit.rs +92 -11
  5. package/crates/team-agent/src/cli/leader.rs +2 -0
  6. package/crates/team-agent/src/cli/mod.rs +255 -37
  7. package/crates/team-agent/src/cli/send.rs +252 -0
  8. package/crates/team-agent/src/cli/status.rs +5 -1
  9. package/crates/team-agent/src/cli/status_port.rs +24 -0
  10. package/crates/team-agent/src/cli/tests/base.rs +21 -3
  11. package/crates/team-agent/src/cli/tests/divergence.rs +6 -6
  12. package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
  13. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +285 -2
  14. package/crates/team-agent/src/cli/tests/status_send.rs +25 -0
  15. package/crates/team-agent/src/cli/types.rs +32 -1
  16. package/crates/team-agent/src/compiler/tests.rs +2 -2
  17. package/crates/team-agent/src/compiler.rs +1 -1
  18. package/crates/team-agent/src/fake_worker.rs +32 -145
  19. package/crates/team-agent/src/leader/start.rs +272 -8
  20. package/crates/team-agent/src/leader/tests/basics.rs +1 -0
  21. package/crates/team-agent/src/leader/tests/identity.rs +121 -25
  22. package/crates/team-agent/src/leader/types.rs +9 -0
  23. package/crates/team-agent/src/lifecycle/display.rs +3 -3
  24. package/crates/team-agent/src/lifecycle/launch.rs +631 -70
  25. package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
  26. package/crates/team-agent/src/lifecycle/restart/common.rs +38 -2
  27. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +79 -4
  28. package/crates/team-agent/src/lifecycle/tests/core.rs +14 -5
  29. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +731 -8
  30. package/crates/team-agent/src/lifecycle/types.rs +5 -0
  31. package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
  32. package/crates/team-agent/src/mcp_server/wire.rs +153 -3
  33. package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
  34. package/crates/team-agent/src/messaging/mod.rs +6 -3
  35. package/crates/team-agent/src/messaging/results.rs +240 -158
  36. package/crates/team-agent/src/messaging/send.rs +3 -2
  37. package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
  38. package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
  39. package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
  40. package/crates/team-agent/src/os_probe.rs +11 -0
  41. package/crates/team-agent/src/state/persist.rs +6 -0
  42. package/crates/team-agent/src/tmux_backend.rs +85 -0
  43. package/crates/team-agent/src/transport/test_support.rs +46 -1
  44. package/crates/team-agent/src/transport.rs +27 -0
  45. package/package.json +4 -4
  46. package/skills/team-agent/references/recovery-runbook.md +277 -0
@@ -59,35 +59,131 @@ use super::*;
59
59
  #[test]
60
60
  #[serial_test::serial(env)]
61
61
  fn leader_start_plan_pins_mode_and_leader_env() {
62
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
63
+ let _e = EnvGuard::apply(&[("TMUX", None), ("TMUX_PANE", None)]);
62
64
  let ws = std::env::temp_dir().join(format!("ta_rs_lsp_{}", std::process::id()));
63
65
  std::fs::create_dir_all(&ws).unwrap();
64
- let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None).unwrap();
66
+ let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
65
67
  assert_eq!(plan.provider, Provider::Fake);
66
- if std::env::var_os("TMUX").is_some() {
67
- // 已在 tmux 内 → exec in-place。
68
- assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
69
- } else {
70
- // 不在 tmux → 新建 tmux session(测试环境假定 tmux 可用;否则 Err(Start))。
71
- assert_eq!(plan.mode, LeaderStartMode::NewTmuxSession);
72
- // session_name 由派生公式确定。
73
- assert_eq!(plan.session_name.as_ref(), Some(&leader_session_name(Provider::Fake, &ws)));
74
- // plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)
75
- assert!(!plan.detached, "leader_start_plan 返回值 detached false");
76
- // leader_env 携带 5 个 TEAM_AGENT_* 导出键(_leader_provider_env)。
77
- for key in [
78
- "TEAM_AGENT_LEADER_PROVIDER",
79
- "TEAM_AGENT_LEADER_SESSION_UUID",
80
- "TEAM_AGENT_MACHINE_FINGERPRINT",
81
- "TEAM_AGENT_WORKSPACE",
82
- "TEAM_AGENT_TEAM_ID",
83
- ] {
84
- assert!(plan.leader_env.contains_key(key), "leader_env 缺导出键 {key}");
85
- }
86
- assert_eq!(
87
- plan.leader_env.get("TEAM_AGENT_LEADER_PROVIDER").map(String::as_str),
88
- Some("fake")
89
- );
68
+ assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
69
+ assert_eq!(
70
+ plan.session_name.as_ref().map(SessionName::as_str),
71
+ Some("team-current")
72
+ );
73
+ assert_eq!(plan.leader_window.as_ref().map(WindowName::as_str), Some("leader"));
74
+ assert!(!plan.is_external_leader);
75
+ assert!(
76
+ plan.argv.iter().any(|arg| arg == "attach-session"),
77
+ "no-tmux managed launch attaches the user client to the team leader window: {:?}",
78
+ plan.argv
79
+ );
80
+ assert!(
81
+ plan.argv.iter().any(|arg| arg == "team-current:leader"),
82
+ "managed client target must be the team leader window: {:?}",
83
+ plan.argv
84
+ );
85
+ // plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。
86
+ assert!(!plan.detached, "leader_start_plan 返回值 detached 恒 false");
87
+ // leader_env 携带 5 个 TEAM_AGENT_* 导出键(_leader_provider_env)。
88
+ for key in [
89
+ "TEAM_AGENT_LEADER_PROVIDER",
90
+ "TEAM_AGENT_LEADER_SESSION_UUID",
91
+ "TEAM_AGENT_MACHINE_FINGERPRINT",
92
+ "TEAM_AGENT_WORKSPACE",
93
+ "TEAM_AGENT_TEAM_ID",
94
+ ] {
95
+ assert!(plan.leader_env.contains_key(key), "leader_env 缺导出键 {key}");
90
96
  }
97
+ assert_eq!(
98
+ plan.leader_env.get("TEAM_AGENT_LEADER_PROVIDER").map(String::as_str),
99
+ Some("fake")
100
+ );
101
+ }
102
+
103
+ #[test]
104
+ #[serial_test::serial(env)]
105
+ fn leader_start_plan_external_leader_keeps_exec_provider_in_tmux() {
106
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
107
+ let _e = EnvGuard::apply(&[("TMUX", Some("/private/tmp/tmux-501/default,88432,187"))]);
108
+ let ws = std::env::temp_dir().join(format!("ta_rs_lsp_external_{}", std::process::id()));
109
+ std::fs::create_dir_all(&ws).unwrap();
110
+
111
+ let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, true).unwrap();
112
+
113
+ assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
114
+ assert!(plan.is_external_leader);
115
+ assert_eq!(plan.leader_window, None);
116
+ assert_eq!(plan.argv, vec!["fake".to_string()]);
117
+ }
118
+
119
+ #[test]
120
+ #[serial_test::serial(env)]
121
+ fn managed_leader_reuses_existing_team_session_name_without_double_prefix() {
122
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
123
+ let _e = EnvGuard::apply(&[("TMUX", None), ("TMUX_PANE", None)]);
124
+ let ws = std::env::temp_dir().join(format!("ta_rs_lsp_existing_{}", std::process::id()));
125
+ std::fs::create_dir_all(&ws).unwrap();
126
+ crate::state::persist::save_runtime_state(
127
+ &ws,
128
+ &serde_json::json!({"session_name": "team-alpha"}),
129
+ )
130
+ .unwrap();
131
+
132
+ let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
133
+
134
+ assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
135
+ assert_eq!(
136
+ plan.session_name.as_ref().map(SessionName::as_str),
137
+ Some("team-alpha")
138
+ );
139
+ assert!(
140
+ !plan.argv.iter().any(|arg| arg.contains("team-team-alpha")),
141
+ "managed session name must not gain a second team- prefix: {:?}",
142
+ plan.argv
143
+ );
144
+ }
145
+
146
+ #[test]
147
+ #[serial_test::serial(env)]
148
+ fn managed_leader_in_same_tmux_server_switches_client() {
149
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
150
+ let ws = std::env::temp_dir().join(format!("ta_rs_lsp_switch_{}", std::process::id()));
151
+ std::fs::create_dir_all(&ws).unwrap();
152
+ let socket = crate::tmux_backend::socket_name_for_workspace(&ws);
153
+ let endpoint = format!("/private/tmp/tmux-501/{socket},88432,187");
154
+ let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint))]);
155
+
156
+ let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
157
+
158
+ assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
159
+ assert!(
160
+ plan.argv.iter().any(|arg| arg == "switch-client"),
161
+ "same-server managed launch must switch the existing tmux client: {:?}",
162
+ plan.argv
163
+ );
164
+ assert!(
165
+ plan.argv.iter().any(|arg| arg == "team-current:leader"),
166
+ "managed switch target must be the team leader window: {:?}",
167
+ plan.argv
168
+ );
169
+ }
170
+
171
+ #[test]
172
+ #[serial_test::serial(env)]
173
+ fn managed_leader_in_different_tmux_server_refuses_with_n38_hint() {
174
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
175
+ let _e = EnvGuard::apply(&[("TMUX", Some("/private/tmp/tmux-501/default,88432,187"))]);
176
+ let ws = std::env::temp_dir().join(format!("ta_rs_lsp_refuse_{}", std::process::id()));
177
+ std::fs::create_dir_all(&ws).unwrap();
178
+
179
+ let err = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false)
180
+ .unwrap_err()
181
+ .to_string();
182
+
183
+ assert!(err.contains("Error:"), "{err}");
184
+ assert!(err.contains("Reason:"), "{err}");
185
+ assert!(err.contains("Action:"), "{err}");
186
+ assert!(err.contains("--external-leader"), "{err}");
91
187
  }
92
188
 
93
189
  #[test]
@@ -145,6 +145,9 @@ pub enum RereadReason {
145
145
  pub enum LeaderStartMode {
146
146
  /// `os.environ["TMUX"]` 已在 tmux 内 → exec provider in-place。
147
147
  ExecProvider,
148
+ /// Managed launcher topology: provider pane lives in the team session's `leader` window;
149
+ /// the invoking terminal is only a tmux client attached/switched to that pane.
150
+ ManagedTmuxClient,
148
151
  /// 无 tmux session → 新建 tmux session。
149
152
  NewTmuxSession,
150
153
  /// session 已存在 / `--attach-session` → attach。
@@ -331,6 +334,12 @@ pub struct LeaderStartPlan {
331
334
  pub session_name: Option<SessionName>,
332
335
  /// 要 exec 的 argv(provider argv 或 tmux argv)。
333
336
  pub argv: Vec<String>,
337
+ /// For managed launches, the provider command argv spawned inside the leader pane.
338
+ pub provider_argv: Vec<String>,
339
+ /// For managed launches, the window that hosts the leader provider pane.
340
+ pub leader_window: Option<WindowName>,
341
+ /// True for external/current-pane leader compatibility paths.
342
+ pub is_external_leader: bool,
334
343
  /// `new_tmux_session` / `exec_provider` 的 leader env 导出。
335
344
  pub leader_env: BTreeMap<String, String>,
336
345
  pub identity: Option<LeaderIdentity>,
@@ -11,15 +11,15 @@ use super::*;
11
11
  // ── lifecycle::display —— 能力门 / 后端解析 / 开关 / rebind 后重建 ────────────
12
12
 
13
13
  /// `resolve_display_backend(requested, recorded, source)`(`display/backend.py`)。默认
14
- /// none;非默认非静默发 `display.backend_resolved`。
14
+ /// adaptive;显式 none 是逃生口。
15
15
  pub fn resolve_display_backend(
16
16
  requested: Option<DisplayBackend>,
17
17
  recorded: Option<DisplayBackend>,
18
18
  ) -> ResolvedBackend {
19
- let backend = requested.or(recorded).unwrap_or(DisplayBackend::None);
19
+ let backend = requested.or(recorded).unwrap_or(DisplayBackend::Adaptive);
20
20
  ResolvedBackend {
21
21
  backend,
22
- non_default: backend != DisplayBackend::None,
22
+ non_default: backend != DisplayBackend::Adaptive,
23
23
  }
24
24
  }
25
25