@team-agent/installer 0.5.77 → 0.5.80

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 (95) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +161 -29
  4. package/crates/team-agent/src/cli/diagnose.rs +333 -68
  5. package/crates/team-agent/src/cli/emit.rs +24 -8
  6. package/crates/team-agent/src/cli/mod.rs +543 -98
  7. package/crates/team-agent/src/cli/models.rs +393 -32
  8. package/crates/team-agent/src/cli/named_address.rs +19 -5
  9. package/crates/team-agent/src/cli/profile.rs +35 -4
  10. package/crates/team-agent/src/cli/spec.rs +7 -2
  11. package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
  12. package/crates/team-agent/src/cli/status_port/runtime.rs +3 -1
  13. package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
  14. package/crates/team-agent/src/cli/status_port/store.rs +1 -1
  15. package/crates/team-agent/src/cli/status_port/tests.rs +37 -1
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
  17. package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
  18. package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
  19. package/crates/team-agent/src/cli/types.rs +1 -0
  20. package/crates/team-agent/src/coordinator/steps/abnormal.rs +122 -2
  21. package/crates/team-agent/src/leader/lease.rs +492 -33
  22. package/crates/team-agent/src/leader/owner_bind.rs +40 -12
  23. package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
  24. package/crates/team-agent/src/leader/rediscover.rs +36 -6
  25. package/crates/team-agent/src/leader/start.rs +197 -88
  26. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
  27. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
  28. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
  29. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
  30. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
  31. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +86 -23
  32. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
  33. package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
  34. package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
  35. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
  36. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
  37. package/crates/team-agent/src/lifecycle/launch.rs +30 -7
  38. package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
  39. package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
  40. package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
  41. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +996 -66
  42. package/crates/team-agent/src/lifecycle/restart.rs +2 -2
  43. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
  44. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
  45. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
  46. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
  47. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -4
  48. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
  49. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
  50. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
  51. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
  52. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
  53. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
  54. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_spaced_ids.stdout.txt +9 -0
  55. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
  56. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
  57. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
  58. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
  59. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
  60. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -2
  61. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
  62. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
  63. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
  64. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +54 -9
  65. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +71 -0
  66. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
  67. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
  68. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
  69. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
  70. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
  71. package/crates/team-agent/src/lifecycle/types.rs +8 -0
  72. package/crates/team-agent/src/mcp_server/wire.rs +140 -11
  73. package/crates/team-agent/src/messaging/delivery.rs +221 -80
  74. package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
  75. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +127 -0
  76. package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
  77. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
  78. package/crates/team-agent/src/provider/session_scan/common/tests.rs +204 -0
  79. package/crates/team-agent/src/provider/session_scan/common.rs +24 -2
  80. package/crates/team-agent/src/state/abnormal_watch/tests/mod.rs +248 -0
  81. package/crates/team-agent/src/state/abnormal_watch.rs +144 -0
  82. package/crates/team-agent/src/state/mod.rs +1 -0
  83. package/crates/team-agent/src/state/persist.rs +102 -4
  84. package/crates/team-agent/src/state/projection.rs +7 -1
  85. package/crates/team-agent/src/state/repository.rs +29 -0
  86. package/crates/team-agent/src/test_support/models_timeout_fixture.rs +43 -21
  87. package/crates/team-agent/src/tmux_backend/tests.rs +149 -4
  88. package/crates/team-agent/src/tmux_backend.rs +239 -127
  89. package/crates/team-agent/src/transport.rs +78 -0
  90. package/crates/team-agent/src/transport_factory.rs +53 -0
  91. package/package.json +4 -5
  92. package/skills/team-agent/SKILL.md +45 -17
  93. package/skills/team-agent/command-coverage.json +4 -29
  94. package/npm/check-skill-links.mjs +0 -88
  95. package/skills/team-agent/references/team-agent-operator.md +0 -672
@@ -40,6 +40,7 @@ use team_agent::transport::test_support::OfflineTransport;
40
40
  #[test]
41
41
  #[serial(env)]
