@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.
Files changed (39) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +38 -7
  4. package/crates/team-agent/src/cli/emit.rs +182 -54
  5. package/crates/team-agent/src/cli/mod.rs +703 -35
  6. package/crates/team-agent/src/cli/status_port.rs +170 -44
  7. package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
  8. package/crates/team-agent/src/cli/types.rs +1 -0
  9. package/crates/team-agent/src/coordinator/health.rs +130 -0
  10. package/crates/team-agent/src/leader/lease.rs +23 -2
  11. package/crates/team-agent/src/leader/rediscover/tests.rs +1 -0
  12. package/crates/team-agent/src/leader/rediscover.rs +2 -0
  13. package/crates/team-agent/src/leader/tests/byte_findings.rs +9 -6
  14. package/crates/team-agent/src/leader/tests/idle.rs +1 -0
  15. package/crates/team-agent/src/leader/tests/lease_claim.rs +157 -0
  16. package/crates/team-agent/src/leader/types.rs +2 -0
  17. package/crates/team-agent/src/lifecycle/launch.rs +554 -65
  18. package/crates/team-agent/src/lifecycle/restart/common.rs +65 -0
  19. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +57 -15
  20. package/crates/team-agent/src/lifecycle/restart/remove.rs +5 -1
  21. package/crates/team-agent/src/lifecycle/restart.rs +20 -0
  22. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +52 -0
  23. package/crates/team-agent/src/lifecycle/types.rs +25 -0
  24. package/crates/team-agent/src/mcp_server/tests/wire.rs +28 -0
  25. package/crates/team-agent/src/mcp_server/wire.rs +81 -1
  26. package/crates/team-agent/src/messaging/delivery.rs +574 -12
  27. package/crates/team-agent/src/messaging/leader_receiver.rs +26 -37
  28. package/crates/team-agent/src/messaging/mod.rs +1 -1
  29. package/crates/team-agent/src/messaging/results.rs +218 -49
  30. package/crates/team-agent/src/messaging/send.rs +15 -19
  31. package/crates/team-agent/src/provider/adapter.rs +95 -10
  32. package/crates/team-agent/src/provider/helpers.rs +10 -1
  33. package/crates/team-agent/src/state/identity.rs +3 -0
  34. package/crates/team-agent/src/state/persist.rs +113 -1
  35. package/crates/team-agent/src/state/projection.rs +127 -3
  36. package/crates/team-agent/src/tmux_backend/tests.rs +179 -0
  37. package/crates/team-agent/src/tmux_backend.rs +124 -12
  38. package/npm/install.mjs +29 -7
  39. 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>,