@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +92 -11
- package/crates/team-agent/src/cli/leader.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +255 -37
- package/crates/team-agent/src/cli/send.rs +252 -0
- package/crates/team-agent/src/cli/status.rs +5 -1
- package/crates/team-agent/src/cli/status_port.rs +24 -0
- package/crates/team-agent/src/cli/tests/base.rs +21 -3
- package/crates/team-agent/src/cli/tests/divergence.rs +6 -6
- package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +285 -2
- package/crates/team-agent/src/cli/tests/status_send.rs +25 -0
- package/crates/team-agent/src/cli/types.rs +32 -1
- package/crates/team-agent/src/compiler/tests.rs +2 -2
- package/crates/team-agent/src/compiler.rs +1 -1
- package/crates/team-agent/src/fake_worker.rs +32 -145
- package/crates/team-agent/src/leader/start.rs +272 -8
- package/crates/team-agent/src/leader/tests/basics.rs +1 -0
- package/crates/team-agent/src/leader/tests/identity.rs +121 -25
- package/crates/team-agent/src/leader/types.rs +9 -0
- package/crates/team-agent/src/lifecycle/display.rs +3 -3
- package/crates/team-agent/src/lifecycle/launch.rs +631 -70
- package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
- package/crates/team-agent/src/lifecycle/restart/common.rs +38 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +79 -4
- package/crates/team-agent/src/lifecycle/tests/core.rs +14 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +731 -8
- package/crates/team-agent/src/lifecycle/types.rs +5 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +153 -3
- package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
- package/crates/team-agent/src/messaging/mod.rs +6 -3
- package/crates/team-agent/src/messaging/results.rs +240 -158
- package/crates/team-agent/src/messaging/send.rs +3 -2
- package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
- package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
- package/crates/team-agent/src/os_probe.rs +11 -0
- package/crates/team-agent/src/state/persist.rs +6 -0
- package/crates/team-agent/src/tmux_backend.rs +85 -0
- package/crates/team-agent/src/transport/test_support.rs +46 -1
- package/crates/team-agent/src/transport.rs +27 -0
- package/package.json +4 -4
- 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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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::
|
|
19
|
+
let backend = requested.or(recorded).unwrap_or(DisplayBackend::Adaptive);
|
|
20
20
|
ResolvedBackend {
|
|
21
21
|
backend,
|
|
22
|
-
non_default: backend != DisplayBackend::
|
|
22
|
+
non_default: backend != DisplayBackend::Adaptive,
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|