@team-agent/installer 0.3.0 → 0.3.2
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 +38 -7
- package/crates/team-agent/src/cli/emit.rs +182 -54
- package/crates/team-agent/src/cli/mod.rs +703 -35
- package/crates/team-agent/src/cli/status_port.rs +170 -44
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/health.rs +130 -0
- package/crates/team-agent/src/leader/lease.rs +23 -2
- package/crates/team-agent/src/leader/rediscover/tests.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +2 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +9 -6
- package/crates/team-agent/src/leader/tests/idle.rs +1 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +157 -0
- package/crates/team-agent/src/leader/types.rs +2 -0
- package/crates/team-agent/src/lifecycle/launch.rs +554 -65
- package/crates/team-agent/src/lifecycle/restart/common.rs +65 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +57 -15
- package/crates/team-agent/src/lifecycle/restart/remove.rs +5 -1
- package/crates/team-agent/src/lifecycle/restart.rs +20 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +52 -0
- package/crates/team-agent/src/lifecycle/types.rs +25 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +28 -0
- package/crates/team-agent/src/mcp_server/wire.rs +81 -1
- package/crates/team-agent/src/messaging/delivery.rs +574 -12
- package/crates/team-agent/src/messaging/leader_receiver.rs +26 -37
- package/crates/team-agent/src/messaging/mod.rs +1 -1
- package/crates/team-agent/src/messaging/results.rs +218 -49
- package/crates/team-agent/src/messaging/send.rs +15 -19
- package/crates/team-agent/src/provider/adapter.rs +95 -10
- package/crates/team-agent/src/provider/helpers.rs +10 -1
- package/crates/team-agent/src/state/identity.rs +3 -0
- package/crates/team-agent/src/state/persist.rs +113 -1
- package/crates/team-agent/src/state/projection.rs +127 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +179 -0
- package/crates/team-agent/src/tmux_backend.rs +124 -12
- package/npm/install.mjs +29 -7
- package/package.json +4 -4
|
@@ -140,6 +140,163 @@ use super::*;
|
|
|
140
140
|
assert!(r.reason.is_none(), "already-bound path carries no acquire reason");
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
#[test]
|
|
144
|
+
#[serial_test::serial(env)]
|
|
145
|
+
fn claim_leader_persists_full_tmux_endpoint_when_tmux_tmpdir_differs_from_coordinator() {
|
|
146
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
147
|
+
let leader_socket = "/tmp/ta-leader-root/tmux-501/dl2f";
|
|
148
|
+
let _e = EnvGuard::apply(&[
|
|
149
|
+
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
150
|
+
("TMUX", Some("/tmp/ta-leader-root/tmux-501/dl2f,12345,0")),
|
|
151
|
+
("TMUX_TMPDIR", Some("/tmp/ta-coordinator-root")),
|
|
152
|
+
]);
|
|
153
|
+
let ws = p2_temp_ws("claim_full_endpoint");
|
|
154
|
+
let team_id = TeamKey::new("current");
|
|
155
|
+
let caller = PaneId::new("%5");
|
|
156
|
+
let mut state = serde_json::json!({"session_name": "team-agent-x"});
|
|
157
|
+
let event_log = crate::event_log::EventLog::new(&ws);
|
|
158
|
+
let live = seeded_liveness(&["%5"]);
|
|
159
|
+
|
|
160
|
+
let r = claim_lease_no_incident(
|
|
161
|
+
&ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
|
|
162
|
+
)
|
|
163
|
+
.unwrap();
|
|
164
|
+
|
|
165
|
+
assert!(r.ok);
|
|
166
|
+
assert_eq!(r.status, LeaseStatus::Claimed);
|
|
167
|
+
assert_eq!(
|
|
168
|
+
r.receiver.as_ref().and_then(|receiver| receiver.tmux_socket.as_deref()),
|
|
169
|
+
Some(leader_socket),
|
|
170
|
+
"claim-leader must persist the full $TMUX socket path, not only the -L short name"
|
|
171
|
+
);
|
|
172
|
+
let persisted: serde_json::Value = serde_json::from_str(
|
|
173
|
+
&std::fs::read_to_string(crate::state::persist::runtime_state_path(&ws)).unwrap(),
|
|
174
|
+
)
|
|
175
|
+
.unwrap();
|
|
176
|
+
assert_eq!(
|
|
177
|
+
persisted["leader_receiver"]["tmux_socket"],
|
|
178
|
+
serde_json::json!(leader_socket),
|
|
179
|
+
"state.json must carry enough endpoint information for later delivery to use tmux -S"
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[test]
|
|
184
|
+
#[serial_test::serial(env)]
|
|
185
|
+
fn claim_leader_already_bound_requires_same_delivery_endpoint_not_only_same_pane_id() {
|
|
186
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
187
|
+
let current_socket = "/tmp/ta-current-leader-root/tmux-501/dl2f";
|
|
188
|
+
let stale_socket = "/tmp/ta-stale-leader-root/tmux-501/dl2f";
|
|
189
|
+
let _e = EnvGuard::apply(&[
|
|
190
|
+
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
191
|
+
("TMUX", Some("/tmp/ta-current-leader-root/tmux-501/dl2f,12345,0")),
|
|
192
|
+
("TMUX_TMPDIR", Some("/tmp/ta-coordinator-root")),
|
|
193
|
+
]);
|
|
194
|
+
let ws = p2_temp_ws("claim_bound_endpoint");
|
|
195
|
+
let team_id = TeamKey::new("current");
|
|
196
|
+
let caller = PaneId::new("%5");
|
|
197
|
+
let mut state = serde_json::json!({
|
|
198
|
+
"session_name": "team-agent-x",
|
|
199
|
+
"team_owner": {
|
|
200
|
+
"pane_id":"%5",
|
|
201
|
+
"provider":"codex",
|
|
202
|
+
"machine_fingerprint":"fp",
|
|
203
|
+
"leader_session_uuid":"U",
|
|
204
|
+
"owner_epoch":1,
|
|
205
|
+
"claimed_at":"t",
|
|
206
|
+
"claimed_via":"claim-leader",
|
|
207
|
+
"tmux_socket": stale_socket
|
|
208
|
+
},
|
|
209
|
+
"leader_receiver": {
|
|
210
|
+
"pane_id":"%5",
|
|
211
|
+
"owner_epoch":1,
|
|
212
|
+
"leader_session_uuid":"U",
|
|
213
|
+
"tmux_socket": stale_socket
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
let event_log = crate::event_log::EventLog::new(&ws);
|
|
217
|
+
let live = seeded_liveness(&["%5"]);
|
|
218
|
+
|
|
219
|
+
let r = claim_lease_no_incident(
|
|
220
|
+
&ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
|
|
221
|
+
)
|
|
222
|
+
.unwrap();
|
|
223
|
+
|
|
224
|
+
assert_ne!(
|
|
225
|
+
r.status,
|
|
226
|
+
LeaseStatus::AlreadyBound,
|
|
227
|
+
"already_bound must verify the same delivery endpoint is reachable; matching bare pane \
|
|
228
|
+
ids are not sufficient because different tmux socket roots can both have %5"
|
|
229
|
+
);
|
|
230
|
+
assert_eq!(
|
|
231
|
+
r.receiver.as_ref().and_then(|receiver| receiver.tmux_socket.as_deref()),
|
|
232
|
+
Some(current_socket),
|
|
233
|
+
"claim should refresh the receiver to the caller's current full tmux endpoint"
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test]
|
|
238
|
+
#[serial_test::serial(env)]
|
|
239
|
+
fn claim_leader_already_bound_normalizes_short_tmux_endpoint_to_full_path() {
|
|
240
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
241
|
+
let current_socket = "/tmp/ta-current-leader-root/tmux-501/dl9aa40c88";
|
|
242
|
+
let short_socket = "dl9aa40c88";
|
|
243
|
+
let _e = EnvGuard::apply(&[
|
|
244
|
+
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
245
|
+
("TMUX", Some("/tmp/ta-current-leader-root/tmux-501/dl9aa40c88,12345,0")),
|
|
246
|
+
("TMUX_TMPDIR", Some("/tmp/ta-coordinator-root")),
|
|
247
|
+
]);
|
|
248
|
+
let ws = p2_temp_ws("claim_bound_short_endpoint");
|
|
249
|
+
let team_id = TeamKey::new("current");
|
|
250
|
+
let caller = PaneId::new("%5");
|
|
251
|
+
let mut state = serde_json::json!({
|
|
252
|
+
"session_name": "team-agent-x",
|
|
253
|
+
"team_owner": {
|
|
254
|
+
"pane_id":"%5",
|
|
255
|
+
"provider":"codex",
|
|
256
|
+
"machine_fingerprint":"fp",
|
|
257
|
+
"leader_session_uuid":"U",
|
|
258
|
+
"owner_epoch":1,
|
|
259
|
+
"claimed_at":"t",
|
|
260
|
+
"claimed_via":"claim-leader",
|
|
261
|
+
"tmux_socket": short_socket
|
|
262
|
+
},
|
|
263
|
+
"leader_receiver": {
|
|
264
|
+
"pane_id":"%5",
|
|
265
|
+
"owner_epoch":1,
|
|
266
|
+
"leader_session_uuid":"U",
|
|
267
|
+
"tmux_socket": short_socket
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
let event_log = crate::event_log::EventLog::new(&ws);
|
|
271
|
+
let live = seeded_liveness(&["%5"]);
|
|
272
|
+
|
|
273
|
+
let r = claim_lease_no_incident(
|
|
274
|
+
&ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
|
|
275
|
+
)
|
|
276
|
+
.unwrap();
|
|
277
|
+
|
|
278
|
+
assert_ne!(
|
|
279
|
+
r.status,
|
|
280
|
+
LeaseStatus::AlreadyBound,
|
|
281
|
+
"claim already_bound must not treat a stored short socket name as equivalent to the \
|
|
282
|
+
caller's full $TMUX endpoint by basename; it must refresh/normalize the receiver"
|
|
283
|
+
);
|
|
284
|
+
assert_eq!(
|
|
285
|
+
r.receiver.as_ref().and_then(|receiver| receiver.tmux_socket.as_deref()),
|
|
286
|
+
Some(current_socket),
|
|
287
|
+
"claim should rewrite any legacy short leader_receiver.tmux_socket to the full physical endpoint"
|
|
288
|
+
);
|
|
289
|
+
let persisted: serde_json::Value = serde_json::from_str(
|
|
290
|
+
&std::fs::read_to_string(crate::state::persist::runtime_state_path(&ws)).unwrap(),
|
|
291
|
+
)
|
|
292
|
+
.unwrap();
|
|
293
|
+
assert_eq!(
|
|
294
|
+
persisted["leader_receiver"]["tmux_socket"],
|
|
295
|
+
serde_json::json!(current_socket),
|
|
296
|
+
"state must not preserve a short endpoint after explicit claim; state={persisted}"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
143
300
|
// RED — NOT-IN-TMUX-PANE: empty caller pane → refused not_in_tmux_pane with
|
|
144
301
|
// the EXACT golden action string (differs from the current claim_leader stub
|
|
145
302
|
// string). golden /tmp/probe_claim.py _lease_refused("not_in_tmux_pane",...).
|
|
@@ -238,6 +238,8 @@ pub struct LeaderReceiver {
|
|
|
238
238
|
pub pane_index: Option<String>,
|
|
239
239
|
pub pane_tty: Option<String>,
|
|
240
240
|
pub pane_current_command: Option<String>,
|
|
241
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
242
|
+
pub tmux_socket: Option<String>,
|
|
241
243
|
/// `_target_fingerprint(pane_info)`。
|
|
242
244
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
243
245
|
pub fingerprint: Option<String>,
|