@team-agent/installer 0.5.27 → 0.5.29
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/diagnose.rs +1 -1
- package/crates/team-agent/src/cli/emit.rs +211 -59
- package/crates/team-agent/src/cli/mod.rs +45 -19
- package/crates/team-agent/src/cli/send.rs +19 -20
- package/crates/team-agent/src/cli/spec.rs +222 -0
- package/crates/team-agent/src/cli/status.rs +29 -0
- package/crates/team-agent/src/cli/status_port.rs +36 -4
- package/crates/team-agent/src/leader/lease.rs +127 -27
- package/crates/team-agent/src/lifecycle/launch.rs +6 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +41 -1
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +85 -0
- package/crates/team-agent/src/state/mod.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +387 -0
- package/crates/team-agent/src/topology.rs +130 -5
- package/package.json +4 -4
|
@@ -38,10 +38,12 @@ pub enum EndpointConvergenceDecision {
|
|
|
38
38
|
Converge {
|
|
39
39
|
old_endpoint: String,
|
|
40
40
|
new_endpoint: String,
|
|
41
|
+
reason: &'static str,
|
|
41
42
|
},
|
|
42
43
|
RefuseLiveOldEndpoint {
|
|
43
44
|
old_endpoint: String,
|
|
44
45
|
new_endpoint: String,
|
|
46
|
+
reason: &'static str,
|
|
45
47
|
},
|
|
46
48
|
Unknown,
|
|
47
49
|
}
|
|
@@ -96,13 +98,23 @@ pub fn endpoint_convergence_decision(
|
|
|
96
98
|
return EndpointConvergenceDecision::NoConflict;
|
|
97
99
|
};
|
|
98
100
|
|
|
101
|
+
let mut converge_reason = "old_endpoint_dead";
|
|
99
102
|
for old_endpoint in stale_endpoints {
|
|
100
103
|
match endpoint_server_alive(&old_endpoint) {
|
|
101
104
|
Some(true) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
match old_endpoint_team_liveness(state, team_key, &old_endpoint) {
|
|
106
|
+
OldEndpointTeamLiveness::TeamLive { reason } => {
|
|
107
|
+
return EndpointConvergenceDecision::RefuseLiveOldEndpoint {
|
|
108
|
+
old_endpoint,
|
|
109
|
+
new_endpoint: candidate_endpoint.to_string(),
|
|
110
|
+
reason,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
OldEndpointTeamLiveness::TeamAbsent { reason } => {
|
|
114
|
+
converge_reason = reason;
|
|
115
|
+
}
|
|
116
|
+
OldEndpointTeamLiveness::Unknown => return EndpointConvergenceDecision::Unknown,
|
|
117
|
+
}
|
|
106
118
|
}
|
|
107
119
|
Some(false) => {}
|
|
108
120
|
None => return EndpointConvergenceDecision::Unknown,
|
|
@@ -112,9 +124,118 @@ pub fn endpoint_convergence_decision(
|
|
|
112
124
|
EndpointConvergenceDecision::Converge {
|
|
113
125
|
old_endpoint: first_old,
|
|
114
126
|
new_endpoint: candidate_endpoint.to_string(),
|
|
127
|
+
reason: converge_reason,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
enum OldEndpointTeamLiveness {
|
|
132
|
+
TeamLive { reason: &'static str },
|
|
133
|
+
TeamAbsent { reason: &'static str },
|
|
134
|
+
Unknown,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn old_endpoint_team_liveness(
|
|
138
|
+
state: &Value,
|
|
139
|
+
team_key: &str,
|
|
140
|
+
old_endpoint: &str,
|
|
141
|
+
) -> OldEndpointTeamLiveness {
|
|
142
|
+
let team_state = state
|
|
143
|
+
.get("teams")
|
|
144
|
+
.and_then(Value::as_object)
|
|
145
|
+
.and_then(|teams| teams.get(team_key))
|
|
146
|
+
.unwrap_or(state);
|
|
147
|
+
let session = non_empty_str(team_state, "session_name")
|
|
148
|
+
.or_else(|| non_empty_str(state, "session_name"))
|
|
149
|
+
.unwrap_or_default();
|
|
150
|
+
if !session.is_empty() {
|
|
151
|
+
match session_exists_on_endpoint_checked(old_endpoint, session) {
|
|
152
|
+
Some(true) => {
|
|
153
|
+
return OldEndpointTeamLiveness::TeamLive {
|
|
154
|
+
reason: "old_team_session_live",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
Some(false) => {}
|
|
158
|
+
None => return OldEndpointTeamLiveness::Unknown,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
match old_endpoint_has_live_team_tuple(state, team_key, old_endpoint) {
|
|
162
|
+
Some(true) => OldEndpointTeamLiveness::TeamLive {
|
|
163
|
+
reason: "old_team_tuple_live",
|
|
164
|
+
},
|
|
165
|
+
Some(false) => OldEndpointTeamLiveness::TeamAbsent {
|
|
166
|
+
reason: "old_team_session_absent_on_live_endpoint",
|
|
167
|
+
},
|
|
168
|
+
None => OldEndpointTeamLiveness::Unknown,
|
|
115
169
|
}
|
|
116
170
|
}
|
|
117
171
|
|
|
172
|
+
fn old_endpoint_has_live_team_tuple(
|
|
173
|
+
state: &Value,
|
|
174
|
+
team_key: &str,
|
|
175
|
+
old_endpoint: &str,
|
|
176
|
+
) -> Option<bool> {
|
|
177
|
+
let backend = crate::tmux_backend::TmuxBackend::for_tmux_endpoint(old_endpoint);
|
|
178
|
+
let targets = backend.list_targets().ok()?;
|
|
179
|
+
let team_state = state
|
|
180
|
+
.get("teams")
|
|
181
|
+
.and_then(Value::as_object)
|
|
182
|
+
.and_then(|teams| teams.get(team_key));
|
|
183
|
+
for observed in &targets {
|
|
184
|
+
if matches!(
|
|
185
|
+
classify_registered_worker_for_observed_pane(team_state.unwrap_or(state), observed),
|
|
186
|
+
WorkerPaneBindingMatch::LiveSameWorker { .. }
|
|
187
|
+
) || leader_receiver_tuple_matches(team_state.unwrap_or(state), observed)
|
|
188
|
+
{
|
|
189
|
+
return Some(true);
|
|
190
|
+
}
|
|
191
|
+
if team_state.is_some() {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if let Some(teams) = state.get("teams").and_then(Value::as_object) {
|
|
195
|
+
if teams.values().any(|entry| {
|
|
196
|
+
matches!(
|
|
197
|
+
classify_registered_worker_for_observed_pane(entry, observed),
|
|
198
|
+
WorkerPaneBindingMatch::LiveSameWorker { .. }
|
|
199
|
+
) || leader_receiver_tuple_matches(entry, observed)
|
|
200
|
+
}) {
|
|
201
|
+
return Some(true);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
Some(false)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fn leader_receiver_tuple_matches(state: &Value, observed: &PaneInfo) -> bool {
|
|
209
|
+
let Some(receiver) = state.get("leader_receiver") else {
|
|
210
|
+
return false;
|
|
211
|
+
};
|
|
212
|
+
let Some(cached_pane_id) = non_empty_str(receiver, "pane_id") else {
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
if cached_pane_id != observed.pane_id.as_str() {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
let expected_session = non_empty_str(receiver, "session_name")
|
|
219
|
+
.or_else(|| non_empty_str(state, "session_name"))
|
|
220
|
+
.unwrap_or_default();
|
|
221
|
+
if !expected_session.is_empty() && observed.session.as_str() != expected_session {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
if let Some(expected_window) = non_empty_str(receiver, "window_name") {
|
|
225
|
+
if observed.window_name.as_ref().map(|w| w.as_str()) != Some(expected_window) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if let (Some(expected_pid), Some(observed_pid)) =
|
|
230
|
+
(agent_pane_pid(receiver), observed.pane_pid)
|
|
231
|
+
{
|
|
232
|
+
if expected_pid != observed_pid {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
true
|
|
237
|
+
}
|
|
238
|
+
|
|
118
239
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
119
240
|
pub enum WorkerPaneBindingMatch {
|
|
120
241
|
LiveSameWorker { agent_id: String },
|
|
@@ -329,9 +450,13 @@ fn agent_pane_pid(agent: &Value) -> Option<u32> {
|
|
|
329
450
|
}
|
|
330
451
|
|
|
331
452
|
fn session_exists_on_endpoint(endpoint: &str, session: &str) -> bool {
|
|
453
|
+
session_exists_on_endpoint_checked(endpoint, session).unwrap_or(false)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fn session_exists_on_endpoint_checked(endpoint: &str, session: &str) -> Option<bool> {
|
|
332
457
|
crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint)
|
|
333
458
|
.has_session(&SessionName::new(session.to_string()))
|
|
334
|
-
.
|
|
459
|
+
.ok()
|
|
335
460
|
}
|
|
336
461
|
|
|
337
462
|
fn collect_stale_endpoint(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.29",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.29",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.29"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|