@team-agent/installer 0.4.11 → 0.5.1

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 (80) 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/diagnose.rs +2 -15
  5. package/crates/team-agent/src/cli/emit.rs +98 -5
  6. package/crates/team-agent/src/cli/mod.rs +2 -0
  7. package/crates/team-agent/src/cli/named_address.rs +864 -0
  8. package/crates/team-agent/src/cli/send.rs +104 -0
  9. package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
  10. package/crates/team-agent/src/cli/tests/mod.rs +1 -0
  11. package/crates/team-agent/src/cli/tests/named_address.rs +455 -0
  12. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  13. package/crates/team-agent/src/cli/types.rs +4 -0
  14. package/crates/team-agent/src/compiler.rs +11 -20
  15. package/crates/team-agent/src/coordinator/orphan.rs +2 -2
  16. package/crates/team-agent/src/coordinator/runtime_detectors.rs +5 -4
  17. package/crates/team-agent/src/coordinator/steps/abnormal.rs +1776 -2
  18. package/crates/team-agent/src/coordinator/steps/mod.rs +19 -0
  19. package/crates/team-agent/src/coordinator/tests/a0_lostupdate.rs +78 -7
  20. package/crates/team-agent/src/coordinator/tests/abnormal.rs +195 -0
  21. package/crates/team-agent/src/coordinator/tick.rs +31 -932
  22. package/crates/team-agent/src/diagnose/orphans.rs +66 -8
  23. package/crates/team-agent/src/layout/worker_window_helpers.rs +5 -7
  24. package/crates/team-agent/src/leader/provider_attribution.rs +7 -5
  25. package/crates/team-agent/src/leader/rediscover.rs +1 -11
  26. package/crates/team-agent/src/lifecycle/launch/plan.rs +19 -8
  27. package/crates/team-agent/src/lifecycle/launch.rs +135 -58
  28. package/crates/team-agent/src/lifecycle/lock.rs +302 -0
  29. package/crates/team-agent/src/lifecycle/mod.rs +1 -0
  30. package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -11
  31. package/crates/team-agent/src/lifecycle/restart/agent.rs +169 -113
  32. package/crates/team-agent/src/lifecycle/restart/common.rs +108 -17
  33. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -32
  34. package/crates/team-agent/src/lifecycle/restart/remove.rs +16 -2
  35. package/crates/team-agent/src/lifecycle/restart/selection.rs +2 -1
  36. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +6 -0
  37. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +3 -3
  38. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +321 -0
  39. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +35 -5
  40. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +802 -0
  41. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +675 -0
  42. package/crates/team-agent/src/lifecycle/tests.rs +3 -0
  43. package/crates/team-agent/src/lifecycle/types.rs +8 -0
  44. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +24 -9
  45. package/crates/team-agent/src/mcp_server/tools.rs +157 -68
  46. package/crates/team-agent/src/messaging/activity.rs +43 -18
  47. package/crates/team-agent/src/messaging/delivery.rs +161 -97
  48. package/crates/team-agent/src/messaging/helpers.rs +1 -0
  49. package/crates/team-agent/src/messaging/leader_receiver.rs +22 -1
  50. package/crates/team-agent/src/messaging/results.rs +34 -9
  51. package/crates/team-agent/src/messaging/scheduler.rs +52 -9
  52. package/crates/team-agent/src/model/spec.rs +10 -6
  53. package/crates/team-agent/src/provider/adapter.rs +10 -1038
  54. package/crates/team-agent/src/provider/adapters/claude.rs +3 -8
  55. package/crates/team-agent/src/provider/adapters/copilot.rs +2 -5
  56. package/crates/team-agent/src/provider/classify.rs +23 -48
  57. package/crates/team-agent/src/provider/command.rs +16 -7
  58. package/crates/team-agent/src/provider/faults.rs +93 -53
  59. package/crates/team-agent/src/provider/session/capture.rs +4 -15
  60. package/crates/team-agent/src/provider/session_scan/claude.rs +309 -0
  61. package/crates/team-agent/src/provider/session_scan/codex.rs +40 -0
  62. package/crates/team-agent/src/provider/session_scan/common.rs +350 -0
  63. package/crates/team-agent/src/provider/session_scan/copilot.rs +202 -0
  64. package/crates/team-agent/src/provider/session_scan.rs +65 -27
  65. package/crates/team-agent/src/provider/tests/faults.rs +80 -0
  66. package/crates/team-agent/src/provider/types.rs +33 -1
  67. package/crates/team-agent/src/provider/wire.rs +171 -1
  68. package/crates/team-agent/src/state/identity.rs +242 -57
  69. package/crates/team-agent/src/state/identity_keys.rs +9 -12
  70. package/crates/team-agent/src/state/mod.rs +5 -1
  71. package/crates/team-agent/src/state/owner_gate.rs +59 -15
  72. package/crates/team-agent/src/state/ownership.rs +12 -11
  73. package/crates/team-agent/src/state/paths.rs +13 -6
  74. package/crates/team-agent/src/state/persist.rs +671 -128
  75. package/crates/team-agent/src/state/projection.rs +240 -49
  76. package/crates/team-agent/src/state/selector.rs +11 -5
  77. package/crates/team-agent/src/tmux_backend/tests.rs +51 -2
  78. package/crates/team-agent/src/tmux_backend.rs +42 -5
  79. package/crates/team-agent/src/transport/test_support.rs +55 -6
  80. package/package.json +4 -4