42
42
  fn cursor_fresh_spawn_does_not_invent_session_id() {
43
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-fresh");
43
44
  let (ws, _home, _guard, team) = seed_cursor("pending");
44
45
  let transport = OfflineTransport::new();
45
46
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
@@ -79,6 +80,7 @@ fn cursor_fresh_spawn_does_not_invent_session_id() {
79
80
  #[test]
80
81
  #[serial(env)]
81
82
  fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
83
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-resume");
82
84
  let (ws, home, _guard, team) = seed_cursor("resume");
83
85
  let first = OfflineTransport::new();
84
86
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
@@ -180,6 +182,7 @@ fn cursor_classify_resumes_when_archive_present_and_refuses_when_missing() {
180
182
  #[test]
181
183
  #[serial(env)]
182
184
  fn cursor_fork_agent_refuses_as_unsupported_not_unverified() {
185
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-fork");
183
186
  let (ws, _home, _guard, team) = seed_cursor("fork");
184
187
  let transport = OfflineTransport::new();
185
188
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
@@ -0,0 +1,9 @@
1
+ provider model context max-out thinking images
2
+ cursor claude-opus-4-7@1m:fast 1M 16.4K yes yes
3
+ cursor claude-opus-4-7@1m:slow 1M 16.4K yes yes
4
+ openai-codex gpt-5.6-luna 272K 128K yes yes
5
+ team-agent Claude Opus 4.6 (Thinking) 200K 32.8K yes yes
6
+ team-agent Claude Sonnet 4.6 (Thinking) 200K 32.8K yes yes
7
+ team-agent Gemini 3.8 Flash 200K 32.8K yes yes
8
+ team-agent gpt-5.6-luna 200K 32.8K yes yes
9
+ team-agent qwen3.8-27b 262.1K 32.8K yes yes
@@ -518,11 +518,8 @@ fn assert_provider_mcp_table(case: &Case, provider: Provider, clone_id: &str) {
518
518
  }
519
519
  Provider::CursorAgent => {
520
520
  apply_cursor_mcp_overlay(&overlay_root, &cfg).expect("cursor overlay");
521
- let text = std::fs::read_to_string(
522
- team_agent::lifecycle::cursor_mcp_json_path(&overlay_root, clone_id)
523
- .expect("iso path"),
524
- )
525
- .expect("cursor mcp.json");
521
+ let text = std::fs::read_to_string(overlay_root.join(".cursor/mcp.json"))
522
+ .expect("cursor mcp.json");
526
523
  assert!(
527
524
  text.contains("\"team_orchestrator\""),
528
525
  "cursor MCP injection must keep team_orchestrator; text={text}"
@@ -21,6 +21,13 @@ mod hermetic_guard;
21
21
  #[allow(dead_code)]
22
22
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
23
23
 
24
+ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
25
+ let env = hermetic_guard::HermeticTestEnv::enter(tag);
26
+ env.scrub_tmux();
27
+ env.assert_no_real_tmux();
28
+ env
29
+ }
30
+
24
31
  use std::path::{Path, PathBuf};
25
32
  use std::sync::atomic::{AtomicU64, Ordering};
26
33
 
@@ -31,6 +38,7 @@ use team_agent::transport::test_support::OfflineTransport;
31
38
  #[test]
32
39
  #[serial(env)]
33
40
  fn grok_role_effort_medium_reaches_argv() {
41
+ let _hermetic = enter_hermetic("grok-effort-medium");
34
42
  let (ws, _home, _guard, team) = seed_grok("effort-medium", Some("medium"));
35
43
  let transport = OfflineTransport::new();
36
44
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
@@ -45,6 +53,7 @@ fn grok_role_effort_medium_reaches_argv() {
45
53
  #[test]
46
54
  #[serial(env)]
47
55
  fn grok_role_without_effort_emits_zero_effort_flags() {
56
+ let _hermetic = enter_hermetic("grok-effort-absent");
48
57
  let (ws, _home, _guard, team) = seed_grok("effort-absent", None);
49
58
  let transport = OfflineTransport::new();
50
59
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
@@ -60,6 +69,7 @@ fn grok_role_without_effort_emits_zero_effort_flags() {
60
69
  #[test]
61
70
  #[serial(env)]
62
71
  fn grok_restart_keeps_declared_effort_medium() {
72
+ let _hermetic = enter_hermetic("grok-effort-restart");
63
73
  let (ws, _home, _guard, team) = seed_grok("effort-restart", Some("medium"));
64
74
  let first = OfflineTransport::new();
65
75
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &first)
@@ -14,6 +14,13 @@ mod hermetic_guard;
14
14
  #[allow(dead_code)]
15
15
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
16
16
 
17
+ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
18
+ let env = hermetic_guard::HermeticTestEnv::enter(tag);
19
+ env.scrub_tmux();
20
+ env.assert_no_real_tmux();
21
+ env
22
+ }
23
+
17
24
  use std::path::{Path, PathBuf};
18
25
  use std::sync::atomic::{AtomicU64, Ordering};
19
26
 
@@ -183,7 +190,7 @@ fn sample_mcp_config(agent_id: &str, workspace: &str) -> McpConfig {
183
190
  fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
184
191
  let ws = tmp_dir("grok-cwd-coexist");
185
192
  let home = tmp_dir("grok-cwd-coexist-home");
186
- seed_grok_home(&home);
193
+ seed_grok_home(&home, Some(&ws));
187
194
  let _guard = HomeGuard::set(&home);
188
195
  let team = write_grok_team_agents(&ws, "grokteam", &["g1", "g2"]);
189
196
 
@@ -209,9 +216,10 @@ fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
209
216
  #[test]
210
217
  #[serial(env)]
211
218
  fn leftover_per_seat_key_is_cleared_before_start() {
219
+ let _hermetic = enter_hermetic("grok-overlay-leftover-seat");
212
220
  let ws = tmp_dir("grok-cwd-per-seat");
213
221
  let home = tmp_dir("grok-cwd-per-seat-home");
214
- seed_grok_home(&home);
222
+ seed_grok_home(&home, Some(&ws));
215
223
  let _guard = HomeGuard::set(&home);
216
224
  let grok = ws.join(".grok");
217
225
  std::fs::create_dir_all(&grok).unwrap();
@@ -279,12 +287,46 @@ TEAM_AGENT_WORKSPACE = "/stale-ws"
279
287
  );
280
288
  }
281
289
 
290
+ #[test]
291
+ #[serial(env)]
292
+ fn grok_untrusted_folder_refuses_to_start() {
293
+ let _hermetic = enter_hermetic("grok-overlay-untrusted");
294
+ let ws = tmp_dir("grok-untrusted");
295
+ let home = tmp_dir("grok-untrusted-home");
296
+ seed_grok_home(&home, None);
297
+ let _guard = HomeGuard::set(&home);
298
+ let team = write_grok_team(&ws, "grokteam", "grok_writer");
299
+ let err = quick_start_with_transport_in_workspace(
300
+ &ws,
301
+ &team,
302
+ None,
303
+ true,
304
+ Some("grokteam"),
305
+ &OfflineTransport::new(),
306
+ )
307
+ .expect_err("untrusted folder must not start a grok seat");
308
+ let text = err.to_string();
309
+ assert!(
310
+ text.contains("not trusted") && text.contains("action:"),
311
+ "untrusted-folder error must be actionable; err={text}"
312
+ );
313
+ assert!(
314
+ text.contains("grok --trust") || text.contains("/hooks-trust"),
315
+ "next step must name grok --trust or /hooks-trust; err={text}"
316
+ );
317
+ }
318
+
282
319
  #[test]
283
320
  #[serial(env)]
284
321
  fn grok_missing_login_refuses_to_start() {
285
322
  let ws = tmp_dir("grok-nologin");
286
323
  let home = tmp_dir("grok-nologin-home");
287
324
  std::fs::create_dir_all(home.join(".grok")).unwrap();
325
+ std::fs::write(
326
+ home.join(".grok").join("trusted_folders.toml"),
327
+ format!("[folders.\"{}\"]\ntrusted = true\n", ws.display()),
328
+ )
329
+ .unwrap();
288
330
  let _guard = HomeGuard::set(&home);
289
331
  let err = ensure_grok_login_and_folder_trust(&ws).expect_err("missing auth.json");
290
332
  match err {
@@ -298,29 +340,13 @@ fn grok_missing_login_refuses_to_start() {
298
340
  }
299
341
  }
300
342
 
301
- #[test]
302
- #[serial(env)]
303
- fn grok_login_present_allows_start_without_inspect_or_toml_trust() {
304
- let ws = tmp_dir("grok-login-only");
305
- let home = tmp_dir("grok-login-only-home");
306
- seed_grok_home(&home);
307
- let _guard = HomeGuard::set(&home);
308
-
309
- assert!(!home.join("bin/grok").exists(), "no inspect shim may exist");
310
- assert!(
311
- !home.join(".grok/trusted_folders.toml").exists(),
312
- "no trusted TOML may be required"
313
- );
314
- ensure_grok_login_and_folder_trust(&ws)
315
- .expect("valid login alone must allow Grok TUI to own folder consent");
316
- }
317
-
318
343
  #[test]
319
344
  #[serial(env)]
320
345
  fn grok_spawn_writes_resolved_team_agent_into_project_grok_config() {
346
+ let _hermetic = enter_hermetic("grok-overlay-spawn");
321
347
  let ws = tmp_dir("grok-mcp-overlay");
322
348
  let home = tmp_dir("grok-mcp-overlay-home");
323
- seed_grok_home(&home);
349
+ seed_grok_home(&home, Some(&ws));
324
350
  let _guard = HomeGuard::set(&home);
325
351
  let team = write_grok_team(&ws, "grokteam", "grok_writer");
326
352
  let config_path = ws.join(".grok").join("config.toml");
@@ -435,40 +461,37 @@ fn write_grok_team_agents(ws: &Path, team_key: &str, agent_ids: &[&str]) -> Path
435
461
  team
436
462
  }
437
463
 
438
- fn seed_grok_home(home: &Path) {
464
+ fn seed_grok_home(home: &Path, trusted_cwd: Option<&Path>) {
439
465
  let grok = home.join(".grok");
440
466
  std::fs::create_dir_all(&grok).unwrap();
441
467
  std::fs::write(grok.join("auth.json"), r#"{"test":"ok"}"#).unwrap();
468
+ if let Some(cwd) = trusted_cwd {
469
+ std::fs::write(
470
+ grok.join("trusted_folders.toml"),
471
+ format!("[folders.\"{}\"]\ntrusted = true\n", cwd.display()),
472
+ )
473
+ .unwrap();
474
+ }
442
475
  }
443
476
 
444
477
  struct HomeGuard {
445
- prev_home: Option<String>,
446
- prev_path: Option<String>,
478
+ prev: Option<String>,
447
479
  }
448
480
 
449
481
  impl HomeGuard {
450
482
  fn set(home: &Path) -> Self {
451
- let prev_home = std::env::var("HOME").ok();
452
- let prev_path = std::env::var("PATH").ok();
483
+ let prev = std::env::var("HOME").ok();
453
484
  std::env::set_var("HOME", home);
454
- std::env::set_var("PATH", home.join("bin"));
455
- Self {
456
- prev_home,
457
- prev_path,
458
- }
485
+ Self { prev }
459
486
  }
460
487
  }
461
488
 
462
489
  impl Drop for HomeGuard {
463
490
  fn drop(&mut self) {
464
- match &self.prev_home {
491
+ match &self.prev {
465
492
  Some(value) => std::env::set_var("HOME", value),
466
493
  None => std::env::remove_var("HOME"),
467
494
  }
468
- match &self.prev_path {
469
- Some(value) => std::env::set_var("PATH", value),
470
- None => std::env::remove_var("PATH"),
471
- }
472
495
  }
473
496
  }
474
497
 
@@ -13,6 +13,13 @@ mod hermetic_guard;
13
13
  #[allow(dead_code)]
14
14
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
15
15
 
16
+ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
17
+ let env = hermetic_guard::HermeticTestEnv::enter(tag);
18
+ env.scrub_tmux();
19
+ env.assert_no_real_tmux();
20
+ env
21
+ }
22
+
16
23
  use std::path::{Path, PathBuf};
17
24
  use std::sync::atomic::{AtomicU64, Ordering};
18
25
 
@@ -91,6 +98,7 @@ fn grok_role_missing_model_refuses_to_start() {
91
98
  #[test]
92
99
  #[serial(env)]
93
100
  fn grok_role_explicit_model_reaches_argv() {
101
+ let _hermetic = enter_hermetic("grok-explicit-model");
94
102
  let ws = tmp_dir("grok-with-model");
95
103
  let home = tmp_dir("grok-with-model-home");
96
104
  seed_grok_home(&home, Some(&ws));
@@ -124,7 +132,9 @@ fn grok_role_explicit_model_reaches_argv() {
124
132
 
125
133
  /// 3. claude 角色缺 model ⇒ 行为不变(仍能启动,不被 grok 的硬性 model 要求误伤)。
126
134
  #[test]
135
+ #[serial(env)]
127
136
  fn claude_role_missing_model_still_starts() {
137
+ let _hermetic = enter_hermetic("grok-claude-missing-model");
128
138
  let ws = tmp_dir("claude-no-model");
129
139
  let team = write_role_team(
130
140
  &ws,
@@ -21,6 +21,13 @@ mod hermetic_guard;
21
21
  #[allow(dead_code)]
22
22
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
23
23
 
24
+ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
25
+ let env = hermetic_guard::HermeticTestEnv::enter(tag);
26
+ env.scrub_tmux();
27
+ env.assert_no_real_tmux();
28
+ env
29
+ }
30
+
24
31
  use std::path::{Path, PathBuf};
25
32
  use std::sync::atomic::{AtomicU64, Ordering};
26
33
 
@@ -35,6 +42,7 @@ use team_agent::transport::test_support::OfflineTransport;
35
42
  #[test]
36
43
  #[serial(env)]
37
44
  fn grok_fresh_spawn_binds_pending_session_id_to_argv_session_id() {
45
+ let _hermetic = enter_hermetic("grok-resume-pending");
38
46
  let (ws, _home, _guard, team) = seed_grok("pending");
39
47
  let transport = OfflineTransport::new();
40
48
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
@@ -68,6 +76,7 @@ fn grok_fresh_spawn_binds_pending_session_id_to_argv_session_id() {
68
76
  #[test]
69
77
  #[serial(env)]
70
78
  fn grok_restart_uses_resume_when_session_captured_and_archive_present() {
79
+ let _hermetic = enter_hermetic("grok-resume-captured");
71
80
  let (ws, home, _guard, team) = seed_grok("resume");
72
81
  let first = OfflineTransport::new();
73
82
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &first)
@@ -229,7 +229,7 @@ got {status:?}"
229
229
  #[test]
230
230
  #[serial(t3_event)]
231
231
  fn t3_unknown_business_status_is_preserved_without_normalized_event() {
232
- let harness = McpSimHarness::new_without_tmux();
232
+ let harness = McpSimHarness::new();
233
233
  let mut worker = harness.spawn_mcp_client("worker_a", "teamA");
234
234
  let status = "garbage-status-xyz";
235
235
  let call = worker.call_tool(
@@ -423,7 +423,8 @@ check behind it = false green); it must reflect an actual doctor result"
423
423
  /// unparseable / unsuccessful, the hint must not default to Present (auth misreported
424
424
  /// as available). PATH-shims a `claude` that prints garbage and exits non-zero.
425
425
  /// Mutates the process-global PATH/HOME — must not race host_cotenant's ps/tmux
426
- /// shims (P0 pid-probe isolation), hence `#[serial(pid_probe)]`.
426
+ /// shims (P0 pid-probe isolation) or other env-mutating tests, hence
427
+ /// `#[serial(env, pid_probe)]`.
427
428
  #[test]
428
429
  #[serial(env, pid_probe)]
429
430
  fn t3_claude_auth_hint_parse_failure_is_not_present() {
@@ -1846,171 +1846,3 @@ fn lanea_fork_rollback_complete_on_post_spawn_failure() {
1846
1846
  // Golden contract (operations.py:384-394): a post-spawn failure kills the spawned window, restores
1847
1847
  // the old spec text + prior state, and runs cleanup_mcp before re-raising.
1848
1848
  }
1849
-
1850
- #[test]
1851
- fn schema_invalid_pi_role_is_rejected_before_normal_and_force_mutation() {
1852
- let ws = lanea_ws_agents(json!({
1853
- "alpha": { "status": "stopped", "provider": "codex", "window": "alpha" },
1854
- "bravo": { "status": "running", "provider": "codex", "window": "bravo" }
1855
- }));
1856
- let tx = LaneTransport::new("team-laneateam", &[]);
1857
- seed_healthy_coordinator(&ws);
1858
- let role_dir = ws.join("role-masters");
1859
- std::fs::create_dir_all(&role_dir).unwrap();
1860
- let role = role_dir.join("invalid-pi.md");
1861
- std::fs::write(
1862
- &role,
1863
- CHARLIE_ROLE
1864
- .replace(
1865
- "provider: codex\nmodel: gpt-5.5",
1866
- "provider: pi\nmodel: openai-codex/gpt-5.6-sol",
1867
- )
1868
- .replace(" - mcp_team", " - fs_read"),
1869
- )
1870
- .unwrap();
1871
-
1872
- let spec_path = selected_spec_path(&ws);
1873
- let state_path = crate::state::persist::runtime_state_path(&ws);
1874
- let event_path = ws.join(".team/logs/events.jsonl");
1875
- let copied_role = ws.join("agents/charlie.md");
1876
- let team_state_path = ws.join("team_state.md");
1877
- let charlie_paths = crate::lifecycle::launch::pi_mcp::pi_seat_paths(
1878
- &ws,
1879
- "laneateam",
1880
- "charlie",
1881
- );
1882
- let alpha_paths =
1883
- crate::lifecycle::launch::pi_mcp::pi_seat_paths(&ws, "laneateam", "alpha");
1884
- let spec_before = std::fs::read(&spec_path).unwrap();
1885
- let state_before = std::fs::read(&state_path).unwrap();
1886
- let role_before = std::fs::read(&role).unwrap();
1887
- let alpha_before = serde_json::from_slice::<serde_json::Value>(&state_before).unwrap()
1888
- ["agents"]["alpha"]
1889
- .clone();
1890
- for path in [
1891
- &event_path,
1892
- &copied_role,
1893
- &team_state_path,
1894
- &charlie_paths.wrapper,
1895
- &charlie_paths.sessions,
1896
- &alpha_paths.wrapper,
1897
- &alpha_paths.sessions,
1898
- ] {
1899
- assert!(!path.exists(), "precondition: {} must be absent", path.display());
1900
- }
1901
-
1902
- let assert_schema_error = |error: crate::lifecycle::LifecycleError| match error {
1903
- crate::lifecycle::LifecycleError::Compile(message) => {
1904
- assert!(message.contains("Pi roles require mcp_team"), "{message}");
1905
- }
1906
- other => panic!("expected Pi role schema error, got {other:?}"),
1907
- };
1908
- let assert_unchanged = |expected_role: &[u8]| {
1909
- assert_eq!(spec_before, std::fs::read(&spec_path).unwrap());
1910
- assert_eq!(state_before, std::fs::read(&state_path).unwrap());
1911
- assert_eq!(expected_role, std::fs::read(&role).unwrap());
1912
- for path in [
1913
- &event_path,
1914
- &copied_role,
1915
- &team_state_path,
1916
- &charlie_paths.wrapper,
1917
- &charlie_paths.sessions,
1918
- &alpha_paths.wrapper,
1919
- &alpha_paths.sessions,
1920
- ] {
1921
- assert!(!path.exists(), "preflight created {}", path.display());
1922
- }
1923
- let state = crate::state::persist::load_runtime_state(&ws).unwrap();
1924
- assert_eq!(state["agents"]["alpha"], alpha_before);
1925
- assert!(state["agents"].get("charlie").is_none(), "no reservation row");
1926
- assert!(tx.spawns().is_empty());
1927
- assert!(tx.killed().is_empty());
1928
- };
1929
-
1930
- let mut normal_calls = 0;
1931
- let mut normal_discover = |_requested: &str| {
1932
- normal_calls += 1;
1933
- Ok(Vec::new())
1934
- };
1935
- let normal = crate::lifecycle::add_agent_with_transport_pi_preflight(
1936
- &ws,
1937
- &aid("charlie"),
1938
- &role,
1939
- false,
1940
- None,
1941
- &tx,
1942
- &mut normal_discover,
1943
- )
1944
- .expect_err("invalid Pi schema must fail before reservation");
1945
- drop(normal_discover);
1946
- assert_eq!(normal_calls, 0);
1947
- assert_schema_error(normal);
1948
- assert_unchanged(&role_before);
1949
-
1950
- let mut force_calls = 0;
1951
- let mut force_discover = |_requested: &str| {
1952
- force_calls += 1;
1953
- Ok(Vec::new())
1954
- };
1955
- let force = crate::lifecycle::add_agent_with_transport_force_pi_preflight(
1956
- &ws,
1957
- &aid("alpha"),
1958
- &role,
1959
- false,
1960
- None,
1961
- true,
1962
- &tx,
1963
- &mut force_discover,
1964
- )
1965
- .expect_err("invalid Pi replacement schema must fail before old-seat consumption");
1966
- drop(force_discover);
1967
- assert_eq!(force_calls, 0);
1968
- assert_schema_error(force);
1969
- assert_unchanged(&role_before);
1970
-
1971
- std::fs::write(
1972
- &role,
1973
- CHARLIE_ROLE.replace(
1974
- "provider: codex\nmodel: gpt-5.5",
1975
- "provider: pi\nmodel: gpt-5.6-sol",
1976
- ),
1977
- )
1978
- .unwrap();
1979
- let model_role_before = std::fs::read(&role).unwrap();
1980
- let assert_model_diagnostic = |error: crate::lifecycle::LifecycleError| match error {
1981
- crate::lifecycle::LifecycleError::PiModelPreflight {
1982
- requested,
1983
- candidates,
1984
- not_ready,
1985
- ..
1986
- } => {
1987
- assert_eq!(requested, "gpt-5.6-sol");
1988
- assert_eq!(candidates, vec!["openai-codex/gpt-5.6-sol"]);
1989
- assert!(!not_ready);
1990
- }
1991
- other => panic!("expected typed Pi preflight error, got {other:?}"),
1992
- };
1993
- for (force, agent_id) in [(false, "charlie"), (true, "alpha")] {
1994
- let mut calls = 0;
1995
- let mut discover = |requested: &str| {
1996
- calls += 1;
1997
- assert_eq!(requested, "gpt-5.6-sol");
1998
- Ok(vec!["openai-codex/gpt-5.6-sol".into()])
1999
- };
2000
- let error = crate::lifecycle::add_agent_with_transport_force_pi_preflight(
2001
- &ws,
2002
- &aid(agent_id),
2003
- &role,
2004
- false,
2005
- None,
2006
- force,
2007
- &tx,
2008
- &mut discover,
2009
- )
2010
- .expect_err("unqualified Pi model must fail before mutation");
2011
- drop(discover);
2012
- assert_eq!(calls, 1);
2013
- assert_model_diagnostic(error);
2014
- assert_unchanged(&model_role_before);
2015
- }
2016
- }