@@ -59,7 +59,10 @@ pub fn check_team_owner(
59
59
  if !owner.is_object() || owner.as_object().is_none_or(serde_json::Map::is_empty) {
60
60
  return None;
61
61
  }
62
- let owner_uuid = owner.get("leader_session_uuid").and_then(Value::as_str).unwrap_or("");
62
+ let owner_uuid = owner
63
+ .get("leader_session_uuid")
64
+ .and_then(Value::as_str)
65
+ .unwrap_or("");
63
66
  let owner_pane = owner.get("pane_id").and_then(Value::as_str).unwrap_or("");
64
67
  let caller_uuid = caller.leader_session_uuid.as_str();
65
68
  let caller_pane = caller.pane_id.as_str();
@@ -178,7 +181,9 @@ fn lexical_abs(p: &Path) -> std::path::PathBuf {
178
181
  if p.is_absolute() {
179
182
  p.to_path_buf()
180
183
  } else {
181
- std::env::current_dir().map(|cwd| cwd.join(p)).unwrap_or_else(|_| p.to_path_buf())
184
+ std::env::current_dir()
185
+ .map(|cwd| cwd.join(p))
186
+ .unwrap_or_else(|_| p.to_path_buf())
182
187
  }
183
188
  }
184
189
 
@@ -253,7 +258,9 @@ mod tests {
253
258
  #[test]
254
259
  fn no_owner_allows() {
255
260
  assert!(check_team_owner(&json!({}), &caller("%c", "u"), true, &LIVE).is_none());
256
- assert!(check_team_owner(&json!({"team_owner": {}}), &caller("%c", "u"), true, &LIVE).is_none());
261
+ assert!(
262
+ check_team_owner(&json!({"team_owner": {}}), &caller("%c", "u"), true, &LIVE).is_none()
263
+ );
257
264
  }
258
265
 
259
266
  #[test]
@@ -298,15 +305,30 @@ mod tests {
298
305
  fn worker_bypass_rules() {
299
306
  let s = json!({"leader": {"id": "leader"}, "agents": {"worker_a": {}, "worker_b": {}}});
300
307
  // worker_a + env worker_a → 绕过。
301
- assert_eq!(worker_sender_bypasses_owner_gate(&s, Some("worker_a"), Some("worker_a")), Some("worker_a".to_string()));
308
+ assert_eq!(
309
+ worker_sender_bypasses_owner_gate(&s, Some("worker_a"), Some("worker_a")),
310
+ Some("worker_a".to_string())
311
+ );
302
312
  // leader → 不绕过(走 owner 门)。
303
- assert_eq!(worker_sender_bypasses_owner_gate(&s, Some("leader"), Some("leader")), None);
313
+ assert_eq!(
314
+ worker_sender_bypasses_owner_gate(&s, Some("leader"), Some("leader")),
315
+ None
316
+ );
304
317
  // 未注册 agent → 不绕过。
305
- assert_eq!(worker_sender_bypasses_owner_gate(&s, Some("ghost"), Some("ghost")), None);
318
+ assert_eq!(
319
+ worker_sender_bypasses_owner_gate(&s, Some("ghost"), Some("ghost")),
320
+ None
321
+ );
306
322
  // sender/env 不一致(伪造)→ 不绕过。
307
- assert_eq!(worker_sender_bypasses_owner_gate(&s, Some("worker_b"), Some("worker_a")), None);
323
+ assert_eq!(
324
+ worker_sender_bypasses_owner_gate(&s, Some("worker_b"), Some("worker_a")),
325
+ None
326
+ );
308
327
  // 无 env_agent_id + 已注册 → 绕过(返回 sender)。
309
- assert_eq!(worker_sender_bypasses_owner_gate(&s, Some("worker_a"), None), Some("worker_a".to_string()));
328
+ assert_eq!(
329
+ worker_sender_bypasses_owner_gate(&s, Some("worker_a"), None),
330
+ Some("worker_a".to_string())
331
+ );
310
332
  }
311
333
 
312
334
  // §11 血泪:/tmp → /private/tmp 软链不对称(macOS 真机 bug)。两边 canonicalize → match。
@@ -319,7 +341,10 @@ mod tests {
319
341
  let raw = Path::new("/tmp").join(dir.file_name().unwrap());
320
342
  let private = Path::new("/private/tmp").join(dir.file_name().unwrap());
321
343
  if raw.exists() && private.exists() {
322
- assert!(workspace_paths_match(&raw, &private), "/tmp 与 /private/tmp 应 canonicalize 相等");
344
+ assert!(
345
+ workspace_paths_match(&raw, &private),
346
+ "/tmp 与 /private/tmp 应 canonicalize 相等"
347
+ );
323
348
  }
324
349
  // 自反:同路径 match。
325
350
  assert!(workspace_paths_match(&dir, &dir));
@@ -333,7 +358,10 @@ mod tests {
333
358
  let repo_backup = base.join("repo-backup");
334
359
  std::fs::create_dir_all(&repo).unwrap();
335
360
  std::fs::create_dir_all(&repo_backup).unwrap();
336
- assert!(!workspace_paths_match(&repo, &repo_backup), "共享前缀的兄弟目录不得 match");
361
+ assert!(
362
+ !workspace_paths_match(&repo, &repo_backup),
363
+ "共享前缀的兄弟目录不得 match"
364
+ );
337
365
  // 子目录也不 match(禁子串/反推)。
338
366
  let sub = repo.join("sub");
339
367
  std::fs::create_dir_all(&sub).unwrap();
@@ -359,15 +387,25 @@ mod tests {
359
387
  c.leader_session_uuid_source = "derived".to_string();
360
388
  let r = check_team_owner(&s, &c, true, &LIVE);
361
389
  assert!(r.is_some(), "两边空 uuid 不得 allow;必落拒绝");
362
- assert_eq!(r.unwrap()["reason_kind"], json!("owner_takeover_required"), "非 sticky_bind(空 uuid 不算同)");
390
+ assert_eq!(
391
+ r.unwrap()["reason_kind"],
392
+ json!("owner_takeover_required"),
393
+ "非 sticky_bind(空 uuid 不算同)"
394
+ );
363
395
  }
364
396
 
365
397
  // 对抗 P2:owner pane liveness == Unknown 时,非 worker caller 也接管;worker 不接管。
366
398
  #[test]
367
399
  fn unknown_owner_pane_allows_nonworker_takeover() {
368
400
  let s = state_owned("%owner", "uuid-owner");
369
- assert!(check_team_owner(&s, &caller("%new", "uuid-new"), false, &UNKNOWN).is_none(), "Unknown owner pane + 非 worker → 接管");
370
- assert!(check_team_owner(&s, &caller("%new", "uuid-new"), true, &UNKNOWN).is_some(), "worker 即便 Unknown 也不接管");
401
+ assert!(
402
+ check_team_owner(&s, &caller("%new", "uuid-new"), false, &UNKNOWN).is_none(),
403
+ "Unknown owner pane + 非 worker → 接管"
404
+ );
405
+ assert!(
406
+ check_team_owner(&s, &caller("%new", "uuid-new"), true, &UNKNOWN).is_some(),
407
+ "worker 即便 Unknown 也不接管"
408
+ );
371
409
  }
372
410
 
373
411
  // 空 caller_pane + 异 uuid + 死 owner:死-owner 接管分支需 caller_pane 非空,故落拒绝。
@@ -375,7 +413,10 @@ mod tests {
375
413
  fn empty_caller_pane_diff_uuid_refuses_even_dead_owner() {
376
414
  let s = state_owned("%owner", "uuid-owner");
377
415
  let r = check_team_owner(&s, &caller("", "uuid-diff"), false, &DEAD);
378
- assert!(r.is_some(), "空 caller_pane 不触发死-owner 接管;异 uuid → 拒绝");
416
+ assert!(
417
+ r.is_some(),
418
+ "空 caller_pane 不触发死-owner 接管;异 uuid → 拒绝"
419
+ );
379
420
  }
380
421
 
381
422
  // 对抗 P0-B(§11 血泪):父目录是软链 + 末段不存在 → realpath_like 解析父软链 → 判 own。
@@ -396,7 +437,10 @@ mod tests {
396
437
  "父软链 + 缺失末段应判 own(realpath_like 解析父软链)"
397
438
  );
398
439
  // 不同末段仍不 match。
399
- assert!(!workspace_paths_match(&link.join("ghostA"), &real.join("ghostB")));
440
+ assert!(!workspace_paths_match(
441
+ &link.join("ghostA"),
442
+ &real.join("ghostB")
443
+ ));
400
444
  }
401
445
  }
402
446
 
@@ -164,13 +164,11 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
164
164
  }
165
165
  // Canonical teams.<key> write. Stage 5 will move this to a per-team
166
166
  // state file; the call sites do not change.
167
- let teams = state
168
- .as_object_mut()
169
- .and_then(|root| {
170
- root.entry("teams")
171
- .or_insert_with(|| serde_json::json!({}))
172
- .as_object_mut()
173
- });
167
+ let teams = state.as_object_mut().and_then(|root| {
168
+ root.entry("teams")
169
+ .or_insert_with(|| serde_json::json!({}))
170
+ .as_object_mut()
171
+ });
174
172
  let Some(teams) = teams else { return };
175
173
  let entry = teams
176
174
  .entry(team_key.to_string())
@@ -402,7 +400,10 @@ mod tests {
402
400
  assert!(state.get("owner_epoch").is_none());
403
401
  assert!(state.get("leader_receiver").is_none());
404
402
  // Canonical teams.<key>: written.
405
- assert_eq!(state["teams"]["alpha"]["team_owner"]["pane_id"], json!("%new"));
403
+ assert_eq!(
404
+ state["teams"]["alpha"]["team_owner"]["pane_id"],
405
+ json!("%new")
406
+ );
406
407
  assert_eq!(state["teams"]["alpha"]["owner_epoch"], json!(7));
407
408
  assert_eq!(
408
409
  state["teams"]["alpha"]["leader_receiver"]["pane_id"],
@@ -442,9 +443,9 @@ mod tests {
442
443
  write_owner(&mut state, "", record);
443
444
  assert_eq!(state["team_owner"]["pane_id"], json!("%top"));
444
445
  assert!(
445
- state.get("teams").is_none_or(|teams| teams
446
- .as_object()
447
- .is_some_and(|map| map.is_empty())),
446
+ state
447
+ .get("teams")
448
+ .is_none_or(|teams| teams.as_object().is_some_and(|map| map.is_empty())),
448
449
  "empty team_key must NOT touch teams.<key>"
449
450
  );
450
451
  }
@@ -96,7 +96,10 @@ impl TeamRuntimePaths {
96
96
  }
97
97
 
98
98
  pub fn for_scope(scope: &TeamScope) -> Self {
99
- Self::new(scope.workspace().to_path_buf(), scope.team_key().to_string())
99
+ Self::new(
100
+ scope.workspace().to_path_buf(),
101
+ scope.team_key().to_string(),
102
+ )
100
103
  }
101
104
 
102
105
  pub fn workspace(&self) -> &Path {
@@ -188,9 +191,7 @@ impl CommandScope {
188
191
  let alive = team_state_candidates(&state);
189
192
  match alive.len() {
190
193
  0 => Self::EmptyWorkspace,
191
- 1 => Self::Resolved(
192
- alive.keys().next().cloned().unwrap_or_default(),
193
- ),
194
+ 1 => Self::Resolved(alive.keys().next().cloned().unwrap_or_default()),
194
195
  _ => {
195
196
  let mut keys: Vec<String> = alive.keys().cloned().collect();
196
197
  keys.sort();
@@ -241,7 +242,10 @@ mod tests {
241
242
  #[test]
242
243
  fn team_runtime_paths_layout_matches_existing_runtime_dir_layout() {
243
244
  let paths = TeamRuntimePaths::new(PathBuf::from("/ws/proj"), "alpha");
244
- assert_eq!(paths.team_dir(), PathBuf::from("/ws/proj/.team/runtime/alpha"));
245
+ assert_eq!(
246
+ paths.team_dir(),
247
+ PathBuf::from("/ws/proj/.team/runtime/alpha")
248
+ );
245
249
  assert_eq!(
246
250
  paths.spec_path(),
247
251
  PathBuf::from("/ws/proj/.team/runtime/alpha/team.spec.yaml")
@@ -338,7 +342,10 @@ mod tests {
338
342
  .unwrap();
339
343
  let scope = CommandScope::resolve(&ws, None);
340
344
  assert!(scope.is_ambiguous());
341
- assert_eq!(scope.candidates(), &["alpha".to_string(), "beta".to_string()]);
345
+ assert_eq!(
346
+ scope.candidates(),
347
+ &["alpha".to_string(), "beta".to_string()]
348
+ );
342
349
  }
343
350
 
344
351
  #[test]